Jump to content

  • Log In with Google      Sign In   
  • Create Account


Like
0Likes
Dislike

Direct3D 7 Immediate Mode Framework Programming 4: Building Worlds with X Files

By Wolfgang Engel | Published Aug 31 2000 03:13 AM in DirectX and XNA

face 000000 material normal file vertice template number connect
If you find this article contains errors or problems rendering it unreadable (missing images or files, mangled code, improper text formatting, etc) please contact the editor so corrections can be made. Thank you for helping us improve this resource

Preface

Attached Image: flyingobjects.gif

To make a game, programmers and graphic designers need to work closely together. 3D characters, models and 3D worlds are build and animated in specialized 3D programs, such as 3D Studio MAX, Maya, Lightwave, trueSpace and so on. After building the graphics with such tools, they have to be imported into the game engine.

So if you'd like to be a game programmer, you need to understand how to load a 3D model or world file generated by external tools into your game engine.

As a sample application, I've modified the sample of the second tutorial First Steps to Animations. So you know most of the source and you can concentrate on the new X file code.

To compile the sample program, you have to link with an additional file: d3dxof.lib. It's used by the Framework D3DFile class. If you'd like to know more on compilation of these tutorials, try to take a look in the first Direct3D.net tutorial "The Basics".

I've found the Mr. Gamemaker article Loading X files" very useful. In addition, the book from Robert L. Dunlop Teach Yourself DirectX 7 in 24 Hours published by Sams has a great chapter on using X files in D3D IM. Paul Bourke has compiled a must have for every X file junkie; a DirectX X File Reference.


3D File Formats


3D scene file formats present two fundamental problems: how to store the objects that will make up the scene and how to store the relationship between these objects.

There have to be user-defined data types. Scene data requires hierarchical relationships such as parent, child and sibling relationships and associational relationships such as meshes being attached to frames, materials being attached to meshes and so on. In addition, a file format design should be robust enough to handle forward and backward compatibility.

The X file format handles all this tasks, so it's a good solution to show the loading of 3D models into a game engine in a tutorial.


X file format

The Direct3D X file format was built for the legacy retained mode of Direct3D and expanded with the advent of DirectX 6 for immediate mode. Let's see on an abstract level how the X file format solves the problems described above:
  • User-defined data types. X files are driven by templates, which can be defined by the user. A template is a definition on how a data object should be stored. The predefined templates are contained in the "rmxftmpl.h" and "rmxftmpl.x" headers (try to pronounce it :-) ) and their indentification signatures in "rmxfguid.h" which are included in d3dfile.cpp, one of the Framework files. See "The Basics" tutorial for more information on the Direct3D 7 IM Framework.
  • Hierarchical relationships. Data types allowed by the template are called optional members. These optional members are saved as children of the data object. The children can be another data object, a reference to an earlier data object, or a binary object.
Ok let's dive into the X file format with a sample: a square. It's located in square.x. Use the DirectX 7 SDK sample app Xfile.exe to play around with the files:

xof 0302txt 0064

Header {
1;
0;
1;
}

// square
Mesh Square {
 // front face and back face
 8;                      	// number of vertices
 1.0; 1.0; 0.0;,         	// vertice 0
-1.0; 1.0; 0.0;,         	// vertice 1
-1.0;-1.0; 0.0;,         	// vertice 2
 1.0;-1.0; 0.0;          	// vertice 3

 1.0; 1.0; 0.0;,         	// vertice 4
 1.0;-1.0; 0.0;,         	// vertice 5
-1.0;-1.0; 0.0;,         	// vertice 6
-1.0; 1.0; 0.0;;         	// vertice 7

 4;                      	// number of triangles
 3;0,1,2;,               	// triangle #1
 3;0,2,3;,               	// triangle #2
 3;4,5,6;,               	// triangle #3
 3;4,6,7;;               	// triangle #4

 MeshMaterialList {
  1;                     	// one material
  2;                     	// two faces
  0,                     	// face #0 use material #0
  0;;                    	// face #1 use material #0

  Material {             	// material #0
   0.0;1.0;0.0;1.0;;     	// face color
   0.0;                  	// power
   0.0;0.0;0.0;;         	// specular color
   0.0;0.0;0.0;;         	// emissive color
  }
 }
}

