Quake III BSP PVS
Posted: Tue Mar 20, 2012 5:50 pm
Lately I've been studying a lot of Id Software's work, and I started on Quake III's BSP maps.
I've got pretty much everything to work fine, I've got it rendering with frustum culling / material sorting and even lightmaps. But the PVS (Potentially visible set?) is being a real pain.
From what I understand, a cluster is a group of leaves, and each cluster has visibility information for each other cluster?
So we can take two leaves, get the cluster index A and cluster index B and check if A is visible from B.
I've found a few guides documenting the Q3's BSP format, and here is what I've got so far:
Loading the PVS:
I've added output from within this if statement, it's getting executed so there is definitely visibility information available (I've heard there isn't always).
To get the current leaf the camera is located within, I use this function:
^This function outputs extremely large values, or mostly -1. I think this could be the problem? :/
Also, one more function of relevance:
This checks if the cluster b is visible from cluster a.
I checked the if statement, VisData is definitely not NULL and a is sometimes -1 but I'm sure that's not supposed to happen.
And I render the map like so:
I know this isn't a very basic question, but if anyone can offer any advice as to where I may be going wrong, or more precious documentation I would MUCH appreciate it!
Also, I know Quake III uses X/Z/Y coords instead of X/Y/Z coords, this gets handled when the map is loaded.
And it's also worth mentioning, I know Quake III is GPL'd now, but the code is so unclear I can barely make any sense out of it.
Thanks!
I've got pretty much everything to work fine, I've got it rendering with frustum culling / material sorting and even lightmaps. But the PVS (Potentially visible set?) is being a real pain.
From what I understand, a cluster is a group of leaves, and each cluster has visibility information for each other cluster?
So we can take two leaves, get the cluster index A and cluster index B and check if A is visible from B.
I've found a few guides documenting the Q3's BSP format, and here is what I've got so far:
Loading the PVS:
Code: Select all
fseek(file, lumps[LMP_VISDATA].offset, SEEK_SET);
if ( lumps[LMP_VISDATA].size > 0 ) {
VisData = new bsp_visdata_t;
fread(&VisData->numVecs, sizeof(int), 1, file);
fread(&VisData->vecSize, sizeof(int), 1, file);
VisData->bytes = new unsigned char[VisData->numVecs * VisData->vecSize];
fread(VisData->bytes, sizeof(unsigned char) * (VisData->numVecs * VisData->vecSize), 1, file);
}
To get the current leaf the camera is located within, I use this function:
Code: Select all
int Eternal::Scene::BspMap::FindLeaf(Core::Vector3 &v) const
{
int index = 0;
bsp_plane_t *plane;
bsp_node_t *node;
while(index >= 0) {
node = &Nodes[index];
plane = &Planes[node->plane];
const float dist = plane->normal[0] * v.x +
plane->normal[1] * v.y +
plane->normal[2] * v.z - plane->d;
if ( dist >= 0.0f ) {
index = node->front;
}
else {
index = node->back;
}
}
return -index - 1;
}
Also, one more function of relevance:
Code: Select all
int Eternal::Scene::BspMap::ClusterVisible(int a, int b) const
{
// a < 0 (camera out of map)
// VisData = NULL ( No visibility information)
if(VisData == NULL || a < 0) {
return 1;
}
int i = (a * VisData->vecSize) + (b >> 3);
unsigned char visSet = VisData->bytes[i];
return (visSet & (1 << (b & 7))) != 0;
}
This checks if the cluster b is visible from cluster a.
I checked the if statement, VisData is definitely not NULL and a is sometimes -1 but I'm sure that's not supposed to happen.
And I render the map like so:
Code: Select all
// Take a copy of the camera position
Core::Vector3 pos = *Camera->GetPosition();
// Find the current leaf we're in
const int curLeaf = FindLeaf(pos);
for ( unsigned int l = 0;l < i_NumLeaves;l++ ) {
/* This doesn't work- It's only ever said EVERYTHING is visible or NOTHING is visible. */
if ( !ClusterVisible(Leaves[curLeaf].cluster, Leaves[l].cluster ) ) {
continue;
}
/* Checks if the leaf is in the frustum */
if ( !Frustum->BoxInFrustum((float)Leaves[l].min[0], (float)Leaves[l].min[1], (float)Leaves[l].min[2],
(float)Leaves[l].max[0], (float)Leaves[l].max[1], (float)Leaves[l].max[2]) )
{
continue;
}
numFaces = Leaves[l].numleaffaces;
while(numFaces--) {
/* This just adds indices to the faces needed to be drawn */
n = LeafFaces[Leaves[l].leafface + numFaces];
if ( drawnFaces[n] == true ) {
continue;
}
const int c = listIndices[Faces[n].glTexID].count;
listIndices[Faces[n].glTexID].Indices[c] = n;
listIndices[Faces[n].glTexID].count++;
drawnFaces[n] = true;
}
}
Also, I know Quake III uses X/Z/Y coords instead of X/Y/Z coords, this gets handled when the map is loaded.
And it's also worth mentioning, I know Quake III is GPL'd now, but the code is so unclear I can barely make any sense out of it.
Thanks!