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".
[bquote]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.[/bquote]
[size="5"]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.
[size="5"]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:
[size="3"]Headerxof 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
}
}
}
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.
[bquote]You can convert between the txt and bin format with the help of convx.exe, which is located in d:\mssdk\DXUtils\Xfiles.[/bquote]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.
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 (#).template Header {
<3D82AB43-62DA-11cf-AB39-0020AF71E433>
WORD major;
WORD minor;
DWORD flags;
}
[bquote]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.[/bquote][size="3"]Mesh
Most of this sample file consists of a mesh template and its integrated templates:
The first number is the number of vertices used. After that, the vertices are set.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 front face uses four vertices consisting of two triangles. There's a front and a back face.
[bquote]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.[/bquote]You can use triangles or quad info in MeshFace:
The Mesh template is an open template, because it uses open brackets [...] at the end.// 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
[size="3"]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:
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.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 {
DWORD nMaterials;
DWORD nFaceIndexes;
array DWORD faceIndexes[nFaceIndexes];
[Material]
}
Beneath that array, Material templates could be integrated. So the MeshMaterialList template is a restricted template, because only specified templates could be integrated.
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: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;
}
[font="Courier New"][color="#000080"]{GreenMat}[/color][/font] is the reference for the Material Object.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}
}
}
[size="3"]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:
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: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 {
DWORD nNormals;
array Vector normals[nNormals];
DWORD nFaceNormals;
array MeshFace faceNormals[nFaceNormals];
}
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.
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:
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 :-).
[size="3"]Textures
Textures could be referenced in a TextureFilename Data Object as child objects of the Material Object:
The reference to the texture filename has to be implemented in the Material Object like this:template TextureFilename {
STRING filename;
}
Additionally you need to specify texture coordinates: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";}
}
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:template MeshTextureCoords {
DWORD nTextureCoords;
array Coords2d textureCoords[nTextureCoords];
}
template Coords2d {
FLOAT u;
FLOAT v;
}
If you'd like to map one texture on the whole square you have to use
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.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
}
To get four textures, use the following coordinates
The complete file square3.x looks like this: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
}
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
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
}
}
[size="3"]Transformation Matricesxof 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
}
}
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:
The templates used in this sample in rmxftmpl.x are: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;,
...
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:template Frame {
<3D82AB46-62DA-11cf-AB39-0020AF71E433>
[...]
}
template FrameTransformMatrix {
Matrix4x4 frameMatrix;
}
template Matrix4x4 {
array FLOAT matrix[16];
}
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
}
}
}
[size="5"]The Framework X File Class
The Direct3D 7 IM Framework provides you the following interface class:
These methods are exported by the Framework. The [font="Courier New"][color="#000080"]GetMeshVertices()[/color][/font] method retrieves the vertices for a specified mesh by traversing its hierarchy of frames and meshes, whereas [font="Courier New"][color="#000080"]GetMeshIndices()[/color][/font] 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 [font="Courier New"][color="#000080"]InitDeviceObjects()[/color][/font] and call [font="Courier New"][color="#000080"]DrawIndexedPrimitive()[/color][/font] with the array of vertices and indices in the [font="Courier New"][color="#000080"]Render()[/color][/font] method.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();
};
[bquote]Check out the second tutorial First Steps to Animations to learn more about indexed primitives.[/bquote][font="Courier New"][color="#000080"]FindObject()[/color][/font] returns the named meshes by searching through all meshes a in file object, whereas [font="Courier New"][color="#000080"]EnumObjects()[/color][/font] enumerates all objects in the file. It's also used by [font="Courier New"][color="#000080"]FindObjects()[/color][/font]. [font="Courier New"][color="#000080"]Scale()[/color][/font] scales a mesh with the help of ScaleMeshCB(). [font="Courier New"][color="#000080"]Load()[/color][/font] creates a hierarchy of frames and meshes and loads the X file with [font="Courier New"][color="#000080"]ParseFrame()[/color][/font] and [font="Courier New"][color="#000080"]ParseMesh()[/color][/font], which enumerate all the child objects. There are two [font="Courier New"][color="#000080"]Render()[/color][/font] 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:
The class CD3DFile uses this class to solve a lot of its tasks. The [font="Courier New"][color="#000080"]CD3DFile::Render()[/color][/font] method will call [font="Courier New"][color="#000080"]CD3DFileObject::Render()[/color][/font] to render the object. We don't worry about it here, because it's used internally by CD3DFile.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();
};
[bquote]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.[/bquote]Well, now that you know the Framework interface, we can dive deeper into the code of the sample app.
[size="5"]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.
[size="3"]OneTimeSceneInit()
Loading an object should happen in the [font="Courier New"][color="#000080"]OneTimeSceneInit()[/color][/font] method of your main file:
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: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;
}
You'll find it at the beginning of the objects.cpp file.struct Object
{
CD3DFile* m_pFile;
D3DVECTOR vLoc;
FLOAT fYaw, fPitch, fRoll;
D3DMATRIX matLocal;
};
[size="3"]Render()
To render the X files, a simple call to [font="Courier New"][color="#000080"]CD3DFile::Render()[/color][/font] has to be performed:
Ok, that wasn't difficult....
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);
...
[size="5"]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 [email="wolf@direct3d.net"]wolf@direct3d.net[/email].
[hr]
(C) 1999-2001 [email="wolf@direct3d.net"]Wolfgang Engel[/email], Mainz, Germany