/*
================
EmitSurface
================
*/
void idGuiModel::EmitSurface( guiModelSurface_t *surf, float modelMatrix[16], float modelViewMatrix[16], bool depthHack ) {
	srfTriangles_t	*tri;
	if( surf->numVerts == 0 ) {
		return;		// nothing in the surface
	}
	// copy verts and indexes
	tri = ( srfTriangles_t * )R_ClearedFrameAlloc( sizeof( *tri ) );
	tri->numIndexes = surf->numIndexes;
	tri->numVerts = surf->numVerts;
	tri->indexes = ( glIndex_t * )R_FrameAlloc( tri->numIndexes * sizeof( tri->indexes[0] ) );
	memcpy( tri->indexes, &indexes[surf->firstIndex], tri->numIndexes * sizeof( tri->indexes[0] ) );
	// we might be able to avoid copying these and just let them reference the list vars
	// but some things, like deforms and recursive
	// guis, need to access the verts in cpu space, not just through the vertex range
	tri->verts = ( idDrawVert * )R_FrameAlloc( tri->numVerts * sizeof( tri->verts[0] ) );
	memcpy( tri->verts, &verts[surf->firstVert], tri->numVerts * sizeof( tri->verts[0] ) );
	// move the verts to the vertex cache
	tri->ambientCache = vertexCache.AllocFrameTemp( tri->verts, tri->numVerts * sizeof( tri->verts[0] ) );
	// if we are out of vertex cache, don't create the surface
	if( !tri->ambientCache ) {
		return;
	}
	renderEntity_t renderEntity;
	memset( &renderEntity, 0, sizeof( renderEntity ) );
	memcpy( renderEntity.shaderParms, surf->color, sizeof( surf->color ) );
	viewEntity_t *guiSpace = ( viewEntity_t * )R_ClearedFrameAlloc( sizeof( *guiSpace ) );
	memcpy( guiSpace->modelMatrix, modelMatrix, sizeof( guiSpace->modelMatrix ) );
	memcpy( guiSpace->modelViewMatrix, modelViewMatrix, sizeof( guiSpace->modelViewMatrix ) );
	guiSpace->weaponDepthHack = depthHack;
	// add the surface, which might recursively create another gui
	R_AddDrawSurf( tri, guiSpace, &renderEntity, surf->material, tr.viewDef->scissor );
}
Пример #2
0
/*
=============
R_SetEntityDefViewEntity

If the entityDef isn't already on the viewEntity list, create
a viewEntity and add it to the list with an empty scissor rect.

This does not instantiate dynamic models for the entity yet.
=============
*/
viewEntity_t *R_SetEntityDefViewEntity( idRenderEntityLocal *def )
{
	viewEntity_t *vModel;
	
	if( def->viewCount == tr.viewCount )
	{
		return def->viewEntity;
	}
	def->viewCount = tr.viewCount;
	
	// set the model and modelview matricies
	vModel = ( viewEntity_t * ) R_ClearedFrameAlloc( sizeof( *vModel ) );
	vModel->entityDef = def;
	
	// the scissorRect will be expanded as the model bounds is accepted into visible portal chains
	vModel->scissorRect.Clear();
	
	// copy the model and weapon depth hack for back-end use
	vModel->modelDepthHack = def->parms.modelDepthHack;
	vModel->weaponDepthHack = def->parms.weaponDepthHack;
	
	R_AxisToModelMatrix( def->parms.axis, def->parms.origin, vModel->modelMatrix );
	
	// we may not have a viewDef if we are just creating shadows at entity creation time
	if( tr.viewDef )
	{
		R_MatrixMultiply( vModel->modelMatrix, tr.viewDef->worldSpace.modelViewMatrix, vModel->modelViewMatrix );
		
		vModel->next = tr.viewDef->viewEntitys;
		tr.viewDef->viewEntitys = vModel;
	}
	def->viewEntity = vModel;
	
	return vModel;
}
Пример #3
0
/*
=============
R_SetLightDefViewLight

If the lightDef is not already on the viewLight list, create
a viewLight and add it to the list with an empty scissor rect.
=============
*/
viewLight_t* R_SetLightDefViewLight( idRenderLightLocal* light )
{
	if( light->viewCount == tr.viewCount )
	{
		// already set up for this frame
		return light->viewLight;
	}
	light->viewCount = tr.viewCount;
	
	// add to the view light chain
	viewLight_t* vLight = ( viewLight_t* )R_ClearedFrameAlloc( sizeof( *vLight ), FRAME_ALLOC_VIEW_LIGHT );
	vLight->lightDef = light;
	
	// the scissorRect will be expanded as the light bounds is accepted into visible portal chains
	// and the scissor will be reduced in R_AddSingleLight based on the screen space projection
	vLight->scissorRect.Clear();
	
	// link the view light
	vLight->next = tr.viewDef->viewLights;
	tr.viewDef->viewLights = vLight;
	
	light->viewLight = vLight;
	
	return vLight;
}
Пример #4
0
/*
=============
R_SetLightDefViewLight

If the lightDef isn't already on the viewLight list, create
a viewLight and add it to the list with an empty scissor rect.
=============
*/
viewLight_t *R_SetLightDefViewLight( idRenderLightLocal *light )
{
	viewLight_t *vLight;
	
	if( light->viewCount == tr.viewCount )
	{
		return light->viewLight;
	}
	light->viewCount = tr.viewCount;
	
	// add to the view light chain
	vLight = ( viewLight_t * ) R_ClearedFrameAlloc( sizeof( *vLight ) );
	vLight->lightDef = light;
	
	// the scissorRect will be expanded as the light bounds is accepted into visible portal chains
	vLight->scissorRect.Clear();
	
	// calculate the shadow cap optimization states
	vLight->viewInsideLight = R_TestPointInViewLight( tr.viewDef->renderView.vieworg, light );
	
	if( !vLight->viewInsideLight )
	{
		vLight->viewSeesShadowPlaneBits = 0;
		
		for( int i = 0; i < light->numShadowFrustums; i++ )
		{
			float d = light->shadowFrustums[i].planes[5].Distance( tr.viewDef->renderView.vieworg );
			
			if( d < INSIDE_LIGHT_FRUSTUM_SLOP )
			{
				vLight->viewSeesShadowPlaneBits |= 1 << i;
			}
		}
	}
	else
	{
		// this should not be referenced in this case
		vLight->viewSeesShadowPlaneBits = 63;
	}
	
	// copy data used by backend
	vLight->globalLightOrigin = light->globalLightOrigin;
	vLight->lightProject[0] = light->lightProject[0];
	vLight->lightProject[1] = light->lightProject[1];
	vLight->lightProject[2] = light->lightProject[2];
	vLight->lightProject[3] = light->lightProject[3];
	vLight->fogPlane = light->frustum[5];
	vLight->frustumTris = light->frustumTris;
	vLight->falloffImage = light->falloffImage;
	vLight->lightShader = light->lightShader;
	vLight->shaderRegisters = NULL;		// allocated and evaluated in R_AddLightSurfaces
	
	// link the view light
	vLight->next = tr.viewDef->viewLights;
	tr.viewDef->viewLights = vLight;
	light->viewLight = vLight;
	
	return vLight;
}
/*
================
idGuiModel::EmitFullScreen

Creates a view that covers the screen and emit the surfaces
================
*/
void idGuiModel::EmitFullScreen( void ) {
	viewDef_t	*viewDef;
	if( surfaces[0].numVerts == 0 ) {
		return;
	}
	viewDef = ( viewDef_t * )R_ClearedFrameAlloc( sizeof( *viewDef ) );
	// for gui editor
	if( !tr.viewDef || !tr.viewDef->isEditor ) {
		viewDef->renderView.x = 0;
		viewDef->renderView.y = 0;
		viewDef->renderView.width = SCREEN_WIDTH;
		viewDef->renderView.height = SCREEN_HEIGHT;
		tr.RenderViewToViewport( &viewDef->renderView, &viewDef->viewport );
		viewDef->scissor.x1 = 0;
		viewDef->scissor.y1 = 0;
		viewDef->scissor.x2 = viewDef->viewport.x2 - viewDef->viewport.x1;
		viewDef->scissor.y2 = viewDef->viewport.y2 - viewDef->viewport.y1;
	} else {
		viewDef->renderView.x = tr.viewDef->renderView.x;
		viewDef->renderView.y = tr.viewDef->renderView.y;
		viewDef->renderView.width = tr.viewDef->renderView.width;
		viewDef->renderView.height = tr.viewDef->renderView.height;
		viewDef->viewport.x1 = tr.viewDef->renderView.x;
		viewDef->viewport.x2 = tr.viewDef->renderView.x + tr.viewDef->renderView.width;
		viewDef->viewport.y1 = tr.viewDef->renderView.y;
		viewDef->viewport.y2 = tr.viewDef->renderView.y + tr.viewDef->renderView.height;
		viewDef->scissor.x1 = tr.viewDef->scissor.x1;
		viewDef->scissor.y1 = tr.viewDef->scissor.y1;
		viewDef->scissor.x2 = tr.viewDef->scissor.x2;
		viewDef->scissor.y2 = tr.viewDef->scissor.y2;
	}
	viewDef->floatTime = tr.frameShaderTime;
	// glOrtho( 0, 640, 480, 0, 0, 1 );		// always assume 640x480 virtual coordinates
	viewDef->projectionMatrix[0] = 2.0f / 640.0f;
	viewDef->projectionMatrix[5] = -2.0f / 480.0f;
	viewDef->projectionMatrix[10] = -2.0f / 1.0f;
	viewDef->projectionMatrix[12] = -1.0f;
	viewDef->projectionMatrix[13] = 1.0f;
	viewDef->projectionMatrix[14] = -1.0f;
	viewDef->projectionMatrix[15] = 1.0f;
	viewDef->worldSpace.modelViewMatrix[0] = 1.0f;
	viewDef->worldSpace.modelViewMatrix[5] = 1.0f;
	viewDef->worldSpace.modelViewMatrix[10] = 1.0f;
	viewDef->worldSpace.modelViewMatrix[15] = 1.0f;
	viewDef->maxDrawSurfs = surfaces.Num();
	viewDef->drawSurfs = ( drawSurf_t ** )R_FrameAlloc( viewDef->maxDrawSurfs * sizeof( viewDef->drawSurfs[0] ) );
	viewDef->numDrawSurfs = 0;
	viewDef_t	*oldViewDef = tr.viewDef;
	tr.viewDef = viewDef;
	// add the surfaces to this view
	for( int i = 0 ; i < surfaces.Num() ; i++ ) {
		EmitSurface( &surfaces[i], viewDef->worldSpace.modelMatrix, viewDef->worldSpace.modelViewMatrix, false );
	}
	tr.viewDef = oldViewDef;
	// add the command to draw this view
	R_AddDrawViewCmd( viewDef );
}
Пример #6
0
/*
=============
R_SetEntityDefViewEntity

If the entityDef is not already on the viewEntity list, create
a viewEntity and add it to the list with an empty scissor rect.
=============
*/
viewEntity_t *R_SetEntityDefViewEntity( idRenderEntityLocal *def ) {
	if ( def->viewCount == tr.viewCount ) {
		// already set up for this frame
		return def->viewEntity;
	}
	def->viewCount = tr.viewCount;

	viewEntity_t * vModel = (viewEntity_t *)R_ClearedFrameAlloc( sizeof( *vModel ), FRAME_ALLOC_VIEW_ENTITY );
	vModel->entityDef = def;

	// the scissorRect will be expanded as the model bounds is accepted into visible portal chains
	// It will remain clear if the model is only needed for shadows.
	vModel->scissorRect.Clear();

	vModel->next = tr.viewDef->viewEntitys;
	tr.viewDef->viewEntitys = vModel;

	def->viewEntity = vModel;

	return vModel;
}
Пример #7
0
/*
================
idGuiModel::EmitFullScreen

Creates a view that covers the screen and emit the surfaces
================
*/
void idGuiModel::EmitFullScreen()
{

	if( surfaces[0].numIndexes == 0 )
	{
		return;
	}
	
	SCOPED_PROFILE_EVENT( "Gui::EmitFullScreen" );
	
	viewDef_t* viewDef = ( viewDef_t* )R_ClearedFrameAlloc( sizeof( *viewDef ), FRAME_ALLOC_VIEW_DEF );
	viewDef->is2Dgui = true;
	tr.GetCroppedViewport( &viewDef->viewport );
	
	bool stereoEnabled = ( renderSystem->GetStereo3DMode() != STEREO3D_OFF );
	if( stereoEnabled )
	{
		const float screenSeparation = GetScreenSeparationForGuis();
		
		// this will be negated on the alternate eyes, both rendered each frame
		viewDef->renderView.stereoScreenSeparation = screenSeparation;
		
		extern idCVar stereoRender_swapEyes;
		viewDef->renderView.viewEyeBuffer = 0;	// render to both buffers
		if( stereoRender_swapEyes.GetBool() )
		{
			viewDef->renderView.stereoScreenSeparation = -screenSeparation;
		}
	}
	
	viewDef->scissor.x1 = 0;
	viewDef->scissor.y1 = 0;
	viewDef->scissor.x2 = viewDef->viewport.x2 - viewDef->viewport.x1;
	viewDef->scissor.y2 = viewDef->viewport.y2 - viewDef->viewport.y1;
	
	viewDef->projectionMatrix[0 * 4 + 0] = 2.0f / renderSystem->GetVirtualWidth();
	viewDef->projectionMatrix[0 * 4 + 1] = 0.0f;
	viewDef->projectionMatrix[0 * 4 + 2] = 0.0f;
	viewDef->projectionMatrix[0 * 4 + 3] = 0.0f;
	
	viewDef->projectionMatrix[1 * 4 + 0] = 0.0f;
	viewDef->projectionMatrix[1 * 4 + 1] = -2.0f / renderSystem->GetVirtualHeight();
	viewDef->projectionMatrix[1 * 4 + 2] = 0.0f;
	viewDef->projectionMatrix[1 * 4 + 3] = 0.0f;
	
	viewDef->projectionMatrix[2 * 4 + 0] = 0.0f;
	viewDef->projectionMatrix[2 * 4 + 1] = 0.0f;
	viewDef->projectionMatrix[2 * 4 + 2] = -2.0f;
	viewDef->projectionMatrix[2 * 4 + 3] = 0.0f;
	
	viewDef->projectionMatrix[3 * 4 + 0] = -1.0f;
	viewDef->projectionMatrix[3 * 4 + 1] = 1.0f;
	viewDef->projectionMatrix[3 * 4 + 2] = -1.0f;
	viewDef->projectionMatrix[3 * 4 + 3] = 1.0f;
	
	// make a tech5 renderMatrix for faster culling
	idRenderMatrix::Transpose( *( idRenderMatrix* )viewDef->projectionMatrix, viewDef->projectionRenderMatrix );
	
	viewDef->worldSpace.modelMatrix[0 * 4 + 0] = 1.0f;
	viewDef->worldSpace.modelMatrix[1 * 4 + 1] = 1.0f;
	viewDef->worldSpace.modelMatrix[2 * 4 + 2] = 1.0f;
	viewDef->worldSpace.modelMatrix[3 * 4 + 3] = 1.0f;
	
	viewDef->worldSpace.modelViewMatrix[0 * 4 + 0] = 1.0f;
	viewDef->worldSpace.modelViewMatrix[1 * 4 + 1] = 1.0f;
	viewDef->worldSpace.modelViewMatrix[2 * 4 + 2] = 1.0f;
	viewDef->worldSpace.modelViewMatrix[3 * 4 + 3] = 1.0f;
	
	viewDef->maxDrawSurfs = surfaces.Num();
	viewDef->drawSurfs = ( drawSurf_t** )R_FrameAlloc( viewDef->maxDrawSurfs * sizeof( viewDef->drawSurfs[0] ), FRAME_ALLOC_DRAW_SURFACE_POINTER );
	viewDef->numDrawSurfs = 0;
	
#if 1
	// RB: give renderView the current time to calculate 2D shader effects
	int shaderTime = tr.frameShaderTime * 1000; //Sys_Milliseconds();
	viewDef->renderView.time[0] = shaderTime;
	viewDef->renderView.time[1] = shaderTime;
	// RB end
#endif
	
	viewDef_t* oldViewDef = tr.viewDef;
	tr.viewDef = viewDef;
	
	EmitSurfaces( viewDef->worldSpace.modelMatrix, viewDef->worldSpace.modelViewMatrix,
				  false /* depthHack */ , stereoEnabled /* stereoDepthSort */, false /* link as entity */ );
				  
	tr.viewDef = oldViewDef;
	
	// add the command to draw this view
	R_AddDrawViewCmd( viewDef, true );
}
Пример #8
0
/*
================
EmitSurfaces

For full screen GUIs, we can add in per-surface stereoscopic depth effects
================
*/
void idGuiModel::EmitSurfaces( float modelMatrix[16], float modelViewMatrix[16],
							   bool depthHack, bool allowFullScreenStereoDepth, bool linkAsEntity )
{

	viewEntity_t* guiSpace = ( viewEntity_t* )R_ClearedFrameAlloc( sizeof( *guiSpace ), FRAME_ALLOC_VIEW_ENTITY );
	memcpy( guiSpace->modelMatrix, modelMatrix, sizeof( guiSpace->modelMatrix ) );
	memcpy( guiSpace->modelViewMatrix, modelViewMatrix, sizeof( guiSpace->modelViewMatrix ) );
	guiSpace->weaponDepthHack = depthHack;
	guiSpace->isGuiSurface = true;
	
	// If this is an in-game gui, we need to be able to find the matrix again for head mounted
	// display bypass matrix fixup.
	if( linkAsEntity )
	{
		guiSpace->next = tr.viewDef->viewEntitys;
		tr.viewDef->viewEntitys = guiSpace;
	}
	
	//---------------------------
	// make a tech5 renderMatrix
	//---------------------------
	idRenderMatrix viewMat;
	idRenderMatrix::Transpose( *( idRenderMatrix* )modelViewMatrix, viewMat );
	idRenderMatrix::Multiply( tr.viewDef->projectionRenderMatrix, viewMat, guiSpace->mvp );
	if( depthHack )
	{
		idRenderMatrix::ApplyDepthHack( guiSpace->mvp );
	}
	
	// to allow 3D-TV effects in the menu system, we define surface flags to set
	// depth fractions between 0=screen and 1=infinity, which directly modulate the
	// screenSeparation parameter for an X offset.
	// The value is stored in the drawSurf sort value, which adjusts the matrix in the
	// backend.
	float defaultStereoDepth = stereoRender_defaultGuiDepth.GetFloat();	// default to at-screen
	
	// add the surfaces to this view
	for( int i = 0; i < surfaces.Num(); i++ )
	{
		const guiModelSurface_t& guiSurf = surfaces[i];
		if( guiSurf.numIndexes == 0 )
		{
			continue;
		}
		
		const idMaterial* shader = guiSurf.material;
		drawSurf_t* drawSurf = ( drawSurf_t* )R_FrameAlloc( sizeof( *drawSurf ), FRAME_ALLOC_DRAW_SURFACE );
		
		drawSurf->numIndexes = guiSurf.numIndexes;
		drawSurf->ambientCache = vertexBlock;
		// build a vertCacheHandle_t that points inside the allocated block
		drawSurf->indexCache = indexBlock + ( ( int64 )( guiSurf.firstIndex * sizeof( triIndex_t ) ) << VERTCACHE_OFFSET_SHIFT );
		drawSurf->shadowCache = 0;
		drawSurf->jointCache = 0;
		drawSurf->frontEndGeo = NULL;
		drawSurf->space = guiSpace;
		drawSurf->material = shader;
		drawSurf->extraGLState = guiSurf.glState;
		drawSurf->scissorRect = tr.viewDef->scissor;
		drawSurf->sort = shader->GetSort();
		drawSurf->renderZFail = 0;
		// process the shader expressions for conditionals / color / texcoords
		const float*	constRegs = shader->ConstantRegisters();
		if( constRegs )
		{
			// shader only uses constant values
			drawSurf->shaderRegisters = constRegs;
		}
		else
		{
			float* regs = ( float* )R_FrameAlloc( shader->GetNumRegisters() * sizeof( float ), FRAME_ALLOC_SHADER_REGISTER );
			drawSurf->shaderRegisters = regs;
			shader->EvaluateRegisters( regs, shaderParms, tr.viewDef->renderView.shaderParms, tr.viewDef->renderView.time[1] * 0.001f, NULL );
		}
		R_LinkDrawSurfToView( drawSurf, tr.viewDef );
		if( allowFullScreenStereoDepth )
		{
			// override sort with the stereoDepth
			//drawSurf->sort = stereoDepth;
			
			switch( guiSurf.stereoType )
			{
				case STEREO_DEPTH_TYPE_NEAR:
					drawSurf->sort = STEREO_DEPTH_NEAR;
					break;
				case STEREO_DEPTH_TYPE_MID:
					drawSurf->sort = STEREO_DEPTH_MID;
					break;
				case STEREO_DEPTH_TYPE_FAR:
					drawSurf->sort = STEREO_DEPTH_FAR;
					break;
				case STEREO_DEPTH_TYPE_NONE:
				default:
					drawSurf->sort = defaultStereoDepth;
					break;
			}
		}
	}
}
Пример #9
0
/*
===================
R_AddSingleLight

May be run in parallel.

Sets vLight->removeFromList to true if the light should be removed from the list.
Builds a chain of entities that need to be added for shadows only off vLight->shadowOnlyViewEntities.
Allocates and fills in vLight->entityInteractionState.

Calc the light shader values, removing any light from the viewLight list
if it is determined to not have any visible effect due to being flashed off or turned off.

Add any precomputed shadow volumes.
===================
*/
static void R_AddSingleLight( viewLight_t* vLight )
{
	// until proven otherwise
	vLight->removeFromList = true;
	vLight->shadowOnlyViewEntities = NULL;
	vLight->preLightShadowVolumes = NULL;
	
	// globals we really should pass in...
	const viewDef_t* viewDef = tr.viewDef;
	
	const idRenderLightLocal* light = vLight->lightDef;
	const idMaterial* lightShader = light->lightShader;
	if( lightShader == NULL )
	{
		common->Error( "R_AddSingleLight: NULL lightShader" );
		return;
	}
	
	SCOPED_PROFILE_EVENT( lightShader->GetName() );
	
	// see if we are suppressing the light in this view
	if( !r_skipSuppress.GetBool() )
	{
		if( light->parms.suppressLightInViewID && light->parms.suppressLightInViewID == viewDef->renderView.viewID )
		{
			return;
		}
		if( light->parms.allowLightInViewID && light->parms.allowLightInViewID != viewDef->renderView.viewID )
		{
			return;
		}
	}
	
	// evaluate the light shader registers
	float* lightRegs = ( float* )R_FrameAlloc( lightShader->GetNumRegisters() * sizeof( float ), FRAME_ALLOC_SHADER_REGISTER );
	lightShader->EvaluateRegisters( lightRegs, light->parms.shaderParms, viewDef->renderView.shaderParms,
									tr.viewDef->renderView.time[0] * 0.001f, light->parms.referenceSound );
									
	// if this is a purely additive light and no stage in the light shader evaluates
	// to a positive light value, we can completely skip the light
	if( !lightShader->IsFogLight() && !lightShader->IsBlendLight() )
	{
		int lightStageNum;
		for( lightStageNum = 0; lightStageNum < lightShader->GetNumStages(); lightStageNum++ )
		{
			const shaderStage_t*	lightStage = lightShader->GetStage( lightStageNum );
			
			// ignore stages that fail the condition
			if( !lightRegs[ lightStage->conditionRegister ] )
			{
				continue;
			}
			
			const int* registers = lightStage->color.registers;
			
			// snap tiny values to zero
			if( lightRegs[ registers[0] ] < 0.001f )
			{
				lightRegs[ registers[0] ] = 0.0f;
			}
			if( lightRegs[ registers[1] ] < 0.001f )
			{
				lightRegs[ registers[1] ] = 0.0f;
			}
			if( lightRegs[ registers[2] ] < 0.001f )
			{
				lightRegs[ registers[2] ] = 0.0f;
			}
			
			if( lightRegs[ registers[0] ] > 0.0f ||
					lightRegs[ registers[1] ] > 0.0f ||
					lightRegs[ registers[2] ] > 0.0f )
			{
				break;
			}
		}
		
		if( lightStageNum == lightShader->GetNumStages() )
		{
			// we went through all the stages and didn't find one that adds anything
			// remove the light from the viewLights list, and change its frame marker
			// so interaction generation doesn't think the light is visible and
			// create a shadow for it
			return;
		}
	}
	
	
	//--------------------------------------------
	// copy data used by backend
	//--------------------------------------------
	vLight->globalLightOrigin = light->globalLightOrigin;
	vLight->lightProject[0] = light->lightProject[0];
	vLight->lightProject[1] = light->lightProject[1];
	vLight->lightProject[2] = light->lightProject[2];
	vLight->lightProject[3] = light->lightProject[3];
	
	// the fog plane is the light far clip plane
	idPlane fogPlane(	light->baseLightProject[2][0] - light->baseLightProject[3][0],
						light->baseLightProject[2][1] - light->baseLightProject[3][1],
						light->baseLightProject[2][2] - light->baseLightProject[3][2],
						light->baseLightProject[2][3] - light->baseLightProject[3][3] );
	const float planeScale = idMath::InvSqrt( fogPlane.Normal().LengthSqr() );
	vLight->fogPlane[0] = fogPlane[0] * planeScale;
	vLight->fogPlane[1] = fogPlane[1] * planeScale;
	vLight->fogPlane[2] = fogPlane[2] * planeScale;
	vLight->fogPlane[3] = fogPlane[3] * planeScale;
	
	// copy the matrix for deforming the 'zeroOneCubeModel' to exactly cover the light volume in world space
	vLight->inverseBaseLightProject = light->inverseBaseLightProject;
	
	// RB begin
	vLight->baseLightProject = light->baseLightProject;
	vLight->pointLight = light->parms.pointLight;
	vLight->parallel = light->parms.parallel;
	vLight->lightCenter = light->parms.lightCenter;
	// RB end
	
	vLight->falloffImage = light->falloffImage;
	vLight->lightShader = light->lightShader;
	vLight->shaderRegisters = lightRegs;
	
	const bool lightCastsShadows = light->LightCastsShadows();
	
	if( r_useLightScissors.GetInteger() != 0 )
	{
		// Calculate the matrix that projects the zero-to-one cube to exactly cover the
		// light frustum in clip space.
		idRenderMatrix invProjectMVPMatrix;
		idRenderMatrix::Multiply( viewDef->worldSpace.mvp, light->inverseBaseLightProject, invProjectMVPMatrix );
		
		// Calculate the projected bounds, either not clipped at all, near clipped, or fully clipped.
		idBounds projected;
		if( r_useLightScissors.GetInteger() == 1 )
		{
			idRenderMatrix::ProjectedBounds( projected, invProjectMVPMatrix, bounds_zeroOneCube );
		}
		else if( r_useLightScissors.GetInteger() == 2 )
		{
			idRenderMatrix::ProjectedNearClippedBounds( projected, invProjectMVPMatrix, bounds_zeroOneCube );
		}
		else
		{
			idRenderMatrix::ProjectedFullyClippedBounds( projected, invProjectMVPMatrix, bounds_zeroOneCube );
		}
		
		if( projected[0][2] >= projected[1][2] )
		{
			// the light was culled to the view frustum
			return;
		}
		
		float screenWidth = ( float )viewDef->viewport.x2 - ( float )viewDef->viewport.x1;
		float screenHeight = ( float )viewDef->viewport.y2 - ( float )viewDef->viewport.y1;
		
		idScreenRect lightScissorRect;
		lightScissorRect.x1 = idMath::Ftoi( projected[0][0] * screenWidth );
		lightScissorRect.x2 = idMath::Ftoi( projected[1][0] * screenWidth );
		lightScissorRect.y1 = idMath::Ftoi( projected[0][1] * screenHeight );
		lightScissorRect.y2 = idMath::Ftoi( projected[1][1] * screenHeight );
		lightScissorRect.Expand();
		
		vLight->scissorRect.Intersect( lightScissorRect );
		vLight->scissorRect.zmin = projected[0][2];
		vLight->scissorRect.zmax = projected[1][2];
		
		// RB: calculate shadow LOD similar to Q3A .md3 LOD code
		vLight->shadowLOD = 0;
		
		if( r_useShadowMapping.GetBool() && lightCastsShadows )
		{
			float           flod, lodscale;
			float           projectedRadius;
			int             lod;
			int             numLods;
			
			numLods = MAX_SHADOWMAP_RESOLUTIONS;
			
			// compute projected bounding sphere
			// and use that as a criteria for selecting LOD
			idVec3 center = projected.GetCenter();
			projectedRadius = projected.GetRadius( center );
			if( projectedRadius > 1.0f )
			{
				projectedRadius = 1.0f;
			}
			
			if( projectedRadius != 0 )
			{
				lodscale = r_shadowMapLodScale.GetFloat();
				
				if( lodscale > 20 )
					lodscale = 20;
					
				flod = 1.0f - projectedRadius * lodscale;
			}
			else
			{
				// object intersects near view plane, e.g. view weapon
				flod = 0;
			}
			
			flod *= numLods;
			
			if( flod < 0 )
			{
				flod = 0;
			}
			
			lod = idMath::Ftoi( flod );
			
			if( lod >= numLods )
			{
				//lod = numLods - 1;
			}
			
			lod += r_shadowMapLodBias.GetInteger();
			
			if( lod < 0 )
			{
				lod = 0;
			}
			
			if( lod >= numLods )
			{
				// don't draw any shadow
				//lod = -1;
				
				lod = numLods - 1;
			}
			
			// 2048^2 ultra quality is only for cascaded shadow mapping with sun lights
			if( lod == 0 && !light->parms.parallel )
			{
				lod = 1;
			}
			
			vLight->shadowLOD = lod;
		}
		// RB end
	}
	
	// this one stays on the list
	vLight->removeFromList = false;
	
	//--------------------------------------------
	// create interactions with all entities the light may touch, and add viewEntities
	// that may cast shadows, even if they aren't directly visible.  Any real work
	// will be deferred until we walk through the viewEntities
	//--------------------------------------------
	const int renderViewID = viewDef->renderView.viewID;
	
	// this bool array will be set true whenever the entity will visibly interact with the light
	vLight->entityInteractionState = ( byte* )R_ClearedFrameAlloc( light->world->entityDefs.Num() * sizeof( vLight->entityInteractionState[0] ), FRAME_ALLOC_INTERACTION_STATE );
	
	idInteraction** const interactionTableRow = light->world->interactionTable + light->index * light->world->interactionTableWidth;
	
	for( areaReference_t* lref = light->references; lref != NULL; lref = lref->ownerNext )
	{
		portalArea_t* area = lref->area;
		
		// some lights have their center of projection outside the world, but otherwise
		// we want to ignore areas that are not connected to the light center due to a closed door
		if( light->areaNum != -1 && r_useAreasConnectedForShadowCulling.GetInteger() == 2 )
		{
			if( !light->world->AreasAreConnected( light->areaNum, area->areaNum, PS_BLOCK_VIEW ) )
			{
				// can't possibly be seen or shadowed
				continue;
			}
		}
		
		// check all the models in this area
		for( areaReference_t* eref = area->entityRefs.areaNext; eref != &area->entityRefs; eref = eref->areaNext )
		{
			idRenderEntityLocal* edef = eref->entity;
			
			if( vLight->entityInteractionState[ edef->index ] != viewLight_t::INTERACTION_UNCHECKED )
			{
				continue;
			}
			// until proven otherwise
			vLight->entityInteractionState[ edef->index ] = viewLight_t::INTERACTION_NO;
			
			// The table is updated at interaction::AllocAndLink() and interaction::UnlinkAndFree()
			const idInteraction* inter = interactionTableRow[ edef->index ];
			
			const renderEntity_t& eParms = edef->parms;
			const idRenderModel* eModel = eParms.hModel;
			
			// a large fraction of static entity / light pairs will still have no interactions even though
			// they are both present in the same area(s)
			if( eModel != NULL && !eModel->IsDynamicModel() && inter == INTERACTION_EMPTY )
			{
				// the interaction was statically checked, and it didn't generate any surfaces,
				// so there is no need to force the entity onto the view list if it isn't
				// already there
				continue;
			}
			
			// We don't want the lights on weapons to illuminate anything else.
			// There are two assumptions here -- that allowLightInViewID is only
			// used for weapon lights, and that all weapons will have weaponDepthHack.
			// A more general solution would be to have an allowLightOnEntityID field.
			// HACK: the armor-mounted flashlight is a private spot light, which is probably
			// wrong -- you would expect to see them in multiplayer.
			//	if( light->parms.allowLightInViewID && light->parms.pointLight && !eParms.weaponDepthHack )
			//	{
			//		continue;
			//	}
			
			// non-shadow casting entities don't need to be added if they aren't
			// directly visible
			if( ( eParms.noShadow || ( eModel && !eModel->ModelHasShadowCastingSurfaces() ) ) && !edef->IsDirectlyVisible() )
			{
				continue;
			}
			
			// if the model doesn't accept lighting or cast shadows, it doesn't need to be added
			if( eModel && !eModel->ModelHasInteractingSurfaces() && !eModel->ModelHasShadowCastingSurfaces() )
			{
				continue;
			}
			
			// no interaction present, so either the light or entity has moved
			// assert( lightHasMoved || edef->entityHasMoved );
			if( inter == NULL )
			{
				// some big outdoor meshes are flagged to not create any dynamic interactions
				// when the level designer knows that nearby moving lights shouldn't actually hit them
				if( eParms.noDynamicInteractions )
				{
					continue;
				}
				
				// do a check of the entity reference bounds against the light frustum to see if they can't
				// possibly interact, despite sharing one or more world areas
				if( R_CullModelBoundsToLight( light, edef->localReferenceBounds, edef->modelRenderMatrix ) )
				{
					continue;
				}
			}
			
			// we now know that the entity and light do overlap
			
			if( edef->IsDirectlyVisible() )
			{
				// entity is directly visible, so the interaction is definitely needed
				vLight->entityInteractionState[ edef->index ] = viewLight_t::INTERACTION_YES;
				continue;
			}
			
			// the entity is not directly visible, but if we can tell that it may cast
			// shadows onto visible surfaces, we must make a viewEntity for it
			if( !lightCastsShadows )
			{
				// surfaces are never shadowed in this light
				continue;
			}
			// if we are suppressing its shadow in this view (player shadows, etc), skip
			if( !r_skipSuppress.GetBool() )
			{
				if( eParms.suppressShadowInViewID && eParms.suppressShadowInViewID == renderViewID )
				{
					continue;
				}
				if( eParms.suppressShadowInLightID && eParms.suppressShadowInLightID == light->parms.lightId )
				{
					continue;
				}
			}
			
			// should we use the shadow bounds from pre-calculated interactions?
			idBounds shadowBounds;
			R_ShadowBounds( edef->globalReferenceBounds, light->globalLightBounds, light->globalLightOrigin, shadowBounds );
			
			// this test is pointless if we knew the light was completely contained
			// in the view frustum, but the entity would also be directly visible in most
			// of those cases.
			
			// this doesn't say that the shadow can't effect anything, only that it can't
			// effect anything in the view, so we shouldn't set up a view entity
			if( idRenderMatrix::CullBoundsToMVP( viewDef->worldSpace.mvp, shadowBounds ) )
			{
				continue;
			}
			
			// debug tool to allow viewing of only one entity at a time
			if( r_singleEntity.GetInteger() >= 0 && r_singleEntity.GetInteger() != edef->index )
			{
				continue;
			}
			
			// we do need it for shadows
			vLight->entityInteractionState[ edef->index ] = viewLight_t::INTERACTION_YES;
			
			// we will need to create a viewEntity_t for it in the serial code section
			shadowOnlyEntity_t* shadEnt = ( shadowOnlyEntity_t* )R_FrameAlloc( sizeof( shadowOnlyEntity_t ), FRAME_ALLOC_SHADOW_ONLY_ENTITY );
			shadEnt->next = vLight->shadowOnlyViewEntities;
			shadEnt->edef = edef;
			vLight->shadowOnlyViewEntities = shadEnt;
		}
	}
	
	//--------------------------------------------
	// add the prelight shadows for the static world geometry
	//--------------------------------------------
	if( light->parms.prelightModel != NULL && !r_useShadowMapping.GetBool() )
	{
		srfTriangles_t* tri = light->parms.prelightModel->Surface( 0 )->geometry;
		
		// these shadows will have valid bounds, and can be culled normally,
		// but they will typically cover most of the light's bounds
		if( idRenderMatrix::CullBoundsToMVP( viewDef->worldSpace.mvp, tri->bounds ) )
		{
			return;
		}
		
		// prelight models should always have static data that never gets purged
		assert( vertexCache.CacheIsCurrent( tri->shadowCache ) );
		assert( vertexCache.CacheIsCurrent( tri->indexCache ) );
		
		drawSurf_t* shadowDrawSurf = ( drawSurf_t* )R_FrameAlloc( sizeof( *shadowDrawSurf ), FRAME_ALLOC_DRAW_SURFACE );
		
		shadowDrawSurf->frontEndGeo = tri;
		shadowDrawSurf->ambientCache = 0;
		shadowDrawSurf->indexCache = tri->indexCache;
		shadowDrawSurf->shadowCache = tri->shadowCache;
		shadowDrawSurf->jointCache = 0;
		shadowDrawSurf->numIndexes = 0;
		shadowDrawSurf->space = &viewDef->worldSpace;
		shadowDrawSurf->material = NULL;
		shadowDrawSurf->extraGLState = 0;
		shadowDrawSurf->shaderRegisters = NULL;
		shadowDrawSurf->scissorRect = vLight->scissorRect;		// default to the light scissor and light depth bounds
		shadowDrawSurf->shadowVolumeState = SHADOWVOLUME_DONE;	// assume the shadow volume is done in case r_skipPrelightShadows is set
		
		if( !r_skipPrelightShadows.GetBool() )
		{
			preLightShadowVolumeParms_t* shadowParms = ( preLightShadowVolumeParms_t* )R_FrameAlloc( sizeof( shadowParms[0] ), FRAME_ALLOC_SHADOW_VOLUME_PARMS );
			
			shadowParms->verts = tri->preLightShadowVertexes;
			shadowParms->numVerts = tri->numVerts * 2;
			shadowParms->indexes = tri->indexes;
			shadowParms->numIndexes = tri->numIndexes;
			shadowParms->triangleBounds = tri->bounds;
			shadowParms->triangleMVP = viewDef->worldSpace.mvp;
			shadowParms->localLightOrigin = vLight->globalLightOrigin;
			shadowParms->localViewOrigin = viewDef->renderView.vieworg;
			shadowParms->zNear = r_znear.GetFloat();
			shadowParms->lightZMin = vLight->scissorRect.zmin;
			shadowParms->lightZMax = vLight->scissorRect.zmax;
			shadowParms->forceShadowCaps = r_forceShadowCaps.GetBool();
			shadowParms->useShadowPreciseInsideTest = r_useShadowPreciseInsideTest.GetBool();
			shadowParms->useShadowDepthBounds = r_useShadowDepthBounds.GetBool();
			shadowParms->numShadowIndices = & shadowDrawSurf->numIndexes;
			shadowParms->renderZFail = & shadowDrawSurf->renderZFail;
			shadowParms->shadowZMin = & shadowDrawSurf->scissorRect.zmin;
			shadowParms->shadowZMax = & shadowDrawSurf->scissorRect.zmax;
			shadowParms->shadowVolumeState = & shadowDrawSurf->shadowVolumeState;
			
			// the pre-light shadow volume "_prelight_light_3297" in "d3xpdm2" is malformed in that it contains the light origin so the precise inside test always fails
			if( tr.primaryWorld->mapName.IcmpPath( "maps/game/mp/d3xpdm2.map" ) == 0 && idStr::Icmp( light->parms.prelightModel->Name(), "_prelight_light_3297" ) == 0 )
			{
				shadowParms->useShadowPreciseInsideTest = false;
			}
			
			shadowDrawSurf->shadowVolumeState = SHADOWVOLUME_UNFINISHED;
			
			shadowParms->next = vLight->preLightShadowVolumes;
			vLight->preLightShadowVolumes = shadowParms;
		}
		
		// actually link it in
		shadowDrawSurf->nextOnLight = vLight->globalShadows;
		vLight->globalShadows = shadowDrawSurf;
	}
}
Пример #10
0
/*
=====================
idRenderModelDecal::CreateDecalDrawSurf
=====================
*/
drawSurf_t * idRenderModelDecal::CreateDecalDrawSurf( const viewEntity_t *space, unsigned int index ) {
	if ( index < 0 || index >= numDecalMaterials ) {
		return NULL;
	}

	const idMaterial * material = decalMaterials[index];

	int maxVerts = 0;
	int maxIndexes = 0;
	for ( unsigned int i = firstDecal; i < nextDecal; i++ ) {
		const decal_t & decal = decals[i & ( MAX_DECALS - 1 )];
		if ( decal.material == material ) {
			maxVerts += decal.numVerts;
			maxIndexes += decal.numIndexes;
		}
	}

	if ( maxVerts == 0 || maxIndexes == 0 ) {
		return NULL;
	}

	// create a new triangle surface in frame memory so it gets automatically disposed of
	srfTriangles_t *newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ), FRAME_ALLOC_SURFACE_TRIANGLES );
	newTri->numVerts = maxVerts;
	newTri->numIndexes = maxIndexes;

	newTri->ambientCache = vertexCache.AllocVertex( NULL, ALIGN( maxVerts * sizeof( idDrawVert ), VERTEX_CACHE_ALIGN ) );
	newTri->indexCache = vertexCache.AllocIndex( NULL, ALIGN( maxIndexes * sizeof( triIndex_t ), INDEX_CACHE_ALIGN ) );

	idDrawVert * mappedVerts = (idDrawVert *)vertexCache.MappedVertexBuffer( newTri->ambientCache );
	triIndex_t * mappedIndexes = (triIndex_t *)vertexCache.MappedIndexBuffer( newTri->indexCache );

	const decalInfo_t decalInfo = material->GetDecalInfo();
	const int maxTime = decalInfo.stayTime + decalInfo.fadeTime;
	const int time = tr.viewDef->renderView.time[0];

	int numVerts = 0;
	int numIndexes = 0;
	for ( unsigned int i = firstDecal; i < nextDecal; i++ ) {
		const decal_t & decal = decals[i & ( MAX_DECALS - 1 )];

		if ( decal.numVerts == 0 ) {
			if ( i == firstDecal ) {
				firstDecal++;
			}
			continue;
		}

		if ( decal.material != material ) {
			continue;
		}

		const int deltaTime = time - decal.startTime;
		const int fadeTime = deltaTime - decalInfo.stayTime;
		if ( deltaTime > maxTime ) {
			continue;	// already completely faded away, but not yet removed
		}

		const float f = ( deltaTime > decalInfo.stayTime ) ? ( (float) fadeTime / decalInfo.fadeTime ) : 0.0f;

		ALIGNTYPE16 float fadeColor[4];
		for ( int j = 0; j < 4; j++ ) {
			fadeColor[j] = 255.0f * ( decalInfo.start[j] + ( decalInfo.end[j] - decalInfo.start[j] ) * f );
		}

		// use SIMD optimized routine to copy the vertices and indices directly to write-combined memory
		// this also applies any depth/time based fading while copying
		R_CopyDecalSurface( mappedVerts, numVerts, mappedIndexes, numIndexes, &decal, fadeColor );

		numVerts += decal.numVerts;
		numIndexes += decal.numIndexes;
	}
	newTri->numVerts = numVerts;
	newTri->numIndexes = numIndexes;

	// create the drawsurf
	drawSurf_t * drawSurf = (drawSurf_t *)R_FrameAlloc( sizeof( *drawSurf ), FRAME_ALLOC_DRAW_SURFACE );
	drawSurf->frontEndGeo = newTri;
	drawSurf->numIndexes = newTri->numIndexes;
	drawSurf->ambientCache = newTri->ambientCache;
	drawSurf->indexCache = newTri->indexCache;
	drawSurf->shadowCache = 0;
	drawSurf->jointCache = 0;
	drawSurf->space = space;
	drawSurf->scissorRect = space->scissorRect;
	drawSurf->extraGLState = 0;
	drawSurf->renderZFail = 0;

	R_SetupDrawSurfShader( drawSurf, material, &space->entityDef->parms );

	return drawSurf;
}
Пример #11
0
/*
====================
idRenderModelOverlay::CreateOverlayDrawSurf
====================
*/
drawSurf_t* idRenderModelOverlay::CreateOverlayDrawSurf( const viewEntity_t* space, const idRenderModel* baseModel, unsigned int index )
{
	if( index < 0 || index >= numOverlayMaterials )
	{
		return NULL;
	}
	
	// md5 models won't have any surfaces when r_showSkel is set
	if( baseModel == NULL || baseModel->IsDefaultModel() || baseModel->NumSurfaces() == 0 )
	{
		return NULL;
	}
	
	assert( baseModel->IsDynamicModel() == DM_STATIC );
	
	const idRenderModelStatic* staticModel = static_cast< const idRenderModelStatic* >( baseModel );
	
	const idMaterial* material = overlayMaterials[index];
	
	int maxVerts = 0;
	int maxIndexes = 0;
	for( unsigned int i = firstOverlay; i < nextOverlay; i++ )
	{
		const overlay_t& overlay = overlays[i & ( MAX_OVERLAYS - 1 )];
		if( overlay.material == material )
		{
			maxVerts += overlay.numVerts;
			maxIndexes += overlay.numIndexes;
		}
	}
	
	if( maxVerts == 0 || maxIndexes == 0 )
	{
		return NULL;
	}
	
	// create a new triangle surface in frame memory so it gets automatically disposed of
	srfTriangles_t* newTri = ( srfTriangles_t* )R_ClearedFrameAlloc( sizeof( *newTri ), FRAME_ALLOC_SURFACE_TRIANGLES );
	newTri->staticModelWithJoints = ( staticModel->jointsInverted != NULL ) ? const_cast< idRenderModelStatic* >( staticModel ) : NULL;	// allow GPU skinning
	
	newTri->ambientCache = vertexCache.AllocVertex( NULL, ALIGN( maxVerts * sizeof( idDrawVert ), VERTEX_CACHE_ALIGN ) );
	newTri->indexCache = vertexCache.AllocIndex( NULL, ALIGN( maxIndexes * sizeof( triIndex_t ), INDEX_CACHE_ALIGN ) );
	
	idDrawVert* mappedVerts = ( idDrawVert* )vertexCache.MappedVertexBuffer( newTri->ambientCache );
	triIndex_t* mappedIndexes = ( triIndex_t* )vertexCache.MappedIndexBuffer( newTri->indexCache );
	
	int numVerts = 0;
	int numIndexes = 0;
	
	for( unsigned int i = firstOverlay; i < nextOverlay; i++ )
	{
		overlay_t& overlay = overlays[i & ( MAX_OVERLAYS - 1 )];
		
		if( overlay.numVerts == 0 )
		{
			if( i == firstOverlay )
			{
				firstOverlay++;
			}
			continue;
		}
		
		if( overlay.material != material )
		{
			continue;
		}
		
		// get the source model surface for this overlay surface
		const modelSurface_t* baseSurf = ( overlay.surfaceNum < staticModel->NumSurfaces() ) ? staticModel->Surface( overlay.surfaceNum ) : NULL;
		
		// if the surface ids no longer match
		if( baseSurf == NULL || baseSurf->id != overlay.surfaceId )
		{
			// find the surface with the correct id
			if( staticModel->FindSurfaceWithId( overlay.surfaceId, overlay.surfaceNum ) )
			{
				baseSurf = staticModel->Surface( overlay.surfaceNum );
			}
			else
			{
				// the surface with this id no longer exists
				FreeOverlay( overlay );
				if( i == firstOverlay )
				{
					firstOverlay++;
				}
				continue;
			}
		}
		
		// check for out of range vertex references
		const srfTriangles_t* baseTri = baseSurf->geometry;
		if( overlay.maxReferencedVertex >= baseTri->numVerts )
		{
			// This can happen when playing a demofile and a model has been changed since it was recorded, so just issue a warning and go on.
			common->Warning( "idRenderModelOverlay::CreateOverlayDrawSurf: overlay vertex out of range.  Model has probably changed since generating the overlay." );
			FreeOverlay( overlay );
			if( i == firstOverlay )
			{
				firstOverlay++;
			}
			continue;
		}
		
		// use SIMD optimized routine to copy the vertices and indices directly to write-combined memory
		R_CopyOverlaySurface( mappedVerts, numVerts, mappedIndexes, numIndexes, &overlay, baseTri->verts );
		
		numIndexes += overlay.numIndexes;
		numVerts += overlay.numVerts;
	}
	
	newTri->numVerts = numVerts;
	newTri->numIndexes = numIndexes;
	
	// create the drawsurf
	drawSurf_t* drawSurf = ( drawSurf_t* )R_FrameAlloc( sizeof( *drawSurf ), FRAME_ALLOC_DRAW_SURFACE );
	drawSurf->frontEndGeo = newTri;
	drawSurf->numIndexes = newTri->numIndexes;
	drawSurf->ambientCache = newTri->ambientCache;
	drawSurf->indexCache = newTri->indexCache;
	drawSurf->shadowCache = 0;
	drawSurf->space = space;
	drawSurf->scissorRect = space->scissorRect;
	drawSurf->extraGLState = 0;
	drawSurf->renderZFail = 0;
	
	R_SetupDrawSurfShader( drawSurf, material, &space->entityDef->parms );
	R_SetupDrawSurfJoints( drawSurf, newTri, NULL );
	
	return drawSurf;
}