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.
338 lines
12 KiB
338 lines
12 KiB
#include "common.h"
|
|
#include "world.h"
|
|
#include "display.h"
|
|
#include "time.h"
|
|
#include "draw.h"
|
|
#include "frustum.h"
|
|
|
|
static CVECTOR colors[] =
|
|
{
|
|
{ 255, 0, 0 },
|
|
{ 0, 255, 0 },
|
|
{ 0, 0, 255 },
|
|
{ 255, 255, 0 },
|
|
{ 255, 0, 255 },
|
|
{ 0, 255, 255 },
|
|
{ 128, 255, 0 },
|
|
{ 255, 128, 0 },
|
|
{ 128, 0, 255 },
|
|
{ 255, 0, 128 },
|
|
{ 0, 128, 255 },
|
|
{ 0, 255, 128 },
|
|
};
|
|
static const int numColors = sizeof(colors) / sizeof(CVECTOR);
|
|
|
|
// 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_planeDot(const SVECTOR *dir, 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*)dir)[plane->type];
|
|
|
|
return (short)m_dot12(dir, &plane->normal);
|
|
}
|
|
|
|
static INLINE char world_cull_backface(const world_t *world, const ps1bsp_face_t *face, short *dot)
|
|
{
|
|
// Backface culling using the face's plane and center point
|
|
// This eliminates the need for normal clipping per polygon
|
|
SVECTOR cam_vec;
|
|
cam_vec.vx = face->center.vx - cam_pos.vx;
|
|
cam_vec.vy = face->center.vy - cam_pos.vy;
|
|
cam_vec.vz = 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];
|
|
*dot = world_planeDot(&cam_vec, plane);
|
|
if ((*dot >= 0) ^ face->side)
|
|
return 1;
|
|
|
|
// Check if the face is behind the camera
|
|
// TODO: this may cull faces close to the camera, use VectorNormalS to normalize and check for angles < -60 degrees
|
|
// TODO: or, just check against all corners of the face...
|
|
// const ps1bsp_facevertex_t *faceVertices = &world->faceVertices[face->firstFaceVertex];
|
|
// for (int vertIdx = 0; vertIdx < face->numFaceVertices; ++vertIdx)
|
|
// {
|
|
// const ps1bsp_vertex_t *vert = &world->vertices[faceVertices[vertIdx].index];
|
|
// cam_vec.vx = vert->x - cam_pos.vx;
|
|
// cam_vec.vy = vert->y - cam_pos.vy;
|
|
// cam_vec.vz = vert->z - cam_pos.vz;
|
|
// if (m_dot12(cam_vec, cam_dir) >= 0)
|
|
// return 0;
|
|
// }
|
|
|
|
// 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.
|
|
if (m_dot12(&cam_vec, &cam_dir) >= 0)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
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];
|
|
|
|
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];
|
|
draw_quadstrip_textured(world->vertices, polyVertices, poly->numPolyVertices, texture->tpage, ot);
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
// NOTE: this value could be REALLY useful for determining the tessellation subdivisions. It has camera distance *and* angle in it.
|
|
// Just include the face size/area for an approximate screen size. Maybe also separate x/y/z for angle-dependent tessellation.
|
|
short dot;
|
|
if (world_cull_backface(world, face, &dot))
|
|
continue;
|
|
|
|
// Sort the faces to draw in front-to-back order
|
|
face->nextFace = firstFace;
|
|
firstFace = face;
|
|
}
|
|
}
|
|
|
|
return firstFace;
|
|
}
|
|
|
|
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;
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
|
|
if (enableTexturing)
|
|
world_drawface = &world_drawface_textured;
|
|
else
|
|
world_drawface = &world_drawface_lit;
|
|
|
|
ps1bsp_leaf_t *firstLeaf = world_sortLeafs(world, world->models[0].nodeId0, pvs);
|
|
ps1bsp_face_t *firstFace = world_sortFaces(world, firstLeaf);
|
|
world_drawFaces(world, firstFace);
|
|
}
|