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.
626 lines
19 KiB
626 lines
19 KiB
#include "common.h"
|
|
#include "bsp.h"
|
|
#include "ps1types.h"
|
|
#include "ps1bsp.h"
|
|
#include "lighting.h"
|
|
#include "texture.h"
|
|
#include "gpc.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);
|
|
dentry.size = sizeof(TData) * data.size();
|
|
return fwrite(data.data(), sizeof(TData), data.size(), f);
|
|
}
|
|
|
|
static SVECTOR convertNormal(vec3_t normal)
|
|
{
|
|
SVECTOR outNormal;
|
|
outNormal.vx = (short)(normal.x * 4096);
|
|
outNormal.vy = (short)(normal.y * 4096);
|
|
outNormal.vz = (short)(normal.z * 4096);
|
|
outNormal.pad = 0;
|
|
return outNormal;
|
|
}
|
|
|
|
static SVECTOR convertWorldPosition(vec3_t point)
|
|
{
|
|
SVECTOR outPoint;
|
|
outPoint.vx = (short)(point.x * 4);
|
|
outPoint.vy = (short)(point.y * 4);
|
|
outPoint.vz = (short)(point.z * 4);
|
|
outPoint.pad = 1;
|
|
return outPoint;
|
|
}
|
|
|
|
static float computeFaceArea(const world_t* world, const face_t* face)
|
|
{
|
|
const plane_t* plane = &world->planes[face->plane_id];
|
|
|
|
// Construct a tangent and bitangent for the plane using the face's first vertex
|
|
int edgeIdx = world->edgeList[face->ledge_id];
|
|
unsigned short vertIndex = edgeIdx > 0 ?
|
|
world->edges[edgeIdx].vertex0 :
|
|
world->edges[-edgeIdx].vertex1;
|
|
|
|
const vertex_t* vertex = &world->vertices[vertIndex];
|
|
Vec3 refPoint = plane->normal * plane->dist;
|
|
Vec3 tangent = (vertex->toVec() - refPoint).normalized();
|
|
Vec3 bitangent = plane->normal.crossProduct(tangent);
|
|
|
|
// Project all face vertices onto the face's plane
|
|
BoundBox bounds;
|
|
for (int edgeListIdx = 0; edgeListIdx < face->ledge_num; ++edgeListIdx)
|
|
{
|
|
edgeIdx = world->edgeList[face->ledge_id + edgeListIdx];
|
|
vertIndex = edgeIdx > 0 ?
|
|
world->edges[edgeIdx].vertex0 :
|
|
world->edges[-edgeIdx].vertex1;
|
|
|
|
vertex_t* vertex = &world->vertices[vertIndex];
|
|
Vec3 vec = vertex->toVec();
|
|
|
|
double x = tangent.dotProduct(vec);
|
|
double y = bitangent.dotProduct(vec);
|
|
bounds.includePoint(Vec3(x, y, 0));
|
|
}
|
|
|
|
Vec3 extents = bounds.max - bounds.min;
|
|
return extents.x * extents.y;
|
|
}
|
|
|
|
int process_faces(const world_t* world, const std::vector<ps1bsp_texture_t>& textures)
|
|
{
|
|
// Write some data to a file
|
|
FILE* fbsp;
|
|
fopen_s(&fbsp, "test.ps1bsp", "wb");
|
|
if (!fbsp)
|
|
return 0;
|
|
|
|
ps1bsp_header_t outHeader = { 0 }; // Write an empty placeholder header first
|
|
outHeader.version = 1;
|
|
fwrite(&outHeader, sizeof(ps1bsp_header_t), 1, fbsp);
|
|
|
|
auto edgeData = analyze_edges(world);
|
|
|
|
// Convert vertex data (no vertex splitting yet)
|
|
std::vector<ps1bsp_vertex_t> outVertices;
|
|
for (unsigned short i = 0; i < world->numVertices; ++i)
|
|
{
|
|
vertex_t* inVertex = &world->vertices[i];
|
|
|
|
ps1bsp_vertex_t outVertex = { 0 };
|
|
// Ensure we don't overflow 16-bit short values. Most Quake maps will stay within these bounds so it *should* be fine (for now).
|
|
if (inVertex->X > -8192 && inVertex->X < 8192 && inVertex->Y > -8192 && inVertex->Y < 8192 && inVertex->Z > -8192 && inVertex->Z < 8192)
|
|
{
|
|
outVertex.x = (short)(inVertex->X * 4);
|
|
outVertex.y = (short)(inVertex->Y * 4);
|
|
outVertex.z = (short)(inVertex->Z * 4);
|
|
}
|
|
else
|
|
{
|
|
printf("Error: vertices found outside of acceptable range: (%f, %f, %f)\n", inVertex->X, inVertex->Y, inVertex->Z);
|
|
fclose(fbsp);
|
|
return 0;
|
|
}
|
|
|
|
outVertices.push_back(outVertex);
|
|
}
|
|
|
|
// Convert faces defined by edges into faces defined by vertex indices
|
|
std::vector<ps1bsp_face_t> outFaces;
|
|
std::vector<ps1bsp_facevertex_t> outFaceVertices;
|
|
FaceBounds faceBounds;
|
|
for (int faceIdx = 0; faceIdx < world->numFaces; ++faceIdx)
|
|
{
|
|
face_t* face = &world->faces[faceIdx];
|
|
const texinfo_t* texinfo = &world->texInfos[face->texinfo_id];
|
|
const miptex_t* miptex = &world->miptexes[texinfo->texture_id];
|
|
const ps1bsp_texture_t& ps1tex = textures[texinfo->texture_id];
|
|
|
|
ps1bsp_face_t outFace = { 0 };
|
|
outFace.planeId = face->plane_id;
|
|
outFace.side = face->side;
|
|
outFace.firstFaceVertex = (unsigned short)outFaceVertices.size();
|
|
outFace.textureId = (unsigned char)texinfo->texture_id;
|
|
|
|
double minS = DBL_MAX, minT = DBL_MAX;
|
|
double maxS = DBL_MIN, maxT = DBL_MIN;
|
|
|
|
// Traverse the list of face edges to collect all of the face's vertices
|
|
Vec3 vertexSum;
|
|
BoundBox bounds;
|
|
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;
|
|
|
|
// Calculate bounding box of this face
|
|
if (edgeListIdx == 0)
|
|
bounds.init(vertexPoint);
|
|
else
|
|
bounds.includePoint(vertexPoint);
|
|
|
|
// Sum all vertices to calculate an average center point
|
|
vertexSum = vertexSum + vertexPoint;
|
|
}
|
|
|
|
// If the texture doesn't tile, we don't need to correct the UVs as much
|
|
double sRange = maxS - minS;
|
|
double tRange = maxT - minT;
|
|
if (sRange < 1) sRange = 1;
|
|
if (tRange < 1) tRange = 1;
|
|
|
|
// Go over the edges again to fudge some UVs for the vertices (this second pass is only necessary because we don't have texture tiling yet)
|
|
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();
|
|
|
|
ps1bsp_facevertex_t faceVertex = { 0 };
|
|
faceVertex.index = vertIndex;
|
|
faceVertex.light = 0;
|
|
|
|
// Calculate texture UVs
|
|
double s = (vertexPoint.dotProduct(texinfo->vectorS) + texinfo->distS) / miptex->width;
|
|
double t = (vertexPoint.dotProduct(texinfo->vectorT) + texinfo->distT) / miptex->height;
|
|
if (minS < 0 || maxS > 1) s = (s - minS) / sRange;
|
|
if (minT < 0 || maxT > 1) t = (t - minT) / tRange;
|
|
|
|
// Rescale the UVs to the dimensions of the mipmap we've selected for our texture atlas
|
|
faceVertex.u = (unsigned char)(s * (ps1tex.w - 1)) + ps1tex.uoffs;
|
|
faceVertex.v = (unsigned char)(t * (ps1tex.h - 1)) + ps1tex.voffs;
|
|
|
|
outFaceVertices.push_back(faceVertex);
|
|
}
|
|
|
|
faceBounds[face] = bounds;
|
|
|
|
// For visualizing and debugging lightmaps
|
|
//if (face->ledge_num >= 10)
|
|
// export_lightmap(world, face, bounds, faceIdx);
|
|
|
|
outFace.numFaceVertices = (unsigned char)(outFaceVertices.size() - outFace.firstFaceVertex);
|
|
outFace.center = convertWorldPosition(vertexSum / outFace.numFaceVertices);
|
|
float area = computeFaceArea(world, face);
|
|
outFace.center.pad = (short)(sqrt(area));
|
|
outFaces.push_back(outFace);
|
|
|
|
gpc_test(world, face);
|
|
}
|
|
|
|
// Iterate over all faces again; now that we know the bounds of each face, we can calculate lighting for all of them
|
|
for (int faceIdx = 0; faceIdx < world->numFaces; ++faceIdx)
|
|
{
|
|
face_t* face = &world->faces[faceIdx];
|
|
ps1bsp_face_t& outFace = outFaces[faceIdx];
|
|
|
|
// Sample lightmap contribution of this face on each vertex
|
|
for (size_t faceVertIdx = 0; faceVertIdx < outFace.numFaceVertices; ++faceVertIdx)
|
|
{
|
|
ps1bsp_facevertex_t& faceVertex = outFaceVertices[outFace.firstFaceVertex + faceVertIdx];
|
|
const vertex_t* vertex = &world->vertices[faceVertex.index];
|
|
faceVertex.light = compute_faceVertex_light5(world, face, faceBounds, vertex->toVec());
|
|
faceVertex.light = (short)((float)faceVertex.light * 1.5f); // Compromise between overbright and non-overbright lighting. Looks good in practice.
|
|
if (faceVertex.light > 255)
|
|
faceVertex.light = 255;
|
|
}
|
|
}
|
|
|
|
// Convert planes
|
|
std::vector<ps1bsp_plane_t> outPlanes;
|
|
for (int planeIdx = 0; planeIdx < world->numPlanes; ++planeIdx)
|
|
{
|
|
plane_t* plane = &world->planes[planeIdx];
|
|
|
|
ps1bsp_plane_t outPlane = { 0 };
|
|
outPlane.normal = convertNormal(plane->normal);
|
|
outPlane.dist = (short)(plane->dist * 4);
|
|
outPlane.type = (short)plane->type;
|
|
|
|
outPlanes.push_back(outPlane);
|
|
}
|
|
|
|
// Convert nodes
|
|
std::vector<ps1bsp_node_t> outNodes;
|
|
for (int nodeIdx = 0; nodeIdx < world->numNodes; ++nodeIdx)
|
|
{
|
|
node_t* node = &world->nodes[nodeIdx];
|
|
|
|
ps1bsp_node_t outNode;
|
|
outNode.planeId = node->plane_id;
|
|
outNode.children[0] = node->front;
|
|
outNode.children[1] = node->back;
|
|
|
|
outNode.firstFace = node->face_id;
|
|
outNode.numFaces = node->face_num;
|
|
|
|
outNodes.push_back(outNode);
|
|
}
|
|
|
|
// Convert leaves
|
|
std::vector<ps1bsp_leaf_t> outLeaves;
|
|
for (int leafIdx = 0; leafIdx < world->numLeaves; ++leafIdx)
|
|
{
|
|
dleaf_t* leaf = &world->leaves[leafIdx];
|
|
|
|
ps1bsp_leaf_t outLeaf;
|
|
outLeaf.type = leaf->type;
|
|
outLeaf.vislist = leaf->vislist;
|
|
outLeaf.firstLeafFace = leaf->lface_id;
|
|
outLeaf.numLeafFaces = leaf->lface_num;
|
|
|
|
//outLeaf.center = convertWorldPosition(leaf->bound.getCenter());
|
|
|
|
outLeaves.push_back(outLeaf);
|
|
}
|
|
|
|
std::vector<unsigned short> outLeafFaces(world->faceList, world->faceList + world->faceListLength);
|
|
std::vector<unsigned char> outVisData(world->visList, world->visList + world->visListLength);
|
|
|
|
// Write collected data to file and update header info
|
|
writeMapData(textures, outHeader.textures, fbsp);
|
|
writeMapData(outVertices, outHeader.vertices, fbsp);
|
|
writeMapData(outFaces, outHeader.faces, fbsp);
|
|
writeMapData(outFaceVertices, outHeader.faceVertices, fbsp);
|
|
writeMapData(outPlanes, outHeader.planes, fbsp);
|
|
writeMapData(outNodes, outHeader.nodes, fbsp);
|
|
writeMapData(outLeaves, outHeader.leaves, fbsp);
|
|
writeMapData(outLeafFaces, outHeader.leafFaces, fbsp);
|
|
writeMapData(outVisData, outHeader.visData, fbsp);
|
|
|
|
// Write final header
|
|
fseek(fbsp, 0, SEEK_SET);
|
|
fwrite(&outHeader, sizeof(ps1bsp_header_t), 1, fbsp);
|
|
fclose(fbsp);
|
|
|
|
printf("PS1BSP: wrote %d vertices, %d faces, %d face verts, %d planes, %d nodes, %d leaves, %d leaf faces\n",
|
|
outVertices.size(), outFaces.size(), outFaceVertices.size(),
|
|
outPlanes.size(), outNodes.size(), outLeaves.size(), outLeafFaces.size());
|
|
|
|
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
|
|
std::vector<ps1bsp_texture_t> textures;
|
|
if (!process_textures(world, textures))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// Inspect faces/edges data
|
|
if (!process_faces(world, textures))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int load_bsp(const char* bspname, world_t* world)
|
|
{
|
|
FILE* f;
|
|
dheader_t* header = &world->header;
|
|
|
|
world->name = bspname;
|
|
|
|
sprintf_s(path, _MAX_PATH, "%s.bsp", bspname);
|
|
fopen_s(&f, path, "rb");
|
|
if (f == NULL)
|
|
return 0;
|
|
|
|
fread(header, sizeof(dheader_t), 1, f);
|
|
printf("Header model version: %d\n", header->version);
|
|
|
|
// Load entities
|
|
fseek(f, header->entities.offset, SEEK_SET);
|
|
world->entitiesLength = header->entities.size + 1;
|
|
world->entities = (char*)malloc(world->entitiesLength * sizeof(char));
|
|
if (world->entities == NULL)
|
|
return 0;
|
|
|
|
memset(world->entities, 0, world->entitiesLength * sizeof(char));
|
|
fread(world->entities, sizeof(char), world->entitiesLength, f);
|
|
|
|
// Load textures
|
|
mipheader_t* mipheader = &world->mipheader;
|
|
fseek(f, header->miptex.offset, SEEK_SET);
|
|
fread(&mipheader->numtex, sizeof(long), 1, f);
|
|
mipheader->offset = (long*)malloc(mipheader->numtex * sizeof(long));
|
|
if (mipheader->offset == NULL)
|
|
return 0;
|
|
|
|
fread(mipheader->offset, sizeof(long), mipheader->numtex, f);
|
|
|
|
world->miptexes = (miptex_t*)malloc(mipheader->numtex * sizeof(miptex_t));
|
|
if (world->miptexes == NULL)
|
|
return 0;
|
|
|
|
const int numMipLevels = 4;
|
|
world->textures = (unsigned char**)malloc(mipheader->numtex * numMipLevels * sizeof(unsigned char*));
|
|
if (world->textures == NULL)
|
|
return 0;
|
|
|
|
memset(world->textures, 0, mipheader->numtex * numMipLevels * sizeof(unsigned char*));
|
|
|
|
for (int texNum = 0; texNum < mipheader->numtex; ++texNum)
|
|
{
|
|
miptex_t* miptex = &world->miptexes[texNum];
|
|
|
|
unsigned long miptexOffset = header->miptex.offset + mipheader->offset[texNum];
|
|
fseek(f, miptexOffset, SEEK_SET);
|
|
fread(miptex, sizeof(miptex_t), 1, f);
|
|
|
|
for (int mipLevel = 0; mipLevel < numMipLevels; ++mipLevel)
|
|
{
|
|
unsigned long mipOffset = *(&miptex->offset1 + mipLevel);
|
|
fseek(f, miptexOffset + mipOffset, SEEK_SET);
|
|
|
|
size_t numBytes = (miptex->width * miptex->height) >> mipLevel;
|
|
unsigned char* texBytes = (unsigned char*)malloc(sizeof(unsigned char) * numBytes);
|
|
if (texBytes == NULL)
|
|
return 0;
|
|
|
|
fread(texBytes, sizeof(unsigned char), numBytes, f);
|
|
world->textures[texNum * numMipLevels + mipLevel] = texBytes;
|
|
}
|
|
}
|
|
|
|
// Load planes
|
|
world->numPlanes = header->planes.size / sizeof(plane_t);
|
|
world->planes = (plane_t*)malloc(header->planes.size);
|
|
if (world->planes == NULL)
|
|
return 0;
|
|
|
|
fseek(f, header->planes.offset, SEEK_SET);
|
|
fread(world->planes, sizeof(plane_t), world->numPlanes, f);
|
|
|
|
// Load vertices
|
|
world->numVertices = header->vertices.size / sizeof(vertex_t);
|
|
world->vertices = (vertex_t*)malloc(header->vertices.size);
|
|
if (world->vertices == NULL)
|
|
return 0;
|
|
|
|
fseek(f, header->vertices.offset, SEEK_SET);
|
|
fread(world->vertices, sizeof(vertex_t), world->numVertices, f);
|
|
|
|
// Load edges
|
|
world->numEdges = header->edges.size / sizeof(edge_t);
|
|
world->edges = (edge_t*)malloc(header->edges.size);
|
|
if (world->edges == NULL)
|
|
return 0;
|
|
|
|
fseek(f, header->edges.offset, SEEK_SET);
|
|
fread(world->edges, sizeof(edge_t), world->numEdges, f);
|
|
|
|
world->edgeListLength = header->ledges.size / sizeof(int);
|
|
world->edgeList = (int*)malloc(header->ledges.size);
|
|
if (world->edgeList == NULL)
|
|
return 0;
|
|
|
|
fseek(f, header->ledges.offset, SEEK_SET);
|
|
fread(world->edgeList, sizeof(int), world->edgeListLength, f);
|
|
|
|
// Load texture info
|
|
world->numTexInfos = header->texinfo.size / sizeof(texinfo_t);
|
|
world->texInfos = (texinfo_t*)malloc(header->texinfo.size);
|
|
if (world->texInfos == NULL)
|
|
return 0;
|
|
|
|
fseek(f, header->texinfo.offset, SEEK_SET);
|
|
fread(world->texInfos, sizeof(texinfo_t), world->numTexInfos, f);
|
|
|
|
// Load faces
|
|
world->numFaces = header->faces.size / sizeof(face_t);
|
|
world->faces = (face_t*)malloc(header->faces.size);
|
|
if (world->faces == NULL)
|
|
return 0;
|
|
|
|
fseek(f, header->faces.offset, SEEK_SET);
|
|
fread(world->faces, sizeof(face_t), world->numFaces, f);
|
|
|
|
world->faceListLength = header->lface.size / sizeof(unsigned short);
|
|
world->faceList = (unsigned short*)malloc(header->lface.size);
|
|
if (world->faceList == NULL)
|
|
return 0;
|
|
|
|
// Load visibility list
|
|
fseek(f, header->lface.offset, SEEK_SET);
|
|
fread(world->faceList, sizeof(unsigned short), world->faceListLength, f);
|
|
|
|
world->visListLength = header->visilist.size / sizeof(unsigned char);
|
|
world->visList = (unsigned char*)malloc(header->visilist.size);
|
|
if (world->visList == NULL)
|
|
return 0;
|
|
|
|
fseek(f, header->visilist.offset, SEEK_SET);
|
|
fread(world->visList, sizeof(unsigned char), world->visListLength, f);
|
|
|
|
// Load nodes
|
|
world->numNodes = header->nodes.size / sizeof(node_t);
|
|
world->nodes = (node_t*)malloc(header->nodes.size);
|
|
if (world->nodes == NULL)
|
|
return 0;
|
|
|
|
fseek(f, header->nodes.offset, SEEK_SET);
|
|
fread(world->nodes, sizeof(node_t), world->numNodes, f);
|
|
|
|
// Load leaves
|
|
world->numLeaves = header->leaves.size / sizeof(dleaf_t);
|
|
world->leaves = (dleaf_t*)malloc(header->leaves.size);
|
|
if (world->leaves == NULL)
|
|
return 0;
|
|
|
|
fseek(f, header->leaves.offset, SEEK_SET);
|
|
fread(world->leaves, sizeof(dleaf_t), world->numLeaves, f);
|
|
|
|
// Load lightmaps
|
|
world->lightmapLength = header->lightmaps.size / sizeof(unsigned char);
|
|
world->lightmap = (unsigned char*)malloc(header->lightmaps.size);
|
|
if (world->lightmap == NULL)
|
|
return 0;
|
|
|
|
fseek(f, header->lightmaps.offset, SEEK_SET);
|
|
fread(world->lightmap, sizeof(unsigned char), world->lightmapLength, f);
|
|
|
|
fclose(f);
|
|
return 1;
|
|
}
|
|
|
|
void free_bsp(world_t* world)
|
|
{
|
|
free(world->lightmap);
|
|
free(world->leaves);
|
|
free(world->nodes);
|
|
free(world->visList);
|
|
|
|
free(world->texInfos);
|
|
free(world->faces);
|
|
free(world->faceList);
|
|
free(world->edges);
|
|
free(world->edgeList);
|
|
free(world->vertices);
|
|
free(world->planes);
|
|
|
|
for (int i = 0; i < world->mipheader.numtex; ++i)
|
|
{
|
|
free(world->textures[i]);
|
|
}
|
|
|
|
free(world->textures);
|
|
free(world->miptexes);
|
|
free(world->mipheader.offset);
|
|
|
|
free(world->entities);
|
|
}
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
world_t world = { 0 };
|
|
if (!load_bsp(argv[1], &world))
|
|
return 1;
|
|
|
|
int result = process_bsp(&world);
|
|
|
|
free_bsp(&world);
|
|
return !result;
|
|
}
|