# Holes when rendering Quake 3 maps?

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

## Recommended Posts

Hi, I've got a strange problem in my engine. I can load and render some quake 3 maps without any problems. At the moment I'm not rendering meshes, patches or billboards; only faces are loaded. While some maps render fine, others render with holes I can't find anything in any of the Q3 map specs that explains this, anyone have any ideas? Here is a "normal" map: and here is a "holey" map: finally here is the code that takes a Quake map polygon and splits it into triangles:
for(vector<stQ3Face>::const_iterator it = faces.begin();
it != faces.end(); ++it) {

//At the moment only handle normal polygons
if ((*it).type != 1) {
continue;
}

int end = (*it).startVertexIndex + (*it).totalVertices;

for (int i = (*it).startVertexIndex; i < end - 1; i++) {
WorldTriangle newTriangle;
newTriangle.m_Index[0] = (*it).startVertexIndex;
newTriangle.m_Index[1] = i + 1;
newTriangle.m_Index[2] = i;

newTriangle.m_TexCoords[0] = vertices[newTriangle.m_Index[0]].texCoord;
newTriangle.m_TexCoords[1] = vertices[newTriangle.m_Index[1]].texCoord;
newTriangle.m_TexCoords[2] = vertices[newTriangle.m_Index[2]].texCoord;

newTriangle.m_TextureName = path(textures[(*it).texID].file).leaf();

slVec3 vec1, vec2;
vec1 = m_VertexData[newTriangle.m_Index[1]].m_Pos - m_VertexData[newTriangle.m_Index[0]].m_Pos;
vec2 = m_VertexData[newTriangle.m_Index[2]].m_Pos - m_VertexData[newTriangle.m_Index[0]].m_Pos;
slVec3Cross(&newTriangle.m_FaceNormal, &vec1, &vec2);
slVec3Normalize(&newTriangle.m_FaceNormal, &newTriangle.m_FaceNormal);

m_TriangleData.push_back(newTriangle);
}
}



##### Share on other sites
You said you weren't rendering Bezier patches - could the holes be places where patches would normally be rendered?

##### Share on other sites
No I don't think so, it's as if every other triangle in the mesh is missing. I thought it could be to do with the winding of the vertices alternating but disabling backface culling does nothing.

##### Share on other sites
Are you sure your loop condition (< end -1) is correct? Shouldn't that be < end ?

##### Share on other sites
Are you sure that you turn the polygons into triangles correctly?
Perhaps they're stored as trianglestrips sometimes, just a guess.

##### Share on other sites
Why do you split the triangles from the maps into triangles? This makes no real sense, except for subdivision.

I just had a look at my Quake3 loader and it doesnot load any "polygons" or triangles strips from the files. Only the triangles are loaded and used for rendering and this is the way to go... there are no holes or any other render errors in my applications with this.

I had a very short look at your code, so only another small question: Why are you calculating normal vectors? These are already saved in the map file...

##### Share on other sites
No, notice I use i + 1 as an index. The end - 1 prevents me reading past the end of the vertex array.

##### Share on other sites
Oops, shoulda refreshed...

OK, my engine internally only deals with triangles internally. Whereas Quake 3 represents polys as triangle fans. That is why I break them down into triangles they are then easier to deal with. I generate the normal because as far as I know there is no guarantee that all triangles in the fan will lie on the same plane (although it is likely).

##### Share on other sites
It seems to me the problem is with Quake3's shaders. Did you check that all of the 'textures' specified in the .bsp are loaded correctly? Because in q3 maps some textures are not really textures on the disk, but rather 'shaders' which describe special texture properties like blending, animation, etc.

Ivan

##### Share on other sites
Hmm, well that would explain why I am always missing a load of textures from the maps I'm downloading, but I doubt that the geometry would be entirely missing. My engine just binds a texture ID of 0 (i.e. draws it white) if the material is missing.

##### Share on other sites
Quote:
 Original post by KazadeNo, notice I use i + 1 as an index. The end - 1 prevents me reading past the end of the vertex array.

Uhm, yeah but let's say startVertexIndex is 10 and totalVertices is 120 then end would be 130. If you loop from startVertexIndex to i < end - 1 then the maximum value i will ever have is end - 1 - 1 = 128. Shouldn't that be 129?

##### Share on other sites
Quote:
 Original post by KazadeOops, shoulda refreshed...OK, my engine internally only deals with triangles internally. Whereas Quake 3 represents polys as triangle fans. That is why I break them down into triangles they are then easier to deal with.

This is wrong since the very early days of Quake3! After the first point release (or so) Quake3 uses only the triangles specified in the map file and they abandoned the triangle fans/strips/whatever. There is a comment somewhere in the Quake3 map compiler, that triangle fans are broken and should not be used.
Update your loader to use the raw triangle data from the map file instead and I am really sure your holes will disappear :-)

I have written several articles (floating around somewhere in the net) about Rendering Quake 3 Data, including maps, models and their shader scripts. The one which might be interesting for you is Loading and Rendering Quake3 Maps, over at viCampus.

Loading the texture files specified from the name in the map file is neither correct nor wrong. The correct way is to create a default shader script and use the name from the map as diffuse texture. This was extended to template scripts in Doom 3 and later, btw.

If you have any questions or problems, feel free to ask :-)

##### Share on other sites
Wow, thanks for that info! All of the sample code and articles I found said they used triangle fans!

Well I'll do that tonight. I'm not writing a complete Q3 map renderer, I'm just really using the BSP for geometry which is why I'm just reading the texture name, I just use it as an key for my own material system.

