From 9f9bf49a86b6070be02ab2b28add4a94fad6167a Mon Sep 17 00:00:00 2001 From: Nico de Poel Date: Sun, 15 Jan 2023 21:42:25 +0100 Subject: [PATCH] Sample lightmaps to accumulate and average lighting values per vertex, then store those in the exported BSP file. --- bsp.h | 42 ++++++++++++++++++++-- main.cpp | 104 ++++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 126 insertions(+), 20 deletions(-) diff --git a/bsp.h b/bsp.h index 6618c8b..fa5fcdd 100644 --- a/bsp.h +++ b/bsp.h @@ -37,11 +37,14 @@ typedef struct // The BSP file header typedef float scalar_t; // Scalar value, -typedef struct // Vector or Position +typedef struct Vec3 // Vector or Position { scalar_t x; // horizontal scalar_t y; // horizontal scalar_t z; // vertical + + Vec3() : x(0), y(0), z(0) { } + Vec3(float x, float y, float z) : x(x), y(y), z(z) { } } vec3_t; typedef struct @@ -53,10 +56,35 @@ typedef struct long type; // Type of plane, depending on normal vector. } plane_t; -typedef struct // Bounding Box, Float values +typedef struct BoundBox // Bounding Box, Float values { vec3_t min; // minimum values of X,Y,Z vec3_t max; // maximum values of X,Y,Z + + BoundBox() { } + + void init(vec3_t point) + { + min = point; + max = point; + } + + void includePoint(vec3_t point) + { + if (point.x < min.x) + min.x = point.x; + if (point.y < min.y) + min.y = point.y; + if (point.z < min.z) + min.z = point.z; + + if (point.x > max.x) + max.x = point.x; + if (point.y > max.y) + max.y = point.y; + if (point.z > max.z) + max.z = point.z; + } } boundbox_t; typedef struct // Bounding Box, Short values @@ -82,11 +110,16 @@ typedef struct // Mip Texture unsigned long offset8; // offset to u_char Pix[width/8 * height/8] } miptex_t; -typedef struct +typedef struct Vertex { float X; // X,Y,Z coordinates of the vertex float Y; // usually some integer value float Z; // but coded in floating point + + vec3_t toVec() + { + return vec3_t(X, Y, Z); + } } vertex_t; typedef struct @@ -181,4 +214,7 @@ typedef struct int entitiesLength; char* entities; + int lightmapLength; + unsigned char* lightmap; + } world_t; diff --git a/main.cpp b/main.cpp index 8667249..096b7a4 100644 --- a/main.cpp +++ b/main.cpp @@ -164,6 +164,52 @@ static void leaf_zone(const dleaf_t* leaf, short zone[3]) zone[2] = midZ & mask; } +static unsigned char sample_lightmap(const world_t* world, const face_t *face, const BoundBox& bounds, const Vec3& point) +{ + if (face->lightmap < 0) + return 0; + + const unsigned char* lightmap = &world->lightmap[face->lightmap]; + const plane_t* plane = &world->planes[face->plane_id]; + + float w, h, u, v; + switch (plane->type) + { + case 0: + case 3: + // Towards X + w = bounds.max.y - bounds.min.y; + h = bounds.max.z - bounds.min.z; + u = (point.y - bounds.min.y) / w; + v = (point.z - bounds.min.z) / h; + break; + case 1: + case 4: + // Towards Y + w = bounds.max.x - bounds.min.x; + h = bounds.max.z - bounds.min.z; + u = (point.x - bounds.min.x) / w; + v = (point.z - bounds.min.z) / h; + break; + case 2: + case 5: + // Towards Z + w = bounds.max.x - bounds.min.x; + h = bounds.max.y - bounds.min.y; + u = (point.x - bounds.min.x) / w; + v = (point.y - bounds.min.y) / h; + break; + default: + printf("Error: unknown plane type %d\n", plane->type); + return 0; + } + + int width = (int)(w / 16); + int height = (int)(h / 16); + + return lightmap[(int)((height * v + u) * width)]; +} + int process_faces(const world_t* world) { // Write some data to a file @@ -185,10 +231,18 @@ int process_faces(const world_t* world) } // Write vertex data to a file (no vertex splitting yet) + BoundBox bounds; + std::vector outVertices; 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]; + + if (i == 0) + bounds.init(inVertex->toVec()); + else + bounds.includePoint(inVertex->toVec()); + 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) @@ -197,10 +251,11 @@ int process_faces(const world_t* world) outVertex.y = (short)(inVertex->Y * 4); outVertex.z = (short)(inVertex->Z * 4); } - outVertex.baseLight = 128; - outVertex.finalLight = 128; + outVertex.baseLight = 0; + outVertex.finalLight = 0; + outVertex.r = 0; - fwrite(&outVertex, sizeof(ps1bsp_vertex_t), 1, fbsp); + outVertices.push_back(outVertex); } std::vector outTriangles; @@ -224,32 +279,38 @@ int process_faces(const world_t* world) world->edges[-edgeIdx].vertex1; faceVertIndices.push_back(vertIndex); - } - - //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); + unsigned char light = sample_lightmap(world, face, bounds, world->vertices[vertIndex].toVec()); + if (light > 0) + { + *(unsigned short*)(&outVertices[vertIndex].baseLight) += light; + outVertices[vertIndex].r++; + } } outFace.numVertices = faceVertIndices.size() - outFace.firstVertexIndex; outFaces.push_back(outFace); } + // Average the lightmap values for this vertex + for (auto iter = outVertices.begin(); iter != outVertices.end(); ++iter) + { + unsigned char count = (*iter).r; + if (count == 0) + continue; + + unsigned short accumulate = *(unsigned short*)(&(*iter).baseLight); + (*iter).baseLight = accumulate / count; + (*iter).r = 0; + } + // Write triangle and face data to file + fwrite(outVertices.data(), sizeof(ps1bsp_vertex_t), outVertices.size(), fbsp); fwrite(faceVertIndices.data(), sizeof(unsigned short), faceVertIndices.size(), fbsp); fwrite(outFaces.data(), sizeof(ps1bsp_face_t), outFaces.size(), fbsp); // Update header information - outHeader.numVertices = world->numVertices; + outHeader.numVertices = outVertices.size(); outHeader.numFaceVertIndices = faceVertIndices.size(); outHeader.numFaces = outFaces.size(); @@ -440,6 +501,15 @@ int load_bsp(const char* bspname, world_t* world) 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; }