#include "common.h" #include "bsp.h" #include "ps1types.h" #include "ps1bsp.h" #include "lighting.h" #include "texture.h" static char path[_MAX_PATH]; template size_t writeMapData(const std::vector& data, ps1bsp_dentry_t& dentry, FILE* f) { dentry.offset = (unsigned int)ftell(f); dentry.size = sizeof(TData) * data.size(); return fwrite(data.data(), sizeof(TData), data.size(), f); } static SVECTOR convertNormal(vec3_t normal) { SVECTOR outNormal; outNormal.vx = (short)(normal.x * 4096); outNormal.vy = (short)(normal.y * 4096); outNormal.vz = (short)(normal.z * 4096); outNormal.pad = 0; return outNormal; } static SVECTOR convertWorldPosition(vec3_t point) { SVECTOR outPoint; outPoint.vx = (short)(point.x * 4); outPoint.vy = (short)(point.y * 4); outPoint.vz = (short)(point.z * 4); outPoint.pad = 1; return outPoint; } static float computeFaceArea(const world_t* world, const face_t* face) { const plane_t* plane = &world->planes[face->plane_id]; // Construct a tangent and bitangent for the plane using the face's first vertex int edgeIdx = world->edgeList[face->ledge_id]; unsigned short vertIndex = edgeIdx > 0 ? world->edges[edgeIdx].vertex0 : world->edges[-edgeIdx].vertex1; const vertex_t* vertex = &world->vertices[vertIndex]; Vec3 refPoint = plane->normal * plane->dist; Vec3 tangent = (vertex->toVec() - refPoint).normalized(); Vec3 bitangent = plane->normal.crossProduct(tangent); // Project all face vertices onto the face's plane BoundBox bounds; for (int edgeListIdx = 0; edgeListIdx < face->ledge_num; ++edgeListIdx) { edgeIdx = world->edgeList[face->ledge_id + edgeListIdx]; vertIndex = edgeIdx > 0 ? world->edges[edgeIdx].vertex0 : world->edges[-edgeIdx].vertex1; vertex_t* vertex = &world->vertices[vertIndex]; Vec3 vec = vertex->toVec(); float x = tangent.dotProduct(vec); float y = bitangent.dotProduct(vec); bounds.includePoint(Vec3(x, y, 0)); } Vec3 extents = bounds.max - bounds.min; return extents.x * extents.y; } int process_faces(const world_t* world, const std::vector& textures) { // Write some data to a file FILE* fbsp; fopen_s(&fbsp, "test.ps1bsp", "wb"); if (!fbsp) return 0; ps1bsp_header_t outHeader = { 0 }; // Write an empty placeholder header first outHeader.version = 1; fwrite(&outHeader, sizeof(ps1bsp_header_t), 1, fbsp); auto edgeData = analyze_edges(world); // Convert vertex data (no vertex splitting yet) std::vector outVertices; for (unsigned short i = 0; i < world->numVertices; ++i) { vertex_t* inVertex = &world->vertices[i]; ps1bsp_vertex_t outVertex = { 0 }; // Ensure we don't overflow 16-bit short values. Most Quake maps will stay within these bounds so it *should* be fine (for now). if (inVertex->X > -8192 && inVertex->X < 8192 && inVertex->Y > -8192 && inVertex->Y < 8192 && inVertex->Z > -8192 && inVertex->Z < 8192) { outVertex.x = (short)(inVertex->X * 4); outVertex.y = (short)(inVertex->Y * 4); outVertex.z = (short)(inVertex->Z * 4); } else { printf("Error: vertices found outside of acceptable range: (%f, %f, %f)\n", inVertex->X, inVertex->Y, inVertex->Z); fclose(fbsp); return 0; } outVertices.push_back(outVertex); } // Convert faces defined by edges into faces defined by vertex indices std::vector outFaces; std::vector outFaceVertices; FaceBounds faceBounds; for (int faceIdx = 0; faceIdx < world->numFaces; ++faceIdx) { face_t* face = &world->faces[faceIdx]; const texinfo_t* texinfo = &world->texInfos[face->texinfo_id]; const miptex_t* miptex = &world->miptexes[texinfo->texture_id]; const ps1bsp_texture_t& ps1tex = textures[texinfo->texture_id]; ps1bsp_face_t outFace = { 0 }; outFace.planeId = face->plane_id; outFace.side = face->side; outFace.firstFaceVertex = (unsigned short)outFaceVertices.size(); outFace.textureId = (unsigned char)texinfo->texture_id; float minS = FLT_MAX, minT = FLT_MAX; float maxS = FLT_MIN, maxT = FLT_MIN; // Traverse the list of face edges to collect all of the face's vertices Vec3 vertexSum; BoundBox bounds; for (int edgeListIdx = 0; edgeListIdx < face->ledge_num; ++edgeListIdx) { int edgeIdx = world->edgeList[face->ledge_id + edgeListIdx]; unsigned short vertIndex = edgeIdx > 0 ? world->edges[edgeIdx].vertex0 : world->edges[-edgeIdx].vertex1; const vertex_t* vertex = &world->vertices[vertIndex]; Vec3 vertexPoint = vertex->toVec(); // Calculate texture UV bounds float s = (vertexPoint.dotProduct(texinfo->vectorS) + texinfo->distS) / miptex->width; float t = (vertexPoint.dotProduct(texinfo->vectorT) + texinfo->distT) / miptex->height; if (s > maxS) maxS = s; if (s < minS) minS = s; if (t > maxT) maxT = t; if (t < minT) minT = t; // Calculate bounding box of this face if (edgeListIdx == 0) bounds.init(vertexPoint); else bounds.includePoint(vertexPoint); // Sum all vertices to calculate an average center point vertexSum = vertexSum + vertexPoint; } // Go over the edges again to fudge some UVs for the vertices (this second pass is only necessary because we don't have texture tiling yet) for (int edgeListIdx = 0; edgeListIdx < face->ledge_num; ++edgeListIdx) { int edgeIdx = world->edgeList[face->ledge_id + edgeListIdx]; unsigned short vertIndex = edgeIdx > 0 ? world->edges[edgeIdx].vertex0 : world->edges[-edgeIdx].vertex1; const vertex_t* vertex = &world->vertices[vertIndex]; Vec3 vertexPoint = vertex->toVec(); ps1bsp_facevertex_t faceVertex = { 0 }; faceVertex.index = vertIndex; faceVertex.light = 0; // Calculate texture UVs float s = (vertexPoint.dotProduct(texinfo->vectorS) + texinfo->distS) / miptex->width; float t = (vertexPoint.dotProduct(texinfo->vectorT) + texinfo->distT) / miptex->height; s = (s - minS) / (maxS - minS); t = (t - minT) / (maxT - minT); // Rescale the UVs to the dimensions of the mipmap we've selected for our texture atlas faceVertex.u = (unsigned char)(s * (ps1tex.w - 1)); faceVertex.v = (unsigned char)(t * (ps1tex.h - 1)); outFaceVertices.push_back(faceVertex); } faceBounds[face] = bounds; // For visualizing and debugging lightmaps //if (face->ledge_num >= 10) // export_lightmap(world, face, bounds, faceIdx); outFace.numFaceVertices = (unsigned char)(outFaceVertices.size() - outFace.firstFaceVertex); outFace.center = convertWorldPosition(vertexSum / outFace.numFaceVertices); float area = computeFaceArea(world, face); outFace.center.pad = (short)(sqrt(area)); outFaces.push_back(outFace); } // Iterate over all faces again; now that we know the bounds of each face, we can calculate lighting for all of them for (int faceIdx = 0; faceIdx < world->numFaces; ++faceIdx) { face_t* face = &world->faces[faceIdx]; ps1bsp_face_t& outFace = outFaces[faceIdx]; // Sample lightmap contribution of this face on each vertex for (size_t faceVertIdx = 0; faceVertIdx < outFace.numFaceVertices; ++faceVertIdx) { ps1bsp_facevertex_t& faceVertex = outFaceVertices[outFace.firstFaceVertex + faceVertIdx]; const vertex_t* vertex = &world->vertices[faceVertex.index]; faceVertex.light = compute_faceVertex_light4(world, face, faceBounds, vertex->toVec()); faceVertex.light <<= 1; if (faceVertex.light > 255) faceVertex.light = 255; } } // Convert planes std::vector outPlanes; for (int planeIdx = 0; planeIdx < world->numPlanes; ++planeIdx) { plane_t* plane = &world->planes[planeIdx]; ps1bsp_plane_t outPlane = { 0 }; outPlane.normal = convertNormal(plane->normal); outPlane.dist = (short)(plane->dist * 4); outPlane.type = (short)plane->type; outPlanes.push_back(outPlane); } // Convert nodes std::vector outNodes; for (int nodeIdx = 0; nodeIdx < world->numNodes; ++nodeIdx) { node_t* node = &world->nodes[nodeIdx]; ps1bsp_node_t outNode; outNode.planeId = node->plane_id; outNode.children[0] = node->front; outNode.children[1] = node->back; outNode.firstFace = node->face_id; outNode.numFaces = node->face_num; outNodes.push_back(outNode); } // Convert leaves std::vector outLeaves; for (int leafIdx = 0; leafIdx < world->numLeaves; ++leafIdx) { dleaf_t* leaf = &world->leaves[leafIdx]; ps1bsp_leaf_t outLeaf; outLeaf.type = leaf->type; outLeaf.vislist = leaf->vislist; outLeaf.firstLeafFace = leaf->lface_id; outLeaf.numLeafFaces = leaf->lface_num; //outLeaf.center = convertWorldPosition(leaf->bound.getCenter()); outLeaves.push_back(outLeaf); } std::vector outLeafFaces(world->faceList, world->faceList + world->faceListLength); std::vector outVisData(world->visList, world->visList + world->visListLength); // Write collected data to file and update header info writeMapData(textures, outHeader.textures, fbsp); writeMapData(outVertices, outHeader.vertices, fbsp); writeMapData(outFaces, outHeader.faces, fbsp); writeMapData(outFaceVertices, outHeader.faceVertices, fbsp); writeMapData(outPlanes, outHeader.planes, fbsp); writeMapData(outNodes, outHeader.nodes, fbsp); writeMapData(outLeaves, outHeader.leaves, fbsp); writeMapData(outLeafFaces, outHeader.leafFaces, fbsp); writeMapData(outVisData, outHeader.visData, fbsp); // Write final header fseek(fbsp, 0, SEEK_SET); fwrite(&outHeader, sizeof(ps1bsp_header_t), 1, fbsp); fclose(fbsp); printf("PS1BSP: wrote %d vertices, %d faces, %d face verts, %d planes, %d nodes, %d leaves, %d leaf faces\n", outVertices.size(), outFaces.size(), outFaceVertices.size(), outPlanes.size(), outNodes.size(), outLeaves.size(), outLeafFaces.size()); return 1; } int process_bsp(const world_t *world) { // Test exporting texture data std::vector textures; if (!process_textures(world, textures)) { return 0; } // Inspect faces/edges data if (!process_faces(world, textures)) { return 0; } return 1; } int load_bsp(const char* bspname, world_t* world) { FILE* f; dheader_t* header = &world->header; world->name = bspname; sprintf_s(path, _MAX_PATH, "%s.bsp", bspname); fopen_s(&f, path, "rb"); if (f == NULL) return 0; fread(header, sizeof(dheader_t), 1, f); printf("Header model version: %d\n", header->version); // Load entities fseek(f, header->entities.offset, SEEK_SET); world->entitiesLength = header->entities.size + 1; world->entities = (char*)malloc(world->entitiesLength * sizeof(char)); if (world->entities == NULL) return 0; memset(world->entities, 0, world->entitiesLength * sizeof(char)); fread(world->entities, sizeof(char), world->entitiesLength, f); // Load textures mipheader_t* mipheader = &world->mipheader; fseek(f, header->miptex.offset, SEEK_SET); fread(&mipheader->numtex, sizeof(long), 1, f); mipheader->offset = (long*)malloc(mipheader->numtex * sizeof(long)); if (mipheader->offset == NULL) return 0; fread(mipheader->offset, sizeof(long), mipheader->numtex, f); world->miptexes = (miptex_t*)malloc(mipheader->numtex * sizeof(miptex_t)); if (world->miptexes == NULL) return 0; const int numMipLevels = 4; world->textures = (unsigned char**)malloc(mipheader->numtex * numMipLevels * sizeof(unsigned char*)); if (world->textures == NULL) return 0; memset(world->textures, 0, mipheader->numtex * numMipLevels * sizeof(unsigned char*)); for (int texNum = 0; texNum < mipheader->numtex; ++texNum) { miptex_t* miptex = &world->miptexes[texNum]; unsigned long miptexOffset = header->miptex.offset + mipheader->offset[texNum]; fseek(f, miptexOffset, SEEK_SET); fread(miptex, sizeof(miptex_t), 1, f); for (int mipLevel = 0; mipLevel < numMipLevels; ++mipLevel) { unsigned long mipOffset = *(&miptex->offset1 + mipLevel); fseek(f, miptexOffset + mipOffset, SEEK_SET); size_t numBytes = (miptex->width * miptex->height) >> mipLevel; unsigned char* texBytes = (unsigned char*)malloc(sizeof(unsigned char) * numBytes); if (texBytes == NULL) return 0; fread(texBytes, sizeof(unsigned char), numBytes, f); world->textures[texNum * numMipLevels + mipLevel] = texBytes; } } // Load planes world->numPlanes = header->planes.size / sizeof(plane_t); world->planes = (plane_t*)malloc(header->planes.size); if (world->planes == NULL) return 0; fseek(f, header->planes.offset, SEEK_SET); fread(world->planes, sizeof(plane_t), world->numPlanes, f); // Load vertices world->numVertices = header->vertices.size / sizeof(vertex_t); world->vertices = (vertex_t*)malloc(header->vertices.size); if (world->vertices == NULL) return 0; fseek(f, header->vertices.offset, SEEK_SET); fread(world->vertices, sizeof(vertex_t), world->numVertices, f); // Load edges world->numEdges = header->edges.size / sizeof(edge_t); world->edges = (edge_t*)malloc(header->edges.size); if (world->edges == NULL) return 0; fseek(f, header->edges.offset, SEEK_SET); fread(world->edges, sizeof(edge_t), world->numEdges, f); world->edgeListLength = header->ledges.size / sizeof(int); world->edgeList = (int*)malloc(header->ledges.size); if (world->edgeList == NULL) return 0; fseek(f, header->ledges.offset, SEEK_SET); fread(world->edgeList, sizeof(int), world->edgeListLength, f); // Load texture info world->numTexInfos = header->texinfo.size / sizeof(texinfo_t); world->texInfos = (texinfo_t*)malloc(header->texinfo.size); if (world->texInfos == NULL) return 0; fseek(f, header->texinfo.offset, SEEK_SET); fread(world->texInfos, sizeof(texinfo_t), world->numTexInfos, f); // Load faces world->numFaces = header->faces.size / sizeof(face_t); world->faces = (face_t*)malloc(header->faces.size); if (world->faces == NULL) return 0; fseek(f, header->faces.offset, SEEK_SET); fread(world->faces, sizeof(face_t), world->numFaces, f); world->faceListLength = header->lface.size / sizeof(unsigned short); world->faceList = (unsigned short*)malloc(header->lface.size); if (world->faceList == NULL) return 0; // Load visibility list fseek(f, header->lface.offset, SEEK_SET); fread(world->faceList, sizeof(unsigned short), world->faceListLength, f); world->visListLength = header->visilist.size / sizeof(unsigned char); world->visList = (unsigned char*)malloc(header->visilist.size); if (world->visList == NULL) return 0; fseek(f, header->visilist.offset, SEEK_SET); fread(world->visList, sizeof(unsigned char), world->visListLength, f); // Load nodes world->numNodes = header->nodes.size / sizeof(node_t); world->nodes = (node_t*)malloc(header->nodes.size); if (world->nodes == NULL) return 0; fseek(f, header->nodes.offset, SEEK_SET); fread(world->nodes, sizeof(node_t), world->numNodes, f); // Load leaves world->numLeaves = header->leaves.size / sizeof(dleaf_t); world->leaves = (dleaf_t*)malloc(header->leaves.size); if (world->leaves == NULL) return 0; fseek(f, header->leaves.offset, SEEK_SET); fread(world->leaves, sizeof(dleaf_t), world->numLeaves, f); // Load lightmaps world->lightmapLength = header->lightmaps.size / sizeof(unsigned char); world->lightmap = (unsigned char*)malloc(header->lightmaps.size); if (world->lightmap == NULL) return 0; fseek(f, header->lightmaps.offset, SEEK_SET); fread(world->lightmap, sizeof(unsigned char), world->lightmapLength, f); fclose(f); return 1; } void free_bsp(world_t* world) { free(world->lightmap); free(world->leaves); free(world->nodes); free(world->visList); free(world->texInfos); free(world->faces); free(world->faceList); free(world->edges); free(world->edgeList); free(world->vertices); free(world->planes); for (int i = 0; i < world->mipheader.numtex; ++i) { free(world->textures[i]); } free(world->textures); free(world->miptexes); free(world->mipheader.offset); free(world->entities); } int main(int argc, char** argv) { world_t world = { 0 }; if (!load_bsp(argv[1], &world)) return 1; int result = process_bsp(&world); free_bsp(&world); return !result; }