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.
367 lines
13 KiB
367 lines
13 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_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(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...
|
|
short camDot = m_dot12(cam_vec, cam_dir);
|
|
return camDot < 0;
|
|
}
|
|
|
|
static void world_drawface_fast(const world_t *world, const ps1bsp_face_t *face, u_long *ot)
|
|
{
|
|
// Early primitive buffer check
|
|
if (!mem_checkprim(sizeof(POLY_G4), face->totalQuads))
|
|
return;
|
|
|
|
short dot;
|
|
if (world_cull_backface(world, face, &dot))
|
|
return;
|
|
|
|
// Draw untextured, vertex colored faces, skipping the entire polygon tessellation step
|
|
SVECTOR *verts = (SVECTOR*)(scratchpad + 256);
|
|
SVECTOR *curVert = verts;
|
|
const ps1bsp_facevertex_t *faceVertex = &world->faceVertices[face->firstFaceVertex];
|
|
for (u_char vertIdx = 0; vertIdx < face->numFaceVertices; ++vertIdx, ++faceVertex, ++curVert)
|
|
{
|
|
const ps1bsp_vertex_t *vert = &world->vertices[faceVertex->index];
|
|
curVert->vx = vert->x;
|
|
curVert->vy = vert->y;
|
|
curVert->vz = vert->z;
|
|
curVert->pad = (unsigned short)(size_t)face & 0xFF; // TODO: apply averaged light * color value
|
|
}
|
|
|
|
if (face->numFaceVertices == 3)
|
|
draw_triangle_lit(verts, ot);
|
|
else
|
|
draw_quadstrip_lit(verts, face->numFaceVertices, ot);
|
|
}
|
|
|
|
static void world_drawface_lit(const world_t *world, const ps1bsp_face_t *face, u_long *ot)
|
|
{
|
|
// Early primitive buffer check
|
|
if (!mem_checkprim(sizeof(POLY_G4), face->totalQuads))
|
|
return;
|
|
|
|
short dot;
|
|
if (world_cull_backface(world, face, &dot))
|
|
return;
|
|
|
|
// Draw untextured, vertex colored polygons
|
|
SVECTOR *verts = (SVECTOR*)(scratchpad + 256);
|
|
const ps1bsp_polygon_t* poly = &world->polygons[face->firstPolygon];
|
|
for (u_char polyIdx = 0; polyIdx < face->numPolygons; ++polyIdx, ++poly)
|
|
{
|
|
ps1bsp_polyvertex_t *polyVertex = &world->polyVertices[poly->firstPolyVertex];
|
|
SVECTOR *curVert = verts;
|
|
for (u_char vertIdx = 0; vertIdx < poly->numPolyVertices; ++vertIdx, ++polyVertex, ++curVert)
|
|
{
|
|
const ps1bsp_vertex_t *vert = &world->vertices[polyVertex->index];
|
|
curVert->vx = vert->x;
|
|
curVert->vy = vert->y;
|
|
curVert->vz = vert->z;
|
|
curVert->pad = polyVertex->light;
|
|
}
|
|
|
|
if (poly->numPolyVertices == 3)
|
|
draw_triangle_lit(verts, ot);
|
|
else
|
|
draw_quadstrip_lit(verts, poly->numPolyVertices, ot);
|
|
}
|
|
}
|
|
|
|
static void world_drawface_textured(const world_t *world, const ps1bsp_face_t *face, u_long *ot)
|
|
{
|
|
// Early primitive buffer check
|
|
if (!mem_checkprim(sizeof(POLY_GT4), face->totalQuads))
|
|
return;
|
|
|
|
// 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))
|
|
return;
|
|
|
|
// Draw textured, vertex colored polygons
|
|
STVECTOR *verts = (STVECTOR*)(scratchpad + 256);
|
|
ps1bsp_texture_t *faceTexture = &world->textures[face->textureId];
|
|
const ps1bsp_polygon_t* poly = &world->polygons[face->firstPolygon];
|
|
|
|
if (face->flags & SURF_DRAWWATER)
|
|
{
|
|
for (u_char polyIdx = 0; polyIdx < face->numPolygons; ++polyIdx, ++poly)
|
|
{
|
|
ps1bsp_polyvertex_t *polyVertex = &world->polyVertices[poly->firstPolyVertex];
|
|
STVECTOR *curVert = verts;
|
|
for (u_char vertIdx = 0; vertIdx < poly->numPolyVertices; ++vertIdx, ++polyVertex, ++curVert)
|
|
{
|
|
const ps1bsp_vertex_t *vert = &world->vertices[polyVertex->index];
|
|
curVert->vx = vert->x;
|
|
curVert->vy = vert->y;
|
|
curVert->vz = vert->z;
|
|
curVert->u = (u_short)polyVertex->u;
|
|
curVert->v = (u_short)polyVertex->v;
|
|
}
|
|
|
|
draw_quadstrip_water(verts, poly->numPolyVertices, faceTexture->tpage, ot);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (u_char polyIdx = 0; polyIdx < face->numPolygons; ++polyIdx, ++poly)
|
|
{
|
|
ps1bsp_polyvertex_t *polyVertex = &world->polyVertices[poly->firstPolyVertex];
|
|
STVECTOR *curVert = verts;
|
|
for (u_char vertIdx = 0; vertIdx < poly->numPolyVertices; ++vertIdx, ++polyVertex, ++curVert)
|
|
{
|
|
const ps1bsp_vertex_t *vert = &world->vertices[polyVertex->index];
|
|
curVert->vx = vert->x;
|
|
curVert->vy = vert->y;
|
|
curVert->vz = vert->z;
|
|
curVert->u = (u_short)polyVertex->u;
|
|
curVert->v = (u_short)polyVertex->v;
|
|
curVert->pad = polyVertex->light;
|
|
}
|
|
|
|
if (poly->numPolyVertices == 3)
|
|
draw_triangle_textured(verts, faceTexture->tpage, ot);
|
|
else
|
|
draw_quadstrip_textured(verts, poly->numPolyVertices, faceTexture->tpage, ot);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void (*world_drawface)(const world_t*, const ps1bsp_face_t*, u_long *ot) = &world_drawface_fast;
|
|
|
|
static ps1bsp_leaf_t *firstLeaf = NULL;
|
|
static u_short leafDepth = 0;
|
|
|
|
static void world_drawLeafs(const world_t *world)
|
|
{
|
|
u_long frameNum = time_getFrameNumber();
|
|
|
|
// Draw each visible leaf's faces in front-to-back order
|
|
// This way if we run out of primitive buffer space, we will stop drawing at far-away faces
|
|
for (const ps1bsp_leaf_t *leaf = firstLeaf; leaf != NULL; leaf = leaf->nextLeaf)
|
|
{
|
|
u_long *ot = curOT + leaf->leafDepth;
|
|
|
|
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]];
|
|
|
|
// Check if we've already drawn this face on the current frame
|
|
// NOTE: this can cause sorting issues when rendering front-to-back with leaf-based depth
|
|
if (face->drawFrame == frameNum)
|
|
continue;
|
|
|
|
world_drawface(world, face, ot);
|
|
face->drawFrame = frameNum;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void world_sortLeafs(const world_t *world, short nodeIdx, u_char *pvs)
|
|
{
|
|
if (nodeIdx < 0) // Leaf node
|
|
{
|
|
u_short leafIdx = ~nodeIdx;
|
|
if (leafIdx == 0) // Leaf 0 should not be drawn
|
|
return;
|
|
|
|
// PVS culling
|
|
u_short test = leafIdx - 1;
|
|
if ((pvs[test >> 3] & (1 << (test & 0x7))) == 0)
|
|
return;
|
|
|
|
// 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->leafDepth = leafDepth++;
|
|
firstLeaf = leaf;
|
|
return;
|
|
}
|
|
|
|
// Perform frustum culling
|
|
const ps1bsp_node_t *node = &world->nodes[nodeIdx];
|
|
if (!frustum_sphereInside(&node->boundingSphere))
|
|
return;
|
|
|
|
const ps1bsp_plane_t *plane = &world->planes[node->planeId];
|
|
short dist = world_pointPlaneDist(&cam_pos, plane);
|
|
|
|
// Sort leafs in front-to-back order, so that we can draw their faces at the correct depths
|
|
char order = dist < 0;
|
|
world_sortLeafs(world, node->children[order], pvs);
|
|
world_sortLeafs(world, node->children[order ^ 1], pvs);
|
|
}
|
|
|
|
// 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;
|
|
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;
|
|
|
|
firstLeaf = NULL;
|
|
leafDepth = 1;
|
|
world_sortLeafs(world, 0, pvs);
|
|
world_drawLeafs(world);
|
|
}
|