#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; double invSLenSqr = 1.0 / texinfo->vectorS.sqrMagnitude(); double invTLenSqr = 1.0 / texinfo->vectorT.sqrMagnitude(); // 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); // 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 (cross product for the third vector) and transform the vertex point to ST-space // And we can create an inverse transform... though just having s and t values probably isn't enough to completely transform back... // 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); auto faceVert = *faceVertices.begin(); // 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]; Vec3 newVert = plane->normal * plane->dist + texinfo->vectorS * (vert->x * miptex->width - texinfo->distS) * invSLenSqr + texinfo->vectorT * (vert->y * miptex->height - texinfo->distT) * invTLenSqr; size_t vertIndex = addVertex(newVert); Vec3 normalizedUV(vert->x - x, vert->y - y, 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); } } if (vertexIndices.find(faceVert) == vertexIndices.end()) { gpc_free_polygon(&facePolygon); return polygons; } gpc_free_polygon(&facePolygon); return polygons; }