Click here to Skip to main content
15,896,269 members
Articles / Desktop Programming / Win32

Half Life Game Level Viewer

Rate me:
Please Sign up or sign in to vote.
4.61/5 (23 votes)
7 Feb 2009CPOL26 min read 80K   2.3K   60  
DirectX based application to open and view Half Life 1 game files
//
//	HFBSPGraph.cpp
//
//	Class to implement the ISceneGraph interface based on Half-Life BSP scene file.
//	This class uses the ICamera object to track player movement and look orientation in the 
//	BSP level and to render the scene from that perspective.  The BSPData class is used to
//	load a BSP level file and then to determine player collision with level geometry and 
//	entity models.  The BSPData class is also used to efficiently find all visible faces
//	to be rendered.  The IRenderer (via DX) object is used for face and texture rendering, 
//	including light map and optional flash light textures.  
//
//	Copyright 2005 Paul Higinbotham
//

#include "stdafx.h"
#include "..\..\memorymgr\PoolAllocator.h"			// Includes std C++ <new> and so uses throwing new operator
#include "HFBSPGraph.h"
#include "..\..\Renderer\DirectXRenderer\DXDefines.h"
#include "..\..\math\Plane.h"


using namespace ZGraphics;


// Constructor
HFBSPGraph::HFBSPGraph()
{
	m_pSGDX9Vertices = 0;
	//
	m_pCamera = 0;
	m_pRenderer = 0;
	//
	m_pCamLeaf = 0;
	//
	m_pD3DRenderCaps = 0;
	m_bSceneLoaded = false;
	m_bMultiTexCapable = false;
	m_bShaderCapable = false;
	m_bHaveVertexShader = false;
	//
	m_VisLeafs.SetSize(100);
	m_VisLeafs.SetGrowSize(50);
	m_VisFaces.SetSize(500);
	m_VisFaces.SetGrowSize(300);
	m_aIntersections.SetSize(20);
	m_aIntersections.SetGrowSize(20);
	//
	// Flash-light texture, transform info
	//
	m_bFlashlight = false;
	m_pDXFLTexture = 0;
	m_pDXNFLTexture = 0;
	m_nFLTextureSize = 128;
	m_mtxFLProjection.MakeZero();
	m_mtxFLProjection(0, 0) = 1.5f;		// f1, 1.7
	m_mtxFLProjection(1, 1) = 1.5f;		// f1, 1.7
	m_mtxFLProjection(2, 0) = 0.50f;
	m_mtxFLProjection(2, 1) = 0.50f;
	m_mtxFLProjection(2, 2) = 1.0f;
	m_mtxFLProjection(2, 3) = 1.0f;
	//
	m_pVertexShader1 = 0;
}

HFBSPGraph::~HFBSPGraph()
{
	_cleanup();
}


//
//	ISceneGraph public methods
//

// Loads scene data from provided bsp file name, creates global geometry data, and places
// camera at initial position.
bool HFBSPGraph::LoadScene(const char * pszFilename)
{
	// Clean up any earlier scene data
	_cleanUpScene();

	// Load scene data lumps from BSP file, create BSP tree data structure
	m_BSPData.LoadData(pszFilename);

	// Load textures from WAD file
	m_Textures.LoadWADTextures(m_BSPData);

	// Create single DX vertex list for all static data, and a map of faces to the list.
	// Also create all DX face textures, light-map and flash-light textures.
	_createStaticGeomData();

	// Find player start entity and place camera accordingly
	_placeCamera();

	m_bSceneLoaded = true;

	return m_bSceneLoaded;
}

void HFBSPGraph::SetCamera(ICamera * pCamera)
{
	m_pCamera = pCamera;
}

// Updates the camera position and look orientation based on the given
// time change (time delta).  Checks for camera (player) collision with level
// geometry and objects, and adjusts its position accordingly.
void HFBSPGraph::UpdateCamera(unsigned long ulTimeDeluSec)
{
	assert(m_pCamera);
	if (!m_pCamera)
		return;

	// Update camera orientation and position
	// ulTimeDel is in uSecs
	Vector3f vOldCamPos = m_pCamera->Position();
	const BSPNode * pOldCamLeaf = m_pCamLeaf;
	m_pCamera->Update(ulTimeDeluSec);

	// Adjust camera position to keep its bounding box inside world boundaries
	_adjustCamPosition(vOldCamPos);

	// Get the BSP level leaf node where the camera is.
	m_pCamLeaf = m_BSPData.FindLeaf(m_pCamera->Position());

	// The camera must stay within a valid empty leaf node.  If for some reason the camera
	// is in an invalid leaf, move it back to its previous position.
	if (m_pCamLeaf->nLeaf == 0)
	{
		m_pCamera->Position() = vOldCamPos;
		m_pCamLeaf = pOldCamLeaf;
	}

	// Make sure the camera frustrum is correctly computed for the final camera position
	m_pCamera->Update(0);
}

// Releases all IRenderer (D3D) objects.  Used for windowed/full-screen mode transitions.
void HFBSPGraph::ReleaseRenderObjects()
{
	m_pD3DRenderCaps = 0;
	m_pRenderer = 0;
	_releaseDXVertices();
	_releaseDXTextures();
	SAFE_RELEASE(m_pDXFLTexture);
	SAFE_RELEASE(m_pDXNFLTexture);

	if (m_pVertexShader1)
		m_pVertexShader1->ReleaseShader();
}

// Recreates all IRenderer (D3D) objects.  Used for windowed/full-screen mode transitions.
void HFBSPGraph::RestoreRenderObjects(IRenderer * pRenderer)
{
	SetRenderer(pRenderer);
	if (m_bSceneLoaded)
	{
		// Creates DX objects for scene vertex buffer and face/light-map, and flashlight textures
		_createStaticGeomData();
	}
}

void HFBSPGraph::ToggleFlashlight()
{
	m_bFlashlight = !m_bFlashlight;
}

// Receives an IRenderer object to be used for scene rendering.  Also enumerates renderer
// capabilities, compiles the vertex shader (if supported), and sets global render states.
void HFBSPGraph::SetRenderer(IRenderer * pRenderer)
{
	m_pRenderer = pRenderer;
	assert(m_pRenderer && m_pCamera);

	// Device capabilities
	m_pD3DRenderCaps = m_pRenderer->GetD3DCaps();
	m_bMultiTexCapable = (m_pD3DRenderCaps->MaxSimultaneousTextures > 2);
	m_bShaderCapable = m_pD3DRenderCaps->VertexShaderVersion >= D3DVS_VERSION(1,1);

	// Get and compile vertex shader if vs_11 is supported
	m_bHaveVertexShader = _setVertexShader();

	// Set world matrix, this only converts HL world coordinates to DX9 world coordinate space
	// For HL world, swap z = x, y = z, x = -y
	m_mtxWorld.MakeZero();
	m_mtxWorld(0, 2) = 1.0f;
	m_mtxWorld(1, 0) = -1.0f;
	m_mtxWorld(2, 1) = 1.0f;
	m_mtxWorld(3, 3) = 1.0f;
	m_pRenderer->SetTransform(D3DTS_WORLD, m_mtxWorld);

	// Set the projection matrix
	const ZView & cView = m_pCamera->GetView();
	m_pRenderer->CreateProjectionMatrixLH(m_mtxProjection, cView.AngleBeta(), cView.AspectRatio(), cView.NearPlaneDistance(), cView.FarPlaneDistance());
	m_pRenderer->SetTransform(D3DTS_PROJECTION, m_mtxProjection);

	// Set static renderer states
	m_pRenderer->SetRenderState(D3DRS_SPECULARENABLE,	FALSE);
	m_pRenderer->SetRenderState(D3DRS_LIGHTING,			FALSE);
	m_pRenderer->SetRenderState(D3DRS_ZENABLE,			TRUE);
	m_pRenderer->SetRenderState(D3DRS_FILLMODE,			D3DFILL_SOLID);
	m_pRenderer->SetRenderState(D3DRS_CULLMODE,			D3DCULL_CCW);
	m_pRenderer->SetRenderState(D3DRS_STENCILENABLE,	FALSE);
	m_pRenderer->SetRenderState(D3DRS_CLIPPING,			TRUE);
}

