better lighting

This commit is contained in:
asrael 2026-01-31 09:31:17 -06:00
parent 805a2713a3
commit 6ed4e17065
75 changed files with 6417 additions and 3667 deletions

View file

@ -1,980 +0,0 @@
#include "pxl8_bsp.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pxl8_color.h"
#include "pxl8_gfx.h"
#include "pxl8_io.h"
#include "pxl8_log.h"
#include "pxl8_mem.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;
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) {
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, 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;
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;
}
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;
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);
return result;
}
if (file_size < sizeof(pxl8_bsp_header)) {
pxl8_error("BSP file too small: %s", path);
pxl8_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);
pxl8_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 = 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++) {
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 = 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);
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 = 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);
}
}
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 = 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);
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_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_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;
}
}
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 = 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].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);
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 = 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);
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);
}
}
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 = 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);
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);
}
}
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 = 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);
}
}
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 = pxl8_calloc(bsp->num_models, sizeof(pxl8_bsp_model));
pxl8_stream_seek(&stream, chunk->offset);
for (u32 i = 0; i < bsp->num_models; i++) {
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);
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 = pxl8_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 = pxl8_malloc(bsp->lightdata_size);
memcpy(bsp->lightdata, file_data + chunk->offset, bsp->lightdata_size);
}
pxl8_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);
pxl8_free(file_data);
pxl8_bsp_destroy(bsp);
return PXL8_ERROR_INVALID_FORMAT;
}
void pxl8_bsp_destroy(pxl8_bsp* bsp) {
if (!bsp) return;
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));
}
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_point_solid(const pxl8_bsp* bsp, pxl8_vec3 pos) {
i32 leaf = pxl8_bsp_find_leaf(bsp, pos);
if (leaf < 0 || (u32)leaf >= bsp->num_leafs) return true;
return bsp->leafs[leaf].contents == -1;
}
static bool point_clear(const pxl8_bsp* bsp, f32 x, f32 y, f32 z, f32 radius) {
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z})) return false;
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x + radius, y, z})) return false;
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x - radius, y, z})) return false;
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z + radius})) return false;
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z - radius})) return false;
return true;
}
pxl8_vec3 pxl8_bsp_trace(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to, f32 radius) {
if (!bsp || bsp->num_nodes == 0) return to;
if (point_clear(bsp, to.x, to.y, to.z, radius)) {
return to;
}
bool x_ok = point_clear(bsp, to.x, from.y, from.z, radius);
bool z_ok = point_clear(bsp, from.x, from.y, to.z, radius);
if (x_ok && z_ok) {
f32 dx = to.x - from.x;
f32 dz = to.z - from.z;
if (dx * dx > dz * dz) {
return (pxl8_vec3){to.x, from.y, from.z};
} else {
return (pxl8_vec3){from.x, from.y, to.z};
}
} else if (x_ok) {
return (pxl8_vec3){to.x, from.y, from.z};
} else if (z_ok) {
return (pxl8_vec3){from.x, from.y, to.z};
}
return from;
}
bool pxl8_bsp_is_leaf_visible(const pxl8_bsp* bsp, i32 leaf_from, i32 leaf_to) {
if (!bsp || !bsp->visdata || bsp->visdata_size == 0) return true;
if (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;
u32 row_size = (bsp->num_leafs + 7) >> 3;
u32 byte_idx = (u32)leaf_to >> 3;
u32 bit_idx = (u32)leaf_to & 7;
u8* vis = bsp->visdata + visofs;
u8* vis_end = bsp->visdata + bsp->visdata_size;
u32 out = 0;
while (out < row_size && vis < vis_end) {
if (*vis) {
if (out == byte_idx) {
return (*vis & (1 << bit_idx)) != 0;
}
out++;
vis++;
} else {
vis++;
if (vis >= vis_end) break;
u32 count = *vis++;
if (out + count > byte_idx && byte_idx >= out) {
return false;
}
out += count;
}
}
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 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, row);
return pvs;
}
i32 visofs = bsp->leafs[leaf].visofs;
if (visofs < 0 || !bsp->visdata || bsp->visdata_size == 0) {
memset(pvs.data, 0xFF, row);
return pvs;
}
u8* in = bsp->visdata + visofs;
u8* out = pvs.data;
u8* out_end = pvs.data + row;
do {
if (*in) {
*out++ = *in++;
} else {
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) {
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 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;
}
pxl8_bsp_lightmap pxl8_bsp_lightmap_uniform(u8 r, u8 g, u8 b) {
return (pxl8_bsp_lightmap){
.color = {r, g, b},
.height = 0,
.offset = 0,
.width = 0,
};
}
pxl8_bsp_lightmap pxl8_bsp_lightmap_mapped(u8 width, u8 height, u32 offset) {
return (pxl8_bsp_lightmap){
.color = {0, 0, 0},
.height = height,
.offset = offset,
.width = width,
};
}
pxl8_bsp_lightmap_sample pxl8_bsp_sample_lightmap(const pxl8_bsp* bsp, u32 face_idx, f32 u, f32 v) {
pxl8_bsp_lightmap_sample white = {255, 255, 255};
if (!bsp || !bsp->lightmaps || face_idx >= bsp->num_lightmaps) {
return white;
}
const pxl8_bsp_lightmap* lm = &bsp->lightmaps[face_idx];
if (lm->width == 0) {
return (pxl8_bsp_lightmap_sample){lm->color[2], lm->color[1], lm->color[0]};
}
if (!bsp->lightdata || bsp->lightdata_size == 0) {
return white;
}
f32 w = (f32)lm->width;
f32 h = (f32)lm->height;
f32 fx = u * w;
f32 fy = v * h;
if (fx < 0) fx = 0;
if (fx > w - 1.001f) fx = w - 1.001f;
if (fy < 0) fy = 0;
if (fy > h - 1.001f) fy = h - 1.001f;
u32 x0 = (u32)fx;
u32 y0 = (u32)fy;
u32 x1 = x0 + 1;
u32 y1 = y0 + 1;
if (x1 >= lm->width) x1 = lm->width - 1;
if (y1 >= lm->height) y1 = lm->height - 1;
f32 frac_x = fx - (f32)x0;
f32 frac_y = fy - (f32)y0;
u32 stride = lm->width;
u32 base = lm->offset;
u32 idx00 = base + y0 * stride + x0;
u32 idx10 = base + y0 * stride + x1;
u32 idx01 = base + y1 * stride + x0;
u32 idx11 = base + y1 * stride + x1;
u8 r00, g00, b00, r10, g10, b10, r01, g01, b01, r11, g11, b11;
if (idx00 < bsp->lightdata_size) pxl8_rgb332_unpack(bsp->lightdata[idx00], &r00, &g00, &b00);
else { r00 = g00 = b00 = 255; }
if (idx10 < bsp->lightdata_size) pxl8_rgb332_unpack(bsp->lightdata[idx10], &r10, &g10, &b10);
else { r10 = g10 = b10 = 255; }
if (idx01 < bsp->lightdata_size) pxl8_rgb332_unpack(bsp->lightdata[idx01], &r01, &g01, &b01);
else { r01 = g01 = b01 = 255; }
if (idx11 < bsp->lightdata_size) pxl8_rgb332_unpack(bsp->lightdata[idx11], &r11, &g11, &b11);
else { r11 = g11 = b11 = 255; }
f32 inv_x = 1.0f - frac_x;
f32 inv_y = 1.0f - frac_y;
u8 r = (u8)(r00 * inv_x * inv_y + r10 * frac_x * inv_y + r01 * inv_x * frac_y + r11 * frac_x * frac_y);
u8 g = (u8)(g00 * inv_x * inv_y + g10 * frac_x * inv_y + g01 * inv_x * frac_y + g11 * frac_x * frac_y);
u8 b = (u8)(b00 * inv_x * inv_y + b10 * frac_x * inv_y + b01 * inv_x * frac_y + b11 * frac_x * frac_y);
return (pxl8_bsp_lightmap_sample){b, g, r};
}
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);
}
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,
pxl8_mesh* mesh
) {
const pxl8_bsp_face* face = &bsp->faces[face_id];
if (face->num_edges < 3) return;
pxl8_vec3 normal = {0, 1, 0};
if (face->plane_id < bsp->num_planes) {
normal = bsp->planes[face->plane_id].normal;
if (face->side) {
normal.x = -normal.x;
normal.y = -normal.y;
normal.z = -normal.z;
}
}
const pxl8_gfx_material* material = NULL;
f32 tex_scale = 64.0f;
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++) {
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)) {
continue;
}
pxl8_vec3 pos = bsp->vertices[vert_idx].position;
f32 u = 0.0f, v = 0.0f;
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;
if (bsp->vertex_lights && vert_idx < bsp->num_vertex_lights) {
light = (bsp->vertex_lights[vert_idx] >> 24) & 0xFF;
}
pxl8_vertex vtx = {
.position = pos,
.normal = normal,
.u = u,
.v = v,
.color = 15,
.light = light,
};
pxl8_mesh_push_vertex(mesh, vtx);
num_verts++;
}
if (num_verts < 3) return;
for (u32 i = 1; i < num_verts - 1; i++) {
pxl8_mesh_push_triangle(mesh, base_idx, base_idx + i, base_idx + i + 1);
}
}
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;
collect_face_to_mesh(bsp, face_id, mesh);
if (mesh->index_count > 0) {
pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_3d_draw_mesh(gfx, mesh, &identity, material);
}
pxl8_mesh_destroy(mesh);
}
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) {
pxl8_debug("[BSP] render: no cell_portals (ptr=%p count=%u)", (void*)bsp->cell_portals, bsp->num_cell_portals);
return;
}
if (!bsp->materials || bsp->num_materials == 0) {
pxl8_debug("[BSP] render: no materials (ptr=%p count=%u)", (void*)bsp->materials, bsp->num_materials);
return;
}
const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx);
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_leafs) 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;
}
u32 head = 0, tail = 0;
screen_rect full_screen = {-1.0f, -1.0f, 1.0f, 1.0f};
visited[camera_leaf >> 3] |= (1 << (camera_leaf & 7));
cell_windows[camera_leaf] = full_screen;
queue[tail++] = (portal_queue_entry){camera_leaf, full_screen};
f32 wall_height = 128.0f;
while (head < tail) {
portal_queue_entry entry = queue[head++];
u32 leaf_id = entry.leaf;
screen_rect window = entry.window;
if (leaf_id >= bsp->num_cell_portals) continue;
const pxl8_bsp_cell_portals* cp = &bsp->cell_portals[leaf_id];
for (u8 i = 0; i < cp->num_portals; i++) {
const pxl8_bsp_portal* portal = &cp->portals[i];
u32 target = portal->target_leaf;
if (target >= bsp->num_leafs) continue;
if (bsp->leafs[target].contents == -1) continue;
if (!pxl8_bsp_pvs_is_visible(&pvs, target)) continue;
screen_rect portal_rect = project_portal_to_screen(portal, vp, wall_height);
if (!screen_rect_valid(portal_rect)) continue;
screen_rect new_window = screen_rect_intersect(window, portal_rect);
if (!screen_rect_valid(new_window)) continue;
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;
}
visited[byte] |= (1 << bit);
cell_windows[target] = new_window;
queue[tail++] = (portal_queue_entry){target, new_window};
}
}
pxl8_mesh* mesh = pxl8_mesh_create(8192, 16384);
if (!mesh) {
pxl8_free(visited);
pxl8_free(cell_windows);
pxl8_free(queue);
return;
}
u32 current_material = 0xFFFFFFFF;
for (u32 leaf_id = 0; leaf_id < bsp->num_leafs; leaf_id++) {
if (!(visited[leaf_id >> 3] & (1 << (leaf_id & 7)))) continue;
if (bsp->leafs[leaf_id].contents == -1) 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 (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;
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]);
}
pxl8_bsp_pvs_destroy(&pvs);
pxl8_free(visited);
pxl8_free(cell_windows);
pxl8_free(queue);
pxl8_mesh_destroy(mesh);
}
u32 pxl8_bsp_face_count(const pxl8_bsp* bsp) {
if (!bsp) return 0;
return bsp->num_faces;
}
pxl8_vec3 pxl8_bsp_face_normal(const pxl8_bsp* bsp, u32 face_id) {
pxl8_vec3 up = {0, 1, 0};
if (!bsp || face_id >= bsp->num_faces) return up;
const pxl8_bsp_face* face = &bsp->faces[face_id];
if (face->plane_id >= bsp->num_planes) return up;
pxl8_vec3 normal = bsp->planes[face->plane_id].normal;
if (face->side) {
normal.x = -normal.x;
normal.y = -normal.y;
normal.z = -normal.z;
}
return normal;
}
void pxl8_bsp_face_set_material(pxl8_bsp* bsp, u32 face_id, u16 material_id) {
if (!bsp || face_id >= bsp->num_faces) return;
bsp->faces[face_id].material_id = material_id;
}
void pxl8_bsp_set_material(pxl8_bsp* bsp, u16 material_id, const pxl8_gfx_material* material) {
if (!bsp || !material) return;
if (material_id >= bsp->num_materials) {
u32 new_count = material_id + 1;
pxl8_gfx_material* new_materials = pxl8_realloc(bsp->materials, new_count * sizeof(pxl8_gfx_material));
if (!new_materials) return;
for (u32 i = bsp->num_materials; i < new_count; i++) {
memset(&new_materials[i], 0, sizeof(pxl8_gfx_material));
}
bsp->materials = new_materials;
bsp->num_materials = new_count;
}
bsp->materials[material_id] = *material;
}
void pxl8_bsp_set_wireframe(pxl8_bsp* bsp, bool wireframe) {
if (!bsp || !bsp->materials) return;
for (u32 i = 0; i < bsp->num_materials; i++) {
bsp->materials[i].wireframe = wireframe;
}
}

View file

