From 8d51e7e252ae367b86e79670c20d798eb8336073 Mon Sep 17 00:00:00 2001 From: Nico de Poel Date: Sat, 4 Feb 2023 13:55:24 +0100 Subject: [PATCH] Embed the texture atlas data inside the exported .ps1bsp file and name those files after the actual map. Makes it a bit easier to switch between maps for testing. --- main.cpp | 41 ++++++++++++++++++++++++++++++++++++++++- ps1bsp.h | 6 ++++++ texture.cpp | 6 +++++- 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/main.cpp b/main.cpp index ae77c9e..4af5709 100644 --- a/main.cpp +++ b/main.cpp @@ -15,6 +15,34 @@ template size_t writeMapData(const std::vector& data, ps1bsp return fwrite(data.data(), sizeof(TData), data.size(), f); } +size_t writeAtlasData(const char* timPath, ps1bsp_dentry_t& dentry, FILE* f) +{ + FILE* ftim; + fopen_s(&ftim, timPath, "rb"); + if (ftim == NULL) + return 0; + + fseek(ftim, 0, SEEK_END); + size_t numBytes = ftell(ftim); + fseek(ftim, 0, SEEK_SET); + + // Round the data size up to multiples of 4 bytes, for 32-bit alignment + size_t numWords = numBytes / 4; + if (numBytes % 4 != 0) + numWords++; + + unsigned long* timData = new unsigned long[numWords]; + fread(timData, sizeof(unsigned char), numBytes, ftim); + fclose(ftim); + + dentry.offset = (unsigned int)ftell(f); + dentry.size = numWords * 4; + + size_t written = fwrite(timData, sizeof(unsigned long), numWords, f); + delete[] timData; + return written; +} + static float computeFaceArea(const world_t* world, const face_t* face) { const plane_t* plane = &world->planes[face->plane_id]; @@ -50,7 +78,8 @@ int process_faces(const world_t* world, const TextureList& textures) { // Write some data to a file FILE* fbsp; - fopen_s(&fbsp, "test.ps1bsp", "wb"); + sprintf_s(path, _MAX_PATH, "%s.ps1bsp", world->name); + fopen_s(&fbsp, path, "wb"); if (!fbsp) return 0; @@ -62,6 +91,8 @@ int process_faces(const world_t* world, const TextureList& textures) ps1bsp_worldspawn_t outWorldSpawn = { 0 }; Tesselator tesselator(world); + + printf("Analyzing %d faces...\n", world->numFaces); // Convert faces defined by edges into faces defined by vertex indices std::vector outFaces; @@ -126,6 +157,8 @@ int process_faces(const world_t* world, const TextureList& textures) outFaces.push_back(outFace); } + printf("Tesselating faces...\n"); + std::vector outPolyVertices; std::vector outPolygons; @@ -254,6 +287,8 @@ int process_faces(const world_t* world, const TextureList& textures) outFace->numPolygons = (unsigned char)(outPolygons.size() - outFace->firstPolygon); } + printf("Converting data...\n"); + // Convert vertex data const auto& inVertices = tesselator.getVertices(); std::vector outVertices; @@ -356,8 +391,12 @@ int process_faces(const world_t* world, const TextureList& textures) outWorldSpawns.push_back(outWorldSpawn); + printf("Writing output file...\n"); + // Write collected data to file and update header info writeMapData(outWorldSpawns, outHeader.worldSpawn, fbsp); + sprintf_s(path, _MAX_PATH, "atlas-%s.tim", world->name); + writeAtlasData(path, outHeader.atlases, fbsp); writeMapData(outTextures, outHeader.textures, fbsp); writeMapData(outVertices, outHeader.vertices, fbsp); writeMapData(outPolygons, outHeader.polygons, fbsp); diff --git a/ps1bsp.h b/ps1bsp.h index 6e4eaed..545ed4c 100644 --- a/ps1bsp.h +++ b/ps1bsp.h @@ -29,6 +29,7 @@ typedef struct u_short version; ps1bsp_dentry_t worldSpawn; + ps1bsp_dentry_t atlases; ps1bsp_dentry_t textures; ps1bsp_dentry_t vertices; ps1bsp_dentry_t polygons; @@ -48,6 +49,11 @@ typedef struct unsigned char skyColor[4]; } ps1bsp_worldspawn_t; +typedef struct +{ + unsigned long timData[1]; // Variable length +} ps1bsp_atlas_t; + typedef struct { unsigned short tpage; // Texture page in PS1 VRAM (precalculated when generating the texture atlas) diff --git a/texture.cpp b/texture.cpp index a63c490..845a4d8 100644 --- a/texture.cpp +++ b/texture.cpp @@ -29,7 +29,7 @@ Keep in mind that the coordinates will be rounded down to the next lowest textur // So we desaturate the palette ahead of time to more closely match the original Quake's look. static void desaturate(const unsigned char inColor[3], unsigned char outColor[3]) { - double f = 0.15; // desaturate by 15% + double f = 0.1; // desaturate by 10% double L = 0.3 * inColor[0] + 0.6 * inColor[1] + 0.1 * inColor[2]; outColor[0] = (unsigned char)((double)inColor[0] + f * (L - inColor[0])); outColor[1] = (unsigned char)((double)inColor[1] + f * (L - inColor[1])); @@ -185,6 +185,8 @@ bool process_textures(const world_t* world, TextureList& outTextures) // TODO: r const auto max_bin = rectpack2D::rect_wh(1024, 256); // 8-bit textures take up half the horizontal space so this is 512x256 in practice, or a quarter of the PS1's VRAM allocation. const auto discard_step = -4; + printf("Finding best texture packing...\n"); + std::vector rectangles; // Try some texture packing and see if we fit inside the PS1's VRAM @@ -253,6 +255,8 @@ bool process_textures(const world_t* world, TextureList& outTextures) // TODO: r memset(outTim.imgData, 0, result_size.w * result_size.h * sizeof(unsigned char)); + printf("Constructing texture atlas...\n"); + // Try to construct the texture atlas, see what we get for (int texNum = 0; texNum < world->mipheader.numtex; ++texNum) {