// Main rendering method.  Computes the new view matrix based on current camera orientation
// and renders scene.
void HFBSPGraph::Render()
{
	assert(m_pRenderer);
	assert(m_pCamLeaf);

	// Set the view (camera) matrix based on current camera world position
	Matrix4f mtxView;
	_createViewMatrix(mtxView, m_mtxWorld);
	m_pRenderer->SetTransform(D3DTS_VIEW, mtxView);

	HRESULT hr = m_pRenderer->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xff303030, 1.0f, 0);
	hr = m_pRenderer->BeginScene();
	if (SUCCEEDED(hr))
	{
		// Render map and entity brush geometry.
		_setSGRenderStates();
		_renderStaticGeometry(mtxView);
		_renderEntities(mtxView);

		// (TODO) Render studio models

		m_pRenderer->EndScene();
	}

	m_pRenderer->Present(0, 0, 0, 0);
}


//
//	Internal methods
//

void HFBSPGraph::_cleanup()
{
	_cleanUpScene();
	DELETE_PTR(m_pVertexShader1);
}

void HFBSPGraph::_cleanUpScene()
{
	m_bSceneLoaded = false;
	_releaseDXTextures();
	_releaseDXVertices();
	SAFE_RELEASE(m_pDXFLTexture);
	SAFE_RELEASE(m_pDXNFLTexture);

	m_pCamLeaf = 0;
}

// Helper method to set renderer texture and sampler states for scene rendering.  Supports hardware
// with and without multi-texture capablitiy, however no multi-texture cap provides poorer experience.
// If multi-texture cap is present then three texture stages are used.  The first is to apply the 
// geometry texture, the second is to apply the lightmap texture, and the third applies the optional
// flashlight texture.
// If there is no multi-texture cap then the textures are applied in three rendering passes.
// Flashlight lightmap texture coordinates are computed in the vertex shader if supported, otherwise
// the flashlight projection matrix is used (but flashlight effect is not very realistic).
void HFBSPGraph::_setSGRenderStates()
{
	// @todo  Create state block for these settings

	// Multi-texturing light-map texture set up (if supported).
	if (m_bMultiTexCapable)
	{
		// Light-map texture
		m_pRenderer->SetTextureState(0, D3DTSS_TEXCOORDINDEX, 0);
		m_pRenderer->SetTextureState(0, D3DTSS_COLOROP,		D3DTOP_SELECTARG1);
		m_pRenderer->SetTextureState(0, D3DTSS_COLORARG1,	D3DTA_TEXTURE);
		m_pRenderer->SetSamplerState(0, D3DSAMP_MINFILTER,	D3DTEXF_LINEAR);
		m_pRenderer->SetSamplerState(0, D3DSAMP_MAGFILTER,	D3DTEXF_LINEAR);
		m_pRenderer->SetSamplerState(0, D3DSAMP_ADDRESSU,	D3DTADDRESS_CLAMP);
		m_pRenderer->SetSamplerState(0, D3DSAMP_ADDRESSV,	D3DTADDRESS_CLAMP);

		// Flashlight texture
		if (m_bHaveVertexShader)
		{
			// Let shader create the texture coordinates for flashlight (tex coord 1)
			m_pRenderer->SetTextureState(1, D3DTSS_TEXCOORDINDEX, 1);
			m_pRenderer->SetTextureState(1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE);
		}
		else
		{
			// Auto-generate texture coordinates for tex stage 1
			m_pRenderer->SetTextureState(1, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT3 | D3DTTFF_PROJECTED);
			m_pRenderer->SetTextureState(1, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION);
			m_pRenderer->SetTransform(D3DTS_TEXTURE1, m_mtxFLProjection);
		}

		m_pRenderer->SetTextureState(1, D3DTSS_COLOROP,		D3DTOP_ADD);
		m_pRenderer->SetTextureState(1, D3DTSS_COLORARG1,	D3DTA_TEXTURE);
		m_pRenderer->SetTextureState(1, D3DTSS_COLORARG2,	D3DTA_CURRENT);
		m_pRenderer->SetSamplerState(1, D3DSAMP_MINFILTER,	D3DTEXF_LINEAR);
		m_pRenderer->SetSamplerState(1, D3DSAMP_MAGFILTER,	D3DTEXF_LINEAR);
		m_pRenderer->SetSamplerState(1, D3DSAMP_ADDRESSU,	D3DTADDRESS_CLAMP);
		m_pRenderer->SetSamplerState(1, D3DSAMP_ADDRESSV,	D3DTADDRESS_CLAMP);

		// Basic texture face texturing set up
		if (m_bHaveVertexShader)
		{
			m_pRenderer->SetTextureState(2, D3DTSS_TEXCOORDINDEX, 2);
		}
		else
		{
			m_pRenderer->SetTextureState(2, D3DTSS_TEXCOORDINDEX, 1);
		}
		m_pRenderer->SetTextureState(2, D3DTSS_COLOROP,		D3DTOP_MODULATE2X);
		m_pRenderer->SetTextureState(2, D3DTSS_COLORARG1,	D3DTA_TEXTURE);
		m_pRenderer->SetTextureState(2, D3DTSS_COLORARG2,	D3DTA_CURRENT);
		m_pRenderer->SetSamplerState(2, D3DSAMP_MINFILTER,	D3DTEXF_LINEAR);
		m_pRenderer->SetSamplerState(2, D3DSAMP_MAGFILTER,	D3DTEXF_LINEAR);
		m_pRenderer->SetSamplerState(2, D3DSAMP_MIPFILTER,	D3DTEXF_LINEAR);
		m_pRenderer->SetSamplerState(2, D3DSAMP_ADDRESSU,	D3DTADDRESS_WRAP);
		m_pRenderer->SetSamplerState(2, D3DSAMP_ADDRESSV,	D3DTADDRESS_WRAP);
	}
	else
	{
		// Turn on texture blending for multi-pass texturing
		m_pRenderer->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
		m_pRenderer->SetRenderState(D3DRS_SRCBLENDALPHA,	D3DBLEND_ONE);
		m_pRenderer->SetRenderState(D3DRS_DESTBLENDALPHA,	D3DBLEND_ZERO);

		// Basic texture face texturing set up
		m_pRenderer->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
		m_pRenderer->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
		m_pRenderer->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
		m_pRenderer->SetSamplerState(0, D3DSAMP_ADDRESSU,	D3DTADDRESS_WRAP);
		m_pRenderer->SetSamplerState(0, D3DSAMP_ADDRESSV,	D3DTADDRESS_WRAP);

		//m_pRenderer->SetTransform(D3DTS_TEXTURE0, m_mtxFLProjection);
	}
}

// Main method for rendering the static level geometry (everything but entity and character models).
// First potentially visible leafs are gathered from the BSP tree in an array in front to back order.  
// Next all potentially visible polygon faces are collected in an array object and this and used
// to do the actual rendering.
void HFBSPGraph::_renderStaticGeometry(const Matrix4f & mtxView)
{
	const Array<int> * pVisLeafs = _visibleLeafsFtoB();
	const Array<lface> * pVisFaces = _getFaces(pVisLeafs);

	_renderFaces(pVisFaces, mtxView, m_mtxWorld);
}

// Main method for rendering entity models.  Entity model objects are divided into to groups. 
// The first group are "solid" objects that player cannot pass through (collision detection is
// peformed with these objects), and are rendered without any transparency.
// The second group are "transparent" objects that the player can pass through (collision 
// detection is turned off for these objects), and are rendered with a small amount of transparency
// to indicate to the player that they can be passed through.
// Transparent objects can also be breakable windows, and in order to make transparency work correctly
// the non-transparent faces are rendered first and the transparent faces second, so that geometry
// and entities behind the transparent entity objects can be seen.
// Transparency is achieved by using the IRenderer (DX) alpha blending capabilities.
void HFBSPGraph::_renderEntities(const Matrix4f & mtxView)
{
	// Turn alpha blending state on
	m_pRenderer->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
	m_pRenderer->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
	m_pRenderer->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
	m_pRenderer->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);

	// Set alpha channel
	m_pRenderer->SetTextureState(2, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
	m_pRenderer->SetTextureState(2, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);

	// First all opaque faces
	_renderEntFaces(false, mtxView);

	// Then all transparent faces
	_renderEntFaces(true, mtxView);

	// Turn off alpha blending state
	m_pRenderer->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
}