@ -1,161 +0,0 @@
#pragma once
#include "pxl8_gfx.h"
#include "pxl8_math.h"
#include "pxl8_mesh.h"
#include "pxl8_types.h"
typedef struct pxl8_bsp_edge {
u16 vertex[2];
} pxl8_bsp_edge;
typedef struct pxl8_bsp_face {
u32 first_edge;
u32 lightmap_offset;
u16 num_edges;
u16 plane_id;
u16 side;
u8 styles[4];
u16 material_id;
pxl8_vec3 aabb_min;
pxl8_vec3 aabb_max;
} pxl8_bsp_face;
typedef struct pxl8_bsp_leaf {
u8 ambient_level[4];
i32 contents;
u16 first_marksurface;
i16 maxs[3];
i16 mins[3];
u16 num_marksurfaces;
i32 visofs;
} pxl8_bsp_leaf;
typedef struct pxl8_bsp_model {
i32 first_face;
i32 headnode[4];
f32 maxs[3];
f32 mins[3];
i32 num_faces;
pxl8_vec3 origin;
i32 visleafs;
} pxl8_bsp_model;
typedef struct pxl8_bsp_node {
i32 children[2];
u16 first_face;
i16 maxs[3];
i16 mins[3];
u16 num_faces;
u32 plane_id;
} pxl8_bsp_node;
typedef struct pxl8_bsp_plane {
f32 dist;
pxl8_vec3 normal;
i32 type;
} pxl8_bsp_plane;
typedef struct pxl8_bsp_vertex {
pxl8_vec3 position;
} pxl8_bsp_vertex;
typedef struct pxl8_bsp_lightmap {
u8 color[3];
u8 height;
u32 offset;
u8 width;
} pxl8_bsp_lightmap;
typedef struct pxl8_bsp_lightmap_sample {
u8 b;
u8 g;
u8 r;
} pxl8_bsp_lightmap_sample;
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_gfx_material* materials;
pxl8_bsp_model* models;
pxl8_bsp_node* nodes;
pxl8_bsp_plane* planes;
u8* render_face_flags;
i32* surfedges;
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_materials;
u32 num_models;
u32 num_nodes;
u32 num_planes;
u32 num_surfedges;
u32 num_vertex_lights;
u32 num_vertices;
u32 visdata_size;
} pxl8_bsp;
#ifdef __cplusplus
extern "C" {
#endif
pxl8_bsp_pvs pxl8_bsp_decompress_pvs(const pxl8_bsp* bsp, i32 leaf);
void pxl8_bsp_destroy(pxl8_bsp* bsp);
u32 pxl8_bsp_face_count(const pxl8_bsp* bsp);
pxl8_vec3 pxl8_bsp_face_normal(const pxl8_bsp* bsp, u32 face_id);
void pxl8_bsp_face_set_material(pxl8_bsp* bsp, u32 face_id, u16 material_id);
i32 pxl8_bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos);
bool pxl8_bsp_is_leaf_visible(const pxl8_bsp* bsp, i32 leaf_from, i32 leaf_to);
bool pxl8_bsp_point_solid(const pxl8_bsp* bsp, pxl8_vec3 pos);
pxl8_vec3 pxl8_bsp_trace(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to, f32 radius);
pxl8_bsp_lightmap pxl8_bsp_lightmap_mapped(u8 width, u8 height, u32 offset);
pxl8_bsp_lightmap pxl8_bsp_lightmap_uniform(u8 r, u8 g, u8 b);
pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp);
void pxl8_bsp_pvs_destroy(pxl8_bsp_pvs* pvs);
bool pxl8_bsp_pvs_is_visible(const pxl8_bsp_pvs* pvs, i32 leaf);
void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos);
void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const pxl8_gfx_material* material);
pxl8_bsp_lightmap_sample pxl8_bsp_sample_lightmap(const pxl8_bsp* bsp, u32 face_idx, f32 u, f32 v);
void pxl8_bsp_set_material(pxl8_bsp* bsp, u16 material_id, const pxl8_gfx_material* material);
void pxl8_bsp_set_wireframe(pxl8_bsp* bsp, bool wireframe);
#ifdef __cplusplus
}
#endif

View file

@ -1,44 +0,0 @@
#include "pxl8_chunk.h"
#include "pxl8_mem.h"
pxl8_chunk* pxl8_chunk_create_vxl(i32 cx, i32 cy, i32 cz) {
pxl8_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_chunk));
if (!chunk) return NULL;
chunk->type = PXL8_CHUNK_VXL;
chunk->cx = cx;
chunk->cy = cy;
chunk->cz = cz;
chunk->voxel = pxl8_voxel_chunk_create();
if (!chunk->voxel) {
pxl8_free(chunk);
return NULL;
}
return chunk;
}
pxl8_chunk* pxl8_chunk_create_bsp(u32 id) {
pxl8_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_chunk));
if (!chunk) return NULL;
chunk->type = PXL8_CHUNK_BSP;
chunk->id = id;
chunk->bsp = NULL;
return chunk;
}
void pxl8_chunk_destroy(pxl8_chunk* chunk) {
if (!chunk) return;
if (chunk->type == PXL8_CHUNK_VXL && chunk->voxel) {
pxl8_voxel_chunk_destroy(chunk->voxel);
} else if (chunk->type == PXL8_CHUNK_BSP && chunk->bsp) {
pxl8_bsp_destroy(chunk->bsp);
}
pxl8_free(chunk);
}

View file

@ -1,33 +0,0 @@
#pragma once
#include "pxl8_bsp.h"
#include "pxl8_types.h"
#include "pxl8_voxel.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum pxl8_chunk_type {
PXL8_CHUNK_VXL,
PXL8_CHUNK_BSP
} pxl8_chunk_type;
typedef struct pxl8_chunk {
pxl8_chunk_type type;
u32 id;
u32 version;
i32 cx, cy, cz;
union {
pxl8_voxel_chunk* voxel;
pxl8_bsp* bsp;
};
} pxl8_chunk;
pxl8_chunk* pxl8_chunk_create_vxl(i32 cx, i32 cy, i32 cz);
pxl8_chunk* pxl8_chunk_create_bsp(u32 id);
void pxl8_chunk_destroy(pxl8_chunk* chunk);
#ifdef __cplusplus
}
#endif

View file

@ -1,67 +0,0 @@
#pragma once
#include "pxl8_chunk.h"
#include "pxl8_mesh.h"
#include "pxl8_protocol.h"
#include "pxl8_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#define PXL8_CHUNK_CACHE_SIZE 64
#define PXL8_CHUNK_MAX_FRAGMENTS 64
#define PXL8_CHUNK_MAX_DATA_SIZE 131072
typedef struct pxl8_chunk_cache_entry {
pxl8_chunk* chunk;
pxl8_mesh* mesh;
u64 last_used;
bool mesh_dirty;
bool valid;
} pxl8_chunk_cache_entry;
typedef struct pxl8_chunk_assembly {
pxl8_chunk_type type;
u32 id;
i32 cx, cy, cz;
u32 version;
u8 fragment_count;
u8 fragments_received;
u8* data;
u32 data_size;
u32 data_capacity;
bool active;
bool complete;
} pxl8_chunk_assembly;
typedef struct pxl8_chunk_cache {
pxl8_chunk_cache_entry entries[PXL8_CHUNK_CACHE_SIZE];
pxl8_chunk_assembly assembly;
u32 entry_count;
u64 frame_counter;
} pxl8_chunk_cache;
pxl8_chunk_cache* pxl8_chunk_cache_create(void);
void pxl8_chunk_cache_destroy(pxl8_chunk_cache* cache);
pxl8_result pxl8_chunk_cache_receive(pxl8_chunk_cache* cache,
const pxl8_chunk_msg_header* hdr,
const u8* payload, usize len);
pxl8_chunk* pxl8_chunk_cache_get_vxl(pxl8_chunk_cache* cache, i32 cx, i32 cy, i32 cz);
pxl8_chunk* pxl8_chunk_cache_get_bsp(pxl8_chunk_cache* cache, u32 id);
pxl8_mesh* pxl8_chunk_cache_get_mesh(pxl8_chunk_cache* cache,
i32 cx, i32 cy, i32 cz,
const pxl8_block_registry* registry,
const pxl8_voxel_mesh_config* config);
void pxl8_chunk_cache_tick(pxl8_chunk_cache* cache);
void pxl8_chunk_cache_evict_distant(pxl8_chunk_cache* cache,
i32 cx, i32 cy, i32 cz, i32 radius);
void pxl8_chunk_cache_invalidate_meshes(pxl8_chunk_cache* cache);
#ifdef __cplusplus
}
#endif

View file

@ -1,898 +0,0 @@
#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 = pxl8_calloc(width * height, sizeof(u8));
return grid->cells != NULL;
}
static u8 room_grid_get(const room_grid* grid, i32 x, i32 y) {
if (x < 0 || x >= grid->width || y < 0 || y >= grid->height) {
return 1;
}
return grid->cells[y * grid->width + x];
}
static void room_grid_set(room_grid* grid, i32 x, i32 y, u8 value) {
if (x < 0 || x >= grid->width || y < 0 || y >= grid->height) {
return;
}
grid->cells[y * grid->width + x] = value;
}
static inline void compute_face_aabb(pxl8_bsp_face* face, const pxl8_bsp_vertex* verts, u32 vert_idx) {
face->aabb_min = (pxl8_vec3){1e30f, 1e30f, 1e30f};
face->aabb_max = (pxl8_vec3){-1e30f, -1e30f, -1e30f};
for (u32 i = 0; i < 4; i++) {
pxl8_vec3 v = verts[vert_idx + i].position;
if (v.x < face->aabb_min.x) face->aabb_min.x = v.x;
if (v.x > face->aabb_max.x) face->aabb_max.x = v.x;
if (v.y < face->aabb_min.y) face->aabb_min.y = v.y;
if (v.y > face->aabb_max.y) face->aabb_max.y = v.y;
if (v.z < face->aabb_min.z) face->aabb_min.z = v.z;
if (v.z > face->aabb_max.z) face->aabb_max.z = v.z;
}
}
static void room_grid_fill(room_grid* grid, u8 value) {
for (i32 y = 0; y < grid->height; y++) {
for (i32 x = 0; x < grid->width; x++) {
room_grid_set(grid, x, y, 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;
i32 floor_ceiling_count = 0;
for (i32 y = 0; y < grid->height; y++) {
for (i32 x = 0; x < grid->width; x++) {
if (room_grid_get(grid, x, y) == 0) {
if (room_grid_get(grid, x - 1, y) == 1) face_count++;
if (room_grid_get(grid, x + 1, y) == 1) face_count++;
if (room_grid_get(grid, x, y - 1) == 1) face_count++;
if (room_grid_get(grid, x, y + 1) == 1) face_count++;
floor_ceiling_count++;
}
}
}
face_count += floor_ceiling_count;
vertex_count = face_count * 4;
pxl8_debug("Level generation: %dx%d grid -> %d faces, %d vertices",
grid->width, grid->height, face_count, vertex_count);
i32 total_cells = grid->width * grid->height;
u32 max_nodes = 2 * total_cells;
u32 total_planes = face_count + max_nodes;
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->materials = NULL;
bsp->num_materials = 0;
i32 vert_idx = 0;
i32 face_idx = 0;
i32 edge_idx = 0;
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};
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx, wall_height, fy};
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->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].material_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);
face_cell[face_idx] = cell_idx;
vert_idx += 4;
edge_idx += 4;
face_idx++;
}
if (room_grid_get(grid, x + 1, y) == 1) {
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx + cell_size, 0, fy};
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx + cell_size, 0, fy + cell_size};
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->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].material_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);
face_cell[face_idx] = cell_idx;
vert_idx += 4;
edge_idx += 4;
face_idx++;
}
if (room_grid_get(grid, x, y - 1) == 1) {
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy};
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx + cell_size, 0, fy};
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->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].material_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);
face_cell[face_idx] = cell_idx;
vert_idx += 4;
edge_idx += 4;
face_idx++;
}
if (room_grid_get(grid, x, y + 1) == 1) {
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy + cell_size};
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx, wall_height, fy + cell_size};
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->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].material_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);
face_cell[face_idx] = cell_idx;
vert_idx += 4;
edge_idx += 4;
face_idx++;
}
}
}
}
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;
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy};
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx, 0, fy + cell_size};
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, 0, fy + cell_size};
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx + cell_size, 0, fy};
bsp->planes[face_idx].normal = (pxl8_vec3){0, 1, 0};
bsp->planes[face_idx].dist = 0;
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].material_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);
face_cell[face_idx] = cell_idx;
vert_idx += 4;
edge_idx += 4;
face_idx++;
}
}
}
bsp->num_vertices = vertex_count;
bsp->num_faces = face_count;
bsp->num_edges = vertex_count;
bsp->num_surfedges = vertex_count;
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 = total_cells;
bsp->num_marksurfaces = face_count;
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++) {
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;
}
static bool bounds_intersects(const pxl8_bounds* a, const pxl8_bounds* b) {
return !(a->x + a->w <= b->x || b->x + b->w <= a->x ||
a->y + a->h <= b->y || b->y + b->h <= a->y);
}
static void carve_corridor_h(room_grid* grid, i32 x1, i32 x2, i32 y) {
i32 start = (x1 < x2) ? x1 : x2;
i32 end = (x1 > x2) ? x1 : x2;
for (i32 x = start; x <= end; x++) {
room_grid_set(grid, x, y, 0);
room_grid_set(grid, x, y - 1, 0);
room_grid_set(grid, x, y + 1, 0);
}
}
static void carve_corridor_v(room_grid* grid, i32 y1, i32 y2, i32 x) {
i32 start = (y1 < y2) ? y1 : y2;
i32 end = (y1 > y2) ? y1 : y2;
for (i32 y = start; y <= end; y++) {
room_grid_set(grid, x, y, 0);
room_grid_set(grid, x - 1, y, 0);
room_grid_set(grid, x + 1, y, 0);
}
}
static pxl8_result procgen_rooms(pxl8_bsp* bsp, const pxl8_procgen_params* params) {
pxl8_debug("procgen_rooms called: %dx%d, seed=%u, min=%d, max=%d, num=%d",
params->width, params->height, params->seed,
params->min_room_size, params->max_room_size, params->num_rooms);
pxl8_rng rng;
pxl8_rng_seed(&rng, params->seed);
room_grid grid;
if (!room_grid_init(&grid, params->width, params->height)) {
pxl8_error("Failed to allocate room grid");
return PXL8_ERROR_OUT_OF_MEMORY;
}
room_grid_fill(&grid, 1);
pxl8_bounds rooms[256];
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));
i32 x = 1 + (pxl8_rng_next(&rng) % (params->width - w - 2));
i32 y = 1 + (pxl8_rng_next(&rng) % (params->height - h - 2));
pxl8_bounds new_room = {x, y, w, h};
bool overlaps = false;
for (i32 i = 0; i < room_count; i++) {
if (bounds_intersects(&new_room, &rooms[i])) {
overlaps = true;
break;
}
}
if (!overlaps) {
for (i32 ry = y; ry < y + h; ry++) {
for (i32 rx = x; rx < x + w; rx++) {
room_grid_set(&grid, rx, ry, 0);
}
}
if (room_count > 0) {
i32 new_cx = x + w / 2;
i32 new_cy = y + h / 2;
i32 prev_cx = rooms[room_count - 1].x + rooms[room_count - 1].w / 2;
i32 prev_cy = rooms[room_count - 1].y + rooms[room_count - 1].h / 2;
if (pxl8_rng_next(&rng) % 2 == 0) {
carve_corridor_h(&grid, prev_cx, new_cx, prev_cy);
carve_corridor_v(&grid, prev_cy, new_cy, new_cx);
} else {
carve_corridor_v(&grid, prev_cy, new_cy, prev_cx);
carve_corridor_h(&grid, prev_cx, new_cx, new_cy);
}
}
rooms[room_count++] = new_room;
}
}
pxl8_debug("Room generation: %dx%d grid -> %d rooms created",
params->width, params->height, room_count);
pxl8_result result = grid_to_bsp(bsp, &grid);
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;
}
pxl8_result pxl8_procgen(pxl8_bsp* bsp, const pxl8_procgen_params* params) {
if (!bsp || !params) {
return PXL8_ERROR_NULL_POINTER;
}
switch (params->type) {
case PXL8_PROCGEN_ROOMS:
return procgen_rooms(bsp, params);
case PXL8_PROCGEN_TERRAIN:
pxl8_error("Terrain generation not yet implemented");
return PXL8_ERROR_NOT_INITIALIZED;
default:
pxl8_error("Unknown procgen type: %d", params->type);
return PXL8_ERROR_INVALID_ARGUMENT;
}
}

