Browse Source

Separated file loading from BSP processing, making it easier to use data from different sources and also ensuring everything gets cleaned up properly at the end.

master
Nico de Poel 3 years ago
parent
commit
bd339fc737
  1. 6
      PS1BSP.vcxproj
  2. 21
      PS1BSP.vcxproj.filters
  3. 75
      bsp.h
  4. 342
      main.cpp
  5. 44
      ps1bsp.h

6
PS1BSP.vcxproj

@ -146,6 +146,12 @@
<ItemGroup>
<ClInclude Include="bsp.h" />
<ClInclude Include="ps1bsp.h" />
<ClInclude Include="rectpack\best_bin_finder.h" />
<ClInclude Include="rectpack\empty_spaces.h" />
<ClInclude Include="rectpack\empty_space_allocators.h" />
<ClInclude Include="rectpack\finders_interface.h" />
<ClInclude Include="rectpack\insert_and_split.h" />
<ClInclude Include="rectpack\rect_structs.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">

21
PS1BSP.vcxproj.filters

@ -13,6 +13,9 @@
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="Header Files\rectpack2D">
<UniqueIdentifier>{4da4a411-1acb-4601-bb08-8a5edfacf240}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="main.cpp">
@ -26,5 +29,23 @@
<ClInclude Include="ps1bsp.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="rectpack\best_bin_finder.h">
<Filter>Header Files\rectpack2D</Filter>
</ClInclude>
<ClInclude Include="rectpack\empty_space_allocators.h">
<Filter>Header Files\rectpack2D</Filter>
</ClInclude>
<ClInclude Include="rectpack\empty_spaces.h">
<Filter>Header Files\rectpack2D</Filter>
</ClInclude>
<ClInclude Include="rectpack\finders_interface.h">
<Filter>Header Files\rectpack2D</Filter>
</ClInclude>
<ClInclude Include="rectpack\insert_and_split.h">
<Filter>Header Files\rectpack2D</Filter>
</ClInclude>
<ClInclude Include="rectpack\rect_structs.h">
<Filter>Header Files\rectpack2D</Filter>
</ClInclude>
</ItemGroup>
</Project>

75
bsp.h

@ -52,8 +52,8 @@ typedef struct // Bounding Box, Float values
typedef struct // Bounding Box, Short values
{
short min; // minimum values of X,Y,Z
short max; // maximum values of X,Y,Z
short min[3]; // minimum values of X,Y,Z
short max[3]; // maximum values of X,Y,Z
} bboxshort_t;
typedef struct // Mip texture list header
@ -80,6 +80,14 @@ typedef struct
float Z; // but coded in floating point
} vertex_t;
typedef struct
{
unsigned short vertex0; // index of the start vertex
// must be in [0,numvertices[
unsigned short vertex1; // index of the end vertex
// must be in [0,numvertices[
} edge_t;
typedef struct
{
unsigned short plane_id; // The plane in which the face lies
@ -96,3 +104,66 @@ typedef struct
long lightmap; // Pointer inside the general light map, or -1
// this define the start of the face light map
} face_t;
typedef struct
{
long plane_id; // The plane that splits the node
// must be in [0,numplanes[
unsigned short front; // If bit15==0, index of Front child node
// If bit15==1, ~front = index of child leaf
unsigned short back; // If bit15==0, id of Back child node
// If bit15==1, ~back = id of child leaf
bboxshort_t box; // Bounding box of node and all childs
unsigned short face_id; // Index of first Polygons in the node
unsigned short face_num; // Number of faces in the node
} node_t;
typedef struct
{
long type; // Special type of leaf
long vislist; // Beginning of visibility lists
// must be -1 or in [0,numvislist[
bboxshort_t bound; // Bounding box of the leaf
unsigned short lface_id; // First item of the list of faces
// must be in [0,numlfaces[
unsigned short lface_num; // Number of faces in the leaf
unsigned char sndwater; // level of the four ambient sounds:
unsigned char sndsky; // 0 is no sound
unsigned char sndslime; // 0xFF is maximum volume
unsigned char sndlava; //
} dleaf_t;
typedef struct
{
const char* name;
dheader_t header;
mipheader_t mipheader;
miptex_t* miptexes;
unsigned char** textures;
int numVertices;
vertex_t* vertices;
int numEdges;
edge_t* edges;
int edgeListLength;
unsigned short* edgeList;
int numFaces;
face_t* faces;
int faceListLength;
unsigned short* faceList;
int numNodes;
node_t* nodes;
int numLeaves;
dleaf_t* leaves;
int entitiesLength;
char* entities;
} world_t;

342
main.cpp

@ -1,38 +1,22 @@
#include <memory.h>
#include <stdlib.h>
#include <stdio.h>
#include <vector>
#include <unordered_map>
#include "bsp.h"
#include "rectpack/finders_interface.h"
#include "ps1bsp.h"
static char path[_MAX_PATH];
int process_entities(dheader_t* header, FILE* f)
int process_entities(const world_t *world)
{
fseek(f, header->entities.offset, SEEK_SET);
char* entities = (char*)malloc((header->entities.size + 1) * sizeof(char));
if (entities == NULL)
return 0;
memset(entities, 0, (header->entities.size + 1) * sizeof(char));
fread(entities, sizeof(char), header->entities.size, f);
printf("Entities list:\n%s\n", entities);
free(entities);
printf("Entities list:\n%s\n", world->entities);
return 1;
}
int process_textures(const char* bspname, dheader_t* header, FILE* f)
int process_textures(const world_t* world)
{
fseek(f, header->miptex.offset, SEEK_SET);
mipheader_t mipheader;
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);
using spaces_type = rectpack2D::empty_spaces<false>;
using rect_type = rectpack2D::output_rect_t<spaces_type>;
@ -49,22 +33,18 @@ int process_textures(const char* bspname, dheader_t* header, FILE* f)
std::vector<rect_type> rectangles;
miptex_t miptex;
// Try some texture packing and see if we fit inside the PS1's VRAM
for (int texNum = 0; texNum < mipheader.numtex; ++texNum)
for (int texNum = 0; texNum < world->mipheader.numtex; ++texNum)
{
unsigned long miptexOffset = header->miptex.offset + mipheader.offset[texNum];
fseek(f, miptexOffset, SEEK_SET);
fread(&miptex, sizeof(miptex_t), 1, f);
miptex_t *miptex = &world->miptexes[texNum];
//printf("Texture %d (%dx%d): %.16s\n", texNum, miptex.width, miptex.height, miptex.name);
// Shrink the larger textures, but keep smaller ones at their original size
int ps1mip = miptex.width > 64 || miptex.height > 64 ? 1 : 0;
int ps1mip = miptex->width > 64 || miptex->height > 64 ? 1 : 0;
if (strcmp(miptex.name, "clip") && strcmp(miptex.name, "trigger"))
rectangles.emplace_back(rectpack2D::rect_xywh(0, 0, miptex.width >> ps1mip, miptex.height >> ps1mip));
if (strcmp(miptex->name, "clip") && strcmp(miptex->name, "trigger"))
rectangles.emplace_back(rectpack2D::rect_xywh(0, 0, miptex->width >> ps1mip, miptex->height >> ps1mip));
else
rectangles.emplace_back(rectpack2D::rect_xywh(0, 0, 0, 0));
}
@ -81,7 +61,7 @@ int process_textures(const char* bspname, dheader_t* header, FILE* f)
)
);
printf("%d textures. Packed texture atlas size: %d x %d\n", mipheader.numtex, result_size.w, result_size.h);
printf("%d textures. Packed texture atlas size: %d x %d\n", world->mipheader.numtex, result_size.w, result_size.h);
unsigned char* atlas = (unsigned char*)malloc(result_size.w * result_size.h * sizeof(unsigned char));
if (atlas == NULL)
return 0;
@ -89,36 +69,30 @@ int process_textures(const char* bspname, dheader_t* header, FILE* f)
memset(atlas, 0, result_size.w * result_size.h * sizeof(unsigned char));
// Try to construct the texture atlas, see what we get
for (int texNum = 0; texNum < mipheader.numtex; ++texNum)
for (int texNum = 0; texNum < world->mipheader.numtex; ++texNum)
{
unsigned long miptexOffset = header->miptex.offset + mipheader.offset[texNum];
fseek(f, miptexOffset, SEEK_SET);
fread(&miptex, sizeof(miptex_t), 1, f);
miptex_t* miptex = &world->miptexes[texNum];
char* outName = miptex.name;
char* outName = miptex->name;
if (*outName == '*' || *outName == '+')
outName++;
for (int mipLevel = 0; mipLevel < 4; ++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);
fread(texBytes, sizeof(unsigned char), numBytes, f);
unsigned char* texBytes = world->textures[texNum * 4 + mipLevel];
FILE* fout;
sprintf_s(path, _MAX_PATH, "textures/%s-%s-mip%d-%dx%d.raw", bspname, outName, mipLevel, miptex.width >> mipLevel, miptex.height >> mipLevel);
fopen_s(&fout, path, "wb");
if (fout != NULL)
FILE* fraw;
sprintf_s(path, _MAX_PATH, "textures/%s-%s-mip%d-%dx%d.raw", world->name, outName, mipLevel, miptex->width >> mipLevel, miptex->height >> mipLevel);
fopen_s(&fraw, path, "wb");
if (fraw != NULL)
{
fwrite(texBytes, sizeof(unsigned char), numBytes, fout);
fclose(fout);
size_t numBytes = (miptex->width * miptex->height) >> mipLevel;
fwrite(texBytes, sizeof(unsigned char), numBytes, fraw);
fclose(fraw);
}
const auto& rectangle = rectangles[texNum];
if (miptex.width >> mipLevel == rectangle.w) // This is the mip level we've previously decided we want for our PS1 atlas
if (miptex->width >> mipLevel == rectangle.w) // This is the mip level we've previously decided we want for our PS1 atlas
{
//printf("Writing texture %s mip %d to position: (%d, %d) w = %d, h = %d\n", miptex.name, mipLevel, rectangle.x, rectangle.y, rectangle.w, rectangle.h);
for (int y = 0; y < rectangle.h; ++y)
@ -126,13 +100,11 @@ int process_textures(const char* bspname, dheader_t* header, FILE* f)
memcpy_s(atlas + ((rectangle.y + y) * result_size.w + rectangle.x), rectangle.w * sizeof(unsigned char), texBytes + (y * rectangle.w), rectangle.w * sizeof(unsigned char));
}
}
free(texBytes);
}
}
FILE* fatlas;
sprintf_s(path, _MAX_PATH, "%s-atlas-%dx%d.raw", bspname, result_size.w, result_size.h);
sprintf_s(path, _MAX_PATH, "%s-atlas-%dx%d.raw", world->name, result_size.w, result_size.h);
fopen_s(&fatlas, path, "wb");
if (fatlas != NULL)
{
@ -141,26 +113,15 @@ int process_textures(const char* bspname, dheader_t* header, FILE* f)
}
free(atlas);
free(mipheader.offset);
return 1;
}
int process_vertices(dheader_t* header, FILE* f)
int process_vertices(const world_t* world)
{
int numVertices = header->vertices.size / sizeof(vertex_t);
int numFaces = header->faces.size / sizeof(face_t);
vertex_t* vertices = (vertex_t*)malloc(header->vertices.size);
if (vertices == NULL)
return 0;
fseek(f, header->vertices.offset, SEEK_SET);
fread(vertices, sizeof(vertex_t), numVertices, f);
vec3_t min = { FLT_MAX, FLT_MAX, FLT_MAX }, max = { -FLT_MAX, -FLT_MAX, -FLT_MAX };
for (int vertIdx = 0; vertIdx < numVertices; ++vertIdx)
for (int vertIdx = 0; vertIdx < world->numVertices; ++vertIdx)
{
vertex_t* vert = &vertices[vertIdx];
vertex_t* vert = &world->vertices[vertIdx];
if (vert->X > max.x) max.x = vert->X;
if (vert->Y > max.y) max.y = vert->Y;
if (vert->Z > max.z) max.z = vert->Z;
@ -169,54 +130,269 @@ int process_vertices(dheader_t* header, FILE* f)
if (vert->Z < min.z) min.z = vert->Z;
}
printf("%d vertices, %d faces, min = (%f, %f, %f), max = (%f, %f, %f)\n", numVertices, numFaces, min.x, min.y, min.z, max.x, max.y, max.z);
printf("%d vertices, %d faces, min = (%f, %f, %f), max = (%f, %f, %f)\n", world->numVertices, world->numFaces, min.x, min.y, min.z, max.x, max.y, max.z);
const int fixedScale = 1 << 14;
int fixedMin[3] = { (int)(min.x * fixedScale), (int)(min.y * fixedScale), (int)(min.z * fixedScale) };
int fixedMax[3] = { (int)(max.x * fixedScale), (int)(max.y * fixedScale), (int)(max.z * fixedScale) };
printf("Fixed point min = (%d, %d, %d), max = (%d, %d, %d)\n", fixedMin[0], fixedMin[1], fixedMin[2], fixedMax[0], fixedMax[1], fixedMax[2]);
return 1;
}
int process_bsp(const char* bspname)
typedef struct
{
FILE* f;
dheader_t header;
vertex_t* vertex;
unsigned short index;
} vertexref_t;
sprintf_s(path, _MAX_PATH, "%s.bsp", bspname);
fopen_s(&f, path, "rb");
if (f == NULL)
int process_faces(const world_t* world)
{
// Write some data to a file
FILE* fbsp;
fopen_s(&fbsp, "test.ps1bsp", "wb");
if (!fbsp)
return 0;
fread(&header, sizeof(dheader_t), 1, f);
printf("Header model version: %d\n", header.version);
ps1bsp_header_t outHeader = { 0 }; // Write an empty placeholder header first
fwrite(&outHeader, sizeof(ps1bsp_header_t), 1, fbsp);
// TODO: convert vertices and group them by material properties (texture, lightmap) and floating origin zone (based on leaf data), duplicate where necessary
// Organize vertex indices so we can shuffle them around and duplicate them, while keeping track of which vertex is where
std::unordered_map<vertex_t*, vertexref_t> vertexRefs;
for (unsigned short i = 0; i < world->header.vertices.size / sizeof(vertex_t); ++i)
{
vertex_t* v = &world->vertices[i];
vertexRefs[v] = vertexref_t{ v, i };
}
// Write vertex data to a file (no vertex splitting yet)
for (unsigned short i = 0; i < world->header.vertices.size / sizeof(vertex_t); ++i)
{
// TODO: we should respect the ordering from vertexRef->index here but meh, problem for later
vertex_t* inVertex = &world->vertices[i];
ps1bsp_vertex_t outVertex = { 0 };
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);
}
outVertex.baseLight = 128;
outVertex.finalLight = 128;
fwrite(&outVertex, sizeof(ps1bsp_vertex_t), 1, fbsp);
}
outHeader.numVertices = (unsigned short)(world->header.vertices.size / sizeof(vertex_t));
std::vector<vertexref_t*> faceVerts;
for (int faceIdx = 0; faceIdx < world->numFaces; ++faceIdx)
{
face_t* face = &world->faces[faceIdx];
for (int edgeListIdx = 0; edgeListIdx < face->ledge_num; ++edgeListIdx)
{
short edgeIdx = world->edgeList[face->ledge_id + edgeListIdx];
int vertIndex = edgeIdx > 0 ?
world->edges[edgeIdx].vertex0 :
world->edges[-edgeIdx].vertex1;
vertex_t* v = &world->vertices[vertIndex];
faceVerts.push_back(&vertexRefs[v]);
}
printf("Face %d: %d vertices\n", faceIdx, faceVerts.size());
// Triangulate face into polygons (triangle fan, the naive method)
// Write polygon and face data to a file
faceVerts.clear();
}
// Write final header
fseek(fbsp, 0, SEEK_SET);
fwrite(&outHeader, sizeof(ps1bsp_header_t), 1, fbsp);
fclose(fbsp);
return 1;
}
int process_bsp(const world_t *world)
{
// Test reading the entity string data
if (!process_entities(&header, f))
if (!process_entities(world))
{
fclose(f);
return 0;
}
// Test exporting texture data
if (!process_textures(bspname, &header, f))
if (!process_textures(world))
{
fclose(f);
return 0;
}
// Inspect vertex data
if (!process_vertices(&header, f))
if (!process_vertices(world))
{
fclose(f);
return 0;
}
// Inspect faces/edges data
if (!process_faces(world))
{
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 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(unsigned short);
world->edgeList = (unsigned short*)malloc(header->ledges.size);
if (world->edgeList == NULL)
return 0;
fseek(f, header->ledges.offset, SEEK_SET);
fread(world->edgeList, sizeof(unsigned short), world->edgeListLength, 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;
fseek(f, header->lface.offset, SEEK_SET);
fread(world->faceList, sizeof(unsigned short), world->faceListLength, f);
fclose(f);
return 1;
}
void free_bsp(world_t* world)
{
free(world->faces);
free(world->faceList);
free(world->edges);
free(world->edgeList);
free(world->vertices);
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)
{
return !process_bsp(argv[1]);
world_t world = { 0 };
if (!load_bsp(argv[1], &world))
return 1;
int result = process_bsp(&world);
free_bsp(&world);
return !result;
}

44
ps1bsp.h

@ -5,6 +5,27 @@
extern "C" {
#endif
/*
Probable rendering process:
- Determine visible leaves based on PVS and frustum culling, the usual.
- Chain together faces/polygons to be drawn. Possibly grouped by texture ID?
Texture chaining might improve performance by making better use of texture cache, but we'll still be separating polygons based on depth anyway, so the advantage is questionable.
Texture chaining should probably be the last optimization we try.
- Tessellate polygons close to the camera by recursively cutting edges in two, up to X times based on camera distance/screen size. Possibly GTE can aid with averaging coordinates? => look at GPF/GPL (general purpose interpolation)
- Collect all vertices that need to be transformed, put them through GTE, update lighting values if needed, and cache the results.
(It may not be worth it to collect and precalculate vertices, as keeping track of all the administration also comes at a considerable cost.)
- Draw all the (tessellated) polygons using the precalculated vertex positions. Use GTE to calculate average depth and order polygons.
Note: we may not have to calculate average depth for BSP polygons, as the leafs already provide an ordering, and leafs are convex so there is no need to sort the polygons within.
We do however need some kind of depth value per leaf to insert alias models at the correct positions in the ordering table.
*/
typedef struct
{
unsigned short numVertices;
unsigned short numTriangles;
unsigned short numFaces;
} ps1bsp_header_t;
typedef struct
{
unsigned char w, h; // These may be necessary for scaling UVs, especially since we use a mix of mip0 and mip1 textures
@ -22,9 +43,21 @@ typedef struct
short y;
short z;
unsigned char baseLight, finalLight; // Used for gouraud shading based on static lightmap data
// Sampled color value from the face texture, for untextured gouraud shaded drawing
unsigned char a : 1; // 0 = opaque, 1 = semi-transparent
unsigned char r : 5;
unsigned char g : 5;
unsigned char b : 5;
#ifdef _WIN32
// Extra fields used for administration during the conversion process
unsigned short index;
#endif
} ps1bsp_vertex_t;
// Instead of edges as in the original BSP format, we store triangles for easy consumption by the PS1
// Note: it may actually be more efficient to render quads for faces with 4+ vertices
typedef struct
{
unsigned short vertex0;
@ -32,6 +65,15 @@ typedef struct
unsigned short vertex2;
} ps1bsp_triangle_t;
typedef struct
{
unsigned short firstTriangleId;
unsigned short numTriangle;
unsigned short firstQuadId; // For if/when we decide to add quads to the mix
unsigned short numQuads;
} ps1bsp_face_t;
// Pre-parsed and encoded entity data (this runs the risk of becoming too bloated)
typedef struct
{
@ -39,7 +81,7 @@ typedef struct
short angle[3]; // Can store both mangle (all axes) and just angle (Z-axis rotation only)
int origin[3]; // In 12-bit fixed point coordinates
unsigned int spawnflags;
unsigned short message_id; // Index into a pool of pre-defined messages
unsigned short messageId; // Index into a pool of pre-defined messages
} ps1bsp_entity_t;
typedef struct

Loading…
Cancel
Save