Welcome, Guest! Login | Register

Complete Freedom of Rotation [Print this Article]
Posted by: Entropy
Date posted: May 18 2003
User Rating: 5 out of 5.0
Number of views: 7866
Number of comments: 5
Description: Changing view angles with quaternions
The goal of this tutorial is to show you how to modify the Half-Life input code to allow changing the player's view angles around any arbitrary axis by using quaternions to change the view angles. These changes will allow you to create "flight-simulator" type controls (that is, the view rotates in "the same" way no matter what your current angles are, rather than the standard mechanism in which yaw changes always cause rotations around the z (vertical) axis). The uses are not limited to flight simulation, however. This technique will also permit such things as wall-walking or any other non-standard orientation you can dream up.

I'm not going to discuss the mathematics of rotations in general or quaternions in particular, since there are better explanations already out there than I could produce. Discussions of those topics can be found in such places as Gamedev, Mathworld, and other places around the internet (Google is your friend).

The basic idea here is to take the control inputs which normally directly change the pitch, yaw or roll and instead define our own axes around which to rotate the view. For this example, the axes we'll use are the axes defined by the current view angles, which will result in "flight-sim-like" behavior. The first order of business is to change the input code to remove all the direct references to the pitch, yaw and roll, and save all those changes to apply later. To do that, we'll add an extra parameter, deltaAngles, to some of the input functions to hold the input until we're ready to use it. Open input.cpp and change the prototype for IN_Move (line 50) to this:
 CODE  
void IN_Move ( float frametime, usercmd_t *cmd, float * deltaAngles);

Next, find the function CL_AdjustAngles. Remove this line:
 CODE  
viewangles[YAW] = anglemod(viewangles[YAW]);

and these:
 CODE  

if (viewangles[PITCH] > cl_pitchdown->value)
   viewangles[PITCH] = cl_pitchdown->value;
if (viewangles[PITCH] < -cl_pitchup->value)
   viewangles[PITCH] = -cl_pitchup->value;

if (viewangles[ROLL] > 50)
   viewangles[ROLL] = 50;
if (viewangles[ROLL] < -50)
   viewangles[ROLL] = -50;

Because we're going to accumulate all the different control inputs that affect the angles, we won't want the angles to "wrap around" (from 360 to 0 degrees, for example) until all the forms of input have had their "say". The anglemod call keeps the yaw in the 0-360 range, which is not what we want. The other lines limit pitch and roll, which is also not what we want to do at this point. Next, in CL_CreateMove, add these lines at the beginning of the function:
 CODE  

vec3_t deltaAngles;
gEngfuncs.GetViewAngles( (float *)viewangles );

and replace this section:
 CODE  

//memset( viewangles, 0, sizeof( vec3_t ) );
//viewangles[ 0 ] = viewangles[ 1 ] = viewangles[ 2 ] = 0.0;
gEngfuncs.GetViewAngles( (float *)viewangles );

CL_AdjustAngles ( frametime, viewangles );

with this:
 CODE  

deltaAngles[ 0 ] = deltaAngles[ 1 ] = deltaAngles[ 2 ] = 0.0;
// Get change in angles due to keyboard input
CL_AdjustAngles ( frametime, deltaAngles );