View file

@ -1,32 +0,0 @@
#pragma once
#include "pxl8_bsp.h"
#include "pxl8_types.h"
typedef enum pxl8_procgen_type {
PXL8_PROCGEN_ROOMS,
PXL8_PROCGEN_TERRAIN
} pxl8_procgen_type;
typedef struct pxl8_procgen_params {
pxl8_procgen_type type;
i32 width;
i32 height;
i32 depth;
u32 seed;
i32 min_room_size;
i32 max_room_size;
i32 num_rooms;
} pxl8_procgen_params;
#ifdef __cplusplus
extern "C" {
#endif
pxl8_result pxl8_procgen(pxl8_bsp* bsp, const pxl8_procgen_params* params);
#ifdef __cplusplus
}
#endif

View file

@ -1,406 +0,0 @@
#include "pxl8_voxel.h"
#include <string.h>
#include "pxl8_mem.h"
#define PXL8_VOXEL_CHUNK_VOLUME (PXL8_VOXEL_CHUNK_SIZE * PXL8_VOXEL_CHUNK_SIZE * PXL8_VOXEL_CHUNK_SIZE)
typedef struct pxl8_block_def {
char name[32];
u8 texture_id;
pxl8_voxel_geometry geometry;
bool registered;
} pxl8_block_def;
struct pxl8_block_registry {
pxl8_block_def blocks[PXL8_BLOCK_COUNT];
};
struct pxl8_voxel_chunk {
pxl8_block blocks[PXL8_VOXEL_CHUNK_VOLUME];
};
static inline u32 voxel_index(i32 x, i32 y, i32 z) {
return (u32)(x + y * PXL8_VOXEL_CHUNK_SIZE + z * PXL8_VOXEL_CHUNK_SIZE * PXL8_VOXEL_CHUNK_SIZE);
}
static inline bool voxel_in_bounds(i32 x, i32 y, i32 z) {
return x >= 0 && x < PXL8_VOXEL_CHUNK_SIZE &&
y >= 0 && y < PXL8_VOXEL_CHUNK_SIZE &&
z >= 0 && z < PXL8_VOXEL_CHUNK_SIZE;
}
pxl8_voxel_chunk* pxl8_voxel_chunk_create(void) {
pxl8_voxel_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_voxel_chunk));
return chunk;
}
void pxl8_voxel_chunk_clear(pxl8_voxel_chunk* chunk) {
if (!chunk) return;
memset(chunk->blocks, 0, sizeof(chunk->blocks));
}
void pxl8_voxel_chunk_destroy(pxl8_voxel_chunk* chunk) {
pxl8_free(chunk);
}
pxl8_block pxl8_voxel_get(const pxl8_voxel_chunk* chunk, i32 x, i32 y, i32 z) {
if (!chunk || !voxel_in_bounds(x, y, z)) return PXL8_BLOCK_AIR;
return chunk->blocks[voxel_index(x, y, z)];
}
void pxl8_voxel_set(pxl8_voxel_chunk* chunk, i32 x, i32 y, i32 z, pxl8_block block) {
if (!chunk || !voxel_in_bounds(x, y, z)) return;
chunk->blocks[voxel_index(x, y, z)] = block;
}
void pxl8_voxel_fill(pxl8_voxel_chunk* chunk, pxl8_block block) {
if (!chunk) return;
memset(chunk->blocks, block, sizeof(chunk->blocks));
}
pxl8_block_registry* pxl8_block_registry_create(void) {
pxl8_block_registry* registry = pxl8_calloc(1, sizeof(pxl8_block_registry));
return registry;
}
void pxl8_block_registry_destroy(pxl8_block_registry* registry) {
pxl8_free(registry);
}
void pxl8_block_registry_register(pxl8_block_registry* registry, pxl8_block id, const char* name, u8 texture_id, pxl8_voxel_geometry geo) {
if (!registry || id == PXL8_BLOCK_AIR) return;
pxl8_block_def* def = &registry->blocks[id];
strncpy(def->name, name ? name : "", sizeof(def->name) - 1);
def->texture_id = texture_id;
def->geometry = geo;
def->registered = true;
}
const char* pxl8_block_registry_name(const pxl8_block_registry* registry, pxl8_block id) {
if (!registry || !registry->blocks[id].registered) return NULL;
return registry->blocks[id].name;
}
pxl8_voxel_geometry pxl8_block_registry_geometry(const pxl8_block_registry* registry, pxl8_block id) {
if (!registry || !registry->blocks[id].registered) return PXL8_VOXEL_GEOMETRY_CUBE;
return registry->blocks[id].geometry;
}
u8 pxl8_block_registry_texture(const pxl8_block_registry* registry, pxl8_block id) {
if (!registry || !registry->blocks[id].registered) return 0;
return registry->blocks[id].texture_id;
}
static bool block_is_opaque(pxl8_block block) {
return block != PXL8_BLOCK_AIR;
}
static pxl8_block get_block_or_neighbor(const pxl8_voxel_chunk* chunk, const pxl8_voxel_chunk** neighbors, i32 x, i32 y, i32 z) {
if (voxel_in_bounds(x, y, z)) {
return chunk->blocks[voxel_index(x, y, z)];
}
i32 nx = x, ny = y, nz = z;
i32 neighbor_idx = -1;
if (x < 0) { neighbor_idx = 0; nx = x + PXL8_VOXEL_CHUNK_SIZE; }
else if (x >= PXL8_VOXEL_CHUNK_SIZE) { neighbor_idx = 1; nx = x - PXL8_VOXEL_CHUNK_SIZE; }
else if (y < 0) { neighbor_idx = 2; ny = y + PXL8_VOXEL_CHUNK_SIZE; }
else if (y >= PXL8_VOXEL_CHUNK_SIZE) { neighbor_idx = 3; ny = y - PXL8_VOXEL_CHUNK_SIZE; }
else if (z < 0) { neighbor_idx = 4; nz = z + PXL8_VOXEL_CHUNK_SIZE; }
else if (z >= PXL8_VOXEL_CHUNK_SIZE) { neighbor_idx = 5; nz = z - PXL8_VOXEL_CHUNK_SIZE; }
if (neighbor_idx >= 0 && neighbors && neighbors[neighbor_idx]) {
return pxl8_voxel_get(neighbors[neighbor_idx], nx, ny, nz);
}
return PXL8_BLOCK_AIR;
}
static f32 compute_ao(const pxl8_voxel_chunk* chunk, const pxl8_voxel_chunk** neighbors,
i32 x, i32 y, i32 z, i32 dx1, i32 dy1, i32 dz1, i32 dx2, i32 dy2, i32 dz2) {
bool side1 = block_is_opaque(get_block_or_neighbor(chunk, neighbors, x + dx1, y + dy1, z + dz1));
bool side2 = block_is_opaque(get_block_or_neighbor(chunk, neighbors, x + dx2, y + dy2, z + dz2));
bool corner = block_is_opaque(get_block_or_neighbor(chunk, neighbors, x + dx1 + dx2, y + dy1 + dy2, z + dz1 + dz2));
if (side1 && side2) return 0.0f;
return (3.0f - (f32)side1 - (f32)side2 - (f32)corner) / 3.0f;
}
static void add_face_vertices(pxl8_mesh* mesh, const pxl8_voxel_mesh_config* config,
pxl8_vec3 pos, i32 face, u8 texture_id,
f32 ao0, f32 ao1, f32 ao2, f32 ao3) {
static const pxl8_vec3 face_normals[6] = {
{-1, 0, 0}, { 1, 0, 0},
{ 0, -1, 0}, { 0, 1, 0},
{ 0, 0, -1}, { 0, 0, 1}
};
static const i32 face_vertices[6][4][3] = {
{{0,0,1}, {0,0,0}, {0,1,0}, {0,1,1}},
{{1,0,0}, {1,0,1}, {1,1,1}, {1,1,0}},
{{0,0,0}, {1,0,0}, {1,0,1}, {0,0,1}},
{{0,1,1}, {1,1,1}, {1,1,0}, {0,1,0}},
{{0,0,0}, {0,1,0}, {1,1,0}, {1,0,0}},
{{1,0,1}, {1,1,1}, {0,1,1}, {0,0,1}}
};
static const f32 face_uvs[4][2] = {
{0, 1}, {1, 1}, {1, 0}, {0, 0}
};
f32 ao_values[4] = {ao0, ao1, ao2, ao3};
f32 ao_strength = config->ambient_occlusion ? config->ao_strength : 0.0f;
f32 tex_scale = config->texture_scale;
pxl8_vec3 normal = face_normals[face];
u16 indices[4];
for (i32 i = 0; i < 4; i++) {
pxl8_vertex v = {0};
v.position.x = pos.x + (f32)face_vertices[face][i][0];
v.position.y = pos.y + (f32)face_vertices[face][i][1];
v.position.z = pos.z + (f32)face_vertices[face][i][2];
v.normal = normal;
v.u = face_uvs[i][0] * tex_scale;
v.v = face_uvs[i][1] * tex_scale;
v.color = texture_id;
v.light = (u8)(255.0f * (1.0f - ao_strength * (1.0f - ao_values[i])));
indices[i] = pxl8_mesh_push_vertex(mesh, v);
}
if (ao0 + ao2 > ao1 + ao3) {
pxl8_mesh_push_quad(mesh, indices[0], indices[1], indices[2], indices[3]);
} else {
pxl8_mesh_push_triangle(mesh, indices[0], indices[1], indices[2]);
pxl8_mesh_push_triangle(mesh, indices[0], indices[2], indices[3]);
}
}
static void add_cube_faces(pxl8_mesh* mesh, const pxl8_voxel_chunk* chunk,
const pxl8_voxel_chunk** neighbors, const pxl8_block_registry* registry,
const pxl8_voxel_mesh_config* config,
i32 x, i32 y, i32 z, pxl8_block block) {
u8 texture_id = pxl8_block_registry_texture(registry, block);
pxl8_vec3 pos = {(f32)x, (f32)y, (f32)z};
static const i32 face_dirs[6][3] = {
{-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1}
};
for (i32 face = 0; face < 6; face++) {
i32 nx = x + face_dirs[face][0];
i32 ny = y + face_dirs[face][1];
i32 nz = z + face_dirs[face][2];
pxl8_block neighbor = get_block_or_neighbor(chunk, neighbors, nx, ny, nz);
if (block_is_opaque(neighbor)) continue;
f32 ao[4] = {1.0f, 1.0f, 1.0f, 1.0f};
if (config->ambient_occlusion) {
switch (face) {
case 0:
ao[0] = compute_ao(chunk, neighbors, x-1, y, z, 0, -1, 0, 0, 0, 1);
ao[1] = compute_ao(chunk, neighbors, x-1, y, z, 0, -1, 0, 0, 0, -1);
ao[2] = compute_ao(chunk, neighbors, x-1, y, z, 0, 1, 0, 0, 0, -1);
ao[3] = compute_ao(chunk, neighbors, x-1, y, z, 0, 1, 0, 0, 0, 1);
break;
case 1:
ao[0] = compute_ao(chunk, neighbors, x+1, y, z, 0, -1, 0, 0, 0, -1);
ao[1] = compute_ao(chunk, neighbors, x+1, y, z, 0, -1, 0, 0, 0, 1);
ao[2] = compute_ao(chunk, neighbors, x+1, y, z, 0, 1, 0, 0, 0, 1);
ao[3] = compute_ao(chunk, neighbors, x+1, y, z, 0, 1, 0, 0, 0, -1);
break;
case 2:
ao[0] = compute_ao(chunk, neighbors, x, y-1, z, -1, 0, 0, 0, 0, -1);
ao[1] = compute_ao(chunk, neighbors, x, y-1, z, 1, 0, 0, 0, 0, -1);
ao[2] = compute_ao(chunk, neighbors, x, y-1, z, 1, 0, 0, 0, 0, 1);
ao[3] = compute_ao(chunk, neighbors, x, y-1, z, -1, 0, 0, 0, 0, 1);
break;
case 3:
ao[0] = compute_ao(chunk, neighbors, x, y+1, z, -1, 0, 0, 0, 0, 1);
ao[1] = compute_ao(chunk, neighbors, x, y+1, z, 1, 0, 0, 0, 0, 1);
ao[2] = compute_ao(chunk, neighbors, x, y+1, z, 1, 0, 0, 0, 0, -1);
ao[3] = compute_ao(chunk, neighbors, x, y+1, z, -1, 0, 0, 0, 0, -1);
break;
case 4:
ao[0] = compute_ao(chunk, neighbors, x, y, z-1, -1, 0, 0, 0, 1, 0);
ao[1] = compute_ao(chunk, neighbors, x, y, z-1, -1, 0, 0, 0, -1, 0);
ao[2] = compute_ao(chunk, neighbors, x, y, z-1, 1, 0, 0, 0, -1, 0);
ao[3] = compute_ao(chunk, neighbors, x, y, z-1, 1, 0, 0, 0, 1, 0);
break;
case 5:
ao[0] = compute_ao(chunk, neighbors, x, y, z+1, 1, 0, 0, 0, -1, 0);
ao[1] = compute_ao(chunk, neighbors, x, y, z+1, 1, 0, 0, 0, 1, 0);
ao[2] = compute_ao(chunk, neighbors, x, y, z+1, -1, 0, 0, 0, 1, 0);
ao[3] = compute_ao(chunk, neighbors, x, y, z+1, -1, 0, 0, 0, -1, 0);
break;
}
}
add_face_vertices(mesh, config, pos, face, texture_id, ao[0], ao[1], ao[2], ao[3]);
}
}
static void add_slab_faces(pxl8_mesh* mesh, const pxl8_voxel_chunk* chunk,
const pxl8_voxel_chunk** neighbors, const pxl8_block_registry* registry,
const pxl8_voxel_mesh_config* config,
i32 x, i32 y, i32 z, pxl8_block block, bool top) {
u8 texture_id = pxl8_block_registry_texture(registry, block);
f32 y_offset = top ? 0.5f : 0.0f;
pxl8_vec3 pos = {(f32)x, (f32)y + y_offset, (f32)z};
static const i32 horiz_faces[4] = {0, 1, 4, 5};
static const i32 face_dirs[6][3] = {
{-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1}
};
for (i32 i = 0; i < 4; i++) {
i32 face = horiz_faces[i];
i32 nx = x + face_dirs[face][0];
i32 nz = z + face_dirs[face][2];
pxl8_block neighbor = get_block_or_neighbor(chunk, neighbors, nx, y, nz);
if (block_is_opaque(neighbor)) continue;
add_face_vertices(mesh, config, pos, face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
}
i32 top_face = 3;
i32 bot_face = 2;
if (top) {
pxl8_block above = get_block_or_neighbor(chunk, neighbors, x, y + 1, z);
if (!block_is_opaque(above)) {
add_face_vertices(mesh, config, pos, top_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
}
add_face_vertices(mesh, config, (pxl8_vec3){(f32)x, (f32)y + 0.5f, (f32)z}, bot_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
} else {
add_face_vertices(mesh, config, pos, top_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
pxl8_block below = get_block_or_neighbor(chunk, neighbors, x, y - 1, z);
if (!block_is_opaque(below)) {
add_face_vertices(mesh, config, pos, bot_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
}
}
}
static void add_slope_faces(pxl8_mesh* mesh, const pxl8_voxel_chunk* chunk,
const pxl8_voxel_chunk** neighbors, const pxl8_block_registry* registry,
const pxl8_voxel_mesh_config* config,
i32 x, i32 y, i32 z, pxl8_block block, i32 direction) {
u8 texture_id = pxl8_block_registry_texture(registry, block);
pxl8_vec3 pos = {(f32)x, (f32)y, (f32)z};
pxl8_block below = get_block_or_neighbor(chunk, neighbors, x, y - 1, z);
if (!block_is_opaque(below)) {
add_face_vertices(mesh, config, pos, 2, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
}
static const i32 dir_to_back_face[4] = {5, 4, 1, 0};
i32 back_face = dir_to_back_face[direction];
static const i32 face_dirs[6][3] = {
{-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1}
};
i32 bx = x + face_dirs[back_face][0];
i32 bz = z + face_dirs[back_face][2];
pxl8_block back_neighbor = get_block_or_neighbor(chunk, neighbors, bx, y, bz);
if (!block_is_opaque(back_neighbor)) {
add_face_vertices(mesh, config, pos, back_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
}
pxl8_vertex verts[4];
memset(verts, 0, sizeof(verts));
static const f32 slope_verts[4][4][3] = {
{{0,0,0}, {1,0,0}, {1,1,1}, {0,1,1}},
{{0,0,1}, {1,0,1}, {1,1,0}, {0,1,0}},
{{1,0,0}, {1,0,1}, {0,1,1}, {0,1,0}},
{{0,0,0}, {0,0,1}, {1,1,1}, {1,1,0}}
};
for (i32 i = 0; i < 4; i++) {
verts[i].position.x = pos.x + slope_verts[direction][i][0];
verts[i].position.y = pos.y + slope_verts[direction][i][1];
verts[i].position.z = pos.z + slope_verts[direction][i][2];
verts[i].color = texture_id;
verts[i].light = 255;
}
static const pxl8_vec3 slope_normals[4] = {
{0, 0.707f, -0.707f}, {0, 0.707f, 0.707f},
{-0.707f, 0.707f, 0}, {0.707f, 0.707f, 0}
};
for (i32 i = 0; i < 4; i++) {
verts[i].normal = slope_normals[direction];
}
u16 i0 = pxl8_mesh_push_vertex(mesh, verts[0]);
u16 i1 = pxl8_mesh_push_vertex(mesh, verts[1]);
u16 i2 = pxl8_mesh_push_vertex(mesh, verts[2]);
u16 i3 = pxl8_mesh_push_vertex(mesh, verts[3]);
pxl8_mesh_push_quad(mesh, i0, i1, i2, i3);
}
pxl8_mesh* pxl8_voxel_build_mesh(const pxl8_voxel_chunk* chunk,
const pxl8_voxel_chunk** neighbors,
const pxl8_block_registry* registry,
const pxl8_voxel_mesh_config* config) {
if (!chunk || !registry) return NULL;
pxl8_voxel_mesh_config cfg = config ? *config : PXL8_VOXEL_MESH_CONFIG_DEFAULT;
u32 max_faces = PXL8_VOXEL_CHUNK_VOLUME * 6;
pxl8_mesh* mesh = pxl8_mesh_create(max_faces * 4, max_faces * 6);
if (!mesh) return NULL;
for (i32 z = 0; z < PXL8_VOXEL_CHUNK_SIZE; z++) {
for (i32 y = 0; y < PXL8_VOXEL_CHUNK_SIZE; y++) {
for (i32 x = 0; x < PXL8_VOXEL_CHUNK_SIZE; x++) {
pxl8_block block = chunk->blocks[voxel_index(x, y, z)];
if (block == PXL8_BLOCK_AIR) continue;
pxl8_voxel_geometry geo = pxl8_block_registry_geometry(registry, block);
switch (geo) {
case PXL8_VOXEL_GEOMETRY_CUBE:
add_cube_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block);
break;
case PXL8_VOXEL_GEOMETRY_SLAB_BOTTOM:
add_slab_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, false);
break;
case PXL8_VOXEL_GEOMETRY_SLAB_TOP:
add_slab_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, true);
break;
case PXL8_VOXEL_GEOMETRY_SLOPE_NORTH:
add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 0);
break;
case PXL8_VOXEL_GEOMETRY_SLOPE_SOUTH:
add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 1);
break;
case PXL8_VOXEL_GEOMETRY_SLOPE_EAST:
add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 2);
break;
case PXL8_VOXEL_GEOMETRY_SLOPE_WEST:
add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 3);
break;
case PXL8_VOXEL_GEOMETRY_STAIRS_NORTH:
case PXL8_VOXEL_GEOMETRY_STAIRS_SOUTH:
case PXL8_VOXEL_GEOMETRY_STAIRS_EAST:
case PXL8_VOXEL_GEOMETRY_STAIRS_WEST:
add_cube_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block);
break;
}
}
}
}
return mesh;
}

