Tutorial 3 for SDK 1.0
Tracelines
by Trevor Hogan

Vectors

Tracelines are one of the most important modding tools you'll ever use but you'll need to know what a vector is first. I'm not going to write yet another generic 3D math tutorial so I'll just point you in the direction of a fairly decent tutorial written by Phil Dadd.

You should know how to add and subtract vectors, how to normalize a vector, and how to multiply a vector by a scalar. If you think you're ready, read on!

The Problem

Imagine you were hired by id to write this amazing new game, Doom 3. You work as hard as you can for several months and eventually you run into a couple of problems. Problems like calculating the (ideal) path of a bullet and determining if it hit a monster or not (and where it hit). Or whether or not the player is aiming at a monster so the crosshair can be coloured red. Or deciding if a monster can see the player and therefore should attack. This may come as a surprise but these problems can be solved with tracelines! Alright, enough talk. First I'll go over how to use tracelines in a script then I'll cover tracelines in the SDK.

Tracelines in a Script

In this example I'll be looking at the crane claw from Alpha Labs 3. If you can't remember it, it's the level where you have to clean up the toxic waste barrels by picking them up with a remote controlled claw.

image1

Open script/map_alphalabs3_crane.script and take a look at the crane_cmd_pickup function.

// ... snip ...

begin = $crane_trace_start.getWorldOrigin();
end = $crane_trace_end.getWorldOrigin();

sys.trace( begin, end, '0 0 0', '0 0 0', MASK_SOLID|CONTENTS_RENDERMODEL, $crane_hang );
bindEntity = sys.getTraceEntity();
if ( !( !bindEntity ) && ( bindEntity != $world ) ) {
      bindJoint = sys.getTraceJoint();

      // ... snip ...

I chopped a bit of code off the beginning and the end but the important stuff is all there. Don't worry if you don't understand any of this; I'll be explaining it in detail soon. Before we dig in let's take a look at the sys.trace definition in script/doom_events.script.

// Returns the fraction of movement completed before the box from 'mins' to 'maxs' hits solid geometry
// when moving from 'start' to 'end'. The 'passEntity' is considered non-solid during the move.
scriptEvent float trace( vector start, vector end, vector mins, vector maxs, float contents_mask, entity passEntity );

This function, sys.trace, performs a traceline. The first two arguments define a start vector and an end vector. Doom 3 will travel along an imaginary line joining these two vectors and stop at the first entity it encounters. The third and fourth arguments are for performing a "tracebox" - i.e. you can tell Doom 3 to move an imaginary box down the line and stop when any part of the box hits an entity. The fifth argument tells the game what it should stop the trace for (solid objects, monsters, players, and more). The last argument specifies an entity that Doom 3 should ignore when performing the trace (useful when a trace is started from inside an entity).

Okay, the crane_cmd_pickup function is executed when the player presses the "CLOSE" button on the crane GUI (see screenshot). The first thing it does is return if the claw is already carrying a barrel (not shown in the code above), then it performs some miscellaneous cosmetic junk (also not shown) and finally the real work begins. The script prepares for the first traceline by defining a start vector (begin) and an end vector (end). Then the magic happens.

begin = $crane_trace_start.getWorldOrigin();
end = $crane_trace_end.getWorldOrigin();

sys.trace( begin, end, '0 0 0', '0 0 0', MASK_SOLID|CONTENTS_RENDERMODEL, $crane_hang );

This traceline is fired from the crane claw to the ground in search of a barrel. Since the third and fourth arguments are '0 0 0' it's a pure traceline and not a tracebox. The last argument excludes the crane itself from the traceline since we don't care about the crane.

bindEntity = sys.getTraceEntity();
if ( !( !bindEntity ) && ( bindEntity != $world ) ) {
      bindJoint = sys.getTraceJoint();

Next the script asks the traceline if it hit an entity by calling the getTraceEntity function. If it hit something and that something wasn't the world (i.e. the floor) it grabs the closest joint and stores it in bindJoint. The rest of the function is pretty mundane and just binds the barrel to the crane so that it moves with the crane. I'll spare you the details.

I hope this example helped you understand how to use tracelines. Now we'll learn how to use tracelines in the SDK and write our very own traceline from scratch.

Tracelines in the SDK

The idClip class provides access to traces in Doom 3. Check out these two sexy functions in game/physics/Clip.h.

// special case translations versus the rest of the world
bool TracePoint( trace_t &results, const idVec3 &start, const idVec3 &end,
                 int contentMask, const idEntity *passEntity );
bool TraceBounds( trace_t &results, const idVec3 &start, const idVec3 &end, const idBounds &bounds,
                 int contentMask, const idEntity *passEntity );

TracePoint performs a traceline and TraceBounds performs a tracebox. As an example let's write some code to display the health of the entity we're aiming at on the HUD. Open game/Player.cpp and scroll to the very end of idPlayer :: Think. Add this code.

trace_t trace;
idVec3 start;
idVec3 end;

// start the traceline at our eyes

start = GetEyePosition( );

// end the traceline 768 units ahead in the direction we're viewing

end = start + viewAngles.ToForward( ) * 768.0f;

// perform the traceline and store the results in trace
// stop the trace for monsters and players
// and ignore this (the player) since we started the trace inside the player

gameLocal.clip.TracePoint( trace, start, end, MASK_MONSTERSOLID | MASK_PLAYERSOLID, this );

// trace.fraction is the fraction of the traceline that was travelled
// if trace.fraction is less than one then we hit something

if( ( trace.fraction < 1.0f ) && ( trace.c.entityNum != ENTITYNUM_NONE ) )
{
      idEntity *ent = gameLocal.entities[trace.c.entityNum];

      // you could have also used gameLocal.GetTraceEntity( trace ) here
      // gameLocal.GetTraceEntity returns the entity's master entity if it exists
      // search the SDK for more examples

      idStr strHealth;

      sprintf( strHealth, "Entity Health: %d", ent->health );

      if( hud )
      {
            hud->SetStateString( "message", strHealth.c_str( ) );
            hud->HandleNamedEvent( "Message" );
      }
}

image2

Very cool and very easy. If you don't understand how I'm displaying the entity's health on the HUD, please review Tutorial 1. Finally, if you're feeling adventurous try changing the health message to display in red when the entity is nearly dead.

November 1, 2004