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.
465 lines
17 KiB
465 lines
17 KiB
#include "common.h"
|
|
#include "world.h"
|
|
#include "display.h"
|
|
#include "time.h"
|
|
#include "draw.h"
|
|
#include "frustum.h"
|
|
|
|
static P_COLOR colors[] =
|
|
{
|
|
{ (255 << 0), 0 }, // Red
|
|
{ (255 << 8), 0 }, // Green
|
|
{ (255 << 16), 0 }, // Blue
|
|
{ (255 << 0) | (255 << 8), 0 },
|
|
{ (255 << 0) | (255 << 16), 0 },
|
|
{ (255 << 8) | (255 << 16), 0 },
|
|
{ (128 << 8) | (255 << 8), 0 },
|
|
{ (255 << 0) | (128 << 8), 0 },
|
|
{ (128 << 0) | (255 << 16), 0 },
|
|
{ (255 << 0) | (128 << 16), 0 },
|
|
{ (128 << 8) | (255 << 16), 0 },
|
|
{ (128 << 8) | (128 << 16), 0 },
|
|
};
|
|
static const int numColors = sizeof(colors) / sizeof(P_COLOR);
|
|
|
|
// Set data pointers directly from an in-memory byte buffer
|
|
#define LOAD_CHUNK(type, dst, num, src, entry) \
|
|
dst = (type*)((src) + (entry).offset); \
|
|
num = (entry).size / sizeof(type);
|
|
|
|
// Allocate memory per chunk and copy data from a byte buffer
|
|
// #define LOAD_CHUNK(type, dst, num, src, entry) \
|
|
// dst = (type*)malloc((entry).size); \
|
|
// memcpy(dst, (src) + (entry).offset, (entry).size); \
|
|
// num = (entry).size / sizeof(type);
|
|
|
|
void world_load(const u_long *data, world_t *world)
|
|
{
|
|
const char *bytes = (const char*)data;
|
|
|
|
ps1bsp_header_t* header = (ps1bsp_header_t*)bytes;
|
|
|
|
LOAD_CHUNK(ps1bsp_worldspawn_t, world->worldSpawn, world->numTextures, bytes, header->worldSpawn);
|
|
LOAD_CHUNK(ps1bsp_atlas_t, world->atlases, world->numAtlases, bytes, header->atlases); // numAtlases contains the number of 32-bit words in a single atlas now
|
|
LOAD_CHUNK(ps1bsp_texture_t, world->textures, world->numTextures, bytes, header->textures);
|
|
LOAD_CHUNK(ps1bsp_vertex_t, world->vertices, world->numVertices, bytes, header->vertices);
|
|
LOAD_CHUNK(ps1bsp_polygon_t, world->polygons, world->numPolygons, bytes, header->polygons);
|
|
LOAD_CHUNK(ps1bsp_polyvertex_t, world->polyVertices, world->numPolyVertices, bytes, header->polyVertices);
|
|
LOAD_CHUNK(ps1bsp_face_t, world->faces, world->numFaces, bytes, header->faces);
|
|
LOAD_CHUNK(ps1bsp_facevertex_t, world->faceVertices, world->numFaceVertices, bytes, header->faceVertices);
|
|
LOAD_CHUNK(ps1bsp_plane_t, world->planes, world->numPlanes, bytes, header->planes);
|
|
LOAD_CHUNK(ps1bsp_node_t, world->nodes, world->numNodes, bytes, header->nodes);
|
|
LOAD_CHUNK(ps1bsp_leaf_t, world->leaves, world->numLeaves, bytes, header->leaves);
|
|
LOAD_CHUNK(ps1bsp_model_t, world->models, world->numModels, bytes, header->models);
|
|
LOAD_CHUNK(u_short, world->leafFaces, world->numLeafFaces, bytes, header->leafFaces);
|
|
LOAD_CHUNK(u_char, world->visData, world->numVisData, bytes, header->visData);
|
|
}
|
|
|
|
static INLINE short world_pointPlaneDist(const VECTOR *point, const ps1bsp_plane_t *plane)
|
|
{
|
|
// Make use of axis-aligned planes to skip the need for a dot product
|
|
if (plane->type < 3)
|
|
return (short)(((int*)point)[plane->type] - plane->dist);
|
|
|
|
return (short)m_pointPlaneDist2(point, &plane->normal, plane->dist);
|
|
}
|
|
|
|
static INLINE short world_cull_backface(const world_t *world, const ps1bsp_face_t *face)
|
|
{
|
|
// Backface culling using the face's plane and center point
|
|
// This eliminates the need for normal clipping per polygon
|
|
VECTOR cam_vec;
|
|
cam_vec.vx = (int)face->center.vx - cam_pos.vx;
|
|
cam_vec.vy = (int)face->center.vy - cam_pos.vy;
|
|
cam_vec.vz = (int)face->center.vz - cam_pos.vz;
|
|
|
|
// Check if the face's plane points towards the camera
|
|
const ps1bsp_plane_t *plane = &world->planes[face->planeId];
|
|
int planeDot = m_dot12(&cam_vec, &plane->normal);
|
|
if ((planeDot >= 0) ^ face->side)
|
|
return 0;
|
|
|
|
// Check if the face is behind the camera
|
|
// NOTE: disabling the behind-the-camera check does *not* actually solve the problem of polygons disappearing when too close to the camera!
|
|
// This means that the GPU probably already clips polygons that stretch too far outside the drawing area. Tessellation should solve this.
|
|
int camDot = m_dot12(&cam_vec, &cam_dir);
|
|
if (camDot < 0)
|
|
return 0;
|
|
|
|
// Flip angle for faces that are on the opposite side of their plane
|
|
char flip = 2 * face->side - 1;
|
|
planeDot = planeDot * flip;
|
|
|
|
// Calculate an LOD value for this face, based on camera position & direction, face normal & area and distance.
|
|
// This is used to determine how many tessellation subdivisions are necessary to prevent texture warping.
|
|
int vecLenSqr = m_dot12(&cam_vec, &cam_vec);
|
|
int lod = (planeDot * vecLenSqr) / (camDot + 1);
|
|
lod = (lod << 6) / (face->center.pad + 1);
|
|
return (short)lod;
|
|
}
|
|
|
|
static u_short world_leafAtPoint(const world_t *world, const VECTOR *point)
|
|
{
|
|
short nodeIdx = 0;
|
|
while (nodeIdx >= 0)
|
|
{
|
|
const ps1bsp_node_t *node = &world->nodes[nodeIdx];
|
|
const ps1bsp_plane_t *plane = &world->planes[node->planeId];
|
|
|
|
short dist = world_pointPlaneDist(point, plane);
|
|
nodeIdx = node->children[(dist > 0) ^ 1];
|
|
}
|
|
|
|
return ~nodeIdx;
|
|
}
|
|
|
|
static void world_drawface_flat(const world_t *world, const ps1bsp_face_t *face, P_COLOR *color, u_long *ot)
|
|
{
|
|
// Draw untextured, vertex colored faces, skipping the entire polygon tessellation step
|
|
const ps1bsp_facevertex_t *faceVertices = &world->faceVertices[face->firstFaceVertex];
|
|
draw_quadstrip_flat(world->vertices, faceVertices, face->numFaceVertices, color, ot);
|
|
}
|
|
|
|
static void world_drawface_fast(const world_t *world, const ps1bsp_face_t *face, u_long *ot)
|
|
{
|
|
// Draw untextured, vertex colored faces, skipping the entire polygon tessellation step
|
|
const ps1bsp_facevertex_t *faceVertices = &world->faceVertices[face->firstFaceVertex];
|
|
draw_quadstrip_colored(world->vertices, faceVertices, face->numFaceVertices, ot);
|
|
}
|
|
|
|
static void world_drawface_lit(const world_t *world, const ps1bsp_face_t *face, u_long *ot)
|
|
{
|
|
// Draw untextured, vertex colored polygons
|
|
const ps1bsp_polygon_t* poly = &world->polygons[face->firstPolygon];
|
|
for (u_char polyIdx = 0; polyIdx < face->numPolygons; ++polyIdx, ++poly)
|
|
{
|
|
const ps1bsp_polyvertex_t *polyVertices = &world->polyVertices[poly->firstPolyVertex];
|
|
draw_quadstrip_lit(world->vertices, polyVertices, poly->numPolyVertices, ot);
|
|
}
|
|
}
|
|
|
|
static void world_drawface_textured(const world_t *world, const ps1bsp_face_t *face, u_long *ot)
|
|
{
|
|
// Draw textured, vertex colored polygons
|
|
const ps1bsp_texture_t *texture = &world->textures[face->textureId];
|
|
const ps1bsp_polygon_t* poly = &world->polygons[face->firstPolygon];
|
|
|
|
u_char tess = face->flags & 3;
|
|
// if (tess)
|
|
// {
|
|
// world_drawface_flat(world, face, &colors[tess - 1], ot);
|
|
// return;
|
|
// }
|
|
|
|
if (face->flags & SURF_DRAWLIQUID)
|
|
{
|
|
for (u_char polyIdx = 0; polyIdx < face->numPolygons; ++polyIdx, ++poly)
|
|
{
|
|
const ps1bsp_polyvertex_t *polyVertices = &world->polyVertices[poly->firstPolyVertex];
|
|
draw_quadstrip_water(world->vertices, polyVertices, poly->numPolyVertices, texture->tpage, face->flags & SURF_DRAWWATER, ot);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (u_char polyIdx = 0; polyIdx < face->numPolygons; ++polyIdx, ++poly)
|
|
{
|
|
const ps1bsp_polyvertex_t *polyVertices = &world->polyVertices[poly->firstPolyVertex];
|
|
switch (poly->numPolyVertices)
|
|
{
|
|
case 3:
|
|
draw_triangle_textured(world->vertices, polyVertices, texture->tpage, ot);
|
|
break;
|
|
default:
|
|
if (tess)
|
|
draw_quadstrip_tess2(world->vertices, polyVertices, poly->numPolyVertices, texture->tpage, ot);
|
|
else
|
|
draw_quadstrip_textured(world->vertices, polyVertices, poly->numPolyVertices, texture->tpage, ot);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void (*world_drawface)(const world_t*, const ps1bsp_face_t*, u_long *ot) = &world_drawface_fast;
|
|
|
|
static void world_drawFaces(const world_t *world, const ps1bsp_face_t *firstFace)
|
|
{
|
|
// Draw the faces in front-to-back order. There are two advantages to this:
|
|
// 1) We don't need to do any depth calculations. The ordering table will be rendered in reverse order, i.e. back-to-front.
|
|
// 2) If we need to stop drawing because the primitive buffer is full, we'll only be culling distant faces.
|
|
for (const ps1bsp_face_t *face = firstFace; face != NULL; face = face->nextFace)
|
|
{
|
|
// Early primitive buffer check
|
|
if (!mem_checkprim(sizeof(POLY_GT4), face->totalQuads))
|
|
break;
|
|
|
|
world_drawface(world, face, curOT);
|
|
}
|
|
}
|
|
|
|
// Simplified BSP subtree traversal specifically for sorting faces of brush models.
|
|
// This skips the frustum culling and PVS culling stages, as this will have already been done on the leafs containing the model.
|
|
static ps1bsp_face_t *world_sortModelFaces(const world_t* world, const ps1bsp_model_t* model, u_long frameNum, ps1bsp_face_t **lastFace)
|
|
{
|
|
short *nodeStack = (short*)(scratchpad + 256);
|
|
u_char stackPtr = 0;
|
|
|
|
// Push the start node onto the stack
|
|
nodeStack[stackPtr++] = model->nodeId0;
|
|
|
|
ps1bsp_face_t *firstFace = NULL;
|
|
while (stackPtr > 0)
|
|
{
|
|
// Pop a node off the stack
|
|
short nodeIdx = nodeStack[--stackPtr];
|
|
|
|
if (nodeIdx < 0) // Leaf node
|
|
{
|
|
u_short leafIdx = ~nodeIdx;
|
|
if (leafIdx == 0) // Leaf 0 should not be drawn
|
|
continue;
|
|
|
|
const ps1bsp_leaf_t *leaf = &world->leaves[leafIdx];
|
|
const u_short *leafFaces = &world->leafFaces[leaf->firstLeafFace];
|
|
for (u_short leafFaceIdx = 0; leafFaceIdx < leaf->numLeafFaces; ++leafFaceIdx)
|
|
{
|
|
ps1bsp_face_t *face = &world->faces[leafFaces[leafFaceIdx]];
|
|
|
|
// Make sure we draw each face only once per frame
|
|
if (face->drawFrame == frameNum)
|
|
continue;
|
|
|
|
face->drawFrame = frameNum;
|
|
|
|
// Cull faces that are facing away from the camera
|
|
if (!world_cull_backface(world, face))
|
|
continue;
|
|
|
|
if (!firstFace)
|
|
*lastFace = face;
|
|
|
|
face->nextFace = firstFace;
|
|
firstFace = face;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
const ps1bsp_node_t *node = &world->nodes[nodeIdx];
|
|
const ps1bsp_plane_t *plane = &world->planes[node->planeId];
|
|
short dist = world_pointPlaneDist(&cam_pos, plane); // TODO: add/subtract model origin from camera to sort faces in model space
|
|
|
|
// Traverse the subtree in back-to-front order
|
|
char order = dist < 0;
|
|
nodeStack[stackPtr++] = node->children[order];
|
|
nodeStack[stackPtr++] = node->children[order ^ 1];
|
|
}
|
|
|
|
return firstFace;
|
|
}
|
|
|
|
static ps1bsp_face_t *world_sortFaces(const world_t *world, const ps1bsp_leaf_t *firstLeaf)
|
|
{
|
|
ps1bsp_face_t *firstFace = NULL;
|
|
u_long frameNum = time_getFrameNumber();
|
|
|
|
// Traverse leaves in back-to-front order
|
|
// This ensures that faces shared between adjacent leaves are drawn in the correct order, with the back leaf being drawn behind
|
|
for (const ps1bsp_leaf_t *leaf = firstLeaf; leaf != NULL; leaf = leaf->nextLeaf)
|
|
{
|
|
const u_short *leafFaces = &world->leafFaces[leaf->firstLeafFace];
|
|
for (u_short leafFaceIdx = 0; leafFaceIdx < leaf->numLeafFaces; ++leafFaceIdx)
|
|
{
|
|
ps1bsp_face_t *face = &world->faces[leafFaces[leafFaceIdx]];
|
|
|
|
// Make sure we draw each face only once per frame
|
|
if (face->drawFrame == frameNum)
|
|
continue;
|
|
|
|
face->drawFrame = frameNum;
|
|
|
|
// Cull faces that are facing away from the camera
|
|
// This also returns an LOD value used to determine tessellation level
|
|
short lod = world_cull_backface(world, face);
|
|
if (!lod)
|
|
continue;
|
|
|
|
face->flags &= ~3;
|
|
if (lod < 64)
|
|
face->flags |= 1;
|
|
else if (lod < 128)
|
|
face->flags |= 2;
|
|
// else if (lod < 256)
|
|
// face->flags |= 3;
|
|
|
|
// Sort the faces to draw in front-to-back order
|
|
face->nextFace = firstFace;
|
|
firstFace = face;
|
|
}
|
|
|
|
// Sort faces for the models in this leaf
|
|
for (const ps1bsp_model_t* model = leaf->models; model != NULL; model = model->nextModel)
|
|
{
|
|
// This successfully sorts the faces within a single model.
|
|
// However, several models occupying the same leaf will still clip through each other
|
|
// and since we're looking at only a single leaf per model, large brush models will often have neighbouring leafs drawing over them.
|
|
ps1bsp_face_t* lastFace;
|
|
ps1bsp_face_t* face = world_sortModelFaces(world, model, frameNum, &lastFace);
|
|
if (face != NULL)
|
|
{
|
|
lastFace->nextFace = firstFace;
|
|
firstFace = face;
|
|
}
|
|
}
|
|
}
|
|
|
|
return firstFace;
|
|
}
|
|
|
|
static void world_sortModels(const world_t *world)
|
|
{
|
|
for (u_short modelIdx = 1; modelIdx < world->numModels; ++modelIdx)
|
|
{
|
|
ps1bsp_model_t* model = (ps1bsp_model_t*)&world->models[modelIdx];
|
|
ps1bsp_leaf_t* leaf = (ps1bsp_leaf_t*)model->currentLeaf;
|
|
|
|
// Update the model's current leaf. This only needs to be done when the model moves.
|
|
if (leaf == NULL)
|
|
{
|
|
VECTOR pos;
|
|
pos.vx = model->origin.vx + model->boundingSphere.vx;
|
|
pos.vy = model->origin.vy + model->boundingSphere.vy;
|
|
pos.vz = model->origin.vz + model->boundingSphere.vz;
|
|
|
|
short leafIdx = world_leafAtPoint(world, &pos);
|
|
if (leafIdx <= 0)
|
|
continue;
|
|
|
|
leaf = (ps1bsp_leaf_t*)&world->leaves[leafIdx];
|
|
model->currentLeaf = leaf;
|
|
}
|
|
|
|
model->nextModel = leaf->models;
|
|
leaf->models = model;
|
|
}
|
|
}
|
|
|
|
static ps1bsp_leaf_t *world_sortLeafs(const world_t *world, short startNode, u_char *pvs)
|
|
{
|
|
short *nodeStack = (short*)(scratchpad + 256); // Place the node stack in fast RAM after the PVS data
|
|
u_char stackPtr = 0;
|
|
|
|
// Push the start node onto the stack
|
|
nodeStack[stackPtr++] = startNode;
|
|
|
|
ps1bsp_leaf_t *firstLeaf = NULL;
|
|
while (stackPtr > 0)
|
|
{
|
|
// Pop a node off the stack
|
|
short nodeIdx = nodeStack[--stackPtr];
|
|
|
|
if (nodeIdx < 0) // Leaf node
|
|
{
|
|
u_short leafIdx = ~nodeIdx;
|
|
if (leafIdx == 0) // Leaf 0 should not be drawn
|
|
continue;
|
|
|
|
// PVS culling
|
|
u_short test = leafIdx - 1;
|
|
if ((pvs[test >> 3] & (1 << (test & 0x7))) == 0)
|
|
continue;
|
|
|
|
// Add the leaf to the sorted linked list
|
|
// Since we're traversing the BSP tree front-to-back, adding each leaf at the start sorts the list in back-to-front order
|
|
ps1bsp_leaf_t *leaf = (ps1bsp_leaf_t*)&world->leaves[leafIdx];
|
|
leaf->nextLeaf = firstLeaf;
|
|
leaf->models = NULL;
|
|
firstLeaf = leaf;
|
|
continue;
|
|
}
|
|
|
|
// Perform frustum culling
|
|
const ps1bsp_node_t *node = &world->nodes[nodeIdx];
|
|
if (!frustum_sphereInside(&node->boundingSphere))
|
|
continue;
|
|
|
|
const ps1bsp_plane_t *plane = &world->planes[node->planeId];
|
|
short dist = world_pointPlaneDist(&cam_pos, plane);
|
|
|
|
// Traverse the BSP tree in front-to-back order
|
|
char order = dist < 0;
|
|
nodeStack[stackPtr++] = node->children[order ^ 1];
|
|
nodeStack[stackPtr++] = node->children[order];
|
|
}
|
|
|
|
return firstLeaf;
|
|
}
|
|
|
|
// Decompress PVS data for the given leaf ID and store it in RAM at the given buffer pointer location.
|
|
// Returns the memory location of decompressed PVS data, and moves the buffer pointer forward.
|
|
static u_char *world_loadVisData(const world_t *world, u_short leafIdx, u_char **buffer)
|
|
{
|
|
u_char *head = *buffer;
|
|
u_char *tail = head;
|
|
|
|
const ps1bsp_leaf_t *leaf = &world->leaves[leafIdx];
|
|
|
|
const u_char *v = &world->visData[leaf->vislist];
|
|
for (int l = 1; l < world->numLeaves; )
|
|
{
|
|
u_char bits = *v++;
|
|
if (bits)
|
|
{
|
|
*tail++ = bits;
|
|
l += 8;
|
|
}
|
|
else
|
|
{
|
|
u_char skip = *v++;
|
|
for (u_char i = 0; i < skip; ++i, l += 8)
|
|
{
|
|
*tail++ = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
*buffer = tail;
|
|
return head;
|
|
}
|
|
|
|
static u_char *world_noVisData(const world_t *world, u_char **buffer)
|
|
{
|
|
u_char *head = *buffer;
|
|
u_char *tail = head;
|
|
|
|
for (int l = 1; l < world->numLeaves; l += 8)
|
|
{
|
|
*tail++ = 0xFF;
|
|
}
|
|
|
|
*buffer = tail;
|
|
return head;
|
|
}
|
|
|
|
void world_draw(const world_t *world)
|
|
{
|
|
// The world doesn't move, so we just set the camera view-projection matrix
|
|
gte_SetRotMatrix(&vp_matrix);
|
|
gte_SetTransMatrix(&vp_matrix);
|
|
|
|
cam_leaf = world_leafAtPoint(world, &cam_pos);
|
|
|
|
u_char *pvsbuf = scratchpad + 32; // Place the PVS data after the frustum in fast RAM
|
|
u_char *pvs = world_loadVisData(world, cam_leaf, &pvsbuf);
|
|
//u_char *pvs = world_noVisData(world, &pvsbuf);
|
|
|
|
ps1bsp_leaf_t *firstLeaf = world_sortLeafs(world, world->models[0].nodeId0, pvs);
|
|
world_sortModels(world);
|
|
ps1bsp_face_t *firstFace = world_sortFaces(world, firstLeaf);
|
|
|
|
if (enableTexturing)
|
|
world_drawface = &world_drawface_textured;
|
|
else
|
|
world_drawface = &world_drawface_lit;
|
|
|
|
world_drawFaces(world, firstFace);
|
|
}
|