J |
ust about everyone has played, or at least seen, Half-Life. This groundbreaking game from VALVe software became an instant success, eventually spawning the insanely popular counter-terrorism mod Counter-Strike (www. counter-strike. net). Figure 8.1 shows Half-Life in action.
Figure 8.1 VALVe software’s legendary Half-Life in action. Half-Life rocked the gaming world with its then groundbreaking graphics, AI, and of course its realistic skeletal animations. Here, a scary alien slave readies a deadly blast of electricity. |
Not only did Half-Life offer a refreshing change of pace from the rush — around-with-guns-blazing 3D games of the time, it was quite revolutionary for its time. Not only was it one of the first mainstream games to incorporate skeletal animation, it had an intriguing story, scripted sequences, and great graphics to boot. Best of all, it even ran on my old 133MHz computer.
In my opinion, I think this is another great format to use due to the popularity of the game. Because of this, there are a great number of free models available at places such as PolyCount (http:// www. polycount. com) and FilePlanet (www. fileplanet. com), as well as a large number of people creating new data for various modifications.
This chapter takes a slightly different approach. Instead of writing a full loader from scratch, you will be using code files prepared by VALVe software. These files are part of the Half-Life SDK. The reason for this is that the Half-Life format is very, very complex.
Using the files from VALVe’s MDL viewer requires a little tweaking before they will work with your basecode. The first thing to do is go into mdlviewer. cpp and gut out all of the functions. These are the initialization and main loop functions for a standalone viewer. Because you are going to be using your own main loop and your own basecode, their main loop and basecode has to go. The main() function and all of its associated parts must be removed to make way for your own.
Also, any references to the glut library (glut. h and glut**) need to be removed. This was also left in for the standalone viewer. All functions that begin with “glut”, such as glutMainLoop() and glutInputfunc, need to be removed. After these functions are gone, you can remove glut. h as well.
This has already been done for you in the included files, found on the CD in the Code/Chapter 8/hl_src directory. To use the MDL loading code, you need the following files (all included on the CD with the demo in the hl_src folder of the demo):
Code/Chapter 8/hl_src /math. cpp Code/Chapter 8/hl_src /mathlib. h Code/Chapter 8/hl_src /mdlviewer. cpp Code/Chapter 8/hl_src /mdlviewer. h Code/Chapter 8/hl_src /studio. h Code/Chapter 8/hl_src /studio_renderer. cpp Code/Chapter 8/hl_src /studio_utils. cpp
Once all these files are included into your project (copy the hl_src directory into your project directory), you are ready to start working.
First things first, let’s load the model and get it set up. First of all you need an instance of the StudioModel class to work with. StudioModel is the name of the class containing everything to do with the models, just as CMd2, Cms3d, and others were in previous chapters.
Loading the model is simple. Using the Init function of your StudioModel instance, you pass it a filename. The class takes care of loading everything from there. It doesn’t get much simpler than that! Here is the actual code to initialize the model file "pirate. mdl":
g_MDL. Init("pirate. mdl");
Before you can jump right to rendering, there are several more pieces you must initiate. First is the animation sequence. An animation sequence in the Half-Life MDL files contains one “action”. This may be running, jumping, crouching, or any other action.
Using the SetSequence function, you can set the current animation sequence. Because it’s the beginning of the program, I set it to 0 in my code:
//Set the current animation sequence to 0 g_MDL. SetSequence(0);
Next you need to initialize the programmable bone controllers. These controllers provide a bit of extra functionality to the model. Using the SetController function, I set all four of the controllers to 0.0. I recommend that you play with the values just to see what they do; maybe a great idea will come to your mind.
//Set all bone controllers to 0 g_MDL. SetController(0, 0.0); g_MDL. SetController(1, 0.0); g_MDL. SetController(2, 0.0); g_MDL. SetController(3, 0.0);
Last, the mouth position is set. The SetMouth function, as you might imagine, effects the position of the model’s mouth. By modifying this value during the game you can create a model that appears to be speaking. Done carefully, it is even possible to get the lips synced with the actor’s voice:
//Set the mouth position to 0 g_MDL. SetMouth(0);
Now the minute you have all been waiting for! Rendering. Displaying the MDLs onscreen is nearly as easy as loading them. A simple call to DrawModel puts the model onto the screen. Just calling DrawModel, however, will not produce any animation. Here’s the code:
// Draw the model without any animation g_MDL. DrawModel();
To actually animate the model, you need a timer. The AdvanceFrames function of the StudioModel class takes the change in time from the last frame to the current one. Using the CTimer class from the basecode makes this really easy to find. Calling the GetSeconds function returns the number of seconds that have elapsed since it was last called. By calling this function and feeding the results into the AdvanceFrame function, you animate the model. You can even vary the speed of the animation by multiplying the elapsed time by a speed value. A speed value of 1.0 gives you the original speed, 2.0 gives you twice the speed, and so on. Be sure to call the DrawModel function sometime after the AdvanceFrame call or you will get a black screen. Here is the code that uses a CTimer function to retrieve the number of seconds since the last frame—it then uses that value in the Animate function:
//Get the number of seconds that have elapsed since the last frame //Multiplying this value by a speed multiplier will slow // down or speed up the animation of the model. float fSec = g_Timer. GetSeconds() * fTimeMult;
//Advance the model’s animation using the value you just calculated g_MDL. AdvanceFrame(fSec);
//Draw the model at its new position g_MDL. DrawModel();
That was pretty easy wasn’t it? Just for kicks, Figure 8.2 shows the MDL loader in action.
If you feel up to looking at VALVe’s code and deciphering parts of the format yourself, try these projects:
■ Extract and replace the internal textures.
■ Extract the skeleton and replace it with one for a file (decompile it).
■ Create your own format out of it by dropping out everything you don’t need.
■ Figure out how to use the attachments (hint, look at md3 first).
Overall, MDL is an interesting, but very complicated, format. It contains data to do almost everything under the sun. From blending animations, to weapons attachment, to embedded textures, MDL has it all.
Enter the MDL format at your own risk 🙂
Although this is a short introduction to such a powerful format, it’s a good start to help you use it within your own games and programs. VALVe created an extremely powerful, but also an extremely complex, file format. The bone and mouth controllers can provide very specific control over the model. Using the mouth controller, you can set up a rudimentary lip-syncing system without creating a new format or changing the core code that actually loads the model.
The next chapter covers id Software’s MD3 format. The MD3 format is another very popular format due to its use in id Software’s own Quake III as well as many games that use the Quake III engine licensed from id. MD3 is another good format to use in your games. Read on!