/*
===========================================================================

Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.

This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").

Doom 3 Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Doom 3 Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

===========================================================================
*/

#include "sys/platform.h"
#include "idlib/containers/List.h"

#include "idlib/bv/Frustum.h"

//#define FRUSTUM_DEBUG

/*
  bit 0 = min x
  bit 1 = max x
  bit 2 = min y
  bit 3 = max y
  bit 4 = min z
  bit 5 = max z
*/
static int boxVertPlanes[8] = {
	( (1<<0) | (1<<2) | (1<<4) ),
	( (1<<1) | (1<<2) | (1<<4) ),
	( (1<<1) | (1<<3) | (1<<4) ),
	( (1<<0) | (1<<3) | (1<<4) ),
	( (1<<0) | (1<<2) | (1<<5) ),
	( (1<<1) | (1<<2) | (1<<5) ),
	( (1<<1) | (1<<3) | (1<<5) ),
	( (1<<0) | (1<<3) | (1<<5) ),
};

/*
============
BoxToPoints
============
*/
void BoxToPoints( const idVec3 &center, const idVec3 &extents, const idMat3 &axis, idVec3 points[8] ) {
	idMat3 ax;
	idVec3 temp[4];

	ax[0] = extents[0] * axis[0];
	ax[1] = extents[1] * axis[1];
	ax[2] = extents[2] * axis[2];
	temp[0] = center - ax[0];
	temp[1] = center + ax[0];
	temp[2] = ax[1] - ax[2];
	temp[3] = ax[1] + ax[2];
	points[0] = temp[0] - temp[3];
	points[1] = temp[1] - temp[3];
	points[2] = temp[1] + temp[2];
	points[3] = temp[0] + temp[2];
	points[4] = temp[0] - temp[2];
	points[5] = temp[1] - temp[2];
	points[6] = temp[1] + temp[3];
	points[7] = temp[0] + temp[3];
}

/*
================
idFrustum::PlaneDistance
================
*/
float idFrustum::PlaneDistance( const idPlane &plane ) const {
	float min, max;

	AxisProjection( plane.Normal(), min, max );
	if ( min + plane[3] > 0.0f ) {
		return min + plane[3];
	}
	if ( max + plane[3] < 0.0f ) {
		return max + plane[3];
	}
	return 0.0f;
}

/*
================
idFrustum::PlaneSide
================
*/
int idFrustum::PlaneSide( const idPlane &plane, const float epsilon ) const {
	float min, max;

	AxisProjection( plane.Normal(), min, max );
	if ( min + plane[3] > epsilon ) {
		return PLANESIDE_FRONT;
	}
	if ( max + plane[3] < epsilon ) {
		return PLANESIDE_BACK;
	}
	return PLANESIDE_CROSS;
}

/*
============
idFrustum::CullPoint
============
*/
bool idFrustum::CullPoint( const idVec3 &point ) const {
	idVec3 p;
	float scale;

	// transform point to frustum space
	p = ( point - origin ) * axis.Transpose();
	// test whether or not the point is within the frustum
	if ( p.x < dNear || p.x > dFar ) {
		return true;
	}
	scale = p.x * invFar;
	if ( idMath::Fabs( p.y ) > dLeft * scale ) {
		return true;
	}
	if ( idMath::Fabs( p.z ) > dUp * scale ) {
		return true;
	}
	return false;
}

/*
============
idFrustum::CullLocalBox

  Tests if any of the planes of the frustum can be used as a separating plane.

   3 muls best case
  25 muls worst case
============
*/
bool idFrustum::CullLocalBox( const idVec3 &localOrigin, const idVec3 &extents, const idMat3 &localAxis ) const {
	float d1, d2;
	idVec3 testOrigin;
	idMat3 testAxis;

	// near plane
	d1 = dNear - localOrigin.x;
	d2 = idMath::Fabs( extents[0] * localAxis[0][0] ) +
				idMath::Fabs( extents[1] * localAxis[1][0] ) +
						idMath::Fabs( extents[2] * localAxis[2][0] );
	if ( d1 - d2 > 0.0f ) {
		return true;
	}

	// far plane
	d1 = localOrigin.x - dFar;
	if ( d1 - d2 > 0.0f ) {
		return true;
	}

	testOrigin = localOrigin;
	testAxis = localAxis;

	if ( testOrigin.y < 0.0f ) {
		testOrigin.y = -testOrigin.y;
		testAxis[0][1] = -testAxis[0][1];
		testAxis[1][1] = -testAxis[1][1];
		testAxis[2][1] = -testAxis[2][1];
	}

	// test left/right planes
	d1 = dFar * testOrigin.y - dLeft * testOrigin.x;
	d2 = idMath::Fabs( extents[0] * ( dFar * testAxis[0][1] - dLeft * testAxis[0][0] ) ) +
				idMath::Fabs( extents[1] * ( dFar * testAxis[1][1] - dLeft * testAxis[1][0] ) ) +
					idMath::Fabs( extents[2] * ( dFar * testAxis[2][1] - dLeft * testAxis[2][0] ) );
	if ( d1 - d2 > 0.0f ) {
		return true;
	}

	if ( testOrigin.z < 0.0f ) {
		testOrigin.z = -testOrigin.z;
		testAxis[0][2] = -testAxis[0][2];
		testAxis[1][2] = -testAxis[1][2];
		testAxis[2][2] = -testAxis[2][2];
	}

	// test up/down planes
	d1 = dFar * testOrigin.z - dUp * testOrigin.x;
	d2 = idMath::Fabs( extents[0] * ( dFar * testAxis[0][2] - dUp * testAxis[0][0] ) ) +
				idMath::Fabs( extents[1] * ( dFar * testAxis[1][2] - dUp * testAxis[1][0] ) ) +
					idMath::Fabs( extents[2] * ( dFar * testAxis[2][2] - dUp * testAxis[2][0] ) );
	if ( d1 - d2 > 0.0f ) {
		return true;
	}

	return false;
}

/*
============
idFrustum::CullBounds

  Tests if any of the planes of the frustum can be used as a separating plane.

  24 muls best case
  37 muls worst case
============
*/
bool idFrustum::CullBounds( const idBounds &bounds ) const {
	idVec3 localOrigin, center, extents;
	idMat3 localAxis;

	center = ( bounds[0] + bounds[1] ) * 0.5f;
	extents = bounds[1] - center;

	// transform the bounds into the space of this frustum
	localOrigin = ( center - origin ) * axis.Transpose();
	localAxis = axis.Transpose();

	return CullLocalBox( localOrigin, extents, localAxis );
}

/*
============
idFrustum::CullBounds

  Tests if any of the planes of the frustum can be used as a separating plane.

  39 muls best case
  61 muls worst case
============
*/
bool idFrustum::CullBox( const idBox &box ) const {
	idVec3 localOrigin;
	idMat3 localAxis;

	// transform the box into the space of this frustum
	localOrigin = ( box.GetCenter() - origin ) * axis.Transpose();
	localAxis = box.GetAxis() * axis.Transpose();

	return CullLocalBox( localOrigin, box.GetExtents(), localAxis );
}

/*
============
idFrustum::CullSphere

  Tests if any of the planes of the frustum can be used as a separating plane.

   9 muls best case
  21 muls worst case
============
*/
bool idFrustum::CullSphere( const idSphere &sphere ) const {
	float d, r, rs, sFar;
	idVec3 center;

	center = ( sphere.GetOrigin() - origin ) * axis.Transpose();
	r = sphere.GetRadius();

	// test near plane
	if ( dNear - center.x > r ) {
		return true;
	}

	// test far plane
	if ( center.x - dFar > r ) {
		return true;
	}

	rs = r * r;
	sFar = dFar * dFar;

	// test left/right planes
	d = dFar * idMath::Fabs( center.y ) - dLeft * center.x;
	if ( ( d * d ) > rs * ( sFar + dLeft * dLeft ) ) {
		return true;
	}

	// test up/down planes
	d = dFar * idMath::Fabs( center.z ) - dUp * center.x;
	if ( ( d * d ) > rs * ( sFar + dUp * dUp ) ) {
		return true;
	}

	return false;
}

/*
============
idFrustum::CullLocalFrustum

  Tests if any of the planes of this frustum can be used as a separating plane.

   0 muls best case
  30 muls worst case
============
*/
bool idFrustum::CullLocalFrustum( const idFrustum &localFrustum, const idVec3 indexPoints[8], const idVec3 cornerVecs[4] ) const {
	int index;
	float dx, dy, dz, leftScale, upScale;

	// test near plane
	dy = -localFrustum.axis[1].x;
	dz = -localFrustum.axis[2].x;
	index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz );
	dx = -cornerVecs[index].x;
	index |= ( FLOATSIGNBITSET( dx ) << 2 );

	if ( indexPoints[index].x < dNear ) {
		return true;
	}

	// test far plane
	dy = localFrustum.axis[1].x;
	dz = localFrustum.axis[2].x;
	index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz );
	dx = cornerVecs[index].x;
	index |= ( FLOATSIGNBITSET( dx ) << 2 );

	if ( indexPoints[index].x > dFar ) {
		return true;
	}

	leftScale = dLeft * invFar;

	// test left plane
	dy = dFar * localFrustum.axis[1].y - dLeft * localFrustum.axis[1].x;
	dz = dFar * localFrustum.axis[2].y - dLeft * localFrustum.axis[2].x;
	index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz );
	dx = dFar * cornerVecs[index].y - dLeft * cornerVecs[index].x;
	index |= ( FLOATSIGNBITSET( dx ) << 2 );

	if ( indexPoints[index].y > indexPoints[index].x * leftScale ) {
		return true;
	}

	// test right plane
	dy = -dFar * localFrustum.axis[1].y - dLeft * localFrustum.axis[1].x;
	dz = -dFar * localFrustum.axis[2].y - dLeft * localFrustum.axis[2].x;
	index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz );
	dx = -dFar * cornerVecs[index].y - dLeft * cornerVecs[index].x;
	index |= ( FLOATSIGNBITSET( dx ) << 2 );

	if ( indexPoints[index].y < -indexPoints[index].x * leftScale ) {
		return true;
	}

	upScale = dUp * invFar;

	// test up plane
	dy = dFar * localFrustum.axis[1].z - dUp * localFrustum.axis[1].x;
	dz = dFar * localFrustum.axis[2].z - dUp * localFrustum.axis[2].x;
	index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz );
	dx = dFar * cornerVecs[index].z - dUp * cornerVecs[index].x;
	index |= ( FLOATSIGNBITSET( dx ) << 2 );

	if ( indexPoints[index].z > indexPoints[index].x * upScale ) {
		return true;
	}

	// test down plane
	dy = -dFar * localFrustum.axis[1].z - dUp * localFrustum.axis[1].x;
	dz = -dFar * localFrustum.axis[2].z - dUp * localFrustum.axis[2].x;
	index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz );
	dx = -dFar * cornerVecs[index].z - dUp * cornerVecs[index].x;
	index |= ( FLOATSIGNBITSET( dx ) << 2 );

	if ( indexPoints[index].z < -indexPoints[index].x * upScale ) {
		return true;
	}

	return false;
}

/*
============
idFrustum::CullFrustum

  Tests if any of the planes of this frustum can be used as a separating plane.

  58 muls best case
  88 muls worst case
============
*/
bool idFrustum::CullFrustum( const idFrustum &frustum ) const {
	idFrustum localFrustum;
	idVec3 indexPoints[8], cornerVecs[4];

	// transform the given frustum into the space of this frustum
	localFrustum = frustum;
	localFrustum.origin = ( frustum.origin - origin ) * axis.Transpose();
	localFrustum.axis = frustum.axis * axis.Transpose();

	localFrustum.ToIndexPointsAndCornerVecs( indexPoints, cornerVecs );

	return CullLocalFrustum( localFrustum, indexPoints, cornerVecs );
}

