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.

114 lines
4.1 KiB

#include "common.h"
#include "bsp.h"
#include "tesselate.h"
#include "gpc.h"
#include "matrix.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];
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;
// 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 textureTrsf;
textureTrsf.SetAxis(0, texinfo->vectorS / (float)miptex->width);
textureTrsf.SetAxis(1, texinfo->vectorT / (float)miptex->height);
textureTrsf.SetAxis(2, plane->normal);
textureTrsf.SetTranslation(Vec3(texinfo->distS / miptex->width, texinfo->distT / miptex->height, -plane->dist));
// Build a polygon in normalized 2D texture space from the original face data
std::vector<Vec3> 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));
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);
}
}
gpc_free_polygon(&facePolygon);
return polygons;
}