diff --git a/bsp.h b/bsp.h index bacce60..6618c8b 100644 --- a/bsp.h +++ b/bsp.h @@ -44,6 +44,15 @@ typedef struct // Vector or Position scalar_t z; // vertical } vec3_t; +typedef struct +{ + vec3_t normal; // Vector orthogonal to plane (Nx,Ny,Nz) + // with Nx2+Ny2+Nz2 = 1 + scalar_t dist; // Offset to plane, along the normal vector. + // Distance from (0,0,0) to the plane + long type; // Type of plane, depending on normal vector. +} plane_t; + typedef struct // Bounding Box, Float values { vec3_t min; // minimum values of X,Y,Z @@ -142,6 +151,9 @@ typedef struct miptex_t* miptexes; unsigned char** textures; + int numPlanes; + plane_t* planes; + int numVertices; vertex_t* vertices; @@ -149,7 +161,7 @@ typedef struct edge_t* edges; int edgeListLength; - unsigned short* edgeList; + int* edgeList; int numFaces; face_t* faces; @@ -157,6 +169,9 @@ typedef struct int faceListLength; unsigned short* faceList; + int visListLength; + unsigned char *visList; + int numNodes; node_t* nodes; diff --git a/main.cpp b/main.cpp index 10d7abd..fc5a7cd 100644 --- a/main.cpp +++ b/main.cpp @@ -37,8 +37,10 @@ int process_textures(const world_t* world) for (int texNum = 0; texNum < world->mipheader.numtex; ++texNum) { miptex_t *miptex = &world->miptexes[texNum]; + if (miptex->name[0] == '\0') // Weird edge case on N64START.bsp, corrupt data perhaps? + miptex->width = miptex->height = 0; - //printf("Texture %d (%dx%d): %.16s\n", texNum, miptex.width, miptex.height, miptex.name); + //printf("Texture %d (%dx%d): %.16s\n", texNum, miptex->width, miptex->height, miptex->name); // Shrink the larger textures, but keep smaller ones at their original size int ps1mip = miptex->width > 64 || miptex->height > 64 ? 1 : 0; @@ -72,6 +74,8 @@ int process_textures(const world_t* world) for (int texNum = 0; texNum < world->mipheader.numtex; ++texNum) { miptex_t* miptex = &world->miptexes[texNum]; + if (miptex->name[0] == '\0') // Weird edge case on N64START.bsp, corrupt data perhaps? + continue; char* outName = miptex->name; if (*outName == '*' || *outName == '+') @@ -94,7 +98,7 @@ int process_textures(const world_t* world) const auto& rectangle = rectangles[texNum]; if (miptex->width >> mipLevel == rectangle.w) // This is the mip level we've previously decided we want for our PS1 atlas { - //printf("Writing texture %s mip %d to position: (%d, %d) w = %d, h = %d\n", miptex.name, mipLevel, rectangle.x, rectangle.y, rectangle.w, rectangle.h); + //printf("Writing texture %s mip %d to position: (%d, %d) w = %d, h = %d\n", miptex->name, mipLevel, rectangle.x, rectangle.y, rectangle.w, rectangle.h); for (int y = 0; y < rectangle.h; ++y) { memcpy_s(atlas + ((rectangle.y + y) * result_size.w + rectangle.x), rectangle.w * sizeof(unsigned char), texBytes + (y * rectangle.w), rectangle.w * sizeof(unsigned char)); @@ -146,6 +150,20 @@ typedef struct unsigned short index; } vertexref_t; +// Determines a floating origin for the given leaf +static void leaf_zone(const dleaf_t* leaf, short zone[3]) +{ + const unsigned short mask = 0xFE00; // Zero out the 9 least significant bits + + short midX = (leaf->bound.min[0] + leaf->bound.max[0]) / 2; + short midY = (leaf->bound.min[1] + leaf->bound.max[1]) / 2; + short midZ = (leaf->bound.min[2] + leaf->bound.max[2]) / 2; + + zone[0] = midX & mask; + zone[1] = midY & mask; + zone[2] = midZ & mask; +} + int process_faces(const world_t* world) { // Write some data to a file @@ -158,21 +176,21 @@ int process_faces(const world_t* world) fwrite(&outHeader, sizeof(ps1bsp_header_t), 1, fbsp); // TODO: convert vertices and group them by material properties (texture, lightmap) and floating origin zone (based on leaf data), duplicate where necessary - - // Organize vertex indices so we can shuffle them around and duplicate them, while keeping track of which vertex is where - std::unordered_map vertexRefs; - for (unsigned short i = 0; i < world->header.vertices.size / sizeof(vertex_t); ++i) + short zone[3]; + for (int leafIdx = 0; leafIdx < world->numLeaves; ++leafIdx) { - vertex_t* v = &world->vertices[i]; - vertexRefs[v] = vertexref_t{ v, i }; + dleaf_t* leaf = &world->leaves[leafIdx]; + leaf_zone(leaf, zone); + //printf("Leaf %d zone (%d, %d, %d) %d face(s)\n", leafIdx, zone[0], zone[1], zone[2], leaf->lface_num); } // Write vertex data to a file (no vertex splitting yet) - for (unsigned short i = 0; i < world->header.vertices.size / sizeof(vertex_t); ++i) + for (unsigned short i = 0; i < world->numVertices; ++i) { // TODO: we should respect the ordering from vertexRef->index here but meh, problem for later 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); @@ -184,39 +202,66 @@ int process_faces(const world_t* world) fwrite(&outVertex, sizeof(ps1bsp_vertex_t), 1, fbsp); } - outHeader.numVertices = (unsigned short)(world->header.vertices.size / sizeof(vertex_t)); + + std::vector outTriangles; + std::vector outFaces; - std::vector faceVerts; + std::vector faceVertIndices; for (int faceIdx = 0; faceIdx < world->numFaces; ++faceIdx) { face_t* face = &world->faces[faceIdx]; - + + // Traverse the list of face edges to collect all of the face's vertices for (int edgeListIdx = 0; edgeListIdx < face->ledge_num; ++edgeListIdx) { - short edgeIdx = world->edgeList[face->ledge_id + edgeListIdx]; + int edgeIdx = world->edgeList[face->ledge_id + edgeListIdx]; - int vertIndex = edgeIdx > 0 ? + unsigned short vertIndex = edgeIdx > 0 ? world->edges[edgeIdx].vertex0 : world->edges[-edgeIdx].vertex1; - vertex_t* v = &world->vertices[vertIndex]; - faceVerts.push_back(&vertexRefs[v]); + faceVertIndices.push_back(vertIndex); } - printf("Face %d: %d vertices\n", faceIdx, faceVerts.size()); + ps1bsp_face_t outFace = { 0 }; + outFace.firstTriangleId = outTriangles.size(); + + //printf("Face %d: %d vertices\n", faceIdx, faceVerts.size()); // Triangulate face into polygons (triangle fan, the naive method) + // TODO better method: generate a quad strip topology + ps1bsp_triangle_t outTriangle; + outTriangle.vertex0 = faceVertIndices[0]; + for (int faceVertIdx = 1; faceVertIdx < faceVertIndices.size() - 1; ++faceVertIdx) + { + outTriangle.vertex1 = faceVertIndices[faceVertIdx]; + outTriangle.vertex2 = faceVertIndices[faceVertIdx + 1]; + + outTriangles.push_back(outTriangle); + } - // Write polygon and face data to a file + outFace.numTriangles = outTriangles.size() - outFace.firstTriangleId; + outFaces.push_back(outFace); - faceVerts.clear(); + faceVertIndices.clear(); } + // Write triangle and face data to file + fwrite(outTriangles.data(), sizeof(ps1bsp_triangle_t), outTriangles.size(), fbsp); + fwrite(outFaces.data(), sizeof(ps1bsp_face_t), outFaces.size(), fbsp); + + // Update header information + outHeader.numVertices = world->numVertices; + outHeader.numTriangles = outTriangles.size(); + outHeader.numFaces = outFaces.size(); + // Write final header fseek(fbsp, 0, SEEK_SET); fwrite(&outHeader, sizeof(ps1bsp_header_t), 1, fbsp); fclose(fbsp); + printf("PS1BSP: wrote %d vertices, %d triangles, %d faces\n", outHeader.numVertices, outHeader.numTriangles, outHeader.numFaces); + return 1; } @@ -317,6 +362,15 @@ int load_bsp(const char* bspname, world_t* world) 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); @@ -336,13 +390,13 @@ int load_bsp(const char* bspname, world_t* world) fseek(f, header->edges.offset, SEEK_SET); fread(world->edges, sizeof(edge_t), world->numEdges, f); - world->edgeListLength = header->ledges.size / sizeof(unsigned short); - world->edgeList = (unsigned short*)malloc(header->ledges.size); + 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(unsigned short), world->edgeListLength, f); + fread(world->edgeList, sizeof(int), world->edgeListLength, f); // Load faces world->numFaces = header->faces.size / sizeof(face_t); @@ -358,9 +412,18 @@ int load_bsp(const char* bspname, world_t* world) 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); @@ -387,12 +450,14 @@ void free_bsp(world_t* world) { free(world->leaves); free(world->nodes); + free(world->visList); 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) { diff --git a/ps1bsp.h b/ps1bsp.h index d3086cb..34490e9 100644 --- a/ps1bsp.h +++ b/ps1bsp.h @@ -49,11 +49,6 @@ typedef struct unsigned char r : 5; unsigned char g : 5; unsigned char b : 5; - -#ifdef _WIN32 - // Extra fields used for administration during the conversion process - unsigned short index; -#endif } ps1bsp_vertex_t; // Instead of edges as in the original BSP format, we store triangles for easy consumption by the PS1 @@ -67,8 +62,8 @@ typedef struct typedef struct { - unsigned short firstTriangleId; - unsigned short numTriangle; + unsigned short firstTriangleId; // TODO: could also just do first-index, num-indices here. No real need for a triangle_t struct. + unsigned short numTriangles; unsigned short firstQuadId; // For if/when we decide to add quads to the mix unsigned short numQuads;