/*
============
idFrustum::CullLocalWinding
============
*/
bool idFrustum::CullLocalWinding( const idVec3 *points, const int numPoints, int *pointCull ) const {
	int i, pCull, culled;
	float leftScale, upScale;

	leftScale = dLeft * invFar;
	upScale = dUp * invFar;

	culled = -1;
	for ( i = 0; i < numPoints; i++ ) {
		const idVec3 &p = points[i];
		pCull = 0;
		if ( p.x < dNear ) {
			pCull = 1;
		}
		else if ( p.x > dFar ) {
			pCull = 2;
		}
		if ( idMath::Fabs( p.y ) > p.x * leftScale ) {
			pCull |= 4 << FLOATSIGNBITSET( p.y );
		}
		if ( idMath::Fabs( p.z ) > p.x * upScale ) {
			pCull |= 16 << FLOATSIGNBITSET( p.z );
		}
		culled &= pCull;
		pointCull[i] = pCull;
	}

	return ( culled != 0 );
}

/*
============
idFrustum::CullWinding
============
*/
bool idFrustum::CullWinding( const idWinding &winding ) const {
	int i, *pointCull;
	idVec3 *localPoints;
	idMat3 transpose;

	localPoints = (idVec3 *) _alloca16( winding.GetNumPoints() * sizeof( idVec3 ) );
	pointCull = (int *) _alloca16( winding.GetNumPoints() * sizeof( int ) );

	transpose = axis.Transpose();
	for ( i = 0; i < winding.GetNumPoints(); i++ ) {
		localPoints[i] = ( winding[i].ToVec3() - origin ) * transpose;
	}

	return CullLocalWinding( localPoints, winding.GetNumPoints(), pointCull );
}

/*
============
idFrustum::BoundsCullLocalFrustum

  Tests if any of the bounding box planes can be used as a separating plane.
============
*/
bool idFrustum::BoundsCullLocalFrustum( const idBounds &bounds, const idFrustum &localFrustum, const idVec3 indexPoints[8], const idVec3 cornerVecs[4] ) const {
	int index;
	float dx, dy, dz;

	dy = -localFrustum.axis[1].x;
	dz = -localFrustum.axis[2].x;
	index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz );
	dx = -cornerVecs[index].x;
	index |= ( FLOATSIGNBITSET( dx ) << 2 );

	if ( indexPoints[index].x < bounds[0].x ) {
		return true;
	}

	dy = localFrustum.axis[1].x;
	dz = localFrustum.axis[2].x;
	index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz );
	dx = cornerVecs[index].x;
	index |= ( FLOATSIGNBITSET( dx ) << 2 );

	if ( indexPoints[index].x > bounds[1].x ) {
		return true;
	}

	dy = -localFrustum.axis[1].y;
	dz = -localFrustum.axis[2].y;
	index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz );
	dx = -cornerVecs[index].y;
	index |= ( FLOATSIGNBITSET( dx ) << 2 );

	if ( indexPoints[index].y < bounds[0].y ) {
		return true;
	}

	dy = localFrustum.axis[1].y;
	dz = localFrustum.axis[2].y;
	index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz );
	dx = cornerVecs[index].y;
	index |= ( FLOATSIGNBITSET( dx ) << 2 );

	if ( indexPoints[index].y > bounds[1].y ) {
		return true;
	}

	dy = -localFrustum.axis[1].z;
	dz = -localFrustum.axis[2].z;
	index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz );
	dx = -cornerVecs[index].z;
	index |= ( FLOATSIGNBITSET( dx ) << 2 );

	if ( indexPoints[index].z < bounds[0].z ) {
		return true;
	}

	dy = localFrustum.axis[1].z;
	dz = localFrustum.axis[2].z;
	index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz );
	dx = cornerVecs[index].z;
	index |= ( FLOATSIGNBITSET( dx ) << 2 );

	if ( indexPoints[index].z > bounds[1].z ) {
		return true;
	}

	return false;
}

/*
============
idFrustum::LocalLineIntersection

   7 divs
  30 muls
============
*/
bool idFrustum::LocalLineIntersection( const idVec3 &start, const idVec3 &end ) const {
	idVec3 dir;
	float d1, d2, fstart, fend, lstart, lend, f, x;
	float leftScale, upScale;
	int startInside = 1;

	leftScale = dLeft * invFar;
	upScale = dUp * invFar;
	dir = end - start;

	// test near plane
	if ( dNear > 0.0f ) {
		d1 = dNear - start.x;
		startInside &= FLOATSIGNBITSET( d1 );
		if ( FLOATNOTZERO( d1 ) ) {
			d2 = dNear - end.x;
			if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) {
				f = d1 / ( d1 - d2 );
				if ( idMath::Fabs( start.y + f * dir.y ) <= dNear * leftScale ) {
					if ( idMath::Fabs( start.z + f * dir.z ) <= dNear * upScale ) {
						return true;
					}
				}
			}
		}
	}

	// test far plane
	d1 = start.x - dFar;
	startInside &= FLOATSIGNBITSET( d1 );
	if ( FLOATNOTZERO( d1 ) ) {
		d2 = end.x - dFar;
		if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) {
			f = d1 / ( d1 - d2 );
			if ( idMath::Fabs( start.y + f * dir.y ) <= dFar * leftScale ) {
				if ( idMath::Fabs( start.z + f * dir.z ) <= dFar * upScale ) {
					return true;
				}
			}
		}
	}

	fstart = dFar * start.y;
	fend = dFar * end.y;
	lstart = dLeft * start.x;
	lend = dLeft * end.x;

	// test left plane
	d1 = fstart - lstart;
	startInside &= FLOATSIGNBITSET( d1 );
	if ( FLOATNOTZERO( d1 ) ) {
		d2 = fend - lend;
		if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) {
			f = d1 / ( d1 - d2 );
			x = start.x + f * dir.x;
			if ( x >= dNear && x <= dFar ) {
				if ( idMath::Fabs( start.z + f * dir.z ) <= x * upScale ) {
					return true;
				}
			}
		}
	}

	// test right plane
	d1 = -fstart - lstart;
	startInside &= FLOATSIGNBITSET( d1 );
	if ( FLOATNOTZERO( d1 ) ) {
		d2 = -fend - lend;
		if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) {
			f = d1 / ( d1 - d2 );
			x = start.x + f * dir.x;
			if ( x >= dNear && x <= dFar ) {
				if ( idMath::Fabs( start.z + f * dir.z ) <= x * upScale ) {
					return true;
				}
			}
		}
	}

	fstart = dFar * start.z;
	fend = dFar * end.z;
	lstart = dUp * start.x;
	lend = dUp * end.x;

	// test up plane
	d1 = fstart - lstart;
	startInside &= FLOATSIGNBITSET( d1 );
	if ( FLOATNOTZERO( d1 ) ) {
		d2 = fend - lend;
		if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) {
			f = d1 / ( d1 - d2 );
			x = start.x + f * dir.x;
			if ( x >= dNear && x <= dFar ) {
				if ( idMath::Fabs( start.y + f * dir.y ) <= x * leftScale ) {
					return true;
				}
			}
		}
	}

	// test down plane
	d1 = -fstart - lstart;
	startInside &= FLOATSIGNBITSET( d1 );
	if ( FLOATNOTZERO( d1 ) ) {
		d2 = -fend - lend;
		if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) {
			f = d1 / ( d1 - d2 );
			x = start.x + f * dir.x;
			if ( x >= dNear && x <= dFar ) {
				if ( idMath::Fabs( start.y + f * dir.y ) <= x * leftScale ) {
					return true;
				}
			}
		}
	}

	return ( startInside != 0 );
}