// Main method for rendering either static geometry or entity model faces.  
// If multi-texturing is supported then each face is rendered in "one-pass", i.e., one call
// to the IRenderer DrawPrimitive method.  Otherwise three separate DrawPrimitive calls are
// made to apply each of the three texture maps (face texture, lightmap texture, optional
// flashlight lightmap texture).
void HFBSPGraph::_renderFaces(const Array<lface> * pFaces, const Matrix4f & mtxView, const Matrix4f & mtxWorld)
{
	// Set the vertex shader matrix constants
	if (m_pVertexShader1)
	{
		Matrix4f mtxWorldView = mtxWorld * mtxView;
		m_pVertexShader1->SetCTMatrix("WorldView", mtxWorldView);

		Matrix4f mtxWorldViewProj = mtxWorldView * m_mtxProjection;
		m_pVertexShader1->SetCTMatrix("WorldViewProj", mtxWorldViewProj);
	}

	if (m_bMultiTexCapable)
	{
		// Set up the flashlight texture
		if (m_bFlashlight)
			m_pRenderer->SetTexture(1, m_pDXFLTexture);		// Light map texture that illuminates scene.
		else
			m_pRenderer->SetTexture(1, m_pDXNFLTexture);	// Null light map texture that has no affect on scene.
	}

	// Render loop for static world models.
	IDirect3DTexture9 * pDXTexture = 0;
	int nMipTexLast = -1;
	for (int n=0; n<(int)pFaces->GetArrayCount(); n++)
	{
		int nMipTex = (*pFaces)[n].nMipTex;

		const bspf_face * pFace = &(m_BSPData.Faces()->m_pArray[pFaces->Get(n).nFaceIndex]);
		const int * pnVertIndex = m_SGFaceToVertex.IsInMap(pFaces->Get(n).nFaceIndex);
		assert(*pnVertIndex > -1);

		int nNumPrimitives = pFace->cEdges - 2;
		IDirect3DTexture9 * pDXLMTexture = _getLMTexture(pFace);

		// If multi-texturing in one pass is supported then include light-map texture blending.
		if (m_bMultiTexCapable)
		{
			// Light-map texture
			m_pRenderer->SetTexture(0, pDXLMTexture);

			// Surface texture.
			// Only get and set texture if it has changed.
			if (nMipTex != nMipTexLast)
			{
				pDXTexture = _getDXTexture(nMipTex);
				m_pRenderer->SetTexture(2, pDXTexture);
				nMipTexLast = nMipTex;
			}

			m_pRenderer->DrawPrimitive(D3DPT_TRIANGLEFAN, *pnVertIndex, nNumPrimitives);
		}
		else
		{
			// If we are not multi-texture capable.  Render in three passes, first for light-map,
			// next for flashlight, next for texture.

			// Light-map
			m_pRenderer->SetTexture(0, pDXLMTexture);
			m_pRenderer->SetTextureState(0, D3DTSS_TEXCOORDINDEX, 0);
			m_pRenderer->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
			m_pRenderer->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO);
			m_pRenderer->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP);
			m_pRenderer->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
			m_pRenderer->DrawPrimitive(D3DPT_TRIANGLEFAN, *pnVertIndex, nNumPrimitives);

			/*
			// Flashlight
			if (m_bFlashlight)
				m_pRenderer->SetTexture(0, m_pDXFLTexture);
			else
				m_pRenderer->SetTexture(0, m_pDXNFLTexture);
			m_pRenderer->SetTextureState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT3 | D3DTTFF_PROJECTED);
			m_pRenderer->SetTextureState(0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION);
			m_pRenderer->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_DESTCOLOR);
			m_pRenderer->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_SRCCOLOR);
			m_pRenderer->DrawPrimitive(D3DPT_TRIANGLEFAN, *pnVertIndex, nNumPrimitives);
			*/

			// Texture
			pDXTexture = _getDXTexture(nMipTex);
			m_pRenderer->SetTexture(0, pDXTexture);
			m_pRenderer->SetTextureState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_DISABLE);
			m_pRenderer->SetTextureState(0, D3DTSS_TEXCOORDINDEX, 1);
			m_pRenderer->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_DESTCOLOR);
			m_pRenderer->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_SRCCOLOR);
			m_pRenderer->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
			m_pRenderer->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
			m_pRenderer->DrawPrimitive(D3DPT_TRIANGLEFAN, *pnVertIndex, nNumPrimitives);
		}
	}
}

// Creates the current camera view matrix and converts to DX9 coordinate space.
__forceinline void HFBSPGraph::_createViewMatrix(Matrix4f & mtxView, const Matrix4f & mtxWorld)
{
	// Create the view transformation matrix for DX9 coord space by first creating the view
	// transformation matrix in HL coord space and then transforming:
	// ViewMtx(DX9) = (Inv_WorldMatrix) * ViewMtx(HF) * (WorldMatrix)
	// where WorldMatrix is the matrix that transforms HL coordinates to DX9 coordinates
	m_pCamera->GetViewMatrix(mtxView);

	// Transform view matrix from HL coord space to DX coord space
	Matrix4f mtxWorld_Inv = mtxWorld.Transpose();	// World matrix is only rotations so inverse == transpose
	mtxView = mtxWorld_Inv * mtxView * mtxWorld;
}

// Method to collect all vertices from a given HL face structure, create a DX9 vertex structure,
// and then add these DX9 vertices to the provided vertex list.  
// The DX9 HFVERTEX structure, defined in IRenderer.h, specifies vertex coordinates, and two sets of 
// texture (u,v) coordinates.  The two texture coordinates are for the face texture and the lightmap 
// texture.  This method provides all of these coordinates for each face polygon vertex.  
// !!NOTE!! that an HFVERTEX structure passed to IRenderer is actually specified to have THREE texture
// coordinates, but only TWO are supplied in this method.  The third set of texture coordinates are
// for the flashlight lightmap texture and are provided by the vertex shader OR the m_mtxFLProjection
// projection matrix if a vertex shader is not supported.
// This method also creates a lightmap texture for the face and adds it to a map object to be
// used later during acutal rendering.
// Keep in mind that this method is not used during rendering of a scene but is called ONLY ONCE when 
// the bsp level is loaded and then this data is cached in the vertex list object.
void HFBSPGraph::_collectFaceVertices(const bspf_face * pFace, Array<HFVERTEX> * pVertexList)
{
	assert (pFace->cEdges > 1);

	// Get texutre information
	const bspf_textinfo * pTextInfo = &(m_BSPData.TextInfo()->m_pArray[pFace->nTextureInfo]);
	int nMipTex = pTextInfo->nMipTex;
	const bspf_miptex * pMipTex = m_BSPData.MipInfoPtr(nMipTex);

	// Walk through each face edge and triangularize
	// Assume a convex face and create a triangle fan
	// @todo  Look at making a triangle strip ... more efficient than a triangle fan.
	Vector3f v0, v1;
	float fUMin = 100000.0f;
	float fUMax = -10000.0f;
	float fVMin = 100000.0f;
	float fVMax = -10000.0f;
	int nStartIndex = (int)pVertexList->GetArrayCount();
	for (int nEdge=0; nEdge<pFace->cEdges; nEdge++)
	{
		_getEdgeVertices(v0, v1, pFace->nE1 + nEdge);

		// Add vertex information
		HFVERTEX vertex(v0);

		// Generate texture coordinates for face
		vertex.u =  v0[0] * pTextInfo->uAxis[0] + v0[1] * pTextInfo->uAxis[1] + v0[2] * pTextInfo->uAxis[2] + pTextInfo->uOffset;
		vertex.v =  v0[0] * pTextInfo->vAxis[0] + v0[1] * pTextInfo->vAxis[1] + v0[2] * pTextInfo->vAxis[2] + pTextInfo->vOffset;
		vertex.u /= (float)pMipTex->width;
		vertex.v /= (float)pMipTex->height;
		vertex.lu = vertex.u;
		vertex.lv = vertex.v;

		fUMin = (vertex.u < fUMin) ? vertex.u : fUMin;
		fUMax = (vertex.u > fUMax) ? vertex.u : fUMax;
		fVMin = (vertex.v < fVMin) ? vertex.v : fVMin;
		fVMax = (vertex.v > fVMax) ? vertex.v : fVMax;

		pVertexList->Add(vertex);
	}

	// Create a light-map texture for this face
	int nLMWidth = (int)(ceil((fUMax * pMipTex->width) / 16.0f) - floor((fUMin * pMipTex->width) / 16.0f) + 1.0f);
	int nLMHeight = (int)(ceil((fVMax * pMipTex->height) / 16.0f) - floor((fVMin * pMipTex->height) / 16.0f) + 1.0f);
	_createLMTexture(pFace, nLMWidth, nLMHeight);

	// Update light-map vertex u, v coordinates.  These should range from [0.0 -> 1.0] over face.
	float fUDel = (fUMax - fUMin);
	if (fUDel > Math<float>::cZeroTolerance)
		fUDel = 1.0f / fUDel;
	else
		fUDel = 1.0f;
	float fVDel = (fVMax - fVMin);
	if (fVDel > Math<float>::cZeroTolerance)
		fVDel = 1.0f / fVDel;
	else
		fVDel = 1.0f;
	for (int n=nStartIndex; n<(int)pVertexList->GetArrayCount(); n++)
	{
		(*pVertexList)[n].lu = ((*pVertexList)[n].lu - fUMin) * fUDel;
		(*pVertexList)[n].lv = ((*pVertexList)[n].lv - fVMin) * fVDel;
	}

	// Adjust texture coordinates so that they always begin inside [0.0 -> 1.0].
	float fFrac;
	fFrac = modf(fUMin, &fUMin);
	if (fUMin < 0.0f && fabs(fFrac) > 0.0001f)
		fUMin -= 1.0f;
	fFrac = modf(fVMin, &fVMin);
	if (fVMin < 0.0f && fabs(fFrac) > 0.0001f)
		fVMin -= 1.0f;

	for (n=nStartIndex; n<(int)pVertexList->GetArrayCount(); n++)
	{
		(*pVertexList)[n].u = (*pVertexList)[n].u - fUMin;
		(*pVertexList)[n].v = (*pVertexList)[n].v - fVMin;
	}
}

