From e0373524ca7b9868cc464a2c036ec66e97836865 Mon Sep 17 00:00:00 2001 From: Nico de Poel Date: Sat, 21 Jan 2023 21:37:48 +0100 Subject: [PATCH] Further improvement to lightmapping system: - Added a function to find all faces that contain a given point. This is useful for both sampling lightmaps, and sampling textures later. - New face vertex lighting function simply collects all faces for a given vertex, and samples all relevant lightmaps for it - A whole bunch more experiments that were more complex and that went nowhere - Ironically all this doesn't really change the current quality of the lighting but hey, that's a result too. And this added code will prove useful soon enough. --- common.h | 1 + lighting.cpp | 229 +++++++++++++++++++++++++++++++++++++++++++++++++++ lighting.h | 11 +++ main.cpp | 3 +- 4 files changed, 243 insertions(+), 1 deletion(-) diff --git a/common.h b/common.h index dc6cc3c..188222f 100644 --- a/common.h +++ b/common.h @@ -1,4 +1,5 @@ #pragma once +#define _USE_MATH_DEFINES #include #include #include diff --git a/lighting.cpp b/lighting.cpp index 306556c..3444a68 100644 --- a/lighting.cpp +++ b/lighting.cpp @@ -250,7 +250,236 @@ unsigned char compute_faceVertex_light2(const world_t* world, const face_t* face return (unsigned char)(light / numSamples); } +// Start with a hash set of all faces +// From the first face onward, group together faces into an interconnected surface based on angle between faces, using a flood-fill type of approach: +// - Add the first face to a surface face list +// - Take the next face from the surface face list (use index to go to next, don't remove it from the surface list) +// - For each face vertex, look up all other faces connected to that vertex +// - If the other face is not present in the original hash set, skip it (it was already added to a surface) +// - If the other face has an angle > 60 degrees with the current face, skip it (sharp edge) +// - Add the other face to the surface face list, remove it from the hash set +// - Repeat from step 'take the next face' +SurfaceList group_surfaces(const world_t* world, const VertexFaces& vertexFaces) +{ + SurfaceList surfaces; + std::unordered_set faceSet; + for (int i = 0; i < world->numFaces; ++i) + faceSet.insert(&world->faces[i]); + + while (!faceSet.empty()) + { + const face_t* firstFace = *faceSet.begin(); + faceSet.erase(firstFace); + + std::vector surfaceFaces; + surfaceFaces.push_back(firstFace); + + for (size_t faceIdx = 0; faceIdx < surfaceFaces.size(); ++faceIdx) + { + const face_t* thisFace = surfaceFaces[faceIdx]; + const plane_t* thisPlane = &world->planes[thisFace->plane_id]; + vec3_t thisNormal = thisFace->side ? -thisPlane->normal : thisPlane->normal; + + for (int edgeListIdx = 0; edgeListIdx < thisFace->ledge_num; ++edgeListIdx) + { + int edgeIdx = world->edgeList[thisFace->ledge_id + edgeListIdx]; + const edge_t* edge = &world->edges[abs(edgeIdx)]; + + for (int v = 0; v < 1; ++v) + { + unsigned short vertIndex = *(&edge->vertex0 + v); + + const vertex_t* vertex = &world->vertices[vertIndex]; + auto vertexFaceIter = vertexFaces.find(vertex); + if (vertexFaceIter == vertexFaces.end()) + { + printf("Couldn't find list of faces for vertex %d, weird...\n", vertIndex); + continue; + } + + for (auto faceIter = vertexFaceIter->second.begin(); faceIter != vertexFaceIter->second.end(); ++faceIter) + { + const face_t* otherFace = *faceIter; + if (faceSet.find(otherFace) == faceSet.end()) + continue; // Face has already been added to a surface, skip it + + const plane_t* otherPlane = &world->planes[otherFace->plane_id]; + vec3_t otherNormal = otherFace->side ? -otherPlane->normal : otherPlane->normal; + float dot = thisNormal.dotProduct(otherNormal); + if (dot < 0.5f) + continue; // Sharp edge, face belongs to a different surface + + // Add face to this surface and make sure it won't be reconsidered for any other surfaces + surfaceFaces.push_back(otherFace); + faceSet.erase(otherFace); + } + } + } + } + + Surface surface; + surface.faces.insert(surfaceFaces.begin(), surfaceFaces.end()); + surfaces.push_back(surface); + } + + return surfaces; +} + +// To sample lightmap at any arbitrary world position: +// - Find the surface that the face belongs to +// - Select all faces from the surface where the position lies in its plane and is inside polygon boundaries +// - Sample those faces at the specified position (sample_lightmap) and average +std::vector find_facesWithPoint(const world_t* world, const face_t* refFace, const SurfaceList& surfaces, Vec3 point) +{ + std::vector faces; + + // Find the surface that the face belongs to + for (auto surfIter = surfaces.begin(); surfIter != surfaces.end(); ++surfIter) + { + if (surfIter->faces.find(refFace) == surfIter->faces.end()) + continue; + + // Select all faces from the surface where the point lies in its plane and is inside polygon boundaries + for (auto faceIter = surfIter->faces.begin(); faceIter != surfIter->faces.end(); ++faceIter) + { + const face_t* face = *faceIter; + const plane_t* plane = &world->planes[face->plane_id]; + + // Check if the point lies on the face's plane + float pointPlaneDist = (point.x * plane->normal.x + point.y * plane->normal.y + point.z * plane->normal.z) - plane->dist; + if (fabs(pointPlaneDist) > FLT_EPSILON) + continue; + + // Simple case first: check if this point lies on one of the face's edges + bool onEdge = false; + for (int edgeListIdx = 0; edgeListIdx < face->ledge_num; ++edgeListIdx) + { + int edgeIdx = world->edgeList[face->ledge_id + edgeListIdx]; + const edge_t* edge = &world->edges[abs(edgeIdx)]; + + Vec3 vert0 = world->vertices[edge->vertex0].toVec(); + Vec3 vert1 = world->vertices[edge->vertex1].toVec(); + + Vec3 dir0 = vert1 - vert0; + Vec3 dir1 = point - vert0; + float mag0 = dir0.magnitude(); + float mag1 = dir1.magnitude(); + + if (mag1 / mag0 <= 1.0f + FLT_EPSILON && dir0.dotProduct(dir1) >= (mag0 - FLT_EPSILON) * (mag1 - FLT_EPSILON)) + onEdge = true; + } + + if (onEdge) + faces.push_back(face); + } + + break; + } + + return faces; +} + // Further improvements: // - Reconstruct connected surfaces through vertices and edges, so we can fully sample all adjacent lightmaps. // Right now we don't properly detect when one face has vertices halfway along the edge of an adjacent face. // - Sample more lightmap points around each vertex to obtain a more representative average value +unsigned char compute_faceVertex_light3(const world_t* world, const face_t* refFace, const SurfaceList& surfaces, const FaceBounds& faceBounds, Vec3 point) +{ + auto faces = find_facesWithPoint(world, refFace, surfaces, point); + if (faces.empty()) + return 0; + + unsigned int light = 0; + for (auto faceIter = faces.begin(); faceIter != faces.end(); ++faceIter) + { + const face_t* face = *faceIter; + light += sample_lightmap(world, face, faceBounds.find(face)->second, point) + (0xFF - face->baselight); + } + + return (unsigned char)(light / faces.size()); +} + +// Find the list of all faces that contain the given point, i.e. the point lies on the face's plane and is contained within its polygon bounds +std::vector world_facesWithPoint(const world_t* world, Vec3 point) +{ + std::vector faces; + for (int faceIdx = 0; faceIdx < world->numFaces; ++faceIdx) + { + const face_t* face = &world->faces[faceIdx]; + const plane_t* plane = &world->planes[face->plane_id]; + + // Check if the point lies on the face's plane (it's not strictly necessary to do this, but this check makes the whole function a lot faster) + double pointPlaneDist = ((double)point.x * plane->normal.x + (double)point.y * plane->normal.y + (double)point.z * plane->normal.z) - plane->dist; + if (fabs(pointPlaneDist) > 0.0001) + continue; + + // Check if the point is contained within the face's polygon + double angleSum = 0; + for (int edgeListIdx = 0; edgeListIdx < face->ledge_num; ++edgeListIdx) + { + int edgeIdx = world->edgeList[face->ledge_id + edgeListIdx]; + + Vec3 v0, v1; + if (edgeIdx > 0) + { + const edge_t* edge = &world->edges[edgeIdx]; + v0 = world->vertices[edge->vertex0].toVec(); + v1 = world->vertices[edge->vertex1].toVec(); + } + else + { + const edge_t* edge = &world->edges[-edgeIdx]; + v0 = world->vertices[edge->vertex1].toVec(); + v1 = world->vertices[edge->vertex0].toVec(); + } + + Vec3 p0 = v0 - point; + Vec3 p1 = v1 - point; + double m0 = p0.magnitude(); + double m1 = p1.magnitude(); + + if ((m0 * m1) <= 0.0001) + { + faces.push_back(face); + break; + } + + double dot = p0.dotProduct(p1) / (m0 * m1); + angleSum += acos(dot); + } + + if (fabs(2 * M_PI - angleSum) <= 0.0001) + faces.push_back(face); + } + + return faces; +} + +unsigned char compute_faceVertex_light4(const world_t* world, const face_t* refFace, const FaceBounds& faceBounds, Vec3 point) +{ + auto faces = world_facesWithPoint(world, point); + if (faces.empty()) + return 0; + + const plane_t* refPlane = &world->planes[refFace->plane_id]; + vec3_t refNormal = refFace->side ? -refPlane->normal : refPlane->normal; + + unsigned int light = 0, numSamples = 0; + for (auto faceIter = faces.begin(); faceIter != faces.end(); ++faceIter) + { + const face_t* face = *faceIter; + const plane_t* plane = &world->planes[face->plane_id]; + vec3_t normal = face->side ? -plane->normal : plane->normal; + + // Check if the face is at a shallow angle with the reference face + float dot = normal.dotProduct(refNormal); + if (dot < 0.5f) + continue; + + light += sample_lightmap(world, face, faceBounds.find(face)->second, point) + (0xFF - face->baselight); + numSamples++; + } + + // We should always end up with at least one sample (that from refFace itself), so if we divide by zero here something is very much wrong + return (unsigned char)(light / numSamples); +} diff --git a/lighting.h b/lighting.h index 040a155..bfabc7f 100644 --- a/lighting.h +++ b/lighting.h @@ -10,8 +10,14 @@ struct EdgeData bool isSharpEdge = false; }; +struct Surface +{ + std::unordered_set faces; +}; + typedef std::unordered_map FaceBounds; typedef std::unordered_map> VertexFaces; +typedef std::vector SurfaceList; unsigned char sample_lightmap(const world_t* world, const face_t* face, const BoundBox& bounds, const Vec3& point); void export_lightmap(const world_t* world, const face_t* face, const BoundBox& bounds, int faceIdx); @@ -19,3 +25,8 @@ void export_lightmap(const world_t* world, const face_t* face, const BoundBox& b std::unordered_map analyze_edges(const world_t* world); unsigned char compute_faceVertex_light(const world_t* world, const face_t* face, unsigned short vertexIndex, const FaceBounds& faceBounds, const std::unordered_map& edgeData); unsigned char compute_faceVertex_light2(const world_t* world, const face_t* face, unsigned short vertexIndex, const FaceBounds& faceBounds, const VertexFaces& vertexFaces); + +SurfaceList group_surfaces(const world_t* world, const VertexFaces& vertexFaces); +unsigned char compute_faceVertex_light3(const world_t* world, const face_t* refFace, const SurfaceList& surfaces, const FaceBounds& faceBounds, Vec3 point); + +unsigned char compute_faceVertex_light4(const world_t* world, const face_t* refFace, const FaceBounds& faceBounds, Vec3 point); diff --git a/main.cpp b/main.cpp index 393529f..abdca5a 100644 --- a/main.cpp +++ b/main.cpp @@ -334,7 +334,8 @@ int process_faces(const world_t* world) for (size_t faceVertIdx = 0; faceVertIdx < outFace.numFaceVertices; ++faceVertIdx) { ps1bsp_facevertex_t& faceVertex = outFaceVertices[outFace.firstFaceVertex + faceVertIdx]; - faceVertex.light = compute_faceVertex_light2(world, face, faceVertex.index, faceBounds, vertexFaces); + const vertex_t* vertex = &world->vertices[faceVertex.index]; + faceVertex.light = compute_faceVertex_light4(world, face, faceBounds, vertex->toVec()); } }