From a8d3bf567b4a010ba827760736cc14c80556460c Mon Sep 17 00:00:00 2001 From: Nico de Poel Date: Mon, 30 Jan 2023 12:52:25 +0100 Subject: [PATCH] First pass at leaf-based rendering order: - Sort leafs first into a linked list, before drawing their faces - Depth value for faces is determined by the leaf order, OT position is calculated once per leaf - Removes the need for per-primitive depth calculation & check, so those are removed - Moved primitive buffer check out of the primitive drawing routines and to the start of each face drawing function - Triangle drawing routines now only draw a single triangle, no loops involved - Simplified drawing routines where possible - Face drawing is still happening in back-to-front order, so this needs another good look at --- draw.h | 177 ++++++++++++++++++++-------------------------------- ps1bsp.h | 23 +++---- test.ps1bsp | Bin 441499 -> 441499 bytes world.c | 148 ++++++++++++++++++++++++++++--------------- 4 files changed, 174 insertions(+), 174 deletions(-) diff --git a/draw.h b/draw.h index df4e3c6..6255212 100644 --- a/draw.h +++ b/draw.h @@ -5,54 +5,32 @@ #include "display.h" #include -static INLINE void draw_trianglefan_lit(SVECTOR *verts, u_char numVerts) +static INLINE void draw_triangle_lit(SVECTOR *verts, u_long *ot) { - int p; - - if (!mem_checkprim(sizeof(POLY_G3), numVerts - 2)) - return; - - // Draw the face as a triangle fan - u_char maxVert = numVerts - 1; - for (u_char vertIdx = 1; vertIdx < maxVert; ++vertIdx) - { - const SVECTOR *v0 = &verts[0]; - const SVECTOR *v1 = &verts[vertIdx]; - const SVECTOR *v2 = &verts[vertIdx + 1]; - - // Naively draw the triangle with GTE, nothing special or optimized about this - gte_ldv3(v0, v1, v2); - gte_rtpt(); // Rotation, translation, perspective projection - - // Average Z for depth sorting and culling - gte_avsz3(); - gte_stotz(&p); - short depth = p >> 2; - if (depth <= 0 || depth >= OTLEN) - continue; - - // Draw a flat-shaded untextured colored triangle - POLY_G3 *poly = (POLY_G3*)mem_prim(sizeof(POLY_G3)); - setPolyG3(poly); - gte_stsxy3_g3(poly); - - poly->r0 = poly->g0 = poly->b0 = (uint8_t)v0->pad; - poly->r1 = poly->g1 = poly->b1 = (uint8_t)v1->pad; - poly->r2 = poly->g2 = poly->b2 = (uint8_t)v2->pad; - - addPrim(curOT + depth, poly); - ++polyCount; - } + // Draw a single triangle + const SVECTOR *v0 = &verts[0]; + const SVECTOR *v1 = &verts[1]; + const SVECTOR *v2 = &verts[2]; + + // Naively draw the triangle with GTE, nothing special or optimized about this + gte_ldv3(v0, v1, v2); + gte_rtpt(); // Rotation, translation, perspective projection + + // Draw a flat-shaded untextured colored triangle + POLY_G3 *poly = (POLY_G3*)mem_prim(sizeof(POLY_G3)); + setPolyG3(poly); + gte_stsxy3_g3(poly); + + poly->r0 = poly->g0 = poly->b0 = (uint8_t)v0->pad; + poly->r1 = poly->g1 = poly->b1 = (uint8_t)v1->pad; + poly->r2 = poly->g2 = poly->b2 = (uint8_t)v2->pad; + + addPrim(ot, poly); + ++polyCount; } -static INLINE void draw_trianglestrip_lit(SVECTOR *verts, u_char numVerts) +static INLINE void draw_trianglestrip_lit(SVECTOR *verts, u_char numVerts, u_long *ot) { - int p; - - u_char numTris = numVerts - 2; - if (!mem_checkprim(sizeof(POLY_G3), numTris)) - return; - // Draw the face as a triangle strip const SVECTOR *v0, *v1, *v2; const SVECTOR *head = verts; @@ -61,6 +39,7 @@ static INLINE void draw_trianglestrip_lit(SVECTOR *verts, u_char numVerts) v2 = head++; // Initialize first vertex to index 0 and set head to index 1 + u_char numTris = numVerts - 2; for (u_char triIdx = 0; triIdx < numTris; ++triIdx) { if (reverse ^= 1) @@ -80,13 +59,6 @@ static INLINE void draw_trianglestrip_lit(SVECTOR *verts, u_char numVerts) gte_ldv3(v0, v1, v2); gte_rtpt(); // Rotation, translation, perspective projection - // Average Z for depth sorting and culling - gte_avsz3(); - gte_stotz(&p); - short depth = p >> 2; - if (depth <= 0 || depth >= OTLEN) - continue; - // Draw a flat-shaded untextured colored triangle POLY_G3 *poly = (POLY_G3*)mem_prim(sizeof(POLY_G3)); setPolyG3(poly); @@ -96,19 +68,13 @@ static INLINE void draw_trianglestrip_lit(SVECTOR *verts, u_char numVerts) poly->r1 = poly->g1 = poly->b1 = (uint8_t)v1->pad; poly->r2 = poly->g2 = poly->b2 = (uint8_t)v2->pad; - addPrim(curOT + depth, poly); + addPrim(ot, poly); ++polyCount; } } -static INLINE void draw_quadstrip_lit(SVECTOR *verts, u_char numVerts) +static INLINE void draw_quadstrip_lit(SVECTOR *verts, u_char numVerts, u_long *ot) { - int p; - - u_char numQuads = (numVerts - 1) >> 1; - if (!mem_checkprim(sizeof(POLY_G4), numQuads)) - return; - // Draw the face as a quad strip const SVECTOR *v0, *v1, *v2, *v3; const SVECTOR *head = verts; @@ -119,6 +85,7 @@ static INLINE void draw_quadstrip_lit(SVECTOR *verts, u_char numVerts) v3 = head++; // Normally a quad strip would have (N-2)/2 quads, but we might end up with a sole triangle at the end which will be drawn as a collapsed quad + u_char numQuads = (numVerts - 1) >> 1; for (u_char quadIdx = 0; quadIdx < numQuads; ++quadIdx) { v0 = v2; @@ -130,19 +97,10 @@ static INLINE void draw_quadstrip_lit(SVECTOR *verts, u_char numVerts) gte_ldv3(v0, v1, v2); gte_rtpt(); // Rotation, translation, perspective projection - // Average Z for depth sorting and culling - gte_avsz3(); - gte_stotz(&p); - short depth = p >> 2; - if (depth <= 0 || depth >= OTLEN) - continue; - // Draw a flat-shaded untextured colored quad POLY_G4 *poly = (POLY_G4*)mem_prim(sizeof(POLY_G4)); setPolyG4(poly); - gte_stsxy0(&poly->x0); - gte_stsxy1(&poly->x1); - gte_stsxy2(&poly->x2); + gte_stsxy3_g3(poly); // Transform the fourth vertex to complete the quad gte_ldv0(v3); @@ -154,22 +112,44 @@ static INLINE void draw_quadstrip_lit(SVECTOR *verts, u_char numVerts) poly->r2 = poly->g2 = poly->b2 = (uint8_t)v2->pad; poly->r3 = poly->g3 = poly->b3 = (uint8_t)v3->pad; - addPrim(curOT + depth, poly); + addPrim(ot, poly); ++polyCount; } } -static INLINE void draw_quadstrip_textured(STVECTOR *verts, u_char numVerts, u_short tpage) +static INLINE void draw_triangle_textured(STVECTOR *verts, u_short tpage, u_long *ot) { - int p; - - // Normally a quad strip would have (N-2)/2 quads, but we might end up with a sole triangle at the end which will be drawn as a collapsed quad - // NOTE: testing has shown that the PS1 is faster just rendering quads and accepting the odd collapsed quad, rather than being clever with pointer comparisons and drawing a single triangle at the end. - u_char numQuads = (numVerts - 1) >> 1; - if (!mem_checkprim(sizeof(POLY_GT4), numQuads)) - return; + // Draw a single textured triangle + const STVECTOR *v0 = &verts[0]; + const STVECTOR *v1 = &verts[1]; + const STVECTOR *v2 = &verts[2]; + + // Naively draw the triangle with GTE, nothing special or optimized about this + gte_ldv3(v0, v1, v2); + gte_rtpt(); // Rotation, translation, perspective projection + + // Draw a gouraud shaded textured triangle + POLY_GT3 *poly = (POLY_GT3*)mem_prim(sizeof(POLY_GT3)); + setPolyGT3(poly); + gte_stsxy3_gt3(poly); + + // Texture UVs + setUV3(poly, v0->u, v0->v, v1->u, v1->v, v2->u, v2->v); + poly->clut = quake_clut; + poly->tpage = tpage; + + poly->r0 = poly->g0 = poly->b0 = (uint8_t)v0->pad; + poly->r1 = poly->g1 = poly->b1 = (uint8_t)v1->pad; + poly->r2 = poly->g2 = poly->b2 = (uint8_t)v2->pad; + + addPrim(ot, poly); + ++polyCount; +} +static INLINE void draw_quadstrip_textured(STVECTOR *verts, u_char numVerts, u_short tpage, u_long *ot) +{ // Draw the face as a quad strip + STVECTOR *v0, *v1, *v2, *v3; u_char i0, i1, i2, i3; u_char head = 0; u_char tail = numVerts; @@ -177,8 +157,10 @@ static INLINE void draw_quadstrip_textured(STVECTOR *verts, u_char numVerts, u_s // Initialize the first two vertices i2 = --tail; i3 = head++; - - STVECTOR *v0, *v1, *v2, *v3; + + // Normally a quad strip would have (N-2)/2 quads, but we might end up with a sole triangle at the end which will be drawn as a collapsed quad + // NOTE: testing has shown that the PS1 is faster just rendering quads and accepting the odd collapsed quad, rather than being clever with pointer comparisons and drawing a single triangle at the end. + u_char numQuads = (numVerts - 1) >> 1; for (u_char quadIdx = 0; quadIdx < numQuads; ++quadIdx) { i0 = i2; @@ -194,19 +176,10 @@ static INLINE void draw_quadstrip_textured(STVECTOR *verts, u_char numVerts, u_s gte_ldv3(v0, v1, v2); gte_rtpt(); // Rotation, translation, perspective projection - // Average Z for depth sorting and culling - gte_avsz3(); - gte_stotz(&p); - short depth = p >> 2; - if (depth <= 0 || depth >= OTLEN) - return; - // Draw a flat-shaded untextured colored quad POLY_GT4 *poly = (POLY_GT4*)mem_prim(sizeof(POLY_GT4)); setPolyGT4(poly); - gte_stsxy0(&poly->x0); - gte_stsxy1(&poly->x1); - gte_stsxy2(&poly->x2); + gte_stsxy3_gt3(poly); v3 = &verts[i3]; @@ -226,21 +199,15 @@ static INLINE void draw_quadstrip_textured(STVECTOR *verts, u_char numVerts, u_s poly->r2 = poly->g2 = poly->b2 = (uint8_t)v2->pad; poly->r3 = poly->g3 = poly->b3 = (uint8_t)v3->pad; - addPrim(curOT + depth, poly); + addPrim(ot, poly); ++polyCount; } } -static INLINE void draw_quadstrip_water(STVECTOR *verts, u_char numVerts, u_short tpage) +static INLINE void draw_quadstrip_water(STVECTOR *verts, u_char numVerts, u_short tpage, u_long *ot) { - int p; - - // Normally a quad strip would have (N-2)/2 quads, but we might end up with a sole triangle at the end which will be drawn as a collapsed quad - u_char numQuads = (numVerts - 1) >> 1; - if (!mem_checkprim(sizeof(POLY_FT4), numQuads)) - return; - // Draw the face as a quad strip + const STVECTOR *v0, *v1, *v2, *v3; u_char i0, i1, i2, i3; u_char head = 0; u_char tail = numVerts; @@ -249,7 +216,8 @@ static INLINE void draw_quadstrip_water(STVECTOR *verts, u_char numVerts, u_shor i2 = --tail; i3 = head++; - const STVECTOR *v0, *v1, *v2, *v3; + // Normally a quad strip would have (N-2)/2 quads, but we might end up with a sole triangle at the end which will be drawn as a collapsed quad + u_char numQuads = (numVerts - 1) >> 1; for (u_char quadIdx = 0; quadIdx < numQuads; ++quadIdx) { i0 = i2; @@ -265,13 +233,6 @@ static INLINE void draw_quadstrip_water(STVECTOR *verts, u_char numVerts, u_shor gte_ldv3(v0, v1, v2); gte_rtpt(); // Rotation, translation, perspective projection - // Average Z for depth sorting and culling - gte_avsz3(); - gte_stotz(&p); - short depth = p >> 2; - if (depth <= 0 || depth >= OTLEN) - continue; - // Draw a flat-shaded untextured colored quad POLY_FT4 *poly = (POLY_FT4*)mem_prim(sizeof(POLY_FT4)); setPolyFT4(poly); @@ -294,7 +255,7 @@ static INLINE void draw_quadstrip_water(STVECTOR *verts, u_char numVerts, u_shor // Unlit poly->r0 = poly->g0 = poly->b0 = 255; - addPrim(curOT + depth, poly); + addPrim(ot, poly); ++polyCount; } } diff --git a/ps1bsp.h b/ps1bsp.h index 17c3513..e9583c5 100755 --- a/ps1bsp.h +++ b/ps1bsp.h @@ -84,19 +84,9 @@ typedef struct unsigned char b : 5; } ps1bsp_facevertex_t; -#define SURF_PLANEBACK 0x2 -#define SURF_DRAWSKY 0x4 -#define SURF_DRAWSPRITE 0x8 -#define SURF_DRAWTURB 0x10 -#define SURF_DRAWTILED 0x20 -#define SURF_DRAWBACKGROUND 0x40 -#define SURF_UNDERWATER 0x80 -#define SURF_NOTEXTURE 0x100 -#define SURF_DRAWFENCE 0x200 -#define SURF_DRAWLAVA 0x400 -#define SURF_DRAWSLIME 0x800 -#define SURF_DRAWTELE 0x1000 -#define SURF_DRAWWATER 0x2000 +#define SURF_DRAWSKY 0x2 +#define SURF_DRAWTURB 0x4 +#define SURF_DRAWWATER 0x8 // High quality: Face -> polygons -> polygon vertex indices (index + UV + light) -> vertices // Low quality: Face -> face vertex indices (index + color) -> vertices @@ -108,13 +98,14 @@ typedef struct // Used for high-quality tesselated textured drawing unsigned short firstPolygon; unsigned char numPolygons; + unsigned char totalQuads; // Used for low-quality untextured vertex colored drawing unsigned short firstFaceVertex; unsigned char numFaceVertices; unsigned char textureId; - unsigned short flags; + unsigned char flags; // Used for backface culling SVECTOR center; @@ -149,8 +140,8 @@ typedef struct ps1bsp_leaf_s u_short numLeafFaces; // Run-time data - struct ps1bsp_leaf_s *nextLeaf; // For chaining leafs in drawing order - u_short leafDepth; // At what depth the leaf's faces should be placed in the ordering table + struct ps1bsp_leaf_s *nextLeaf; // For chaining leafs in drawing order + u_short leafDepth; // At what depth the leaf's faces should be placed in the ordering table } ps1bsp_leaf_t; // Pre-parsed and encoded entity data (this runs the risk of becoming too bloated) diff --git a/test.ps1bsp b/test.ps1bsp index 8c18893656b4b2027a03e1e5d6768d715fc88a51..8d808f6354c2558896d01080efd804f2e6746f3a 100755 GIT binary patch delta 33073 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 hsamyL4DVQKA+VL0wxw(O9E+)C#g5^^BT| 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 K#qdA>{r>=NDZa-5 diff --git a/world.c b/world.c index 31139e2..92eb2e7 100644 --- a/world.c +++ b/world.c @@ -92,9 +92,11 @@ static INLINE char world_cull_backface(const world_t *world, const ps1bsp_face_t return camDot < 0; } -static void world_drawface_fast(const world_t *world, const ps1bsp_face_t *face) +static void world_drawface_fast(const world_t *world, const ps1bsp_face_t *face, u_long *ot) { - // TODO: early primitive buffer check (POLY_G4) + // Early primitive buffer check + if (!mem_checkprim(sizeof(POLY_G4), face->totalQuads)) + return; short dot; if (world_cull_backface(world, face, &dot)) @@ -114,14 +116,16 @@ static void world_drawface_fast(const world_t *world, const ps1bsp_face_t *face) } if (face->numFaceVertices == 3) - draw_trianglefan_lit(verts, 3); + draw_triangle_lit(verts, ot); else - draw_quadstrip_lit(verts, face->numFaceVertices); + draw_quadstrip_lit(verts, face->numFaceVertices, ot); } -static void world_drawface_lit(const world_t *world, const ps1bsp_face_t *face) +static void world_drawface_lit(const world_t *world, const ps1bsp_face_t *face, u_long *ot) { - // TODO: early primitive buffer check (POLY_G4) + // Early primitive buffer check + if (!mem_checkprim(sizeof(POLY_G4), face->totalQuads)) + return; short dot; if (world_cull_backface(world, face, &dot)) @@ -144,81 +148,123 @@ static void world_drawface_lit(const world_t *world, const ps1bsp_face_t *face) } if (poly->numPolyVertices == 3) - draw_trianglefan_lit(verts, 3); + draw_triangle_lit(verts, ot); else - draw_quadstrip_lit(verts, poly->numPolyVertices); + draw_quadstrip_lit(verts, poly->numPolyVertices, ot); } } -static void world_drawface_textured(const world_t *world, const ps1bsp_face_t *face) +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; - // TODO: do an early primitive buffer check here (POLY_GT4), so we can skip vertex copying if it's already full - // When doing tessellation, we need the above dot product to decide how many polys to draw, so we can only safely check the primbuffer size here - // Though since we're drawing front-to-back and tessellation will only happen close to the camera, I suppose we could get away with just checking for non-tessellated polycounts - // 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]; - for (u_char polyIdx = 0; polyIdx < face->numPolygons; ++polyIdx, ++poly) + + if (face->flags & SURF_DRAWWATER) { - ps1bsp_polyvertex_t *polyVertex = &world->polyVertices[poly->firstPolyVertex]; - STVECTOR *curVert = verts; - for (u_char vertIdx = 0; vertIdx < poly->numPolyVertices; ++vertIdx, ++polyVertex, ++curVert) + for (u_char polyIdx = 0; polyIdx < face->numPolygons; ++polyIdx, ++poly) { - 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; + 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 (face->flags & SURF_DRAWWATER) - draw_quadstrip_water(verts, poly->numPolyVertices, faceTexture->tpage); - else - draw_quadstrip_textured(verts, poly->numPolyVertices, faceTexture->tpage); + 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*) = &world_drawface_fast; +static void (*world_drawface)(const world_t*, const ps1bsp_face_t*, u_long *ot) = &world_drawface_fast; -static void world_drawnode(const world_t *world, short nodeIdx, u_char *pvs) -{ - if (nodeIdx < 0) // Leaf node - { - // Check if this leaf is visible from the current camera position - u_short test = ~nodeIdx - 1; - if ((pvs[test >> 3] & (1 << (test & 0x7))) == 0) - return; +static ps1bsp_leaf_t *firstLeaf = NULL; +static u_short leafDepth = 0; - const ps1bsp_leaf_t *leaf = &world->leaves[~nodeIdx]; - // if (!frustum_boxInside(&leaf->mins, &leaf->maxs)) // TODO: these additional frustum checks actually make things slower, probably not worth it - // return; +static void world_drawLeafs(const world_t *world) +{ + u_long frameNum = time_getFrameNumber(); - u_long frameNum = time_getFrameNumber(); - const u_short *leafFace = &world->leafFaces[leaf->firstLeafFace]; + // 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; - for (u_short leafFaceIdx = 0; leafFaceIdx < leaf->numLeafFaces; ++leafFaceIdx, ++leafFace) + const u_short *leafFaces = &world->leafFaces[leaf->firstLeafFace]; + for (u_short leafFaceIdx = 0; leafFaceIdx < leaf->numLeafFaces; ++leafFaceIdx) { - ps1bsp_face_t *face = &world->faces[*leafFace]; + 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); + 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; } @@ -230,11 +276,10 @@ static void world_drawnode(const world_t *world, short nodeIdx, u_char *pvs) const ps1bsp_plane_t *plane = &world->planes[node->planeId]; short dist = world_pointPlaneDist(&cam_pos, plane); - // Draw child nodes in front-to-back order; adding faces to the OT will reverse the drawing order - // PLAN: traverse back-to-front to determine leaf order & depth, then draw faces front-to-back with primbuf checks + // Sort leafs in front-to-back order, so that we can draw their faces at the correct depths char order = dist < 0; - world_drawnode(world, node->children[order], pvs); - world_drawnode(world, node->children[order ^ 1], pvs); + 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. @@ -315,5 +360,8 @@ void world_draw(const world_t *world) else world_drawface = &world_drawface_lit; - world_drawnode(world, 0, pvs); + firstLeaf = NULL; + leafDepth = 1; + world_sortLeafs(world, 0, pvs); + world_drawLeafs(world); }