I will assume you have at least some knowledge in liner algebra for the purpose of this article. I won't explain what a dot product * is, what trigonometric functions are, etc.

Here is a figure that shows how models are transformed. Half-Life uses a right-handed coordinate system, and treats x, y, and z components as forward, left, and up directions, respectively.

Note that pitch will increase as we look down.

A

First, we must convert the model's angles to two vectors representing orientation, because the liner algebra doesn't use angles directly. As you may know, the engine provides a function to convert a vector to angles, but not the reverse (there

We chose (1,0,0) and (0,1,0) when the model doesn't rotate as the vectors. Then, we project the vectors to the surface. We can project a vector to a plane using this equation:

`v' = v - (n ? v)n`

where

Finally, we convert the vectors back to angles.

void CBasePlayerItem::FallThink ( void )

{

pev->nextthink = gpGlobals->time + 0.1;

if ( pev->flags & FL_ONGROUND )

{

if ( !FNullEnt( pev->owner ) )

{

int pitch = 95 + RANDOM_LONG(0,29);

EMIT_SOUND_DYN(ENT(pev), ..

}

// lie flat

pev->angles.x = 0;

pev->angles.z = 0;

Materialize();

}

}

You see, this code assumes a surface is always flat. So we begin to comment out the code.

First, we trace a line down to get the normal vector, so we need a TraceResult object to store the result of tracing.

Then, name the orientation vectors "angdir" and "angdiry". They can be easily obtained using the angles (look at the first figure). One notable point is that they are in degrees, so we must convert degrees to radians prior to calling the trigonometric functions. This is simplified by defining a macro like this:

` #define deg2rad (2 * M_PI / 360)`

Project the vectors using tr.vecPlaneNormal and the equation shown above. Change the angles with the projected vectors. UTIL_VecToAngles() sets resulting angle's roll to 0, so we must find it with angdiry.

The final code:

TraceResult tr;

// look down directly to know the surface we're lying.

UTIL_TraceLine( pev->origin,

pev->origin - Vector(0,0,10), ignore_monsters, edict(), &tr );

#ifndef M_PI

#define M_PI 3.14159265358979

#endif

#define ang2rad (2 * M_PI / 360)

if ( tr.flFraction < 1.0 )

{

Vector angdir = Vector(

cos(pev->angles.y * ang2rad) * cos(pev->angles.x * ang2rad),

sin(pev->angles.y * ang2rad) * cos(pev->angles.x * ang2rad),

-sin(pev->angles.x * ang2rad));

Vector angdiry = Vector(

sin(pev->angles.y * ang2rad) * cos(pev->angles.x * ang2rad),

cos(pev->angles.y * ang2rad) * cos(pev->angles.x * ang2rad), -sin(pev->angles.x * ang2rad));

pev->angles = UTIL_VecToAngles(

angdir - DotProduct(angdir, tr.vecPlaneNormal) * tr.vecPlaneNormal);

pev->angles.z = -UTIL_VecToAngles(

angdiry - DotProduct(angdiry, tr.vecPlaneNormal) *

tr.vecPlaneNormal).x;

}

#undef ang2rad

(Some operators (->) may be cut in some browsers by wrapping.)

If you want to make weapons act more realistically (a.l.a. the Svencoop mod), you must do some more tweaking. But it is beyond the scope of this article.

Thanks to Zipster for inspiring me to write this.

( * ) It is one of the wonders of English to me that the terms "dot product" and "cross product" are not less popular than their synonyms, "scalar product" and "vector product", respectively. I honestly never thought "dot product" is a good term, because it has no particular reason except the form, and the form may vary by writer (not necesarilly a dot). Actually, a person I know writes a scalar(dot) product of vectors A and B as "<A,B>", in order to distinguish it from normal "products" in terms of tensors. "Scalar product", on the other hand, is more explicit that it yields scalar quantity and is very different from a vector product. But if it causes less confusion, I settle for it. I don't think I can or I should change the trend.

( ** ) Three values, pitch, yaw, and roll, form rotational matrices around y, z, and x axes, respectively. And then the matrices are multiplied in that order into one matrix. Model vertices are transformed by the matrix from left. So vertex coordinates are rotated in roll, yaw, pitch in that order. It explains why models are transformed in that (like seen in the figure) way.

If the model is to be scaled, a diagonal matrix will be multiplied as well.

( *** ) Mathematically, a normal vector needs not to have a length of 1. But practical computing almost always treats normal vectors to have, for the sake of performance and convenience.

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