// Helper method to get the two vertex points associated with the given edge reference, 
// where the two vertices are in the correct winding order (v1 -> v2).
__forceinline void HFBSPGraph::_getEdgeVertices(Vector3f & v1, Vector3f & v2, int nEdge)
{
	// Get the edge index for this face.
	int nEdgeIndex = (int)m_BSPData.FaceEdges()->m_pArray[nEdge];
	short nV1, nV2;
	const bspf_edge * pEdge;

	// Get the vertex indices in the correct winding order.
	if (nEdgeIndex < 0)
	{
		pEdge = &(m_BSPData.Edges()->m_pArray[-nEdgeIndex]);
		nV1 = pEdge->nV2;
		nV2 = pEdge->nV1;
	}
	else
	{
		pEdge = &(m_BSPData.Edges()->m_pArray[nEdgeIndex]);
		nV1 = pEdge->nV1;
		nV2 = pEdge->nV2;
	}

	// Get the actual vertex points.
	for (int n=0; n<3; n++)
	{
		v1[n] = m_BSPData.Vertices()->m_pArray[nV1].fPoint[n];
		v2[n] = m_BSPData.Vertices()->m_pArray[nV2].fPoint[n];
	}
}

// Get the face texture from the reference index.  All face textures should be in the
// m_DXTextures map object since they were pre-loaded at level load time.
IDirect3DTexture9 * HFBSPGraph::_getDXTexture(int nMipTex)
{
	// Check to see if DX (device ready) texture has already been created and is in map
	IDirect3DTexture9 * pDXTexture = 0;
	IDirect3DTexture9 *const * ppDXTexture = m_DXTextures.IsInMap(nMipTex);

	if (ppDXTexture)
	{
		// Found
		pDXTexture = const_cast<IDirect3DTexture9 *>(*ppDXTexture);
		assert(pDXTexture);
	}
	else
	{
		assert(0);
	}

	return pDXTexture;
}

// Method to look up the RGB texture for this face, convert it to DX compatible texture data, and
// add it to the DX texture map object.
void HFBSPGraph::_createDXTexture(const bspf_face * pFace)
{
	const bspf_textinfo * pTextInfo = &(m_BSPData.TextInfo()->m_pArray[pFace->nTextureInfo]);
	int nMipTex = pTextInfo->nMipTex;

	IDirect3DTexture9 * const * ppDXTexture = m_DXTextures.IsInMap(nMipTex);
	if (ppDXTexture)
		return;

	// If not found then create DX texture from WAD RGB texture
	const wtexture * pWTexture = m_Textures.GetWTexture(nMipTex);
	assert(pWTexture);

	// Create the DX texture
	rgbtex RGBTex = pWTexture->RGBTexture;
	IDirect3DTexture9 * pDXTexture;
	pDXTexture = m_pRenderer->GetTexture((BYTE *)(RGBTex.pRGBTex), 0, D3DUSAGE_AUTOGENMIPMAP, D3DFMT_A8R8G8B8, RGBTex.nWidth, RGBTex.nHeight);
	assert(pDXTexture);

	// Add texture to map
	m_DXTextures.Add(nMipTex, pDXTexture);
}

// Method to create DX compatible flash light textures, one texture illuminates and the other does nothing.
void HFBSPGraph::_createFLDXTexture()
{
	SAFE_RELEASE(m_pDXFLTexture);
	SAFE_RELEASE(m_pDXNFLTexture);

	unsigned long * pRGBFLTexture;
	unsigned long * pRGBNFLTexture;

	m_Textures.CreateFLTextures(&pRGBFLTexture, &pRGBNFLTexture, m_nFLTextureSize);

	// Next create the DX device texture object
	m_pDXFLTexture = m_pRenderer->GetTexture((BYTE *)(pRGBFLTexture), 1, 0, D3DFMT_X8R8G8B8, m_nFLTextureSize, m_nFLTextureSize);
	assert(m_pDXFLTexture);

	m_pDXNFLTexture = m_pRenderer->GetTexture((BYTE *)(pRGBNFLTexture), 1, 0, D3DFMT_X8R8G8B8, m_nFLTextureSize, m_nFLTextureSize);
	assert(m_pDXNFLTexture);

	// clean up
	DELETE_PTR(pRGBFLTexture);
	DELETE_PTR(pRGBNFLTexture);

	/*
	// @test
	HRESULT hr = m_pRenderer->SaveTextureToFile("flashTexture.bmp", m_pDXFLTexture, D3DXIFF_BMP);
	*/
}

// Get the light map texture for given face.  All face light map textures should be in the
// m_DXLMTextures map object since they were pre-loaded at level load time.
IDirect3DTexture9 * HFBSPGraph::_getLMTexture(const bspf_face * pFace)
{
	// Check to see if DX (device ready) LM texture has already been created and is in map
	IDirect3DTexture9 * pDXLMTexture = 0;
	IDirect3DTexture9 *const * ppDXLMTexture = m_DXLMTextures.IsInMap(pFace->nLightMapOfs);

	if (ppDXLMTexture)
	{
		// Found
		pDXLMTexture = const_cast<IDirect3DTexture9 *>(*ppDXLMTexture);
		assert(pDXLMTexture);
	}

	return pDXLMTexture;
}

// Helper method to create a DX compatible RGB light map texture for the given face, and add
// it to the light map texture map object.
void HFBSPGraph::_createLMTexture(const bspf_face * pFace, int nLMWidth, int nLMHeight)
{
	if ( unsigned long * pLMRGBTexture = m_Textures.CreateLMTexture(m_BSPData, pFace, nLMWidth, nLMHeight) )
	{
		if (m_DXLMTextures.IsInMap(pFace->nLightMapOfs) == 0)
		{
			// Next create a DX device texture
			IDirect3DTexture9 * pDXLMTexture = m_pRenderer->GetTexture((BYTE *)(pLMRGBTexture), 1, 0, D3DFMT_X8R8G8B8, nLMWidth, nLMHeight);
			assert(pDXLMTexture);

			// Add LM texture to map
			m_DXLMTextures.Add(pFace->nLightMapOfs, pDXLMTexture);
		}

		DELETE_ARRAY_PTR(pLMRGBTexture);
	}
}

