Tools for preprocessing data files from Quake to make them suitable for use on PS1 hardware
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

119 lines
3.8 KiB

#include "common.h"
#include "bsp.h"
#include "tesselate.h"
#include "gpc.h"
std::vector<Tesselator::Polygon> Tesselator::tesselateFace(const face_t* face)
{
std::vector<Polygon> 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;
}