Header

xof is the magic 4-byte number. The major version number is 03 and the minor version number is 02. The file format is txt for text or bin for a binary format.

You can convert between the txt and bin format with the help of convx.exe, which is located in d:\mssdk\DXUtils\Xfiles.

Other possible file formats are tzip and bzip, which stands for MSZip compressed text or bin files. The floating point data type is set to the default 0064 instead of 0032. You will find the first template named Header in the third row of square.x. Its declaration is located in the rmxftmpl.x file, like all other templates.

template Header {
 <3D82AB43-62DA-11cf-AB39-0020AF71E433>
 WORD major;
 WORD minor;
 DWORD flags;
}

This template defines the application-specific header for the Direct3D Retained Mode usage of the DirectX file format. Retained Mode uses the major and minor flags to specify the current major and minor versions. We're not using RM here, so we can skip that by commenting it out or deleting it. You might use the Header template for your graphics version control system. Comments are set, as you might see above, with the double slash like in C++ or the hash symbol (#).

Templates consist of four parts. The unique name, which consists of numbers, characters and the underscore. It shouldn't start with a number. The second part is the UUID (the Universally Unique Identifier) and the third part consists of the data types of the entries. The last part regulates the degree of restriction. A template could be open, closed or restricted. Open templates can contain every other data type, closed templates no other data types and restricted templates can only integrate specific other data types.

Mesh

Most of this sample file consists of a mesh template and its integrated templates:

template Mesh {
 <3D82AB44-62DA-11cf-AB39-0020AF71E433>
 DWORD nVertices;
 array Vector vertices[nVertices];
 DWORD nFaces;
 array MeshFace faces[nFaces];
 [...]
}    	 

template Vector {
 <3D82AB5E-62DA-11cf-AB39-0020AF71E433>
 FLOAT x;
 FLOAT y;
 FLOAT z;
}

template MeshFace {
 <3D82AB5F-62DA-11cf-AB39-0020AF71E433>
 DWORD nFaceVertexIndices;
 array DWORD faceVertexIndices[nFaceVertexIndices];
}

The first number is the number of vertices used. After that, the vertices are set.

Attached Image: square.gif

The front face uses four vertices consisting of two triangles. There's a front and a back face.

In Direct3D, only the front side of a face is visible. A front face is one in which vertices are defined in clockwise order. Any face that is not a front face is a back face. Direct3D does not always render back faces; therefore, back faces are said to be culled = Backface culling.

You can use triangles or quad info in MeshFace:

// Instead of .. 

 4;                      	// number of triangles
 3;0,1,2;,               	// triangle #1
 3;0,2,3;,               	// triangle #2
 3;4,5,6;,               	// triangle #3
 3;4,6,7;;               	// triangle #4

// we could use this

 2;                   	// number of quads
 4;0,1,2,3;,          	// quad #1
 4;4,5,6,7;;          	// quad #2

The Mesh template is an open template, because it uses open brackets [...] at the end.

MeshMaterialList

The MeshMaterialList is a child object of the Mesh Object and is incorporated in the Mesh object. The DirectX 7 SDK sample Xfile.exe wouldn't load the file square.x without it. With the MeshMaterialList, you can reference on more than one material. It needs the number of faces and materials and concatenates one face with a material. The template in rmxftmpl.x looks like:

MeshMaterialList {
  1;                     	// one material
  2;                     	// two faces
  0,                     	// face #0 use material #0
  0;;                    	// face #1 use material #0

  Material {             	// material #0
   0.0;1.0;0.0;1.0;;     	// face color
   0.0;                  	// power
   0.0;0.0;0.0;;         	// specular color
   0.0;0.0;0.0;;         	// emissive color
  }
 }