// Method to create all non-changing data for the current loaded bsp level.  This includes an array
// object containing all vertex information for the level (and which is IRenderer (DX) compatible), 
// all face textures, all face light map textures, and the flashlight light map texture.
// Note that IRenderer (DX) vertex information includes vertex, and texture coordinates for all
// textures applied.  See definition of HFVERTEX in IRenderer.h file.
void HFBSPGraph::_createStaticGeomData()
{
	assert(m_pRenderer);

	// Prepare array of face indices to DX vertex indices
	m_SGFaceToVertex.Clear();
	m_SGFaceToVertex.SetSize(5000);
	m_SGFaceToVertex.SetGrowSize(1000);
	m_SGFaceToVertex.SetCompareFn(CompareIntSet);

	// Prepare array for DX device ready textures
	m_DXTextures.Clear();
	m_DXTextures.SetSize(200);
	m_DXTextures.SetCompareFn(CompareIntSet);

	// Prepare array for LightMap (LM) DX device ready textures
	m_DXLMTextures.Clear();
	m_DXLMTextures.SetSize(200);
	m_DXLMTextures.SetCompareFn(CompareIntSet);

	// Start by creating an array of vertices and a map from face index to vertex index.
	// Also create the texture and light-map for this face.
	Array<HFVERTEX> m_Vertices(20000);
	m_Vertices.SetGrowSize(5000);
	for (int n=0; n<m_BSPData.Faces()->m_cSize; n++)
	{
		const bspf_face * pFace = &(m_BSPData.Faces()->m_pArray[n]);
		m_SGFaceToVertex.Add(n, (int)m_Vertices.GetArrayCount());	// Add face index and vertex array index to map

		_collectFaceVertices(pFace, &m_Vertices);					// Get face vertices and create face light-map

		_createDXTexture(pFace);									// Create DX textures
	}

	// Creates the DX flash-light texture
	_createFLDXTexture();

	// Create new DX9 vertex buffer to hold all face vertices for static geometry
	SAFE_RELEASE(m_pSGDX9Vertices);
	m_pRenderer->CreateVertexBuffer(&m_Vertices, &m_pSGDX9Vertices);
	m_pRenderer->SetStreamSource(0, m_pSGDX9Vertices, 0);
}

// Method to initially place the camera in the current bsp level.  The starting location of 
// the camera (and player) is determined by the "info_player_start" entity class.
void HFBSPGraph::_placeCamera()
{
	const EntVars * pEnt = m_BSPData.FindEntity(cString("info_player_start"));
	if (pEnt)
	{
		m_pCamera->Position() = pEnt->vOrigin;
		m_pCamera->SetYawAngle(pEnt->fYawAngle);
	}
	else
	{
		throw "Error:  Unable to find player starting position for this level.";
	}

	m_pCamera->SetCrouched(false);

	// Make sure camera is not embedded in floor
	_adjustCamPosition(Vector3f::cZero, true);
}

// Step height defines maximum height player will automatically step up without first jumping.
static float s_fStepHeight = 18.0f;

// Compute the center point of a given bounding box.
__forceinline Vector3f _computeCenterPt(const Vector3f vBB[2])
{
	Vector3f ptCenter;
	ptCenter[0] = (vBB[1][0] - vBB[0][0]) / 2.0f + vBB[0][0];
	ptCenter[1] = (vBB[1][1] - vBB[0][1]) / 2.0f + vBB[0][1];
	ptCenter[2] = (vBB[1][2] - vBB[0][2]) / 2.0f + vBB[0][2];

	return ptCenter;
}

// Computes the difference vector along a plane normal.  Used to find adjustment vector to avoid
// collision to a face.
__forceinline Vector3f _computeDiffVector(const Planef fPlane, const Vector3f & ptIntersect, const Vector3f & ptTest)
{
	return fPlane.Normal() * (fPlane.Normal().Dot((ptIntersect - ptTest)));
}

// Shrinks a bounding box in all directions by given amount value.
__forceinline void _shrinkVBB(Vector3f vBB[2], float fAmount)
{
	Vector3f vAmount(fAmount, fAmount, fAmount);
	vBB[0] += vAmount;
	vBB[1] -= vAmount;
}

// Structure to hold plane normal information, includes normal vector and depth of penetration.
struct PlaneNormal
{
	float		fDepth;
	Vector3f	vNormal;

	PlaneNormal()
	{
		fDepth = 0.0f;
	}
	PlaneNormal(float fDpth, Vector3f const& vNorm)
	{
		fDepth = fDpth;
		vNormal = vNorm;
	}
};

// Helper function to remove any z component in the intersection plane and then re-normalize
// the plane normal vector and recompute the intersection distance (penetration depth).
void _adjustPlaneForZ(SectInfo& sectInfo)
{
	Planef& fPlane = sectInfo.fPlane;
	if ( fabs(fPlane.Normal()[2]) > 0.01f )
	{
		fPlane.Normal()[2] = 0.0f;
		fPlane.Normal().Normalize();

		Vector3f ptIntersect = sectInfo.ptIntersect;
		fPlane.Distance() = sqrt( ptIntersect[0]*ptIntersect[0] + ptIntersect[1]*ptIntersect[2] );
	}
}

// Helper function to look at array of bounding box intersections and remove items that are
// uninteresting for lateral intersections (recall that z coordinate is the vertical in HL
// world).
// Also checks to see if any two intersection planes are positioned in a concave manner,
// and if so returns true.
bool _cleanUpData(Array<SectInfo> & aIntersections, Vector3f const& vPosDir)
{
	for ( int j=(int)aIntersections.GetArrayCount()-1; j>=0; --j )
	{
		// Remove nonsense intersection planes
		SectInfo & sectInfo = aIntersections[j];
		if ( sectInfo.fDepth < 0.01f || sectInfo.fPlane.Normal().Dot( vPosDir ) >= 0.0f)
		{
			aIntersections.Remove(j);
		}
		else
		{
			// Remove Z component from planes
			_adjustPlaneForZ(sectInfo);
		}
	}

	if ( aIntersections.GetArrayCount() == 0 )
		return false;

	// Look for concave planes
	for ( j=0; j<(int)aIntersections.GetArrayCount(); ++j )
	{
		SectInfo& sectInfo1 = aIntersections[j];

		for ( int n=j+1; n<(int)aIntersections.GetArrayCount(); ++n )
		{
			SectInfo& sectInfo2 = aIntersections[n];
			float fDot = sectInfo1.fPlane.Normal().Dot( sectInfo2.fPlane.Normal() );
			if ( fDot < 0.90f )
			{
				float t1 = sectInfo1.fPlane.TestPoint( sectInfo2.ptIntersect );
				float t2 = sectInfo2.fPlane.TestPoint( sectInfo1.ptIntersect );
				if ( t1 >= 0.0f || t2 >= 0.0f )
				{
					// Found planes forming a concave shape
					return true;
				}
			}
		}
	}

	return false;
}

// Helper function to check the intersections data for concave planes where bounding box
// intersections occurred.  If any are found then this function returns true and means
// that the camera (player) cannot be moved from keyboard commands and must remain
// where it is.
bool _checkForIntersections(Array<SectInfo> & aIntersections, Vector3f const& vPosDir)
{
	// Clean up data and look for concave intersection
	if ( _cleanUpData(aIntersections, vPosDir) )
	{
		return true;
	}

	// Check for any valid multiple plane intersection, where planes are almost perpendicular
	size_t ncount = aIntersections.GetArrayCount();
	if ( ncount > 1 )
	{
		for (size_t n=0; n<ncount; ++n)
		{
			for (size_t m=n+1; m<ncount; ++m)
			{
				SectInfo sectInfo1 = aIntersections[n];
				SectInfo sectInfo2 = aIntersections[m];
				float fDot = sectInfo1.fPlane.Normal().Dot( sectInfo2.fPlane.Normal() );
				if ( (fabs(fDot) < 0.30f) && (sectInfo1.fDepth > 0.01f && sectInfo2.fDepth > 0.01f) )
				{
					return true;
				}
			}
		}
	}

	return false;
}