Also delete the call to gEngfuncs.SetViewAngles that you see just below this. Our goal here is to keep the view angles separate from the input changes in the angles (which we'll store in deltaAngles). CL_AdjustAngles processes keyboard input that affects view angles. Rather than passing it the actual viewangles, deltaAngles is passed in, so the function ends up giving us the changes caused by keyboard commands. Next, change the call to IN_Move (around line 726) to add deltaAngles so that it will be available to the mouse and joystick code:
 CODE  

IN_Move ( frametime, cmd, deltaAngles );

Scroll down until you find
 CODE  
gEngfuncs.GetViewAngles( (float *)viewangles );
(around line 756) and delete that line (we only want to fetch the view angles once - at the beginning of the function). In its place, put this section:
 CODE  

float angles[3];
VectorCopy(viewangles, angles);
QuaternionRotate(angles, deltaAngles, viewangles);
if (viewangles[PITCH] > 180)
   viewangles[PITCH] -= 360;
if (viewangles[PITCH] < -180)
   viewangles[PITCH] += 360;
if (viewangles[ROLL] > 180)
   viewangles[ROLL] -= 360;
if (viewangles[ROLL] < -180)
   viewangles[ROLL] += 360;

also add the prototype for QuaternionRotate at the beginning of input.cpp:
 CODE  

extern "C" void QuaternionRotate(float anglesIn[3], float deltaAngles[3], float anglesOut[3]);

The QuaternionRotate function takes the input (deltaAngles) and applies it to the view angles (angles), returning the result in viewangles - more on this later. The resulting view angles are then bounded, and at the end of the function we pass the view angles back to the engine with this as the last line in the function:
 CODE  

gEngfuncs.SetViewAngles( (float *)viewangles );

The rest of the input code changes will be in inputw32.cpp. These changes will basically do the same thing as those above - changing references to 'viewangles' to use 'deltaAngles' and removing any code that limits the angle changes. Find the function IN_MouseMove (line 300) and change the first line to:
 CODE  

void IN_MouseMove ( float frametime, usercmd_t *cmd, float * deltaAngles)

then delete these lines:
 CODE  

vec3_t viewangles;
gEngfuncs.GetViewAngles( (float *)viewangles );

Replace 'viewangles' in these lines (lines 353 & 357) with 'deltaAngles':
 CODE  

viewangles[YAW] -= m_yaw->value * mouse_x;
...
viewangles[PITCH] += m_pitch->value * mouse_y;

...and delete these lines:
 CODE  

if (viewangles[PITCH] > cl_pitchdown->value)
   viewangles[PITCH] = cl_pitchdown->value;
if (viewangles[PITCH] < -cl_pitchup->value)
   viewangles[PITCH] = -cl_pitchup->value;

Further down, delete this line:
 CODE  

gEngfuncs.SetViewAngles( (float *)viewangles );

Now find the function IN_JoyMove and add deltaAngles:
 CODE  

void IN_JoyMove ( float frametime, usercmd_t *cmd, float * deltaAngles )

and delete these lines:
 CODE  

vec3_t viewangles;

gEngfuncs.GetViewAngles( (float *)viewangles );

Change the references to 'viewangles' in the rest of the function to 'deltaAngles' - there are 6, on lines 788, 792, 841, 845, 860, and 864. Finally, delete this section:
 CODE  

// bounds check pitch
if (viewangles[PITCH] > cl_pitchdown->value)
   viewangles[PITCH] = cl_pitchdown->value;
if (viewangles[PITCH] < -cl_pitchup->value)
   viewangles[PITCH] = -cl_pitchup->value;

gEngfuncs.SetViewAngles( (float *)viewangles );

Now change the IN_Move function to:
 CODE  

void IN_Move ( float frametime, usercmd_t *cmd, float * deltaAngles)
{
   if ( !iMouseInUse && mouseactive )
   {
      IN_MouseMove ( frametime, cmd, deltaAngles);
   }

   IN_JoyMove ( frametime, cmd, deltaAngles);
}


OK, we've revamped the input system to cause all processing of the viewangle input to go through our function 'QuaternionRotate'. It is this function that really demonstrates the power of using quaternions. You should add quaternion.c and quaternion.h to your common folder, and add quaternion.c to the client project. These two files are my implementation of quaternions. You should at least look these files over and try to understand where the equations come from - the Matrix and Quaternion FAQ is quite helpful. There are also functions in these files which are not used in this tutorial, but which you might find useful in writing your own code which uses quaternions. Here is the QuaternionRotate function, which does most of the work to make the viewangles change:
 CODE  

void QuaternionRotate(const float * anglesIn, const float * deltaAngles, float * anglesOut)
{
    static quaternion_t qIn, qYaw, qPitch, qRoll, qDelta, qResult, qTemp;
    vec3_t forward, right, up;

    // Convert the input angles to quaternion notation and find the axes
    EulerToQuaternion(anglesIn, &qIn);
    AngleVectors(anglesIn, forward, right, up);

    // Calculate the additional rotations from the delta angles and the axes
    QuaternionFromAxisAndAngle(up, deltaAngles[YAW], &qYaw);
    QuaternionFromAxisAndAngle(right, -deltaAngles[PITCH], &qPitch);
    QuaternionFromAxisAndAngle(forward, deltaAngles[ROLL], &qRoll);

    // Get the total additional rotation
    QuaternionMultiply(qPitch, qYaw, &qTemp);
    QuaternionMultiply(qTemp, qRoll, &qDelta);

    // Calculate the new angles
    QuaternionMultiply(qDelta, qIn, &qResult);
    QuaternionToEuler(qResult, anglesOut);
}  

The first step in this function is to convert the view angles passed from CL_CreateMove as anglesIn (Euler angles) into a quaternion with a call to EulerToQuaternion. Then vectors representing the player's local axes (forward, right and up) are calculated. These axes are then used with deltaAngles to calculate quaternions representing the "pitch", "yaw" and "roll" rotations around the player's axes (as opposed to the absolute axes of the world). Multiplying all the individual quaternions yields a quaternion for the desired change in the view angles, qDelta. Multiplying this by the quaternion for the original view angles (qIn) yields a quaternion representing the final rotation (qResult), which is then converted back to Euler angles for use by the rest of the game. We're not restricted to using the player's current view angles to determine the axes around which to rotate the view. Any axis can be used. For example, the normal vector of a wall (or other plane) could be used as the axis for "yaw" rotations and axes at right angles to the normal for "pitch" and "roll" - so that the controls would behave just like the standard controls, except that the player would seem to be walking on a wall. Or, by using a combination of the player's view angles and a fixed "up" vector of (0, 0, 1) you'd get something like the standard Half-Life controls. The possibilities are endless.

[edit]
Note from omega: Entropy is hereby known as "God of the Evil Angles"
[/edit]

Rate This Article
This article is currently rated: 5 out of 5.0 (3 Votes)

You have to register to rate this article.
User Comments Showing comments 1-5

Posted By: Teddy on Sep 17 2003 at 02:46:53
great tutorial, very clever!

the links to the files quaternion.c and quaternion.h are broken tho

Posted By: rkzad on Sep 17 2003 at 06:45:23
They work for me.

Posted By: Teddy on Sep 17 2003 at 23:50:24
me too!

i mean.. they're working now :)

Posted By: haribubba on Jan 08 2006 at 21:53:26
broken again :(
anyone have these files?

Posted By: amogan on Jan 18 2006 at 21:40:09
If you're modding HL2 now, take a look at /public/vector.h
It contains a Quaternion class!
And if you're doing HL1.. just google for the files.


You must register to post a comment. If you have already registered, you must login.

Latest Articles
3rd person View in Multiplayer
Half-Life 2 | Coding | Client Side Tutorials
How to enable it in HL2DM

By: cct | Nov 13 2006

Making a Camera
Half-Life 2 | Level Design
This camera is good for when you join a map, it gives you a view of the map before you join a team

By: slackiller | Mar 05 2006

Making a camera , Part 2
Half-Life 2 | Level Design
these cameras are working monitors that turn on when a button is pushed.

By: slackiller | Mar 04 2006

Storing weapons on ladder
Half-Life 2 | Coding | Snippets
like Raven Sheild or BF2

By: British_Bomber | Dec 24 2005

Implementation of a string lookup table
Half-Life 2 | Coding | Snippets
A string lookup table is a set of functions that is used to convert strings to pre-defined values

By: deathz0rz | Nov 13 2005


Latest Comments
knock knock
General | News
By: omega | Dec 22 2016
 
knock knock
General | News
By: MIFUNE | Oct 10 2015
 
New HL HUD Message System
Half-Life | Coding | Shared Tutorials
By: chbrules | Dec 31 2011
 
knock knock
General | News
By: Whistler | Nov 05 2011
 
Particle Engine tutorial part 4
Half-Life | Coding | Client Side Tutorials
By: darkPhoenix | Feb 18 2010
 
Particle Engine tutorial part 2
Half-Life | Coding | Client Side Tutorials
By: darkPhoenix | Feb 11 2010
 
Particle Engine tutorial part 3
Half-Life | Coding | Client Side Tutorials
By: darkPhoenix | Feb 11 2010
 
Game Movement Series #2: Analog Jumping and Floating
Half-Life 2 | Coding | Shared Tutorials
By: mars3554 | Oct 26 2009
 
Particle Engine tutorial part 5
Half-Life | Coding | Client Side Tutorials
By: Deadpool | Aug 02 2009
 
Particle Engine tutorial part 5
Half-Life | Coding | Client Side Tutorials
By: Persuter | Aug 02 2009
 

Site Info
297 Approved Articless
8 Pending Articles
3940 Registered Members
0 People Online (6 guests)
About - Credits - Contact Us

Wavelength version: 3.0.0.9
Valid XHTML 1.0! Valid CSS!