/*
============
idFrustum::LocalRayIntersection

  Returns true if the ray starts inside the frustum.
  If there was an intersection scale1 <= scale2
============
*/
bool idFrustum::LocalRayIntersection( const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const {
	idVec3 end;
	float d1, d2, fstart, fend, lstart, lend, f, x;
	float leftScale, upScale;
	int startInside = 1;

	leftScale = dLeft * invFar;
	upScale = dUp * invFar;
	end = start + dir;

	scale1 = idMath::INFINITY;
	scale2 = -idMath::INFINITY;

	// test near plane
	if ( dNear > 0.0f ) {
		d1 = dNear - start.x;
		startInside &= FLOATSIGNBITSET( d1 );
		d2 = dNear - end.x;
		if ( d1 != d2 ) {
			f = d1 / ( d1 - d2 );
			if ( idMath::Fabs( start.y + f * dir.y ) <= dNear * leftScale ) {
				if ( idMath::Fabs( start.z + f * dir.z ) <= dNear * upScale ) {
					if ( f < scale1 ) scale1 = f;
					if ( f > scale2 ) scale2 = f;
				}
			}
		}
	}

	// test far plane
	d1 = start.x - dFar;
	startInside &= FLOATSIGNBITSET( d1 );
	d2 = end.x - dFar;
	if ( d1 != d2 ) {
		f = d1 / ( d1 - d2 );
		if ( idMath::Fabs( start.y + f * dir.y ) <= dFar * leftScale ) {
			if ( idMath::Fabs( start.z + f * dir.z ) <= dFar * upScale ) {
				if ( f < scale1 ) scale1 = f;
				if ( f > scale2 ) scale2 = f;
			}
		}
	}

	fstart = dFar * start.y;
	fend = dFar * end.y;
	lstart = dLeft * start.x;
	lend = dLeft * end.x;

	// test left plane
	d1 = fstart - lstart;
	startInside &= FLOATSIGNBITSET( d1 );
	d2 = fend - lend;
	if ( d1 != d2 ) {
		f = d1 / ( d1 - d2 );
		x = start.x + f * dir.x;
		if ( x >= dNear && x <= dFar ) {
			if ( idMath::Fabs( start.z + f * dir.z ) <= x * upScale ) {
				if ( f < scale1 ) scale1 = f;
				if ( f > scale2 ) scale2 = f;
			}
		}
	}

	// test right plane
	d1 = -fstart - lstart;
	startInside &= FLOATSIGNBITSET( d1 );
	d2 = -fend - lend;
	if ( d1 != d2 ) {
		f = d1 / ( d1 - d2 );
		x = start.x + f * dir.x;
		if ( x >= dNear && x <= dFar ) {
			if ( idMath::Fabs( start.z + f * dir.z ) <= x * upScale ) {
				if ( f < scale1 ) scale1 = f;
				if ( f > scale2 ) scale2 = f;
			}
		}
	}

	fstart = dFar * start.z;
	fend = dFar * end.z;
	lstart = dUp * start.x;
	lend = dUp * end.x;

	// test up plane
	d1 = fstart - lstart;
	startInside &= FLOATSIGNBITSET( d1 );
	d2 = fend - lend;
	if ( d1 != d2 ) {
		f = d1 / ( d1 - d2 );
		x = start.x + f * dir.x;
		if ( x >= dNear && x <= dFar ) {
			if ( idMath::Fabs( start.y + f * dir.y ) <= x * leftScale ) {
				if ( f < scale1 ) scale1 = f;
				if ( f > scale2 ) scale2 = f;
			}
		}
	}

	// test down plane
	d1 = -fstart - lstart;
	startInside &= FLOATSIGNBITSET( d1 );
	d2 = -fend - lend;
	if ( d1 != d2 ) {
		f = d1 / ( d1 - d2 );
		x = start.x + f * dir.x;
		if ( x >= dNear && x <= dFar ) {
			if ( idMath::Fabs( start.y + f * dir.y ) <= x * leftScale ) {
				if ( f < scale1 ) scale1 = f;
				if ( f > scale2 ) scale2 = f;
			}
		}
	}

	return ( startInside != 0 );
}

/*
============
idFrustum::ContainsPoint
============
*/
bool idFrustum::ContainsPoint( const idVec3 &point ) const {
	return !CullPoint( point );
}

/*
============
idFrustum::LocalFrustumIntersectsFrustum
============
*/
bool idFrustum::LocalFrustumIntersectsFrustum( const idVec3 points[8], const bool testFirstSide ) const {
	int i;

	// test if any edges of the other frustum intersect this frustum
	for ( i = 0; i < 4; i++ ) {
		if ( LocalLineIntersection( points[i], points[4+i] ) ) {
			return true;
		}
	}
	if ( testFirstSide ) {
		for ( i = 0; i < 4; i++ ) {
			if ( LocalLineIntersection( points[i], points[(i+1)&3] ) ) {
				return true;
			}
		}
	}
	for ( i = 0; i < 4; i++ ) {
		if ( LocalLineIntersection( points[4+i], points[4+((i+1)&3)] ) ) {
			return true;
		}
	}

	return false;
}

/*
============
idFrustum::LocalFrustumIntersectsBounds
============
*/
bool idFrustum::LocalFrustumIntersectsBounds( const idVec3 points[8], const idBounds &bounds ) const {
	int i;

	// test if any edges of the other frustum intersect this frustum
	for ( i = 0; i < 4; i++ ) {
		if ( bounds.LineIntersection( points[i], points[4+i] ) ) {
			return true;
		}
	}
	if ( dNear > 0.0f ) {
		for ( i = 0; i < 4; i++ ) {
			if ( bounds.LineIntersection( points[i], points[(i+1)&3] ) ) {
				return true;
			}
		}
	}
	for ( i = 0; i < 4; i++ ) {
		if ( bounds.LineIntersection( points[4+i], points[4+((i+1)&3)] ) ) {
			return true;
		}
	}

	return false;
}

/*
============
idFrustum::IntersectsBounds
============
*/
bool idFrustum::IntersectsBounds( const idBounds &bounds ) const {
	idVec3 localOrigin, center, extents;
	idMat3 localAxis;

	center = ( bounds[0] + bounds[1] ) * 0.5f;
	extents = bounds[1] - center;

	localOrigin = ( center - origin ) * axis.Transpose();
	localAxis = axis.Transpose();

	if ( CullLocalBox( localOrigin, extents, localAxis ) ) {
		return false;
	}

	idVec3 indexPoints[8], cornerVecs[4];

	ToIndexPointsAndCornerVecs( indexPoints, cornerVecs );

	if ( BoundsCullLocalFrustum( bounds, *this, indexPoints, cornerVecs ) ) {
		return false;
	}

	idSwap( indexPoints[2], indexPoints[3] );
	idSwap( indexPoints[6], indexPoints[7] );

	if ( LocalFrustumIntersectsBounds( indexPoints, bounds ) ) {
		return true;
	}

	BoxToPoints( localOrigin, extents, localAxis, indexPoints );

	if ( LocalFrustumIntersectsFrustum( indexPoints, true ) ) {
		return true;
	}

	return false;
}

/*
============
idFrustum::IntersectsBox
============
*/
bool idFrustum::IntersectsBox( const idBox &box ) const {
	idVec3 localOrigin;
	idMat3 localAxis;

	localOrigin = ( box.GetCenter() - origin ) * axis.Transpose();
	localAxis = box.GetAxis() * axis.Transpose();

	if ( CullLocalBox( localOrigin, box.GetExtents(), localAxis ) ) {
		return false;
	}

	idVec3 indexPoints[8], cornerVecs[4];
	idFrustum localFrustum;

	localFrustum = *this;
	localFrustum.origin = ( origin - box.GetCenter() ) * box.GetAxis().Transpose();
	localFrustum.axis = axis * box.GetAxis().Transpose();
	localFrustum.ToIndexPointsAndCornerVecs( indexPoints, cornerVecs );

	if ( BoundsCullLocalFrustum( idBounds( -box.GetExtents(), box.GetExtents() ), localFrustum, indexPoints, cornerVecs ) ) {
		return false;
	}

	idSwap( indexPoints[2], indexPoints[3] );
	idSwap( indexPoints[6], indexPoints[7] );

	if ( LocalFrustumIntersectsBounds( indexPoints, idBounds( -box.GetExtents(), box.GetExtents() ) ) ) {
		return true;
	}

	BoxToPoints( localOrigin, box.GetExtents(), localAxis, indexPoints );

	if ( LocalFrustumIntersectsFrustum( indexPoints, true ) ) {
		return true;
	}

	return false;
}

/*
============
idFrustum::IntersectsSphere

  FIXME: test this
============
*/
#define VORONOI_INDEX( x, y, z )	( x + y * 3 + z * 9 )

bool idFrustum::IntersectsSphere( const idSphere &sphere ) const {
	int index, x, y, z;
	float scale, r, d;
	idVec3 p, dir, points[8];

	if ( CullSphere( sphere ) ) {
		return false;
	}

	x = y = z = 0;
	dir.Zero();

	p = ( sphere.GetOrigin() - origin ) * axis.Transpose();

	if ( p.x <= dNear ) {
		scale = dNear * invFar;
		dir.y = idMath::Fabs( p.y ) - dLeft * scale;
		dir.z = idMath::Fabs( p.z ) - dUp * scale;
	}
	else if ( p.x >= dFar ) {
		dir.y = idMath::Fabs( p.y ) - dLeft;
		dir.z = idMath::Fabs( p.z ) - dUp;
	}
	else {
		scale = p.x * invFar;
		dir.y = idMath::Fabs( p.y ) - dLeft * scale;
		dir.z = idMath::Fabs( p.z ) - dUp * scale;
	}
	if ( dir.y > 0.0f ) {
		y = ( 1 + FLOATSIGNBITNOTSET( p.y ) );
	}
	if ( dir.z > 0.0f ) {
		z = ( 1 + FLOATSIGNBITNOTSET( p.z ) );
	}
	if ( p.x < dNear ) {
		scale = dLeft * dNear * invFar;
		if ( p.x < dNear + ( scale - p.y ) * scale * invFar ) {
			scale = dUp * dNear * invFar;
			if ( p.x < dNear + ( scale - p.z ) * scale * invFar ) {
				x = 1;
			}
		}
	}
	else {
		if ( p.x > dFar ) {
			x = 2;
		}
		else if ( p.x > dFar + ( dLeft - p.y ) * dLeft * invFar ) {
			x = 2;
		}
		else if ( p.x > dFar + ( dUp - p.z ) * dUp * invFar ) {
			x = 2;
		}
	}

	r = sphere.GetRadius();
	index = VORONOI_INDEX( x, y, z );
	switch( index ) {
		case VORONOI_INDEX( 0, 0, 0 ): return true;
		case VORONOI_INDEX( 1, 0, 0 ): return ( dNear - p.x < r );
		case VORONOI_INDEX( 2, 0, 0 ): return ( p.x - dFar < r );
		case VORONOI_INDEX( 0, 1, 0 ): d = dFar * p.y - dLeft * p.x; return ( d * d < r * r * ( dFar * dFar + dLeft * dLeft ) );
		case VORONOI_INDEX( 0, 2, 0 ): d = -dFar * p.z - dLeft * p.x; return ( d * d < r * r * ( dFar * dFar + dLeft * dLeft ) );
		case VORONOI_INDEX( 0, 0, 1 ): d = dFar * p.z - dUp * p.x; return ( d * d < r * r * ( dFar * dFar + dUp * dUp ) );
		case VORONOI_INDEX( 0, 0, 2 ): d = -dFar * p.z - dUp * p.x; return ( d * d < r * r * ( dFar * dFar + dUp * dUp ) );
		default: {
			ToIndexPoints( points );
			switch( index ) {
				case VORONOI_INDEX( 1, 1, 1 ): return sphere.ContainsPoint( points[0] );
				case VORONOI_INDEX( 2, 1, 1 ): return sphere.ContainsPoint( points[4] );
				case VORONOI_INDEX( 1, 2, 1 ): return sphere.ContainsPoint( points[1] );
				case VORONOI_INDEX( 2, 2, 1 ): return sphere.ContainsPoint( points[5] );
				case VORONOI_INDEX( 1, 1, 2 ): return sphere.ContainsPoint( points[2] );
				case VORONOI_INDEX( 2, 1, 2 ): return sphere.ContainsPoint( points[6] );
				case VORONOI_INDEX( 1, 2, 2 ): return sphere.ContainsPoint( points[3] );
				case VORONOI_INDEX( 2, 2, 2 ): return sphere.ContainsPoint( points[7] );
				case VORONOI_INDEX( 1, 1, 0 ): return sphere.LineIntersection( points[0], points[2] );
				case VORONOI_INDEX( 2, 1, 0 ): return sphere.LineIntersection( points[4], points[6] );
				case VORONOI_INDEX( 1, 2, 0 ): return sphere.LineIntersection( points[1], points[3] );
				case VORONOI_INDEX( 2, 2, 0 ): return sphere.LineIntersection( points[5], points[7] );
				case VORONOI_INDEX( 1, 0, 1 ): return sphere.LineIntersection( points[0], points[1] );
				case VORONOI_INDEX( 2, 0, 1 ): return sphere.LineIntersection( points[4], points[5] );
				case VORONOI_INDEX( 0, 1, 1 ): return sphere.LineIntersection( points[0], points[4] );
				case VORONOI_INDEX( 0, 2, 1 ): return sphere.LineIntersection( points[1], points[5] );
				case VORONOI_INDEX( 1, 0, 2 ): return sphere.LineIntersection( points[2], points[3] );
				case VORONOI_INDEX( 2, 0, 2 ): return sphere.LineIntersection( points[6], points[7] );
				case VORONOI_INDEX( 0, 1, 2 ): return sphere.LineIntersection( points[2], points[6] );
				case VORONOI_INDEX( 0, 2, 2 ): return sphere.LineIntersection( points[3], points[7] );
			}
			break;
		}
	}
	return false;
}

/*
============
idFrustum::IntersectsFrustum
============
*/
bool idFrustum::IntersectsFrustum( const idFrustum &frustum ) const {
	idVec3 indexPoints2[8], cornerVecs2[4];
	idFrustum localFrustum2;

	localFrustum2 = frustum;
	localFrustum2.origin = ( frustum.origin - origin ) * axis.Transpose();
	localFrustum2.axis = frustum.axis * axis.Transpose();
	localFrustum2.ToIndexPointsAndCornerVecs( indexPoints2, cornerVecs2 );

	if ( CullLocalFrustum( localFrustum2, indexPoints2, cornerVecs2 ) ) {
		return false;
	}

	idVec3 indexPoints1[8], cornerVecs1[4];
	idFrustum localFrustum1;

	localFrustum1 = *this;
	localFrustum1.origin = ( origin - frustum.origin ) * frustum.axis.Transpose();
	localFrustum1.axis = axis * frustum.axis.Transpose();
	localFrustum1.ToIndexPointsAndCornerVecs( indexPoints1, cornerVecs1 );

	if ( frustum.CullLocalFrustum( localFrustum1, indexPoints1, cornerVecs1 ) ) {
		return false;
	}

	idSwap( indexPoints2[2], indexPoints2[3] );
	idSwap( indexPoints2[6], indexPoints2[7] );

	if ( LocalFrustumIntersectsFrustum( indexPoints2, ( localFrustum2.dNear > 0.0f ) ) ) {
		return true;
	}

	idSwap( indexPoints1[2], indexPoints1[3] );
	idSwap( indexPoints1[6], indexPoints1[7] );

	if ( frustum.LocalFrustumIntersectsFrustum( indexPoints1, ( localFrustum1.dNear > 0.0f ) ) ) {
		return true;
	}

	return false;
}

/*
============
idFrustum::IntersectsWinding
============
*/
bool idFrustum::IntersectsWinding( const idWinding &winding ) const {
	int i, j, *pointCull;
	float min, max;
	idVec3 *localPoints, indexPoints[8], cornerVecs[4];
	idMat3 transpose;
	idPlane plane;

	localPoints = (idVec3 *) _alloca16( winding.GetNumPoints() * sizeof( idVec3 ) );
	pointCull = (int *) _alloca16( winding.GetNumPoints() * sizeof( int ) );

	transpose = axis.Transpose();
	for ( i = 0; i < winding.GetNumPoints(); i++ ) {
		localPoints[i] = ( winding[i].ToVec3() - origin ) * transpose;
	}

	// if the winding is culled
	if ( CullLocalWinding( localPoints, winding.GetNumPoints(), pointCull ) ) {
		return false;
	}

	winding.GetPlane( plane );

	ToIndexPointsAndCornerVecs( indexPoints, cornerVecs );
	AxisProjection( indexPoints, cornerVecs, plane.Normal(), min, max );

	// if the frustum does not cross the winding plane
	if ( min + plane[3] > 0.0f || max + plane[3] < 0.0f ) {
		return false;
	}

	// test if any of the winding edges goes through the frustum
	for ( i = 0; i < winding.GetNumPoints(); i++ ) {
		j = (i+1)%winding.GetNumPoints();
		if ( !( pointCull[i] & pointCull[j] ) ) {
			if ( LocalLineIntersection( localPoints[i], localPoints[j] ) ) {
				return true;
			}
		}
	}

	idSwap( indexPoints[2], indexPoints[3] );
	idSwap( indexPoints[6], indexPoints[7] );

	// test if any edges of the frustum intersect the winding
	for ( i = 0; i < 4; i++ ) {
		if ( winding.LineIntersection( plane, indexPoints[i], indexPoints[4+i] ) ) {
			return true;
		}
	}
	if ( dNear > 0.0f ) {
		for ( i = 0; i < 4; i++ ) {
			if ( winding.LineIntersection( plane, indexPoints[i], indexPoints[(i+1)&3] ) ) {
				return true;
			}
		}
	}
	for ( i = 0; i < 4; i++ ) {
		if ( winding.LineIntersection( plane, indexPoints[4+i], indexPoints[4+((i+1)&3)] ) ) {
			return true;
		}
	}

	return false;
}

/*
============
idFrustum::LineIntersection

  Returns true if the line intersects the box between the start and end point.
============
*/
bool idFrustum::LineIntersection( const idVec3 &start, const idVec3 &end ) const {
	return LocalLineIntersection( ( start - origin ) * axis.Transpose(), ( end - origin ) * axis.Transpose() );
}

/*
============
idFrustum::RayIntersection

  Returns true if the ray intersects the bounds.
  The ray can intersect the bounds in both directions from the start point.
  If start is inside the frustum then scale1 < 0 and scale2 > 0.
============
*/
bool idFrustum::RayIntersection( const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const {
	if ( LocalRayIntersection( ( start - origin ) * axis.Transpose(), dir * axis.Transpose(), scale1, scale2 ) ) {
		return true;
	}
	if ( scale1 <= scale2 ) {
		return true;
	}
	return false;
}

/*
============
idFrustum::FromProjection

  Creates a frustum which contains the projection of the bounds.
============
*/
bool idFrustum::FromProjection( const idBounds &bounds, const idVec3 &projectionOrigin, const float dFar ) {
	return FromProjection( idBox( bounds, vec3_origin, mat3_identity ), projectionOrigin, dFar );
}

/*
============
idFrustum::FromProjection

  Creates a frustum which contains the projection of the box.
============
*/
bool idFrustum::FromProjection( const idBox &box, const idVec3 &projectionOrigin, const float dFar ) {
	int i, bestAxis;
	float value, bestValue;
	idVec3 dir;

	assert( dFar > 0.0f );

	this->dNear = this->dFar = this->invFar = 0.0f;

	dir = box.GetCenter() - projectionOrigin;
	if ( dir.Normalize() == 0.0f ) {
		return false;
	}

	bestAxis = 0;
	bestValue = idMath::Fabs( box.GetAxis()[0] * dir );
	for ( i = 1; i < 3; i++ ) {
		value = idMath::Fabs( box.GetAxis()[i] * dir );
		if ( value * box.GetExtents()[bestAxis] * box.GetExtents()[bestAxis] < bestValue * box.GetExtents()[i] * box.GetExtents()[i] ) {
			bestValue = value;
			bestAxis = i;
		}
	}

#if 1

	int j, minX, minY, maxY, minZ, maxZ;
	idVec3 points[8];

	minX = minY = maxY = minZ = maxZ = 0;

	for ( j = 0; j < 2; j++ ) {

		axis[0] = dir;
		axis[1] = box.GetAxis()[bestAxis] - ( box.GetAxis()[bestAxis] * axis[0] ) * axis[0];
		axis[1].Normalize();
		axis[2].Cross( axis[0], axis[1] );

		BoxToPoints( ( box.GetCenter() - projectionOrigin ) * axis.Transpose(), box.GetExtents(), box.GetAxis() * axis.Transpose(), points );

		if ( points[0].x <= 1.0f ) {
			return false;
		}

		minX = minY = maxY = minZ = maxZ = 0;
		for ( i = 1; i < 8; i++ ) {
			if ( points[i].x <= 1.0f ) {
				return false;
			}
			if ( points[i].x < points[minX].x ) {
				minX = i;
			}
			if ( points[minY].x * points[i].y < points[i].x * points[minY].y ) {
				minY = i;
			} else if ( points[maxY].x * points[i].y > points[i].x * points[maxY].y ) {
				maxY = i;
			}
			if ( points[minZ].x * points[i].z < points[i].x * points[minZ].z ) {
				minZ = i;
			} else if ( points[maxZ].x * points[i].z > points[i].x * points[maxZ].z ) {
				maxZ = i;
			}
		}

		if ( j == 0 ) {
			dir += idMath::Tan16( 0.5f * ( idMath::ATan16( points[minY].y, points[minY].x ) + idMath::ATan16( points[maxY].y, points[maxY].x ) ) ) * axis[1];
			dir += idMath::Tan16( 0.5f * ( idMath::ATan16( points[minZ].z, points[minZ].x ) + idMath::ATan16( points[maxZ].z, points[maxZ].x ) ) ) * axis[2];
			dir.Normalize();
		}
	}

	this->origin = projectionOrigin;
	this->dNear = points[minX].x;
	this->dFar = dFar;
	this->dLeft = Max( idMath::Fabs( points[minY].y / points[minY].x ), idMath::Fabs( points[maxY].y / points[maxY].x ) ) * dFar;
	this->dUp = Max( idMath::Fabs( points[minZ].z / points[minZ].x ), idMath::Fabs( points[maxZ].z / points[maxZ].x ) ) * dFar;
	this->invFar = 1.0f / dFar;

#elif 1

	int j;
	float f, x;
	idBounds b;
	idVec3 points[8];

	for ( j = 0; j < 2; j++ ) {

		axis[0] = dir;
		axis[1] = box.GetAxis()[bestAxis] - ( box.GetAxis()[bestAxis] * axis[0] ) * axis[0];
		axis[1].Normalize();
		axis[2].Cross( axis[0], axis[1] );

		BoxToPoints( ( box.GetCenter() - projectionOrigin ) * axis.Transpose(), box.GetExtents(), box.GetAxis() * axis.Transpose(), points );

		b.Clear();
		for ( i = 0; i < 8; i++ ) {
			x = points[i].x;
			if ( x <= 1.0f ) {
				return false;
			}
			f = 1.0f / x;
			points[i].y *= f;
			points[i].z *= f;
			b.AddPoint( points[i] );
		}

		if ( j == 0 ) {
			dir += idMath::Tan16( 0.5f * ( idMath::ATan16( b[1][1] ) + idMath::ATan16( b[0][1] ) ) ) * axis[1];
			dir += idMath::Tan16( 0.5f * ( idMath::ATan16( b[1][2] ) + idMath::ATan16( b[0][2] ) ) ) * axis[2];
			dir.Normalize();
		}
	}

	this->origin = projectionOrigin;
	this->dNear = b[0][0];
	this->dFar = dFar;
	this->dLeft = Max( idMath::Fabs( b[0][1] ), idMath::Fabs( b[1][1] ) ) * dFar;
	this->dUp = Max( idMath::Fabs( b[0][2] ), idMath::Fabs( b[1][2] ) ) * dFar;
	this->invFar = 1.0f / dFar;

#else

	float dist;
	idVec3 org;

	axis[0] = dir;
	axis[1] = box.GetAxis()[bestAxis] - ( box.GetAxis()[bestAxis] * axis[0] ) * axis[0];
	axis[1].Normalize();
	axis[2].Cross( axis[0], axis[1] );

	for ( i = 0; i < 3; i++ ) {
		dist[i] = idMath::Fabs( box.GetExtents()[0] * ( axis[i] * box.GetAxis()[0] ) ) +
					idMath::Fabs( box.GetExtents()[1] * ( axis[i] * box.GetAxis()[1] ) ) +
						idMath::Fabs( box.GetExtents()[2] * ( axis[i] * box.GetAxis()[2] ) );
	}

	dist[0] = axis[0] * ( box.GetCenter() - projectionOrigin ) - dist[0];
	if ( dist[0] <= 1.0f ) {
		return false;
	}
	float invDist = 1.0f / dist[0];

	this->origin = projectionOrigin;
	this->dNear = dist[0];
	this->dFar = dFar;
	this->dLeft = dist[1] * invDist * dFar;
	this->dUp = dist[2] * invDist * dFar;
	this->invFar = 1.0f / dFar;

#endif

	return true;
}

/*
============
idFrustum::FromProjection

  Creates a frustum which contains the projection of the sphere.
============
*/
bool idFrustum::FromProjection( const idSphere &sphere, const idVec3 &projectionOrigin, const float dFar ) {
	idVec3 dir;
	float d, r, s, x, y;

	assert( dFar > 0.0f );

	dir = sphere.GetOrigin() - projectionOrigin;
	d = dir.Normalize();
	r = sphere.GetRadius();

	if ( d <= r + 1.0f ) {
		this->dNear = this->dFar = this->invFar = 0.0f;
		return false;
	}

	origin = projectionOrigin;
	axis = dir.ToMat3();

	s = idMath::Sqrt( d * d - r * r );
	x = r / d * s;
	y = idMath::Sqrt( s * s - x * x );

	this->dNear = d - r;
	this->dFar = dFar;
	this->dLeft = x / y * dFar;
	this->dUp = dLeft;
	this->invFar = 1.0f / dFar;

	return true;
}

/*
============
idFrustum::ConstrainToBounds

  Returns false if no part of the bounds extends beyond the near plane.
============
*/
bool idFrustum::ConstrainToBounds( const idBounds &bounds ) {
	float min, max, newdFar;

	bounds.AxisProjection( axis[0], min, max );
	newdFar = max - axis[0] * origin;
	if ( newdFar <= dNear ) {
		MoveFarDistance( dNear + 1.0f );
		return false;
	}
	MoveFarDistance( newdFar );
	return true;
}

/*
============
idFrustum::ConstrainToBox

  Returns false if no part of the box extends beyond the near plane.
============
*/
bool idFrustum::ConstrainToBox( const idBox &box ) {
	float min, max, newdFar;

	box.AxisProjection( axis[0], min, max );
	newdFar = max - axis[0] * origin;
	if ( newdFar <= dNear ) {
		MoveFarDistance( dNear + 1.0f );
		return false;
	}
	MoveFarDistance( newdFar );
	return true;
}

/*
============
idFrustum::ConstrainToSphere

  Returns false if no part of the sphere extends beyond the near plane.
============
*/
bool idFrustum::ConstrainToSphere( const idSphere &sphere ) {
	float min, max, newdFar;

	sphere.AxisProjection( axis[0], min, max );
	newdFar = max - axis[0] * origin;
	if ( newdFar <= dNear ) {
		MoveFarDistance( dNear + 1.0f );
		return false;
	}
	MoveFarDistance( newdFar );
	return true;
}

/*
============
idFrustum::ConstrainToFrustum

  Returns false if no part of the frustum extends beyond the near plane.
============
*/
bool idFrustum::ConstrainToFrustum( const idFrustum &frustum ) {
	float min, max, newdFar;

	frustum.AxisProjection( axis[0], min, max );
	newdFar = max - axis[0] * origin;
	if ( newdFar <= dNear ) {
		MoveFarDistance( dNear + 1.0f );
		return false;
	}
	MoveFarDistance( newdFar );
	return true;
}

/*
============
idFrustum::ToPlanes

  planes point outwards
============
*/
void idFrustum::ToPlanes( idPlane planes[6] ) const {
	int i;
	idVec3 scaled[2];
	idVec3 points[4];

	planes[0].Normal() = -axis[0];
	planes[0].SetDist( -dNear );
	planes[1].Normal() = axis[0];
	planes[1].SetDist( dFar );

	scaled[0] = axis[1] * dLeft;
	scaled[1] = axis[2] * dUp;
	points[0] = scaled[0] + scaled[1];
	points[1] = -scaled[0] + scaled[1];
	points[2] = -scaled[0] - scaled[1];
	points[3] = scaled[0] - scaled[1];

	for ( i = 0; i < 4; i++ ) {
		planes[i+2].Normal() = points[i].Cross( points[(i+1)&3] - points[i] );
		planes[i+2].Normalize();
		planes[i+2].FitThroughPoint( points[i] );
	}
}

/*
============
idFrustum::ToPoints
============
*/
void idFrustum::ToPoints( idVec3 points[8] ) const {
	idMat3 scaled;

	scaled[0] = origin + axis[0] * dNear;
	scaled[1] = axis[1] * ( dLeft * dNear * invFar );
	scaled[2] = axis[2] * ( dUp * dNear * invFar );

	points[0] = scaled[0] + scaled[1];
	points[1] = scaled[0] - scaled[1];
	points[2] = points[1] - scaled[2];
	points[3] = points[0] - scaled[2];
	points[0] += scaled[2];
	points[1] += scaled[2];

	scaled[0] = origin + axis[0] * dFar;
	scaled[1] = axis[1] * dLeft;
	scaled[2] = axis[2] * dUp;

	points[4] = scaled[0] + scaled[1];
	points[5] = scaled[0] - scaled[1];
	points[6] = points[5] - scaled[2];
	points[7] = points[4] - scaled[2];
	points[4] += scaled[2];
	points[5] += scaled[2];
}

/*
============
idFrustum::ToClippedPoints
============
*/
void idFrustum::ToClippedPoints( const float fractions[4], idVec3 points[8] ) const {
	idMat3 scaled;

	scaled[0] = origin + axis[0] * dNear;
	scaled[1] = axis[1] * ( dLeft * dNear * invFar );
	scaled[2] = axis[2] * ( dUp * dNear * invFar );

	points[0] = scaled[0] + scaled[1];
	points[1] = scaled[0] - scaled[1];
	points[2] = points[1] - scaled[2];
	points[3] = points[0] - scaled[2];
	points[0] += scaled[2];
	points[1] += scaled[2];

	scaled[0] = axis[0] * dFar;
	scaled[1] = axis[1] * dLeft;
	scaled[2] = axis[2] * dUp;

	points[4] = scaled[0] + scaled[1];
	points[5] = scaled[0] - scaled[1];
	points[6] = points[5] - scaled[2];
	points[7] = points[4] - scaled[2];
	points[4] += scaled[2];
	points[5] += scaled[2];

	points[4] = origin + fractions[0] * points[4];
	points[5] = origin + fractions[1] * points[5];
	points[6] = origin + fractions[2] * points[6];
	points[7] = origin + fractions[3] * points[7];
}

/*
============
idFrustum::ToIndexPoints
============
*/
void idFrustum::ToIndexPoints( idVec3 indexPoints[8] ) const {
	idMat3 scaled;

	scaled[0] = origin + axis[0] * dNear;
	scaled[1] = axis[1] * ( dLeft * dNear * invFar );
	scaled[2] = axis[2] * ( dUp * dNear * invFar );

	indexPoints[0] = scaled[0] - scaled[1];
	indexPoints[2] = scaled[0] + scaled[1];
	indexPoints[1] = indexPoints[0] + scaled[2];
	indexPoints[3] = indexPoints[2] + scaled[2];
	indexPoints[0] -= scaled[2];
	indexPoints[2] -= scaled[2];

	scaled[0] = origin + axis[0] * dFar;
	scaled[1] = axis[1] * dLeft;
	scaled[2] = axis[2] * dUp;

	indexPoints[4] = scaled[0] - scaled[1];
	indexPoints[6] = scaled[0] + scaled[1];
	indexPoints[5] = indexPoints[4] + scaled[2];
	indexPoints[7] = indexPoints[6] + scaled[2];
	indexPoints[4] -= scaled[2];
	indexPoints[6] -= scaled[2];
}

/*
============
idFrustum::ToIndexPointsAndCornerVecs

  22 muls
============
*/
void idFrustum::ToIndexPointsAndCornerVecs( idVec3 indexPoints[8], idVec3 cornerVecs[4] ) const {
	idMat3 scaled;

	scaled[0] = origin + axis[0] * dNear;
	scaled[1] = axis[1] * ( dLeft * dNear * invFar );
	scaled[2] = axis[2] * ( dUp * dNear * invFar );

	indexPoints[0] = scaled[0] - scaled[1];
	indexPoints[2] = scaled[0] + scaled[1];
	indexPoints[1] = indexPoints[0] + scaled[2];
	indexPoints[3] = indexPoints[2] + scaled[2];
	indexPoints[0] -= scaled[2];
	indexPoints[2] -= scaled[2];

	scaled[0] = axis[0] * dFar;
	scaled[1] = axis[1] * dLeft;
	scaled[2] = axis[2] * dUp;

	cornerVecs[0] = scaled[0] - scaled[1];
	cornerVecs[2] = scaled[0] + scaled[1];
	cornerVecs[1] = cornerVecs[0] + scaled[2];
	cornerVecs[3] = cornerVecs[2] + scaled[2];
	cornerVecs[0] -= scaled[2];
	cornerVecs[2] -= scaled[2];

	indexPoints[4] = cornerVecs[0] + origin;
	indexPoints[5] = cornerVecs[1] + origin;
	indexPoints[6] = cornerVecs[2] + origin;
	indexPoints[7] = cornerVecs[3] + origin;
}

/*
============
idFrustum::AxisProjection

  18 muls
============
*/
void idFrustum::AxisProjection( const idVec3 indexPoints[8], const idVec3 cornerVecs[4], const idVec3 &dir, float &min, float &max ) const {
	float dx, dy, dz;
	int index;

	dy = dir.x * axis[1].x + dir.y * axis[1].y + dir.z * axis[1].z;
	dz = dir.x * axis[2].x + dir.y * axis[2].y + dir.z * axis[2].z;
	index = ( FLOATSIGNBITSET( dy ) << 1 ) | FLOATSIGNBITSET( dz );
	dx = dir.x * cornerVecs[index].x + dir.y * cornerVecs[index].y + dir.z * cornerVecs[index].z;
	index |= ( FLOATSIGNBITSET( dx ) << 2 );
	min = indexPoints[index] * dir;
	index = ~index & 3;
	dx = -dir.x * cornerVecs[index].x - dir.y * cornerVecs[index].y - dir.z * cornerVecs[index].z;
	index |= ( FLOATSIGNBITSET( dx ) << 2 );
	max = indexPoints[index] * dir;
}

/*
============
idFrustum::AxisProjection

  40 muls
============
*/
void idFrustum::AxisProjection( const idVec3 &dir, float &min, float &max ) const {
	idVec3 indexPoints[8], cornerVecs[4];

	ToIndexPointsAndCornerVecs( indexPoints, cornerVecs );
	AxisProjection( indexPoints, cornerVecs, dir, min, max );
}

/*
============
idFrustum::AxisProjection

  76 muls
============
*/
void idFrustum::AxisProjection( const idMat3 &ax, idBounds &bounds ) const {
	idVec3 indexPoints[8], cornerVecs[4];

	ToIndexPointsAndCornerVecs( indexPoints, cornerVecs );
	AxisProjection( indexPoints, cornerVecs, ax[0], bounds[0][0], bounds[1][0] );
	AxisProjection( indexPoints, cornerVecs, ax[1], bounds[0][1], bounds[1][1] );
	AxisProjection( indexPoints, cornerVecs, ax[2], bounds[0][2], bounds[1][2] );
}

/*
============
idFrustum::AddLocalLineToProjectionBoundsSetCull
============
*/
void idFrustum::AddLocalLineToProjectionBoundsSetCull( const idVec3 &start, const idVec3 &end, int &startCull, int &endCull, idBounds &bounds ) const {
	idVec3 dir, p;
	float d1, d2, fstart, fend, lstart, lend, f;
	float leftScale, upScale;
	int cull1, cull2;

#ifdef FRUSTUM_DEBUG
	static idCVar r_showInteractionScissors( "r_showInteractionScissors", "0", CVAR_RENDERER | CVAR_INTEGER, "", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> );
	if ( r_showInteractionScissors.GetInteger() > 1 ) {
		session->rw->DebugLine( colorGreen, origin + start * axis, origin + end * axis );
	}
#endif

	leftScale = dLeft * invFar;
	upScale = dUp * invFar;
	dir = end - start;

	fstart = dFar * start.y;
	fend = dFar * end.y;
	lstart = dLeft * start.x;
	lend = dLeft * end.x;

	// test left plane
	d1 = -fstart + lstart;
	d2 = -fend + lend;
	cull1 = FLOATSIGNBITSET( d1 );
	cull2 = FLOATSIGNBITSET( d2 );
	if ( FLOATNOTZERO( d1 ) ) {
		if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) {
			f = d1 / ( d1 - d2 );
			p.x = start.x + f * dir.x;
			if ( p.x > 0.0f ) {
				p.z = start.z + f * dir.z;
				if ( idMath::Fabs( p.z ) <= p.x * upScale ) {
					p.y = 1.0f;
					p.z = p.z * dFar / ( p.x * dUp );
					bounds.AddPoint( p );
				}
			}
		}
	}

	// test right plane
	d1 = fstart + lstart;
	d2 = fend + lend;
	cull1 |= FLOATSIGNBITSET( d1 ) << 1;
	cull2 |= FLOATSIGNBITSET( d2 ) << 1;
	if ( FLOATNOTZERO( d1 ) ) {
		if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) {
			f = d1 / ( d1 - d2 );
			p.x = start.x + f * dir.x;
			if ( p.x > 0.0f ) {
				p.z = start.z + f * dir.z;
				if ( idMath::Fabs( p.z  ) <= p.x * upScale ) {
					p.y = -1.0f;
					p.z = p.z * dFar / ( p.x * dUp );
					bounds.AddPoint( p );
				}
			}
		}
	}

	fstart = dFar * start.z;
	fend = dFar * end.z;
	lstart = dUp * start.x;
	lend = dUp * end.x;

	// test up plane
	d1 = -fstart + lstart;
	d2 = -fend + lend;
	cull1 |= FLOATSIGNBITSET( d1 ) << 2;
	cull2 |= FLOATSIGNBITSET( d2 ) << 2;
	if ( FLOATNOTZERO( d1 ) ) {
		if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) {
			f = d1 / ( d1 - d2 );
			p.x = start.x + f * dir.x;
			if ( p.x > 0.0f ) {
				p.y = start.y + f * dir.y;
				if ( idMath::Fabs( p.y ) <= p.x * leftScale ) {
					p.y = p.y * dFar / ( p.x * dLeft );
					p.z = 1.0f;
					bounds.AddPoint( p );
				}
			}
		}
	}

	// test down plane
	d1 = fstart + lstart;
	d2 = fend + lend;
	cull1 |= FLOATSIGNBITSET( d1 ) << 3;
	cull2 |= FLOATSIGNBITSET( d2 ) << 3;
	if ( FLOATNOTZERO( d1 ) ) {
		if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) {
			f = d1 / ( d1 - d2 );
			p.x = start.x + f * dir.x;
			if ( p.x > 0.0f ) {
				p.y = start.y + f * dir.y;
				if ( idMath::Fabs( p.y ) <= p.x * leftScale ) {
					p.y = p.y * dFar / ( p.x * dLeft );
					p.z = -1.0f;
					bounds.AddPoint( p );
				}
			}
		}
	}

	if ( cull1 == 0 && start.x > 0.0f ) {
		// add start point to projection bounds
		p.x = start.x;
		p.y = start.y * dFar / ( start.x * dLeft );
		p.z = start.z * dFar / ( start.x * dUp );
		bounds.AddPoint( p );
	}

	if ( cull2 == 0 && end.x > 0.0f ) {
		// add end point to projection bounds
		p.x = end.x;
		p.y = end.y * dFar / ( end.x * dLeft );
		p.z = end.z * dFar / ( end.x * dUp );
		bounds.AddPoint( p );
	}

	if ( start.x < bounds[0].x ) {
		bounds[0].x = start.x < 0.0f ? 0.0f : start.x;
	}
	if ( end.x < bounds[0].x ) {
		bounds[0].x = end.x < 0.0f ? 0.0f : end.x;
	}

	startCull = cull1;
	endCull = cull2;
}

