From 4f99baea65009f3a3e730bf1e5e3cc8b331b72a6 Mon Sep 17 00:00:00 2001 From: Nico de Poel Date: Thu, 26 Jan 2023 11:36:58 +0100 Subject: [PATCH] Encapsulated GPC-based tesselation into a Tesselator class, which now also produces normalized UVs per vertex-texture pair and a list of Polygon structs with texture and vertex index data. --- PS1BSP.vcxproj | 2 + PS1BSP.vcxproj.filters | 6 +++ common.h | 12 +++++ main.cpp | 103 ++--------------------------------- tesselate.cpp | 119 +++++++++++++++++++++++++++++++++++++++++ tesselate.h | 33 ++++++++++++ 6 files changed, 176 insertions(+), 99 deletions(-) create mode 100644 tesselate.cpp create mode 100644 tesselate.h diff --git a/PS1BSP.vcxproj b/PS1BSP.vcxproj index b22cbb9..4a5ccb1 100644 --- a/PS1BSP.vcxproj +++ b/PS1BSP.vcxproj @@ -144,6 +144,7 @@ + @@ -160,6 +161,7 @@ + diff --git a/PS1BSP.vcxproj.filters b/PS1BSP.vcxproj.filters index 7366cc1..b514ea6 100644 --- a/PS1BSP.vcxproj.filters +++ b/PS1BSP.vcxproj.filters @@ -33,6 +33,9 @@ Source Files + + Source Files + @@ -77,6 +80,9 @@ Header Files + + Header Files + diff --git a/common.h b/common.h index c5ef7dd..513d82f 100644 --- a/common.h +++ b/common.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -105,6 +106,17 @@ static bool operator==(const Vec3& lhs, const Vec3& rhs) fabs(rhs.z - lhs.z) < 0.001; } +static bool operator<(const Vec3& lhs, const Vec3& rhs) +{ + if (fabs(rhs.x - lhs.x) < 0.001) + if (fabs(rhs.y - lhs.y) < 0.001) + return lhs.z < rhs.z; + else + return lhs.y < rhs.y; + else + return lhs.x < rhs.x; +} + static float halton(int32_t index, int32_t base) { float f = 1.0f, result = 0.0f; diff --git a/main.cpp b/main.cpp index cbf5bec..d753403 100644 --- a/main.cpp +++ b/main.cpp @@ -4,12 +4,10 @@ #include "ps1bsp.h" #include "lighting.h" #include "texture.h" -#include "gpc.h" +#include "tesselate.h" static char path[_MAX_PATH]; -void gpc_test(const world_t* world, const face_t* face); - template size_t writeMapData(const std::vector& data, ps1bsp_dentry_t& dentry, FILE* f) { dentry.offset = (unsigned int)ftell(f); @@ -110,6 +108,8 @@ int process_faces(const world_t* world, const std::vector& tex outVertices.push_back(outVertex); } + + Tesselator tesselator(world); // Convert faces defined by edges into faces defined by vertex indices std::vector outFaces; @@ -208,7 +208,7 @@ int process_faces(const world_t* world, const std::vector& tex outFace.center.pad = (short)(sqrt(area)); outFaces.push_back(outFace); - gpc_test(world, face); + auto tessPolys = tesselator.tesselateFace(face); } // Iterate over all faces again; now that we know the bounds of each face, we can calculate lighting for all of them @@ -303,101 +303,6 @@ int process_faces(const world_t* world, const std::vector& tex return 1; } -void gpc_test(const world_t* world, const face_t* face) -{ - const texinfo_t* texinfo = &world->texInfos[face->texinfo_id]; - const miptex_t* miptex = &world->miptexes[texinfo->texture_id]; - const plane_t* plane = &world->planes[face->plane_id]; - - double minS = DBL_MAX, minT = DBL_MAX; - double maxS = DBL_MIN, maxT = DBL_MIN; - - gpc_polygon polygon = { 0 }; - gpc_vertex_list contour; - contour.num_vertices = face->ledge_num; - contour.vertex = (gpc_vertex*)malloc(contour.num_vertices * sizeof(gpc_vertex)); - if (contour.vertex == NULL) - return; - - std::vector originalVecs; - 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(); - originalVecs.push_back(vertexPoint); - - // Calculate texture UV bounds - double s = (vertexPoint.dotProduct(texinfo->vectorS) + texinfo->distS) / miptex->width; - double 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; - - contour.vertex[edgeListIdx] = gpc_vertex{ s, t }; - } - - gpc_add_contour(&polygon, &contour, 0); - - std::vector vertices; - std::unordered_map vertexIndices; - - // Create a virtual grid at the texture bounds and iterate over each cell to break up the face into repeating tiles - for (double y = floor(minT); y <= ceil(maxT); y += 1.0) - { - for (double x = floor(minS); x <= ceil(maxS); x += 1.0) - { - // Create a square polygon that covers the entire cell - gpc_polygon cell = { 0 }; - gpc_vertex_list cell_bounds; - cell_bounds.num_vertices = 4; - cell_bounds.vertex = (gpc_vertex*)malloc(4 * sizeof(gpc_vertex)); - cell_bounds.vertex[0] = gpc_vertex{ x, y }; - cell_bounds.vertex[1] = gpc_vertex{ x + 1.0, y }; - cell_bounds.vertex[2] = gpc_vertex{ x + 1.0, y + 1.0 }; - cell_bounds.vertex[3] = gpc_vertex{ x, y + 1.0 }; - gpc_add_contour(&cell, &cell_bounds, 0); - - // Take the intersection to get the chunk of the face that's inside this cell - gpc_polygon result; - gpc_polygon_clip(GPC_INT, &polygon, &cell, &result); - - // We should get a polygon with exactly one contour as a result; if not, the face was not on this grid cell - if (result.num_contours <= 0) - continue; - - // Reconstruct the polygon's vertices in world space - for (int v = 0; v < result.contour[0].num_vertices; ++v) - { - const auto vert = &result.contour[0].vertex[v]; - Vec3 newVert = - plane->normal * plane->dist + - texinfo->vectorS * (float)(vert->x * miptex->width - texinfo->distS) + - texinfo->vectorT * (float)(vert->y * miptex->height - texinfo->distT); - - // Make sure we don't store duplicate vertices - auto vertIter = vertexIndices.find(newVert); - if (vertIter == vertexIndices.end()) - { - vertexIndices[newVert] = vertices.size(); - vertices.push_back(newVert); - } - - // Store the relevant (S, T) coordinates for each vertex-texinfo pair - } - - gpc_free_polygon(&result); - gpc_free_polygon(&cell); - } - } - - gpc_free_polygon(&polygon); -} - int process_bsp(const world_t *world) { // Test exporting texture data diff --git a/tesselate.cpp b/tesselate.cpp new file mode 100644 index 0000000..033e881 --- /dev/null +++ b/tesselate.cpp @@ -0,0 +1,119 @@ +#include "common.h" +#include "bsp.h" +#include "tesselate.h" +#include "gpc.h" + +std::vector Tesselator::tesselateFace(const face_t* face) +{ + std::vector polygons; + + const texinfo_t* texinfo = &world->texInfos[face->texinfo_id]; + const miptex_t* miptex = &world->miptexes[texinfo->texture_id]; + const plane_t* plane = &world->planes[face->plane_id]; + + double minS = DBL_MAX, minT = DBL_MAX; + double maxS = DBL_MIN, maxT = DBL_MIN; + + gpc_polygon facePolygon = { 0 }; + gpc_vertex_list contour; + contour.num_vertices = face->ledge_num; + contour.vertex = (gpc_vertex*)malloc(contour.num_vertices * sizeof(gpc_vertex)); + if (contour.vertex == NULL) + return polygons; + + // Build a polygon in normalized 2D texture space from the original face data + 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 + double s = (vertexPoint.dotProduct(texinfo->vectorS) + texinfo->distS) / miptex->width; + double 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; + + contour.vertex[edgeListIdx] = gpc_vertex{ s, t }; + } + + gpc_add_contour(&facePolygon, &contour, 0); + + // Create a virtual grid at the texture bounds and iterate over each cell to break up the face into repeating tiles + for (double y = floor(minT); y <= ceil(maxT); y += 1.0) + { + for (double x = floor(minS); x <= ceil(maxS); x += 1.0) + { + // Create a square polygon that covers the entire cell + gpc_polygon cell = { 0 }; + gpc_vertex_list cell_bounds; + cell_bounds.num_vertices = 4; + cell_bounds.vertex = (gpc_vertex*)malloc(4 * sizeof(gpc_vertex)); + cell_bounds.vertex[0] = gpc_vertex{ x, y }; + cell_bounds.vertex[1] = gpc_vertex{ x + 1.0, y }; + cell_bounds.vertex[2] = gpc_vertex{ x + 1.0, y + 1.0 }; + cell_bounds.vertex[3] = gpc_vertex{ x, y + 1.0 }; + gpc_add_contour(&cell, &cell_bounds, 0); + + // Take the intersection to get the chunk of the face that's inside this cell + gpc_polygon result; + gpc_polygon_clip(GPC_INT, &facePolygon, &cell, &result); + + // We should get a polygon with exactly one contour as a result; if not, the face was not on this grid cell + if (result.num_contours <= 0) + continue; + + Polygon newPoly; + newPoly.texinfo = texinfo; + + // Reconstruct the polygon's vertices in 3D world space + for (int v = 0; v < result.contour[0].num_vertices; ++v) + { + const auto vert = &result.contour[0].vertex[v]; + Vec3 newVert = + plane->normal * plane->dist + + texinfo->vectorS * (float)(vert->x * miptex->width - texinfo->distS) + + texinfo->vectorT * (float)(vert->y * miptex->height - texinfo->distT); + + // Make sure we don't store duplicate vertices + size_t vertexIndex; + auto vertIter = vertexIndices.find(newVert); + if (vertIter != vertexIndices.end()) + { + vertexIndex = vertIter->second; + } + else + { + vertexIndex = vertices.size(); + vertexIndices[newVert] = vertexIndex; + vertices.push_back(newVert); + } + + newPoly.indices.push_back(vertexIndex); + + // Store the relevant (S, T) coordinates for each vertex-texinfo pair + VertexTexturePair uvPair{ newVert, texinfo }; + auto vertUVIter = vertexUVs.find(uvPair); + if (vertUVIter == vertexUVs.end()) + { + // Normalize the UV to fall within [0..1] range + Vec3 uv(vert->x - x, vert->y - y, 0); + vertexUVs[uvPair] = uv; + } + } + + polygons.push_back(newPoly); + + gpc_free_polygon(&result); + gpc_free_polygon(&cell); + } + } + + gpc_free_polygon(&facePolygon); + return polygons; +} diff --git a/tesselate.h b/tesselate.h new file mode 100644 index 0000000..f56a209 --- /dev/null +++ b/tesselate.h @@ -0,0 +1,33 @@ +#pragma once + +class Tesselator +{ +public: + typedef std::vector VertexList; + typedef std::unordered_map VertexIndexMap; + typedef std::pair VertexTexturePair; + typedef std::map VertexUVMap; + + struct Polygon + { + const texinfo_t* texinfo; + std::vector indices; + }; + + Tesselator(const world_t* world) : world(world) + { + } + + const VertexList& getVertices() const { return vertices; } + const VertexIndexMap& getVertexIndices() const { return vertexIndices; } + const VertexUVMap& getVertexUVs() const { return vertexUVs; } + + std::vector tesselateFace(const face_t* face); + +private: + const world_t* world; + + VertexList vertices; + VertexIndexMap vertexIndices; + VertexUVMap vertexUVs; +};