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.

142 lines
5.0 KiB

#include "common.h"
#include "bsp.h"
#include "tesselate.h"
#include "gpc.h"
#include "matrix.h"
bool texture_isRepeatable(const miptex_t* miptex);
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;
Matrix4x4 textureTrsf = buildTextureSpaceTransform(texinfo, miptex, plane);
// 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
double cellWidth = 1.0, cellHeight = 1.0;
if (texture_isRepeatable(miptex))
{
cellWidth = cellHeight = 2.0;
// Snap minS and minT to a grid of cellWidth/Height cell sizes, to avoid creating lots of T-junctions between adjacent faces
minS = floor(minS / cellWidth) * cellWidth;
minT = floor(minT / cellHeight) * cellHeight;
}
for (double y = floor(minT); y < ceil(maxT); y += cellHeight)
{
for (double x = floor(minS); x < ceil(maxS); x += cellWidth)
{
// 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 + cellHeight };
cell_bounds.vertex[2] = gpc_vertex{ x + cellWidth, y + cellHeight };
cell_bounds.vertex[3] = gpc_vertex{ x + cellWidth, 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;
}