...
template MeshMaterialList {
 <f6f23f42-7686-11cf-8f52-0040333594a3>
 DWORD nMaterials;
 DWORD nFaceIndexes;
 array DWORD faceIndexes[nFaceIndexes];
 [Material]
}

The first variable holds the number of materials used by this sample. The second variable holds the number of faces. The square uses the front and back face, so the variable has to be set to 2. The concatenation of the materials with the faces happens with the faceIndexes array. Every face is concatenated with a material by naming the material number. The X file reader knows the proper face by counting the number of faces provided.

Beneath that array, Material templates could be integrated. So the MeshMaterialList template is a restricted template, because only specified templates could be integrated.

template Material {
 <3D82AB4D-62DA-11cf-AB39-0020AF71E433>
 ColorRGBA faceColor;
 FLOAT power;
 ColorRGB specularColor;
 ColorRGB emissiveColor;
 [...]
}  	 

template ColorRGBA {
 <35FF44E0-6C7C-11cf-8F52-0040333594A3>
 FLOAT red;
 FLOAT green;
 FLOAT blue;
 FLOAT alpha;
}

template ColorRGB {
 FLOAT red;
 FLOAT green;
 FLOAT blue;
}

The Material data object holds the material color, power, specular color and emissive color. The open brackets show that it can integrate other data objects. The Material data object could be referenced by MeshMaterialList. The file square2.x shows a referenced Material Data Object and the use of quads:

xof 0302txt 0064


  Material GreenMat {             	// material #0
   0.0;1.0;0.0;1.0;;
   0.0;
   0.0;0.0;0.0;;
   0.0;0.0;0.0;;
  }

// square
Mesh Square {
 // front face and back face
 8;                      	// number of vertices
 1.0; 1.0; 0.0;,         	// vertice 0
-1.0; 1.0; 0.0;,         	// vertice 1
-1.0;-1.0; 0.0;,         	// vertice 2
 1.0;-1.0; 0.0;          	// vertice 3

 1.0; 1.0; 0.0;,         	// vertice 4
 1.0;-1.0; 0.0;,         	// vertice 5
-1.0;-1.0; 0.0;,         	// vertice 6
-1.0; 1.0; 0.0;;         	// vertice 7

 2;                   	// number of quads
 4;0,1,2,3;,          	// quad #1
 4;4,5,6,7;;          	// quad #2

 MeshMaterialList {
  1;                     	// one material
  2;                     	// tow faces
  0,                     	// face #0 use material #0
  0;;                    	// face #1 use material #0

  {GreenMat}
 }
}

{GreenMat} is the reference for the Material Object.

Normals

Even though Direct3D (since version 7) performs lighting calculations on all vertices, even those without vertex normals, nevertheless you should integrate normals with the MeshNormals template to get the desired lighting, as you'll see in a few seconds:

MeshNormals {
  2;                   	// 2 normals for every face
  0.0; 0.0; 1.0;,      	// normal #1
  0.0; 0.0;-1.0;;      	// normal #2

  2;                   	// two faces
  4;0,0,0;,            	// connect face #1 with normal #1
  4;1,1,1;;            	// connect face #2 with normal #2
 }     	 

template MeshNormals {
 <f6f23f43-7686-11cf-8f52-0040333594a3>
 DWORD nNormals;
 array Vector normals[nNormals];
 DWORD nFaceNormals;
 array MeshFace faceNormals[nFaceNormals];
}

The vertex normal vector is used in Gouraud shading mode (default shading mode since Direct3D 6), to control lighting and made some texturing effects. So what's a shading mode? Shading is the process of performing lighting computations and determining pixel colors from them. These pixel colors will later be drawn on the object. Flat shading lights per polygon or face, Gouraud shading lights per vertex:

Attached Image: gouraud shading.gif
Teapot as a wireframe model, flat shaded model and as a Gouraud shaded model

As you might see, the viewer might see the faces of the teapot with flat shading, but will get the illusion of a round teapot by using the Gouraud model. How does that work? Let's build a more simple example.

Attached Image: normals of triangle.gif