/*
============
idFrustum::AddLocalLineToProjectionBoundsUseCull
============
*/
void idFrustum::AddLocalLineToProjectionBoundsUseCull( const idVec3 &start, const idVec3 &end, int startCull, int endCull, idBounds &bounds ) const {
	idVec3 dir, p;
	float d1, d2, fstart, fend, lstart, lend, f;
	float leftScale, upScale;
	int clip;

	clip = startCull ^ endCull;
	if ( !clip ) {
		return;
	}

#ifdef FRUSTUM_DEBUG
	static idCVar r_showInteractionScissors( "r_showInteractionScissors", "0", CVAR_RENDERER | CVAR_INTEGER, "", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> );
	if ( r_showInteractionScissors.GetInteger() > 1 ) {
		session->rw->DebugLine( colorGreen, origin + start * axis, origin + end * axis );
	}
#endif

	leftScale = dLeft * invFar;
	upScale = dUp * invFar;
	dir = end - start;

	if ( clip & (1|2) ) {

		fstart = dFar * start.y;
		fend = dFar * end.y;
		lstart = dLeft * start.x;
		lend = dLeft * end.x;

		if ( clip & 1 ) {
			// test left plane
			d1 = -fstart + lstart;
			d2 = -fend + lend;
			if ( FLOATNOTZERO( d1 ) ) {
				if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) {
					f = d1 / ( d1 - d2 );
					p.x = start.x + f * dir.x;
					if ( p.x > 0.0f ) {
						p.z = start.z + f * dir.z;
						if ( idMath::Fabs( p.z ) <= p.x * upScale ) {
							p.y = 1.0f;
							p.z = p.z * dFar / ( p.x * dUp );
							bounds.AddPoint( p );
						}
					}
				}
			}
		}

		if ( clip & 2 ) {
			// test right plane
			d1 = fstart + lstart;
			d2 = fend + lend;
			if ( FLOATNOTZERO( d1 ) ) {
				if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) {
					f = d1 / ( d1 - d2 );
					p.x = start.x + f * dir.x;
					if ( p.x > 0.0f ) {
						p.z = start.z + f * dir.z;
						if ( idMath::Fabs( p.z  ) <= p.x * upScale ) {
							p.y = -1.0f;
							p.z = p.z * dFar / ( p.x * dUp );
							bounds.AddPoint( p );
						}
					}
				}
			}
		}
	}

	if ( clip & (4|8) ) {

		fstart = dFar * start.z;
		fend = dFar * end.z;
		lstart = dUp * start.x;
		lend = dUp * end.x;

		if ( clip & 4 ) {
			// test up plane
			d1 = -fstart + lstart;
			d2 = -fend + lend;
			if ( FLOATNOTZERO( d1 ) ) {
				if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) {
					f = d1 / ( d1 - d2 );
					p.x = start.x + f * dir.x;
					if ( p.x > 0.0f ) {
						p.y = start.y + f * dir.y;
						if ( idMath::Fabs( p.y ) <= p.x * leftScale ) {
							p.y = p.y * dFar / ( p.x * dLeft );
							p.z = 1.0f;
							bounds.AddPoint( p );
						}
					}
				}
			}
		}

		if ( clip & 8 ) {
			// test down plane
			d1 = fstart + lstart;
			d2 = fend + lend;
			if ( FLOATNOTZERO( d1 ) ) {
				if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) {
					f = d1 / ( d1 - d2 );
					p.x = start.x + f * dir.x;
					if ( p.x > 0.0f ) {
						p.y = start.y + f * dir.y;
						if ( idMath::Fabs( p.y ) <= p.x * leftScale ) {
							p.y = p.y * dFar / ( p.x * dLeft );
							p.z = -1.0f;
							bounds.AddPoint( p );
						}
					}
				}
			}
		}
	}
}

/*
============
idFrustum::BoundsRayIntersection

  Returns true if the ray starts inside the bounds.
  If there was an intersection scale1 <= scale2
============
*/
bool idFrustum::BoundsRayIntersection( const idBounds &bounds, const idVec3 &start, const idVec3 &dir, float &scale1, float &scale2 ) const {
	idVec3 end, p;
	float d1, d2, f;
	int i, startInside = 1;

	scale1 = idMath::INFINITY;
	scale2 = -idMath::INFINITY;

	end = start + dir;

	for ( i = 0; i < 2; i++ ) {
		d1 = start.x - bounds[i].x;
		startInside &= FLOATSIGNBITSET( d1 ) ^ i;
		d2 = end.x - bounds[i].x;
		if ( d1 != d2 ) {
			f = d1 / ( d1 - d2 );
			p.y = start.y + f * dir.y;
			if ( bounds[0].y <= p.y && p.y <= bounds[1].y ) {
				p.z = start.z + f * dir.z;
				if ( bounds[0].z <= p.z && p.z <= bounds[1].z ) {
					if ( f < scale1 ) scale1 = f;
					if ( f > scale2 ) scale2 = f;
				}
			}
		}

		d1 = start.y - bounds[i].y;
		startInside &= FLOATSIGNBITSET( d1 ) ^ i;
		d2 = end.y - bounds[i].y;
		if ( d1 != d2 ) {
			f = d1 / ( d1 - d2 );
			p.x = start.x + f * dir.x;
			if ( bounds[0].x <= p.x && p.x <= bounds[1].x ) {
				p.z = start.z + f * dir.z;
				if ( bounds[0].z <= p.z && p.z <= bounds[1].z ) {
					if ( f < scale1 ) scale1 = f;
					if ( f > scale2 ) scale2 = f;
				}
			}
		}

		d1 = start.z - bounds[i].z;
		startInside &= FLOATSIGNBITSET( d1 ) ^ i;
		d2 = end.z - bounds[i].z;
		if ( d1 != d2 ) {
			f = d1 / ( d1 - d2 );
			p.x = start.x + f * dir.x;
			if ( bounds[0].x <= p.x && p.x <= bounds[1].x ) {
				p.y = start.y + f * dir.y;
				if ( bounds[0].y <= p.y && p.y <= bounds[1].y ) {
					if ( f < scale1 ) scale1 = f;
					if ( f > scale2 ) scale2 = f;
				}
			}
		}
	}

	return ( startInside != 0 );
}

