Ejemplo n.º 1
0
/*
================
BoundTriList
================
*/
void BoundTriList( const mapTri_t *list, idBounds &b ) {
    b.Clear();
    for ( ; list ; list = list->next ) {
        b.AddPoint( list->v[0].xyz );
        b.AddPoint( list->v[1].xyz );
        b.AddPoint( list->v[2].xyz );
    }
}
Ejemplo n.º 2
0
/*
=================
HashTriangles

Removes triangles that are degenerated or flipped backwards
=================
*/
void HashTriangles(optimizeGroup_t *groupList)
{
	mapTri_t	*a;
	int			vert;
	int			i;
	optimizeGroup_t	*group;

	// clear the hash tables
	memset(hashVerts, 0, sizeof(hashVerts));

	numHashVerts = 0;
	numTotalVerts = 0;

	// bound all the triangles to determine the bucket size
	hashBounds.Clear();

	for (group = groupList ; group ; group = group->nextGroup) {
		for (a = group->triList ; a ; a = a->next) {
			hashBounds.AddPoint(a->v[0].xyz);
			hashBounds.AddPoint(a->v[1].xyz);
			hashBounds.AddPoint(a->v[2].xyz);
		}
	}

	// spread the bounds so it will never have a zero size
	for (i = 0 ; i < 3 ; i++) {
		hashBounds[0][i] = floor(hashBounds[0][i] - 1);
		hashBounds[1][i] = ceil(hashBounds[1][i] + 1);
		hashIntMins[i] = hashBounds[0][i] * SNAP_FRACTIONS;

		hashScale[i] = (hashBounds[1][i] - hashBounds[0][i]) / HASH_BINS;
		hashIntScale[i] = hashScale[i] * SNAP_FRACTIONS;

		if (hashIntScale[i] < 1) {
			hashIntScale[i] = 1;
		}
	}

	// add all the points to the hash buckets
	for (group = groupList ; group ; group = group->nextGroup) {
		// don't create tjunctions against discrete surfaces (blood decals, etc)
		if (group->material != NULL && group->material->IsDiscrete()) {
			continue;
		}

		for (a = group->triList ; a ; a = a->next) {
			for (vert = 0 ; vert < 3 ; vert++) {
				a->hashVert[vert] = GetHashVert(a->v[vert].xyz);
			}
		}
	}
}
Ejemplo n.º 3
0
/*
================
FindOptVertex
================
*/
optVertex_t *FindOptVertex(idDrawVert *v, optimizeGroup_t *opt)
{
	int		i;
	float	x, y;
	optVertex_t	*vert;

	// deal with everything strictly as 2D
	x = v->xyz * opt->axis[0];
	y = v->xyz * opt->axis[1];

	// should we match based on the t-junction fixing hash verts?
	for (i = 0 ; i < numOptVerts ; i++) {
		if (optVerts[i].pv[0] == x && optVerts[i].pv[1] == y) {
			return &optVerts[i];
		}
	}

	if (numOptVerts >= MAX_OPT_VERTEXES) {
		common->Error("MAX_OPT_VERTEXES");
		return NULL;
	}

	numOptVerts++;

	vert = &optVerts[i];
	memset(vert, 0, sizeof(*vert));
	vert->v = *v;
	vert->pv[0] = x;
	vert->pv[1] = y;
	vert->pv[2] = 0;

	optBounds.AddPoint(vert->pv);

	return vert;
}
Ejemplo n.º 4
0
/*
=========================
R_PreciseCullSurface

Check the surface for visibility on a per-triangle basis
for cases when it is going to be VERY expensive to draw (subviews)

If not culled, also returns the bounding box of the surface in 
Normalized Device Coordinates, so it can be used to crop the scissor rect.

OPTIMIZE: we could also take exact portal passing into consideration
=========================
*/
bool R_PreciseCullSurface( const drawSurf_t *drawSurf, idBounds &ndcBounds ) {
	const srfTriangles_t *tri;
	int numTriangles;
	idPlane clip, eye;
	int i, j;
	unsigned int pointOr;
	unsigned int pointAnd;
	idVec3 localView;
	idFixedWinding w;

	tri = drawSurf->geo;

	pointOr = 0;
	pointAnd = (unsigned int)~0;

	// get an exact bounds of the triangles for scissor cropping
	ndcBounds.Clear();

	for ( i = 0; i < tri->numVerts; i++ ) {
		int j;
		unsigned int pointFlags;

		R_TransformModelToClip( tri->verts[i].xyz, drawSurf->space->modelViewMatrix,
			tr.viewDef->projectionMatrix, eye, clip );

		pointFlags = 0;
		for ( j = 0; j < 3; j++ ) {
			if ( clip[j] >= clip[3] ) {
				pointFlags |= (1 << (j*2));
			} else if ( clip[j] <= -clip[3] ) {
				pointFlags |= ( 1 << (j*2+1));
			}
		}

		pointAnd &= pointFlags;
		pointOr |= pointFlags;
	}

	// trivially reject
	if ( pointAnd ) {
		return true;
	}

	// backface and frustum cull
	numTriangles = tri->numIndexes / 3;

	R_GlobalPointToLocal( drawSurf->space->modelMatrix, tr.viewDef->renderView.vieworg, localView );

	for ( i = 0; i < tri->numIndexes; i += 3 ) {
		idVec3	dir, normal;
		float	dot;
		idVec3	d1, d2;

		const idVec3 &v1 = tri->verts[tri->indexes[i]].xyz;
		const idVec3 &v2 = tri->verts[tri->indexes[i+1]].xyz;
		const idVec3 &v3 = tri->verts[tri->indexes[i+2]].xyz;

		// this is a hack, because R_GlobalPointToLocal doesn't work with the non-normalized
		// axis that we get from the gui view transform.  It doesn't hurt anything, because
		// we know that all gui generated surfaces are front facing
		if ( tr.guiRecursionLevel == 0 ) {
			// we don't care that it isn't normalized,
			// all we want is the sign
			d1 = v2 - v1;
			d2 = v3 - v1;
			normal = d2.Cross( d1 );

			dir = v1 - localView;

			dot = normal * dir;
			if ( dot >= 0.0f ) {
				return true;
			}
		}

		// now find the exact screen bounds of the clipped triangle
		w.SetNumPoints( 3 );
		R_LocalPointToGlobal( drawSurf->space->modelMatrix, v1, w[0].ToVec3() );
		R_LocalPointToGlobal( drawSurf->space->modelMatrix, v2, w[1].ToVec3() );
		R_LocalPointToGlobal( drawSurf->space->modelMatrix, v3, w[2].ToVec3() );
		w[0].s = w[0].t = w[1].s = w[1].t = w[2].s = w[2].t = 0.0f;

		for ( j = 0; j < 4; j++ ) {
			if ( !w.ClipInPlace( -tr.viewDef->frustum[j], 0.1f ) ) {
				break;
			}
		}
		for ( j = 0; j < w.GetNumPoints(); j++ ) {
			idVec3	screen;

			R_GlobalToNormalizedDeviceCoordinates( w[j].ToVec3(), screen );
			ndcBounds.AddPoint( screen );
		}
	}

	// if we don't enclose any area, return
	if ( ndcBounds.IsCleared() ) {
		return true;
	}

	return false;
}
Ejemplo n.º 5
0
/*
==================
FixGlobalTjunctions
==================
*/
void	FixGlobalTjunctions( uEntity_t *e ) {
	mapTri_t	*a;
	int			vert;
	int			i;
	optimizeGroup_t	*group;
	int			areaNum;

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

	// clear the hash tables
	memset( hashVerts, 0, sizeof( hashVerts ) );

	numHashVerts = 0;
	numTotalVerts = 0;

	// bound all the triangles to determine the bucket size
	hashBounds.Clear();
	for ( areaNum = 0 ; areaNum < e->numAreas ; areaNum++ ) {
		for ( group = e->areas[areaNum].groups ; group ; group = group->nextGroup ) {
			for ( a = group->triList ; a ; a = a->next ) {
				hashBounds.AddPoint( a->v[0].xyz );
				hashBounds.AddPoint( a->v[1].xyz );
				hashBounds.AddPoint( a->v[2].xyz );
			}
		}
	}

	// spread the bounds so it will never have a zero size
	for ( i = 0 ; i < 3 ; i++ ) {
		hashBounds[0][i] = floor( hashBounds[0][i] - 1 );
		hashBounds[1][i] = ceil( hashBounds[1][i] + 1 );
		hashIntMins[i] = hashBounds[0][i] * SNAP_FRACTIONS;

		hashScale[i] = ( hashBounds[1][i] - hashBounds[0][i] ) / HASH_BINS;
		hashIntScale[i] = hashScale[i] * SNAP_FRACTIONS;
		if ( hashIntScale[i] < 1 ) {
			hashIntScale[i] = 1;
		}
	}

	// add all the points to the hash buckets
	for ( areaNum = 0 ; areaNum < e->numAreas ; areaNum++ ) {
		for ( group = e->areas[areaNum].groups ; group ; group = group->nextGroup ) {
			// don't touch discrete surfaces
			if ( group->material != NULL && group->material->IsDiscrete() ) {
				continue;
			}

			for ( a = group->triList ; a ; a = a->next ) {
				for ( vert = 0 ; vert < 3 ; vert++ ) {
					a->hashVert[vert] = GetHashVert( a->v[vert].xyz );
				}
			}
		}
	}

	// add all the func_static model vertexes to the hash buckets
	// optionally inline some of the func_static models
	if ( dmapGlobals.entityNum == 0 ) {
		for ( int eNum = 1 ; eNum < dmapGlobals.num_entities ; eNum++ ) {
			uEntity_t *entity = &dmapGlobals.uEntities[eNum];
			const char *className = entity->mapEntity->epairs.GetString( "classname" );
			if ( idStr::Icmp( className, "func_static" ) ) {
				continue;
			}
			const char *modelName = entity->mapEntity->epairs.GetString( "model" );
			if ( !modelName ) {
				continue;
			}
			if ( !strstr( modelName, ".lwo" ) && !strstr( modelName, ".ase" ) && !strstr( modelName, ".ma" ) ) {
				continue;
			}

			idRenderModel	*model = renderModelManager->FindModel( modelName );

//			common->Printf( "adding T junction verts for %s.\n", entity->mapEntity->epairs.GetString( "name" ) );

			idMat3	axis;
			// get the rotation matrix in either full form, or single angle form
			if ( !entity->mapEntity->epairs.GetMatrix( "rotation", "1 0 0 0 1 0 0 0 1", axis ) ) {
				float angle = entity->mapEntity->epairs.GetFloat( "angle" );
				if ( angle != 0.0f ) {
					axis = idAngles( 0.0f, angle, 0.0f ).ToMat3();
				} else {
					axis.Identity();
				}
			}

			idVec3	origin = entity->mapEntity->epairs.GetVector( "origin" );

			for ( i = 0 ; i < model->NumSurfaces() ; i++ ) {
				const modelSurface_t *surface = model->Surface( i );
				const srfTriangles_t *tri = surface->geometry;

				mapTri_t	mapTri;
				memset( &mapTri, 0, sizeof( mapTri ) );
				mapTri.material = surface->shader;
				// don't let discretes (autosprites, etc) merge together
				if ( mapTri.material->IsDiscrete() ) {
					mapTri.mergeGroup = (void *)surface;
				}
				for ( int j = 0 ; j < tri->numVerts ; j += 3 ) {
					idVec3 v = tri->verts[j].xyz * axis + origin;
					GetHashVert( v );
				}
			}
		}
	}



	// now fix each area
	for ( areaNum = 0 ; areaNum < e->numAreas ; areaNum++ ) {
		for ( group = e->areas[areaNum].groups ; group ; group = group->nextGroup ) {
			// don't touch discrete surfaces
			if ( group->material != NULL && group->material->IsDiscrete() ) {
				continue;
			}

			mapTri_t *newList = NULL;
			for ( mapTri_t *tri = group->triList ; tri ; tri = tri->next ) {
				mapTri_t *fixed = FixTriangleAgainstHash( tri );
				newList = MergeTriLists( newList, fixed );
			}
			FreeTriList( group->triList );
			group->triList = newList;
		}
	}


	// done
	FreeTJunctionHash();
}
Ejemplo n.º 6
0
/*
============
GetObstacles
============
*/
int GetObstacles( const idPhysics *physics, const idAAS *aas, const idEntity *ignore, int areaNum, const idVec3 &startPos, const idVec3 &seekPos, obstacle_t *obstacles, int maxObstacles, idBounds &clipBounds ) {
	int i, j, numListedClipModels, numObstacles, numVerts, clipMask, blockingObstacle, blockingEdgeNum;
	int wallEdges[MAX_AAS_WALL_EDGES], numWallEdges, verts[2], lastVerts[2], nextVerts[2];
	float stepHeight, headHeight, blockingScale, min, max;
	idVec3 seekDelta, silVerts[32], start, end, nextStart, nextEnd;
	idVec2 expBounds[2], edgeDir, edgeNormal, nextEdgeDir, nextEdgeNormal, lastEdgeNormal;
	idVec2 obDelta;
	idPhysics *obPhys;
	idBox box;
	idEntity *obEnt;
	idClipModel *clipModel;
	idClipModel *clipModelList[ MAX_GENTITIES ];

	numObstacles = 0;

	seekDelta = seekPos - startPos;
	expBounds[0] = physics->GetBounds()[0].ToVec2() - idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON );
	expBounds[1] = physics->GetBounds()[1].ToVec2() + idVec2( CM_BOX_EPSILON, CM_BOX_EPSILON );

	physics->GetAbsBounds().AxisProjection( -physics->GetGravityNormal(), stepHeight, headHeight );
	stepHeight += aas->GetSettings()->maxStepHeight;

	// clip bounds for the obstacle search space
	clipBounds[0] = clipBounds[1] = startPos;
	clipBounds.AddPoint( seekPos );
	clipBounds.ExpandSelf( MAX_OBSTACLE_RADIUS );
	clipMask = physics->GetClipMask();

	// find all obstacles touching the clip bounds
	numListedClipModels = gameLocal.clip.ClipModelsTouchingBounds( clipBounds, clipMask, clipModelList, MAX_GENTITIES );

	for ( i = 0; i < numListedClipModels && numObstacles < MAX_OBSTACLES; i++ ) {
		clipModel = clipModelList[i];
		obEnt = clipModel->GetEntity();

		if ( !clipModel->IsTraceModel() ) {
			continue;
		}

		if ( obEnt->IsType( idActor::Type ) ) {
			obPhys = obEnt->GetPhysics();
			// ignore myself, my enemy, and dead bodies
			if ( ( obPhys == physics ) || ( obEnt == ignore ) || ( obEnt->health <= 0 ) ) {
				continue;
			}
			// if the actor is moving
			idVec3 v1 = obPhys->GetLinearVelocity();
			if ( v1.LengthSqr() > Square( 10.0f ) ) {
				idVec3 v2 = physics->GetLinearVelocity();
				if ( v2.LengthSqr() > Square( 10.0f ) ) {
					// if moving in about the same direction
					if ( v1 * v2 > 0.0f ) {
						continue;
					}
				}
			}
		} else if ( obEnt->IsType( idMoveable::Type ) ) {
			// moveables are considered obstacles
		} else {
			// ignore everything else
			continue;
		}

		// check if we can step over the object
		clipModel->GetAbsBounds().AxisProjection( -physics->GetGravityNormal(), min, max );
		if ( max < stepHeight || min > headHeight ) {
			// can step over this one
			continue;
		}

		// project a box containing the obstacle onto the floor plane
		box = idBox( clipModel->GetBounds(), clipModel->GetOrigin(), clipModel->GetAxis() );
		numVerts = box.GetParallelProjectionSilhouetteVerts( physics->GetGravityNormal(), silVerts );

		// create a 2D winding for the obstacle;
		obstacle_t &obstacle = obstacles[numObstacles++];
		obstacle.winding.Clear();
		for ( j = 0; j < numVerts; j++ ) {
			obstacle.winding.AddPoint( silVerts[j].ToVec2() );
		}

		if ( ai_showObstacleAvoidance.GetBool() ) {
			for ( j = 0; j < numVerts; j++ ) {
				silVerts[j].z = startPos.z;
			}
			for ( j = 0; j < numVerts; j++ ) {
				gameRenderWorld->DebugArrow( colorWhite, silVerts[j], silVerts[(j+1)%numVerts], 4 );
			}
		}

		// expand the 2D winding for collision with a 2D box
		obstacle.winding.ExpandForAxialBox( expBounds );
		obstacle.winding.GetBounds( obstacle.bounds );
		obstacle.entity = obEnt;
	}

	// if there are no dynamic obstacles the path should be through valid AAS space
	if ( numObstacles == 0 ) {
		return 0;
	}

	// if the current path doesn't intersect any dynamic obstacles the path should be through valid AAS space
	if ( PointInsideObstacle( obstacles, numObstacles, startPos.ToVec2() ) == -1 ) {
		if ( !GetFirstBlockingObstacle( obstacles, numObstacles, -1, startPos.ToVec2(), seekDelta.ToVec2(), blockingScale, blockingObstacle, blockingEdgeNum ) ) {
			return 0;
		}
	}

	// create obstacles for AAS walls
	if ( aas ) {
		float halfBoundsSize = ( expBounds[ 1 ].x - expBounds[ 0 ].x ) * 0.5f;

		numWallEdges = aas->GetWallEdges( areaNum, clipBounds, TFL_WALK, wallEdges, MAX_AAS_WALL_EDGES );
		aas->SortWallEdges( wallEdges, numWallEdges );

		lastVerts[0] = lastVerts[1] = 0;
		lastEdgeNormal.Zero();
		nextVerts[0] = nextVerts[1] = 0;
		for ( i = 0; i < numWallEdges && numObstacles < MAX_OBSTACLES; i++ ) {
            aas->GetEdge( wallEdges[i], start, end );
			aas->GetEdgeVertexNumbers( wallEdges[i], verts );
			edgeDir = end.ToVec2() - start.ToVec2();
			edgeDir.Normalize();
			edgeNormal.x = edgeDir.y;
			edgeNormal.y = -edgeDir.x;
			if ( i < numWallEdges-1 ) {
				aas->GetEdge( wallEdges[i+1], nextStart, nextEnd );
				aas->GetEdgeVertexNumbers( wallEdges[i+1], nextVerts );
				nextEdgeDir = nextEnd.ToVec2() - nextStart.ToVec2();
				nextEdgeDir.Normalize();
				nextEdgeNormal.x = nextEdgeDir.y;
				nextEdgeNormal.y = -nextEdgeDir.x;
			}

			obstacle_t &obstacle = obstacles[numObstacles++];
			obstacle.winding.Clear();
			obstacle.winding.AddPoint( end.ToVec2() );
			obstacle.winding.AddPoint( start.ToVec2() );
			obstacle.winding.AddPoint( start.ToVec2() - edgeDir - edgeNormal * halfBoundsSize );
			obstacle.winding.AddPoint( end.ToVec2() + edgeDir - edgeNormal * halfBoundsSize );
			if ( lastVerts[1] == verts[0] ) {
				obstacle.winding[2] -= lastEdgeNormal * halfBoundsSize;
			} else {
				obstacle.winding[1] -= edgeDir;
			}
			if ( verts[1] == nextVerts[0] ) {
				obstacle.winding[3] -= nextEdgeNormal * halfBoundsSize;
			} else {
				obstacle.winding[0] += edgeDir;
			}
			obstacle.winding.GetBounds( obstacle.bounds );
			obstacle.entity = NULL;

			memcpy( lastVerts, verts, sizeof( lastVerts ) );
			lastEdgeNormal = edgeNormal;
		}
	}

	// show obstacles
	if ( ai_showObstacleAvoidance.GetBool() ) {
		for ( i = 0; i < numObstacles; i++ ) {
			obstacle_t &obstacle = obstacles[i];
			for ( j = 0; j < obstacle.winding.GetNumPoints(); j++ ) {
				silVerts[j].ToVec2() = obstacle.winding[j];
				silVerts[j].z = startPos.z;
			}
			for ( j = 0; j < obstacle.winding.GetNumPoints(); j++ ) {
				gameRenderWorld->DebugArrow( colorGreen, silVerts[j], silVerts[(j+1)%obstacle.winding.GetNumPoints()], 4 );
			}
		}
	}

	return numObstacles;
}
Ejemplo n.º 7
0
/*
============
idFrustum::ProjectionBounds
============
*/
bool idFrustum::ProjectionBounds( const idBox &box, idBounds &projectionBounds ) const
{
	int i, p1, p2, pointCull[8], culled, outside;
	float scale1, scale2;
	idFrustum localFrustum;
	idVec3 points[8], localOrigin;
	idMat3 localAxis, localScaled;
	idBounds bounds( -box.GetExtents(), box.GetExtents() );
	
	// if the frustum origin is inside the bounds
	if( bounds.ContainsPoint( ( origin - box.GetCenter() ) * box.GetAxis().Transpose() ) )
	{
		// bounds that cover the whole frustum
		float boxMin, boxMax, base;
		
		base = origin * axis[0];
		box.AxisProjection( axis[0], boxMin, boxMax );
		
		projectionBounds[0].x = boxMin - base;
		projectionBounds[1].x = boxMax - base;
		projectionBounds[0].y = projectionBounds[0].z = -1.0f;
		projectionBounds[1].y = projectionBounds[1].z = 1.0f;
		
		return true;
	}
	
	projectionBounds.Clear();
	
	// transform the bounds into the space of this frustum
	localOrigin = ( box.GetCenter() - origin ) * axis.Transpose();
	localAxis = box.GetAxis() * axis.Transpose();
	BoxToPoints( localOrigin, box.GetExtents(), localAxis, points );
	
	// test outer four edges of the bounds
	culled = -1;
	outside = 0;
	for( i = 0; i < 4; i++ )
	{
		p1 = i;
		p2 = 4 + i;
		AddLocalLineToProjectionBoundsSetCull( points[p1], points[p2], pointCull[p1], pointCull[p2], projectionBounds );
		culled &= pointCull[p1] & pointCull[p2];
		outside |= pointCull[p1] | pointCull[p2];
	}
	
	// if the bounds are completely outside this frustum
	if( culled )
	{
		return false;
	}
	
	// if the bounds are completely inside this frustum
	if( !outside )
	{
		return true;
	}
	
	// test the remaining edges of the bounds
	for( i = 0; i < 4; i++ )
	{
		p1 = i;
		p2 = ( i + 1 ) & 3;
		AddLocalLineToProjectionBoundsUseCull( points[p1], points[p2], pointCull[p1], pointCull[p2], projectionBounds );
	}
	
	for( i = 0; i < 4; i++ )
	{
		p1 = 4 + i;
		p2 = 4 + ( ( i + 1 ) & 3 );
		AddLocalLineToProjectionBoundsUseCull( points[p1], points[p2], pointCull[p1], pointCull[p2], projectionBounds );
	}
	
	// if the bounds extend beyond two or more boundaries of this frustum
	if( outside != 1 && outside != 2 && outside != 4 && outside != 8 )
	{
	
		localOrigin = ( origin - box.GetCenter() ) * box.GetAxis().Transpose();
		localScaled = axis * box.GetAxis().Transpose();
		localScaled[0] *= dFar;
		localScaled[1] *= dLeft;
		localScaled[2] *= dUp;
		
		// test the outer edges of this frustum for intersection with the bounds
		if( ( outside & 2 ) && ( outside & 8 ) )
		{
			BoundsRayIntersection( bounds, localOrigin, localScaled[0] - localScaled[1] - localScaled[2], scale1, scale2 );
			if( scale1 <= scale2 && scale1 >= 0.0f )
			{
				projectionBounds.AddPoint( idVec3( scale1 * dFar, -1.0f, -1.0f ) );
				projectionBounds.AddPoint( idVec3( scale2 * dFar, -1.0f, -1.0f ) );
			}
		}
		if( ( outside & 2 ) && ( outside & 4 ) )
		{
			BoundsRayIntersection( bounds, localOrigin, localScaled[0] - localScaled[1] + localScaled[2], scale1, scale2 );
			if( scale1 <= scale2 && scale1 >= 0.0f )
			{
				projectionBounds.AddPoint( idVec3( scale1 * dFar, -1.0f, 1.0f ) );
				projectionBounds.AddPoint( idVec3( scale2 * dFar, -1.0f, 1.0f ) );
			}
		}
		if( ( outside & 1 ) && ( outside & 8 ) )
		{
			BoundsRayIntersection( bounds, localOrigin, localScaled[0] + localScaled[1] - localScaled[2], scale1, scale2 );
			if( scale1 <= scale2 && scale1 >= 0.0f )
			{
				projectionBounds.AddPoint( idVec3( scale1 * dFar, 1.0f, -1.0f ) );
				projectionBounds.AddPoint( idVec3( scale2 * dFar, 1.0f, -1.0f ) );
			}
		}
		if( ( outside & 1 ) && ( outside & 2 ) )
		{
			BoundsRayIntersection( bounds, localOrigin, localScaled[0] + localScaled[1] + localScaled[2], scale1, scale2 );
			if( scale1 <= scale2 && scale1 >= 0.0f )
			{
				projectionBounds.AddPoint( idVec3( scale1 * dFar, 1.0f, 1.0f ) );
				projectionBounds.AddPoint( idVec3( scale2 * dFar, 1.0f, 1.0f ) );
			}
		}
	}
	
	return true;
}
/*
=========================
R_PreciseCullSurface

Check the surface for visibility on a per-triangle basis
for cases when it is going to be VERY expensive to draw (subviews)

If not culled, also returns the bounding box of the surface in
Normalized Device Coordinates, so it can be used to crop the scissor rect.

OPTIMIZE: we could also take exact portal passing into consideration
=========================
*/
bool R_PreciseCullSurface( const drawSurf_t* drawSurf, idBounds& ndcBounds )
{
	const srfTriangles_t* tri = drawSurf->frontEndGeo;
	
	unsigned int pointOr = 0;
	unsigned int pointAnd = ( unsigned int )~0;
	
	// get an exact bounds of the triangles for scissor cropping
	ndcBounds.Clear();
	
	const idJointMat* joints = ( tri->staticModelWithJoints != NULL && r_useGPUSkinning.GetBool() ) ? tri->staticModelWithJoints->jointsInverted : NULL;
	
	for( int i = 0; i < tri->numVerts; i++ )
	{
		const idVec3 vXYZ = idDrawVert::GetSkinnedDrawVertPosition( tri->verts[i], joints );
		
		idPlane eye, clip;
		R_TransformModelToClip( vXYZ, drawSurf->space->modelViewMatrix, tr.viewDef->projectionMatrix, eye, clip );
		
		unsigned int pointFlags = 0;
		for( int j = 0; j < 3; j++ )
		{
			if( clip[j] >= clip[3] )
			{
				pointFlags |= ( 1 << ( j * 2 + 0 ) );
			}
			else if( clip[j] <= -clip[3] )  	// FIXME: the D3D near clip plane is at zero instead of -1
			{
				pointFlags |= ( 1 << ( j * 2 + 1 ) );
			}
		}
		
		pointAnd &= pointFlags;
		pointOr |= pointFlags;
	}
	
	// trivially reject
	if( pointAnd != 0 )
	{
		return true;
	}
	
	// backface and frustum cull
	idVec3 localViewOrigin;
	R_GlobalPointToLocal( drawSurf->space->modelMatrix, tr.viewDef->renderView.vieworg, localViewOrigin );
	
	for( int i = 0; i < tri->numIndexes; i += 3 )
	{
		const idVec3 v1 = idDrawVert::GetSkinnedDrawVertPosition( tri->verts[ tri->indexes[ i + 0 ] ], joints );
		const idVec3 v2 = idDrawVert::GetSkinnedDrawVertPosition( tri->verts[ tri->indexes[ i + 1 ] ], joints );
		const idVec3 v3 = idDrawVert::GetSkinnedDrawVertPosition( tri->verts[ tri->indexes[ i + 2 ] ], joints );
		
		// this is a hack, because R_GlobalPointToLocal doesn't work with the non-normalized
		// axis that we get from the gui view transform.  It doesn't hurt anything, because
		// we know that all gui generated surfaces are front facing
		if( tr.guiRecursionLevel == 0 )
		{
			// we don't care that it isn't normalized,
			// all we want is the sign
			const idVec3 d1 = v2 - v1;
			const idVec3 d2 = v3 - v1;
			const idVec3 normal = d2.Cross( d1 );
			
			const idVec3 dir = v1 - localViewOrigin;
			
			const float dot = normal * dir;
			if( dot >= 0.0f )
			{
				return true;
			}
		}
		
		// now find the exact screen bounds of the clipped triangle
		idFixedWinding w;
		w.SetNumPoints( 3 );
		R_LocalPointToGlobal( drawSurf->space->modelMatrix, v1, w[0].ToVec3() );
		R_LocalPointToGlobal( drawSurf->space->modelMatrix, v2, w[1].ToVec3() );
		R_LocalPointToGlobal( drawSurf->space->modelMatrix, v3, w[2].ToVec3() );
		w[0].s = w[0].t = w[1].s = w[1].t = w[2].s = w[2].t = 0.0f;
		
		for( int j = 0; j < 4; j++ )
		{
			if( !w.ClipInPlace( -tr.viewDef->frustums[FRUSTUM_PRIMARY][j], 0.1f ) )
			{
				break;
			}
		}
		for( int j = 0; j < w.GetNumPoints(); j++ )
		{
			idVec3 screen;
			
			R_GlobalToNormalizedDeviceCoordinates( w[j].ToVec3(), screen );
			ndcBounds.AddPoint( screen );
		}
	}
	
	// if we don't enclose any area, return
	if( ndcBounds.IsCleared() )
	{
		return true;
	}
	
	return false;
}