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
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 = ceil(st.x); if (st.x < minS) minS = floor(st.x);
|
|
if (st.y > maxT) maxT = ceil(st.y); if (st.y < minT) minT = floor(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 = minT; y < maxT; y += cellHeight)
|
|
{
|
|
for (double x = minS; x < 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;
|
|
}
|