Implementing Scripted Cameras in Vanilla Quake 3 |
by Dairyman |
Having a scripted camera system is a must if you want to include cut scenes in your game. Luckily, Quake 3 already contains the code to implement scripted cameras, even though it was never used in the game and is mostly commented out in the source. This tutorial will show what code needs to be uncommented to add the scripted camera system and will expand on this system to include some features that are useful in creating cut scenes.
Splines Library
The actual scripted camera code in contained in the Splines project. This code is normally never used when compiling the Quake3 executable. The following will describe how to compile this project and add the resulting lib to the quake3 project using Visual C++. Other compilers will have to adapt this to their own needs.
First, the Splines project must have some code changes made to it. Switch to this project using Project->Set Active Project and make the following changes :
In splines.cpp in function idCameraDef::getPosition about line 358 add :
// camera script
//Com_Printf("Time: %d\n", t);
// end camera script
assert(splineTime.Num() == splinePoints.Num());
In splines.cpp in function idSplineList::startCamera about line 647 add :
void idCameraDef::startCamera(long t) {
buildCamera();
cameraPosition->start(t);
// camera script
fov.start(t);
// end camera script
startTime = t;
cameraRunning = true;
}
In splines.cpp about line 1227 change :
qboolean getCameraInfo(int time, float *origin, float*angles, float *fov) {
idVec3_t dir, org;
org[0] = origin[0];
org[1] = origin[1];
org[2] = origin[2];
//float fov = 90;
if (camera.getCameraInfo(time, org, dir, fov)) { //&fov)) {
origin[0] = org[0];
origin[1] = org[1];
origin[2] = org[2];
angles[1] = atan2 (dir[1], dir[0])*180/3.14159;
angles[0] = asin (dir[2])*180/3.14159;
return qtrue;
}
return qfalse;
}
This allows us access to the FOV specified in the camera script. Next, build splines.lib by compiling the project. This lib will be created in the code/splines/Release folder. Copy the splines.lib file from here to the code folder.
Now switch to the quake3 project using Project->Set Active Project. Then go to Project->Settings and select the quake3 project. Select the Link tab and add splines.lib to the end of the library list in Object/library modules.
Code Changes for the quake3 Project
With splines.lib added to the quake3 project we must uncomment some code to allow access to the new library.
In cl_cgame.c about line 31 add :
extern void startCamera(int time);
extern qboolean getCameraInfo(int time, vec3_t *origin, vec3_t *angles, float *fov);
In cl_cgame.c in function CL_CgameSystemCalls about line 684 add :
// camera script
case CG_LOADCAMERA:
return loadCamera(VMA(1));
case CG_STARTCAMERA:
startCamera(args[1]);
return 0;
case CG_GETCAMERAINFO:
return getCameraInfo(args[1], VMA(2), VMA(3), VMA(4));
// end camera script
Before you can build a new quake3 executable that includes the scripted cameras you must make the changes shown below for the cgame module and rebuild that module as well.
Code Changes for the cgame Project
In order to actually use the scripted camera system we must also make some changes in the cgame module. These changes will allow us to switch to a scripted camera and have it displayed correctly on the screen.
In cg_local.h in the cg_t struct definition about line 702 add :
float xyspeed;
int nextOrbitTime;
// camera script
qboolean cameraMode; // if rendering from a loaded camera
// end camera script
In cg_local.h about line 1800 add :
qboolean trap_loadCamera(const char *name);
void trap_startCamera(int time);
// camera script
qboolean trap_getCameraInfo(int time, vec3_t *origin, vec3_t *angles, float *fov);
void CG_StartCamera(const char *name, qboolean startBlack );
// end camera script
In cg_local.h in the cgs_t struct definition about line 1147 add :
char acceptVoice[MAX_NAME_LENGTH];
// camera script
float scrFadeAlpha, scrFadeAlphaCurrent;
int scrFadeStartTime;
int scrFadeDuration;
// end camera script
// media
cgMedia_t media;
} cgs_t;
In cg_local.h about line 1425 add :
// camera script
void CG_Fade( int a, int time, int duration );
void CG_DrawFlashFade( void );
// end camera script
In cg_public.h in the cgameImport_t struct definition about line 166 add :
CG_FS_SEEK,
// camera script
CG_LOADCAMERA,
CG_STARTCAMERA,
CG_GETCAMERAINFO,
// end camera script
CG_MEMSET = 100,
In cg_syscalls.c about line 421 add :
void trap_CIN_SetExtents (int handle, int x, int y, int w, int h) {
syscall(CG_CIN_SETEXTENTS, handle, x, y, w, h);
}
// camera script
qboolean trap_loadCamera( const char *name ) {
return syscall( CG_LOADCAMERA, name );
}
void trap_startCamera(int time) {
syscall(CG_STARTCAMERA, time);
}
qboolean trap_getCameraInfo( int time, vec3_t *origin, vec3_t *angles, float *fov) {
return syscall( CG_GETCAMERAINFO, time, origin, angles, fov );
}
// end camera script
qboolean trap_GetEntityToken( char *buffer, int bufferSize ) {
return syscall( CG_GET_ENTITY_TOKEN, buffer, bufferSize );
}
In cg_view.c in function CG_DamageBlendBlob about line 568 add :
if ( !cg.damageValue ) {
return;
}
// camera script
if (cg.cameraMode) {
return;
}
// end camera script
// ragePro systems can't fade blends, so don't obscure the screen
if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) {
In cg_view.c in function CG_CalcViewValues about line 621 add :
ps = &cg.predictedPlayerState;
// camera script
if (cg.cameraMode) {
vec3_t origin, angles;
float fov = 90;
float x;
if (trap_getCameraInfo(cg.time, &origin, &angles, &fov)) {
VectorCopy(origin, cg.refdef.vieworg);
angles[ROLL] = 0;
angles[PITCH] = -angles[PITCH]; // Bug Fix for GtkRadiant cameras
VectorCopy(angles, cg.refdefViewAngles);
AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis );
x = cg.refdef.width / tan( fov / 360 * M_PI );
cg.refdef.fov_y = atan2( cg.refdef.height, x );
cg.refdef.fov_y = cg.refdef.fov_y * 360 / M_PI;
cg.refdef.fov_x = fov;
return 0;;
} else {
cg.cameraMode = qfalse;
CG_Fade(255, 0, 0); // go black
CG_Fade(0, cg.time + 200, 1500); // then fadeup
//
// letterbox look
//
black_bars=0;
}
}
// end camera script
// intermission view
if ( ps->pm_type == PM_INTERMISSION ) {
Note : If you have not implemented the target_player_stop entity from the Single Player Game Entities then comment out the line that reads :
black_bars=0;
In cg_weapons.c in function CG_AddViewWeapon about line 1428 add :
// no gun if in third person view or a camera is active
// camera script
if ( cg.renderingThirdPerson || cg.cameraMode) {
// end camera script
return;
}
In cg_consolecmds.c about line 430 add :
// camera script
/*
==============
CG_StartCamera
==============
*/
void CG_StartCamera( const char *name, qboolean startBlack ) {
char lname[MAX_QPATH];
COM_StripExtension(name, lname);
Q_strcat( lname, sizeof(lname), ".camera" );
if (trap_loadCamera(va("cameras/%s", lname)))
{
cg.cameraMode = qtrue;
if(startBlack)
{
CG_Fade(255, 0, 0); // go black
CG_Fade(0, cg.time, 1500);
}
//
// letterbox look
//
black_bars=1;
trap_startCamera(cg.time); // camera on in client
} else {
CG_Printf ("Unable to load camera %s\n",name);
}
}
static void CG_Camera_f( void ) {
char name[MAX_QPATH];
trap_Argv( 1, name, sizeof(name));
CG_StartCamera(name, qfalse );
}
// end camera script
typedef struct {
char *cmd;
void (*function)(void);
} consoleCommand_t;
Note : If you have not implemented the target_player_stop entity from the Single Player Game Entities then comment out the line that reads :
black_bars=1;
In cg_consolecmds.c about line 515 add :
{ "startOrbit", CG_StartOrbit_f },
// camera script
{ "camera", CG_Camera_f },
// end camera script
{ "loaddeferred", CG_LoadDeferredPlayers }
};
In cg_draw.c in function CG_Draw2D about line 2575 add :
if ( cg.snap->ps.pm_type == PM_INTERMISSION ) {
CG_DrawIntermission();
return;
}
// camera script
if (cg.cameraMode) {
CG_DrawFlashFade();
return;
}
// end camera script
if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) {
CG_DrawSpectator();
CG_DrawCrosshair();
In cg_draw.c in function CG_Draw2D about line 2676 add :
CG_DrawCenterString();
}
// camera script
CG_DrawFlashFade();
// end camera script
}
In cg_draw.c at the end of the file add :
// camera script
/*
=================
CG_Fade
=================
*/
void CG_Fade( int a, int time, int duration ) {
cgs.scrFadeAlpha = (float)a / 255.0f;
cgs.scrFadeStartTime = time;
cgs.scrFadeDuration = duration;
if (cgs.scrFadeStartTime + cgs.scrFadeDuration <= cg.time) {
cgs.scrFadeAlphaCurrent = cgs.scrFadeAlpha;
}
return;
}
void CG_DrawFlashFade( void ) {
static int lastTime;
int elapsed, time;
vec4_t col;
if (cgs.scrFadeStartTime + cgs.scrFadeDuration < cg.time) {
cgs.scrFadeAlphaCurrent = cgs.scrFadeAlpha;
} else if (cgs.scrFadeAlphaCurrent != cgs.scrFadeAlpha) {
elapsed = (time = trap_Milliseconds()) - lastTime;
lastTime = time;
if (elapsed < 500 && elapsed > 0) {
if (cgs.scrFadeAlphaCurrent > cgs.scrFadeAlpha) {
cgs.scrFadeAlphaCurrent -= ((float)elapsed/(float)cgs.scrFadeDuration);
if (cgs.scrFadeAlphaCurrent < cgs.scrFadeAlpha)
cgs.scrFadeAlphaCurrent = cgs.scrFadeAlpha;
} else {
cgs.scrFadeAlphaCurrent += ((float)elapsed/(float)cgs.scrFadeDuration);
if (cgs.scrFadeAlphaCurrent > cgs.scrFadeAlpha)
cgs.scrFadeAlphaCurrent = cgs.scrFadeAlpha;
}
}
}
// now draw the fade
if (cgs.scrFadeAlphaCurrent > 0.0) {
VectorClear( col );
col[3] = cgs.scrFadeAlphaCurrent;
CG_FillRect( 0, 0, 640, 480, col );
}
}
// end camera script
In cg_predict.c in function CG_PredictPlayerState about line 609 add :
if ( cg_pmove.pmove_fixed ) {
cg_pmove.cmd.serverTime = ((cg_pmove.cmd.serverTime + pmove_msec.integer-1)
/ pmove_msec.integer) * pmove_msec.integer;
}
// camera script
if (cgs.scrFadeAlphaCurrent) {
cg_pmove.cmd.buttons = 0;
cg_pmove.cmd.forwardmove = 0;
cg_pmove.cmd.rightmove = 0;
cg_pmove.cmd.upmove = 0;
if (cg_pmove.cmd.serverTime - cg.predictedPlayerState.commandTime > 1)
cg_pmove.cmd.serverTime = cg.predictedPlayerState.commandTime + 1;
}
// end camera script
Pmove (&cg_pmove);
In cg_servercmds.c in function CG_ServerCommand about line 984 add :
if ( !cmd[0] ) {
// server claimed the command
return;
}
// camera script
if ( !strcmp( cmd, "startCam" ) ) {
CG_StartCamera( CG_Argv(1), atoi(CG_Argv(2)) );
return;
}
// end camera script
if ( !strcmp( cmd, "cp" ) ) {
You can now build the new quake3 executable and the new cgame module and have scripted camera support.
The new features that have been added in addition to the standard scripted camera system are screen fading to and from black in conjunction with the scripted camera use, using the Fov from the camera script and a server command that allows the server to start a scripted camera.
Using the Camera
Camera script files are text files that have a camera extension and are placed in the baseq3/cameras folder. The scripted camera system will look in that folder for a file with the camera extension when you start a scripted camera. There are two ways that a scripted camera can be started - from the console and via a server command.
To run a camera script from the console you use the camera command as follows :
camera <camera script>
where <camera script> is the name of a camera script file (without the extension) that is present in the cameras folder. For example, to run the camera script in the hangar.camera file you would enter at the console :
camera hangar
To have the server start a camera script on the client you must have the server send the startCam command to the client. This can be done using the trap_SendServerCommand as follows :
trap_SendServerCommand( clientnum, va("startCam %s %d", filename, black) );
where clientnum is the client number or -1 for all clients, filename is the name of the camera script file without any extension and black is 1 if you want the camera scene to fade in from black at the start and is 0 if you don't.
A Look at Camera Scripts
The following is a brief description of camera scripts as gleaned from various sources and from examination of the code in the splines project.
A camera definition consists of a time value for the camera, a camera path, one or more view directions know as target paths, a fov for the camera and one or more events that can occur along the timeline of the camera path. All of this information is enclosed inside the following :
cameraPathDef
{
}
1. Time Value
The time value for the camera is the number of seconds that the camera will be active before it signals the client that it has ended. By default, if no value is specified for the time it is set to 30 seconds. The time is set using the following :
time <value>
where <value> is the number of seconds. A fractional <value> can be specified. For example, to set the total time the camera is active to 7.5 seconds you would use :
cameraPathDef
{
time 7.50
}
2. Events
The timeline of the camera runs from 0 to the value specified by the time command. You can make it so that certain events occur at specific times along that timeline by use of the event command. An event is defined as follows :
event
{
type <event type>
param "<parameter>"
time <start time>
}
where <start time> is time in milliseconds from the start of the camera timeline that the event occurs, <event type> is a number corresponding to a camera event and <parameter> is the parameter required by the event.
The Quake 3 camera code supports three event types, which are :
<event type> |
Description |
1 |
Wait - pause camera movement for <parameter> seconds |
4 |
Switch Target - switch to target path whose name is defined by <parameter> |
9 |
Stop Camera - signal to client that the camera is done |
For <event type> = 4 (Switch Target) - if there is only one target path then <parameter> can be left blank.
For <event type> = 1 (Wait) - the amount of wait time specified by <parameter> is added to the total time the camera is active and extends the timeline of the camera. A wait event pauses the time advancement and movement of the camera path for the duration of the wait. The active target path continues to move during a wait.
3. Fov
The fov value for the camera sets the field of view in degrees. A smaller fov value displays less of the surrounding area and appears to zoom in on the target. A larger value shows more of the surrounding area and appears to pull away from the target. By default the fov is set to 90 degrees. You can define the fov value by :
fov
{
fov <fov value>
}
where <fov value> is the camera field of view in degrees. A fractional <fov value> can be used.
4. Target Path
A Target Path defines the position that the camera looks at during its timeline. There can be more than one target path in the camera script and the Switch Target Event is used to change from one target path to another. Target Paths come in three flavors - Fixed, Interpolated and Spline.
A Fixed Target Path is one that does not move during the camera timeline. As it name implies it's position is fixed. You define a Fixed Target Path by :
target_Fixed
{
name <name>
pos ( <x> <y> <z> )
}
where <name> is the name of the target, which is used by the Switch Target Event, and <x> <y> <z> are the target position coordinates.
An Interpolated Target Path is a target that moves between two position coordinates in a specified amount of time. You define an Interpolated Target Path by :
target_Interpolated
{
name <name>
startPos ( <x0> <y0> <z0> )
endPos ( <x1> <y1> <z1> )
}
where <name> is the name of the target, which is used by the Switch Target Event, <x0> <y0> <z0> are the starting position coordinates and <x1> <y1> <z1> are the ending position coordinates. The amount of time that the target takes to move from start to finish is the camera timeline plus any wait times added from Wait Events.
A Spline Target Path is a target that moves in a curve through a number of specified positions in a specified amount of time. It uses splines to best fit the curve to the provided positions. You define a Spline Target Path by :
target_Spline
{
name <name>
target
{
granularity <step>
( <x0> <y0> <z0> )
( <x1> <y1> <z1> }
.....
( <xn> <yn> <zn> }
}
}
where <name> is the name of the target, which is used by the Switch Target Event, <step> is the granularity of the spline (default value is 0.025), <x0> <y0> <z0> are the first position coordinates, <x1> <y1> <z1> are the second position coordinates, ..... and <xn> <yn> <zn> are the last position coordinates. The amount of time that the target takes to move from start to finish is the camera timeline plus any wait times added from Wait Events.
5. Camera Path
The Camera Path defines the position of the camera during its timeline. Like Target Paths, the Camera Path comes in three flavors - Fixed, Interpolated and Spline.
A Fixed Camera Path is one that does not move during the camera timeline. The camera remains at a fixed position through out the life of the script. It is defined by :
camera_Fixed
{
name <name>
pos ( <x> <y> <z> )
}
where <name> is the name of the camera and <x> <y> <z> are the camera position coordinates.
An Interpolated Camera Path is a camera that moves between two position coordinates in a specified amount of time. You define an Interpolated Camera Path by :
camera_Interpolated
{
name <name>
startPos ( <x0> <y0> <z0> )
endPos ( <x1> <y1> <z1> )
}
where <name> is the name of the camera, <x0> <y0> <z0> are the starting position coordinates and <x1> <y1> <z1> are the ending position coordinates. The speed at which the camera moves is determined by the distance to move and the original camera timeline, without any wait times added to it.
A Spline Camera Path is a camera that moves in a curve through a number of specified positions in a specified amount of time. It uses splines to best fit the curve to the provided positions. You define a Spline Camera Path by :
camera_Spline
{
name <name>
target
{
granularity <step>
( <x0> <y0> <z0> )
( <x1> <y1> <z1> }
.....
( <xn> <yn> <zn> }
}
}
where <name> is the name of the camera, <step> is the granularity of the spline (usual value is 0.025), <x0> <y0> <z0> are the first position coordinates, <x1> <y1> <z1> are the second position coordinates, ..... and <xn> <yn> <zn> are the last position coordinates. The speed at which the camera moves is determined by the distance to move and the original camera timeline, without any wait times added to it..
An Sample Camera Script
The following is an example of a camera script with both a moving camera and a moving target.
cameraPathDef
{
time 7.500000
camera_Interpolated
{
name position
startPos ( -328.727203 958.092957 40.125 )
endPos ( -1269.483276 963.486816 40.125 )
}
target_Spline
{
name target1
target
{
granularity 0.025000
( -608.000000 512.000000 0.000000 )
( -584.000000 512.000000 0.000000 )
( -560.000000 512.000000 0.000000 )
( -536.000000 512.000000 0.000000 )
( -480.000000 512.000000 0.000000 )
( -408.000000 512.000000 0.000000 )
( -368.000000 512.000000 24.000000 )
( -336.000000 512.000000 32.000000 )
}
}
event
{
type 4
param "target1"
time 0
}
event
{
type 1
param "7"
time 3500
}
fov
{
fov 90.000000
}
}
The original timeline for this camera is 7.5 seconds, as set by the time command. You will notice that one of the event commands is of type 1, which is a Wait Event, with a parameter of 7 and a time of 3500. This says that at 3.5 seconds (3500 milliseconds) into the timeline there will be a 7 second delay in the movement of the camera. Thus the actual timeline for the camera is 7.5 plus 7 = 14.5 seconds.
The camera type is Interpolated, which means it moves from startPos to endPos at a speed calculated to cover that distance in the time set by the original timeline, which is 7.5 seconds. The Wait Event does not come into play when calculating this speed.
There is only one target for this camera and it is of type Spline, which means it moves in a curve through the positions defined in the target section. Its speed is calculated to cover the distance in the time set by the actual timeline, which is 14.5 seconds. The Wait Event does not affect the movement of the target (a target never stops moving) but it does affect the movement speed by extending the timeline.
There are two event sections in this camera. The first one has a type of 4, which is a Switch Target Event, with a target name of target1 and a time of 0. This causes the camera to point towards the target with this name at 0 milliseconds on the actual timeline. The second is the Wait Event described above, which activates at 3500 milliseconds into the timeline.
The fov section sets the camera's field of view to 90 degrees.
Additions to Scripted Cameras
It is possible to add new features to the scripted camera system and here we will look at two new features, a stop camera command and multiple cameras.
1. Stop Camera
There may be a time when you wish to stop a scripted camera from running and restore the viewpoint to normal. We will add a console command and a server command to do just this.
In cg_consolecmds.c just after function CG_Camera_f about line 470 add :
static void CG_CameraStop_f( void ) {
cg.cameraMode = qfalse;
}
In cg_consolecmds.c about line 536 add :
{ "camera", CG_Camera_f },
{ "stopcamera", CG_CameraStop_f },
In cg_servercmds.c in function CG_ServerCommand about line 1000 add :
if ( !strcmp( cmd, "startCam" ) ) {
CG_StartCamera( CG_Argv(1), atoi(CG_Argv(2)) );
return;
}
if ( !strcmp( cmd, "stopCam" ) ) {
cg.cameraMode = qfalse;
return;
}
To stop a camera from the console type the following :
stopcamera
To have the server stop a camera script on the client you must have the server send the stopCam command to the client. This can be done using the trap_SendServerCommand as follows :
trap_SendServerCommand( clientnum, "stopCam" );
where clientnum is the client number or -1 for all clients.
2. Multiple Cameras
A feature that may have a use in some circumstances is to have multiple camera scripts active and switch between them to change what is being shown. To implement this we must make changes to the splines project, the quake3 project and the cgame project.
Splines Project
In splines.cpp at the end of the file modify the following :
#define MAX_CAMERAS 8
idCameraDef camera[MAX_CAMERAS];
extern "C" {
qboolean loadCamera(int camNum, const char *name) {
if(camNum >= MAX_CAMERAS)
return qfalse;
camera[camNum].clear();
return static_cast<qboolean>(camera[camNum].load(name));
}
// camera script
qboolean getCameraInfo( int camNum, int time, float *origin, float*angles, float *fov) {
idVec3_t dir, org;
if(camNum >= MAX_CAMERAS)
return qfalse;
org[0] = origin[0];
org[1] = origin[1];
org[2] = origin[2];
if (camera[camNum].getCameraInfo(time, org, dir, fov)) {
origin[0] = org[0];
origin[1] = org[1];
origin[2] = org[2];
angles[1] = atan2 (dir[1], dir[0])*180/3.14159;
angles[0] = asin (dir[2])*180/3.14159;
return qtrue;
}
return qfalse;
}
// end camera script
void startCamera(int camNum, int time) {
if(camNum >= MAX_CAMERAS)
return;
camera[camNum].startCamera(time);
}
Compile the splines project and copy splines.lib to the code folder.
Quake3 Project
In cl_cgame.c about line 30 add :
extern qboolean loadCamera(int camNum, const char *name);
extern void startCamera(int camNum, int time);
// camera script
extern qboolean getCameraInfo(int camNum, int time, vec3_t *origin, vec3_t *angles, float *fov);
In cl_cgame.c in function CL_CgameSystemCalls about line 684 add :
// camera script
case CG_LOADCAMERA:
return loadCamera(args[1], VMA(2));
case CG_STARTCAMERA:
startCamera(args[1], args[2]);
return 0;
case CG_GETCAMERAINFO:
return getCameraInfo(args[1], args[2], VMA(3), VMA(4), VMA(5));
// end camera script
Compile the project and create the new quake3 executable.
Cgame Project
In cg_local.h about line 1800 add :
qboolean trap_loadCamera(int camNum, const char *name);
void trap_startCamera(int camNum, int time);
// camera script
qboolean trap_getCameraInfo(int camNum, int time, vec3_t *origin, vec3_t *angles, float *fov);
void CG_StartCamera(const char *name, qboolean startBlack );
In cg_syscalls.c about line 421 add :
void trap_CIN_SetExtents (int handle, int x, int y, int w, int h) {
syscall(CG_CIN_SETEXTENTS, handle, x, y, w, h);
}
// camera script
qboolean trap_loadCamera( int camNum, const char *name ) {
return syscall( CG_LOADCAMERA, camNum, name );
}
void trap_startCamera(int camNum, int time) {
syscall(CG_STARTCAMERA, camNum, time);
}
qboolean trap_getCameraInfo( int camNum, int time, vec3_t *origin, vec3_t *angles, float *fov) {
return syscall( CG_GETCAMERAINFO, camNum, time, origin, angles, fov );
}
In cg_consolecmds.c about line 430 add :
// camera script
/*
==============
CG_StartCamera
==============
*/
void CG_StartCamera( const char *name, qboolean startBlack ) {
char lname[MAX_QPATH];
COM_StripExtension(name, lname);
Q_strcat( lname, sizeof(lname), ".camera" );
if (trap_loadCamera(CAM_PRIMARY, va("cameras/%s", lname)))
{
cg.cameraMode = qtrue;
if(startBlack)
{
CG_Fade(255, 0, 0); // go black
CG_Fade(0, cg.time, 1500);
}
//
// letterbox look
//
black_bars=1;
trap_startCamera(CAM_PRIMARY, cg.time); // camera on in client
} else {
CG_Printf ("Unable to load camera %s\n",name);
}
}
In cg_view.c in function CG_CalcViewValues about line 621 add :
ps = &cg.predictedPlayerState;
// camera script
if (cg.cameraMode) {
vec3_t origin, angles;
float fov = 90;
float x;
if (trap_getCameraInfo(CAM_PRIMARY, cg.time, &origin, &angles, &fov)) {
VectorCopy(origin, cg.refdef.vieworg);
Compile the project and multiple cameras are supported. The current console and server commands do not use multiple cameras.
3. trap_stopCamera Function
There may be a time when you wish to stop an individual camera so that trap_getCameraInfo for it returns false. The changes required are as follows. To implement this we must make changes to the splines project, the quake3 project and the cgame project.
Splines Project
In splines.h in function stopCamera about line 939 change :
void stopCamera() {
cameraRunning = false;
}
In splines.cpp after function startCamera at end of file add :
void startCamera(int camNum, int time) {
if(camNum >= MAX_CAMERAS)
return;
camera[camNum].startCamera(time);
}
void stopCamera(int camNum) {
if(camNum >= MAX_CAMERAS)
return;
camera[camNum].stopCamera();
}
Compile the splines project and copy splines.lib to the code folder.
Quake3 Project
In cl_cgame.c about line 30 add :
extern qboolean loadCamera(int camNum, const char *name);
extern void startCamera(int camNum, int time);
// camera script
extern qboolean getCameraInfo(int camNum, int time, vec3_t *origin, vec3_t *angles, float *fov);
extern void stopCamera(int camNum);
In cl_cgame.c in function CL_CgameSystemCalls about line 684 add :
// camera script
case CG_LOADCAMERA:
return loadCamera(args[1], VMA(2));
case CG_STARTCAMERA:
startCamera(args[1], args[2]);
return 0;
case CG_GETCAMERAINFO:
return getCameraInfo(args[1], args[2], VMA(3), VMA(4), VMA(5));
// end camera script
case CG_STOPCAMERA:
stopCamera(args[1]);
return 0;
You can not compile this project until the changes to the cgame project are made.
Cgame Project
In cg_local.h about line 1800 add :
qboolean trap_loadCamera(int camNum, const char *name);
void trap_startCamera(int camNum, int time);
// camera script
qboolean trap_getCameraInfo(int camNum, int time, vec3_t *origin, vec3_t *angles, float *fov);
void CG_StartCamera(const char *name, qboolean startBlack );
void trap_stopCamera(int camNum);
In cg_public.h in the cgameImport_t struct definition about line 166 add :
CG_FS_SEEK,
// camera script
CG_LOADCAMERA,
CG_STARTCAMERA,
CG_GETCAMERAINFO,
// end camera script
CG_STOPCAMERA,
CG_MEMSET = 100,
In cg_syscalls.c about line 435 add :
qboolean trap_getCameraInfo( int camNum, int time, vec3_t *origin, vec3_t *angles, float *fov) {
return syscall( CG_GETCAMERAINFO, camNum, time, origin, angles, fov );
}
void trap_stopCamera(int camNum) {
syscall(CG_STOPCAMERA, camNum);
}
Compile both the quake3 and cgame projects to complete the trap_stopCamera addition.
How Cameras Work
If you wish to use the cameras in your own project and don't wish to use the server and console commands provided here then you must understand how cameras work in the Quake 3 environment. This section will explain that.
1. The first step in using a camera is to load and parse the camera script. This is done using the trap_loadCamera function as follows :
trap_loadCamera(<cam number>, <filename> )
where <cam number> is the number of the camera (0 if only one camera) and <filename> is the path and name (with extension) of the camera script file. This function loads and parses the file and prepares the camera for use and returns qtrue if successful and qfalse if not. Once a camera has loaded it's data that data remains in the camera unless it is overwritten by another trap_loadCamera to that particular camera number.
2. When you wish to use the camera it must be started using the trap_startCamera function like :
trap_startCamera(<cam number>, <time>)
where <cam number> is the number of the camera (0 if only one camera) and <time> is the game time, usually the value of cg.time. This sets the starting time for the camera, which it uses to calculated camera and target positions. A camera can be restarted multiple times using this function, if desired, as the data is always avalible.
3. With the camera running you can get the information from the camera using the trap_getCameraInfo function as follows :
trap_getCameraInfo( <cam number>, <time>, <origin>, <angles>, <fov>)
where <cam number> is the number of the camera (0 if only one camera), <time> is the game time, usually the value of cg.time, <origin> is a pointer to a vec3_t variable, <angles> is a pointer to a vec3_t variable and <fov> is a pointer to a float variable. The function returns qtrue if the camera is running and qfalse if it has ended or is stopped. The <time> value is used by the function to calculate the current camera and target positions so it can return the correct information for the amount of time that has passed since the camera was started.
The variables pointed to by <origin>, <angles> and <fov> are used in the CG_CalcViewValues function to set the client's view origin, angles and fov. The values of these variables are only valid if the trap_getCameraInfo function returns qtrue.
4. If you wish to stop a camera script before it is finished running this can be done using the trap_stopCamera function like :
trap_stopCamera(<cam number>)
where <cam number> is the number of the camera (0 if only one camera). This will cause the trap_getCameraInfo function to return qfalse for that camera number.
It should be noted that cameras do not run in the background, using up resources. They use the trap_getCameraInfo function call to calculate information but otherwise are inactive. Using trap_stopCamera to stop a camera from running or having the camera script end does not delete the camera or any of it's data. It just makes it unavalible until you restart the camera using the trap_startCamera function.
When using multiple cameras it is possible to load all the cameras at one time, using the trap_loadCamera function for each one, and then start them at different times as needed.