Thanks for all the information, that article looks really useful.

##### Share on other sites
Just in case anyone else has the same problem. Switching from the triangle fans to triangles fixed the problem. I had no idea that there were 2 sets of indices. [smile]

##### Share on other sites
Hi, I also have the same problem with rendering certain Quake3 maps, I'm sure its the same thing causing it and am currently trying to swap over to Triangles but I'm having trouble working out what to change in my code to do that correctly, this is what I think is the relevant section of my current code:

// If we're not using VBOs.if(!GLEE_ARB_vertex_buffer_object || map->mVertexBufferAssigned == false){	// Set the array of vertexes to draw.	glVertexPointer(3, GL_FLOAT, sizeof(Q3Vertex), &(map->mVertexes[0].position));}// If we're using VBOs.else{	// Bind the VBO to use.	glBindBuffer(GL_ARRAY_BUFFER, map->mVBOVertex);	glVertexPointer(3, GL_FLOAT, 0, 0);}// Draw all of the polygon elements.glDrawArrays(GL_TRIANGLE_FAN, curFace.vertex, curFace.numVertices);

I've tried replacing GL_TRIANGLE_FAN with GL_TRIANGLES and that didn't work (all maps became holey), so I'm unsure as to what to change without resorting to trial and error to fix it. Can anyone help? Thanks! :) I apologise if its really obvious how to do this, I'm still fairly new to OpenGL.

##### Share on other sites
Heres how i did it in d3d.

void Mesh_BSP::Draw( void ){	Globals::D3DDevice->SetStreamSource( 0, Vertex_Buffer, 0, sizeof(Map_Vertex) );	Globals::D3DDevice->SetFVF( FVF_MapVertex );		Globals::D3DDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE );	Globals::D3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );	Globals::D3DDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );	Globals::D3DDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTA_DIFFUSE );	Globals::D3DDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE );	Globals::D3DDevice->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_MODULATE );	Globals::D3DDevice->SetTextureStageState( 1, D3DTSS_COLORARG1, D3DTA_TEXTURE );	Globals::D3DDevice->SetTextureStageState( 1, D3DTSS_COLORARG2, D3DTA_CURRENT );	Globals::D3DDevice->SetTextureStageState( 1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1 );	Globals::D3DDevice->SetTextureStageState( 1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE );	Globals::D3DDevice->SetRenderState( D3DRS_CULLMODE, true );		for( int i = 0; i < NumFaces; i++ )		RenderFace( i );		Globals::D3DDevice->SetRenderState( D3DRS_CULLMODE, false );		Globals::D3DDevice->SetTextureStageState( 1, D3DTSS_COLOROP,  D3DTOP_DISABLE );}void Mesh_BSP::RenderFace( const int &Index ){	BSPFace *FacePtr = &Faces[Index];	if( FacePtr->LmIndex > 0 )		Globals::D3DDevice->SetTexture( 0, LightmapTextures[FacePtr->LmIndex] );	if( BlitTextures[FacePtr->Texture] )		Globals::D3DDevice->SetTexture( 1, BlitTextures[FacePtr->Texture] );		switch( FacePtr->Type )	{		case BSP_POLYGON:			Globals::D3DDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, FacePtr->Vertex, FacePtr->NumVertexes - 2 );			break;		case BSP_PATCH:			break;		case BSP_MESH:			break;	}}

##### Share on other sites
Quote:
 Original post by S8NI've tried replacing GL_TRIANGLE_FAN with GL_TRIANGLES and that didn't work (all maps became holey)

The reason is that there are 2 sets of indices...

The way that I was originally doing it was reading the startVertex and looping to startVertex + numVertices drawing a trangle fan.

The proper way to do it is to load the face indices lump, which is a separate chunk of the file to read. These indices describe the polygons as triangles rather than triangle fans.

Take a look at the link Enrico posted above. It describes how to load and render these indices, there is also some sample code at the bottom. If you still have no luck I will post my new code tonight.

##### Share on other sites
I think I've worked it out now after looking at the links and the OpenGL API more carefully. [smile] This is what I changed my code to:

// If we're not using VBOs.if(!GLEE_ARB_vertex_buffer_object || map->mVertexBufferAssigned == false){	// Set the array of vertexes to draw.	glVertexPointer(3, GL_FLOAT, sizeof(Q3Vertex), &(map->mVertexes[curFace.vertex].position));}// If we're using VBOs.else{	// Bind the VBO to use.	glBindBuffer(GL_ARRAY_BUFFER, map->mVBOVertex);	glVertexPointer(3, GL_FLOAT, 0, BUFFER_OFFSET(sizeof(float) * 3 * curFace.vertex));}// Draw all of the polygon elements.glDrawElements(GL_TRIANGLES, curFace.numMeshVerts, GL_UNSIGNED_INT, &(map->mMeshVerts[curFace.meshVert]));
I also had to change my texture coord pointers in a similar way. My Face Indices are called Mesh Verts as that is what the Q3 BSP documentation I was using called it when I went to store that lump. The map in question that I tried is rendering much more accurately now, there are still a couple of holes in the map but I think that is probably because I haven't coded any rendering for curved surfaces. [grin] Thanks for the help!

##### Share on other sites
Quote:
 Original post by S8NThe map in question that I tried is rendering much more accurately now, there are still a couple of holes in the map but I think that is probably because I haven't coded any rendering for curved surfaces. [grin] Thanks for the help!

I have some more articles on my hard disk, waiting for publishing. The article list includes Quake3's curved surfaces, their shader scripts, model rendering and some more... However, they all need some kind of polishing or are just not finished :-(