View file

@ -1,67 +0,0 @@
#pragma once
#include "pxl8_mesh.h"
#include "pxl8_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#define PXL8_VOXEL_CHUNK_SIZE 32
#define PXL8_BLOCK_COUNT 256
typedef struct pxl8_voxel_chunk pxl8_voxel_chunk;
typedef struct pxl8_block_registry pxl8_block_registry;
typedef u8 pxl8_block;
#define PXL8_BLOCK_AIR 0
typedef enum pxl8_voxel_geometry {
PXL8_VOXEL_GEOMETRY_CUBE,
PXL8_VOXEL_GEOMETRY_SLAB_BOTTOM,
PXL8_VOXEL_GEOMETRY_SLAB_TOP,
PXL8_VOXEL_GEOMETRY_SLOPE_NORTH,
PXL8_VOXEL_GEOMETRY_SLOPE_SOUTH,
PXL8_VOXEL_GEOMETRY_SLOPE_EAST,
PXL8_VOXEL_GEOMETRY_SLOPE_WEST,
PXL8_VOXEL_GEOMETRY_STAIRS_NORTH,
PXL8_VOXEL_GEOMETRY_STAIRS_SOUTH,
PXL8_VOXEL_GEOMETRY_STAIRS_EAST,
PXL8_VOXEL_GEOMETRY_STAIRS_WEST
} pxl8_voxel_geometry;
typedef struct pxl8_voxel_mesh_config {
bool ambient_occlusion;
f32 ao_strength;
f32 texture_scale;
} pxl8_voxel_mesh_config;
#define PXL8_VOXEL_MESH_CONFIG_DEFAULT ((pxl8_voxel_mesh_config){ \
.ambient_occlusion = true, \
.ao_strength = 0.2f, \
.texture_scale = 1.0f \
})
pxl8_voxel_chunk* pxl8_voxel_chunk_create(void);
void pxl8_voxel_chunk_clear(pxl8_voxel_chunk* chunk);
void pxl8_voxel_chunk_destroy(pxl8_voxel_chunk* chunk);
pxl8_block pxl8_voxel_get(const pxl8_voxel_chunk* chunk, i32 x, i32 y, i32 z);
void pxl8_voxel_set(pxl8_voxel_chunk* chunk, i32 x, i32 y, i32 z, pxl8_block block);
void pxl8_voxel_fill(pxl8_voxel_chunk* chunk, pxl8_block block);
pxl8_block_registry* pxl8_block_registry_create(void);
void pxl8_block_registry_destroy(pxl8_block_registry* registry);
void pxl8_block_registry_register(pxl8_block_registry* registry, pxl8_block id, const char* name, u8 texture_id, pxl8_voxel_geometry geo);
const char* pxl8_block_registry_name(const pxl8_block_registry* registry, pxl8_block id);
pxl8_voxel_geometry pxl8_block_registry_geometry(const pxl8_block_registry* registry, pxl8_block id);
u8 pxl8_block_registry_texture(const pxl8_block_registry* registry, pxl8_block id);
pxl8_mesh* pxl8_voxel_build_mesh(const pxl8_voxel_chunk* chunk,
const pxl8_voxel_chunk** neighbors,
const pxl8_block_registry* registry,
const pxl8_voxel_mesh_config* config);
#ifdef __cplusplus
}
#endif

View file