// This is the main method adjusts the camera (player) position based on whether any intersections
// of the camera bounding box into "solid space" is found.  This method uses the BSPData class 
// geometry/entity intersection functions to determine if the camera is being moved into solid
// space, and if so computes an adjustment to the camera so that it stays in empty space.
// Collisions are tested in three main areas:
//  a) Collisions during camera lateral motion.
//  b) Collisions of the camera with the ground.
//  c) Collisions of the camera with the ceiling.
// Special adjustments are made for:
//  a) Lateral collision with two faces forming a concave shape (in which case the camera cannot move).
//  b) Collision with the ground while moving along the ground.
//  c) Allow motion along the non-level ground (allow a ramp of about 45 deg or less).
//  d) Allow automatic step up a set height change (s_fStepHeight).
//  e) Allow jump action only when camera is on ground.
//  f) Disallow rise from crouch if insufficient height.
// This code works reasonably well but is far from perfect.  This code is a result of a lot of
// experimentation and has become pretty messy.  Some effort should be made to clean this code up, 
// probably by re-thinking and re-writing most of it.
void HFBSPGraph::_adjustCamPosition(const Vector3f & ptCamOld, bool bIgnoreStep)
{
	Vector3f vBB[2];

	//
	// First check for lateral intersection adjustments
	//

	Vector3f vPosDelta(m_pCamera->Position() - ptCamOld);
	if ( vPosDelta.MagSquared() > 0.001f )
	{
		m_pCamera->GetBB(vBB);
		Vector3f ptCamera = _computeCenterPt(vBB);
		vBB[0][2] += s_fStepHeight;
		Vector3f vPosDir = vPosDelta; vPosDir.Normalize();

		// Collect intersection data, if any.
		m_aIntersections.Clear();
		m_BSPData.BBIntersectGeometry(vBB, m_aIntersections);
		m_BSPData.BBIntersectEntities(ptCamera, vBB, m_aIntersections);

		if ( _cleanUpData(m_aIntersections, vPosDir) )
		{
			// We found two concave planes.  Disallow lateral movement
			m_aIntersections.Clear();
			m_pCamera->Position() = ptCamOld;
		}

		// Skip this work if no lateral intersections were found.
		if ( m_aIntersections.GetArrayCount() > 0 )
		{
			Array<PlaneNormal> aNormals; aNormals.SetSize(m_aIntersections.GetArrayCount());
			while ( m_aIntersections.GetArrayCount() > 0 )
			{
				SectInfo sectInfo(m_aIntersections[0]);
				m_aIntersections.Remove(0);

				// Combine similar plane orientations and create set of plane normals for intersection
				for ( int n=(int)m_aIntersections.GetArrayCount()-1; n>=0; --n )
				{
					SectInfo& sectInfo2 = m_aIntersections[n];
					float fDot = sectInfo.fPlane.Normal().Dot( sectInfo2.fPlane.Normal() );

					// Check for plane with same orientation
					if ( (fDot > 0.99f && fDot < 1.01f) )
					{
						sectInfo.fDepth = (sectInfo2.fDepth > sectInfo.fDepth) ? sectInfo2.fDepth : sectInfo.fDepth;
						sectInfo.ptIntersect = (sectInfo.ptIntersect+sectInfo2.ptIntersect) / 2.0f;
						m_aIntersections.Remove(n);
						continue;
					}
				}

				// Save plane normal
				if ( sectInfo.fDepth > 0.0f )
					aNormals.Add( PlaneNormal(sectInfo.fDepth, sectInfo.fPlane.Normal()) );
			}

			if ( aNormals.GetArrayCount() > 0 )
			{
				// Add all plane normals together.
				Vector3f vNormal = Vector3f::cZero;
				float fDepth = 0.0f;
				for ( int n=0; n<(int)aNormals.GetArrayCount(); ++n )
				{
					vNormal += aNormals[n].vNormal;
					fDepth = (fDepth > aNormals[n].fDepth) ? fDepth : aNormals[n].fDepth;
				}
				vNormal.Normalize();

				float fPosDepth = vNormal.Dot(-vPosDelta);

				// Increase fPosDepth if we are moving in the Z direction (HACK)
				float fZMove = fabs(vPosDelta[2]);
				if (fZMove > 0.0f)
					fPosDepth += fZMove;

				fDepth = (fPosDepth < fDepth) ? fPosDepth : fDepth;
				m_pCamera->Position() += (vNormal * fDepth);

				if (!bIgnoreStep)
				{
					// Check camera movement to ensure it doesn't bring the BB into a collision with concave shapes
					m_pCamera->GetBB(vBB);
					Vector3f ptCamera = _computeCenterPt(vBB);
					vBB[0][2] += s_fStepHeight;
					m_aIntersections.Clear();
					m_BSPData.BBIntersectGeometry(vBB, m_aIntersections);
					m_BSPData.BBIntersectEntities(ptCamera, vBB, m_aIntersections);

					if ( _checkForIntersections(m_aIntersections, vPosDir) )
					{
						m_pCamera->Position() = ptCamOld;
					}
				}
			}
		}
	}

	//
	// Test camera bottom for intersection with floor.
	//
	Vector3f ptStart, ptTest, ptIntersect, ptIntSect;
	Planef fPlane;
	m_pCamera->GetBB(vBB);
	_shrinkVBB(vBB, 2.0f);
	Vector3f ptCenter = _computeCenterPt(vBB);
	float fZDiff = 0.0f;
	int nx, ny;

	// Check corners
	for (nx=0; nx<2; nx++)
	{
		for (ny=0; ny<2; ny++)
		{
			ptStart = Vector3f(vBB[nx][0], vBB[ny][1], vBB[1][2]);
			ptTest = Vector3f(vBB[nx][0], vBB[ny][1], vBB[0][2]);

			// Static geometry
			if ( m_BSPData.RayIntersectFace(ptStart, ptTest, fPlane, ptIntersect) )
			{
				// Only account for true floor surfaces, including ramps
				if (fPlane.Normal()[2] > 0.70f)
				{
					Vector3f ptTemp = _computeDiffVector(fPlane, ptIntersect, ptTest);
					if (fZDiff == 0.0f)
					{
						ptIntSect = ptIntersect;
						fZDiff = ptTemp[2];
					}
					else if (ptTemp[2] < fZDiff)
					{
						ptIntSect = ptIntersect;
						fZDiff = ptTemp[2];
					}
				}
			}

			// Entity model geometry
			m_BSPData.IntersectEntBB(ptStart, ptTest, vBB, m_aIntersections);
			for (int n=0; n<(int)m_aIntersections.GetArrayCount(); n++)
			{
				fPlane = m_aIntersections[n].fPlane;
				ptIntersect = m_aIntersections[n].ptIntersect;
				if (fPlane.Normal()[2] > 0.70f)
				{
					Vector3f ptTemp = _computeDiffVector(fPlane, ptIntersect, ptTest);
					if (fZDiff == 0.0f)
					{
						ptIntSect = ptIntersect;
						fZDiff = ptTemp[2];
					}
					else if (ptTemp[2] < fZDiff)
					{
						ptIntSect = ptIntersect;
						fZDiff = ptTemp[2];
					}
				}
			}
		}
	}

	// Check center of camera volume
	ptStart = Vector3f(ptCenter[0], ptCenter[1], vBB[1][2]);
	ptTest = Vector3f(ptCenter[0], ptCenter[1], vBB[0][2]);
	if ( m_BSPData.RayIntersectFace(ptStart, ptTest, fPlane, ptIntersect) )
	{
		// Only account for true floor surfaces, including ramps
		if (fPlane.Normal()[2] > 0.70f)
		{
			Vector3f ptTemp = _computeDiffVector(fPlane, ptIntersect, ptTest);
			if (fZDiff == 0.0f)
			{
				ptIntSect = ptIntersect;
				fZDiff = ptTemp[2];
			}
			else if (ptTemp[2] < fZDiff)
			{
				ptIntSect = ptIntersect;
				fZDiff = ptTemp[2];
			}
		}
	}

	bool bBottomIntersect = false;
	if (fZDiff > 0.0f)
	{
		// We have a floor collision.
		if (bIgnoreStep || fZDiff < s_fStepHeight)
		{
			// Adjust camera to remain above the floor.
			m_pCamera->Position()[2] += fZDiff;
			m_pCamera->GetBB(vBB);
			_shrinkVBB(vBB, 2.0f);
			m_pCamera->StopJump();
		}
		// Otherwise ran into something too large for a stair step.  May be a desk so don't
		// adjust camera height to be on top.
		bBottomIntersect = true;
	}

	// If not bottom intersection then we may be above the floor and need to fall.
	bool bOnFloor = bBottomIntersect;
	if (!bBottomIntersect)
	{
		// Test point in center (x/y) of camera BB
		ptStart = ptCenter;
		ptTest = ptCenter;
		ptTest[2] = vBB[0][2] - 0.5f;
		if ( m_BSPData.RayIntersectFace(ptStart, ptTest, fPlane, ptIntersect) )
		{
			bOnFloor = true;
		}
		
		if (!bOnFloor)
		{
			m_pCamera->StartFall();
		}
	}
	m_pCamera->SetCamOnFloor(bOnFloor);

	//
	// Check for top intersection
	//
	m_pCamera->GetBB(vBB);
	_shrinkVBB(vBB, 2.0f);
	ptCenter = _computeCenterPt(vBB);
	fZDiff = 0.0f;
	bool bAllPoints = true;
	for (nx=0; nx<2; nx++)
	{
		for (ny=0; ny<2; ny++)
		{
			ptStart = Vector3f(vBB[nx][0], vBB[ny][1], vBB[0][2]);
			ptTest = Vector3f(vBB[nx][0], vBB[ny][1], vBB[1][2]);

			// Static geometry
			if ( m_BSPData.RayIntersectFace(ptStart, ptTest, fPlane, ptIntersect) )
			{
				Vector3f ptTemp = _computeDiffVector(fPlane, ptIntersect, ptTest);
				if (ptTemp[2] < fZDiff)
				{
					fZDiff = ptTemp[2];
					ptIntSect = ptIntersect;
				}
			}
			else
			{
				// Entity model geometry
				m_BSPData.IntersectEntBB(ptStart, ptTest, vBB, m_aIntersections);
				int nCount = (int)m_aIntersections.GetArrayCount();
				bAllPoints = nCount > 0;
				for (int n=0; n<nCount; n++)
				{
					fPlane = m_aIntersections[n].fPlane;
					ptIntersect = m_aIntersections[n].ptIntersect;
					Vector3f ptTemp = _computeDiffVector(fPlane, ptIntersect, ptTest);
					if (ptTemp[2] < fZDiff)
					{
						fZDiff = ptTemp[2];
						ptIntSect = ptIntersect;
					}
				}
			}
		}
	}
	if (!bAllPoints)
	{
		// Check center point
		ptStart = ptCenter;
		ptTest = ptCenter;
		ptTest[2] = vBB[1][2];
		if ( m_BSPData.RayIntersectFace(ptStart, ptTest, fPlane, ptIntersect) )
		{
			Vector3f ptTemp = _computeDiffVector(fPlane, ptIntersect, ptTest);
			fZDiff = ptTemp[2];
			ptIntSect = ptIntersect;
		}
		else
			fZDiff = 0.0f;
	}
	if (fZDiff < 0.0f)
	{
		// We have a ceiling collision.
		if (bAllPoints && (bBottomIntersect || bOnFloor))
		{
			// If we also have a floor collision we don't have enough room and need to crouch
			// m_pCamera->HandleTooTall(-fZDiff);
			m_pCamera->Position() = ptCamOld;
		}
		else
		{
			// Otherwise adjust camera position down to clear ceiling.
			m_pCamera->Position()[2] += fZDiff;
		}

		m_pCamera->LimitJump();
		m_pCamera->StopLevitate();
	}
	else if (m_pCamera->IsCrouched())
	{
		// No top intersection.  If we are currently crouched then see if there is enough room to rise.
		ptStart = ptCenter;
		ptTest = ptCenter;
		ptTest[2] = vBB[0][2] + m_pCamera->GetUprightHeight();
		if ( !m_BSPData.RayIntersectFace(ptStart, ptTest, fPlane, ptIntersect) )
		{
			// Clear to rise.
			m_pCamera->StartRise();
		}
	}
}

// Walks the BSP tree, starting from the leaf where the camera currently resides, and collects all
// potentially visible leaf nodes (recall that the BSP tree leaf nodes contain the faces to render).
// The walking of the tree is done recursively.
// Non visible leafs are rejected by:
//  a) Rejecting large parts of the BSP tree that are outside the viewing frustum.
//	b) Rejecting any remaining leaf nodes that are not part of the PVS (potentially visible set).
const Array<int> * HFBSPGraph::_visibleLeafsFtoB()
{
	assert(m_pCamLeaf);

	m_VisLeafs.Clear();

	// Get visibility set for this leaf
	const unsigned char * pVisSet = m_BSPData.VisSet(m_BSPData.Leafs()->m_pArray[m_pCamLeaf->nLeaf].ofsCluster);
	assert(pVisSet);

	// Walk BSP tree front to back, culling nodes outside camera frustum, collecting faces to draw
	// only in visible leaves (using PVS).
	const BSPNode * pCurrNode = m_pCamLeaf;
	const BSPNode * pParent = m_pCamLeaf->pParent;
	_collectLeafsFtoB_r(pCurrNode, &m_VisLeafs, pVisSet);
	while (pParent)
	{
		if (pParent->pFront == pCurrNode)
		{
			_collectLeafsFtoB_r(pParent->pBack, &m_VisLeafs, pVisSet);
		}
		else
		{
			_collectLeafsFtoB_r(pParent->pFront, &m_VisLeafs, pVisSet);
		}

		pCurrNode = pParent;
		pParent = pCurrNode->pParent;
	}

	return &m_VisLeafs;
}

// Helper method to recursively collect potentially visible leafs (called by _visibleLeafsFtoB method).
void HFBSPGraph::_collectLeafsFtoB_r(const BSPNode * pNode, Array<int> * pLeafs, const unsigned char * pVisSet)
{
	// Cull nodes outside camera frustum
	if (!pNode || !_intersectFrustum(pNode))
		return;

	// Add leaf nodes (nLeaf > -1) to collection array
	// Skip non-drawable solid leaves (nLeaf == 0) and all non-visible leaves
	if ( (pNode->nLeaf > 0) && m_BSPData.IsLeafVisible(pVisSet, pNode->nLeaf) )
	{
		pLeafs->Add(pNode->nLeaf);
		return;
	}

	// Recurse into tree front to back order
	if ( m_BSPData.SPlanes()->m_pArray[pNode->nPlane].TestPoint(m_pCamera->Position()) > 0.0f )
	{
		_collectLeafsFtoB_r(pNode->pFront, pLeafs, pVisSet);
		_collectLeafsFtoB_r(pNode->pBack, pLeafs, pVisSet);
	}
	else
	{
		_collectLeafsFtoB_r(pNode->pBack, pLeafs, pVisSet);
		_collectLeafsFtoB_r(pNode->pFront, pLeafs, pVisSet);
	}
}

// Method to test if a single polygon face is potentially visible.  First test checks to 
// see if face is pointing toward or away from the camera (if away it is rejected).  Second
// test is to see if the face bounding box is within the viewing frustum (note that there
// is a special WorldFrustum for this which is the camera frustum in HL world coordinates).
bool HFBSPGraph::_isFaceVisible(const bspf_face * pFace)
{
	assert(pFace);

	// Cull back facing faces
	// Get Look vector and compare to face normal.  Need to adjust for largest field of view angle.
	Planef facePlane(m_BSPData.SPlanes()->m_pArray[pFace->nPlane]);
	if (pFace->cSide == 1)
		facePlane.InvertPlane();
	Vector3f vLook = m_pCamera->GetViewLookVector();
	if (vLook.Dot(facePlane.Normal()) > m_pCamera->GetLargestViewSin())
		return false;

	// Create bounding box for face
	Vector3f vBB[2];
	vBB[0] = Vector3f(10000.0f, 10000.0f, 10000.0f);			// Min point
	vBB[1] = Vector3f(-10000.0f, -10000.0f, -10000.0f);			// Max point

	for (int nEdge=0; nEdge<pFace->cEdges; nEdge++)
	{
		int nEdgeIndex = (int)m_BSPData.FaceEdges()->m_pArray[pFace->nE1 + nEdge];
		const bspf_edge * pEdge = &(m_BSPData.Edges()->m_pArray[abs(nEdgeIndex)]);
		for (int n=0; n<3; n++)
		{
			float fValue;
			
			if (nEdgeIndex < 0)
				fValue = m_BSPData.Vertices()->m_pArray[pEdge->nV2].fPoint[n];
			else
				fValue = m_BSPData.Vertices()->m_pArray[pEdge->nV1].fPoint[n];
			vBB[0][n] = (fValue < vBB[0][n]) ? fValue : vBB[0][n];
			vBB[1][n] = (fValue > vBB[1][n]) ? fValue : vBB[1][n];
		}
	}

	// Check for intersection of world camera frustum with face world bounding box
	return m_pCamera->WorldFrustum()->TestBoundingBox(vBB) == 0;
}

