From 5a3b6aac46a0113d6315d742da8fe4279c970b03 Mon Sep 17 00:00:00 2001 From: Nico de Poel Date: Thu, 12 Jan 2023 16:12:20 +0100 Subject: [PATCH] First implementation of world loading and rendering. No BSP logic or optimizations in here yet, but something is being displayed! --- CMakeLists.txt | 2 +- common.h | 2 +- display.c | 39 +++++++++++++++----- display.h | 5 +++ main.c | 19 +++++++--- ps1bsp.h | 97 +++++++++++++++++++++++++++++++++++++++++++++++++ qmath.c | 34 +++++++++++++++++ qmath.h | 6 +++ test.ps1bsp | Bin 0 -> 83502 bytes world.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++++ world.h | 17 +++++++++ 11 files changed, 298 insertions(+), 18 deletions(-) create mode 100755 ps1bsp.h create mode 100644 qmath.c create mode 100644 qmath.h create mode 100755 test.ps1bsp create mode 100644 world.c create mode 100644 world.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 84c4fc9..e1d8e77 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ file(GLOB _sources *.c) psn00bsdk_add_executable(ps1bsp STATIC ${_sources}) psn00bsdk_target_incbin(ps1bsp PRIVATE tim_e1m1 atlas-e1m1.tim) -psn00bsdk_target_incbin(ps1bsp PRIVATE tim_e2m2 atlas-e2m2.tim) +psn00bsdk_target_incbin(ps1bsp PRIVATE bsp_test test.ps1bsp) psn00bsdk_add_cd_image( iso # Target name diff --git a/common.h b/common.h index 5603df1..3bdbc3c 100644 --- a/common.h +++ b/common.h @@ -8,7 +8,7 @@ #include #include -#define RotMatrixQ RotMatrix // TODO: temporary hack to allow Quake-specific code without implementation +#include "qmath.h" extern VECTOR cam_pos; extern SVECTOR cam_rot; diff --git a/display.c b/display.c index b690d49..d16daad 100644 --- a/display.c +++ b/display.c @@ -8,11 +8,13 @@ static DISPENV disp[2]; static DRAWENV draw[2]; static int db; -u_long ot[2][OTLEN]; // Ordering tables, two arrays for double buffering. These are basically the buckets for bucket sorting of polygons. +#define PRIMBUFLEN 131072 + +static u_long ot[2][OTLEN]; // Ordering tables, two arrays for double buffering. These are basically the buckets for bucket sorting of polygons. // You can also see them as "layers" in the modern 3D graphics sense, but they serve a more immediate role for polygon ordering. // Layer 0 is free for overlays, as polygons at depth 0 will be clipped. -char primbuff[2][65536]; // Primitive buffer, just a raw buffer of bytes to use as a pool for primitives -char *nextpri; +static char primbuff[2][PRIMBUFLEN]; // Primitive buffer, just a raw buffer of bytes to use as a pool for primitives +static char *nextpri; const MATRIX identity = { ONE, 0, 0, @@ -41,6 +43,9 @@ MATRIX light_dirs = { 0, 0, 0 }; +MATRIX vp_matrix; +u_long *curOT; + // Scale X coordinates to correct the aspect ratio for the chosen resolution VECTOR aspect_scale = { SCREENWIDTH * ONE / 320 , ONE, ONE }; @@ -76,12 +81,10 @@ void display_init() PutDrawEnv(&draw[db]); // Load test font - FntLoad(960, 448); + FntLoad(960, 0); // Open up a test font text stream - FntOpen(0, 8, SCREENWIDTH, SCREENHEIGHT, 0, 200); - - // TODO: OT initialization + FntOpen(0, 8, SCREENWIDTH, SCREENHEIGHT, 0, 512); // Initialize GTE InitGeom(); @@ -91,7 +94,10 @@ void display_init() void display_start() { - ClearOTagR(ot[db], OTLEN); + curOT = ot[db]; + ClearOTagR(curOT, OTLEN); + + nextpri = primbuff[db]; gte_SetBackColor(48, 48, 48); // Ambient light color gte_SetColorMatrix(&light_cols); // Light color (up to three different lights) @@ -107,14 +113,16 @@ void display_start() TransMatrix(&view_matrix, &tpos); // Apply transformed position to the translation part of the view matrix // Compose view and projection matrices to obtain a combined view-projection matrix - CompMatrixLV(&proj_matrix, &view_matrix, &view_matrix); + CompMatrixLV(&proj_matrix, &view_matrix, &vp_matrix); } void display_finish() { + DrawOTag(curOT + OTLEN - 1); // This performs a DMA transfer to quickly send all the primitives off to the GPU + // Flip buffer index db = !db; - + // Wait for all drawing to complete DrawSync(0); @@ -129,3 +137,14 @@ void display_finish() // Enable display output, ResetGraph() disables it by default SetDispMask(1); } + +void *display_allocPrim(size_t size) +{ + if (nextpri + size > primbuff[db] + PRIMBUFLEN) + return NULL; + + // TODO: maybe add a bounds check? + void *prim = nextpri; + nextpri += size; + return prim; +} diff --git a/display.h b/display.h index 15d7f21..5b88d96 100644 --- a/display.h +++ b/display.h @@ -6,8 +6,13 @@ #define OTLEN 1024 +extern MATRIX vp_matrix; +extern u_long *curOT; + void display_init(); void display_start(); void display_finish(); +void *display_allocPrim(size_t size); + #endif // __DISPLAY_H__ diff --git a/main.c b/main.c index f15b675..2613dc3 100644 --- a/main.c +++ b/main.c @@ -3,12 +3,16 @@ #include "display.h" #include "time.h" #include "asset.h" +#include "world.h" extern u_long tim_e1m1[]; -extern u_long tim_e2m2[]; +extern u_long bsp_test[]; -VECTOR cam_pos = { 0, -400, 100 }; -SVECTOR cam_rot = { 0 }; +VECTOR cam_pos = { 2176, 1152, 128 }; // START +//VECTOR cam_pos = { 1920, -1408, 352 }; // E1M1 +SVECTOR cam_rot = { 0, 0, 0 }; + +world_t world; // BSP face rendering: // - Gather vertex data from face start index + length @@ -24,8 +28,9 @@ void init(void) input_init(); display_init(); - asset_loadTexture(tim_e1m1, NULL); - asset_loadTexture(tim_e2m2, NULL); + //asset_loadTexture(tim_e1m1, NULL); + + world_load(bsp_test, &world); } // Main function, program entrypoint @@ -40,8 +45,10 @@ int main(int argc, const char *argv[]) input_process(); display_start(); - + // Draw stuff + world_draw(&world); + FntPrint(-1, "Camera pos = (%d, %d, %d) rot = (%d, %d, %d)\n", cam_pos.vx, cam_pos.vy, cam_pos.vz, cam_rot.vx, cam_rot.vy, cam_rot.vz); FntFlush(-1); diff --git a/ps1bsp.h b/ps1bsp.h new file mode 100755 index 0000000..e795033 --- /dev/null +++ b/ps1bsp.h @@ -0,0 +1,97 @@ +#ifndef __PS1BSP_H__ +#define __PS1BSP_H__ + +#ifdef __cplusplus +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 + int tpage; // Texture page in PS1 VRAM (precalculated when generating the texture atlas) + short uoffs, voffs; // Texture coordinate offset within the texture page + unsigned short nextframe; // If non-zero, the texture is animated and this points to the next texture in the sequence +} ps1bsp_texture_t; + +// This matches the SVECTOR data type, using the extra padding to store vertex color data. +// The full range and precision required cannot be stored in just shorts, so we make use of a floating origin stored in the BSP leafs. +// With this the higher-order bits of each vertex position are calculated into the model-view matrix, giving good precision for polygons near the camera. +typedef struct +{ + short x; + 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; +} 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; + unsigned short vertex1; + unsigned short vertex2; +} ps1bsp_triangle_t; + +typedef struct +{ + unsigned short firstTriangleId; // TODO: could also just do first-index, num-indices here. No real need for a triangle_t struct. + unsigned short numTriangles; + + 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 +{ + unsigned short classtype; // Hash of the original classname + 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 messageId; // Index into a pool of pre-defined messages +} ps1bsp_entity_t; + +typedef struct +{ + unsigned short length; + char message[]; +} ps1bsp_message_t; + +typedef struct +{ + // TODO: add floating origin position, so face vertices can be moved relative to the camera position +} ps1bsp_leaf_t; + +#ifdef __cplusplus +} +#endif + +#endif // __PS1BSP_H__ diff --git a/qmath.c b/qmath.c new file mode 100644 index 0000000..2ffb5e9 --- /dev/null +++ b/qmath.c @@ -0,0 +1,34 @@ +#include "common.h" + +MATRIX *RotMatrixQ(SVECTOR *r, MATRIX *m) +{ + short s[3],c[3]; + MATRIX tm[3]; + + s[0] = isin(r->vx); s[1] = isin(r->vy); s[2] = isin(r->vz); + c[0] = icos(r->vx); c[1] = icos(r->vy); c[2] = icos(r->vz); + + // mY (roll) + m->m[0][0] = c[1]; m->m[0][1] = 0; m->m[0][2] = s[1]; + m->m[1][0] = 0; m->m[1][1] = ONE; m->m[1][2] = 0; + m->m[2][0] = -s[1]; m->m[2][1] = 0; m->m[2][2] = c[1]; + + // mX (pitch) + tm[0].m[0][0] = ONE; tm[0].m[0][1] = 0; tm[0].m[0][2] = 0; + tm[0].m[1][0] = 0; tm[0].m[1][1] = c[0]; tm[0].m[1][2] = -s[0]; + tm[0].m[2][0] = 0; tm[0].m[2][1] = s[0]; tm[0].m[2][2] = c[0]; + + // mZ (yaw) + tm[1].m[0][0] = c[2]; tm[1].m[0][1] = -s[2]; tm[1].m[0][2] = 0; + tm[1].m[1][0] = s[2]; tm[1].m[1][1] = c[2]; tm[1].m[1][2] = 0; + tm[1].m[2][0] = 0; tm[1].m[2][1] = 0; tm[1].m[2][2] = ONE; + + PushMatrix(); + + MulMatrix0( m, &tm[1], &tm[2] ); + MulMatrix0( &tm[2], &tm[0], m ); + + PopMatrix(); + + return m; +} diff --git a/qmath.h b/qmath.h new file mode 100644 index 0000000..8724f54 --- /dev/null +++ b/qmath.h @@ -0,0 +1,6 @@ +#ifndef __QMATH_H__ +#define __QMATH_H__ + +MATRIX *RotMatrixQ(SVECTOR *r, MATRIX *m); + +#endif // __QMATH_H__ diff --git a/test.ps1bsp b/test.ps1bsp new file mode 100755 index 0000000000000000000000000000000000000000..9497db638dbee5d4cab6dbacaa0c2c2030d3d324 GIT binary patch literal 83502 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 literal 0 HcmV?d00001 diff --git a/world.c b/world.c new file mode 100644 index 0000000..f87da95 --- /dev/null +++ b/world.c @@ -0,0 +1,95 @@ +#include "common.h" +#include "world.h" +#include "display.h" + +#include + +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); + +void world_load(const u_long *data, world_t *world) +{ + const char *bytes = (const char*)data; + + world->header = (ps1bsp_header_t*)bytes; + bytes += sizeof(ps1bsp_header_t); + + world->vertices = (ps1bsp_vertex_t*)bytes; + bytes += sizeof(ps1bsp_vertex_t) * world->header->numVertices; + + world->triangles = (ps1bsp_triangle_t*)bytes; + bytes += sizeof(ps1bsp_triangle_t) * world->header->numTriangles; + + world->faces = (ps1bsp_face_t*)bytes; + bytes += sizeof(ps1bsp_face_t) * world->header->numFaces; +} + +void world_draw(const world_t *world) +{ + int p; + + // The world doesn't move, so we just set the camera view-projection matrix + gte_SetRotMatrix(&vp_matrix); + gte_SetTransMatrix(&vp_matrix); + + for (int faceIdx = 0; faceIdx < world->header->numFaces; ++faceIdx) + { + const ps1bsp_face_t *face = &world->faces[faceIdx]; + const CVECTOR *col = &colors[faceIdx % numColors]; + + for (int triangleIdx = 0; triangleIdx < face->numTriangles; ++triangleIdx) + { + const ps1bsp_triangle_t *tri = &world->triangles[face->firstTriangleId + triangleIdx]; + + // Naively draw the triangle with GTE, nothing special or optimized about this + SVECTOR *v0 = (SVECTOR*)&world->vertices[tri->vertex0]; + SVECTOR *v1 = (SVECTOR*)&world->vertices[tri->vertex1]; + SVECTOR *v2 = (SVECTOR*)&world->vertices[tri->vertex2]; + + gte_ldv3(v0, v1, v2); + gte_rtpt(); // Rotation, translation, perspective projection + + // Normal clipping for backface culling + gte_nclip(); + gte_stopz(&p); + if (p < 0) + continue; + + // Average Z for depth sorting and culling + gte_avsz3(); + gte_stotz(&p); + unsigned short depth = p >> 2; + if (depth <= 0 || depth >= OTLEN) + continue; + + // Draw a flat-shaded untextured colored triangle + POLY_F3 *poly = (POLY_F3*)display_allocPrim(sizeof(POLY_F3)); + if (poly == NULL) + break; + + setPolyF3(poly); + + gte_stsxy3_f3(poly); + + poly->r0 = col->r; + poly->g0 = col->g; + poly->b0 = col->b; + + addPrim(curOT + depth, poly); + } + } +} diff --git a/world.h b/world.h new file mode 100644 index 0000000..af210cd --- /dev/null +++ b/world.h @@ -0,0 +1,17 @@ +#ifndef __WORLD_H__ +#define __WORLD_H__ + +#include "ps1bsp.h" + +typedef struct +{ + ps1bsp_header_t *header; + ps1bsp_vertex_t *vertices; + ps1bsp_triangle_t *triangles; + ps1bsp_face_t *faces; +} world_t; + +void world_load(const u_long *data, world_t *world); +void world_draw(const world_t *world); + +#endif // __WORLD_H__