562 lines
20 KiB
C
562 lines
20 KiB
C
#include "pxl8_bsp.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "pxl8_gfx.h"
|
|
#include "pxl8_io.h"
|
|
#include "pxl8_macros.h"
|
|
|
|
#define BSP_VERSION 29
|
|
|
|
typedef enum {
|
|
CHUNK_ENTITIES = 0,
|
|
CHUNK_PLANES = 1,
|
|
CHUNK_TEXTURES = 2,
|
|
CHUNK_VERTICES = 3,
|
|
CHUNK_VISIBILITY = 4,
|
|
CHUNK_NODES = 5,
|
|
CHUNK_TEXINFO = 6,
|
|
CHUNK_FACES = 7,
|
|
CHUNK_LIGHTING = 8,
|
|
CHUNK_CLIPNODES = 9,
|
|
CHUNK_LEAFS = 10,
|
|
CHUNK_MARKSURFACES = 11,
|
|
CHUNK_EDGES = 12,
|
|
CHUNK_SURFEDGES = 13,
|
|
CHUNK_MODELS = 14,
|
|
CHUNK_COUNT = 15
|
|
} pxl8_bsp_chunk_type;
|
|
|
|
typedef struct {
|
|
u32 offset;
|
|
u32 size;
|
|
} pxl8_bsp_chunk;
|
|
|
|
typedef struct {
|
|
u32 version;
|
|
pxl8_bsp_chunk chunks[CHUNK_COUNT];
|
|
} pxl8_bsp_header;
|
|
|
|
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;
|
|
}
|
|
|
|
static bool validate_chunk(const pxl8_bsp_chunk* chunk, u32 element_size, size_t file_size) {
|
|
if (chunk->size == 0) return true;
|
|
if (chunk->offset >= file_size) return false;
|
|
if (chunk->offset + chunk->size > file_size) return false;
|
|
if (chunk->size % element_size != 0) return false;
|
|
return true;
|
|
}
|
|
|
|
static inline bool pxl8_bsp_get_edge_vertex(const pxl8_bsp* bsp, i32 surfedge_idx, u32* out_vert_idx) {
|
|
if (surfedge_idx >= (i32)bsp->num_surfedges) return false;
|
|
|
|
i32 edge_idx = bsp->surfedges[surfedge_idx];
|
|
u32 vertex_index;
|
|
|
|
if (edge_idx >= 0) {
|
|
if ((u32)edge_idx >= bsp->num_edges) return false;
|
|
vertex_index = 0;
|
|
} else {
|
|
edge_idx = -edge_idx;
|
|
if ((u32)edge_idx >= bsp->num_edges) return false;
|
|
vertex_index = 1;
|
|
}
|
|
|
|
*out_vert_idx = bsp->edges[edge_idx].vertex[vertex_index];
|
|
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));
|
|
|
|
FILE* f = fopen(path, "rb");
|
|
if (!f) {
|
|
pxl8_error("Failed to load BSP file: %s", path);
|
|
return PXL8_ERROR_FILE_NOT_FOUND;
|
|
}
|
|
|
|
fseek(f, 0, SEEK_END);
|
|
size_t file_size = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
|
|
u8* file_data = (u8*)malloc(file_size);
|
|
if (!file_data) {
|
|
fclose(f);
|
|
pxl8_error("Failed to allocate memory for BSP file: %s", path);
|
|
return PXL8_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
if (fread(file_data, 1, file_size, f) != file_size) {
|
|
free(file_data);
|
|
fclose(f);
|
|
pxl8_error("Failed to read BSP file: %s", path);
|
|
return PXL8_ERROR_INVALID_FORMAT;
|
|
}
|
|
fclose(f);
|
|
|
|
if (file_size < sizeof(pxl8_bsp_header)) {
|
|
pxl8_error("BSP file too small: %s", path);
|
|
free(file_data);
|
|
return PXL8_ERROR_INVALID_FORMAT;
|
|
}
|
|
|
|
pxl8_stream stream = pxl8_stream_create(file_data, (u32)file_size);
|
|
|
|
pxl8_bsp_header header;
|
|
header.version = pxl8_read_u32(&stream);
|
|
|
|
if (header.version != BSP_VERSION) {
|
|
pxl8_error("Invalid BSP version: %u (expected %d)", header.version, BSP_VERSION);
|
|
free(file_data);
|
|
return PXL8_ERROR_INVALID_FORMAT;
|
|
}
|
|
|
|
for (i32 i = 0; i < CHUNK_COUNT; i++) {
|
|
header.chunks[i].offset = pxl8_read_u32(&stream);
|
|
header.chunks[i].size = pxl8_read_u32(&stream);
|
|
}
|
|
|
|
pxl8_bsp_chunk* chunk = &header.chunks[CHUNK_VERTICES];
|
|
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));
|
|
if (!bsp->vertices) goto error_cleanup;
|
|
pxl8_stream_seek(&stream, chunk->offset);
|
|
for (u32 i = 0; i < bsp->num_vertices; i++) {
|
|
bsp->vertices[i].position = read_vec3(&stream);
|
|
}
|
|
}
|
|
|
|
chunk = &header.chunks[CHUNK_EDGES];
|
|
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));
|
|
pxl8_stream_seek(&stream, chunk->offset);
|
|
for (u32 i = 0; i < bsp->num_edges; i++) {
|
|
bsp->edges[i].vertex[0] = pxl8_read_u16(&stream);
|
|
bsp->edges[i].vertex[1] = pxl8_read_u16(&stream);
|
|
}
|
|
}
|
|
|
|
chunk = &header.chunks[CHUNK_SURFEDGES];
|
|
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));
|
|
pxl8_stream_seek(&stream, chunk->offset);
|
|
for (u32 i = 0; i < bsp->num_surfedges; i++) {
|
|
bsp->surfedges[i] = pxl8_read_i32(&stream);
|
|
}
|
|
}
|
|
|
|
chunk = &header.chunks[CHUNK_PLANES];
|
|
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));
|
|
pxl8_stream_seek(&stream, chunk->offset);
|
|
for (u32 i = 0; i < bsp->num_planes; i++) {
|
|
bsp->planes[i].normal = read_vec3(&stream);
|
|
bsp->planes[i].dist = pxl8_read_f32(&stream);
|
|
bsp->planes[i].type = pxl8_read_i32(&stream);
|
|
}
|
|
}
|
|
|
|
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));
|
|
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);
|
|
}
|
|
}
|
|
|
|
chunk = &header.chunks[CHUNK_FACES];
|
|
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));
|
|
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].styles[0] = pxl8_read_u8(&stream);
|
|
bsp->faces[i].styles[1] = pxl8_read_u8(&stream);
|
|
bsp->faces[i].styles[2] = pxl8_read_u8(&stream);
|
|
bsp->faces[i].styles[3] = pxl8_read_u8(&stream);
|
|
bsp->faces[i].lightmap_offset = pxl8_read_u32(&stream);
|
|
|
|
bsp->faces[i].aabb_min = (pxl8_vec3){1e30f, 1e30f, 1e30f};
|
|
bsp->faces[i].aabb_max = (pxl8_vec3){-1e30f, -1e30f, -1e30f};
|
|
}
|
|
}
|
|
|
|
chunk = &header.chunks[CHUNK_NODES];
|
|
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));
|
|
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);
|
|
bsp->nodes[i].first_face = pxl8_read_u16(&stream);
|
|
bsp->nodes[i].num_faces = pxl8_read_u16(&stream);
|
|
}
|
|
}
|
|
|
|
chunk = &header.chunks[CHUNK_LEAFS];
|
|
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));
|
|
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);
|
|
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);
|
|
}
|
|
}
|
|
|
|
chunk = &header.chunks[CHUNK_MARKSURFACES];
|
|
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));
|
|
pxl8_stream_seek(&stream, chunk->offset);
|
|
for (u32 i = 0; i < bsp->num_marksurfaces; i++) {
|
|
bsp->marksurfaces[i] = pxl8_read_u16(&stream);
|
|
}
|
|
}
|
|
|
|
chunk = &header.chunks[CHUNK_MODELS];
|
|
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));
|
|
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);
|
|
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);
|
|
bsp->models[i].first_face = pxl8_read_i32(&stream);
|
|
bsp->models[i].num_faces = pxl8_read_i32(&stream);
|
|
}
|
|
}
|
|
|
|
chunk = &header.chunks[CHUNK_VISIBILITY];
|
|
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);
|
|
memcpy(bsp->visdata, file_data + chunk->offset, bsp->visdata_size);
|
|
}
|
|
|
|
chunk = &header.chunks[CHUNK_LIGHTING];
|
|
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);
|
|
memcpy(bsp->lightdata, file_data + chunk->offset, bsp->lightdata_size);
|
|
}
|
|
|
|
free(file_data);
|
|
|
|
for (u32 i = 0; i < bsp->num_faces; i++) {
|
|
pxl8_bsp_face* face = &bsp->faces[i];
|
|
f32 min_x = 1e30f, min_y = 1e30f, min_z = 1e30f;
|
|
f32 max_x = -1e30f, max_y = -1e30f, max_z = -1e30f;
|
|
|
|
for (u32 j = 0; j < face->num_edges; j++) {
|
|
i32 surfedge_idx = face->first_edge + j;
|
|
u32 vert_idx;
|
|
|
|
if (!pxl8_bsp_get_edge_vertex(bsp, surfedge_idx, &vert_idx)) continue;
|
|
|
|
pxl8_vec3 v = bsp->vertices[vert_idx].position;
|
|
|
|
if (v.x < min_x) min_x = v.x;
|
|
if (v.x > max_x) max_x = v.x;
|
|
if (v.y < min_y) min_y = v.y;
|
|
if (v.y > max_y) max_y = v.y;
|
|
if (v.z < min_z) min_z = v.z;
|
|
if (v.z > max_z) max_z = v.z;
|
|
}
|
|
|
|
face->aabb_min = (pxl8_vec3){min_x, min_y, min_z};
|
|
face->aabb_max = (pxl8_vec3){max_x, max_y, max_z};
|
|
}
|
|
|
|
pxl8_debug("Loaded BSP: %u verts, %u faces, %u nodes, %u leafs",
|
|
bsp->num_vertices, bsp->num_faces, bsp->num_nodes, bsp->num_leafs);
|
|
|
|
return PXL8_OK;
|
|
|
|
error_cleanup:
|
|
pxl8_error("BSP chunk validation failed: %s", path);
|
|
free(file_data);
|
|
pxl8_bsp_destroy(bsp);
|
|
return PXL8_ERROR_INVALID_FORMAT;
|
|
}
|
|
|
|
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);
|
|
|
|
memset(bsp, 0, sizeof(*bsp));
|
|
}
|
|
|
|
i32 pxl8_bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos) {
|
|
if (!bsp || bsp->num_nodes == 0) return -1;
|
|
|
|
i32 node_id = 0;
|
|
|
|
while (node_id >= 0) {
|
|
const pxl8_bsp_node* node = &bsp->nodes[node_id];
|
|
const pxl8_bsp_plane* plane = &bsp->planes[node->plane_id];
|
|
|
|
f32 dist = pxl8_vec3_dot(pos, plane->normal) - plane->dist;
|
|
node_id = node->children[dist < 0 ? 1 : 0];
|
|
}
|
|
|
|
return -(node_id + 1);
|
|
}
|
|
|
|
bool pxl8_bsp_is_leaf_visible(const pxl8_bsp* bsp, i32 leaf_from, i32 leaf_to) {
|
|
if (!bsp || !bsp->visdata || leaf_from < 0 || leaf_to < 0) return true;
|
|
if ((u32)leaf_from >= bsp->num_leafs || (u32)leaf_to >= bsp->num_leafs) return true;
|
|
|
|
i32 visofs = bsp->leafs[leaf_from].visofs;
|
|
if (visofs < 0) return true;
|
|
|
|
i32 byte_idx = leaf_to >> 3;
|
|
i32 bit_idx = leaf_to & 7;
|
|
|
|
if ((u32)visofs + byte_idx >= bsp->visdata_size) return true;
|
|
|
|
return (bsp->visdata[visofs + byte_idx] & (1 << bit_idx)) != 0;
|
|
}
|
|
|
|
static inline bool face_in_frustum(const pxl8_bsp* bsp, u32 face_id, const pxl8_frustum* frustum) {
|
|
const pxl8_bsp_face* face = &bsp->faces[face_id];
|
|
return pxl8_frustum_test_aabb(frustum, face->aabb_min, face->aabb_max);
|
|
}
|
|
|
|
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;
|
|
|
|
const pxl8_bsp_face* face = &bsp->faces[face_id];
|
|
if (face->num_edges < 3) return;
|
|
|
|
pxl8_vec3 verts[64];
|
|
u32 num_verts = 0;
|
|
|
|
u32 color = 15;
|
|
bool use_texture = (face->texinfo_id < bsp->num_texinfo);
|
|
|
|
|
|
for (u32 i = 0; i < face->num_edges && num_verts < 64; i++) {
|
|
i32 surfedge_idx = face->first_edge + i;
|
|
u32 vert_idx;
|
|
|
|
if (!pxl8_bsp_get_edge_vertex(bsp, surfedge_idx, &vert_idx)) continue;
|
|
|
|
verts[num_verts++] = bsp->vertices[vert_idx].position;
|
|
}
|
|
|
|
if (num_verts < 3) return;
|
|
|
|
if (use_texture && face->texinfo_id < bsp->num_texinfo) {
|
|
const pxl8_bsp_texinfo* texinfo = &bsp->texinfo[face->texinfo_id];
|
|
f32 tex_scale = 64.0f;
|
|
|
|
for (u32 tri_idx = 1; tri_idx < num_verts - 1; tri_idx++) {
|
|
pxl8_vec3 v0 = verts[0];
|
|
pxl8_vec3 v1 = verts[tri_idx];
|
|
pxl8_vec3 v2 = verts[tri_idx + 1];
|
|
|
|
f32 u0 = (pxl8_vec3_dot(v0, texinfo->u_axis) + texinfo->u_offset) / tex_scale;
|
|
f32 v0_uv = (pxl8_vec3_dot(v0, texinfo->v_axis) + texinfo->v_offset) / tex_scale;
|
|
f32 u1 = (pxl8_vec3_dot(v1, texinfo->u_axis) + texinfo->u_offset) / tex_scale;
|
|
f32 v1_uv = (pxl8_vec3_dot(v1, texinfo->v_axis) + texinfo->v_offset) / tex_scale;
|
|
f32 u2 = (pxl8_vec3_dot(v2, texinfo->u_axis) + texinfo->u_offset) / tex_scale;
|
|
f32 v2_uv = (pxl8_vec3_dot(v2, texinfo->v_axis) + texinfo->v_offset) / tex_scale;
|
|
|
|
pxl8_3d_draw_triangle_textured(gfx, v0, v1, v2, u0, v0_uv, u1, v1_uv, u2, v2_uv, texture_id);
|
|
}
|
|
} else{
|
|
for (u32 i = 1; i < num_verts - 1; i++) {
|
|
pxl8_3d_draw_triangle_raw(gfx, verts[0], verts[i], verts[i + 1], color);
|
|
}
|
|
}
|
|
}
|
|
|
|
void pxl8_bsp_render_textured(
|
|
pxl8_gfx* gfx,
|
|
const pxl8_bsp* bsp,
|
|
pxl8_vec3 camera_pos
|
|
) {
|
|
if (!gfx || !bsp || bsp->num_faces == 0) return;
|
|
|
|
const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx);
|
|
if (!frustum) return;
|
|
|
|
i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
|
|
|
|
static u8* rendered_faces = NULL;
|
|
static u32 rendered_faces_capacity = 0;
|
|
|
|
if (rendered_faces_capacity < bsp->num_faces) {
|
|
rendered_faces = realloc(rendered_faces, bsp->num_faces);
|
|
if (!rendered_faces) return;
|
|
rendered_faces_capacity = bsp->num_faces;
|
|
pxl8_debug("Allocated face tracking buffer: %u bytes", bsp->num_faces);
|
|
}
|
|
|
|
memset(rendered_faces, 0, bsp->num_faces);
|
|
|
|
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;
|
|
|
|
const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id];
|
|
|
|
for (u32 i = 0; i < leaf->num_marksurfaces; i++) {
|
|
u32 surf_idx = leaf->first_marksurface + i;
|
|
if (surf_idx >= bsp->num_marksurfaces) 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)) 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;
|
|
} else {
|
|
static bool warned = false;
|
|
if (!warned) {
|
|
pxl8_warn("Face %u has invalid texinfo_id %u (num_texinfo=%u)",
|
|
face_id, face->texinfo_id, bsp->num_texinfo);
|
|
warned = true;
|
|
}
|
|
}
|
|
|
|
pxl8_bsp_render_face(gfx, bsp, face_id, texture_id);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
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);
|
|
|
|
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;
|
|
|
|
const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id];
|
|
|
|
for (u32 i = 0; i < leaf->num_marksurfaces; i++) {
|
|
u32 surf_idx = leaf->first_marksurface + i;
|
|
if (surf_idx >= bsp->num_marksurfaces) continue;
|
|
|
|
u32 face_id = bsp->marksurfaces[surf_idx];
|
|
if (face_id >= bsp->num_faces) continue;
|
|
|
|
if (!face_in_frustum(bsp, face_id, frustum)) continue;
|
|
|
|
const pxl8_bsp_face* face = &bsp->faces[face_id];
|
|
|
|
for (u32 e = 0; e < face->num_edges; e++) {
|
|
i32 surfedge_idx = face->first_edge + e;
|
|
i32 next_surfedge_idx = face->first_edge + ((e + 1) % face->num_edges);
|
|
|
|
if (surfedge_idx >= (i32)bsp->num_surfedges ||
|
|
next_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_3d(gfx, p0, p1, color);
|
|
}
|
|
}
|
|
}
|
|
}
|