// Helper method to check if a node bounding box is inside or outside the view frustum.
bool HFBSPGraph::_intersectFrustum(const BSPNode * pNode)
{
	if (pNode->nLeaf > -1)
	{
		// Leaf node.  Return true.  Leaf faces will be tested later.
		return true;
	}

	Vector3f vBB[2] = { Vector3f(pNode->MinBox[0], pNode->MinBox[1], pNode->MinBox[2]), 
						Vector3f(pNode->MaxBox[0], pNode->MaxBox[1], pNode->MaxBox[2]) };

	// Check for intersection of world camera frustum with leaf world bounding box
	return m_pCamera->WorldFrustum()->TestBoundingBox(vBB) == 0;
}

bool CompareFaces(const lface & Ele1, const lface & Ele2)
{
	return (Ele1.nMipTex > Ele2.nMipTex);
}

// Iterates through the provided potentially visible leaf array and fills the face
// array object with potentially visible faces to be rendered.  The actual rendering
// uses this face array for the faces to render.
const Array<lface> * HFBSPGraph::_getFaces(const Array<int> * pVisLeafs)
{
	m_VisFaces.Clear();

	for (int n=0; n<(int)pVisLeafs->GetArrayCount(); n++)
	{
		size_t tStart = m_VisFaces.GetArrayCount();
		const bspf_leaf * pLeaf = &(m_BSPData.Leafs()->m_pArray[pVisLeafs->Get(n)]);
		for (int i=0; i<pLeaf->cFaces; i++)
		{
			int nFaceIndex = m_BSPData.LeafFaces()->m_pArray[pLeaf->nFirstFace+i];
			const bspf_face * pFace = &(m_BSPData.Faces()->m_pArray[nFaceIndex]);

			// Check to see if face is visible inside camera frustum
			if (!_isFaceVisible(pFace))
				continue;

			const bspf_textinfo * pTextInfo = &(m_BSPData.TextInfo()->m_pArray[pFace->nTextureInfo]);
			lface lFace;
			lFace.nFaceIndex = nFaceIndex;
			lFace.nMipTex = pTextInfo->nMipTex;
			m_VisFaces.Add(lFace);
		}

		// Sort faces within a leaf based on nMipTex (group all faces with same texture together)
		// Probably doesn't help much and can be removed.
		size_t tCount = m_VisFaces.GetArrayCount();
		m_VisFaces.Sort(CompareFaces, tStart, (tCount-tStart));
	}

	return &m_VisFaces;
}

// Method to render entity model faces for all entities in the potentially visible entity
// list (based on the current camera position in the level).  The entities are divided
// into two parts: transparent and solid, and this method takes a boolean parameter to
// determine which set of entities are rendered.
void HFBSPGraph::_renderEntFaces(bool bTransparent, const Matrix4f & mtxView)
{
	// Go through all potentially visible brush entities and render if in view frustum
	const VisEnts * pVEnts = m_BSPData.GetVisibleEntities(m_pCamLeaf->nLeaf);
	for (int n=0; n<(int)pVEnts->GetSetCount(); n++)
	{
		const EntVars * pEnt = &(m_BSPData.GetEntities()->Get(pVEnts->Get(n)));
		int nBrushIndex = pEnt->nBrush_model_index;

		// Cull non-brush entities, entities that don't meet the transparency condition, and special entities
		if ( (nBrushIndex <= 0) || 
			 (bTransparent && (pEnt->flags & ENTITY_TRANSPARENT) == 0) || 
			 (!bTransparent && (pEnt->flags & ENTITY_OPAQUE) == 0) ||
			 (pEnt->flags & ENTITY_SPECIAL) != 0 ||
			 (pEnt->vOrigin != Vector3f::cZero) )
		{
			continue;
		}

		const bspf_dmodel * pModel = &(m_BSPData.Models()->m_pArray[nBrushIndex]);

		// Cull to view frustum
		Vector3f vBB[2] = { Vector3f(pModel->mins), Vector3f(pModel->maxs) };
		if ( m_pCamera->WorldFrustum()->TestBoundingBox(vBB) == 0 )
		{
			m_VisFaces.Clear();

			for (int i=0; i<pModel->numfaces; i++)
			{
				int nFaceIndex = pModel->firstface + i;
				const bspf_face * pFace = &(m_BSPData.Faces()->m_pArray[nFaceIndex]);
				const bspf_textinfo * pTextInfo = &(m_BSPData.TextInfo()->m_pArray[pFace->nTextureInfo]);
				lface lFace;
				lFace.nFaceIndex = nFaceIndex;
				lFace.nMipTex = pTextInfo->nMipTex;
				m_VisFaces.Add(lFace);
			}

			// Group all faces with the same texture together (reduce SetTexture state changes)
			m_VisFaces.Sort(CompareFaces);

			// @review  Not sure about model translation.  Doesn't seem to be necessary for brush models.
			// What is pEnt->vOrigin?
			/*
			Matrix4f mtxWorld;
			if (pEnt->vOrigin != Vector3f::cZero)
			{
				Vector4f vTranslate(pEnt->vOrigin);
				mtxWorld = m_mtxWorld;
				mtxWorld.SetRow(3, vTranslate);
			}
			else
			{
				mtxWorld = m_mtxWorld;
			}
			m_pRenderer->SetTransform(D3DTS_WORLD, mtxWorld);
			*/

			if (bTransparent)
			{
				int nfactor = (int)(pEnt->fRenderamt * 255.0f);
				DWORD dwBlend = MAKE_ARGB(nfactor, nfactor, nfactor, nfactor);

				if (dwBlend != 0)
				{
					m_pRenderer->SetRenderState(D3DRS_TEXTUREFACTOR, dwBlend);
					m_pRenderer->SetTextureState(2, D3DTSS_ALPHAARG1, D3DTA_TFACTOR);
					m_pRenderer->SetTextureState(2, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
				}
			}
			_renderFaces(&m_VisFaces, mtxView, m_mtxWorld);
			if (bTransparent)
			{
				m_pRenderer->SetRenderState(D3DRS_TEXTUREFACTOR, 0xFFFFFFFF);
			}
		}
	}

	/*
	// Restore world transform to non-translated
	m_pRenderer->SetTransform(D3DTS_WORLD, m_mtxWorld);
	*/
}

// Helper method to create the vertex shader object.  This includes compiling the vertex shader
// file (VertexShader1.fx) and this file must reside in the same directory as the binary files
// for this application.
bool HFBSPGraph::_setVertexShader()
{
	if (!m_bShaderCapable)
	{
		m_pRenderer->SetVertexShader(NULL);
		DELETE_PTR(m_pVertexShader1);
		return false;
	}

	if (m_pVertexShader1)
	{
		return m_pVertexShader1->ReCreateShader(m_pRenderer);
	}

	const char szFuncName[] = "VS_Flashlight";
	const char szFileName[] = "VertexShader1.fx";
	m_pVertexShader1 = DXVertexShader::CreateShader(szFileName, szFuncName, m_pRenderer);
	if (m_pVertexShader1)
	{
		return m_pVertexShader1->SetShader();
	}

	return false;
}

void HFBSPGraph::_releaseDXTextures()
{
	for (int n=0; n<(int)m_DXTextures.GetMapCount(); n++)
	{
		IDirect3DTexture9 * pDXTexture = m_DXTextures[n].value;
		SAFE_RELEASE(pDXTexture);
	}
	m_DXTextures.Clear();

	for (n=0; n<(int)m_DXLMTextures.GetMapCount(); n++)
	{
		IDirect3DTexture9 * pDXLMTexture = m_DXLMTextures[n].value;
		SAFE_RELEASE(pDXLMTexture);
	}
	m_DXLMTextures.Clear();
}

void HFBSPGraph::_releaseDXVertices()
{
	SAFE_RELEASE(m_pSGDX9Vertices);
	m_SGFaceToVertex.Clear();
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
I am a senior software developer currently doing contract work for Microsoft. My educational background is in electrical engineering and I hold a masters degree from the University of Washington. I have experience in hardware and systems design but have done primarily software development for the last two decades. I have worked for various small companies as well as start-up companies, and have worked as a full time employee SDE at Microsoft Corporation.

Comments and Discussions