@ -1,28 +1,66 @@
#include "pxl8_world.h"
#include <stdio.h>
#include <string.h>
#include "pxl8_hal.h"
#ifdef PXL8_ASYNC_THREADS
#include <stdatomic.h>
#include "pxl8_queue.h"
#endif
#include "pxl8_bsp_render.h"
#include "pxl8_io.h"
#include "pxl8_gfx3d.h"
#include "pxl8_log.h"
#include "pxl8_mem.h"
#include "pxl8_sim.h"
#include "pxl8_vxl.h"
#include "pxl8_vxl_render.h"
#define PXL8_WORLD_ENTITY_CAPACITY 256
#define VOXEL_SCALE 16.0f
#define VOXEL_CHUNK_SIZE 32.0f
#define WORLD_CHUNK_SIZE (VOXEL_CHUNK_SIZE * VOXEL_SCALE)
struct pxl8_world {
pxl8_chunk* active_chunk;
pxl8_block_registry* block_registry;
pxl8_chunk_cache* chunk_cache;
pxl8_world_chunk* active_chunk;
pxl8_vxl_block_registry* block_registry;
pxl8_world_chunk_cache* chunk_cache;
pxl8_entity_pool* entities;
pxl8_bsp_render_state* bsp_render_state;
pxl8_vxl_render_state* vxl_render_state;
pxl8_sdf sdf;
i32 render_distance;
i32 sim_distance;
pxl8_sim_entity local_player;
u64 client_tick;
#ifdef PXL8_ASYNC_THREADS
pxl8_sim_entity render_state[2];
atomic_uint active_buffer;
pxl8_thread* sim_thread;
atomic_bool sim_running;
atomic_bool sim_paused;
pxl8_net* net;
pxl8_queue input_queue;
f32 sim_accumulator;
#endif
};
pxl8_world* pxl8_world_create(void) {
pxl8_world* world = pxl8_calloc(1, sizeof(pxl8_world));
if (!world) return NULL;
world->block_registry = pxl8_block_registry_create();
world->chunk_cache = pxl8_chunk_cache_create();
world->block_registry = pxl8_vxl_block_registry_create();
world->chunk_cache = pxl8_world_chunk_cache_create();
world->entities = pxl8_entity_pool_create(PXL8_WORLD_ENTITY_CAPACITY);
world->vxl_render_state = pxl8_vxl_render_state_create();
world->render_distance = 3;
world->sim_distance = 4;
if (!world->block_registry || !world->chunk_cache || !world->entities) {
if (!world->block_registry || !world->chunk_cache || !world->entities || !world->vxl_render_state) {
pxl8_world_destroy(world);
return NULL;
}
@ -33,28 +71,30 @@ pxl8_world* pxl8_world_create(void) {
void pxl8_world_destroy(pxl8_world* world) {
if (!world) return;
pxl8_block_registry_destroy(world->block_registry);
pxl8_chunk_cache_destroy(world->chunk_cache);
pxl8_vxl_block_registry_destroy(world->block_registry);
pxl8_world_chunk_cache_destroy(world->chunk_cache);
pxl8_entity_pool_destroy(world->entities);
pxl8_bsp_render_state_destroy(world->bsp_render_state);
pxl8_vxl_render_state_destroy(world->vxl_render_state);
pxl8_free(world);
}
pxl8_chunk_cache* pxl8_world_chunk_cache(pxl8_world* world) {
pxl8_world_chunk_cache* pxl8_world_get_chunk_cache(pxl8_world* world) {
if (!world) return NULL;
return world->chunk_cache;
}
pxl8_chunk* pxl8_world_active_chunk(pxl8_world* world) {
pxl8_world_chunk* pxl8_world_active_chunk(pxl8_world* world) {
if (!world) return NULL;
return world->active_chunk;
}
void pxl8_world_set_active_chunk(pxl8_world* world, pxl8_chunk* chunk) {
void pxl8_world_set_active_chunk(pxl8_world* world, pxl8_world_chunk* chunk) {
if (!world) return;
world->active_chunk = chunk;
}
pxl8_block_registry* pxl8_world_block_registry(pxl8_world* world) {
pxl8_vxl_block_registry* pxl8_world_block_registry(pxl8_world* world) {
if (!world) return NULL;
return world->block_registry;
}
@ -69,40 +109,362 @@ pxl8_entity pxl8_world_spawn(pxl8_world* world) {
return pxl8_entity_spawn(world->entities);
}
bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, f32 radius) {
(void)radius;
static bool voxel_point_solid(pxl8_world_chunk_cache* cache, f32 x, f32 y, f32 z) {
i32 cx = (i32)floorf(x / WORLD_CHUNK_SIZE);
i32 cy = (i32)floorf(y / WORLD_CHUNK_SIZE);
i32 cz = (i32)floorf(z / WORLD_CHUNK_SIZE);
if (!world || !world->active_chunk) return false;
f32 local_x = (x - cx * WORLD_CHUNK_SIZE) / VOXEL_SCALE;
f32 local_y = (y - cy * WORLD_CHUNK_SIZE) / VOXEL_SCALE;
f32 local_z = (z - cz * WORLD_CHUNK_SIZE) / VOXEL_SCALE;
if (world->active_chunk->type == PXL8_CHUNK_BSP && world->active_chunk->bsp) {
return pxl8_bsp_point_solid(world->active_chunk->bsp, pos);
i32 lx = (i32)floorf(local_x);
i32 ly = (i32)floorf(local_y);
i32 lz = (i32)floorf(local_z);
lx = lx < 0 ? 0 : (lx >= 32 ? 31 : lx);
ly = ly < 0 ? 0 : (ly >= 32 ? 31 : ly);
lz = lz < 0 ? 0 : (lz >= 32 ? 31 : lz);
pxl8_world_chunk* chunk = pxl8_world_chunk_cache_get_vxl(cache, cx, cy, cz);
if (!chunk || !chunk->voxels) {
return ly < 8;
}
return pxl8_vxl_block_get(chunk->voxels, lx, ly, lz) != PXL8_VXL_BLOCK_AIR;
}
static pxl8_sim_world make_sim_world(const pxl8_world* world, pxl8_vec3 pos) {
pxl8_sim_world sim = {0};
if (world->active_chunk && world->active_chunk->type == PXL8_WORLD_CHUNK_BSP) {
sim.bsp = world->active_chunk->bsp;
return sim;
}
if (world->chunk_cache) {
i32 cx = (i32)floorf(pos.x / WORLD_CHUNK_SIZE);
i32 cy = (i32)floorf(pos.y / WORLD_CHUNK_SIZE);
i32 cz = (i32)floorf(pos.z / WORLD_CHUNK_SIZE);
pxl8_world_chunk* chunk = pxl8_world_chunk_cache_get_vxl(world->chunk_cache, cx, cy, cz);
if (chunk && chunk->voxels) {
sim.vxl = chunk->voxels;
sim.vxl_cx = cx;
sim.vxl_cy = cy;
sim.vxl_cz = cz;
}
}
return sim;
}
static void entity_to_userdata(const pxl8_sim_entity* ent, u8* userdata) {
u8* p = userdata;
memcpy(p, &ent->pos.x, 4); p += 4;
memcpy(p, &ent->pos.y, 4); p += 4;
memcpy(p, &ent->pos.z, 4); p += 4;
memcpy(p, &ent->yaw, 4); p += 4;
memcpy(p, &ent->pitch, 4); p += 4;
memcpy(p, &ent->vel.x, 4); p += 4;
memcpy(p, &ent->vel.y, 4); p += 4;
memcpy(p, &ent->vel.z, 4); p += 4;
memcpy(p, &ent->flags, 4); p += 4;
memcpy(p, &ent->kind, 2);
}
static void userdata_to_entity(const u8* userdata, pxl8_sim_entity* ent) {
const u8* p = userdata;
memcpy(&ent->pos.x, p, 4); p += 4;
memcpy(&ent->pos.y, p, 4); p += 4;
memcpy(&ent->pos.z, p, 4); p += 4;
memcpy(&ent->yaw, p, 4); p += 4;
memcpy(&ent->pitch, p, 4); p += 4;
memcpy(&ent->vel.x, p, 4); p += 4;
memcpy(&ent->vel.y, p, 4); p += 4;
memcpy(&ent->vel.z, p, 4); p += 4;
memcpy(&ent->flags, p, 4); p += 4;
memcpy(&ent->kind, p, 2);
}
static void update_sdf(pxl8_world* world, pxl8_vec3 center, f32 cell_size) {
if (!world) return;
world->sdf.cell = cell_size;
world->sdf.origin.x = center.x - (PXL8_SDF_X / 2) * cell_size;
world->sdf.origin.y = center.y - (PXL8_SDF_Y / 2) * cell_size;
world->sdf.origin.z = center.z - (PXL8_SDF_Z / 2) * cell_size;
i16 seed_x[PXL8_SDF_SIZE];
i16 seed_y[PXL8_SDF_SIZE];
i16 seed_z[PXL8_SDF_SIZE];
for (i32 iy = 0; iy < PXL8_SDF_Y; iy++) {
f32 wy = world->sdf.origin.y + (iy + 0.5f) * cell_size;
for (i32 iz = 0; iz < PXL8_SDF_Z; iz++) {
f32 wz = world->sdf.origin.z + (iz + 0.5f) * cell_size;
for (i32 ix = 0; ix < PXL8_SDF_X; ix++) {
f32 wx = world->sdf.origin.x + (ix + 0.5f) * cell_size;
i32 idx = iy * PXL8_SDF_Z * PXL8_SDF_X + iz * PXL8_SDF_X + ix;
if (pxl8_world_point_solid(world, wx, wy, wz)) {
seed_x[idx] = (i16)ix;
seed_y[idx] = (i16)iy;
seed_z[idx] = (i16)iz;
} else {
seed_x[idx] = -1;
seed_y[idx] = -1;
seed_z[idx] = -1;
}
}
}
}
for (i32 step = 16; step >= 1; step /= 2) {
for (i32 iy = 0; iy < PXL8_SDF_Y; iy++) {
for (i32 iz = 0; iz < PXL8_SDF_Z; iz++) {
for (i32 ix = 0; ix < PXL8_SDF_X; ix++) {
i32 idx = iy * PXL8_SDF_Z * PXL8_SDF_X + iz * PXL8_SDF_X + ix;
i32 best_dist_sq = (seed_x[idx] >= 0)
? (ix - seed_x[idx]) * (ix - seed_x[idx]) +
(iy - seed_y[idx]) * (iy - seed_y[idx]) +
(iz - seed_z[idx]) * (iz - seed_z[idx])
: 0x7FFFFFFF;
for (i32 dy = -step; dy <= step; dy += step) {
i32 ny = iy + dy;
if (ny < 0 || ny >= PXL8_SDF_Y) continue;
for (i32 dz = -step; dz <= step; dz += step) {
i32 nz = iz + dz;
if (nz < 0 || nz >= PXL8_SDF_Z) continue;
for (i32 dx = -step; dx <= step; dx += step) {
if (dx == 0 && dy == 0 && dz == 0) continue;
i32 nx = ix + dx;
if (nx < 0 || nx >= PXL8_SDF_X) continue;
i32 nidx = ny * PXL8_SDF_Z * PXL8_SDF_X + nz * PXL8_SDF_X + nx;
if (seed_x[nidx] < 0) continue;
i32 dist_sq = (ix - seed_x[nidx]) * (ix - seed_x[nidx]) +
(iy - seed_y[nidx]) * (iy - seed_y[nidx]) +
(iz - seed_z[nidx]) * (iz - seed_z[nidx]);
if (dist_sq < best_dist_sq) {
best_dist_sq = dist_sq;
seed_x[idx] = seed_x[nidx];
seed_y[idx] = seed_y[nidx];
seed_z[idx] = seed_z[nidx];
}
}
}
}
}
}
}
}
for (i32 i = 0; i < PXL8_SDF_SIZE; i++) {
if (seed_x[i] < 0) {
world->sdf.data[i] = 127;
} else {
i32 idx_x = i % PXL8_SDF_X;
i32 idx_z = (i / PXL8_SDF_X) % PXL8_SDF_Z;
i32 idx_y = i / (PXL8_SDF_X * PXL8_SDF_Z);
i32 dx = idx_x - seed_x[i];
i32 dy = idx_y - seed_y[i];
i32 dz = idx_z - seed_z[i];
f32 dist = sqrtf((f32)(dx * dx + dy * dy + dz * dz));
i32 d = (i32)(dist * cell_size);
if (d > 127) d = 127;
world->sdf.data[i] = (i8)d;
}
}
}
bool pxl8_world_point_solid(const pxl8_world* world, f32 x, f32 y, f32 z) {
if (!world) return false;
if (world->active_chunk) {
if (world->active_chunk->type == PXL8_WORLD_CHUNK_BSP && world->active_chunk->bsp) {
return pxl8_bsp_point_solid(world->active_chunk->bsp, (pxl8_vec3){x, y, z});
}
} else if (world->chunk_cache) {
return voxel_point_solid(world->chunk_cache, x, y, z);
}
return false;
}
pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius) {
if (!world || !world->active_chunk) return to;
pxl8_ray pxl8_world_ray(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to) {
pxl8_ray result = {0};
if (world->active_chunk->type == PXL8_CHUNK_BSP && world->active_chunk->bsp) {
return pxl8_bsp_trace(world->active_chunk->bsp, from, to, radius);
if (!world) return result;
pxl8_vec3 dir = pxl8_vec3_sub(to, from);
f32 length = pxl8_vec3_length(dir);
if (length < 0.001f) return result;
f32 step_size = 1.0f;
f32 traveled = 0.0f;
while (traveled < length) {
f32 t = traveled / length;
pxl8_vec3 pos = {
from.x + dir.x * t,
from.y + dir.y * t,
from.z + dir.z * t
};
if (pxl8_world_point_solid(world, pos.x, pos.y, pos.z)) {
result.hit = true;
result.fraction = t;
result.point = pos;
f32 eps = 0.1f;
bool sx_neg = pxl8_world_point_solid(world, pos.x - eps, pos.y, pos.z);
bool sx_pos = pxl8_world_point_solid(world, pos.x + eps, pos.y, pos.z);
bool sy_neg = pxl8_world_point_solid(world, pos.x, pos.y - eps, pos.z);
bool sy_pos = pxl8_world_point_solid(world, pos.x, pos.y + eps, pos.z);
bool sz_neg = pxl8_world_point_solid(world, pos.x, pos.y, pos.z - eps);
bool sz_pos = pxl8_world_point_solid(world, pos.x, pos.y, pos.z + eps);
result.normal = (pxl8_vec3){
(sx_neg && !sx_pos) ? 1.0f : (!sx_neg && sx_pos) ? -1.0f : 0.0f,
(sy_neg && !sy_pos) ? 1.0f : (!sy_neg && sy_pos) ? -1.0f : 0.0f,
(sz_neg && !sz_pos) ? 1.0f : (!sz_neg && sz_pos) ? -1.0f : 0.0f
};
f32 nl = pxl8_vec3_length(result.normal);
if (nl > 0.001f) {
result.normal = pxl8_vec3_scale(result.normal, 1.0f / nl);
} else {
result.normal = (pxl8_vec3){0, 1, 0};
}
return result;
}
traveled += step_size;
}
return to;
return result;
}
void pxl8_world_update(pxl8_world* world, f32 dt) {
(void)dt;
pxl8_ray pxl8_world_sweep(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius) {
pxl8_ray result = {0};
if (!world) return result;
f32 diag = radius * 0.707f;
bool dest_blocked = pxl8_world_point_solid(world, to.x, to.y, to.z) ||
pxl8_world_point_solid(world, to.x + radius, to.y, to.z) ||
pxl8_world_point_solid(world, to.x - radius, to.y, to.z) ||
pxl8_world_point_solid(world, to.x, to.y, to.z + radius) ||
pxl8_world_point_solid(world, to.x, to.y, to.z - radius) ||
pxl8_world_point_solid(world, to.x + diag, to.y, to.z + diag) ||
pxl8_world_point_solid(world, to.x + diag, to.y, to.z - diag) ||
pxl8_world_point_solid(world, to.x - diag, to.y, to.z + diag) ||
pxl8_world_point_solid(world, to.x - diag, to.y, to.z - diag);
if (dest_blocked) {
result.hit = true;
result.fraction = 0;
result.point = from;
pxl8_vec3 dir = pxl8_vec3_sub(to, from);
f32 length = pxl8_vec3_length(dir);
if (length > 0.001f) {
result.normal = pxl8_vec3_scale(dir, -1.0f / length);
} else {
result.normal = (pxl8_vec3){0, 1, 0};
}
}
return result;
}
void pxl8_world_update(pxl8_world* world, const pxl8_input_state* input, f32 dt) {
if (!world) return;
pxl8_chunk_cache_tick(world->chunk_cache);
pxl8_world_chunk_cache_tick(world->chunk_cache);
if (input && (world->local_player.flags & PXL8_SIM_FLAG_ALIVE)) {
pxl8_input_msg msg = {0};
msg.move_x = (pxl8_key_down(input, "d") ? 1.0f : 0.0f) - (pxl8_key_down(input, "a") ? 1.0f : 0.0f);
msg.move_y = (pxl8_key_down(input, "w") ? 1.0f : 0.0f) - (pxl8_key_down(input, "s") ? 1.0f : 0.0f);
msg.look_dx = (f32)pxl8_mouse_dx(input);
msg.look_dy = (f32)pxl8_mouse_dy(input);
msg.buttons = pxl8_key_down(input, "space") ? 1 : 0;
pxl8_sim_world sim = make_sim_world(world, world->local_player.pos);
pxl8_sim_move_player(&world->local_player, &msg, &sim, dt);
}
}
void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) {
if (!world || !gfx || !world->active_chunk) return;
if (!world || !gfx) return;
if (world->active_chunk->type == PXL8_CHUNK_BSP && world->active_chunk->bsp) {
pxl8_bsp_render(gfx, world->active_chunk->bsp, camera_pos);
update_sdf(world, camera_pos, PXL8_SDF_CELL);
pxl8_3d_set_sdf(gfx, &world->sdf);
if (world->active_chunk) {
if (world->active_chunk->type == PXL8_WORLD_CHUNK_BSP && world->active_chunk->bsp) {
pxl8_3d_set_bsp(gfx, world->active_chunk->bsp);
pxl8_bsp_render(gfx, world->active_chunk->bsp,
world->bsp_render_state, camera_pos);
}
} else {
pxl8_3d_set_bsp(gfx, NULL);
i32 cx = (i32)floorf(camera_pos.x / WORLD_CHUNK_SIZE);
i32 cy = (i32)floorf(camera_pos.y / WORLD_CHUNK_SIZE);
i32 cz = (i32)floorf(camera_pos.z / WORLD_CHUNK_SIZE);
const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx);
bool wireframe = world->vxl_render_state && world->vxl_render_state->wireframe;
pxl8_gfx_material mat = {
.texture_id = 0,
.dynamic_lighting = true,
.vertex_color_passthrough = true,
.alpha = 255,
.wireframe = wireframe,
};
i32 r = world->render_distance;
for (i32 dy = -r; dy <= r; dy++) {
for (i32 dz = -r; dz <= r; dz++) {
for (i32 dx = -r; dx <= r; dx++) {
i32 chunk_cx = cx + dx;
i32 chunk_cy = cy + dy;
i32 chunk_cz = cz + dz;
f32 chunk_x = chunk_cx * WORLD_CHUNK_SIZE;
f32 chunk_y = chunk_cy * WORLD_CHUNK_SIZE;
f32 chunk_z = chunk_cz * WORLD_CHUNK_SIZE;
if (frustum) {
pxl8_vec3 aabb_min = {chunk_x, chunk_y, chunk_z};
pxl8_vec3 aabb_max = {chunk_x + WORLD_CHUNK_SIZE, chunk_y + WORLD_CHUNK_SIZE, chunk_z + WORLD_CHUNK_SIZE};
if (!pxl8_frustum_test_aabb(frustum, aabb_min, aabb_max)) continue;
}
pxl8_vxl_mesh_config config = PXL8_VXL_MESH_CONFIG_DEFAULT;
config.chunk_x = chunk_cx;
config.chunk_y = chunk_cy;
config.chunk_z = chunk_cz;
pxl8_mesh* mesh = pxl8_world_chunk_cache_get_mesh(
world->chunk_cache,
chunk_cx, chunk_cy, chunk_cz,
world->block_registry, &config);
if (mesh && mesh->index_count > 0) {
pxl8_mat4 scale = pxl8_mat4_scale(VOXEL_SCALE, VOXEL_SCALE, VOXEL_SCALE);
pxl8_mat4 translate = pxl8_mat4_translate(chunk_x, chunk_y, chunk_z);
pxl8_mat4 model = pxl8_mat4_multiply(translate, scale);
pxl8_3d_draw_mesh(gfx, mesh, &model, &mat);
}
}
}
}
}
}
@ -113,7 +475,7 @@ void pxl8_world_sync(pxl8_world* world, pxl8_net* net) {
u32 chunk_id = pxl8_net_chunk_id(net);
if (chunk_type == PXL8_CHUNK_TYPE_BSP && chunk_id != 0) {
pxl8_chunk* chunk = pxl8_chunk_cache_get_bsp(world->chunk_cache, chunk_id);
pxl8_world_chunk* chunk = pxl8_world_chunk_cache_get_bsp(world->chunk_cache, chunk_id);
if (chunk && chunk->bsp) {
if (world->active_chunk != chunk) {
world->active_chunk = chunk;
@ -121,5 +483,278 @@ void pxl8_world_sync(pxl8_world* world, pxl8_net* net) {
chunk_id, chunk->bsp->num_vertices, chunk->bsp->num_faces);
}
}
} else if (chunk_id == 0 && world->active_chunk != NULL) {
world->active_chunk = NULL;
}
}
static void ensure_bsp_render_state(pxl8_world* world) {
if (!world || world->bsp_render_state) return;
if (!world->active_chunk || world->active_chunk->type != PXL8_WORLD_CHUNK_BSP) return;
if (!world->active_chunk->bsp) return;
world->bsp_render_state = pxl8_bsp_render_state_create(world->active_chunk->bsp->num_faces);
}
void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_gfx_material* material) {
if (!world || !material) return;
ensure_bsp_render_state(world);
if (!world->bsp_render_state) return;
pxl8_bsp_set_material(world->bsp_render_state, material_id, material);
}
void pxl8_world_set_wireframe(pxl8_world* world, bool enabled) {
if (!world) return;
ensure_bsp_render_state(world);
if (world->bsp_render_state) {
pxl8_bsp_set_wireframe(world->bsp_render_state, enabled);
}
if (world->vxl_render_state) {
pxl8_vxl_set_wireframe(world->vxl_render_state, enabled);
}
}
i32 pxl8_world_get_render_distance(const pxl8_world* world) {
if (!world) return 3;
return world->render_distance;
}
void pxl8_world_set_render_distance(pxl8_world* world, i32 distance) {
if (!world) return;
if (distance < 1) distance = 1;
if (distance > 8) distance = 8;
world->render_distance = distance;
}
i32 pxl8_world_get_sim_distance(const pxl8_world* world) {
if (!world) return 4;
return world->sim_distance;
}
void pxl8_world_set_sim_distance(pxl8_world* world, i32 distance) {
if (!world) return;
if (distance < 1) distance = 1;
if (distance > 8) distance = 8;
world->sim_distance = distance;
}
void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z) {
if (!world) return;
world->local_player.pos = (pxl8_vec3){x, y, z};
world->local_player.vel = (pxl8_vec3){0, 0, 0};
world->local_player.yaw = 0;
world->local_player.pitch = 0;
world->local_player.flags = PXL8_SIM_FLAG_ALIVE | PXL8_SIM_FLAG_PLAYER | PXL8_SIM_FLAG_GROUNDED;
world->local_player.kind = 0;
world->client_tick = 0;
#ifdef PXL8_ASYNC_THREADS
world->render_state[0] = world->local_player;
world->render_state[1] = world->local_player;
#endif
}
pxl8_sim_entity* pxl8_world_local_player(pxl8_world* world) {
if (!world) return NULL;
if (!(world->local_player.flags & PXL8_SIM_FLAG_ALIVE)) return NULL;
return &world->local_player;
}
void pxl8_world_predict(pxl8_world* world, pxl8_net* net, const pxl8_input_msg* input, f32 dt) {
if (!world || !net || !input) return;
if (!(world->local_player.flags & PXL8_SIM_FLAG_ALIVE)) return;
pxl8_sim_world sim = make_sim_world(world, world->local_player.pos);
pxl8_sim_move_player(&world->local_player, input, &sim, dt);
world->client_tick++;
entity_to_userdata(&world->local_player, pxl8_net_predicted_state(net));
pxl8_net_predicted_tick_set(net, world->client_tick);
}
void pxl8_world_reconcile(pxl8_world* world, pxl8_net* net, f32 dt) {
if (!world || !net) return;
if (!(world->local_player.flags & PXL8_SIM_FLAG_ALIVE)) return;
if (!pxl8_net_needs_correction(net)) return;
u64 player_id = pxl8_net_player_id(net);
const u8* server_state = pxl8_net_entity_userdata(net, player_id);
if (!server_state) return;
userdata_to_entity(server_state, &world->local_player);
const pxl8_snapshot_header* snap = pxl8_net_snapshot(net);
u64 server_tick = snap ? snap->tick : 0;
for (u64 tick = server_tick + 1; tick <= world->client_tick; tick++) {
const pxl8_input_msg* input = pxl8_net_input_at(net, tick);
if (!input) continue;
pxl8_sim_world sim = make_sim_world(world, world->local_player.pos);
pxl8_sim_move_player(&world->local_player, input, &sim, dt);
}
entity_to_userdata(&world->local_player, pxl8_net_predicted_state(net));
}
#ifdef PXL8_ASYNC_THREADS
#define SIM_TIMESTEP (1.0f / 60.0f)
static void pxl8_world_sim_tick(pxl8_world* world, f32 dt) {
if (!(world->local_player.flags & PXL8_SIM_FLAG_ALIVE)) return;
pxl8_input_msg merged = {0};
pxl8_input_msg* input = NULL;
bool has_input = false;
while ((input = pxl8_queue_pop(&world->input_queue))) {
merged.look_dx += input->look_dx;
merged.look_dy += input->look_dy;
merged.move_x = input->move_x;
merged.move_y = input->move_y;
merged.buttons |= input->buttons;
has_input = true;
pxl8_free(input);
}
const f32 MAX_LOOK_DELTA = 100.0f;
if (merged.look_dx > MAX_LOOK_DELTA) merged.look_dx = MAX_LOOK_DELTA;
if (merged.look_dx < -MAX_LOOK_DELTA) merged.look_dx = -MAX_LOOK_DELTA;
if (merged.look_dy > MAX_LOOK_DELTA) merged.look_dy = MAX_LOOK_DELTA;
if (merged.look_dy < -MAX_LOOK_DELTA) merged.look_dy = -MAX_LOOK_DELTA;
pxl8_sim_world sim = make_sim_world(world, world->local_player.pos);
pxl8_sim_move_player(&world->local_player, &merged, &sim, dt);
if (world->net) {
entity_to_userdata(&world->local_player, pxl8_net_predicted_state(world->net));
pxl8_net_predicted_tick_set(world->net, world->client_tick);
}
world->client_tick++;
(void)has_input;
}
static void pxl8_world_swap_buffers(pxl8_world* world) {
u32 back = atomic_load(&world->active_buffer) ^ 1;
world->render_state[back] = world->local_player;
atomic_store(&world->active_buffer, back);
}
static int pxl8_world_sim_thread(void* data) {
pxl8_world* world = (pxl8_world*)data;
u64 last_time = pxl8_get_ticks_ns();
while (atomic_load(&world->sim_running)) {
if (atomic_load(&world->sim_paused)) {
last_time = pxl8_get_ticks_ns();
pxl8_sleep_ms(1);
continue;
}
u64 now = pxl8_get_ticks_ns();
f32 dt = (f32)(now - last_time) / 1e9f;
last_time = now;
if (dt > 0.1f) dt = 0.1f;
if (dt < 0.0001f) dt = 0.0001f;
world->sim_accumulator += dt;
while (world->sim_accumulator >= SIM_TIMESTEP) {
pxl8_world_chunk_cache_tick(world->chunk_cache);
if (world->net) {
pxl8_packet* pkt;
while ((pkt = pxl8_net_pop_packet(world->net))) {
pxl8_net_process_packet(world->net, pkt);
pxl8_net_packet_free(pkt);
}
pxl8_world_sync(world, world->net);
pxl8_world_reconcile(world, world->net, SIM_TIMESTEP);
}
pxl8_world_sim_tick(world, SIM_TIMESTEP);
world->sim_accumulator -= SIM_TIMESTEP;
}
pxl8_world_swap_buffers(world);
pxl8_sleep_ms(1);
}
return 0;
}
void pxl8_world_start_sim_thread(pxl8_world* world, pxl8_net* net) {
if (!world || world->sim_thread) return;
world->net = net;
pxl8_queue_init(&world->input_queue);
atomic_store(&world->active_buffer, 0);
atomic_store(&world->sim_running, true);
world->sim_accumulator = 0.0f;
world->render_state[0] = world->local_player;
world->render_state[1] = world->local_player;
world->sim_thread = pxl8_thread_create(pxl8_world_sim_thread, "pxl8_sim", world);
}
void pxl8_world_stop_sim_thread(pxl8_world* world) {
if (!world || !world->sim_thread) return;
atomic_store(&world->sim_running, false);
pxl8_thread_wait(world->sim_thread, NULL);
world->sim_thread = NULL;
pxl8_input_msg* input;
while ((input = pxl8_queue_pop(&world->input_queue))) {
pxl8_free(input);
}
}
void pxl8_world_pause_sim(pxl8_world* world, bool paused) {
if (!world) return;
if (paused) {
atomic_store(&world->sim_paused, true);
} else {
pxl8_input_msg* input;
while ((input = pxl8_queue_pop(&world->input_queue))) {
pxl8_free(input);
}
world->sim_accumulator = 0.0f;
atomic_store(&world->sim_paused, false);
}
}
void pxl8_world_push_input(pxl8_world* world, const pxl8_input_msg* input) {
if (!world || !input) return;
pxl8_input_msg* copy = pxl8_malloc(sizeof(pxl8_input_msg));
if (copy) {
*copy = *input;
if (!pxl8_queue_push(&world->input_queue, copy)) {
pxl8_free(copy);
}
}
}
const pxl8_sim_entity* pxl8_world_get_render_state(const pxl8_world* world) {
if (!world) return NULL;
u32 front = atomic_load(&((pxl8_world*)world)->active_buffer);
return &world->render_state[front];
}
f32 pxl8_world_get_interp_alpha(const pxl8_world* world) {
if (!world) return 1.0f;
return world->sim_accumulator / SIM_TIMESTEP;
}
#endif

View file

@ -1,12 +1,13 @@
#pragma once
#include "pxl8_chunk.h"
#include "pxl8_chunk_cache.h"
#include "pxl8_entity.h"
#include "pxl8_gfx.h"
#include "pxl8_math.h"
#include "pxl8_net.h"
#include "pxl8_sim.h"
#include "pxl8_types.h"
#include "pxl8_world_chunk.h"
#include "pxl8_world_chunk_cache.h"
#ifdef __cplusplus
extern "C" {
@ -17,21 +18,44 @@ typedef struct pxl8_world pxl8_world;
pxl8_world* pxl8_world_create(void);
void pxl8_world_destroy(pxl8_world* world);
pxl8_chunk_cache* pxl8_world_chunk_cache(pxl8_world* world);
pxl8_chunk* pxl8_world_active_chunk(pxl8_world* world);
void pxl8_world_set_active_chunk(pxl8_world* world, pxl8_chunk* chunk);
pxl8_world_chunk_cache* pxl8_world_get_chunk_cache(pxl8_world* world);
pxl8_world_chunk* pxl8_world_active_chunk(pxl8_world* world);
void pxl8_world_set_active_chunk(pxl8_world* world, pxl8_world_chunk* chunk);
pxl8_block_registry* pxl8_world_block_registry(pxl8_world* world);
pxl8_vxl_block_registry* pxl8_world_block_registry(pxl8_world* world);
pxl8_entity_pool* pxl8_world_entities(pxl8_world* world);
pxl8_entity pxl8_world_spawn(pxl8_world* world);
bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, f32 radius);
pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius);
bool pxl8_world_point_solid(const pxl8_world* world, f32 x, f32 y, f32 z);
pxl8_ray pxl8_world_ray(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to);
pxl8_ray pxl8_world_sweep(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius);
void pxl8_world_update(pxl8_world* world, f32 dt);
void pxl8_world_update(pxl8_world* world, const pxl8_input_state* input, f32 dt);
void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);
void pxl8_world_sync(pxl8_world* world, pxl8_net* net);
void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_gfx_material* material);
void pxl8_world_set_wireframe(pxl8_world* world, bool enabled);
i32 pxl8_world_get_render_distance(const pxl8_world* world);
void pxl8_world_set_render_distance(pxl8_world* world, i32 distance);
i32 pxl8_world_get_sim_distance(const pxl8_world* world);
void pxl8_world_set_sim_distance(pxl8_world* world, i32 distance);
void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z);
pxl8_sim_entity* pxl8_world_local_player(pxl8_world* world);
void pxl8_world_predict(pxl8_world* world, pxl8_net* net, const pxl8_input_msg* input, f32 dt);
void pxl8_world_reconcile(pxl8_world* world, pxl8_net* net, f32 dt);
#ifdef PXL8_ASYNC_THREADS
void pxl8_world_start_sim_thread(pxl8_world* world, pxl8_net* net);
void pxl8_world_stop_sim_thread(pxl8_world* world);
void pxl8_world_pause_sim(pxl8_world* world, bool paused);
void pxl8_world_push_input(pxl8_world* world, const pxl8_input_msg* input);
const pxl8_sim_entity* pxl8_world_get_render_state(const pxl8_world* world);
f32 pxl8_world_get_interp_alpha(const pxl8_world* world);
#endif
#ifdef __cplusplus
}
#endif

View file

@ -0,0 +1,46 @@
#include "pxl8_world_chunk.h"
#include "pxl8_bsp.h"
#include "pxl8_mem.h"
#include "pxl8_vxl.h"
pxl8_world_chunk* pxl8_world_chunk_create_vxl(i32 cx, i32 cy, i32 cz) {
pxl8_world_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_world_chunk));
if (!chunk) return NULL;
chunk->type = PXL8_WORLD_CHUNK_VXL;
chunk->cx = cx;
chunk->cy = cy;
chunk->cz = cz;
chunk->voxels = pxl8_vxl_chunk_create();
if (!chunk->voxels) {
pxl8_free(chunk);
return NULL;
}
return chunk;
}
pxl8_world_chunk* pxl8_world_chunk_create_bsp(u32 id) {
pxl8_world_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_world_chunk));
if (!chunk) return NULL;
chunk->type = PXL8_WORLD_CHUNK_BSP;
chunk->id = id;
chunk->bsp = NULL;
return chunk;
}
void pxl8_world_chunk_destroy(pxl8_world_chunk* chunk) {
if (!chunk) return;
if (chunk->type == PXL8_WORLD_CHUNK_VXL && chunk->voxels) {
pxl8_vxl_chunk_destroy(chunk->voxels);
} else if (chunk->type == PXL8_WORLD_CHUNK_BSP && chunk->bsp) {
pxl8_bsp_destroy(chunk->bsp);
}
pxl8_free(chunk);
}