This will appear as a flat triangle when it's shaded with Gouraud shading, because all of the vertex normals point the same way, so all the points in the face in between the vertices get the same lighting. It would look the same with flat shading, because flat shading shades per face. Now we'll change the normals to be non-perpendicular:

Attached Image: changed normals of triangle.gif

With flat shading, nothing would have been changed, because the face hasn't changed. With Gouraud shading, the face appears curved, because the direction of the normals varies from vertex to vertex in a way that suggests the face is curled down at the corners. This is how the teapot is rounded up. And this is also how the object is rounded up. You don't have to do anything in Direct3D to choose the Gouraud shading mode, because it's the default shading mode. So ... no code :-).

Textures

Textures could be referenced in a TextureFilename Data Object as child objects of the Material Object:

template TextureFilename {
 STRING filename;
}

The reference to the texture filename has to be implemented in the Material Object like this:

Material GreenMat {             	// material #0
   0.0;1.0;0.0;1.0;;
   0.0;
   0.0;0.0;0.0;;
   0.0;0.0;0.0;;

   TextureFilename{"wall.bmp";}
  }

Additionally you need to specify texture coordinates:

template MeshTextureCoords {
 <f6f23f40-7686-11cf-8f52-0040333594a3>
 DWORD nTextureCoords;
 array Coords2d textureCoords[nTextureCoords];
}

template Coords2d {
 <f6f23f44-7686-11cf-8f52-0040333594a3>FLOAT u;
 FLOAT v;
}

The first variable holds the number of vertices which have to be used in conjunction with the texture coordinates. The following array holds the tu/tv pairs of the textures. To map a texture to that square, we could use the following texture coordinates:

Attached Image: onetexture.gif

If you'd like to map one texture on the whole square you have to use

MeshTextureCoords {
   4;           	// 4 texture coords
   1.0; 1.0;,   	// coord  0
   1.0; 0.0;,   	// coord  1
   0.0; 0.0;,   	// coord  2
   0.0; 1.0;;   	// coord  3
 }

The bottom right corner is (1.0f, 1.0f) and the upper left corner is (0.0f, 0.0f) regardless of the actual size of the texture. Even if the texture is wider than it is tall.<br clear="all">
Attached Image: fourtextures.gif

To get four textures, use the following coordinates

MeshTextureCoords {
 4;
 // four textures
 1.0; 1.0;,         	// coord  0
-1.0; 1.0;,         	// coord  1
-1.0;-1.0;,         	// coord  2
 1.0;-1.0;;         	// coord  3
}

The complete file square3.x looks like this:

xof 0302txt 0064

 Material GreenMat {             	// material #0
   0.0;1.0;0.0;1.0;;
   0.0;
   0.0;0.0;0.0;;
   0.0;0.0;0.0;;

   TextureFilename{"wall.bmp";}
  }

// square
Mesh Square {
 // front face and back face
 8;                      	// number of vertices
 1.0; 1.0; 0.0;,         	// vertice 0
-1.0; 1.0; 0.0;,         	// vertice 1
-1.0;-1.0; 0.0;,         	// vertice 2
 1.0;-1.0; 0.0;          	// vertice 3

 1.0; 1.0; 0.0;,         	// vertice 4
 1.0;-1.0; 0.0;,         	// vertice 5
-1.0;-1.0; 0.0;,         	// vertice 6
-1.0; 1.0; 0.0;;         	// vertice 7

 2;                   	// number of quads
 4;0,1,2,3;,          	// quad #1
 4;4,5,6,7;;          	// quad #2

 MeshMaterialList {
  1;                     	// one material
  2;                     	// two faces
  0,                     	// face #0 use material #0
  0;;                    	// face #1 use material #0

  {GreenMat}
 }

   MeshTextureCoords {
	8;                 	// 8 texture coords
	1.0; 1.0;,         	// coord  0
	1.0; 0.0;,         	// coord  1
	0.0; 0.0;,         	// coord  2
	0.0; 1.0;;         	// coord  3

	1.0; 1.0;,         	// coords 4
	1.0;-1.0;,         	// coords 5
   -1.0;-1.0;,         	// coords 6
   -1.0; 1.0;;         	// coords 7
   }

 MeshNormals {
  2;                   	// 2 normals for every face
  0.0; 0.0; 1.0;,      	// normal #1
  0.0; 0.0;-1.0;;      	// normal #2

  2;                   	// two faces
  4;0,0,0;,            	// connect face #1 with normal #1
  4;1,1,1;;            	// connect face #2 with normal #2
 }    
}

Xfile.exe blends the material and the texture, so that the texture looks green. This alpha blending effect is described in the Multitexturing tutorial. Textures with the same texture names won't be loaded twice. A flag will handle internally duplicated textures. Ok let's start an exercise: You produce a X file out of the specs of the object in the second tutorial First Steps to Animation. NO cheating ... finished? Ok ... here's my version:

xof 0302txt 0064

Header {
1;
0;
1;
}

// boid
Mesh boid {
 7;                     	// number of vertices
 0.00; 0.00; 0.50;,       	// vertice 0
 0.50; 0.00;-0.50;,       	// vertice 1
 0.15; 0.15;-0.35;,      	// vertice 2
-0.15; 0.15;-0.35;,      	// vertice 3
 0.15;-0.15;-0.35;, 
-0.15;-0.15;-0.35;,
-0.50; 0.00;-0.50;; 

 10;                     	// number of triangles
 3;0,1,2;,               	// triangle #1
 3;0,2,3;,               	// triangle #2
 3;0,3,6;,               	// triangle #3
 3;0,4,1;,               	// ...
 3;0,5,4;,
 3;0,6,5;,
 3;1,4,2;,
 3;2,4,3;,
 3;3,4,5;,
 3;3,5,6;;


 MeshMaterialList {
  1;                     	// one material
  10;                    	// ten faces
  0,0;;                  	// face #1 use material #0
  0,0;;                  	// face #2 use material #0
  0,0;;                  	// face #3 use material #0
  0,0;;                  	// face #4 use material #0
  0,0;;                  	// face #5 use material #0
  0,0;;                  	// face #6 use material #0
  0,0;;                  	// face #7 use material #0
  0,0;;                  	// face #8 use material #0
  0,0;;                  	// face #9 use material #0
  0,0;;                  	// face #10 use material #0

  Material {             	// material #0
   1.0;0.92;0.0;1.0;;
   1.0;
   0.0;0.0;0.0;;
   0.0;0.0;0.0;;
  }
 }

 MeshNormals {
  9;                   	// 9 normals
  0.2; 1.0; 0.0;,      	// normal #1
  0.1; 1.0; 0.0;,      	// normal #2
  0.0; 1.0; 0.0;,      	// normal #3
 -0.1; 1.0; 0.0;,      	// normal #4
 -0.2; 1.0; 0.0;,      	// normal #5
 -0.4; 0.0;-1.0;,      	// normal #6
 -0.2; 0.0;-1.0;,      	// normal #7
  0.2; 0.0;-1.0;,      	// normal #8
  0.4; 0.0;-1.0;;      	// normal #9

  9;                   	// two faces
  4;0,0,0;,            	// connect face #1 with normal #1
  4;1,1,1;,            	// connect face #2 with normal #2  
  4;2,2,2;,            	// connect face #3 with normal #3
  4;3,3,3;,            	// connect face #4 with normal #4
  4;4,4,4;,            	// connect face #5 with normal #5
  4;5,5,5;,            	// connect face #6 with normal #6
  4;6,6,6;,            	// connect face #7 with normal #7
  4;7,7,7;,            	// connect face #8 with normal #8
  4;8,8,8;;            	// connect face #9 with normal #9
 }
}

Transformation Matrices

To transform, for example, parts of an object independently from each other, you have to divide the model into different frames. For example a tank could turn its cannon up and down and to the left or right side. Its chains and wheels will turn when it drives through the wild enemy terrain. So there is a frame for the hull, for the turret, the cannon and one for the chains/wheels. Every frame will hold the matrix for the specified part of the tank. A Frame data object is expected to take the following structure:

Frame Hull {
 FrameTransformMatrix {
  1.000000, 0.000000, 0.000000, 0.000000,
  0.000000, 1.000000, 0.000000, 0.000000,
  0.000000, -0.000000, 1.000000, 0.000000,
  206.093353, -6.400993, -31.132195, 1.000000;;
 }
 Mesh Hull {
   2470;
   41.310013; -26.219450; -113.602348;,
   ...

 Frame Wheels_L {
  FrameTransformMatrix {
   1.000000, 0.000000, 0.000000, 0.000000,
   0.000000, 1.000000, -0.000000, 0.000000,
   0.000000, 0.000000, 1.000000, 0.000000,
   -56.020325, -31.414078, 3.666503, 1.000000;;
  }
  Mesh Wheels_L {

	2513;
	-4.642166; -11.402874; -98.607910;,
...

Frame Wheels_R {
  FrameTransformMatrix {
   1.000000, 0.000000, 0.000000, 0.000000,
   0.000000, 1.000000, -0.000000, 0.000000,
   0.000000, 0.000000, 1.000000, 0.000000,
   56.687805, -31.414078, 3.666503, 1.000000;;
  }
  Mesh Wheels_R {

	2513;
	4.642181; -11.402874; -98.607910;,

Frame Turret {
  FrameTransformMatrix {
   1.000000, 0.000000, 0.000000, 0.000000,
   0.000000, 1.000000, 0.000000, 0.000000,
   0.000000, 0.000000, 1.000000, 0.000000,
   -2.077148, 84.137527, 29.323750, 1.000000;;
  }
  Mesh Turret {

	2152;
	52.655853; -36.225544; -16.728998;,
...

The templates used in this sample in rmxftmpl.x are:

template Frame {
 <3D82AB46-62DA-11cf-AB39-0020AF71E433>
 [...]
}

template FrameTransformMatrix {
 Matrix4x4 frameMatrix;
}

template Matrix4x4 {
 array FLOAT matrix[16];
}

As an old Direct3D.net tutorial freak, you might notice the use of 4x4 Matrices as described in the second tutorial First Steps to Animation. The whole sample is provided in the source package, which you may download with this tutorial. Another simple sample you should look at is the file car.x in the DirectX 7 SDK. And here's an advanced version of the X file of the sample program boidsy2.x:

xof 0302txt 0064

Header {
1;
0;
1;
}

Frame BOID_Root {
 FrameTransformMatrix {
  1.000000, 0.000000, 0.000000, 0.000000,
  0.000000, 1.000000, 0.000000, 0.000000,
  0.000000, 0.000000, 1.000000, 0.000000,
  0.000000, 0.000000, 0.000000, 1.000000;;
 }

// boid
Mesh boid {
 7;                     	// number of vertices
 0.00; 0.00; 0.50;,       	// vertice 0
 0.50; 0.00;-0.50;,       	// vertice 1
 0.15; 0.15;-0.35;,      	// vertice 2
-0.15; 0.15;-0.35;,      	// vertice 3
 0.15;-0.15;-0.35;, 
-0.15;-0.15;-0.35;,
-0.50; 0.00;-0.50;; 

 10;                     	// number of triangles
 3;0,1,2;,               	// triangle #1
 3;0,2,3;,               	// triangle #2
 3;0,3,6;,               	// triangle #3
 3;0,4,1;,               	// ...
 3;0,5,4;,
 3;0,6,5;,
 3;1,4,2;,
 3;2,4,3;,
 3;3,4,5;,
 3;3,5,6;;


 MeshMaterialList {
  1;                     	// one material
  10;                    	// ten faces
  0,0;;                  	// face #1 use material #0
  0,0;;                  	// face #2 use material #0
  0,0;;                  	// face #3 use material #0
  0,0;;                  	// face #4 use material #0
  0,0;;                  	// face #5 use material #0
  0,0;;                  	// face #6 use material #0
  0,0;;                  	// face #7 use material #0
  0,0;;                  	// face #8 use material #0
  0,0;;                  	// face #9 use material #0
  0,0;;                  	// face #10 use material #0

  Material {             	// material #0
   1.0;0.92;0.0;1.0;;
   1.0;
   0.0;0.0;0.0;;
   0.0;0.0;0.0;;
  }
 }

 MeshNormals {
  9;                   	// 9 normals
  0.2; 1.0; 0.0;,      	// normal #1
  0.1; 1.0; 0.0;,      	// normal #2
  0.0; 1.0; 0.0;,      	// normal #3
 -0.1; 1.0; 0.0;,      	// normal #4
 -0.2; 1.0; 0.0;,      	// normal #5
 -0.4; 0.0;-1.0;,      	// normal #6
 -0.2; 0.0;-1.0;,      	// normal #7
  0.2; 0.0;-1.0;,      	// normal #8
  0.4; 0.0;-1.0;;      	// normal #9

  9;                   	// two faces
  4;0,0,0;,            	// connect face #1 with normal #1
  4;1,1,1;,            	// connect face #2 with normal #2  
  4;2,2,2;,            	// connect face #3 with normal #3
  4;3,3,3;,            	// connect face #4 with normal #4
  4;4,4,4;,            	// connect face #5 with normal #5
  4;5,5,5;,            	// connect face #6 with normal #6
  4;6,6,6;,            	// connect face #7 with normal #7
  4;7,7,7;,            	// connect face #8 with normal #8
  4;8,8,8;;            	// connect face #9 with normal #9
  }
 }
}


The Framework X File Class

The Direct3D 7 IM Framework provides you the following interface class:

class CD3DFile
{
  CD3DFileObject*   m_pRoot;

public:
  HRESULT	GetMeshVertices( TCHAR* strName, D3DVERTEX** ppVertices,
                            	DWORD* pdwNumVertices );
  HRESULT	GetMeshIndices( TCHAR* strName, WORD** ppIndices,
                           	DWORD* pdwNumIndices );
    
  CD3DFileObject* FindObject( TCHAR* strName );
  VOID        	EnumObjects( BOOL (*fnCallback)
                           	(CD3DFileObject*,D3DMATRIX*,VOID*),
                           	D3DMATRIX* pmat, VOID* pContext );
  VOID        	Scale( FLOAT fScale );

  HRESULT Load( TCHAR* strFilename );
  HRESULT Render( LPDIRECT3DDEVICE7 );

  CD3DFile();
  ~CD3DFile();
};

These methods are exported by the Framework. The GetMeshVertices() method retrieves the vertices for a specified mesh by traversing its hierarchy of frames and meshes, whereas GetMeshIndices() retrieves the vertices for the specified mesh. Both methods are useful, when, for example, you would like to use your own render method and not the one provided with the framework. To use your own RenderMethod, you might use these in two function calls in InitDeviceObjects() and call DrawIndexedPrimitive() with the array of vertices and indices in the Render() method.

Check out the second tutorial First Steps to Animations to learn more about indexed primitives.

FindObject() returns the named meshes by searching through all meshes a in file object, whereas EnumObjects() enumerates all objects in the file. It's also used by FindObjects(). Scale() scales a mesh with the help of ScaleMeshCB(). Load() creates a hierarchy of frames and meshes and loads the X file with ParseFrame() and ParseMesh(), which enumerate all the child objects. There are two Render() methods in d3dfile.cpp in the Framework source. One is in the class CD3DFileObject and the other one in the class CD3DFile. That brings us to the second class used only internally by the Framework for objects in a .X file. You'll find it in the d3dfile.h:

class CD3DFileObject
{
  ... 
  // lot of stuff
  ...
  // Common functions
  VOID	Render( LPDIRECT3DDEVICE7 pd3dDevice , BOOL bAlpha );
  BOOL	EnumObjects( BOOL (*fnCallback)(CD3DFileObject*,D3DMATRIX*,VOID*),
                   	D3DMATRIX* pmat, VOID* pContext );

  // Constuctor / destructor
  CD3DFileObject( TCHAR* strName );
  ~CD3DFileObject();
};

The class CD3DFile uses this class to solve a lot of its tasks. The CD3DFile::Render() method will call CD3DFileObject::Render() to render the object. We don't worry about it here, because it's used internally by CD3DFile.

These render methods aren't optimal. They support alpha blending, but not other features of Direct3D, especially Multi-Texturing. You might be programming your own X file routines to wring out the last possible byte and cycle of the X file format and Direct3D. So d3dfile.cpp should only serve as an example for your own X file class, which will be optimized based on the needs your game will have. See the tutorial from Mr. Gamemaker at Loading X files for an optimized X file class.

Well, now that you know the Framework interface, we can dive deeper into the code of the sample app.


Down to the Code

You know most of the code from the second tutorial First Steps to Animation, so we can concentrate on the new X file code.

OneTimeSceneInit()

Loading an object should happen in the OneTimeSceneInit() method of your main file:

HRESULT CMyD3DApplication::OneTimeSceneInit()
{
  // yellow object
  m_pObjects[0].vLoc   = D3DVECTOR(-1.0f, 0.0f, 0.0f);
  m_pObjects[0].fYaw   = 0.0f;
  m_pObjects[0].fPitch = 0.0f;
  m_pObjects[0].fRoll  = 0.0f;

  // red object
  m_pObjects[1].vLoc   = D3DVECTOR(1.0f, 0.0f, 0.0f);
  m_pObjects[1].fYaw   = 0.0f;
  m_pObjects[1].fPitch = 0.0f;
  m_pObjects[1].fRoll  = 0.0f;

  HRESULT hr;

  // yellow object
  m_pObjects[0].m_pFile  = new CD3DFile();
    
  hr = m_pObjects[0].m_pFile->Load ("boidy2.x");

  if( FAILED(hr) )
  {
	MessageBox( NULL, TEXT("Error loading boidy.x file"),
            	TEXT("Animation"), MB_OK|MB_ICONERROR);
	return E_FAIL;
  }
    
  // get the X file matrix
  CD3DFileObject* pObject = 
  m_pObjects[0].m_pFile->FindObject( "BOID_Root" );

  if( pObject )
  {
	m_pObjects[0].matLocal = *pObject->GetMatrix();
  }

  // red object
  // the same as above

  return S_OK;
}

After allocating the X file class, we could load the X file and search for the BOID_Root Frame template and later for the FrameTransformMatrix template to get the transformation matrix. That's easy ... isn't it? To store the class pointer in the structure of the object, I've expanded the structure used by the objects like this:

struct Object
{
  CD3DFile*   m_pFile;
  D3DVECTOR   vLoc;
  FLOAT   	fYaw, fPitch, fRoll; 
  D3DMATRIX   matLocal;
};

You'll find it at the beginning of the objects.cpp file.

Render()

To render the X files, a simple call to CD3DFile::Render() has to be performed:

...
if (m_pObjects[0].m_pFile)
  m_pObjects[0].m_pFile->Render( m_pd3dDevice );

// Apply the object's local matrix
m_pd3dDevice->SetTransform(D3DTRANSFORMSTATE_WORLD,
                       	&m_pObjects[0].matLocal);
...

Ok, that wasn't difficult.


Finale

Rumours tell us that the X file support in DirectX 8 will be greatly enhanced. There should be a Maya and a 3D Studio Max X file exporter with source. New X file handling routines will be provided with Direct3D's utility library, Direct3DX. So your time has been well-spent learning the ins and outs of the X file format and the simple interface provided by the Direct3D 7 Framework.

I hope you enjoyed our trip to the X file format. This will be a work in progress in the future. If you find any mistakes or if you have any good ideas to improve this tutorial or if you dislike or like it, give me a sign at wolf@direct3d.net.

 

© 1999-2001 Wolfgang Engel, Mainz, Germany








Comments

Note: Please offer only positive, constructive comments - we are looking to promote a positive atmosphere where collaboration is valued above all else.




PARTNERS