#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; }