View file

@ -0,0 +1,33 @@
#pragma once
#include "pxl8_bsp.h"
#include "pxl8_types.h"
#include "pxl8_vxl.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum pxl8_world_chunk_type {
PXL8_WORLD_CHUNK_VXL,
PXL8_WORLD_CHUNK_BSP
} pxl8_world_chunk_type;
typedef struct pxl8_world_chunk {
pxl8_world_chunk_type type;
u32 id;
u32 version;
i32 cx, cy, cz;
union {
pxl8_bsp* bsp;
pxl8_vxl_chunk* voxels;
};
} pxl8_world_chunk;
pxl8_world_chunk* pxl8_world_chunk_create_vxl(i32 cx, i32 cy, i32 cz);
pxl8_world_chunk* pxl8_world_chunk_create_bsp(u32 id);
void pxl8_world_chunk_destroy(pxl8_world_chunk* chunk);
#ifdef __cplusplus
}
#endif

View file

@ -1,4 +1,4 @@
#include "pxl8_chunk_cache.h"
#include "pxl8_world_chunk_cache.h"
#include <stdlib.h>
#include <string.h>
@ -7,12 +7,12 @@
#include "pxl8_log.h"
#include "pxl8_mem.h"
#define PXL8_VOXEL_CHUNK_VOLUME (PXL8_VOXEL_CHUNK_SIZE * PXL8_VOXEL_CHUNK_SIZE * PXL8_VOXEL_CHUNK_SIZE)
#define PXL8_VXL_CHUNK_VOLUME (PXL8_VXL_CHUNK_SIZE * PXL8_VXL_CHUNK_SIZE * PXL8_VXL_CHUNK_SIZE)
static pxl8_chunk_cache_entry* find_entry_vxl(pxl8_chunk_cache* cache, i32 cx, i32 cy, i32 cz) {
static pxl8_world_chunk_cache_entry* find_entry_vxl(pxl8_world_chunk_cache* cache, i32 cx, i32 cy, i32 cz) {
for (u32 i = 0; i < cache->entry_count; i++) {
pxl8_chunk_cache_entry* e = &cache->entries[i];
if (e->valid && e->chunk && e->chunk->type == PXL8_CHUNK_VXL &&
pxl8_world_chunk_cache_entry* e = &cache->entries[i];
if (e->valid && e->chunk && e->chunk->type == PXL8_WORLD_CHUNK_VXL &&
e->chunk->cx == cx && e->chunk->cy == cy && e->chunk->cz == cz) {
return e;
}
@ -20,10 +20,10 @@ static pxl8_chunk_cache_entry* find_entry_vxl(pxl8_chunk_cache* cache, i32 cx, i
return NULL;
}
static pxl8_chunk_cache_entry* find_entry_bsp(pxl8_chunk_cache* cache, u32 id) {
static pxl8_world_chunk_cache_entry* find_entry_bsp(pxl8_world_chunk_cache* cache, u32 id) {
for (u32 i = 0; i < cache->entry_count; i++) {
pxl8_chunk_cache_entry* e = &cache->entries[i];
if (e->valid && e->chunk && e->chunk->type == PXL8_CHUNK_BSP &&
pxl8_world_chunk_cache_entry* e = &cache->entries[i];
if (e->valid && e->chunk && e->chunk->type == PXL8_WORLD_CHUNK_BSP &&
e->chunk->id == id) {
return e;
}
@ -31,16 +31,16 @@ static pxl8_chunk_cache_entry* find_entry_bsp(pxl8_chunk_cache* cache, u32 id) {
return NULL;
}
static pxl8_chunk_cache_entry* alloc_entry(pxl8_chunk_cache* cache) {
if (cache->entry_count < PXL8_CHUNK_CACHE_SIZE) {
pxl8_chunk_cache_entry* e = &cache->entries[cache->entry_count++];
static pxl8_world_chunk_cache_entry* alloc_entry(pxl8_world_chunk_cache* cache) {
if (cache->entry_count < PXL8_WORLD_CHUNK_CACHE_SIZE) {
pxl8_world_chunk_cache_entry* e = &cache->entries[cache->entry_count++];
memset(e, 0, sizeof(*e));
return e;
}
for (u32 i = 0; i < PXL8_CHUNK_CACHE_SIZE; i++) {
for (u32 i = 0; i < PXL8_WORLD_CHUNK_CACHE_SIZE; i++) {
if (!cache->entries[i].valid) {
pxl8_chunk_cache_entry* e = &cache->entries[i];
pxl8_world_chunk_cache_entry* e = &cache->entries[i];
memset(e, 0, sizeof(*e));
return e;
}
@ -48,22 +48,22 @@ static pxl8_chunk_cache_entry* alloc_entry(pxl8_chunk_cache* cache) {
u64 oldest = cache->entries[0].last_used;
u32 slot = 0;
for (u32 i = 1; i < PXL8_CHUNK_CACHE_SIZE; i++) {
for (u32 i = 1; i < PXL8_WORLD_CHUNK_CACHE_SIZE; i++) {
if (cache->entries[i].last_used < oldest) {
oldest = cache->entries[i].last_used;
slot = i;
}
}
pxl8_chunk_cache_entry* e = &cache->entries[slot];
if (e->chunk) pxl8_chunk_destroy(e->chunk);
pxl8_world_chunk_cache_entry* e = &cache->entries[slot];
if (e->chunk) pxl8_world_chunk_destroy(e->chunk);
if (e->mesh) pxl8_mesh_destroy(e->mesh);
memset(e, 0, sizeof(*e));
return e;
}
static void assembly_reset(pxl8_chunk_assembly* a) {
a->type = PXL8_CHUNK_VXL;
static void assembly_reset(pxl8_world_chunk_assembly* a) {
a->type = PXL8_WORLD_CHUNK_VXL;
a->id = 0;
a->cx = 0;
a->cy = 0;
@ -76,9 +76,9 @@ static void assembly_reset(pxl8_chunk_assembly* a) {
a->complete = false;
}
static void assembly_init(pxl8_chunk_assembly* a, const pxl8_chunk_msg_header* hdr) {
static void assembly_init(pxl8_world_chunk_assembly* a, const pxl8_chunk_msg_header* hdr) {
assembly_reset(a);
a->type = hdr->chunk_type == PXL8_CHUNK_TYPE_BSP ? PXL8_CHUNK_BSP : PXL8_CHUNK_VXL;
a->type = hdr->chunk_type == PXL8_CHUNK_TYPE_BSP ? PXL8_WORLD_CHUNK_BSP : PXL8_WORLD_CHUNK_VXL;
a->id = hdr->id;
a->cx = hdr->cx;
a->cy = hdr->cy;
@ -94,25 +94,31 @@ static void assembly_init(pxl8_chunk_assembly* a, const pxl8_chunk_msg_header* h
}
}
static bool rle_decode_voxel(const u8* src, usize src_len, pxl8_voxel_chunk* chunk) {
static bool rle_decode_voxel(const u8* src, usize src_len, pxl8_vxl_chunk* chunk) {
u8* linear = pxl8_malloc(PXL8_VXL_CHUNK_VOLUME);
if (!linear) return false;
usize src_pos = 0;
usize dst_pos = 0;
while (src_pos + 1 < src_len && dst_pos < PXL8_VOXEL_CHUNK_VOLUME) {
while (src_pos + 1 < src_len && dst_pos < PXL8_VXL_CHUNK_VOLUME) {
u8 block = src[src_pos++];
u8 run_minus_one = src[src_pos++];
usize run = (usize)run_minus_one + 1;
for (usize i = 0; i < run && dst_pos < PXL8_VOXEL_CHUNK_VOLUME; i++) {
i32 x = (i32)(dst_pos % PXL8_VOXEL_CHUNK_SIZE);
i32 y = (i32)((dst_pos / PXL8_VOXEL_CHUNK_SIZE) % PXL8_VOXEL_CHUNK_SIZE);
i32 z = (i32)(dst_pos / (PXL8_VOXEL_CHUNK_SIZE * PXL8_VOXEL_CHUNK_SIZE));
pxl8_voxel_set(chunk, x, y, z, block);
dst_pos++;
for (usize i = 0; i < run && dst_pos < PXL8_VXL_CHUNK_VOLUME; i++) {
linear[dst_pos++] = block;
}
}
return dst_pos == PXL8_VOXEL_CHUNK_VOLUME;
if (dst_pos != PXL8_VXL_CHUNK_VOLUME) {
pxl8_free(linear);
return false;
}
pxl8_vxl_chunk_from_linear(chunk, linear);
pxl8_free(linear);
return true;
}
static pxl8_result deserialize_vertex(pxl8_stream* s, pxl8_bsp_vertex* v) {
@ -204,8 +210,8 @@ static pxl8_result deserialize_cell_portals(pxl8_stream* s, pxl8_bsp_cell_portal
return PXL8_OK;
}
static pxl8_bsp* assembly_to_bsp(pxl8_chunk_assembly* a) {
if (!a->complete || a->data_size < 44) {
static pxl8_bsp* assembly_to_bsp(pxl8_world_chunk_assembly* a) {
if (!a->complete || a->data_size < 48) {
return NULL;
}
@ -315,11 +321,11 @@ static pxl8_bsp* assembly_to_bsp(pxl8_chunk_assembly* a) {
return bsp;
}
static pxl8_result assemble_vxl(pxl8_chunk_cache* cache, pxl8_chunk_assembly* a) {
pxl8_chunk_cache_entry* entry = find_entry_vxl(cache, a->cx, a->cy, a->cz);
static pxl8_result assemble_vxl(pxl8_world_chunk_cache* cache, pxl8_world_chunk_assembly* a) {
pxl8_world_chunk_cache_entry* entry = find_entry_vxl(cache, a->cx, a->cy, a->cz);
if (!entry) {
entry = alloc_entry(cache);
entry->chunk = pxl8_chunk_create_vxl(a->cx, a->cy, a->cz);
entry->chunk = pxl8_world_chunk_create_vxl(a->cx, a->cy, a->cz);
entry->valid = true;
}
@ -332,9 +338,10 @@ static pxl8_result assemble_vxl(pxl8_chunk_cache* cache, pxl8_chunk_assembly* a)
entry->mesh = NULL;
}
pxl8_voxel_chunk_clear(entry->chunk->voxel);
pxl8_vxl_block_clear(entry->chunk->voxels);
if (!rle_decode_voxel(a->data, a->data_size, entry->chunk->voxel)) {
if (!rle_decode_voxel(a->data, a->data_size, entry->chunk->voxels)) {
pxl8_error("[CLIENT] RLE decode failed for chunk (%d,%d,%d)", a->cx, a->cy, a->cz);
return PXL8_ERROR_INVALID_ARGUMENT;
}
@ -342,7 +349,7 @@ static pxl8_result assemble_vxl(pxl8_chunk_cache* cache, pxl8_chunk_assembly* a)
return PXL8_OK;
}
static pxl8_result assemble_bsp(pxl8_chunk_cache* cache, pxl8_chunk_assembly* a) {
static pxl8_result assemble_bsp(pxl8_world_chunk_cache* cache, pxl8_world_chunk_assembly* a) {
pxl8_debug("[CLIENT] assemble_bsp: id=%u data_size=%zu", a->id, a->data_size);
pxl8_bsp* bsp = assembly_to_bsp(a);
if (!bsp) {
@ -352,14 +359,14 @@ static pxl8_result assemble_bsp(pxl8_chunk_cache* cache, pxl8_chunk_assembly* a)
}
pxl8_debug("[CLIENT] assemble_bsp: BSP created with %u verts %u faces", bsp->num_vertices, bsp->num_faces);
pxl8_chunk_cache_entry* entry = find_entry_bsp(cache, a->id);
pxl8_world_chunk_cache_entry* entry = find_entry_bsp(cache, a->id);
if (entry) {
if (entry->chunk && entry->chunk->bsp) {
pxl8_bsp_destroy(entry->chunk->bsp);
}
} else {
entry = alloc_entry(cache);
entry->chunk = pxl8_chunk_create_bsp(a->id);
entry->chunk = pxl8_world_chunk_create_bsp(a->id);
entry->valid = true;
}
@ -371,19 +378,19 @@ static pxl8_result assemble_bsp(pxl8_chunk_cache* cache, pxl8_chunk_assembly* a)
return PXL8_OK;
}
pxl8_chunk_cache* pxl8_chunk_cache_create(void) {
pxl8_chunk_cache* cache = pxl8_calloc(1, sizeof(pxl8_chunk_cache));
pxl8_world_chunk_cache* pxl8_world_chunk_cache_create(void) {
pxl8_world_chunk_cache* cache = pxl8_calloc(1, sizeof(pxl8_world_chunk_cache));
if (!cache) return NULL;
assembly_reset(&cache->assembly);
return cache;
}
void pxl8_chunk_cache_destroy(pxl8_chunk_cache* cache) {
void pxl8_world_chunk_cache_destroy(pxl8_world_chunk_cache* cache) {
if (!cache) return;
for (u32 i = 0; i < cache->entry_count; i++) {
pxl8_chunk_cache_entry* e = &cache->entries[i];
if (e->chunk) pxl8_chunk_destroy(e->chunk);
pxl8_world_chunk_cache_entry* e = &cache->entries[i];
if (e->chunk) pxl8_world_chunk_destroy(e->chunk);
if (e->mesh) pxl8_mesh_destroy(e->mesh);
}
@ -391,12 +398,12 @@ void pxl8_chunk_cache_destroy(pxl8_chunk_cache* cache) {
pxl8_free(cache);
}
pxl8_result pxl8_chunk_cache_receive(pxl8_chunk_cache* cache,
const pxl8_chunk_msg_header* hdr,
const u8* payload, usize len) {
pxl8_result pxl8_world_chunk_cache_receive(pxl8_world_chunk_cache* cache,
const pxl8_chunk_msg_header* hdr,
const u8* payload, usize len) {
if (!cache || !hdr || !payload) return PXL8_ERROR_INVALID_ARGUMENT;
pxl8_chunk_assembly* a = &cache->assembly;
pxl8_world_chunk_assembly* a = &cache->assembly;
bool new_assembly = !a->active ||
(hdr->chunk_type == PXL8_CHUNK_TYPE_BSP && a->id != hdr->id) ||
@ -409,7 +416,7 @@ pxl8_result pxl8_chunk_cache_receive(pxl8_chunk_cache* cache,
assembly_init(a, hdr);
}
if (hdr->fragment_idx >= PXL8_CHUNK_MAX_FRAGMENTS) {
if (hdr->fragment_idx >= PXL8_WORLD_CHUNK_MAX_FRAGMENTS) {
return PXL8_ERROR_INVALID_ARGUMENT;
}
@ -430,9 +437,7 @@ pxl8_result pxl8_chunk_cache_receive(pxl8_chunk_cache* cache,
if (hdr->flags & PXL8_CHUNK_FLAG_FINAL) {
a->complete = true;
pxl8_debug("[CLIENT] Final fragment received, assembling type=%d data_size=%zu", a->type, a->data_size);
if (a->type == PXL8_CHUNK_BSP) {
if (a->type == PXL8_WORLD_CHUNK_BSP) {
return assemble_bsp(cache, a);
} else {
return assemble_vxl(cache, a);
@ -442,9 +447,9 @@ pxl8_result pxl8_chunk_cache_receive(pxl8_chunk_cache* cache,
return PXL8_OK;
}
pxl8_chunk* pxl8_chunk_cache_get_vxl(pxl8_chunk_cache* cache, i32 cx, i32 cy, i32 cz) {
pxl8_world_chunk* pxl8_world_chunk_cache_get_vxl(pxl8_world_chunk_cache* cache, i32 cx, i32 cy, i32 cz) {
if (!cache) return NULL;
pxl8_chunk_cache_entry* e = find_entry_vxl(cache, cx, cy, cz);
pxl8_world_chunk_cache_entry* e = find_entry_vxl(cache, cx, cy, cz);
if (e) {
e->last_used = cache->frame_counter;
return e->chunk;
@ -452,9 +457,9 @@ pxl8_chunk* pxl8_chunk_cache_get_vxl(pxl8_chunk_cache* cache, i32 cx, i32 cy, i3
return NULL;
}
pxl8_chunk* pxl8_chunk_cache_get_bsp(pxl8_chunk_cache* cache, u32 id) {
pxl8_world_chunk* pxl8_world_chunk_cache_get_bsp(pxl8_world_chunk_cache* cache, u32 id) {
if (!cache) return NULL;
pxl8_chunk_cache_entry* e = find_entry_bsp(cache, id);
pxl8_world_chunk_cache_entry* e = find_entry_bsp(cache, id);
if (e) {
e->last_used = cache->frame_counter;
return e->chunk;
@ -462,17 +467,28 @@ pxl8_chunk* pxl8_chunk_cache_get_bsp(pxl8_chunk_cache* cache, u32 id) {
return NULL;
}
pxl8_mesh* pxl8_chunk_cache_get_mesh(pxl8_chunk_cache* cache,
i32 cx, i32 cy, i32 cz,
const pxl8_block_registry* registry,
const pxl8_voxel_mesh_config* config) {
pxl8_mesh* pxl8_world_chunk_cache_get_mesh(pxl8_world_chunk_cache* cache,
i32 cx, i32 cy, i32 cz,
const pxl8_vxl_block_registry* registry,
const pxl8_vxl_mesh_config* config) {
if (!cache || !registry) return NULL;
pxl8_chunk_cache_entry* entry = find_entry_vxl(cache, cx, cy, cz);
if (!entry || !entry->chunk || !entry->chunk->voxel) return NULL;
pxl8_world_chunk_cache_entry* entry = find_entry_vxl(cache, cx, cy, cz);
if (!entry || !entry->chunk || !entry->chunk->voxels) return NULL;
pxl8_world_chunk* nx = pxl8_world_chunk_cache_get_vxl(cache, cx - 1, cy, cz);
pxl8_world_chunk* px = pxl8_world_chunk_cache_get_vxl(cache, cx + 1, cy, cz);
pxl8_world_chunk* ny = pxl8_world_chunk_cache_get_vxl(cache, cx, cy - 1, cz);
pxl8_world_chunk* py = pxl8_world_chunk_cache_get_vxl(cache, cx, cy + 1, cz);
pxl8_world_chunk* nz = pxl8_world_chunk_cache_get_vxl(cache, cx, cy, cz - 1);
pxl8_world_chunk* pz = pxl8_world_chunk_cache_get_vxl(cache, cx, cy, cz + 1);
bool all_neighbors = nx && px && ny && py && nz && pz;
if (entry->mesh && !entry->mesh_dirty) {
return entry->mesh;
if (entry->has_all_neighbors == all_neighbors) {
return entry->mesh;
}
}
if (entry->mesh) {
@ -480,47 +496,43 @@ pxl8_mesh* pxl8_chunk_cache_get_mesh(pxl8_chunk_cache* cache,
entry->mesh = NULL;
}
pxl8_chunk* nx = pxl8_chunk_cache_get_vxl(cache, cx - 1, cy, cz);
pxl8_chunk* px = pxl8_chunk_cache_get_vxl(cache, cx + 1, cy, cz);
pxl8_chunk* ny = pxl8_chunk_cache_get_vxl(cache, cx, cy - 1, cz);
pxl8_chunk* py = pxl8_chunk_cache_get_vxl(cache, cx, cy + 1, cz);
pxl8_chunk* nz = pxl8_chunk_cache_get_vxl(cache, cx, cy, cz - 1);
pxl8_chunk* pz = pxl8_chunk_cache_get_vxl(cache, cx, cy, cz + 1);
const pxl8_voxel_chunk* neighbors[6] = {
nx ? nx->voxel : NULL,
px ? px->voxel : NULL,
ny ? ny->voxel : NULL,
py ? py->voxel : NULL,
nz ? nz->voxel : NULL,
pz ? pz->voxel : NULL
const pxl8_vxl_chunk* neighbors[6] = {
nx ? nx->voxels : NULL,
px ? px->voxels : NULL,
ny ? ny->voxels : NULL,
py ? py->voxels : NULL,
nz ? nz->voxels : NULL,
pz ? pz->voxels : NULL
};
entry->mesh = pxl8_voxel_build_mesh(entry->chunk->voxel, neighbors, registry, config);
pxl8_vxl_mesh_config local_config = config ? *config : PXL8_VXL_MESH_CONFIG_DEFAULT;
entry->mesh = pxl8_vxl_build_mesh(entry->chunk->voxels, neighbors, registry, &local_config);
entry->mesh_dirty = false;
entry->has_all_neighbors = all_neighbors;
return entry->mesh;
}
void pxl8_chunk_cache_tick(pxl8_chunk_cache* cache) {
void pxl8_world_chunk_cache_tick(pxl8_world_chunk_cache* cache) {
if (!cache) return;
cache->frame_counter++;
}
void pxl8_chunk_cache_evict_distant(pxl8_chunk_cache* cache,
i32 cx, i32 cy, i32 cz, i32 radius) {
void pxl8_world_chunk_cache_evict_distant(pxl8_world_chunk_cache* cache,
i32 cx, i32 cy, i32 cz, i32 radius) {
if (!cache) return;
for (u32 i = 0; i < cache->entry_count; i++) {
pxl8_chunk_cache_entry* e = &cache->entries[i];
if (!e->valid || !e->chunk || e->chunk->type != PXL8_CHUNK_VXL) continue;
pxl8_world_chunk_cache_entry* e = &cache->entries[i];
if (!e->valid || !e->chunk || e->chunk->type != PXL8_WORLD_CHUNK_VXL) continue;
i32 dx = e->chunk->cx - cx;
i32 dy = e->chunk->cy - cy;
i32 dz = e->chunk->cz - cz;
if (abs(dx) > radius || abs(dy) > radius || abs(dz) > radius) {
pxl8_chunk_destroy(e->chunk);
pxl8_world_chunk_destroy(e->chunk);
if (e->mesh) pxl8_mesh_destroy(e->mesh);
e->chunk = NULL;
e->mesh = NULL;
@ -529,7 +541,7 @@ void pxl8_chunk_cache_evict_distant(pxl8_chunk_cache* cache,
}
}
void pxl8_chunk_cache_invalidate_meshes(pxl8_chunk_cache* cache) {
void pxl8_world_chunk_cache_invalidate_meshes(pxl8_world_chunk_cache* cache) {
if (!cache) return;
for (u32 i = 0; i < cache->entry_count; i++) {

View file

@ -0,0 +1,69 @@
#pragma once
#include "pxl8_mesh.h"
#include "pxl8_protocol.h"
#include "pxl8_types.h"
#include "pxl8_vxl_render.h"
#include "pxl8_world_chunk.h"
#ifdef __cplusplus
extern "C" {
#endif
#define PXL8_WORLD_CHUNK_CACHE_SIZE 512
#define PXL8_WORLD_CHUNK_MAX_FRAGMENTS 64
#define PXL8_WORLD_CHUNK_MAX_DATA_SIZE 131072
typedef struct pxl8_world_chunk_cache_entry {
pxl8_world_chunk* chunk;
pxl8_mesh* mesh;
u64 last_used;
bool mesh_dirty;
bool valid;
bool has_all_neighbors;
} pxl8_world_chunk_cache_entry;
typedef struct pxl8_world_chunk_assembly {
pxl8_world_chunk_type type;
u32 id;
i32 cx, cy, cz;
u32 version;
u8 fragment_count;
u8 fragments_received;
u8* data;
u32 data_size;
u32 data_capacity;
bool active;
bool complete;
} pxl8_world_chunk_assembly;
typedef struct pxl8_world_chunk_cache {
pxl8_world_chunk_cache_entry entries[PXL8_WORLD_CHUNK_CACHE_SIZE];
pxl8_world_chunk_assembly assembly;
u32 entry_count;
u64 frame_counter;
} pxl8_world_chunk_cache;
pxl8_world_chunk_cache* pxl8_world_chunk_cache_create(void);
void pxl8_world_chunk_cache_destroy(pxl8_world_chunk_cache* cache);
pxl8_result pxl8_world_chunk_cache_receive(pxl8_world_chunk_cache* cache,
const pxl8_chunk_msg_header* hdr,
const u8* payload, usize len);
pxl8_world_chunk* pxl8_world_chunk_cache_get_vxl(pxl8_world_chunk_cache* cache, i32 cx, i32 cy, i32 cz);
pxl8_world_chunk* pxl8_world_chunk_cache_get_bsp(pxl8_world_chunk_cache* cache, u32 id);
pxl8_mesh* pxl8_world_chunk_cache_get_mesh(pxl8_world_chunk_cache* cache,
i32 cx, i32 cy, i32 cz,
const pxl8_vxl_block_registry* registry,
const pxl8_vxl_mesh_config* config);
void pxl8_world_chunk_cache_tick(pxl8_world_chunk_cache* cache);
void pxl8_world_chunk_cache_evict_distant(pxl8_world_chunk_cache* cache,
i32 cx, i32 cy, i32 cz, i32 radius);
void pxl8_world_chunk_cache_invalidate_meshes(pxl8_world_chunk_cache* cache);
#ifdef __cplusplus
}
#endif