VERC: Dynamically Resizing Models Last edited 16 years ago2003-03-03 17:26:00 UTC by Penguinboy Penguinboy

This article was recovered from an archive and needs to be reviewed

  1. The formatting may be incorrect as it was automatically converted to WikiCode from HTML, it needs to be revised and reformatted
  2. Some information may be out of date as it was written before Half-Life was available on Steam
  3. After the article is re-formatted and updated for Steam HL, remove this notice
  4. Please do not remove the archive notice from the bottom of the article.
  5. Some archive articles are no longer useful, or they duplicate information from other tutorials and entity guides. In this case, delete the page after merging any relevant information into other pages. Contact an admin to delete a page.
In the client.dll source code, there's a file called StudoModelRenderer.cpp . It handles the drawing of all the .mdl models in the game.

Each time the engine wants to draw a model, it calls the function StudioDrawModel . This function then works out where the model is, where all the bones of its skeleton should go, whether there are any special effects that need applying, and finally draws it.

Needless to say, having access to all this code gives us a great opportunity to do special effects!

For this article, I'll be looking at something simple - scaling. I added the following code at the very end of StudioSetupTransform (a function in StudioModelRenderer.cpp which decides where the model will be drawn on the screen):

if (m_pCurrentEntity->curstate.scale != 0){     int j;     for (i = 0; i < 3; i++)          for (j = 0; j < 3; j++)          {               (*m_protationmatrix)[i][j] *=                    m_pCurrentEntity->curstate.scale;          }     }}
That's it. Add this code, compile, and you've finished! To test it, you just need to make a monster in Worldcraft, turn off Smartedit and give it a "scale" setting of 2.0 (double size) or 0.5 (half size), or whatever.

So, how does this actually work?

m_pCurrentEntity->curstate is the current state of the entity we're supposed to be drawing. The "scale" variable is one of the properties that make up that state. (I didn't create it - it's been there all along, although previously it was only used by sprites. I'm using it here because having a pre-existing variable makes the example nice and simple. And it seems sensible to reuse it for such a similar purpose.)

So, that's the simple stuff out of the way. What are these "for" loops doing? Multiplying up the numbers in m_protationmatrix .

To understand that, we need to talk a little bit about matrices. Yeah, matrices - the seemingly pointless things you hated (or, for the younger audience, will hate) in maths lessons. Well, this is the moment where you get to learn what matrices are actually for !

A matrix is a rectangular grid of numbers. m_protationmatrix is a matrix with 4 columns and 3 rows, and its 12 numbers are used to define exactly where a model will appear on the screen. Don't let the name fool you: it can express all sorts of transformations, not just a rotation. It comes in two parts:

a b c xd e f yg h i z

Numbers in the first three columns are used to scale, rotate and/or skew the model. (I'll explain how in a minute.) Numbers in the right-hand column simply define where the model is in 3d space.

In the StudioSetupTransform function, there's a good example of how the last column can be used - the very first and very last things it does.

void CStudioModelRenderer::StudioSetUpTransform (int trivial_accept){//...some variables...     vec3_t modelpos;     VectorCopy( m_pCurrentEntity->origin, modelpos );//...the rest of the function...     (*m_protationmatrix)[0][3] = modelpos[0];     (*m_protationmatrix)[1][3] = modelpos[1];     (*m_protationmatrix)[2][3] = modelpos[2];}
As I hope you can see, this code first saves the entity's origin into the modelpos vector, and at the end, copies that vector directly into the last column of the matrix. Yes, it really is that simple to use.

Sadly, the same isn't true of the other three columns. To understand how those work, let's talk about matrices.

Here's how you multiply a 3-by-3 matrix by a 3d vector, to produce a new 3d vector:
[a b c]   [x]   [?][d e f] * [y] = [?][g h i]   [z]   [?]
[a b c]

2) Multiply the first number in that line by the first number in the vector, multiply the second by the second, and so on.
[ax by cz]

3) Add up the results, and write the answer as the first entry in the result vector.
[ax+by+cz][    ?   ][    ?   ]
[a b c]   [x]   [ax+by+cz][d e f] * [y] = [dx+ey+fz][g h i]   [z]   [gx+hy+iz]
So for example, with a matrix like this...

[1 0 0][1 0 0][1 0 0]

...each line says "stir in 100% of the vector's x value, and 0% of the other two values". Let's see how that works on a vector containing a bunch of random numbers:
[1 0 0]   [453]   [1*453 + 0*7 + 0*99]   [453][1 0 0] * [  7] = [1*453 + 0*7 + 0*99] = [453][1 0 0]   [ 99]   [1*453 + 0*7 + 0*99]   [453]
Now, here's a related one:

[1 0 0][0 1 0][0 0 1]

Here, the first line says "use 100% of x, and nothing else". The second line says "use 100% of y, and nothing else". The third line says "use 100% of z, and nothing else".

Guess what the result is -
[1 0 0]   [453]   [1*453 + 0*7 + 0*99]   [453][0 1 0] * [  7] = [0*453 + 1*7 + 0*99] = [  7][0 0 1]   [ 99]   [0*453 + 0*7 + 1*99]   [ 99]
Ok. How about if we wanted to rearrange the components of a vector? (don't ask me why...)
[0 1 0]   [453]   [0*453 + 1*7 + 0*99]   [  7][0 0 1] * [  7] = [0*453 + 0*7 + 1*99] = [ 99][1 0 0]   [ 99]   [1*453 + 0*7 + 0*99]   [453]
[2 0 0]   [453]   [2*453 + 0*7 + 0*99]   [906][0 2 0] * [  7] = [0*453 + 2*7 + 0*99] = [ 14][0 0 2]   [ 99]   [0*453 + 0*7 + 2*99]   [198]
So, two pages later, we finally get to see why the scaling code works. Half-Life supplies me with a matrix, I multiply each entry by the scale factor, and as a result, when the model gets drawn, each bit is scaled up! (note that the scaling will be relative to its origin - which for most monsters is at its feet, but not for all).

I hope this was helpful. I'll go into more interesting effects in a future article - assuming people are interested...
This article was originally published on the Valve Editing Resource Collective (VERC).
TWHL only archives articles from defunct websites. For more information on TWHL's archiving efforts, please visit the TWHL Archiving Project page.


You must log in to post a comment. You can login or register a new account.