improve sw renderer

This commit is contained in:
asrael 2026-01-21 23:19:50 -06:00
parent 415d424057
commit 39ee0fefb7
89 changed files with 9380 additions and 2307 deletions

View file

@ -8,6 +8,7 @@
#include "pxl8_gfx.h"
#include "pxl8_io.h"
#include "pxl8_log.h"
#include "pxl8_mem.h"
#define BSP_VERSION 29
@ -40,15 +41,23 @@ typedef struct {
pxl8_bsp_chunk chunks[CHUNK_COUNT];
} pxl8_bsp_header;
typedef struct {
f32 x0, y0, x1, y1;
} screen_rect;
typedef struct {
u32 leaf;
screen_rect window;
} portal_queue_entry;
static inline pxl8_vec3 read_vec3(pxl8_stream* stream) {
pxl8_vec3 v;
v.x = pxl8_read_f32(stream);
v.y = pxl8_read_f32(stream);
v.z = pxl8_read_f32(stream);
return v;
f32 x = pxl8_read_f32(stream);
f32 y = pxl8_read_f32(stream);
f32 z = pxl8_read_f32(stream);
return (pxl8_vec3){x, z, y};
}
static bool validate_chunk(const pxl8_bsp_chunk* chunk, u32 element_size, size_t file_size) {
static bool validate_chunk(const pxl8_bsp_chunk* chunk, u32 element_size, usize file_size) {
if (chunk->size == 0) return true;
if (chunk->offset >= file_size) return false;
if (chunk->offset + chunk->size > file_size) return false;
@ -75,32 +84,13 @@ static inline bool pxl8_bsp_get_edge_vertex(const pxl8_bsp* bsp, i32 surfedge_id
return *out_vert_idx < bsp->num_vertices;
}
static inline bool pxl8_bsp_get_edge_vertices(const pxl8_bsp* bsp, i32 surfedge_idx, u32* out_v0_idx, u32* out_v1_idx) {
if (surfedge_idx >= (i32)bsp->num_surfedges) return false;
i32 edge_idx = bsp->surfedges[surfedge_idx];
if (edge_idx >= 0) {
if ((u32)edge_idx >= bsp->num_edges) return false;
*out_v0_idx = bsp->edges[edge_idx].vertex[0];
*out_v1_idx = bsp->edges[edge_idx].vertex[1];
} else {
edge_idx = -edge_idx;
if ((u32)edge_idx >= bsp->num_edges) return false;
*out_v0_idx = bsp->edges[edge_idx].vertex[1];
*out_v1_idx = bsp->edges[edge_idx].vertex[0];
}
return *out_v0_idx < bsp->num_vertices && *out_v1_idx < bsp->num_vertices;
}
pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!path || !bsp) return PXL8_ERROR_INVALID_ARGUMENT;
memset(bsp, 0, sizeof(*bsp));
u8* file_data = NULL;
size_t file_size = 0;
usize file_size = 0;
pxl8_result result = pxl8_io_read_binary_file(path, &file_data, &file_size);
if (result != PXL8_OK) {
pxl8_error("Failed to load BSP file: %s", path);
@ -109,7 +99,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (file_size < sizeof(pxl8_bsp_header)) {
pxl8_error("BSP file too small: %s", path);
free(file_data);
pxl8_free(file_data);
return PXL8_ERROR_INVALID_FORMAT;
}
@ -120,7 +110,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (header.version != BSP_VERSION) {
pxl8_error("Invalid BSP version: %u (expected %d)", header.version, BSP_VERSION);
free(file_data);
pxl8_free(file_data);
return PXL8_ERROR_INVALID_FORMAT;
}
@ -133,7 +123,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 12, file_size)) goto error_cleanup;
bsp->num_vertices = chunk->size / 12;
if (bsp->num_vertices > 0) {
bsp->vertices = calloc(bsp->num_vertices, sizeof(pxl8_bsp_vertex));
bsp->vertices = pxl8_calloc(bsp->num_vertices, sizeof(pxl8_bsp_vertex));
if (!bsp->vertices) goto error_cleanup;
pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_vertices; i++) {
@ -145,7 +135,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 4, file_size)) goto error_cleanup;
bsp->num_edges = chunk->size / 4;
if (bsp->num_edges > 0) {
bsp->edges = calloc(bsp->num_edges, sizeof(pxl8_bsp_edge));
bsp->edges = pxl8_calloc(bsp->num_edges, sizeof(pxl8_bsp_edge));
pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_edges; i++) {
bsp->edges[i].vertex[0] = pxl8_read_u16(&stream);
@ -157,7 +147,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 4, file_size)) goto error_cleanup;
bsp->num_surfedges = chunk->size / 4;
if (bsp->num_surfedges > 0) {
bsp->surfedges = calloc(bsp->num_surfedges, sizeof(i32));
bsp->surfedges = pxl8_calloc(bsp->num_surfedges, sizeof(i32));
pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_surfedges; i++) {
bsp->surfedges[i] = pxl8_read_i32(&stream);
@ -168,7 +158,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 20, file_size)) goto error_cleanup;
bsp->num_planes = chunk->size / 20;
if (bsp->num_planes > 0) {
bsp->planes = calloc(bsp->num_planes, sizeof(pxl8_bsp_plane));
bsp->planes = pxl8_calloc(bsp->num_planes, sizeof(pxl8_bsp_plane));
pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_planes; i++) {
bsp->planes[i].normal = read_vec3(&stream);
@ -179,16 +169,20 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
chunk = &header.chunks[CHUNK_TEXINFO];
if (!validate_chunk(chunk, 40, file_size)) goto error_cleanup;
bsp->num_texinfo = chunk->size / 40;
if (bsp->num_texinfo > 0) {
bsp->texinfo = calloc(bsp->num_texinfo, sizeof(pxl8_bsp_texinfo));
bsp->num_materials = chunk->size / 40;
if (bsp->num_materials > 0) {
bsp->materials = pxl8_calloc(bsp->num_materials, sizeof(pxl8_gfx_material));
pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_texinfo; i++) {
bsp->texinfo[i].u_axis = read_vec3(&stream);
bsp->texinfo[i].u_offset = pxl8_read_f32(&stream);
bsp->texinfo[i].v_axis = read_vec3(&stream);
bsp->texinfo[i].v_offset = pxl8_read_f32(&stream);
bsp->texinfo[i].miptex = pxl8_read_u32(&stream);
for (u32 i = 0; i < bsp->num_materials; i++) {
bsp->materials[i].u_axis = read_vec3(&stream);
bsp->materials[i].u_offset = pxl8_read_f32(&stream);
bsp->materials[i].v_axis = read_vec3(&stream);
bsp->materials[i].v_offset = pxl8_read_f32(&stream);
bsp->materials[i].texture_id = pxl8_read_u32(&stream);
bsp->materials[i].alpha = 255;
bsp->materials[i].dither = true;
bsp->materials[i].dynamic_lighting = true;
bsp->materials[i].double_sided = true;
}
}
@ -196,14 +190,14 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 20, file_size)) goto error_cleanup;
bsp->num_faces = chunk->size / 20;
if (bsp->num_faces > 0) {
bsp->faces = calloc(bsp->num_faces, sizeof(pxl8_bsp_face));
bsp->faces = pxl8_calloc(bsp->num_faces, sizeof(pxl8_bsp_face));
pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_faces; i++) {
bsp->faces[i].plane_id = pxl8_read_u16(&stream);
bsp->faces[i].side = pxl8_read_u16(&stream);
bsp->faces[i].first_edge = pxl8_read_u32(&stream);
bsp->faces[i].num_edges = pxl8_read_u16(&stream);
bsp->faces[i].texinfo_id = pxl8_read_u16(&stream);
bsp->faces[i].material_id = pxl8_read_u16(&stream);
bsp->faces[i].styles[0] = pxl8_read_u8(&stream);
bsp->faces[i].styles[1] = pxl8_read_u8(&stream);
bsp->faces[i].styles[2] = pxl8_read_u8(&stream);
@ -219,14 +213,24 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 24, file_size)) goto error_cleanup;
bsp->num_nodes = chunk->size / 24;
if (bsp->num_nodes > 0) {
bsp->nodes = calloc(bsp->num_nodes, sizeof(pxl8_bsp_node));
bsp->nodes = pxl8_calloc(bsp->num_nodes, sizeof(pxl8_bsp_node));
pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_nodes; i++) {
bsp->nodes[i].plane_id = pxl8_read_u32(&stream);
bsp->nodes[i].children[0] = pxl8_read_i16(&stream);
bsp->nodes[i].children[1] = pxl8_read_i16(&stream);
for (u32 j = 0; j < 3; j++) bsp->nodes[i].mins[j] = pxl8_read_i16(&stream);
for (u32 j = 0; j < 3; j++) bsp->nodes[i].maxs[j] = pxl8_read_i16(&stream);
i16 nx = pxl8_read_i16(&stream);
i16 ny = pxl8_read_i16(&stream);
i16 nz = pxl8_read_i16(&stream);
bsp->nodes[i].mins[0] = nx;
bsp->nodes[i].mins[1] = nz;
bsp->nodes[i].mins[2] = ny;
i16 mx = pxl8_read_i16(&stream);
i16 my = pxl8_read_i16(&stream);
i16 mz = pxl8_read_i16(&stream);
bsp->nodes[i].maxs[0] = mx;
bsp->nodes[i].maxs[1] = mz;
bsp->nodes[i].maxs[2] = my;
bsp->nodes[i].first_face = pxl8_read_u16(&stream);
bsp->nodes[i].num_faces = pxl8_read_u16(&stream);
}
@ -236,13 +240,23 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 28, file_size)) goto error_cleanup;
bsp->num_leafs = chunk->size / 28;
if (bsp->num_leafs > 0) {
bsp->leafs = calloc(bsp->num_leafs, sizeof(pxl8_bsp_leaf));
bsp->leafs = pxl8_calloc(bsp->num_leafs, sizeof(pxl8_bsp_leaf));
pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_leafs; i++) {
bsp->leafs[i].contents = pxl8_read_i32(&stream);
bsp->leafs[i].visofs = pxl8_read_i32(&stream);
for (u32 j = 0; j < 3; j++) bsp->leafs[i].mins[j] = pxl8_read_i16(&stream);
for (u32 j = 0; j < 3; j++) bsp->leafs[i].maxs[j] = pxl8_read_i16(&stream);
i16 nx = pxl8_read_i16(&stream);
i16 ny = pxl8_read_i16(&stream);
i16 nz = pxl8_read_i16(&stream);
bsp->leafs[i].mins[0] = nx;
bsp->leafs[i].mins[1] = nz;
bsp->leafs[i].mins[2] = ny;
i16 mx = pxl8_read_i16(&stream);
i16 my = pxl8_read_i16(&stream);
i16 mz = pxl8_read_i16(&stream);
bsp->leafs[i].maxs[0] = mx;
bsp->leafs[i].maxs[1] = mz;
bsp->leafs[i].maxs[2] = my;
bsp->leafs[i].first_marksurface = pxl8_read_u16(&stream);
bsp->leafs[i].num_marksurfaces = pxl8_read_u16(&stream);
for (u32 j = 0; j < 4; j++) bsp->leafs[i].ambient_level[j] = pxl8_read_u8(&stream);
@ -253,7 +267,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 2, file_size)) goto error_cleanup;
bsp->num_marksurfaces = chunk->size / 2;
if (bsp->num_marksurfaces > 0) {
bsp->marksurfaces = calloc(bsp->num_marksurfaces, sizeof(u16));
bsp->marksurfaces = pxl8_calloc(bsp->num_marksurfaces, sizeof(u16));
pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_marksurfaces; i++) {
bsp->marksurfaces[i] = pxl8_read_u16(&stream);
@ -264,11 +278,21 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 64, file_size)) goto error_cleanup;
bsp->num_models = chunk->size / 64;
if (bsp->num_models > 0) {
bsp->models = calloc(bsp->num_models, sizeof(pxl8_bsp_model));
bsp->models = pxl8_calloc(bsp->num_models, sizeof(pxl8_bsp_model));
pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_models; i++) {
for (u32 j = 0; j < 3; j++) bsp->models[i].mins[j] = pxl8_read_f32(&stream);
for (u32 j = 0; j < 3; j++) bsp->models[i].maxs[j] = pxl8_read_f32(&stream);
f32 minx = pxl8_read_f32(&stream);
f32 miny = pxl8_read_f32(&stream);
f32 minz = pxl8_read_f32(&stream);
bsp->models[i].mins[0] = minx;
bsp->models[i].mins[1] = minz;
bsp->models[i].mins[2] = miny;
f32 maxx = pxl8_read_f32(&stream);
f32 maxy = pxl8_read_f32(&stream);
f32 maxz = pxl8_read_f32(&stream);
bsp->models[i].maxs[0] = maxx;
bsp->models[i].maxs[1] = maxz;
bsp->models[i].maxs[2] = maxy;
bsp->models[i].origin = read_vec3(&stream);
for (u32 j = 0; j < 4; j++) bsp->models[i].headnode[j] = pxl8_read_i32(&stream);
bsp->models[i].visleafs = pxl8_read_i32(&stream);
@ -281,7 +305,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 1, file_size)) goto error_cleanup;
bsp->visdata_size = chunk->size;
if (bsp->visdata_size > 0) {
bsp->visdata = malloc(bsp->visdata_size);
bsp->visdata = pxl8_malloc(bsp->visdata_size);
memcpy(bsp->visdata, file_data + chunk->offset, bsp->visdata_size);
}
@ -289,11 +313,11 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
if (!validate_chunk(chunk, 1, file_size)) goto error_cleanup;
bsp->lightdata_size = chunk->size;
if (bsp->lightdata_size > 0) {
bsp->lightdata = malloc(bsp->lightdata_size);
bsp->lightdata = pxl8_malloc(bsp->lightdata_size);
memcpy(bsp->lightdata, file_data + chunk->offset, bsp->lightdata_size);
}
free(file_data);
pxl8_free(file_data);
for (u32 i = 0; i < bsp->num_faces; i++) {
pxl8_bsp_face* face = &bsp->faces[i];
@ -327,7 +351,7 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
error_cleanup:
pxl8_error("BSP chunk validation failed: %s", path);
free(file_data);
pxl8_free(file_data);
pxl8_bsp_destroy(bsp);
return PXL8_ERROR_INVALID_FORMAT;
}
@ -335,18 +359,21 @@ error_cleanup:
void pxl8_bsp_destroy(pxl8_bsp* bsp) {
if (!bsp) return;
free(bsp->edges);
free(bsp->faces);
free(bsp->leafs);
free(bsp->lightdata);
free(bsp->marksurfaces);
free(bsp->models);
free(bsp->nodes);
free(bsp->planes);
free(bsp->surfedges);
free(bsp->texinfo);
free(bsp->vertices);
free(bsp->visdata);
pxl8_free(bsp->cell_portals);
pxl8_free(bsp->edges);
pxl8_free(bsp->faces);
pxl8_free(bsp->leafs);
pxl8_free(bsp->lightdata);
pxl8_free(bsp->marksurfaces);
pxl8_free(bsp->materials);
pxl8_free(bsp->models);
pxl8_free(bsp->nodes);
pxl8_free(bsp->planes);
pxl8_free(bsp->render_face_flags);
pxl8_free(bsp->surfedges);
pxl8_free(bsp->vertex_lights);
pxl8_free(bsp->vertices);
pxl8_free(bsp->visdata);
memset(bsp, 0, sizeof(*bsp));
}
@ -375,85 +402,88 @@ bool pxl8_bsp_is_leaf_visible(const pxl8_bsp* bsp, i32 leaf_from, i32 leaf_to) {
i32 visofs = bsp->leafs[leaf_from].visofs;
if (visofs < 0) return true;
u32 target_byte = leaf_to >> 3;
u32 target_bit = leaf_to & 7;
u32 pvs_size = (bsp->num_leafs + 7) / 8;
u32 row_size = (bsp->num_leafs + 7) >> 3;
u32 byte_idx = (u32)leaf_to >> 3;
u32 bit_idx = (u32)leaf_to & 7;
u32 pos = (u32)visofs;
u32 current_byte = 0;
u8* vis = bsp->visdata + visofs;
u8* vis_end = bsp->visdata + bsp->visdata_size;
u32 out = 0;
while (current_byte < pvs_size && pos < bsp->visdata_size) {
u8 b = bsp->visdata[pos++];
if (b != 0) {
if (current_byte == target_byte) {
return (b & (1 << target_bit)) != 0;
while (out < row_size && vis < vis_end) {
if (*vis) {
if (out == byte_idx) {
return (*vis & (1 << bit_idx)) != 0;
}
current_byte++;
out++;
vis++;
} else {
if (pos >= bsp->visdata_size) return false;
u32 count = bsp->visdata[pos++];
if (target_byte < current_byte + count) {
vis++;
if (vis >= vis_end) break;
u32 count = *vis++;
if (out + count > byte_idx && byte_idx >= out) {
return false;
}
current_byte += count;
out += count;
}
}
return false;
return out > byte_idx ? false : true;
}
pxl8_bsp_pvs pxl8_bsp_decompress_pvs(const pxl8_bsp* bsp, i32 leaf) {
pxl8_bsp_pvs pvs = {0};
u32 pvs_size = (bsp->num_leafs + 7) / 8;
pvs.data = calloc(pvs_size, 1);
pvs.size = pvs_size;
u32 row = (bsp->num_leafs + 7) >> 3;
pvs.data = pxl8_malloc(row);
pvs.size = row;
if (!pvs.data) return pvs;
if (!bsp || leaf < 0 || (u32)leaf >= bsp->num_leafs) {
memset(pvs.data, 0xFF, pvs_size);
memset(pvs.data, 0xFF, row);
return pvs;
}
i32 visofs = bsp->leafs[leaf].visofs;
if (visofs < 0 || !bsp->visdata || bsp->visdata_size == 0) {
memset(pvs.data, 0xFF, pvs_size);
memset(pvs.data, 0xFF, row);
return pvs;
}
u32 pos = (u32)visofs;
u32 out = 0;
u8* in = bsp->visdata + visofs;
u8* out = pvs.data;
u8* out_end = pvs.data + row;
while (out < pvs_size && pos < bsp->visdata_size) {
u8 b = bsp->visdata[pos++];
if (b != 0) {
pvs.data[out++] = b;
do {
if (*in) {
*out++ = *in++;
} else {
if (pos >= bsp->visdata_size) break;
u32 count = bsp->visdata[pos++];
out += count;
in++;
i32 c = *in++;
while (c > 0 && out < out_end) {
*out++ = 0;
c--;
}
}
}
} while (out < out_end);
return pvs;
}
void pxl8_bsp_pvs_destroy(pxl8_bsp_pvs* pvs) {
if (pvs) {
free(pvs->data);
pxl8_free(pvs->data);
pvs->data = NULL;
pvs->size = 0;
}
}
bool pxl8_bsp_pvs_is_visible(const pxl8_bsp_pvs* pvs, i32 leaf) {
if (!pvs || !pvs->data || leaf < 0) return false;
u32 byte_idx = leaf >> 3;
u32 bit_idx = leaf & 7;
if (byte_idx >= pvs->size) return false;
if (!pvs || !pvs->data || leaf < 0) return true;
u32 byte_idx = (u32)leaf >> 3;
u32 bit_idx = (u32)leaf & 7;
if (byte_idx >= pvs->size) return true;
return (pvs->data[byte_idx] & (1 << bit_idx)) != 0;
}
@ -546,6 +576,89 @@ static inline bool face_in_frustum(const pxl8_bsp* bsp, u32 face_id, const pxl8_
return pxl8_frustum_test_aabb(frustum, face->aabb_min, face->aabb_max);
}
static inline bool leaf_in_frustum(const pxl8_bsp_leaf* leaf, const pxl8_frustum* frustum) {
pxl8_vec3 mins = {(f32)leaf->mins[0], (f32)leaf->mins[1], (f32)leaf->mins[2]};
pxl8_vec3 maxs = {(f32)leaf->maxs[0], (f32)leaf->maxs[1], (f32)leaf->maxs[2]};
return pxl8_frustum_test_aabb(frustum, mins, maxs);
}
static inline bool screen_rect_valid(screen_rect r) {
return r.x0 < r.x1 && r.y0 < r.y1;
}
static inline screen_rect screen_rect_intersect(screen_rect a, screen_rect b) {
return (screen_rect){
.x0 = (a.x0 > b.x0) ? a.x0 : b.x0,
.y0 = (a.y0 > b.y0) ? a.y0 : b.y0,
.x1 = (a.x1 < b.x1) ? a.x1 : b.x1,
.y1 = (a.y1 < b.y1) ? a.y1 : b.y1,
};
}
static inline void expand_rect_with_ndc(screen_rect* r, f32 nx, f32 ny) {
if (nx < r->x0) r->x0 = nx;
if (nx > r->x1) r->x1 = nx;
if (ny < r->y0) r->y0 = ny;
if (ny > r->y1) r->y1 = ny;
}
static screen_rect project_portal_to_screen(const pxl8_bsp_portal* portal, const pxl8_mat4* vp, f32 wall_height) {
pxl8_vec3 world_corners[4] = {
{portal->x0, 0, portal->z0},
{portal->x1, 0, portal->z1},
{portal->x1, wall_height, portal->z1},
{portal->x0, wall_height, portal->z0},
};
pxl8_vec4 clip[4];
bool in_front[4];
i32 front_count = 0;
const f32 NEAR_W = 0.001f;
for (i32 i = 0; i < 4; i++) {
clip[i] = pxl8_mat4_multiply_vec4(*vp, (pxl8_vec4){world_corners[i].x, world_corners[i].y, world_corners[i].z, 1.0f});
in_front[i] = clip[i].w > NEAR_W;
if (in_front[i]) front_count++;
}
if (front_count == 0) return (screen_rect){0, 0, 0, 0};
screen_rect result = {1e30f, 1e30f, -1e30f, -1e30f};
for (i32 i = 0; i < 4; i++) {
i32 j = (i + 1) % 4;
pxl8_vec4 a = clip[i];
pxl8_vec4 b = clip[j];
bool a_in = in_front[i];
bool b_in = in_front[j];
if (a_in) {
f32 inv_w = 1.0f / a.w;
expand_rect_with_ndc(&result, a.x * inv_w, a.y * inv_w);
}
if (a_in != b_in) {
f32 t = (NEAR_W - a.w) / (b.w - a.w);
pxl8_vec4 intersection = {
a.x + t * (b.x - a.x),
a.y + t * (b.y - a.y),
a.z + t * (b.z - a.z),
NEAR_W
};
f32 inv_w = 1.0f / intersection.w;
expand_rect_with_ndc(&result, intersection.x * inv_w, intersection.y * inv_w);
}
}
if (result.x0 < -1.0f) result.x0 = -1.0f;
if (result.y0 < -1.0f) result.y0 = -1.0f;
if (result.x1 > 1.0f) result.x1 = 1.0f;
if (result.y1 > 1.0f) result.y1 = 1.0f;
return result;
}
static void collect_face_to_mesh(
const pxl8_bsp* bsp,
u32 face_id,
@ -564,17 +677,18 @@ static void collect_face_to_mesh(
}
}
const pxl8_bsp_texinfo* texinfo = NULL;
const pxl8_gfx_material* material = NULL;
f32 tex_scale = 64.0f;
if (face->texinfo_id < bsp->num_texinfo) {
texinfo = &bsp->texinfo[face->texinfo_id];
if (face->material_id < bsp->num_materials) {
material = &bsp->materials[face->material_id];
}
u16 base_idx = (u16)mesh->vertex_count;
u32 num_verts = 0;
for (u32 i = 0; i < face->num_edges && num_verts < 64; i++) {
i32 surfedge_idx = face->first_edge + i;
u32 edge_i = face->side ? (face->num_edges - 1 - i) : i;
i32 surfedge_idx = face->first_edge + edge_i;
u32 vert_idx;
if (!pxl8_bsp_get_edge_vertex(bsp, surfedge_idx, &vert_idx)) {
@ -584,9 +698,9 @@ static void collect_face_to_mesh(
pxl8_vec3 pos = bsp->vertices[vert_idx].position;
f32 u = 0.0f, v = 0.0f;
if (texinfo) {
u = (pxl8_vec3_dot(pos, texinfo->u_axis) + texinfo->u_offset) / tex_scale;
v = (pxl8_vec3_dot(pos, texinfo->v_axis) + texinfo->v_offset) / tex_scale;
if (material) {
u = (pxl8_vec3_dot(pos, material->u_axis) + material->u_offset) / tex_scale;
v = (pxl8_vec3_dot(pos, material->v_axis) + material->v_offset) / tex_scale;
}
u8 light = 255;
@ -613,8 +727,8 @@ static void collect_face_to_mesh(
}
}
void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 texture_id) {
if (!gfx || !bsp || face_id >= bsp->num_faces) return;
void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const pxl8_gfx_material* material) {
if (!gfx || !bsp || face_id >= bsp->num_faces || !material) return;
pxl8_mesh* mesh = pxl8_mesh_create(64, 192);
if (!mesh) return;
@ -623,109 +737,115 @@ void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 t
if (mesh->index_count > 0) {
pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_material mat = pxl8_material_create(texture_id);
pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat);
pxl8_3d_draw_mesh(gfx, mesh, &identity, material);
}
pxl8_mesh_destroy(mesh);
}
void pxl8_bsp_render_textured(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos) {
static int call_count = 0;
if (!gfx || !bsp || bsp->num_faces == 0) {
if (call_count++ < 5) {
pxl8_debug("bsp_render_textured: early return - gfx=%p, bsp=%p, num_faces=%u",
(void*)gfx, (void*)bsp, bsp ? bsp->num_faces : 0);
}
return;
}
void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos) {
if (!gfx || !bsp || bsp->num_faces == 0) return;
if (!bsp->cell_portals || bsp->num_cell_portals == 0) return;
if (!bsp->materials || bsp->num_materials == 0) return;
const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx);
if (!frustum) {
if (call_count++ < 5) {
pxl8_debug("bsp_render_textured: frustum is NULL!");
}
const pxl8_mat4* vp = pxl8_3d_get_view_proj(gfx);
if (!frustum || !vp) return;
i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
if (camera_leaf < 0 || (u32)camera_leaf >= bsp->num_cell_portals) return;
pxl8_bsp* bsp_mut = (pxl8_bsp*)bsp;
if (!bsp_mut->render_face_flags) {
bsp_mut->render_face_flags = pxl8_calloc(bsp->num_faces, 1);
if (!bsp_mut->render_face_flags) return;
}
memset(bsp_mut->render_face_flags, 0, bsp->num_faces);
pxl8_bsp_pvs pvs = pxl8_bsp_decompress_pvs(bsp, camera_leaf);
u32 visited_bytes = (bsp->num_leafs + 7) / 8;
u8* visited = pxl8_calloc(visited_bytes, 1);
screen_rect* cell_windows = pxl8_calloc(bsp->num_leafs, sizeof(screen_rect));
portal_queue_entry* queue = pxl8_malloc(bsp->num_leafs * 4 * sizeof(portal_queue_entry));
if (!visited || !cell_windows || !queue) {
pxl8_free(visited);
pxl8_free(cell_windows);
pxl8_free(queue);
pxl8_bsp_pvs_destroy(&pvs);
return;
}
i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
u32 head = 0, tail = 0;
screen_rect full_screen = {-1.0f, -1.0f, 1.0f, 1.0f};
static u8* rendered_faces = NULL;
static u32 rendered_faces_capacity = 0;
visited[camera_leaf >> 3] |= (1 << (camera_leaf & 7));
cell_windows[camera_leaf] = full_screen;
queue[tail++] = (portal_queue_entry){camera_leaf, full_screen};
if (rendered_faces_capacity < bsp->num_faces) {
u8* new_buffer = realloc(rendered_faces, bsp->num_faces);
if (!new_buffer) return;
rendered_faces = new_buffer;
rendered_faces_capacity = bsp->num_faces;
}
f32 wall_height = 128.0f;
memset(rendered_faces, 0, bsp->num_faces);
while (head < tail) {
portal_queue_entry entry = queue[head++];
u32 leaf_id = entry.leaf;
screen_rect window = entry.window;
pxl8_mesh* mesh = pxl8_mesh_create(8192, 16384);
if (!mesh) return;
if (leaf_id >= bsp->num_cell_portals) continue;
const pxl8_bsp_cell_portals* cp = &bsp->cell_portals[leaf_id];
u32 current_texture = 0xFFFFFFFF;
for (u8 i = 0; i < cp->num_portals; i++) {
const pxl8_bsp_portal* portal = &cp->portals[i];
u32 target = portal->target_leaf;
for (u32 leaf_id = 0; leaf_id < bsp->num_leafs; leaf_id++) {
if (camera_leaf >= 0 && !pxl8_bsp_is_leaf_visible(bsp, camera_leaf, leaf_id)) continue;
if (target >= bsp->num_leafs) continue;
if (bsp->leafs[target].contents == -1) continue;
if (!pxl8_bsp_pvs_is_visible(&pvs, target)) continue;
const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id];
screen_rect portal_rect = project_portal_to_screen(portal, vp, wall_height);
if (!screen_rect_valid(portal_rect)) continue;
for (u32 i = 0; i < leaf->num_marksurfaces; i++) {
u32 surf_idx = leaf->first_marksurface + i;
if (surf_idx >= bsp->num_marksurfaces) continue;
screen_rect new_window = screen_rect_intersect(window, portal_rect);
if (!screen_rect_valid(new_window)) continue;
u32 face_id = bsp->marksurfaces[surf_idx];
if (face_id >= bsp->num_faces) continue;
if (rendered_faces[face_id]) continue;
rendered_faces[face_id] = 1;
if (!face_in_frustum(bsp, face_id, frustum)) {
u32 byte = target >> 3;
u32 bit = target & 7;
if (visited[byte] & (1 << bit)) {
screen_rect existing = cell_windows[target];
bool expanded = false;
if (new_window.x0 < existing.x0) { cell_windows[target].x0 = new_window.x0; expanded = true; }
if (new_window.y0 < existing.y0) { cell_windows[target].y0 = new_window.y0; expanded = true; }
if (new_window.x1 > existing.x1) { cell_windows[target].x1 = new_window.x1; expanded = true; }
if (new_window.y1 > existing.y1) { cell_windows[target].y1 = new_window.y1; expanded = true; }
if (expanded && tail < bsp->num_leafs * 4) {
queue[tail++] = (portal_queue_entry){target, cell_windows[target]};
}
continue;
}
const pxl8_bsp_face* face = &bsp->faces[face_id];
u32 texture_id = 0;
if (face->texinfo_id < bsp->num_texinfo) {
texture_id = bsp->texinfo[face->texinfo_id].miptex;
}
if (texture_id != current_texture && mesh->index_count > 0) {
pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_material mat = pxl8_material_with_lighting(pxl8_material_with_double_sided(pxl8_material_create(current_texture)));
pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat);
pxl8_mesh_clear(mesh);
}
current_texture = texture_id;
collect_face_to_mesh(bsp, face_id, mesh);
visited[byte] |= (1 << bit);
cell_windows[target] = new_window;
queue[tail++] = (portal_queue_entry){target, new_window};
}
}
if (mesh->index_count > 0) {
pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_material mat = pxl8_material_with_lighting(pxl8_material_with_double_sided(pxl8_material_create(current_texture)));
pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat);
pxl8_mesh* mesh = pxl8_mesh_create(8192, 16384);
if (!mesh) {
pxl8_free(visited);
pxl8_free(cell_windows);
pxl8_free(queue);
return;
}
pxl8_mesh_destroy(mesh);
}
void pxl8_bsp_render_wireframe(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos, u32 color) {
if (!gfx || !bsp) return;
const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx);
if (!frustum) return;
i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
u8 line_color = (u8)(color & 0xFF);
u32 current_material = 0xFFFFFFFF;
u32 total_faces = 0;
for (u32 leaf_id = 0; leaf_id < bsp->num_leafs; leaf_id++) {
if (camera_leaf >= 0 && !pxl8_bsp_is_leaf_visible(bsp, camera_leaf, leaf_id)) continue;
u32 byte = leaf_id >> 3;
u32 bit = leaf_id & 7;
if (!(visited[byte] & (1 << bit))) continue;
const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id];
if (!leaf_in_frustum(leaf, frustum)) continue;
for (u32 i = 0; i < leaf->num_marksurfaces; i++) {
u32 surf_idx = leaf->first_marksurface + i;
@ -734,23 +854,45 @@ void pxl8_bsp_render_wireframe(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 cam
u32 face_id = bsp->marksurfaces[surf_idx];
if (face_id >= bsp->num_faces) continue;
if (bsp_mut->render_face_flags[face_id]) continue;
bsp_mut->render_face_flags[face_id] = 1;
if (!face_in_frustum(bsp, face_id, frustum)) continue;
const pxl8_bsp_face* face = &bsp->faces[face_id];
u32 material_id = face->material_id;
if (material_id >= bsp->num_materials) continue;
total_faces++;
for (u32 e = 0; e < face->num_edges; e++) {
i32 surfedge_idx = face->first_edge + e;
if (surfedge_idx >= (i32)bsp->num_surfedges) continue;
u32 v0_idx, v1_idx;
if (!pxl8_bsp_get_edge_vertices(bsp, surfedge_idx, &v0_idx, &v1_idx)) continue;
pxl8_vec3 p0 = bsp->vertices[v0_idx].position;
pxl8_vec3 p1 = bsp->vertices[v1_idx].position;
pxl8_3d_draw_line(gfx, p0, p1, line_color);
if (material_id != current_material && mesh->index_count > 0 && current_material < bsp->num_materials) {
pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_3d_draw_mesh(gfx, mesh, &identity, &bsp->materials[current_material]);
pxl8_mesh_clear(mesh);
}
current_material = material_id;
collect_face_to_mesh(bsp, face_id, mesh);
}
}
if (mesh->index_count > 0 && current_material < bsp->num_materials) {
pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_3d_draw_mesh(gfx, mesh, &identity, &bsp->materials[current_material]);
}
static u32 debug_count = 0;
if (debug_count++ < 5) {
pxl8_debug("BSP render: %u faces, mesh verts=%u indices=%u", total_faces, mesh->vertex_count, mesh->index_count);
if (mesh->vertex_count > 0) {
pxl8_vertex* v = &mesh->vertices[0];
pxl8_debug(" vert[0]: pos=(%.1f,%.1f,%.1f) uv=(%.2f,%.2f)",
v->position.x, v->position.y, v->position.z, v->u, v->v);
}
}
pxl8_bsp_pvs_destroy(&pvs);
pxl8_free(visited);
pxl8_free(cell_windows);
pxl8_free(queue);
pxl8_mesh_destroy(mesh);
}