#include "common.h" #include "bsp.h" #include "tesselate.h" #include "gpc.h" #include "matrix.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]; 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; double minS = DBL_MAX, minT = DBL_MAX; double maxS = DBL_MIN, maxT = DBL_MIN; Matrix4x4 textureTrsf = buildTextureSpaceTransform(texinfo, miptex, plane); // Build a polygon in normalized 2D texture space from the original face data std::vector faceVertices; 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(); faceVertices.push_back(vertexPoint); // Transform the vertex to texture space and calculate the texture UV bounds Vec3 st = textureTrsf.TransformPoint(vertexPoint); if (st.x > maxS) maxS = st.x; if (st.x < minS) minS = st.x; if (st.y > maxT) maxT = st.y; if (st.y < minT) minT = st.y; contour.vertex[edgeListIdx] = gpc_vertex{ st.x, st.y }; } gpc_add_contour(&facePolygon, &contour, 0); // Invert the texture matrix so we can transform vertices from 2D texture space back to 3D world space if (!textureTrsf.Invert()) { printf("Failed to invert texture space transform!\n"); return polygons; } // 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, y + 1.0 }; cell_bounds.vertex[2] = gpc_vertex{ x + 1.0, y + 1.0 }; cell_bounds.vertex[3] = gpc_vertex{ x + 1.0, y }; 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 = { 0 }; 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; // 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]; // Transform the vertex back to world space Vec3 newVert = textureTrsf.TransformPoint(Vec3(vert->x, vert->y, 0.0)); size_t vertIndex = addVertex(newVert); Vec3 normalizedUV(vert->x - x, vert->y - y, 0.0); // Normalize the UV to fall within [0..1] range newPoly.polyVertices.push_back(PolyVertex{ vertIndex, normalizedUV }); } polygons.push_back(newPoly); gpc_free_polygon(&result); gpc_free_polygon(&cell); } } gpc_free_polygon(&facePolygon); return polygons; } Matrix4x4 Tesselator::buildTextureSpaceTransform(const texinfo_t* texinfo, const miptex_t* miptex, const plane_t* plane) { // vectorS and vectorT are normally perpendicular (dot product is 0), magnitude isn't always 1 but that's fine. // Means we can construct a coordinate space from them (plane normal for the third vector) and transform the vertices to texture space. // And we can create an inverse transform, meaning we can transform vertices from 2D texture space back to 3D world space. Matrix4x4 matrix; matrix.SetAxis(0, texinfo->vectorS / (float)miptex->width); matrix.SetAxis(1, texinfo->vectorT / (float)miptex->height); matrix.SetAxis(2, plane->normal); matrix.SetTranslation(Vec3(texinfo->distS / miptex->width, texinfo->distT / miptex->height, -plane->dist)); return matrix; } Matrix4x4 Tesselator::buildLightmapTransform(const texinfo_t* texinfo, const plane_t* plane) { Matrix4x4 matrix; matrix.SetAxis(0, texinfo->vectorS); matrix.SetAxis(1, texinfo->vectorT); matrix.SetAxis(2, plane->normal); matrix.SetTranslation(Vec3(texinfo->distS, texinfo->distT, -plane->dist)); return matrix; }