improve sw renderer
This commit is contained in:
parent
415d424057
commit
39ee0fefb7
89 changed files with 9380 additions and 2307 deletions
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ typedef struct pxl8_bsp_face {
|
|||
u16 side;
|
||||
|
||||
u8 styles[4];
|
||||
u16 texinfo_id;
|
||||
u16 material_id;
|
||||
|
||||
pxl8_vec3 aabb_min;
|
||||
pxl8_vec3 aabb_max;
|
||||
|
|
@ -63,16 +63,6 @@ typedef struct pxl8_bsp_plane {
|
|||
i32 type;
|
||||
} pxl8_bsp_plane;
|
||||
|
||||
typedef struct pxl8_bsp_texinfo {
|
||||
u32 miptex;
|
||||
char name[16];
|
||||
|
||||
f32 u_offset;
|
||||
pxl8_vec3 u_axis;
|
||||
|
||||
f32 v_offset;
|
||||
pxl8_vec3 v_axis;
|
||||
} pxl8_bsp_texinfo;
|
||||
|
||||
typedef struct pxl8_bsp_vertex {
|
||||
pxl8_vec3 position;
|
||||
|
|
@ -91,47 +81,52 @@ typedef struct pxl8_bsp_lightmap_sample {
|
|||
u8 r;
|
||||
} pxl8_bsp_lightmap_sample;
|
||||
|
||||
typedef struct pxl8_bsp_material_batch {
|
||||
u16* face_indices;
|
||||
u32 face_count;
|
||||
u8 material_id;
|
||||
pxl8_mesh* mesh;
|
||||
} pxl8_bsp_material_batch;
|
||||
|
||||
typedef struct pxl8_bsp_pvs {
|
||||
u8* data;
|
||||
u32 size;
|
||||
} pxl8_bsp_pvs;
|
||||
|
||||
typedef struct pxl8_bsp_portal {
|
||||
f32 x0, z0;
|
||||
f32 x1, z1;
|
||||
u32 target_leaf;
|
||||
} pxl8_bsp_portal;
|
||||
|
||||
typedef struct pxl8_bsp_cell_portals {
|
||||
pxl8_bsp_portal portals[4];
|
||||
u8 num_portals;
|
||||
} pxl8_bsp_cell_portals;
|
||||
|
||||
typedef struct pxl8_bsp {
|
||||
pxl8_bsp_cell_portals* cell_portals;
|
||||
pxl8_bsp_edge* edges;
|
||||
pxl8_bsp_face* faces;
|
||||
pxl8_bsp_leaf* leafs;
|
||||
u8* lightdata;
|
||||
pxl8_bsp_lightmap* lightmaps;
|
||||
u16* marksurfaces;
|
||||
pxl8_bsp_material_batch* material_batches;
|
||||
pxl8_gfx_material* materials;
|
||||
pxl8_bsp_model* models;
|
||||
pxl8_bsp_node* nodes;
|
||||
pxl8_bsp_plane* planes;
|
||||
u8* render_face_flags;
|
||||
i32* surfedges;
|
||||
pxl8_bsp_texinfo* texinfo;
|
||||
u32* vertex_lights;
|
||||
pxl8_bsp_vertex* vertices;
|
||||
u8* visdata;
|
||||
|
||||
u32 lightdata_size;
|
||||
u32 num_cell_portals;
|
||||
u32 num_edges;
|
||||
u32 num_faces;
|
||||
u32 num_leafs;
|
||||
u32 num_lightmaps;
|
||||
u32 num_marksurfaces;
|
||||
u32 num_material_batches;
|
||||
u32 num_materials;
|
||||
u32 num_models;
|
||||
u32 num_nodes;
|
||||
u32 num_planes;
|
||||
u32 num_surfedges;
|
||||
u32 num_texinfo;
|
||||
u32 num_vertex_lights;
|
||||
u32 num_vertices;
|
||||
u32 visdata_size;
|
||||
|
|
@ -155,9 +150,8 @@ pxl8_bsp_lightmap pxl8_bsp_lightmap_uniform(u8 r, u8 g, u8 b);
|
|||
pxl8_bsp_lightmap pxl8_bsp_lightmap_mapped(u8 width, u8 height, u32 offset);
|
||||
pxl8_bsp_lightmap_sample pxl8_bsp_sample_lightmap(const pxl8_bsp* bsp, u32 face_idx, f32 u, f32 v);
|
||||
|
||||
void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 texture_id);
|
||||
void pxl8_bsp_render_textured(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos);
|
||||
void pxl8_bsp_render_wireframe(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos, u32 color);
|
||||
void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const pxl8_gfx_material* material);
|
||||
void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,40 @@
|
|||
#include "pxl8_gen.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_mem.h"
|
||||
#include "pxl8_rng.h"
|
||||
|
||||
#define CELL_SIZE 64.0f
|
||||
#define PVS_MAX_DEPTH 64
|
||||
#define WALL_HEIGHT 128.0f
|
||||
|
||||
typedef struct room_grid {
|
||||
u8* cells;
|
||||
i32 width;
|
||||
i32 height;
|
||||
} room_grid;
|
||||
|
||||
typedef struct bsp_build_context {
|
||||
pxl8_bsp* bsp;
|
||||
const room_grid* grid;
|
||||
u32 node_count;
|
||||
u32 plane_offset;
|
||||
} bsp_build_context;
|
||||
|
||||
typedef struct light_source {
|
||||
pxl8_vec3 position;
|
||||
f32 intensity;
|
||||
f32 radius;
|
||||
} light_source;
|
||||
|
||||
static bool room_grid_init(room_grid* grid, i32 width, i32 height) {
|
||||
grid->width = width;
|
||||
grid->height = height;
|
||||
grid->cells = calloc(width * height, sizeof(u8));
|
||||
grid->cells = pxl8_calloc(width * height, sizeof(u8));
|
||||
|
||||
return grid->cells != NULL;
|
||||
}
|
||||
|
|
@ -57,6 +76,333 @@ static void room_grid_fill(room_grid* grid, u8 value) {
|
|||
}
|
||||
}
|
||||
|
||||
static f32 compute_vertex_light(
|
||||
pxl8_vec3 pos,
|
||||
pxl8_vec3 normal,
|
||||
const light_source* lights,
|
||||
u32 num_lights,
|
||||
f32 ambient
|
||||
) {
|
||||
f32 total = ambient;
|
||||
|
||||
for (u32 i = 0; i < num_lights; i++) {
|
||||
pxl8_vec3 to_light = {
|
||||
lights[i].position.x - pos.x,
|
||||
lights[i].position.y - pos.y,
|
||||
lights[i].position.z - pos.z
|
||||
};
|
||||
|
||||
f32 dist_sq = to_light.x * to_light.x + to_light.y * to_light.y + to_light.z * to_light.z;
|
||||
f32 dist = sqrtf(dist_sq);
|
||||
|
||||
if (dist > lights[i].radius) continue;
|
||||
if (dist < 1.0f) dist = 1.0f;
|
||||
|
||||
f32 inv_dist = 1.0f / dist;
|
||||
pxl8_vec3 light_dir = {
|
||||
to_light.x * inv_dist,
|
||||
to_light.y * inv_dist,
|
||||
to_light.z * inv_dist
|
||||
};
|
||||
|
||||
f32 ndotl = normal.x * light_dir.x + normal.y * light_dir.y + normal.z * light_dir.z;
|
||||
if (ndotl < 0) ndotl = 0;
|
||||
|
||||
f32 attenuation = 1.0f - (dist / lights[i].radius);
|
||||
if (attenuation < 0) attenuation = 0;
|
||||
attenuation *= attenuation;
|
||||
|
||||
total += lights[i].intensity * ndotl * attenuation;
|
||||
}
|
||||
|
||||
if (total > 1.0f) total = 1.0f;
|
||||
return total;
|
||||
}
|
||||
|
||||
static void compute_bsp_vertex_lighting(
|
||||
pxl8_bsp* bsp,
|
||||
const light_source* lights,
|
||||
u32 num_lights,
|
||||
f32 ambient
|
||||
) {
|
||||
if (!bsp || bsp->num_vertices == 0) return;
|
||||
|
||||
bsp->vertex_lights = pxl8_calloc(bsp->num_vertices, sizeof(u32));
|
||||
if (!bsp->vertex_lights) return;
|
||||
bsp->num_vertex_lights = bsp->num_vertices;
|
||||
|
||||
for (u32 f = 0; f < bsp->num_faces; f++) {
|
||||
const pxl8_bsp_face* face = &bsp->faces[f];
|
||||
pxl8_vec3 normal = {0, 1, 0};
|
||||
|
||||
if (face->plane_id < bsp->num_planes) {
|
||||
normal = bsp->planes[face->plane_id].normal;
|
||||
}
|
||||
|
||||
for (u32 e = 0; e < face->num_edges; e++) {
|
||||
i32 surfedge_idx = face->first_edge + e;
|
||||
if (surfedge_idx >= (i32)bsp->num_surfedges) continue;
|
||||
|
||||
i32 edge_idx = bsp->surfedges[surfedge_idx];
|
||||
u32 vert_idx;
|
||||
|
||||
if (edge_idx >= 0) {
|
||||
if ((u32)edge_idx >= bsp->num_edges) continue;
|
||||
vert_idx = bsp->edges[edge_idx].vertex[0];
|
||||
} else {
|
||||
edge_idx = -edge_idx;
|
||||
if ((u32)edge_idx >= bsp->num_edges) continue;
|
||||
vert_idx = bsp->edges[edge_idx].vertex[1];
|
||||
}
|
||||
|
||||
if (vert_idx >= bsp->num_vertices) continue;
|
||||
|
||||
pxl8_vec3 pos = bsp->vertices[vert_idx].position;
|
||||
f32 light = compute_vertex_light(pos, normal, lights, num_lights, ambient);
|
||||
|
||||
u8 light_byte = (u8)(light * 255.0f);
|
||||
bsp->vertex_lights[vert_idx] = ((u32)light_byte << 24) | 0x00FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_debug("Computed vertex lighting: %u vertices, %u lights", bsp->num_vertices, num_lights);
|
||||
}
|
||||
|
||||
static pxl8_bsp_cell_portals* build_pxl8_bsp_cell_portals(const room_grid* grid, f32 cell_size) {
|
||||
i32 total_cells = grid->width * grid->height;
|
||||
pxl8_bsp_cell_portals* portals = pxl8_calloc(total_cells, sizeof(pxl8_bsp_cell_portals));
|
||||
if (!portals) return NULL;
|
||||
|
||||
for (i32 y = 0; y < grid->height; y++) {
|
||||
for (i32 x = 0; x < grid->width; x++) {
|
||||
if (room_grid_get(grid, x, y) != 0) continue;
|
||||
|
||||
i32 c = y * grid->width + x;
|
||||
f32 cx = x * cell_size;
|
||||
f32 cz = y * cell_size;
|
||||
|
||||
if (x > 0 && room_grid_get(grid, x - 1, y) == 0) {
|
||||
pxl8_bsp_portal* p = &portals[c].portals[portals[c].num_portals++];
|
||||
p->x0 = cx;
|
||||
p->z0 = cz;
|
||||
p->x1 = cx;
|
||||
p->z1 = cz + cell_size;
|
||||
p->target_leaf = y * grid->width + (x - 1);
|
||||
}
|
||||
if (x < grid->width - 1 && room_grid_get(grid, x + 1, y) == 0) {
|
||||
pxl8_bsp_portal* p = &portals[c].portals[portals[c].num_portals++];
|
||||
p->x0 = cx + cell_size;
|
||||
p->z0 = cz + cell_size;
|
||||
p->x1 = cx + cell_size;
|
||||
p->z1 = cz;
|
||||
p->target_leaf = y * grid->width + (x + 1);
|
||||
}
|
||||
if (y > 0 && room_grid_get(grid, x, y - 1) == 0) {
|
||||
pxl8_bsp_portal* p = &portals[c].portals[portals[c].num_portals++];
|
||||
p->x0 = cx + cell_size;
|
||||
p->z0 = cz;
|
||||
p->x1 = cx;
|
||||
p->z1 = cz;
|
||||
p->target_leaf = (y - 1) * grid->width + x;
|
||||
}
|
||||
if (y < grid->height - 1 && room_grid_get(grid, x, y + 1) == 0) {
|
||||
pxl8_bsp_portal* p = &portals[c].portals[portals[c].num_portals++];
|
||||
p->x0 = cx;
|
||||
p->z0 = cz + cell_size;
|
||||
p->x1 = cx + cell_size;
|
||||
p->z1 = cz + cell_size;
|
||||
p->target_leaf = (y + 1) * grid->width + x;
|
||||
}
|
||||
}
|
||||
}
|
||||
return portals;
|
||||
}
|
||||
|
||||
typedef struct flood_entry {
|
||||
u32 leaf;
|
||||
u32 depth;
|
||||
} flood_entry;
|
||||
|
||||
static void portal_flood_bfs(
|
||||
u32 start_leaf,
|
||||
const pxl8_bsp_cell_portals* portals,
|
||||
const pxl8_bsp_leaf* leafs,
|
||||
u8* pvs,
|
||||
u32 num_leafs,
|
||||
f32 cell_size,
|
||||
i32 grid_width
|
||||
) {
|
||||
(void)cell_size;
|
||||
(void)grid_width;
|
||||
|
||||
u32 pvs_bytes = (num_leafs + 7) / 8;
|
||||
u8* visited = pxl8_calloc(pvs_bytes, 1);
|
||||
flood_entry* queue = pxl8_malloc(num_leafs * sizeof(flood_entry));
|
||||
if (!visited || !queue) {
|
||||
pxl8_free(visited);
|
||||
pxl8_free(queue);
|
||||
return;
|
||||
}
|
||||
|
||||
u32 head = 0, tail = 0;
|
||||
|
||||
pvs[start_leaf >> 3] |= (1 << (start_leaf & 7));
|
||||
visited[start_leaf >> 3] |= (1 << (start_leaf & 7));
|
||||
queue[tail++] = (flood_entry){start_leaf, 0};
|
||||
|
||||
while (head < tail) {
|
||||
flood_entry e = queue[head++];
|
||||
|
||||
if (e.depth >= PVS_MAX_DEPTH) continue;
|
||||
if (leafs[e.leaf].contents == -1) continue;
|
||||
|
||||
const pxl8_bsp_cell_portals* cp = &portals[e.leaf];
|
||||
for (u8 i = 0; i < cp->num_portals; i++) {
|
||||
u32 target = cp->portals[i].target_leaf;
|
||||
u32 byte = target >> 3;
|
||||
u32 bit = target & 7;
|
||||
|
||||
if (visited[byte] & (1 << bit)) continue;
|
||||
visited[byte] |= (1 << bit);
|
||||
|
||||
if (leafs[target].contents == -1) continue;
|
||||
|
||||
pvs[byte] |= (1 << bit);
|
||||
queue[tail++] = (flood_entry){target, e.depth + 1};
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_free(visited);
|
||||
pxl8_free(queue);
|
||||
}
|
||||
|
||||
static u8* compute_leaf_pvs(u32 start_leaf, const pxl8_bsp_cell_portals* portals,
|
||||
u32 num_leafs, const pxl8_bsp_leaf* leafs,
|
||||
const room_grid* grid, f32 cell_size) {
|
||||
u32 pvs_bytes = (num_leafs + 7) / 8;
|
||||
u8* pvs = pxl8_calloc(pvs_bytes, 1);
|
||||
if (!pvs) return NULL;
|
||||
|
||||
portal_flood_bfs(start_leaf, portals, leafs, pvs, num_leafs, cell_size, grid->width);
|
||||
|
||||
return pvs;
|
||||
}
|
||||
|
||||
static u32 rle_compress_pvs(const u8* pvs, u32 pvs_bytes, u8* out) {
|
||||
u32 out_pos = 0;
|
||||
u32 i = 0;
|
||||
|
||||
while (i < pvs_bytes) {
|
||||
if (pvs[i] != 0) {
|
||||
out[out_pos++] = pvs[i++];
|
||||
} else {
|
||||
u32 count = 0;
|
||||
while (i < pvs_bytes && pvs[i] == 0 && count < 255) {
|
||||
count++;
|
||||
i++;
|
||||
}
|
||||
out[out_pos++] = 0;
|
||||
out[out_pos++] = (u8)count;
|
||||
}
|
||||
}
|
||||
return out_pos;
|
||||
}
|
||||
|
||||
static pxl8_result build_pvs_data(pxl8_bsp* bsp, const pxl8_bsp_cell_portals* portals,
|
||||
const room_grid* grid, f32 cell_size) {
|
||||
u32 num_leafs = bsp->num_leafs;
|
||||
u32 pvs_bytes = (num_leafs + 7) / 8;
|
||||
|
||||
u32 max_visdata = num_leafs * pvs_bytes * 2;
|
||||
u8* visdata = pxl8_malloc(max_visdata);
|
||||
if (!visdata) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
u32 visdata_pos = 0;
|
||||
|
||||
u8* compressed = pxl8_malloc(pvs_bytes * 2);
|
||||
if (!compressed) {
|
||||
pxl8_free(visdata);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
u32 debug_count = 0;
|
||||
for (u32 leaf = 0; leaf < num_leafs; leaf++) {
|
||||
if (bsp->leafs[leaf].contents == -1) {
|
||||
bsp->leafs[leaf].visofs = -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
u8* pvs = compute_leaf_pvs(leaf, portals, num_leafs, bsp->leafs, grid, cell_size);
|
||||
if (!pvs) {
|
||||
pxl8_free(compressed);
|
||||
pxl8_free(visdata);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
if (debug_count < 3) {
|
||||
u32 visible = 0;
|
||||
for (u32 b = 0; b < pvs_bytes; b++) {
|
||||
for (u32 i = 0; i < 8; i++) {
|
||||
if (pvs[b] & (1 << i)) visible++;
|
||||
}
|
||||
}
|
||||
pxl8_debug("Leaf %u PVS: %u cells visible (portals: %u)", leaf, visible, portals[leaf].num_portals);
|
||||
debug_count++;
|
||||
}
|
||||
|
||||
u32 compressed_size = rle_compress_pvs(pvs, pvs_bytes, compressed);
|
||||
|
||||
bsp->leafs[leaf].visofs = visdata_pos;
|
||||
memcpy(visdata + visdata_pos, compressed, compressed_size);
|
||||
visdata_pos += compressed_size;
|
||||
|
||||
pxl8_free(pvs);
|
||||
}
|
||||
|
||||
pxl8_free(compressed);
|
||||
bsp->visdata = pxl8_realloc(visdata, visdata_pos > 0 ? visdata_pos : 1);
|
||||
bsp->visdata_size = visdata_pos;
|
||||
|
||||
pxl8_debug("Built PVS: %u leafs, %u bytes visdata", num_leafs, visdata_pos);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static i32 build_bsp_node(bsp_build_context* ctx, i32 x0, i32 y0, i32 x1, i32 y1, i32 depth) {
|
||||
if (x1 - x0 == 1 && y1 - y0 == 1) {
|
||||
i32 leaf_idx = y0 * ctx->grid->width + x0;
|
||||
return -(leaf_idx + 1);
|
||||
}
|
||||
|
||||
i32 node_idx = ctx->node_count++;
|
||||
pxl8_bsp_node* node = &ctx->bsp->nodes[node_idx];
|
||||
|
||||
i32 plane_idx = ctx->plane_offset++;
|
||||
pxl8_bsp_plane* plane = &ctx->bsp->planes[plane_idx];
|
||||
node->plane_id = plane_idx;
|
||||
|
||||
if (depth % 2 == 0) {
|
||||
i32 mid_x = (x0 + x1) / 2;
|
||||
f32 split_pos = mid_x * CELL_SIZE;
|
||||
|
||||
plane->normal = (pxl8_vec3){1, 0, 0};
|
||||
plane->dist = split_pos;
|
||||
|
||||
node->children[0] = build_bsp_node(ctx, mid_x, y0, x1, y1, depth + 1);
|
||||
node->children[1] = build_bsp_node(ctx, x0, y0, mid_x, y1, depth + 1);
|
||||
} else {
|
||||
i32 mid_y = (y0 + y1) / 2;
|
||||
f32 split_pos = mid_y * CELL_SIZE;
|
||||
|
||||
plane->normal = (pxl8_vec3){0, 0, 1};
|
||||
plane->dist = split_pos;
|
||||
|
||||
node->children[0] = build_bsp_node(ctx, x0, mid_y, x1, y1, depth + 1);
|
||||
node->children[1] = build_bsp_node(ctx, x0, y0, x1, mid_y, depth + 1);
|
||||
}
|
||||
|
||||
return node_idx;
|
||||
}
|
||||
|
||||
static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
|
||||
i32 vertex_count = 0;
|
||||
i32 face_count = 0;
|
||||
|
|
@ -74,37 +420,47 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
|
|||
}
|
||||
}
|
||||
|
||||
face_count += floor_ceiling_count * 2;
|
||||
face_count += floor_ceiling_count;
|
||||
vertex_count = face_count * 4;
|
||||
|
||||
pxl8_debug("Cave generation: %dx%d grid -> %d faces, %d vertices",
|
||||
pxl8_debug("Level generation: %dx%d grid -> %d faces, %d vertices",
|
||||
grid->width, grid->height, face_count, vertex_count);
|
||||
|
||||
bsp->vertices = calloc(vertex_count, sizeof(pxl8_bsp_vertex));
|
||||
bsp->faces = calloc(face_count, sizeof(pxl8_bsp_face));
|
||||
bsp->planes = calloc(face_count, sizeof(pxl8_bsp_plane));
|
||||
bsp->edges = calloc(vertex_count, sizeof(pxl8_bsp_edge));
|
||||
bsp->surfedges = calloc(vertex_count, sizeof(i32));
|
||||
i32 total_cells = grid->width * grid->height;
|
||||
u32 max_nodes = 2 * total_cells;
|
||||
u32 total_planes = face_count + max_nodes;
|
||||
|
||||
if (!bsp->vertices || !bsp->faces || !bsp->planes || !bsp->edges || !bsp->surfedges) {
|
||||
bsp->vertices = pxl8_calloc(vertex_count, sizeof(pxl8_bsp_vertex));
|
||||
bsp->faces = pxl8_calloc(face_count, sizeof(pxl8_bsp_face));
|
||||
bsp->planes = pxl8_calloc(total_planes, sizeof(pxl8_bsp_plane));
|
||||
bsp->edges = pxl8_calloc(vertex_count, sizeof(pxl8_bsp_edge));
|
||||
bsp->surfedges = pxl8_calloc(vertex_count, sizeof(i32));
|
||||
bsp->nodes = pxl8_calloc(max_nodes, sizeof(pxl8_bsp_node));
|
||||
|
||||
u32* face_cell = pxl8_calloc(face_count, sizeof(u32));
|
||||
|
||||
if (!bsp->vertices || !bsp->faces || !bsp->planes || !bsp->edges ||
|
||||
!bsp->surfedges || !bsp->nodes || !face_cell) {
|
||||
pxl8_free(face_cell);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
bsp->texinfo = NULL;
|
||||
bsp->num_texinfo = 0;
|
||||
bsp->materials = NULL;
|
||||
bsp->num_materials = 0;
|
||||
|
||||
i32 vert_idx = 0;
|
||||
i32 face_idx = 0;
|
||||
i32 edge_idx = 0;
|
||||
|
||||
const f32 cell_size = 64.0f;
|
||||
const f32 wall_height = 128.0f;
|
||||
const f32 cell_size = CELL_SIZE;
|
||||
const f32 wall_height = WALL_HEIGHT;
|
||||
|
||||
for (i32 y = 0; y < grid->height; y++) {
|
||||
for (i32 x = 0; x < grid->width; x++) {
|
||||
if (room_grid_get(grid, x, y) == 0) {
|
||||
f32 fx = (f32)x * cell_size;
|
||||
f32 fy = (f32)y * cell_size;
|
||||
i32 cell_idx = y * grid->width + x;
|
||||
|
||||
if (room_grid_get(grid, x - 1, y) == 1) {
|
||||
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy};
|
||||
|
|
@ -112,13 +468,13 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
|
|||
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx, wall_height, fy + cell_size};
|
||||
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx, 0, fy + cell_size};
|
||||
|
||||
bsp->planes[face_idx].normal = (pxl8_vec3){-1, 0, 0};
|
||||
bsp->planes[face_idx].dist = -fx;
|
||||
bsp->planes[face_idx].normal = (pxl8_vec3){1, 0, 0};
|
||||
bsp->planes[face_idx].dist = fx;
|
||||
|
||||
bsp->faces[face_idx].plane_id = face_idx;
|
||||
bsp->faces[face_idx].num_edges = 4;
|
||||
bsp->faces[face_idx].first_edge = edge_idx;
|
||||
bsp->faces[face_idx].texinfo_id = 0;
|
||||
bsp->faces[face_idx].material_id = 0;
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
|
||||
|
|
@ -127,6 +483,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
|
|||
}
|
||||
|
||||
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
|
||||
face_cell[face_idx] = cell_idx;
|
||||
|
||||
vert_idx += 4;
|
||||
edge_idx += 4;
|
||||
|
|
@ -139,13 +496,13 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
|
|||
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy + cell_size};
|
||||
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx + cell_size, wall_height, fy};
|
||||
|
||||
bsp->planes[face_idx].normal = (pxl8_vec3){1, 0, 0};
|
||||
bsp->planes[face_idx].dist = fx + cell_size;
|
||||
bsp->planes[face_idx].normal = (pxl8_vec3){-1, 0, 0};
|
||||
bsp->planes[face_idx].dist = -(fx + cell_size);
|
||||
|
||||
bsp->faces[face_idx].plane_id = face_idx;
|
||||
bsp->faces[face_idx].num_edges = 4;
|
||||
bsp->faces[face_idx].first_edge = edge_idx;
|
||||
bsp->faces[face_idx].texinfo_id = 0;
|
||||
bsp->faces[face_idx].material_id = 0;
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
|
||||
|
|
@ -154,6 +511,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
|
|||
}
|
||||
|
||||
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
|
||||
face_cell[face_idx] = cell_idx;
|
||||
|
||||
vert_idx += 4;
|
||||
edge_idx += 4;
|
||||
|
|
@ -166,13 +524,13 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
|
|||
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy};
|
||||
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx, wall_height, fy};
|
||||
|
||||
bsp->planes[face_idx].normal = (pxl8_vec3){0, 0, -1};
|
||||
bsp->planes[face_idx].dist = -fy;
|
||||
bsp->planes[face_idx].normal = (pxl8_vec3){0, 0, 1};
|
||||
bsp->planes[face_idx].dist = fy;
|
||||
|
||||
bsp->faces[face_idx].plane_id = face_idx;
|
||||
bsp->faces[face_idx].num_edges = 4;
|
||||
bsp->faces[face_idx].first_edge = edge_idx;
|
||||
bsp->faces[face_idx].texinfo_id = 0;
|
||||
bsp->faces[face_idx].material_id = 0;
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
|
||||
|
|
@ -181,6 +539,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
|
|||
}
|
||||
|
||||
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
|
||||
face_cell[face_idx] = cell_idx;
|
||||
|
||||
vert_idx += 4;
|
||||
edge_idx += 4;
|
||||
|
|
@ -193,13 +552,13 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
|
|||
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy + cell_size};
|
||||
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx + cell_size, 0, fy + cell_size};
|
||||
|
||||
bsp->planes[face_idx].normal = (pxl8_vec3){0, 0, 1};
|
||||
bsp->planes[face_idx].dist = fy + cell_size;
|
||||
bsp->planes[face_idx].normal = (pxl8_vec3){0, 0, -1};
|
||||
bsp->planes[face_idx].dist = -(fy + cell_size);
|
||||
|
||||
bsp->faces[face_idx].plane_id = face_idx;
|
||||
bsp->faces[face_idx].num_edges = 4;
|
||||
bsp->faces[face_idx].first_edge = edge_idx;
|
||||
bsp->faces[face_idx].texinfo_id = 0;
|
||||
bsp->faces[face_idx].material_id = 0;
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
|
||||
|
|
@ -208,6 +567,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
|
|||
}
|
||||
|
||||
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
|
||||
face_cell[face_idx] = cell_idx;
|
||||
|
||||
vert_idx += 4;
|
||||
edge_idx += 4;
|
||||
|
|
@ -222,6 +582,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
|
|||
if (room_grid_get(grid, x, y) == 0) {
|
||||
f32 fx = (f32)x * cell_size;
|
||||
f32 fy = (f32)y * cell_size;
|
||||
i32 cell_idx = y * grid->width + x;
|
||||
|
||||
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy};
|
||||
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx, 0, fy + cell_size};
|
||||
|
|
@ -234,32 +595,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
|
|||
bsp->faces[face_idx].plane_id = face_idx;
|
||||
bsp->faces[face_idx].num_edges = 4;
|
||||
bsp->faces[face_idx].first_edge = edge_idx;
|
||||
bsp->faces[face_idx].texinfo_id = 0;
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
|
||||
bsp->edges[edge_idx + i].vertex[1] = vert_idx + ((i + 1) % 4);
|
||||
bsp->surfedges[edge_idx + i] = edge_idx + i;
|
||||
}
|
||||
|
||||
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
|
||||
|
||||
vert_idx += 4;
|
||||
edge_idx += 4;
|
||||
face_idx++;
|
||||
|
||||
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, wall_height, fy};
|
||||
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx + cell_size, wall_height, fy};
|
||||
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy + cell_size};
|
||||
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx, wall_height, fy + cell_size};
|
||||
|
||||
bsp->planes[face_idx].normal = (pxl8_vec3){0, -1, 0};
|
||||
bsp->planes[face_idx].dist = -wall_height;
|
||||
|
||||
bsp->faces[face_idx].plane_id = face_idx;
|
||||
bsp->faces[face_idx].num_edges = 4;
|
||||
bsp->faces[face_idx].first_edge = edge_idx;
|
||||
bsp->faces[face_idx].texinfo_id = 0;
|
||||
bsp->faces[face_idx].material_id = 0;
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
|
||||
|
|
@ -268,6 +604,7 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
|
|||
}
|
||||
|
||||
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
|
||||
face_cell[face_idx] = cell_idx;
|
||||
|
||||
vert_idx += 4;
|
||||
edge_idx += 4;
|
||||
|
|
@ -278,28 +615,147 @@ static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
|
|||
|
||||
bsp->num_vertices = vertex_count;
|
||||
bsp->num_faces = face_count;
|
||||
bsp->num_planes = face_count;
|
||||
bsp->num_edges = vertex_count;
|
||||
bsp->num_surfedges = vertex_count;
|
||||
|
||||
bsp->leafs = calloc(1, sizeof(pxl8_bsp_leaf));
|
||||
bsp->marksurfaces = calloc(face_count, sizeof(u16));
|
||||
bsp->leafs = pxl8_calloc(total_cells, sizeof(pxl8_bsp_leaf));
|
||||
bsp->marksurfaces = pxl8_calloc(face_count, sizeof(u16));
|
||||
|
||||
if (!bsp->leafs || !bsp->marksurfaces) {
|
||||
pxl8_free(face_cell);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
bsp->num_leafs = 1;
|
||||
bsp->num_leafs = total_cells;
|
||||
bsp->num_marksurfaces = face_count;
|
||||
|
||||
bsp->leafs[0].first_marksurface = 0;
|
||||
bsp->leafs[0].num_marksurfaces = face_count;
|
||||
bsp->leafs[0].contents = -2;
|
||||
u32* faces_per_cell = pxl8_calloc(total_cells, sizeof(u32));
|
||||
u32* cell_offset = pxl8_calloc(total_cells, sizeof(u32));
|
||||
u32* cell_cursor = pxl8_calloc(total_cells, sizeof(u32));
|
||||
|
||||
if (!faces_per_cell || !cell_offset || !cell_cursor) {
|
||||
pxl8_free(faces_per_cell);
|
||||
pxl8_free(cell_offset);
|
||||
pxl8_free(cell_cursor);
|
||||
pxl8_free(face_cell);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
for (i32 i = 0; i < face_count; i++) {
|
||||
bsp->marksurfaces[i] = i;
|
||||
faces_per_cell[face_cell[i]]++;
|
||||
}
|
||||
|
||||
u32 offset = 0;
|
||||
for (i32 c = 0; c < total_cells; c++) {
|
||||
cell_offset[c] = offset;
|
||||
offset += faces_per_cell[c];
|
||||
}
|
||||
|
||||
for (i32 i = 0; i < face_count; i++) {
|
||||
u32 c = face_cell[i];
|
||||
bsp->marksurfaces[cell_offset[c] + cell_cursor[c]++] = i;
|
||||
}
|
||||
|
||||
for (i32 y = 0; y < grid->height; y++) {
|
||||
for (i32 x = 0; x < grid->width; x++) {
|
||||
i32 c = y * grid->width + x;
|
||||
pxl8_bsp_leaf* leaf = &bsp->leafs[c];
|
||||
|
||||
f32 fx = (f32)x * cell_size;
|
||||
f32 fz = (f32)y * cell_size;
|
||||
|
||||
leaf->mins[0] = (i16)fx;
|
||||
leaf->mins[1] = 0;
|
||||
leaf->mins[2] = (i16)fz;
|
||||
leaf->maxs[0] = (i16)(fx + cell_size);
|
||||
leaf->maxs[1] = (i16)wall_height;
|
||||
leaf->maxs[2] = (i16)(fz + cell_size);
|
||||
|
||||
if (room_grid_get(grid, x, y) == 0) {
|
||||
leaf->contents = -2;
|
||||
leaf->first_marksurface = cell_offset[c];
|
||||
leaf->num_marksurfaces = faces_per_cell[c];
|
||||
} else {
|
||||
leaf->contents = -1;
|
||||
leaf->first_marksurface = 0;
|
||||
leaf->num_marksurfaces = 0;
|
||||
}
|
||||
leaf->visofs = -1;
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_free(faces_per_cell);
|
||||
pxl8_free(cell_offset);
|
||||
pxl8_free(cell_cursor);
|
||||
pxl8_free(face_cell);
|
||||
|
||||
bsp_build_context ctx = {
|
||||
.bsp = bsp,
|
||||
.grid = grid,
|
||||
.node_count = 0,
|
||||
.plane_offset = face_count,
|
||||
};
|
||||
|
||||
build_bsp_node(&ctx, 0, 0, grid->width, grid->height, 0);
|
||||
bsp->num_nodes = ctx.node_count;
|
||||
bsp->num_planes = ctx.plane_offset;
|
||||
|
||||
pxl8_debug("Built BSP tree: %u nodes, %u leafs, %u planes",
|
||||
bsp->num_nodes, bsp->num_leafs, bsp->num_planes);
|
||||
|
||||
pxl8_bsp_cell_portals* portals = build_pxl8_bsp_cell_portals(grid, cell_size);
|
||||
if (!portals) {
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
u32 walkable_cells = 0;
|
||||
u32 total_portals = 0;
|
||||
i32 first_walkable = -1;
|
||||
for (i32 c = 0; c < total_cells; c++) {
|
||||
if (bsp->leafs[c].contents == -2) {
|
||||
walkable_cells++;
|
||||
if (first_walkable < 0) first_walkable = c;
|
||||
}
|
||||
total_portals += portals[c].num_portals;
|
||||
}
|
||||
pxl8_debug("Portal stats: %u walkable cells, %u total portals (avg %.1f per cell)",
|
||||
walkable_cells, total_portals, (f32)total_portals / walkable_cells);
|
||||
|
||||
if (first_walkable >= 0) {
|
||||
u32 pvs_bytes = (total_cells + 7) / 8;
|
||||
u8* visited = pxl8_calloc(pvs_bytes, 1);
|
||||
u8* queue = pxl8_malloc(total_cells * sizeof(u32));
|
||||
u32 head = 0, tail = 0;
|
||||
((u32*)queue)[tail++] = first_walkable;
|
||||
visited[first_walkable >> 3] |= (1 << (first_walkable & 7));
|
||||
u32 reachable = 0;
|
||||
while (head < tail) {
|
||||
u32 c = ((u32*)queue)[head++];
|
||||
reachable++;
|
||||
for (u8 i = 0; i < portals[c].num_portals; i++) {
|
||||
u32 n = portals[c].portals[i].target_leaf;
|
||||
u32 nb = n >> 3, ni = n & 7;
|
||||
if (!(visited[nb] & (1 << ni))) {
|
||||
visited[nb] |= (1 << ni);
|
||||
((u32*)queue)[tail++] = n;
|
||||
}
|
||||
}
|
||||
}
|
||||
pxl8_debug("Connectivity: %u/%u walkable cells reachable from leaf %d",
|
||||
reachable, walkable_cells, first_walkable);
|
||||
pxl8_free(visited);
|
||||
pxl8_free(queue);
|
||||
}
|
||||
|
||||
pxl8_result pvs_result = build_pvs_data(bsp, portals, grid, cell_size);
|
||||
if (pvs_result != PXL8_OK) {
|
||||
pxl8_free(portals);
|
||||
return pvs_result;
|
||||
}
|
||||
|
||||
bsp->cell_portals = portals;
|
||||
bsp->num_cell_portals = total_cells;
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
|
|
@ -348,6 +804,9 @@ static pxl8_result procgen_rooms(pxl8_bsp* bsp, const pxl8_procgen_params* param
|
|||
i32 room_count = 0;
|
||||
i32 max_attempts = params->num_rooms * 10;
|
||||
|
||||
const f32 cell_size = CELL_SIZE;
|
||||
const f32 light_height = 80.0f;
|
||||
|
||||
for (i32 attempt = 0; attempt < max_attempts && room_count < params->num_rooms && room_count < 256; attempt++) {
|
||||
i32 w = params->min_room_size + (pxl8_rng_next(&rng) % (params->max_room_size - params->min_room_size + 1));
|
||||
i32 h = params->min_room_size + (pxl8_rng_next(&rng) % (params->max_room_size - params->min_room_size + 1));
|
||||
|
|
@ -394,7 +853,27 @@ static pxl8_result procgen_rooms(pxl8_bsp* bsp, const pxl8_procgen_params* param
|
|||
params->width, params->height, room_count);
|
||||
|
||||
pxl8_result result = grid_to_bsp(bsp, &grid);
|
||||
free(grid.cells);
|
||||
pxl8_free(grid.cells);
|
||||
|
||||
if (result != PXL8_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
light_source lights[256];
|
||||
u32 num_lights = 0;
|
||||
|
||||
for (i32 i = 0; i < room_count && num_lights < 256; i++) {
|
||||
f32 cx = (rooms[i].x + rooms[i].w / 2.0f) * cell_size;
|
||||
f32 cy = (rooms[i].y + rooms[i].h / 2.0f) * cell_size;
|
||||
|
||||
lights[num_lights++] = (light_source){
|
||||
.position = {cx, light_height, cy},
|
||||
.intensity = 0.8f,
|
||||
.radius = 300.0f,
|
||||
};
|
||||
}
|
||||
|
||||
compute_bsp_vertex_lighting(bsp, lights, num_lights, 0.1f);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
@ -417,88 +896,3 @@ pxl8_result pxl8_procgen(pxl8_bsp* bsp, const pxl8_procgen_params* params) {
|
|||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
}
|
||||
|
||||
static u32 hash2d(i32 x, i32 y) {
|
||||
u32 h = ((u32)x * 374761393u) + ((u32)y * 668265263u);
|
||||
h ^= h >> 13;
|
||||
h ^= h << 17;
|
||||
h ^= h >> 5;
|
||||
return h;
|
||||
}
|
||||
|
||||
void pxl8_procgen_tex(u8* buffer, const pxl8_procgen_tex_params* params) {
|
||||
if (!buffer || !params) return;
|
||||
|
||||
for (i32 y = 0; y < params->height; y++) {
|
||||
for (i32 x = 0; x < params->width; x++) {
|
||||
f32 u = (f32)x / (f32)params->width;
|
||||
f32 v = (f32)y / (f32)params->height;
|
||||
|
||||
u8 color = params->base_color;
|
||||
|
||||
// Tile-based pattern (floor style)
|
||||
if (params->seed == 11111) {
|
||||
i32 tile_x = (i32)floorf(u * 8.0f);
|
||||
i32 tile_y = (i32)floorf(v * 8.0f);
|
||||
u32 h = hash2d(tile_x, tile_y);
|
||||
|
||||
f32 pattern = (f32)(h & 0xFF) / 255.0f;
|
||||
i32 quantized = (pattern < 0.3f) ? 0 : (pattern < 0.7f) ? 1 : 2;
|
||||
|
||||
color = params->base_color + quantized;
|
||||
|
||||
// Checkerboard dither
|
||||
if (((tile_x + tile_y) & 1) == 0 && (h & 0x100)) {
|
||||
color = (color < 255) ? color + 1 : color;
|
||||
}
|
||||
}
|
||||
// Large tile pattern (ceiling style)
|
||||
else if (params->seed == 22222) {
|
||||
i32 coarse_x = (i32)floorf(u * 2.0f);
|
||||
i32 coarse_y = (i32)floorf(v * 2.0f);
|
||||
u32 coarse_h = hash2d(coarse_x, coarse_y);
|
||||
|
||||
i32 subdivision = (coarse_h >> 8) & 0x3;
|
||||
i32 tile_x, tile_y;
|
||||
|
||||
switch (subdivision) {
|
||||
case 0: tile_x = (i32)floorf(u * 3.0f); tile_y = (i32)floorf(v * 3.0f); break;
|
||||
case 1: tile_x = (i32)floorf(u * 5.0f); tile_y = (i32)floorf(v * 5.0f); break;
|
||||
case 2: tile_x = (i32)floorf(u * 2.0f); tile_y = (i32)floorf(v * 4.0f); break;
|
||||
default: tile_x = (i32)floorf(u * 4.0f); tile_y = (i32)floorf(v * 2.0f); break;
|
||||
}
|
||||
|
||||
u32 h = hash2d(tile_x, tile_y);
|
||||
f32 pattern = (f32)(h & 0xFF) / 255.0f;
|
||||
|
||||
if (pattern < 0.25f) color = params->base_color;
|
||||
else if (pattern < 0.50f) color = params->base_color + 1;
|
||||
else if (pattern < 0.75f) color = params->base_color + 2;
|
||||
else color = params->base_color + 3;
|
||||
}
|
||||
// Brick pattern (wall style)
|
||||
else {
|
||||
f32 brick_y = floorf(v * 4.0f);
|
||||
f32 offset = ((i32)brick_y & 1) ? 0.5f : 0.0f;
|
||||
i32 brick_x = (i32)floorf(u * 4.0f + offset);
|
||||
brick_y = (i32)brick_y;
|
||||
|
||||
f32 brick_u = fabsf((u * 4.0f + offset) - floorf(u * 4.0f + offset) - 0.5f);
|
||||
f32 brick_v = fabsf((v * 4.0f) - floorf(v * 4.0f) - 0.5f);
|
||||
|
||||
u32 h = hash2d(brick_x, (i32)brick_y);
|
||||
f32 noise = (f32)(h & 0xFF) / 255.0f;
|
||||
|
||||
// Mortar lines
|
||||
if (brick_u > 0.47f || brick_v > 0.47f) {
|
||||
color = params->base_color - 2;
|
||||
} else {
|
||||
i32 shade = (i32)(noise * 3.0f);
|
||||
color = params->base_color + shade;
|
||||
}
|
||||
}
|
||||
|
||||
buffer[y * params->width + x] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,24 +21,11 @@ typedef struct pxl8_procgen_params {
|
|||
i32 num_rooms;
|
||||
} pxl8_procgen_params;
|
||||
|
||||
typedef struct pxl8_procgen_tex_params {
|
||||
char name[16];
|
||||
u32 seed;
|
||||
i32 width;
|
||||
i32 height;
|
||||
f32 scale;
|
||||
f32 roughness;
|
||||
u8 base_color;
|
||||
u8 variation;
|
||||
u8 max_color;
|
||||
} pxl8_procgen_tex_params;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_result pxl8_procgen(pxl8_bsp* bsp, const pxl8_procgen_params* params);
|
||||
void pxl8_procgen_tex(u8* buffer, const pxl8_procgen_tex_params* params);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,23 +7,21 @@
|
|||
#include "pxl8_gen.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_math.h"
|
||||
#include "pxl8_mem.h"
|
||||
|
||||
struct pxl8_world {
|
||||
pxl8_bsp bsp;
|
||||
bool loaded;
|
||||
bool wireframe;
|
||||
u32 wireframe_color;
|
||||
};
|
||||
|
||||
pxl8_world* pxl8_world_create(void) {
|
||||
pxl8_world* world = (pxl8_world*)calloc(1, sizeof(pxl8_world));
|
||||
pxl8_world* world = (pxl8_world*)pxl8_calloc(1, sizeof(pxl8_world));
|
||||
if (!world) {
|
||||
pxl8_error("Failed to allocate world");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
world->loaded = false;
|
||||
world->wireframe_color = 15;
|
||||
|
||||
return world;
|
||||
}
|
||||
|
|
@ -35,7 +33,7 @@ void pxl8_world_destroy(pxl8_world* world) {
|
|||
pxl8_bsp_destroy(&world->bsp);
|
||||
}
|
||||
|
||||
free(world);
|
||||
pxl8_free(world);
|
||||
}
|
||||
|
||||
pxl8_result pxl8_world_generate(pxl8_world* world, pxl8_gfx* gfx, const pxl8_procgen_params* params) {
|
||||
|
|
@ -98,12 +96,12 @@ pxl8_result pxl8_world_apply_textures(pxl8_world* world, const pxl8_world_textur
|
|||
|
||||
pxl8_bsp* bsp = &world->bsp;
|
||||
|
||||
u32 max_texinfo = count * 6;
|
||||
bsp->texinfo = calloc(max_texinfo, sizeof(pxl8_bsp_texinfo));
|
||||
if (!bsp->texinfo) {
|
||||
u32 max_materials = count * 6;
|
||||
bsp->materials = pxl8_calloc(max_materials, sizeof(pxl8_gfx_material));
|
||||
if (!bsp->materials) {
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
bsp->num_texinfo = 0;
|
||||
bsp->num_materials = 0;
|
||||
|
||||
for (u32 face_idx = 0; face_idx < bsp->num_faces; face_idx++) {
|
||||
pxl8_bsp_face* face = &bsp->faces[face_idx];
|
||||
|
|
@ -136,45 +134,50 @@ pxl8_result pxl8_world_apply_textures(pxl8_world* world, const pxl8_world_textur
|
|||
v_axis = (pxl8_vec3){0.0f, 1.0f, 0.0f};
|
||||
}
|
||||
|
||||
u32 texinfo_idx = bsp->num_texinfo;
|
||||
u32 material_idx = bsp->num_materials;
|
||||
bool found_existing = false;
|
||||
for (u32 i = 0; i < bsp->num_texinfo; i++) {
|
||||
if (strcmp(bsp->texinfo[i].name, matched->name) == 0 &&
|
||||
bsp->texinfo[i].miptex == matched->texture_id &&
|
||||
bsp->texinfo[i].u_axis.x == u_axis.x &&
|
||||
bsp->texinfo[i].u_axis.y == u_axis.y &&
|
||||
bsp->texinfo[i].u_axis.z == u_axis.z &&
|
||||
bsp->texinfo[i].v_axis.x == v_axis.x &&
|
||||
bsp->texinfo[i].v_axis.y == v_axis.y &&
|
||||
bsp->texinfo[i].v_axis.z == v_axis.z) {
|
||||
texinfo_idx = i;
|
||||
for (u32 i = 0; i < bsp->num_materials; i++) {
|
||||
if (strcmp(bsp->materials[i].name, matched->name) == 0 &&
|
||||
bsp->materials[i].texture_id == matched->texture_id &&
|
||||
bsp->materials[i].u_axis.x == u_axis.x &&
|
||||
bsp->materials[i].u_axis.y == u_axis.y &&
|
||||
bsp->materials[i].u_axis.z == u_axis.z &&
|
||||
bsp->materials[i].v_axis.x == v_axis.x &&
|
||||
bsp->materials[i].v_axis.y == v_axis.y &&
|
||||
bsp->materials[i].v_axis.z == v_axis.z) {
|
||||
material_idx = i;
|
||||
found_existing = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_existing) {
|
||||
if (bsp->num_texinfo >= max_texinfo) {
|
||||
pxl8_error("Too many unique texinfo entries");
|
||||
if (bsp->num_materials >= max_materials) {
|
||||
pxl8_error("Too many unique material entries");
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
memcpy(bsp->texinfo[texinfo_idx].name, matched->name, sizeof(bsp->texinfo[texinfo_idx].name));
|
||||
bsp->texinfo[texinfo_idx].name[sizeof(bsp->texinfo[texinfo_idx].name) - 1] = '\0';
|
||||
bsp->texinfo[texinfo_idx].miptex = matched->texture_id;
|
||||
bsp->texinfo[texinfo_idx].u_offset = 0.0f;
|
||||
bsp->texinfo[texinfo_idx].v_offset = 0.0f;
|
||||
bsp->texinfo[texinfo_idx].u_axis = u_axis;
|
||||
bsp->texinfo[texinfo_idx].v_axis = v_axis;
|
||||
pxl8_gfx_material* mat = &bsp->materials[material_idx];
|
||||
memcpy(mat->name, matched->name, sizeof(mat->name));
|
||||
mat->name[sizeof(mat->name) - 1] = '\0';
|
||||
mat->texture_id = matched->texture_id;
|
||||
mat->u_offset = 0.0f;
|
||||
mat->v_offset = 0.0f;
|
||||
mat->u_axis = u_axis;
|
||||
mat->v_axis = v_axis;
|
||||
mat->alpha = 255;
|
||||
mat->dither = true;
|
||||
mat->double_sided = true;
|
||||
mat->dynamic_lighting = true;
|
||||
|
||||
bsp->num_texinfo++;
|
||||
bsp->num_materials++;
|
||||
}
|
||||
|
||||
face->texinfo_id = texinfo_idx;
|
||||
face->material_id = material_idx;
|
||||
}
|
||||
|
||||
pxl8_info("Applied %u textures to %u faces, created %u texinfo entries",
|
||||
count, bsp->num_faces, bsp->num_texinfo);
|
||||
pxl8_info("Applied %u textures to %u faces, created %u materials",
|
||||
count, bsp->num_faces, bsp->num_materials);
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
|
@ -398,9 +401,13 @@ void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (world->wireframe) {
|
||||
pxl8_bsp_render_wireframe(gfx, &world->bsp, camera_pos, world->wireframe_color);
|
||||
} else {
|
||||
pxl8_bsp_render_textured(gfx, &world->bsp, camera_pos);
|
||||
pxl8_bsp_render(gfx, &world->bsp, camera_pos);
|
||||
}
|
||||
|
||||
void pxl8_world_set_wireframe(pxl8_world* world, bool enabled) {
|
||||
if (!world || !world->loaded) return;
|
||||
|
||||
for (u32 i = 0; i < world->bsp.num_materials; i++) {
|
||||
world->bsp.materials[i].wireframe = enabled;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, f32 radi
|
|||
bool pxl8_world_is_loaded(const pxl8_world* world);
|
||||
void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);
|
||||
pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius);
|
||||
void pxl8_world_set_wireframe(pxl8_world* world, bool enabled);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue