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
Nico de Poel 3 years ago
parent
commit
4f99baea65
  1. 2
      PS1BSP.vcxproj
  2. 6
      PS1BSP.vcxproj.filters
  3. 12
      common.h
  4. 103
      main.cpp
  5. 119
      tesselate.cpp
  6. 33
      tesselate.h

2
PS1BSP.vcxproj

@ -144,6 +144,7 @@
<ClCompile Include="gpc.cpp" />
<ClCompile Include="lighting.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="tesselate.cpp" />
<ClCompile Include="texture.cpp" />
<ClCompile Include="tim.cpp" />
</ItemGroup>
@ -160,6 +161,7 @@
<ClInclude Include="rectpack\finders_interface.h" />
<ClInclude Include="rectpack\insert_and_split.h" />
<ClInclude Include="rectpack\rect_structs.h" />
<ClInclude Include="tesselate.h" />
<ClInclude Include="texture.h" />
<ClInclude Include="tim.h" />
</ItemGroup>

6
PS1BSP.vcxproj.filters

@ -33,6 +33,9 @@
<ClCompile Include="gpc.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="tesselate.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="bsp.h">
@ -77,6 +80,9 @@
<ClInclude Include="gpc.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="tesselate.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<CopyFileToFolders Include="palette.lmp">

12
common.h

@ -6,6 +6,7 @@
#include <cmath>
#include <cfloat>
#include <vector>
#include <map>
#include <unordered_map>
#include <unordered_set>
@ -105,6 +106,17 @@ static bool operator==(const Vec3& lhs, const Vec3& rhs)
fabs(rhs.z - lhs.z) < 0.001;
}
static bool operator<(const Vec3& lhs, const Vec3& rhs)
{
if (fabs(rhs.x - lhs.x) < 0.001)
if (fabs(rhs.y - lhs.y) < 0.001)
return lhs.z < rhs.z;
else
return lhs.y < rhs.y;
else
return lhs.x < rhs.x;
}
static float halton(int32_t index, int32_t base)
{
float f = 1.0f, result = 0.0f;

103
main.cpp

@ -4,12 +4,10 @@
#include "ps1bsp.h"
#include "lighting.h"
#include "texture.h"
#include "gpc.h"
#include "tesselate.h"
static char path[_MAX_PATH];
void gpc_test(const world_t* world, const face_t* face);
template<class TData> size_t writeMapData(const std::vector<TData>& data, ps1bsp_dentry_t& dentry, FILE* f)
{
dentry.offset = (unsigned int)ftell(f);
@ -110,6 +108,8 @@ int process_faces(const world_t* world, const std::vector<ps1bsp_texture_t>& tex
outVertices.push_back(outVertex);
}
Tesselator tesselator(world);
// Convert faces defined by edges into faces defined by vertex indices
std::vector<ps1bsp_face_t> outFaces;
@ -208,7 +208,7 @@ int process_faces(const world_t* world, const std::vector<ps1bsp_texture_t>& tex
outFace.center.pad = (short)(sqrt(area));
outFaces.push_back(outFace);
gpc_test(world, face);
auto tessPolys = tesselator.tesselateFace(face);
}
// Iterate over all faces again; now that we know the bounds of each face, we can calculate lighting for all of them
@ -303,101 +303,6 @@ int process_faces(const world_t* world, const std::vector<ps1bsp_texture_t>& tex
return 1;
}
void gpc_test(const world_t* world, const face_t* face)
{
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 polygon = { 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;
std::vector<Vec3> originalVecs;
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();
originalVecs.push_back(vertexPoint);
// 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(&polygon, &contour, 0);
std::vector<Vec3> vertices;
std::unordered_map<Vec3, size_t> vertexIndices;
// 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, &polygon, &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;
// Reconstruct the polygon's vertices in 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
auto vertIter = vertexIndices.find(newVert);
if (vertIter == vertexIndices.end())
{
vertexIndices[newVert] = vertices.size();
vertices.push_back(newVert);
}
// Store the relevant (S, T) coordinates for each vertex-texinfo pair
}
gpc_free_polygon(&result);
gpc_free_polygon(&cell);
}
}
gpc_free_polygon(&polygon);
}
int process_bsp(const world_t *world)
{
// Test exporting texture data

119
tesselate.cpp

@ -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;
}

33
tesselate.h

@ -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;
};
Loading…
Cancel
Save