Sign in to follow this  

Attempting a BSP loader

This topic is 3846 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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?

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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!

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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[i].LightMap[0][0][0],
128 * 128 * 3, 3 );

D3DXCreateTexture( Device, 128, 128, 1, 0,
D3DFMT_R8G8B8, D3DPOOL_MANAGED, &LightmapTextures[i] );

D3DLOCKED_RECT lr;
LightmapTextures[i]->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[i].LightMap[j][k][0] << 16) |
(Lightmaps[i].LightMap[j][k][1] << 8) |
Lightmaps[i].LightMap[j][k][2];
}
}
LightmapTextures[i]->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

Share this post


Link to post
Share on other sites

This topic is 3846 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this