/*
============
idFrustum::ProjectionBounds
============
*/
bool idFrustum::ProjectionBounds( const idBounds &bounds, idBounds &projectionBounds ) const {
	return ProjectionBounds( idBox( bounds, vec3_origin, mat3_identity ), projectionBounds );
}

/*
============
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;
}

/*
============
idFrustum::ProjectionBounds
============
*/
bool idFrustum::ProjectionBounds( const idSphere &sphere, idBounds &projectionBounds ) const {
	float d, r, rs, sFar;
	idVec3 center;

	projectionBounds.Clear();

	center = ( sphere.GetOrigin() - origin ) * axis.Transpose();
	r = sphere.GetRadius();
	rs = r * r;
	sFar = dFar * dFar;

	// test left/right planes
	d = dFar * idMath::Fabs( center.y ) - dLeft * center.x;
	if ( ( d * d ) > rs * ( sFar + dLeft * dLeft ) ) {
		return false;
	}

	// test up/down planes
	d = dFar * idMath::Fabs( center.z ) - dUp * center.x;
	if ( ( d * d ) > rs * ( sFar + dUp * dUp ) ) {
		return false;
	}

	// bounds that cover the whole frustum
	projectionBounds[0].x = 0.0f;
	projectionBounds[1].x = dFar;
	projectionBounds[0].y = projectionBounds[0].z = -1.0f;
	projectionBounds[1].y = projectionBounds[1].z = 1.0f;
	return true;
}

/*
============
idFrustum::ProjectionBounds
============
*/
bool idFrustum::ProjectionBounds( const idFrustum &frustum, idBounds &projectionBounds ) const {
	int i, p1, p2, pointCull[8], culled, outside;
	float scale1, scale2;
	idFrustum localFrustum;
	idVec3 points[8], localOrigin;
	idMat3 localScaled;

	// if the frustum origin is inside the other frustum
	if ( frustum.ContainsPoint( origin ) ) {
		// bounds that cover the whole frustum
		float frustumMin, frustumMax, base;

		base = origin * axis[0];
		frustum.AxisProjection( axis[0], frustumMin, frustumMax );

		projectionBounds[0].x = frustumMin - base;
		projectionBounds[1].x = frustumMax - base;
		projectionBounds[0].y = projectionBounds[0].z = -1.0f;
		projectionBounds[1].y = projectionBounds[1].z = 1.0f;
		return true;
	}

	projectionBounds.Clear();

	// transform the given frustum into the space of this frustum
	localFrustum = frustum;
	localFrustum.origin = ( frustum.origin - origin ) * axis.Transpose();
	localFrustum.axis = frustum.axis * axis.Transpose();
	localFrustum.ToPoints( points );

	// test outer four edges of the other frustum
	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 other frustum is completely outside this frustum
	if ( culled ) {
		return false;
	}

	// if the other frustum is completely inside this frustum
	if ( !outside ) {
		return true;
	}

	// test the remaining edges of the other frustum
	if ( localFrustum.dNear > 0.0f ) {
		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 other frustum extends beyond two or more boundaries of this frustum
	if ( outside != 1 && outside != 2 && outside != 4 && outside != 8 ) {

		localOrigin = ( origin - frustum.origin ) * frustum.axis.Transpose();
		localScaled = axis * frustum.axis.Transpose();
		localScaled[0] *= dFar;
		localScaled[1] *= dLeft;
		localScaled[2] *= dUp;

		// test the outer edges of this frustum for intersection with the other frustum
		if ( (outside & 2) && (outside & 8) ) {
			frustum.LocalRayIntersection( 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) ) {
			frustum.LocalRayIntersection( 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) ) {
			frustum.LocalRayIntersection( 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) ) {
			frustum.LocalRayIntersection( 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;
}

/*
============
idFrustum::ProjectionBounds
============
*/
bool idFrustum::ProjectionBounds( const idWinding &winding, idBounds &projectionBounds ) const {
	int i, p1, p2, *pointCull, culled, outside;
	float scale;
	idVec3 *localPoints;
	idMat3 transpose, scaled;
	idPlane plane;

	projectionBounds.Clear();

	// transform the winding points into the space of this frustum
	localPoints = (idVec3 *) _alloca16( winding.GetNumPoints() * sizeof( idVec3 ) );
	transpose = axis.Transpose();
	for ( i = 0; i < winding.GetNumPoints(); i++ ) {
		localPoints[i] = ( winding[i].ToVec3() - origin ) * transpose;
	}

	// test the winding edges
	culled = -1;
	outside = 0;
	pointCull = (int *) _alloca16( winding.GetNumPoints() * sizeof( int ) );
	for ( i = 0; i < winding.GetNumPoints(); i += 2 ) {
		p1 = i;
		p2 = (i+1)%winding.GetNumPoints();
		AddLocalLineToProjectionBoundsSetCull( localPoints[p1], localPoints[p2], pointCull[p1], pointCull[p2], projectionBounds );
		culled &= pointCull[p1] & pointCull[p2];
		outside |= pointCull[p1] | pointCull[p2];
	}

	// if completely culled
	if ( culled ) {
		return false;
	}

	// if completely inside
	if ( !outside ) {
		return true;
	}

	// test remaining winding edges
	for ( i = 1; i < winding.GetNumPoints(); i += 2 ) {
		p1 = i;
		p2 = (i+1)%winding.GetNumPoints();
		AddLocalLineToProjectionBoundsUseCull( localPoints[p1], localPoints[p2], pointCull[p1], pointCull[p2], projectionBounds );
	}

	// if the winding extends beyond two or more boundaries of this frustum
	if ( outside != 1 && outside != 2 && outside != 4 && outside != 8 ) {

		winding.GetPlane( plane );
		scaled[0] = axis[0] * dFar;
		scaled[1] = axis[1] * dLeft;
		scaled[2] = axis[2] * dUp;

		// test the outer edges of this frustum for intersection with the winding
		if ( (outside & 2) && (outside & 8) ) {
			if ( winding.RayIntersection( plane, origin, scaled[0] - scaled[1] - scaled[2], scale ) ) {
				projectionBounds.AddPoint( idVec3( scale * dFar, -1.0f, -1.0f ) );
			}
		}
		if ( (outside & 2) && (outside & 4) ) {
			if ( winding.RayIntersection( plane, origin, scaled[0] - scaled[1] + scaled[2], scale ) ) {
				projectionBounds.AddPoint( idVec3( scale * dFar, -1.0f, 1.0f ) );
			}
		}
		if ( (outside & 1) && (outside & 8) ) {
			if ( winding.RayIntersection( plane, origin, scaled[0] + scaled[1] - scaled[2], scale ) ) {
				projectionBounds.AddPoint( idVec3( scale * dFar, 1.0f, -1.0f ) );
			}
		}
		if ( (outside & 1) && (outside & 2) ) {
			if ( winding.RayIntersection( plane, origin, scaled[0] + scaled[1] + scaled[2], scale ) ) {
				projectionBounds.AddPoint( idVec3( scale * dFar, 1.0f, 1.0f ) );
			}
		}
	}

	return true;
}

/*
============
idFrustum::ClipFrustumToBox

  Clips the frustum far extents to the box.
============
*/
void idFrustum::ClipFrustumToBox( const idBox &box, float clipFractions[4], int clipPlanes[4] ) const {
	int i, index;
	float f, minf;
	idMat3 scaled, localAxis, transpose;
	idVec3 localOrigin, cornerVecs[4];
	idBounds bounds;

	transpose = box.GetAxis();
	transpose.TransposeSelf();
	localOrigin = ( origin - box.GetCenter() ) * transpose;
	localAxis = axis * transpose;

	scaled[0] = localAxis[0] * dFar;
	scaled[1] = localAxis[1] * dLeft;
	scaled[2] = localAxis[2] * dUp;
	cornerVecs[0] = scaled[0] + scaled[1];
	cornerVecs[1] = scaled[0] - scaled[1];
	cornerVecs[2] = cornerVecs[1] - scaled[2];
	cornerVecs[3] = cornerVecs[0] - scaled[2];
	cornerVecs[0] += scaled[2];
	cornerVecs[1] += scaled[2];

	bounds[0] = -box.GetExtents();
	bounds[1] = box.GetExtents();

	minf = ( dNear + 1.0f ) * invFar;

	for ( i = 0; i < 4; i++ ) {

		index = FLOATSIGNBITNOTSET( cornerVecs[i].x );
		f = ( bounds[index].x - localOrigin.x ) / cornerVecs[i].x;
		clipFractions[i] = f;
		clipPlanes[i] = 1 << index;

		index = FLOATSIGNBITNOTSET( cornerVecs[i].y );
		f = ( bounds[index].y - localOrigin.y ) / cornerVecs[i].y;
		if ( f < clipFractions[i] ) {
			clipFractions[i] = f;
			clipPlanes[i] = 4 << index;
		}

		index = FLOATSIGNBITNOTSET( cornerVecs[i].z );
		f = ( bounds[index].z - localOrigin.z ) / cornerVecs[i].z;
		if ( f < clipFractions[i] ) {
			clipFractions[i] = f;
			clipPlanes[i] = 16 << index;
		}

		// make sure the frustum is not clipped between the frustum origin and the near plane
		if ( clipFractions[i] < minf ) {
			clipFractions[i] = minf;
		}
	}
}

/*
============
idFrustum::ClipLine

  Returns true if part of the line is inside the frustum.
  Does not clip to the near and far plane.
============
*/
bool idFrustum::ClipLine( const idVec3 localPoints[8], const idVec3 points[8], int startIndex, int endIndex, idVec3 &start, idVec3 &end, int &startClip, int &endClip ) const {
	float d1, d2, fstart, fend, lstart, lend, f, x;
	float leftScale, upScale;
	float scale1, scale2;
	int startCull, endCull;
	idVec3 localStart, localEnd, localDir;

	leftScale = dLeft * invFar;
	upScale = dUp * invFar;

	localStart = localPoints[startIndex];
	localEnd = localPoints[endIndex];
	localDir = localEnd - localStart;

	startClip = endClip = -1;
	scale1 = idMath::INFINITY;
	scale2 = -idMath::INFINITY;

	fstart = dFar * localStart.y;
	fend = dFar * localEnd.y;
	lstart = dLeft * localStart.x;
	lend = dLeft * localEnd.x;

	// test left plane
	d1 = -fstart + lstart;
	d2 = -fend + lend;
	startCull = FLOATSIGNBITSET( d1 );
	endCull = FLOATSIGNBITSET( d2 );
	if ( FLOATNOTZERO( d1 ) ) {
		if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) {
			f = d1 / ( d1 - d2 );
			x = localStart.x + f * localDir.x;
			if ( x >= 0.0f ) {
				if ( idMath::Fabs( localStart.z + f * localDir.z ) <= x * upScale ) {
					if ( f < scale1 ) { scale1 = f; startClip = 0; }
					if ( f > scale2 ) { scale2 = f; endClip = 0; }
				}
			}
		}
	}

	// test right plane
	d1 = fstart + lstart;
	d2 = fend + lend;
	startCull |= FLOATSIGNBITSET( d1 ) << 1;
	endCull |= FLOATSIGNBITSET( d2 ) << 1;
	if ( FLOATNOTZERO( d1 ) ) {
		if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) {
			f = d1 / ( d1 - d2 );
			x = localStart.x + f * localDir.x;
			if ( x >= 0.0f ) {
				if ( idMath::Fabs( localStart.z + f * localDir.z ) <= x * upScale ) {
					if ( f < scale1 ) { scale1 = f; startClip = 1; }
					if ( f > scale2 ) { scale2 = f; endClip = 1; }
				}
			}
		}
	}

	fstart = dFar * localStart.z;
	fend = dFar * localEnd.z;
	lstart = dUp * localStart.x;
	lend = dUp * localEnd.x;

	// test up plane
	d1 = -fstart + lstart;
	d2 = -fend + lend;
	startCull |= FLOATSIGNBITSET( d1 ) << 2;
	endCull |= FLOATSIGNBITSET( d2 ) << 2;
	if ( FLOATNOTZERO( d1 ) ) {
		if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) {
			f = d1 / ( d1 - d2 );
			x = localStart.x + f * localDir.x;
			if ( x >= 0.0f ) {
				if ( idMath::Fabs( localStart.y + f * localDir.y ) <= x * leftScale ) {
					if ( f < scale1 ) { scale1 = f; startClip = 2; }
					if ( f > scale2 ) { scale2 = f; endClip = 2; }
				}
			}
		}
	}

	// test down plane
	d1 = fstart + lstart;
	d2 = fend + lend;
	startCull |= FLOATSIGNBITSET( d1 ) << 3;
	endCull |= FLOATSIGNBITSET( d2 ) << 3;
	if ( FLOATNOTZERO( d1 ) ) {
		if ( FLOATSIGNBITSET( d1 ) ^ FLOATSIGNBITSET( d2 ) ) {
			f = d1 / ( d1 - d2 );
			x = localStart.x + f * localDir.x;
			if ( x >= 0.0f ) {
				if ( idMath::Fabs( localStart.y + f * localDir.y ) <= x * leftScale ) {
					if ( f < scale1 ) { scale1 = f; startClip = 3; }
					if ( f > scale2 ) { scale2 = f; endClip = 3; }
				}
			}
		}
	}

	// if completely inside
	if ( !( startCull | endCull ) ) {
		start = points[startIndex];
		end = points[endIndex];
		return true;
	}
	else if ( scale1 <= scale2 ) {
		if ( !startCull ) {
			start = points[startIndex];
			startClip = -1;
		}
		else {
			start = points[startIndex] + scale1 * ( points[endIndex] - points[startIndex] );
		}
		if ( !endCull ) {
			end = points[endIndex];
			endClip = -1;
		}
		else {
			end = points[startIndex] + scale2 * ( points[endIndex] - points[startIndex] );
		}
		return true;
	}
	return false;
}

/*
============
idFrustum::AddLocalCapsToProjectionBounds
============
*/
static int capPointIndex[4][2] = {
	{ 0, 3 },
	{ 1, 2 },
	{ 0, 1 },
	{ 2, 3 }
};

ID_INLINE bool idFrustum::AddLocalCapsToProjectionBounds( const idVec3 endPoints[4], const int endPointCull[4], const idVec3 &point, int pointCull, int pointClip, idBounds &projectionBounds ) const {
	int *p;

	if ( pointClip < 0 ) {
		return false;
	}
	p = capPointIndex[pointClip];
	AddLocalLineToProjectionBoundsUseCull( endPoints[p[0]], point, endPointCull[p[0]], pointCull, projectionBounds );
	AddLocalLineToProjectionBoundsUseCull( endPoints[p[1]], point, endPointCull[p[1]], pointCull, projectionBounds );
	return true;
}

/*
============
idFrustum::ClippedProjectionBounds
============
*/
bool idFrustum::ClippedProjectionBounds( const idFrustum &frustum, const idBox &clipBox, idBounds &projectionBounds ) const {
	int i, p1, p2, clipPointCull[8], clipPlanes[4], usedClipPlanes, nearCull, farCull, outside;
	int pointCull[2], startClip, endClip, boxPointCull[8];
	float clipFractions[4], s1, s2, t1, t2, leftScale, upScale;
	idFrustum localFrustum;
	idVec3 clipPoints[8], localPoints1[8], localPoints2[8], localOrigin1, localOrigin2, start, end;
	idMat3 localAxis1, localAxis2, transpose;
	idBounds clipBounds;

	// if the frustum origin is inside the other frustum
	if ( frustum.ContainsPoint( origin ) ) {
		// bounds that cover the whole frustum
		float clipBoxMin, clipBoxMax, frustumMin, frustumMax, base;

		base = origin * axis[0];
		clipBox.AxisProjection( axis[0], clipBoxMin, clipBoxMax );
		frustum.AxisProjection( axis[0], frustumMin, frustumMax );

		projectionBounds[0].x = Max( clipBoxMin, frustumMin ) - base;
		projectionBounds[1].x = Min( clipBoxMax, frustumMax ) - base;
		projectionBounds[0].y = projectionBounds[0].z = -1.0f;
		projectionBounds[1].y = projectionBounds[1].z = 1.0f;
		return true;
	}

	projectionBounds.Clear();

	// clip the outer edges of the given frustum to the clip bounds
	frustum.ClipFrustumToBox( clipBox, clipFractions, clipPlanes );
	usedClipPlanes = clipPlanes[0] | clipPlanes[1] | clipPlanes[2] | clipPlanes[3];

	// transform the clipped frustum to the space of this frustum
	transpose = axis;
	transpose.TransposeSelf();
	localFrustum = frustum;
	localFrustum.origin = ( frustum.origin - origin ) * transpose;
	localFrustum.axis = frustum.axis * transpose;
	localFrustum.ToClippedPoints( clipFractions, clipPoints );

	// test outer four edges of the clipped frustum
	for ( i = 0; i < 4; i++ ) {
		p1 = i;
		p2 = 4 + i;
		AddLocalLineToProjectionBoundsSetCull( clipPoints[p1], clipPoints[p2], clipPointCull[p1], clipPointCull[p2], projectionBounds );
	}

	// get cull bits for the clipped frustum
	outside = clipPointCull[0] | clipPointCull[1] | clipPointCull[2] | clipPointCull[3] |
					clipPointCull[4] | clipPointCull[5] | clipPointCull[6] | clipPointCull[7];
	nearCull = clipPointCull[0] & clipPointCull[1] & clipPointCull[2] & clipPointCull[3];
	farCull = clipPointCull[4] & clipPointCull[5] & clipPointCull[6] & clipPointCull[7];

	// if the clipped frustum is not completely inside this frustum
	if ( outside ) {

		// test the remaining edges of the clipped frustum
		if ( !nearCull && localFrustum.dNear > 0.0f ) {
			for ( i = 0; i < 4; i++ ) {
				p1 = i;
				p2 = (i+1)&3;
				AddLocalLineToProjectionBoundsUseCull( clipPoints[p1], clipPoints[p2], clipPointCull[p1], clipPointCull[p2], projectionBounds );
			}
		}

		if ( !farCull ) {
			for ( i = 0; i < 4; i++ ) {
				p1 = 4 + i;
				p2 = 4 + ((i+1)&3);
				AddLocalLineToProjectionBoundsUseCull( clipPoints[p1], clipPoints[p2], clipPointCull[p1], clipPointCull[p2], projectionBounds );
			}
		}
	}

	// if the clipped frustum far end points are inside this frustum
	if ( !( farCull && !( nearCull & farCull ) ) &&
			// if the clipped frustum is not clipped to a single plane of the clip bounds
			( clipPlanes[0] != clipPlanes[1] || clipPlanes[1] != clipPlanes[2] || clipPlanes[2] != clipPlanes[3] ) ) {

		// transform the clip box into the space of the other frustum
		transpose = frustum.axis;
		transpose.TransposeSelf();
		localOrigin1 = ( clipBox.GetCenter() - frustum.origin ) * transpose;
		localAxis1 = clipBox.GetAxis() * transpose;
		BoxToPoints( localOrigin1, clipBox.GetExtents(), localAxis1, localPoints1 );

		// cull the box corners with the other frustum
		leftScale = frustum.dLeft * frustum.invFar;
		upScale = frustum.dUp * frustum.invFar;
		for ( i = 0; i < 8; i++ ) {
			idVec3 &p = localPoints1[i];
			if ( !( boxVertPlanes[i] & usedClipPlanes ) || p.x <= 0.0f ) {
				boxPointCull[i] = 1|2|4|8;
			}
			else {
				boxPointCull[i] = 0;
				if ( idMath::Fabs( p.y ) > p.x * leftScale ) {
					boxPointCull[i] |= 1 << FLOATSIGNBITSET( p.y );
				}
				if ( idMath::Fabs( p.z ) > p.x * upScale ) {
					boxPointCull[i] |= 4 << FLOATSIGNBITSET( p.z );
				}
			}
		}

		// transform the clip box into the space of this frustum
		transpose = axis;
		transpose.TransposeSelf();
		localOrigin2 = ( clipBox.GetCenter() - origin ) * transpose;
		localAxis2 = clipBox.GetAxis() * transpose;
		BoxToPoints( localOrigin2, clipBox.GetExtents(), localAxis2, localPoints2 );

		// clip the edges of the clip bounds to the other frustum and add the clipped edges to the projection bounds
		for ( i = 0; i < 4; i++ ) {
			p1 = i;
			p2 = 4 + i;
			if ( !( boxPointCull[p1] & boxPointCull[p2] ) ) {
				if ( frustum.ClipLine( localPoints1, localPoints2, p1, p2, start, end, startClip, endClip ) ) {
					AddLocalLineToProjectionBoundsSetCull( start, end, pointCull[0], pointCull[1], projectionBounds );
					AddLocalCapsToProjectionBounds( clipPoints+4, clipPointCull+4, start, pointCull[0], startClip, projectionBounds );
					AddLocalCapsToProjectionBounds( clipPoints+4, clipPointCull+4, end, pointCull[1], endClip, projectionBounds );
					outside |= pointCull[0] | pointCull[1];
				}
			}
		}

		for ( i = 0; i < 4; i++ ) {
			p1 = i;
			p2 = (i+1)&3;
			if ( !( boxPointCull[p1] & boxPointCull[p2] ) ) {
				if ( frustum.ClipLine( localPoints1, localPoints2, p1, p2, start, end, startClip, endClip ) ) {
					AddLocalLineToProjectionBoundsSetCull( start, end, pointCull[0], pointCull[1], projectionBounds );
					AddLocalCapsToProjectionBounds( clipPoints+4, clipPointCull+4, start, pointCull[0], startClip, projectionBounds );
					AddLocalCapsToProjectionBounds( clipPoints+4, clipPointCull+4, end, pointCull[1], endClip, projectionBounds );
					outside |= pointCull[0] | pointCull[1];
				}
			}
		}

		for ( i = 0; i < 4; i++ ) {
			p1 = 4 + i;
			p2 = 4 + ((i+1)&3);
			if ( !( boxPointCull[p1] & boxPointCull[p2] ) ) {
				if ( frustum.ClipLine( localPoints1, localPoints2, p1, p2, start, end, startClip, endClip ) ) {
					AddLocalLineToProjectionBoundsSetCull( start, end, pointCull[0], pointCull[1], projectionBounds );
					AddLocalCapsToProjectionBounds( clipPoints+4, clipPointCull+4, start, pointCull[0], startClip, projectionBounds );
					AddLocalCapsToProjectionBounds( clipPoints+4, clipPointCull+4, end, pointCull[1], endClip, projectionBounds );
					outside |= pointCull[0] | pointCull[1];
				}
			}
		}
	}

	// if the clipped frustum extends beyond two or more boundaries of this frustum
	if ( outside != 1 && outside != 2 && outside != 4 && outside != 8 ) {

		// transform this frustum into the space of the other frustum
		transpose = frustum.axis;
		transpose.TransposeSelf();
		localOrigin1 = ( origin - frustum.origin ) * transpose;
		localAxis1 = axis * transpose;
		localAxis1[0] *= dFar;
		localAxis1[1] *= dLeft;
		localAxis1[2] *= dUp;

		// transform this frustum into the space of the clip bounds
		transpose = clipBox.GetAxis();
		transpose.TransposeSelf();
		localOrigin2 = ( origin - clipBox.GetCenter() ) * transpose;
		localAxis2 = axis * transpose;
		localAxis2[0] *= dFar;
		localAxis2[1] *= dLeft;
		localAxis2[2] *= dUp;

		clipBounds[0] = -clipBox.GetExtents();
		clipBounds[1] = clipBox.GetExtents();

		// test the outer edges of this frustum for intersection with both the other frustum and the clip bounds
		if ( (outside & 2) && (outside & 8) ) {
			frustum.LocalRayIntersection( localOrigin1, localAxis1[0] - localAxis1[1] - localAxis1[2], s1, s2 );
			if ( s1 <= s2 && s1 >= 0.0f ) {
				BoundsRayIntersection( clipBounds, localOrigin2, localAxis2[0] - localAxis2[1] - localAxis2[2], t1, t2 );
				if ( t1 <= t2 && t2 > s1 && t1 < s2 ) {
					projectionBounds.AddPoint( idVec3( s1 * dFar, -1.0f, -1.0f ) );
					projectionBounds.AddPoint( idVec3( s2 * dFar, -1.0f, -1.0f ) );
				}
			}
		}
		if ( (outside & 2) && (outside & 4) ) {
			frustum.LocalRayIntersection( localOrigin1, localAxis1[0] - localAxis1[1] + localAxis1[2], s1, s2 );
			if ( s1 <= s2 && s1 >= 0.0f ) {
				BoundsRayIntersection( clipBounds, localOrigin2, localAxis2[0] - localAxis2[1] + localAxis2[2], t1, t2 );
				if ( t1 <= t2 && t2 > s1 && t1 < s2 ) {
					projectionBounds.AddPoint( idVec3( s1 * dFar, -1.0f, 1.0f ) );
					projectionBounds.AddPoint( idVec3( s2 * dFar, -1.0f, 1.0f ) );
				}
			}
		}
		if ( (outside & 1) && (outside & 8) ) {
			frustum.LocalRayIntersection( localOrigin1, localAxis1[0] + localAxis1[1] - localAxis1[2], s1, s2 );
			if ( s1 <= s2 && s1 >= 0.0f ) {
				BoundsRayIntersection( clipBounds, localOrigin2, localAxis2[0] + localAxis2[1] - localAxis2[2], t1, t2 );
				if ( t1 <= t2 && t2 > s1 && t1 < s2 ) {
					projectionBounds.AddPoint( idVec3( s1 * dFar, 1.0f, -1.0f ) );
					projectionBounds.AddPoint( idVec3( s2 * dFar, 1.0f, -1.0f ) );
				}
			}
		}
		if ( (outside & 1) && (outside & 2) ) {
			frustum.LocalRayIntersection( localOrigin1, localAxis1[0] + localAxis1[1] + localAxis1[2], s1, s2 );
			if ( s1 <= s2 && s1 >= 0.0f ) {
				BoundsRayIntersection( clipBounds, localOrigin2, localAxis2[0] + localAxis2[1] + localAxis2[2], t1, t2 );
				if ( t1 <= t2 && t2 > s1 && t1 < s2 ) {
					projectionBounds.AddPoint( idVec3( s1 * dFar, 1.0f, 1.0f ) );
					projectionBounds.AddPoint( idVec3( s2 * dFar, 1.0f, 1.0f ) );
				}
			}
		}
	}

	return true;
}
