Browse Source
Encapsulated GPC-based tesselation into a Tesselator class, which now also produces normalized UVs per vertex-texture pair and a list of Polygon structs with texture and vertex index data.
master
Encapsulated GPC-based tesselation into a Tesselator class, which now also produces normalized UVs per vertex-texture pair and a list of Polygon structs with texture and vertex index data.
master
6 changed files with 176 additions and 99 deletions
-
2PS1BSP.vcxproj
-
6PS1BSP.vcxproj.filters
-
12common.h
-
103main.cpp
-
119tesselate.cpp
-
33tesselate.h
@ -0,0 +1,119 @@ |
|||
#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; |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
#pragma once |
|||
|
|||
class Tesselator |
|||
{ |
|||
public: |
|||
typedef std::vector<Vec3> VertexList; |
|||
typedef std::unordered_map<Vec3, size_t> VertexIndexMap; |
|||
typedef std::pair<Vec3, const texinfo_t*> VertexTexturePair; |
|||
typedef std::map<VertexTexturePair, Vec3> VertexUVMap; |
|||
|
|||
struct Polygon |
|||
{ |
|||
const texinfo_t* texinfo; |
|||
std::vector<size_t> indices; |
|||
}; |
|||
|
|||
Tesselator(const world_t* world) : world(world) |
|||
{ |
|||
} |
|||
|
|||
const VertexList& getVertices() const { return vertices; } |
|||
const VertexIndexMap& getVertexIndices() const { return vertexIndices; } |
|||
const VertexUVMap& getVertexUVs() const { return vertexUVs; } |
|||
|
|||
std::vector<Polygon> tesselateFace(const face_t* face); |
|||
|
|||
private: |
|||
const world_t* world; |
|||
|
|||
VertexList vertices; |
|||
VertexIndexMap vertexIndices; |
|||
VertexUVMap vertexUVs; |
|||
}; |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue