🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Untitled

posted in DruinkJournal
Published January 05, 2008
Advertisement
Well, randomly inspired by This post by Scet today (Well, This image, anyway), I decided to try making a Quake BSP renderer. I don't currently have any way to get remotely complicated maps into my engine, so a basic BSP renderer should be a good start.
I've been meaning to tinker with the Quake I format models for a while now, so this is a reasonable start.

I got a wee bit done today; not an awful lot - most of the time was spent tidying up engine code, and adding a proper camera class so I can wander about the levels without having to have weird key combinations. So now I can aim with the mouse and use up/down to move forwards and back, left/right to strafe left and right, and numpad 1 and numpad0 to move up/down.
I can load a BSP map and render it in wireframe in one massive chunk withot any problems at all. The BSP file contains a list of all the vertices (Well, vertex positions), and a list of index pairs that make up the edge. So it's just a case of using those to draw a large indexed linelist.

So, screenshots...

Quake I start map, at the spawnpoint, facing to the left slightly
Start map, start pos
(Click to enlarge)

Quake I start map, top-down view
Start map, top down
(Click to enlarge)

Next up will be making it use triangles obviously, I'm not sure how that'll go. Something that worries me slightly is that the Unofficial Quake Specs say that the BSP file version should be 23, but the files I have (Not sure what version of Quake, but it's the full one) are version 29. So there's bound to be some annoying differences that I'll run into.

And for anyone that cares at all, here's the relevant code:
bool PMapContextBSP::Render(PGraphics& gfx){	m_pShader->SetMatrix("mWorldViewProj", m_pOwner->GetTransform() * gfx.GetViewProj());	gfx.SetIndices(m_pIB);	gfx.SetVertexDecl(m_pVertexDecl);	gfx.SetVertexShader(m_pShader);	gfx.SetStreamSource(m_pVB, 0);	gfx.SetTexture(0, NULL);	gfx.DrawIndexedPrimitive(D3DPT_LINELIST, 0, 0, m_header.vertices.nSize / sizeof(BSPVert),		0, m_header.edges.nSize / sizeof(BSPEdge));	return true;}//============================================================================bool PMapContextBSP::Load(PGraphics& gfx, EResource::SP pResource){	// Validate file size and version	const u8* pData = (const u8*)pResource->GetData();	const BSPHeader* pHeader = (const BSPHeader*)pData;	if(pResource->GetLength() < sizeof(BSPHeader))	{		ELog::Get().DebugLog(L"Map file is too short to include a header\n");		return false;	}	else if(pHeader->nVersion != 0x1d)	{		ELog::Get().DebugLog(L"Invalid version in map file\n");		return false;	}	if(s_bDebugSpew)	{		ELog::Get().DebugFormat(L"Loading '%s':\n", m_pOwner->GetID().ToString().c_str());	}	// Vertices	u32 nVerts = pHeader->vertices.nSize / sizeof(BSPVert);	if(pHeader->vertices.nOffset >= pResource->GetLength() ||		pHeader->vertices.nOffset+nVerts*sizeof(BSPVert) >= pResource->GetLength())	{		ELog::Get().DebugLog(L"Invalid vertex directory in map file\n");		return false;	}	const BSPVert* pVertices = (const BSPVert*)(pData + pHeader->vertices.nOffset);	// Edges	u32 nEdges = pHeader->edges.nSize / sizeof(BSPEdge);	if(pHeader->edges.nOffset >= pResource->GetLength() ||		pHeader->edges.nOffset+nVerts*sizeof(BSPEdge) >= pResource->GetLength())	{		ELog::Get().DebugLog(L"Invalid edge directory in map file\n");		return false;	}	const BSPEdge* pEdges = (const BSPEdge*)(pData + pHeader->edges.nOffset);	// Allocate VB	m_pVB = new PVertexBuffer(gfx);	HRESULT hResult = m_pVB->Create(nVerts, sizeof(Vertex));	if(FAILED(hResult))	{		m_pVB = 0;		ELog::Get().DebugLog(L"Failed to create vertex buffer\n");		return false;	}	// Allocate IB	m_pIB = new PIndexBuffer(gfx);	hResult = m_pIB->Create(nEdges*2);	if(FAILED(hResult))	{		m_pIB = 0;		m_pVB = 0;		ELog::Get().DebugLog(L"Failed to create index buffer\n");		return false;	}	// Lock IB	{		u16* pLock = (u16*)m_pIB->Lock(0, nEdges*2);		if(!pLock)		{			m_pIB = 0;			m_pVB = 0;			ELog::Get().DebugLog(L"Failed to lock index buffer\n");			return false;		}		for(u32 i=0; i		{			*pLock++ = pEdges.v0;			*pLock++ = pEdges.v1;		}		m_pIB->Unlock();	}	// Init vertex decl	m_pVertexDecl = new PVertexDecl;	m_pVertexDecl->AppendPosition().AppendDiffuse();	if(!m_pVertexDecl->GetDecl()) // Ensure decl is created	{		m_pVertexDecl = 0;		m_pVB = 0;		m_pIB = 0;		ELog::Get().DebugLog(L"Failed to create vertex declaration\n");		return false;	}	// Init shader	std::wstring strShader = L"Basic.vsh";	m_pShader = gfx.GetShader(strShader.c_str(), PVertexShader::VS_2_0);	if(!m_pShader)	{		m_pVertexDecl = 0;		m_pVB = 0;		m_pIB = 0;		ELog::Get().DebugLog(L"Failed to load vertex shader\n");		return false;	}	// Fill VB	{		Vertex* pLock = (Vertex*)m_pVB->Lock(0, nVerts);		for(u32 i=0; i		{			pLock->vPos = EVector3(pVertices.pos.x, pVertices.pos.z, pVertices.pos.y);			pLock->nColour = 0xffff0000;			pLock->tu = 0.0f;			pLock->tv = 0.0f;			++pLock;		}		m_pVB->Unlock();	}	// Save header and return	m_header = *pHeader;	return true;}


Anyway, that's enough for now, I're orf.
Previous Entry Untitled
Next Entry Untitled
0 likes 6 comments

Comments

Mushu
get a haircut
January 05, 2008 03:34 PM
Mithrandir
I concur.
January 05, 2008 03:37 PM
Scet
While the Unofficial Quake Specs does get the layout of the structures right, there where a few changes in data types between then and the final version(29).

This is bspfile.h from the Quake Source, which is a much better reference.


/*
Copyright (C) 1996-1997 Id Software, Inc.

This program 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 2
of the License, or (at your option) any later version.

This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/


// upper design bounds

#define	MAX_MAP_HULLS		4

#define	MAX_MAP_MODELS		256
#define	MAX_MAP_BRUSHES		4096
#define	MAX_MAP_ENTITIES	1024
#define	MAX_MAP_ENTSTRING	65536

#define	MAX_MAP_PLANES		32767
#define	MAX_MAP_NODES		32767		// because negative shorts are contents
#define	MAX_MAP_CLIPNODES	32767		//
#define	MAX_MAP_LEAFS		8192
#define	MAX_MAP_VERTS		65535
#define	MAX_MAP_FACES		65535
#define	MAX_MAP_MARKSURFACES 65535
#define	MAX_MAP_TEXINFO		4096
#define	MAX_MAP_EDGES		256000
#define	MAX_MAP_SURFEDGES	512000
#define	MAX_MAP_TEXTURES	512
#define	MAX_MAP_MIPTEX		0x200000
#define	MAX_MAP_LIGHTING	0x100000
#define	MAX_MAP_VISIBILITY	0x100000

#define	MAX_MAP_PORTALS		65536

// key / value pair sizes

#define	MAX_KEY		32
#define	MAX_VALUE	1024

//=============================================================================


#define BSPVERSION	29
#define	TOOLVERSION	2

typedef struct
{
	int		fileofs, filelen;
} lump_t;

#define	LUMP_ENTITIES	0
#define	LUMP_PLANES		1
#define	LUMP_TEXTURES	2
#define	LUMP_VERTEXES	3
#define	LUMP_VISIBILITY	4
#define	LUMP_NODES		5
#define	LUMP_TEXINFO	6
#define	LUMP_FACES		7
#define	LUMP_LIGHTING	8
#define	LUMP_CLIPNODES	9
#define	LUMP_LEAFS		10
#define	LUMP_MARKSURFACES 11
#define	LUMP_EDGES		12
#define	LUMP_SURFEDGES	13
#define	LUMP_MODELS		14

#define	HEADER_LUMPS	15

typedef struct
{
	float		mins[3], maxs[3];
	float		origin[3];
	int			headnode[MAX_MAP_HULLS];
	int			visleafs;		// not including the solid leaf 0
	int			firstface, numfaces;
} dmodel_t;

typedef struct
{
	int			version;	
	lump_t		lumps[HEADER_LUMPS];
} dheader_t;

typedef struct
{
	int			nummiptex;
	int			dataofs[4];		// [nummiptex]
} dmiptexlump_t;

#define	MIPLEVELS	4
typedef struct miptex_s
{
	char		name[16];
	unsigned	width, height;
	unsigned	offsets[MIPLEVELS];		// four mip maps stored
} miptex_t;


typedef struct
{
	float	point[3];
} dvertex_t;


// 0-2 are axial planes
#define	PLANE_X			0
#define	PLANE_Y			1
#define	PLANE_Z			2

// 3-5 are non-axial planes snapped to the nearest
#define	PLANE_ANYX		3
#define<
January 05, 2008 06:16 PM
Evil Steve
Quote: Original post by Scet
While the Unofficial Quake Specs does get the layout of the structures right, there where a few changes in data types between then and the final version(29).

This is bspfile.h from the Quake Source, which is a much better reference.

*** Source Snippet Removed ***
Ah cool, thanks. I have the source code kicking about on my machine somewhere; I was just going to carry on blindly till something goes wrong, which probably isn't very clever [smile]

Do you know of an example change in data type? Do you mean an entire struct changing, or something changing from e.g. int to unsigned int? And does that change the size of the parent struct?
January 06, 2008 12:25 PM
Scet
Quote: Original post by Evil Steve
Do you know of an example change in data type? Do you mean an entire struct changing, or something changing from e.g. int to unsigned int? And does that change the size of the parent struct?


I don't really remember which ones change. The change is in the type, short to int, unsigned to signed, etc.

Sometimes the size of the struct changes, but since you're probably doing the whole "sizeof( lump ) / sizeof( struct )" the map will load fine. However, later during rendering you'd run into an index into an array that's out of bounds because the struct size was off.
January 06, 2008 03:04 PM
Evil Steve
Quote: Original post by Scet
Quote: Original post by Evil Steve
Do you know of an example change in data type? Do you mean an entire struct changing, or something changing from e.g. int to unsigned int? And does that change the size of the parent struct?


I don't really remember which ones change. The change is in the type, short to int, unsigned to signed, etc.

Sometimes the size of the struct changes, but since you're probably doing the whole "sizeof( lump ) / sizeof( struct )" the map will load fine. However, later during rendering you'd run into an index into an array that's out of bounds because the struct size was off.
Ah, ok. I'll definitely use the source code equivalents then.

Cheers [smile]
January 07, 2008 03:41 AM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement
Advertisement