Attempting a BSP loader

Started by
5 comments, last by fitfool 16 years, 10 months ago
I am trying to write a BSP loader, but I have a few different questions. Which would be the best version to write a loader for? One of the Quakes, or one of the Half Lifes? I am leaning towards Quake 3 because that is the one I have found the most information about. Also, when I am loading the texture lump, I noticed a lot of texture names, but could only find a physical file for one. I assume that these other texture names refer to texture files included with the Quake 3 game. How do people usually get around this? Are there any generic BSP files that include all of their textures with them?
Advertisement
yes the textures you are seeing are part of the quake3 game, (you are reading from the pk3 file aren't you) the are loads of free maps out the try www.planetquake.com just look for a fully customised one with all new textures or you could make a map your self.
Well, I think just about everyone will tell you to go with Q3, but I have a different point of view.

See I wanted to write a whole engine, and to write anything I needed to be able to load test data.

The Q3 format (BSPv38) geometry is stored as tri-meshes and the Half-Life format (BSPv30 i think) stores geometry as general polygons. For pretty much all purposes tri-meshes are better, so yes, the v30 Half-Life format is antiquated.

But Q3 supports meaterals, which means their textures are not just textures. They are complex files including image, shader, and meateral information. Not what I wanted to deal with in my first engine. Also QRadiant, the level editor for Q3 SSUUUCCCKKKKSSSS! While the editor for the Half-Life version, Hammer (formerly World-Craft) is one of the best out there.

At first I wrote an extreemly basic Q3 loader, it loaded only 1 type of geometry from the file (there are several "types" of geometry in Q3) and parsed out the texture name to just load a straight texture. It was ugly.

I ripped it out and wrote a new loader for the Half-Life version which loads all geometry, texture, and BSP data and uses the BSP data along with PVS data in the file for hidden surface removal.

The engine is here if your interested, all the code is in the SVN there, but you'll probably want to take a look at the 'old' branch in the SVN, it was before the BSP loader got ripped apart into more logical sections, in that version both loading and rendering are handled all in the BSPManager class:
http://sourceforge.net/projects/beng

You can contact me and discuss it all further if you want.
==============================
A Developers Blog | Dark Rock Studios - My Site
I've always thought Hammer was a pain in the butt to use. GTKRadiant 1.4.0 is still a favorite editor of mine.

I would say to go for Quake 3 because of the vast amount of documentation on the engine. Head on over to http://www.quakesrc.org/ to find more info on Quake development.

I'm currently working on a BSP loader myself for a C to C++ conversion of the Q3 engine. It hasn't been the easiest thing in the world so far but I wouldn't say it's overly difficult.

Good luck!
Quote:Original post by ImperfectFreak
I am trying to write a BSP loader, but I have a few different questions. Which would be the best version to write a loader for? One of the Quakes, or one of the Half Lifes? I am leaning towards Quake 3 because that is the one I have found the most information about.

Take Quake3 files: The map compiler and editor is still one of the best (and does not suck! The maps from Doom3 and Quake4 are made with it!).
I have tutorials finished at my website. Maybe it helps as a start :-)

I have a quite good working Quake4 Map viewer ready. I just need to clean up the code and prepare some documentation ;-)

Quote:Also, when I am loading the texture lump, I noticed a lot of texture names, but could only find a physical file for one.

These names are not texture names. They are names for materials (in Quake3-terminology: shaders), which can be thought of several textures and settings (alpha blending, testing, etc.) for a surface.

Quote:Are there any generic BSP files that include all of their textures with them?

No, the textures are separate, so you can reuse them on multiple maps.

--
Try Torque Constructor. Its CSG level format is really simple, and the tool is pretty good (and free to all, not just Torque owners).
I would recommend q3 bsp's. Mostly because Half Life's BSP texture's are actually stored directly into the .bsp file which can be a pain to extract. I wrote a bsp loader about a week ago and have been eager to see what other people think ive it so i will post it here.


//c_bsp_loader.h#pragma once#include <string>#include <d3d9.h>#include <d3dx9.h>#define D3DFVF_MAPVERTEX ( D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX2 )struct Map_Vertex{	float x, y, z;	D3DXVECTOR3 Normal;	float u, v;	float lu, lv;};struct Face{	int Texture; 		int Effect; 		int Type; 		int Vertex; 		int NumVertexes; 	int MeshVert; 		int NumMeshverts; 		int LmIndex; 		int LmStart[2]; 		int LmSize[2]; 		float LmOrigin[3]; 		float LmVecs[2][3]; 		float Normal[3];	int Size[2];};struct BSPVertex{	float Position[3];	float TexCoord[2][2];	float Normal[3];	unsigned char Color[4];};struct BSPTexture{	char Name[64];	int Flags;	int Contents;};struct BSPLightmap{	unsigned char LightMap[128][128][3];};struct Direntry{	int Offset;	int Length;};struct Header{	char Magic[4];	int Version;	Direntry Direntrys[17];};class BSP_Loader{	private:		   Header BSPHeader;		   BSPVertex *VertexPointer;		   BSPTexture *Textures;		   BSPLightmap *Lightmaps;		   Face *Faces;		   int NumVertex;		   int NumFaces;		   int NumTextures;		   int NumLightmaps;		   LPDIRECT3DVERTEXBUFFER9 Vertex_Buffer;		   LPDIRECT3DTEXTURE9 *BlitTextures;		   LPDIRECT3DTEXTURE9 *LightmapTextures;		   Map_Vertex *MapVertex;	public:		  void Load( char *Name, IDirect3DDevice9 *Device );		  void LoadBuffer( IDirect3DDevice9 *Device );		  void LoadBSPTexture( IDirect3DDevice9 *D3DDevice, std::wstring Name, IDirect3DTexture9 **TextureSource );		  void LoadTextures( IDirect3DDevice9 *Device );		  void LoadLightmaps ( IDirect3DDevice9 *Device );		  void Render( IDirect3DDevice9 *Device );		  void RenderFace( IDirect3DDevice9 *&Device, const int &Index );		  void ApplyGamma( unsigned char *pImage, int size, float facto );		  void InitVertexs( void );		  void DeInit( void );		  ~BSP_Loader( void );};//c_bsp_loader.cpp#pragma once#define _CRT_SECURE_NO_DEPRECATE#include <d3d9.h>#include <d3dx9.h>#include <atlbase.h>#include <fstream>#include "c_bsp_loader.h"#include "c_custom_matrix.h"void BSP_Loader::Load( char *Name, IDirect3DDevice9 *Device ){	FILE *MapFile = fopen( Name, "rb" );		if( MapFile == NULL )	{		MessageBox( NULL, L"Could Not Open Specified Map File", L"ERROR", MB_OK );		PostQuitMessage( 0 );	}			fread( &BSPHeader, sizeof(BSPHeader), 1, MapFile );		NumVertex = ( BSPHeader.Direntrys[10].Length / sizeof(BSPVertex) );	VertexPointer = new BSPVertex[NumVertex];	NumFaces = ( BSPHeader.Direntrys[13].Length / sizeof(Face) );	Faces = new Face[NumFaces];		NumTextures = ( BSPHeader.Direntrys[1].Length / sizeof(BSPTexture) );	Textures = new BSPTexture[NumTextures];		NumLightmaps = ( BSPHeader.Direntrys[14].Length / sizeof(BSPLightmap) );	Lightmaps = new BSPLightmap[NumLightmaps];			fseek( MapFile, BSPHeader.Direntrys[10].Offset, SEEK_SET );	fread( VertexPointer, sizeof(BSPVertex), NumVertex, MapFile );	fseek( MapFile, BSPHeader.Direntrys[13].Offset, SEEK_SET );	fread( Faces, sizeof(Face), NumFaces, MapFile );	fseek( MapFile, BSPHeader.Direntrys[1].Offset, SEEK_SET );	fread( Textures, sizeof(BSPTexture), NumTextures, MapFile );	fseek( MapFile, BSPHeader.Direntrys[14].Offset, SEEK_SET );	fread( Lightmaps, sizeof(BSPLightmap), NumLightmaps, MapFile );	fclose( MapFile );		InitVertexs();	LoadTextures( Device );	LoadLightmaps( Device );	LoadBuffer( Device );		return;}void BSP_Loader::ApplyGamma( unsigned char *pImage, int size, float factor ){	for(int i = 0; i < size / 3; i++, pImage += 3) 	{		float scale = 1.0f, temp = 0.0f;		float r = 0, g = 0, b = 0;		// extract the current RGB values		r = (float)pImage[0];		g = (float)pImage[1];		b = (float)pImage[2];		// Multiply the factor by the RGB values, while keeping it to a 255 ratio		r = r * factor / 255.0f;		g = g * factor / 255.0f;		b = b * factor / 255.0f;				// Check if the the values went past the highest value		if(r > 1.0f && (temp = (1.0f/r)) < scale) scale=temp;		if(g > 1.0f && (temp = (1.0f/g)) < scale) scale=temp;		if(b > 1.0f && (temp = (1.0f/b)) < scale) scale=temp;		// Get the scale for this pixel and multiply it by our pixel values		scale*=255.0f;				r*=scale;	g*=scale;	b*=scale;		// Assign the new gamma'nized RGB values to our image		pImage[0] = (byte)r;		pImage[1] = (byte)g;		pImage[2] = (byte)b;	}	return;}void BSP_Loader::LoadBuffer( IDirect3DDevice9 *Device ){	Device->CreateVertexBuffer( sizeof(BSPVertex) * NumVertex,                                0, D3DFVF_MAPVERTEX ,                                D3DPOOL_MANAGED, &Vertex_Buffer, NULL );	MapVertex = new Map_Vertex[NumVertex];	for( int X = 0; X < NumVertex; X++ )	{		MapVertex[X].x = VertexPointer[X].Position[0] / 2;		MapVertex[X].y = VertexPointer[X].Position[1] / 2;		MapVertex[X].z = VertexPointer[X].Position[2] / 2;		MapVertex[X].Normal = D3DXVECTOR3( VertexPointer[X].Normal[0],										   VertexPointer[X].Normal[1],										   VertexPointer[X].Normal[2] );				MapVertex[X].u = VertexPointer[X].TexCoord[1][1];		MapVertex[X].v = VertexPointer[X].TexCoord[1][0];		MapVertex[X].lu = VertexPointer[X].TexCoord[0][0];		MapVertex[X].lv = VertexPointer[X].TexCoord[0][1];	}	void *Pointer;		Vertex_Buffer->Lock( 0, sizeof(Map_Vertex) * NumVertex, (void**)&Pointer, 0 );	memcpy( Pointer, MapVertex, sizeof(Map_Vertex) * NumVertex );	Vertex_Buffer->Unlock();	return;}void BSP_Loader::InitVertexs( void ){	for( int X = 0; X < NumVertex; X++ )	{		float Temp = VertexPointer[X].Position[1];		VertexPointer[X].Position[1] = VertexPointer[X].Position[2];		VertexPointer[X].Position[2] = Temp;	}	return;}void BSP_Loader::Render( IDirect3DDevice9 *Device ){	Device->SetStreamSource( 0, Vertex_Buffer, 0, sizeof(Map_Vertex) );	Device->SetFVF( D3DFVF_MAPVERTEX );		Device->SetTextureStageState( 0, D3DTSS_COLOROP,  D3DTOP_MODULATE );	Device->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );	Device->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );	Device->SetTextureStageState( 0, D3DTSS_ALPHAOP,  D3DTA_DIFFUSE );	Device->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE );	Device->SetTextureStageState( 1, D3DTSS_COLOROP,  D3DTOP_MODULATE );	Device->SetTextureStageState( 1, D3DTSS_COLORARG1, D3DTA_TEXTURE );	Device->SetTextureStageState( 1, D3DTSS_COLORARG2, D3DTA_CURRENT );	Device->SetTextureStageState( 1, D3DTSS_ALPHAOP,  D3DTOP_SELECTARG1 );	Device->SetTextureStageState( 1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE );	Device->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR );	Device->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR );	Device->SetSamplerState( 0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR );	Device->SetSamplerState( 1, D3DSAMP_MINFILTER, D3DTEXF_LINEAR );	Device->SetSamplerState( 1, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR );	Device->SetSamplerState( 1, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR );	for( int X = 0; X < NumFaces; X++ )	{		RenderFace( Device, X );	}	Device->SetTextureStageState( 1, D3DTSS_COLOROP,  D3DTOP_DISABLE );	return;}void BSP_Loader::LoadTextures( IDirect3DDevice9 *Device ){	BlitTextures = new LPDIRECT3DTEXTURE9[NumTextures];	for( int X = 0; X < NumTextures; X++ )	{		USES_CONVERSION;		LoadBSPTexture( Device, A2W(Textures[X].Name), &BlitTextures[X] );	}	return;}void BSP_Loader::LoadLightmaps( IDirect3DDevice9 *Device ){	LightmapTextures = new LPDIRECT3DTEXTURE9[NumLightmaps];	 for(int i = 0; i < NumLightmaps; i++) 	   {		 ApplyGamma( &Lightmaps.LightMap[0][0][0],	                 128 * 128 * 3, 3 );         D3DXCreateTexture( Device, 128, 128, 1, 0, 						   D3DFMT_R8G8B8, D3DPOOL_MANAGED, &LightmapTextures );         D3DLOCKED_RECT lr;         LightmapTextures->LockRect(0, &lr, NULL, 0);         DWORD *tempImg = (DWORD*)lr.pBits;         if(!tempImg) continue;         int index = 0;         for(int k = 0; k < 128; k++)         {               for(int j = 0; j < 128; j++)               {				       tempImg[index++] = (Lightmaps.LightMap[j][k][0] << 16) |                                          (Lightmaps.LightMap[j][k][1] << 8) |                                           Lightmaps.LightMap[j][k][2];               }         }		 LightmapTextures->UnlockRect(0);	   }	 return;}void BSP_Loader::LoadBSPTexture( IDirect3DDevice9 *D3DDevice, std::wstring Name, IDirect3DTexture9 **TextureSource ){	std::wstring TempString;	TempString = Name + L".jpg";		if( FAILED( D3DXCreateTextureFromFile(D3DDevice, 		        TempString.c_str(),    			    TextureSource) ) )	{		TempString = Name + L".tga";				if( FAILED( D3DXCreateTextureFromFile(D3DDevice, 		        TempString.c_str(),    			    TextureSource) ) )		{			*TextureSource = NULL;		}	}	return;}void BSP_Loader::RenderFace( IDirect3DDevice9 *&Device, const int &Index ){	Face *FacePtr = &Faces[Index];	if( BlitTextures[FacePtr->Texture] )				Device->SetTexture( 1, BlitTextures[FacePtr->Texture] );	if( FacePtr->LmIndex != -1 )		Device->SetTexture( 0, LightmapTextures[FacePtr->LmIndex] );		switch( FacePtr->Type )	{		case 1:			Device->DrawPrimitive( D3DPT_TRIANGLEFAN, FacePtr->Vertex, FacePtr->NumVertexes - 2 ); 			break;	}				return;}void BSP_Loader::DeInit( void ){	delete [] VertexPointer;	delete [] MapVertex;	delete [] Faces;	delete [] Textures;	delete [] Lightmaps;	for( int X = 0; X < NumTextures; X++ )	{		if( BlitTextures[X] != NULL )			BlitTextures[X]->Release();	}	for( int X = 0; X < NumLightmaps; X++ )	{		if( LightmapTextures[X] != NULL )			LightmapTextures[X]->Release();	}	return;}BSP_Loader::~BSP_Loader( void ){	DeInit();	return;}		



Btw, the lightmap loading function was taken from the BSP loader at ultimategameprogramming.com. Halve fun.

FitFool

This topic is closed to new replies.

Advertisement