示例#1
0
/*
==============
CaptureRenderToFile

==============
*/
void idRenderSystemLocal::CaptureRenderToFile( const char *fileName, bool fixAlpha ) {
	if ( !glConfig.isInitialized ) {
		return;
	}

	renderCrop_t *rc = &renderCrops[currentRenderCrop];

	guiModel->EmitFullScreen();
	guiModel->Clear();
	R_IssueRenderCommands();

	qglReadBuffer( GL_BACK );

	// include extra space for OpenGL padding to word boundaries
	int	c = ( rc->width + 3 ) * rc->height;
	byte *data = (byte *)R_StaticAlloc( c * 3 );
	
	qglReadPixels( rc->x, rc->y, rc->width, rc->height, GL_RGB, GL_UNSIGNED_BYTE, data ); 

	byte *data2 = (byte *)R_StaticAlloc( c * 4 );

	for ( int i = 0 ; i < c ; i++ ) {
		data2[ i * 4 ] = data[ i * 3 ];
		data2[ i * 4 + 1 ] = data[ i * 3 + 1 ];
		data2[ i * 4 + 2 ] = data[ i * 3 + 2 ];
		data2[ i * 4 + 3 ] = 0xff;
	}

	R_WriteTGA( fileName, data2, rc->width, rc->height, true );

	R_StaticFree( data );
	R_StaticFree( data2 );
}
示例#2
0
/*
=================
R_ClearedStaticAlloc
=================
*/
void *R_ClearedStaticAlloc( int bytes ) {
	void	*buf;

	buf = R_StaticAlloc( bytes );
	SIMDProcessor->Memset( buf, 0, bytes );
	return buf;
}
示例#3
0
/*
=================
R_HeightmapToNormalMap

it is not possible to convert a heightmap into a normal map
properly without knowing the texture coordinate stretching.
We can assume constant and equal ST vectors for walls, but not for characters.
=================
*/
static void R_HeightmapToNormalMap( byte *data, int width, int height, float scale )
{
	int		i, j;
	byte	*depth;
	
	scale = scale / 256;
	
	// copy and convert to grey scale
	j = width * height;
	depth = ( byte * ) R_StaticAlloc( j );
	
	for( i = 0; i < j; i++ )
	{
		depth[i] = ( data[i * 4] + data[i * 4 + 1] + data[i * 4 + 2] ) / 3;
	}
	
	idVec3	dir, dir2;
	
	for( i = 0; i < height; i++ )
	{
		for( j = 0; j < width; j++ )
		{
			int		d1, d2, d3, d4;
			int		a1, a2, a3, a4;
			
			// FIXME: look at five points?
			// look at three points to estimate the gradient
			a1 = d1 = depth[( i * width + j )];
			a2 = d2 = depth[( i * width + ( ( j + 1 ) & ( width - 1 ) ) )];
			a3 = d3 = depth[( ( ( i + 1 ) & ( height - 1 ) ) * width + j )];
			a4 = d4 = depth[( ( ( i + 1 ) & ( height - 1 ) ) * width + ( ( j + 1 ) & ( width - 1 ) ) )];
			
			d2 -= d1;
			d3 -= d1;
			
			dir[0] = -d2 * scale;
			dir[1] = -d3 * scale;
			dir[2] = 1;
			dir.NormalizeFast();
			
			a1 -= a3;
			a4 -= a3;
			
			dir2[0] = -a4 * scale;
			dir2[1] = a1 * scale;
			dir2[2] = 1;
			dir2.NormalizeFast();
			
			dir += dir2;
			dir.NormalizeFast();
			
			a1 = ( i * width + j ) * 4;
			data[a1 + 0] = ( byte )( dir[0] * 127 + 128 );
			data[a1 + 1] = ( byte )( dir[1] * 127 + 128 );
			data[a1 + 2] = ( byte )( dir[2] * 127 + 128 );
			data[a1 + 3] = 255;
		}
	}
	R_StaticFree( depth );
}
示例#4
0
/*
================
R_CalcInteractionFacing

Determines which triangles of the surface are facing towards the light origin.

The facing array should be allocated with one extra index than
the number of surface triangles, which will be used to handle dangling
edge silhouettes.
================
*/
void R_CalcInteractionFacing( const idRenderEntityLocal *ent, const srfTriangles_t *tri, const idRenderLightLocal *light, srfCullInfo_t &cullInfo ) {
	SCOPED_PROFILE_EVENT( "R_CalcInteractionFacing" );

	if ( cullInfo.facing != NULL ) {
		return;
	}

	idVec3 localLightOrigin;
	R_GlobalPointToLocal( ent->modelMatrix, light->globalLightOrigin, localLightOrigin );

	const int numFaces = tri->numIndexes / 3;
	cullInfo.facing = (byte *) R_StaticAlloc( ( numFaces + 1 ) * sizeof( cullInfo.facing[0] ), TAG_RENDER_INTERACTION );

	// exact geometric cull against face
	for ( int i = 0, face = 0; i < tri->numIndexes; i += 3, face++ ) {
		const idDrawVert & v0 = tri->verts[tri->indexes[i + 0]];
		const idDrawVert & v1 = tri->verts[tri->indexes[i + 1]];
		const idDrawVert & v2 = tri->verts[tri->indexes[i + 2]];

		const idPlane plane( v0.xyz, v1.xyz, v2.xyz );
		const float d = plane.Distance( localLightOrigin );

		cullInfo.facing[face] = ( d >= 0.0f );
	}
	cullInfo.facing[numFaces] = 1;	// for dangling edges to reference
}
/*
================
R_CalcInteractionFacing

Determines which triangles of the surface are facing towards the light origin.

The facing array should be allocated with one extra index than
the number of surface triangles, which will be used to handle dangling
edge silhouettes.
================
*/
void R_CalcInteractionFacing( const idRenderEntityLocal *ent, const srfTriangles_t *tri, const idRenderLightLocal *light, srfCullInfo_t &cullInfo ) {
	idVec3 localLightOrigin;

	if ( cullInfo.facing != NULL ) {
		return;
	}

	R_GlobalPointToLocal( ent->modelMatrix, light->globalLightOrigin, localLightOrigin );

	int numFaces = tri->numIndexes / 3;

	if ( !tri->facePlanes || !tri->facePlanesCalculated ) {
		R_DeriveFacePlanes( const_cast<srfTriangles_t *>(tri) );
	}

	cullInfo.facing = (byte *) R_StaticAlloc( ( numFaces + 1 ) * sizeof( cullInfo.facing[0] ) );

	// calculate back face culling
	float *planeSide = (float *) _alloca16( numFaces * sizeof( float ) );

	// exact geometric cull against face
	SIMDProcessor->Dot( planeSide, localLightOrigin, tri->facePlanes, numFaces );
	SIMDProcessor->CmpGE( cullInfo.facing, planeSide, 0.0f, numFaces );

	cullInfo.facing[ numFaces ] = 1;	// for dangling edges to reference
}
示例#6
0
/*
====================
GenerateMegaPreview

Make a 2k x 2k preview image for a mega texture that can be used in modeling programs
====================
*/
void	idMegaTexture::GenerateMegaPreview( const char *fileName ) {
	idFile	*fileHandle = fileSystem->OpenFileRead( fileName );
	if ( !fileHandle ) {
		common->Printf( "idMegaTexture: failed to open %s\n", fileName );
		return;
	}

	idStr	outName = fileName;
	outName.StripFileExtension();
	outName += "_preview.tga";

	common->Printf( "Creating %s.\n", outName.c_str() );

	megaTextureHeader_t header;

	fileHandle->Read( &header, sizeof( header ) );
	if ( header.tileSize < 64 || header.tilesWide < 1 || header.tilesHigh < 1 ) {
		common->Printf( "idMegaTexture: bad header on %s\n", fileName );
		return;
	}

	int	tileSize = header.tileSize;
	int	width = header.tilesWide;
	int	height = header.tilesHigh;
	int	tileOffset = 1;
	int	tileBytes = tileSize * tileSize * 4;
	// find the level that fits
	while ( width * tileSize > 2048 || height * tileSize > 2048 ) {
		tileOffset += width * height;
		width >>= 1;
		if ( width < 1 ) {
			width = 1;
		}
		height >>= 1;
		if ( height < 1 ) {
			height = 1;
		}
	}

	byte *pic = (byte *)R_StaticAlloc( width * height * tileBytes );
	byte	*oldBlock = (byte *)_alloca( tileBytes );
	for ( int y = 0 ; y < height ; y++ ) {
		for ( int x = 0 ; x < width ; x++ ) {
			int tileNum = tileOffset + y * width + x;
			fileHandle->Seek( tileNum * tileBytes, FS_SEEK_SET );
			fileHandle->Read( oldBlock, tileBytes );

			for ( int yy = 0 ; yy < tileSize ; yy++ ) {
				memcpy( pic + ( ( y * tileSize + yy ) * width * tileSize + x * tileSize  ) * 4,
					oldBlock + yy * tileSize * 4, tileSize * 4 );
			}
		}
	}

	R_WriteTGA( outName.c_str(), pic, width * tileSize, height * tileSize, false );

	R_StaticFree( pic );

	delete fileHandle;
}
示例#7
0
/*
================
R_SmoothNormalMap
================
*/
static void R_SmoothNormalMap( byte* data, int width, int height )
{
	byte*	orig;
	int		i, j, k, l;
	idVec3	normal;
	byte*	out;
	static float	factors[3][3] =
	{
		{ 1, 1, 1 },
		{ 1, 1, 1 },
		{ 1, 1, 1 }
	};
	
	orig = ( byte* )R_StaticAlloc( width * height * 4, TAG_IMAGE );
	memcpy( orig, data, width * height * 4 );
	
	for( i = 0 ; i < width ; i++ )
	{
		for( j = 0 ; j < height ; j++ )
		{
			normal = vec3_origin;
			for( k = -1 ; k < 2 ; k++ )
			{
				for( l = -1 ; l < 2 ; l++ )
				{
					byte*	in;
					
					in = orig + ( ( ( j + l ) & ( height - 1 ) ) * width + ( ( i + k ) & ( width - 1 ) ) ) * 4;
					
					// ignore 000 and -1 -1 -1
					if( in[0] == 0 && in[1] == 0 && in[2] == 0 )
					{
						continue;
					}
					if( in[0] == 128 && in[1] == 128 && in[2] == 128 )
					{
						continue;
					}
					
					normal[0] += factors[k + 1][l + 1] * ( in[0] - 128 );
					normal[1] += factors[k + 1][l + 1] * ( in[1] - 128 );
					normal[2] += factors[k + 1][l + 1] * ( in[2] - 128 );
				}
			}
			normal.Normalize();
			out = data + ( j * width + i ) * 4;
			out[0] = ( byte )( 128 + 127 * normal[0] );
			out[1] = ( byte )( 128 + 127 * normal[1] );
			out[2] = ( byte )( 128 + 127 * normal[2] );
		}
	}
	
	R_StaticFree( orig );
}
示例#8
0
/*
=====================
R_CalcInteractionCullBits

We want to cull a little on the sloppy side, because the pre-clipping
of geometry to the lights in dmap will give many cases that are right
at the border. We throw things out on the border, because if any one
vertex is clearly inside, the entire triangle will be accepted.
=====================
*/
void R_CalcInteractionCullBits( const idRenderEntityLocal* ent, const srfTriangles_t* tri, const idRenderLightLocal* light, srfCullInfo_t& cullInfo )
{
	SCOPED_PROFILE_EVENT( "R_CalcInteractionCullBits" );
	
	if( cullInfo.cullBits != NULL )
	{
		return;
	}
	
	idPlane frustumPlanes[6];
	idRenderMatrix::GetFrustumPlanes( frustumPlanes, light->baseLightProject, true, true );
	
	int frontBits = 0;
	
	// cull the triangle surface bounding box
	for( int i = 0; i < 6; i++ )
	{
		R_GlobalPlaneToLocal( ent->modelMatrix, frustumPlanes[i], cullInfo.localClipPlanes[i] );
		
		// get front bits for the whole surface
		if( tri->bounds.PlaneDistance( cullInfo.localClipPlanes[i] ) >= LIGHT_CLIP_EPSILON )
		{
			frontBits |= 1 << i;
		}
	}
	
	// if the surface is completely inside the light frustum
	if( frontBits == ( ( 1 << 6 ) - 1 ) )
	{
		cullInfo.cullBits = LIGHT_CULL_ALL_FRONT;
		return;
	}
	
	cullInfo.cullBits = ( byte* ) R_StaticAlloc( tri->numVerts * sizeof( cullInfo.cullBits[0] ), TAG_RENDER_INTERACTION );
	memset( cullInfo.cullBits, 0, tri->numVerts * sizeof( cullInfo.cullBits[0] ) );
	
	for( int i = 0; i < 6; i++ )
	{
		// if completely infront of this clipping plane
		if( frontBits & ( 1 << i ) )
		{
			continue;
		}
		for( int j = 0; j < tri->numVerts; j++ )
		{
			float d = cullInfo.localClipPlanes[i].Distance( tri->verts[j].xyz );
			cullInfo.cullBits[j] |= ( d < LIGHT_CLIP_EPSILON ) << i;
		}
	}
}
示例#9
0
/*
==============
idRenderSystemLocal::CaptureRenderToFile
==============
*/
void idRenderSystemLocal::CaptureRenderToFile( const char *fileName, bool fixAlpha ) {
	if ( !R_IsInitialized() ) {
		return;
	}

	idScreenRect & rc = renderCrops[currentRenderCrop];

	guiModel->EmitFullScreen();
	guiModel->Clear();
	RenderCommandBuffers( frameData->cmdHead );


	if ( !vr->useFBO ) // koz
	{
		glReadBuffer( GL_BACK );
	}

	// include extra space for OpenGL padding to word boundaries
	int	c = ( rc.GetWidth() + 3 ) * rc.GetHeight();
	byte *data = (byte *)R_StaticAlloc( c * 3 );
	
	qglReadPixels( rc.x1, rc.y1, rc.GetWidth(), rc.GetHeight(), GL_RGB, GL_UNSIGNED_BYTE, data ); 

	byte *data2 = (byte *)R_StaticAlloc( c * 4 );

	for ( int i = 0 ; i < c ; i++ ) {
		data2[ i * 4 ] = data[ i * 3 ];
		data2[ i * 4 + 1 ] = data[ i * 3 + 1 ];
		data2[ i * 4 + 2 ] = data[ i * 3 + 2 ];
		data2[ i * 4 + 3 ] = 0xff;
	}

	R_WriteTGA( fileName, data2, rc.GetWidth(), rc.GetHeight(), true );

	R_StaticFree( data );
	R_StaticFree( data2 );
}
示例#10
0
/*
===================
RB_ShowIntensity

Debugging tool to see how much dynamic range a scene is using.
The greatest of the rgb values at each pixel will be used, with
the resulting color shading from red at 0 to green at 128 to blue at 255
===================
*/
static void RB_ShowIntensity() {
	byte	*colorReadback;
	int		i, j, c;

	if ( !r_showIntensity.GetBool() ) {
		return;
	}

	colorReadback = (byte *)R_StaticAlloc( renderSystem->GetWidth() * renderSystem->GetHeight() * 4, TAG_RENDER_TOOLS );
	qglReadPixels( 0, 0, renderSystem->GetWidth(), renderSystem->GetHeight(), GL_RGBA, GL_UNSIGNED_BYTE, colorReadback );

	c = renderSystem->GetWidth() * renderSystem->GetHeight() * 4;
	for ( i = 0; i < c; i+=4 ) {
		j = colorReadback[i];
		if ( colorReadback[i+1] > j ) {
			j = colorReadback[i+1];
		}
		if ( colorReadback[i+2] > j ) {
			j = colorReadback[i+2];
		}
		if ( j < 128 ) {
			colorReadback[i+0] = 2*(128-j);
			colorReadback[i+1] = 2*j;
			colorReadback[i+2] = 0;
		} else {
			colorReadback[i+0] = 0;
			colorReadback[i+1] = 2*(255-j);
			colorReadback[i+2] = 2*(j-128);
		}
	}

	// draw it back to the screen
	qglLoadIdentity();
	qglMatrixMode( GL_PROJECTION );
	GL_State( GLS_DEPTHFUNC_ALWAYS );
	qglPushMatrix();
	qglLoadIdentity(); 
    qglOrtho( 0, 1, 0, 1, -1, 1 );
	qglRasterPos2f( 0, 0 );
	qglPopMatrix();
	GL_Color( 1, 1, 1 );
	globalImages->BindNull();
	qglMatrixMode( GL_MODELVIEW );

	qglDrawPixels( renderSystem->GetWidth(), renderSystem->GetHeight(), GL_RGBA , GL_UNSIGNED_BYTE, colorReadback );

	R_StaticFree( colorReadback );
}
示例#11
0
/*
===================
RB_CountStencilBuffer

Print an overdraw count based on stencil index values
===================
*/
static void RB_CountStencilBuffer() {
	int		count;
	int		i;
	byte	*stencilReadback;


	stencilReadback = (byte *)R_StaticAlloc( renderSystem->GetWidth() * renderSystem->GetHeight(), TAG_RENDER_TOOLS );
	qglReadPixels( 0, 0, renderSystem->GetWidth(), renderSystem->GetHeight(), GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, stencilReadback );

	count = 0;
	for ( i = 0; i < renderSystem->GetWidth() * renderSystem->GetHeight(); i++ ) {
		count += stencilReadback[i];
	}

	R_StaticFree( stencilReadback );

	// print some stats (not supposed to do from back end in SMP...)
	common->Printf( "overdraw: %5.1f\n", (float)count/(renderSystem->GetWidth() * renderSystem->GetHeight())  );
}
示例#12
0
/*
=====================
R_CalcInteractionCullBits

We want to cull a little on the sloppy side, because the pre-clipping
of geometry to the lights in dmap will give many cases that are right
at the border we throw things out on the border, because if any one
vertex is clearly inside, the entire triangle will be accepted.
=====================
*/
void R_CalcInteractionCullBits( const idRenderEntityLocal *ent, const srfTriangles_t *tri, const idRenderLightLocal *light, srfCullInfo_t &cullInfo ) {
	int i, frontBits;

	if ( cullInfo.cullBits != NULL ) {
		return;
	}

	frontBits = 0;

	// cull the triangle surface bounding box
	for ( i = 0; i < 6; i++ ) {

		R_GlobalPlaneToLocal( ent->modelMatrix, -light->frustum[i], cullInfo.localClipPlanes[i] );

		// get front bits for the whole surface
		if ( tri->bounds.PlaneDistance( cullInfo.localClipPlanes[i] ) >= LIGHT_CLIP_EPSILON ) {
			frontBits |= 1<<i;
		}
	}

	// if the surface is completely inside the light frustum
	if ( frontBits == ( ( 1 << 6 ) - 1 ) ) {
		cullInfo.cullBits = LIGHT_CULL_ALL_FRONT;
		return;
	}

	cullInfo.cullBits = (byte *) R_StaticAlloc( tri->numVerts * sizeof( cullInfo.cullBits[0] ) );
	SIMDProcessor->Memset( cullInfo.cullBits, 0, tri->numVerts * sizeof( cullInfo.cullBits[0] ) );

	float *planeSide = (float *) _alloca16( tri->numVerts * sizeof( float ) );

	for ( i = 0; i < 6; i++ ) {
		// if completely infront of this clipping plane
		if ( frontBits & ( 1 << i ) ) {
			continue;
		}
		SIMDProcessor->Dot( planeSide, cullInfo.localClipPlanes[i], tri->verts, tri->numVerts );
		SIMDProcessor->CmpLT( cullInfo.cullBits, i, planeSide, LIGHT_CLIP_EPSILON, tri->numVerts );
	}
}
示例#13
0
/*
===================
RB_ShowDepthBuffer

Draw the depth buffer as colors
===================
*/
static void RB_ShowDepthBuffer() {
	void	*depthReadback;

	if ( !r_showDepth.GetBool() ) {
		return;
	}

	qglPushMatrix();
	qglLoadIdentity();
	qglMatrixMode( GL_PROJECTION );
	qglPushMatrix();
	qglLoadIdentity(); 
    qglOrtho( 0, 1, 0, 1, -1, 1 );
	qglRasterPos2f( 0, 0 );
	qglPopMatrix();
	qglMatrixMode( GL_MODELVIEW );
	qglPopMatrix();

	GL_State( GLS_DEPTHFUNC_ALWAYS );
	GL_Color( 1, 1, 1 );
	globalImages->BindNull();

	depthReadback = R_StaticAlloc( renderSystem->GetWidth() * renderSystem->GetHeight()*4, TAG_RENDER_TOOLS );
	memset( depthReadback, 0, renderSystem->GetWidth() * renderSystem->GetHeight()*4 );

	qglReadPixels( 0, 0, renderSystem->GetWidth(), renderSystem->GetHeight(), GL_DEPTH_COMPONENT , GL_FLOAT, depthReadback );

#if 0
	for ( i = 0; i < renderSystem->GetWidth() * renderSystem->GetHeight(); i++ ) {
		((byte *)depthReadback)[i*4] = 
		((byte *)depthReadback)[i*4+1] = 
		((byte *)depthReadback)[i*4+2] = 255 * ((float *)depthReadback)[i];
		((byte *)depthReadback)[i*4+3] = 1;
	}
#endif

	qglDrawPixels( renderSystem->GetWidth(), renderSystem->GetHeight(), GL_RGBA , GL_UNSIGNED_BYTE, depthReadback );
	R_StaticFree( depthReadback );
}
示例#14
0
/*
===================
RB_ScanStencilBuffer

Debugging tool to see what values are in the stencil buffer
===================
*/
void RB_ScanStencilBuffer() {
	int		counts[256];
	int		i;
	byte	*stencilReadback;

	memset( counts, 0, sizeof( counts ) );

	stencilReadback = (byte *)R_StaticAlloc( renderSystem->GetWidth() * renderSystem->GetHeight(), TAG_RENDER_TOOLS );
	qglReadPixels( 0, 0, renderSystem->GetWidth(), renderSystem->GetHeight(), GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, stencilReadback );

	for ( i = 0; i < renderSystem->GetWidth() * renderSystem->GetHeight(); i++ ) {
		counts[ stencilReadback[i] ]++;
	}

	R_StaticFree( stencilReadback );

	// print some stats (not supposed to do from back end in SMP...)
	common->Printf( "stencil values:\n" );
	for ( i = 0; i < 255; i++ ) {
		if ( counts[i] ) {
			common->Printf( "%i: %i\n", i, counts[i] );
		}
	}
}
示例#15
0
/*
=================
R_ClearedStaticAlloc
=================
*/
void *R_ClearedStaticAlloc( int bytes ) {
	void * buf = R_StaticAlloc( bytes );
	memset( buf, 0, bytes );
	return buf;
}
示例#16
0
void fhBaseRenderList::Init() {
	assert(!renderlistMemory);
	renderlistMemory = R_StaticAlloc(renderlistMaxSize);
}
示例#17
0
/*
==============
RenderBump_f

==============
*/
void RenderBump_f( const idCmdArgs &args ) {
	idRenderModel	*lowPoly;
	idStr	source;
	int		i, j;
	const char	*cmdLine;
	int		numRenderBumps;
	renderBump_t	*renderBumps, *rb = NULL;
	renderBump_t	opt;
	int		startTime, endTime;

	// update the screen as we print
	common->SetRefreshOnPrint( true );

	// there should be a single parameter, the filename for a game loadable low-poly model
	if ( args.Argc() != 2 ) {
		common->Error( "Usage: renderbump <lowPolyModel>" );
	}

	common->Printf( "----- Renderbump %s -----\n", args.Argv( 1 ) );

	startTime = Sys_Milliseconds();

	// get the lowPoly model
	source = args.Argv( 1 );
	lowPoly = renderModelManager->CheckModel( source );
	if ( !lowPoly ) {
		common->Error( "Can't load model %s", source.c_str() );
	}

	renderBumps = (renderBump_t *)R_StaticAlloc( lowPoly->NumSurfaces() * sizeof( *renderBumps ) );
	numRenderBumps = 0;
	for ( i = 0 ; i < lowPoly->NumSurfaces() ; i++ ) {
		const modelSurface_t	*ms = lowPoly->Surface( i );

		// default options
		memset( &opt, 0, sizeof( opt ) );
		opt.width = 512;
		opt.height = 512;
		opt.antiAlias = 1;
		opt.outline = 8;
		opt.traceFrac = 0.05f;

		// parse the renderbump parameters for this surface
		cmdLine = ms->shader->GetRenderBump();

		common->Printf( "surface %i, shader %s\nrenderBump = %s ", i,
			ms->shader->GetName(), cmdLine );

		if ( !ms->geometry ) {
			common->Printf( "(no geometry)\n" );
			continue;
		}

		idCmdArgs localArgs;
		localArgs.TokenizeString( cmdLine, false );

		if ( localArgs.Argc() < 2 ) {
			common->Printf( "(no action)\n" );
			continue;
		}

		common->Printf( "(rendering)\n" );

		for ( j = 0 ; j < localArgs.Argc() - 2; j++ ) {
			const char *s;

			s = localArgs.Argv( j );
			if ( s[0] == '-' ) {
				j++;
				s = localArgs.Argv( j );
				if ( s[0] == '\0' ) {
					continue;
				}
			}

			if ( !idStr::Icmp( s, "size" ) ) {
				if ( j + 2 >= localArgs.Argc() ) {
					j = localArgs.Argc();
					break;
				}
				opt.width = atoi( localArgs.Argv( j + 1 ) );
				opt.height = atoi( localArgs.Argv( j + 2 ) );
				j += 2;
			} else if ( !idStr::Icmp( s, "trace" ) ) {
				opt.traceFrac = atof( localArgs.Argv( j + 1 ) );
				j += 1;
			} else if ( !idStr::Icmp( s, "globalMap" ) ) {
				opt.saveGlobalMap = true;
			} else if ( !idStr::Icmp( s, "colorMap" ) ) {
				opt.saveColorMap = true;
			} else if ( !idStr::Icmp( s, "outline" ) ) {
				opt.outline = atoi( localArgs.Argv( j + 1 ) );
				j += 1;
			} else if ( !idStr::Icmp( s, "aa" ) ) {
				opt.antiAlias = atoi( localArgs.Argv( j + 1 ) );
				j += 1;
			} else {
				common->Printf( "WARNING: Unknown option \"%s\"\n", s );
				break;
			}
		}

		if ( j != ( localArgs.Argc() - 2 ) ) {
			common->Error( "usage: renderBump [-size width height] [-aa <1-2>] [globalMap] [colorMap] [-trace <0.01 - 1.0>] normalMapImageFile highPolyAseFile" );
		}
		idStr::Copynz( opt.outputName, localArgs.Argv( j ), sizeof( opt.outputName ) );
		idStr::Copynz( opt.highName, localArgs.Argv( j + 1 ), sizeof( opt.highName ) );

		// adjust size for anti-aliasing
		opt.width <<= opt.antiAlias;
		opt.height <<= opt.antiAlias;

		// see if we already have a renderbump going for another surface that this should use
		for ( j = 0 ; j < numRenderBumps ; j++ ) {
			rb = &renderBumps[j];

			if ( idStr::Icmp( rb->outputName, opt.outputName ) ) {
				continue;
			}
			// all the other parameters must match, or it is an error
			if ( idStr::Icmp( rb->highName, opt.highName) || rb->width != opt.width ||
				rb->height != opt.height || rb->antiAlias != opt.antiAlias ||
				rb->traceFrac != opt.traceFrac ) {
				common->Error( "mismatched renderbump parameters on image %s", rb->outputName );
				continue;
			}

			// saveGlobalMap will be a sticky option
			rb->saveGlobalMap = rb->saveGlobalMap | opt.saveGlobalMap;
			break;
		}

		// create a new renderbump if needed
		if ( j == numRenderBumps ) {
			numRenderBumps++;
			rb = &renderBumps[j];
			*rb = opt;

			InitRenderBump( rb );
		}

		// render the triangles for this surface
		RenderBumpTriangles( ms->geometry, rb );
	}

	//
	// anti-alias and write out all renderbumps that we have completed
	//
	for ( i = 0 ; i < numRenderBumps ; i++ ) {
		WriteRenderBump( &renderBumps[i], opt.outline << opt.antiAlias );
	}

	R_StaticFree( renderBumps );

	endTime = Sys_Milliseconds();
	common->Printf( "%5.2f seconds for renderBump\n", ( endTime - startTime ) / 1000.0 );
	common->Printf( "---------- RenderBump Completed ----------\n" );

	// stop updating the screen as we print
	common->SetRefreshOnPrint( false );
}