diff --git a/demo/main.fnl b/demo/main.fnl index 10116c3..c2c7d41 100644 --- a/demo/main.fnl +++ b/demo/main.fnl @@ -1,4 +1,5 @@ (local pxl8 (require :pxl8)) +(local bsp_world (require :mod.bsp_world)) (local cube3d (require :mod.cube3d)) (var time 0) @@ -16,11 +17,13 @@ (var logo-sprite nil) (global init (fn [] + (bsp_world.init) (pxl8.load_palette "res/sprites/pxl8_logo.ase") (set logo-sprite (pxl8.load_sprite "res/sprites/pxl8_logo.ase")) (set particles (pxl8.particles_new 1000)))) (global update (fn [dt] + (bsp_world.update dt) (set time (+ time dt)) (when (pxl8.key_pressed "1") @@ -46,6 +49,8 @@ (set use-nes-palette (not use-nes-palette)) (local palette-path (if use-nes-palette "res/palettes/nes.ase" "res/sprites/pxl8_logo.ase")) (pxl8.load_palette palette-path)) + (when (pxl8.key_pressed "0") + (set current-effect 0)) (case current-effect 1 (do @@ -63,6 +68,8 @@ (global frame (fn [] (case current-effect + 0 (bsp_world.frame) + 1 (do (pxl8.clr 0) (when logo-sprite diff --git a/demo/mod/bsp_world.fnl b/demo/mod/bsp_world.fnl new file mode 100644 index 0000000..31cb77b --- /dev/null +++ b/demo/mod/bsp_world.fnl @@ -0,0 +1,53 @@ +(local pxl8 (require :pxl8)) + +(var camera-angle 0) +(local camera-height 0) +(local camera-distance 300) +(var fps 0) +(var world nil) + +(fn init [] + (set world (pxl8.world_new)) + (let [result (pxl8.world_load world "res/maps/test.bsp")] + (if (< result 0) + (pxl8.error (.. "Failed to load test.bsp - result: " result)) + (pxl8.info "Loaded world successfully!")))) + +(fn update [dt] + (when (> dt 0) + (set fps (math.floor (/ 1.0 dt)))) + (when (pxl8.world_is_loaded world) + (set camera-angle (+ camera-angle (* dt 0.5))))) + +(fn frame [] + (pxl8.clr 0) + (pxl8.text (.. "FPS: " fps) 10 40 14) + + (if (pxl8.world_is_loaded world) + (let [cam-x (* camera-distance (math.cos camera-angle)) + cam-z (* camera-distance (math.sin camera-angle))] + + (pxl8.text (.. "Camera: " (string.format "%.0f" cam-x) "," + (string.format "%.0f" camera-height) "," + (string.format "%.0f" cam-z)) 10 55 12) + + (pxl8.clear_zbuffer) + (pxl8.set_backface_culling true) + (pxl8.set_wireframe true) + + (let [aspect (/ (pxl8.get_width) (pxl8.get_height)) + fov 1.047] + (pxl8.set_projection (pxl8.mat4_perspective fov aspect 1.0 4096.0))) + + (pxl8.set_view (pxl8.mat4_lookat + [cam-x camera-height cam-z] + [0 0 0] + [0 1 0])) + + (pxl8.set_model (pxl8.mat4_identity)) + + (pxl8.world_render world [cam-x camera-height cam-z])))) + +{:init init + :update update + :frame frame} diff --git a/demo/mod/cube3d.fnl b/demo/mod/cube3d.fnl index 094f6d3..fd36355 100644 --- a/demo/mod/cube3d.fnl +++ b/demo/mod/cube3d.fnl @@ -20,7 +20,6 @@ (var cam-pitch -0.2) (var show-debug-ui false) (var fps 0) -(var fps-timer 0) (var fps-accumulator 0) (var fps-frame-count 0) @@ -174,7 +173,7 @@ (pxl8.set_projection (pxl8.mat4_perspective fov aspect 0.1 100.0)))) (let [target-x (* (math.sin cam-yaw) (math.cos cam-pitch)) - target-y (* (math.sin cam-pitch)) + target-y (math.sin cam-pitch) target-z (* (- (math.cos cam-yaw)) (math.cos cam-pitch)) look-x (+ cam-x target-x) look-y (+ cam-y target-y) @@ -213,13 +212,13 @@ (pxl8.draw_triangle_3d v0 v1 v2 color)))))) (let [new-state (debug-ui.render {:show-debug-ui show-debug-ui - :fps fps - :wireframe wireframe - :auto-rotate auto-rotate - :orthographic orthographic - :use-texture use-texture - :affine affine - :init-texture init-texture})] + :fps fps + :wireframe wireframe + :auto-rotate auto-rotate + :orthographic orthographic + :use-texture use-texture + :affine affine + :init-texture init-texture})] (when (not= new-state.show-debug-ui nil) (set show-debug-ui new-state.show-debug-ui)) (when (not= new-state.wireframe nil) (set wireframe new-state.wireframe)) (when (not= new-state.auto-rotate nil) (set auto-rotate new-state.auto-rotate)) @@ -229,5 +228,5 @@ (when use-texture (init-texture))) (when (not= new-state.affine nil) (set affine new-state.affine)))) -{: update - : frame} +{:update update + :frame frame} diff --git a/pxl8.sh b/pxl8.sh index 0a3d9f5..ef7db2a 100755 --- a/pxl8.sh +++ b/pxl8.sh @@ -384,6 +384,7 @@ case "$COMMAND" in src/pxl8.c src/pxl8_ase.c src/pxl8_blit.c + src/pxl8_bsp.c src/pxl8_cart.c src/pxl8_font.c src/pxl8_gfx.c @@ -394,6 +395,7 @@ case "$COMMAND" in src/pxl8_tilesheet.c src/pxl8_ui.c src/pxl8_vfx.c + src/pxl8_world.c " LUAJIT_LIB="lib/luajit/src/libluajit.a" diff --git a/src/lua/pxl8.lua b/src/lua/pxl8.lua index 4bb956e..ab1dec5 100644 --- a/src/lua/pxl8.lua +++ b/src/lua/pxl8.lua @@ -4,8 +4,6 @@ local gfx = _pxl8_gfx local input = _pxl8_input local ui = _pxl8_ui --- pxl8 lua api --- local pxl8 = {} function pxl8.clr(color) @@ -86,7 +84,6 @@ function pxl8.upload_atlas() C.pxl8_gfx_upload_atlas(gfx) end --- log function pxl8.info(msg) C.pxl8_lua_info(msg) end @@ -419,4 +416,29 @@ function pxl8.ui_window_set_open(title, open) C.pxl8_ui_window_set_open(ui, title, open) end +function pxl8.world_new() + return C.pxl8_world_create() +end + +function pxl8.world_destroy(world) + C.pxl8_world_destroy(world) +end + +function pxl8.world_load(world, filepath) + return C.pxl8_world_load(world, filepath) +end + +function pxl8.world_render(world, camera_pos) + local vec = ffi.new("pxl8_vec3", {x = camera_pos[1], y = camera_pos[2], z = camera_pos[3]}) + C.pxl8_world_render(world, gfx, vec) +end + +function pxl8.world_unload(world) + C.pxl8_world_unload(world) +end + +function pxl8.world_is_loaded(world) + return C.pxl8_world_is_loaded(world) +end + return pxl8 diff --git a/src/pxl8_bsp.c b/src/pxl8_bsp.c new file mode 100644 index 0000000..820c7fd --- /dev/null +++ b/src/pxl8_bsp.c @@ -0,0 +1,419 @@ +#include + +#include + +#include "pxl8_bsp.h" +#include "pxl8_gfx.h" +#include "pxl8_macros.h" + +#define BSP_VERSION 29 + +typedef enum { + CHUNK_ENTITIES = 0, + CHUNK_PLANES = 1, + CHUNK_TEXTURES = 2, + CHUNK_VERTICES = 3, + CHUNK_VISIBILITY = 4, + CHUNK_NODES = 5, + CHUNK_TEXINFO = 6, + CHUNK_FACES = 7, + CHUNK_LIGHTING = 8, + CHUNK_CLIPNODES = 9, + CHUNK_LEAFS = 10, + CHUNK_MARKSURFACES = 11, + CHUNK_EDGES = 12, + CHUNK_SURFEDGES = 13, + CHUNK_MODELS = 14, + CHUNK_COUNT = 15 +} pxl8_bsp_chunk_type; + +typedef struct { + u32 offset; + u32 size; +} pxl8_bsp_chunk; + +typedef struct { + u32 version; + pxl8_bsp_chunk chunks[CHUNK_COUNT]; +} pxl8_bsp_header; + +static u16 read_u16(const u8* data) { + return (u16)data[0] | ((u16)data[1] << 8); +} + +static u32 read_u32(const u8* data) { + return (u32)data[0] | ((u32)data[1] << 8) | ((u32)data[2] << 16) | ((u32)data[3] << 24); +} + +static i16 read_i16(const u8* data) { + return (i16)read_u16(data); +} + +static i32 read_i32(const u8* data) { + return (i32)read_u32(data); +} + +static f32 read_f32(const u8* data) { + u32 val = read_u32(data); + f32 result; + memcpy(&result, &val, sizeof(f32)); + return result; +} + +pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) { + if (!path || !bsp) return PXL8_ERROR_INVALID_ARGUMENT; + + memset(bsp, 0, sizeof(*bsp)); + + size_t file_size; + u8* file_data = (u8*)SDL_LoadFile(path, &file_size); + if (!file_data) { + pxl8_error("Failed to load BSP file: %s", path); + return PXL8_ERROR_FILE_NOT_FOUND; + } + + if (file_size < sizeof(pxl8_bsp_header)) { + pxl8_error("BSP file too small: %s", path); + SDL_free(file_data); + return PXL8_ERROR_INVALID_FORMAT; + } + + pxl8_bsp_header header; + header.version = read_u32(file_data); + + if (header.version != BSP_VERSION) { + pxl8_error("Invalid BSP version: %u (expected %d)", header.version, BSP_VERSION); + SDL_free(file_data); + return PXL8_ERROR_INVALID_FORMAT; + } + + for (i32 i = 0; i < CHUNK_COUNT; i++) { + header.chunks[i].offset = read_u32(file_data + 4 + i * 8); + header.chunks[i].size = read_u32(file_data + 4 + i * 8 + 4); + } + + pxl8_bsp_chunk* vertices_chunk = &header.chunks[CHUNK_VERTICES]; + bsp->num_vertices = vertices_chunk->size / 12; + if (bsp->num_vertices > 0) { + bsp->vertices = (pxl8_bsp_vertex*)SDL_calloc(bsp->num_vertices, sizeof(pxl8_bsp_vertex)); + const u8* data = file_data + vertices_chunk->offset; + for (u32 i = 0; i < bsp->num_vertices; i++) { + bsp->vertices[i].position.x = read_f32(data + i * 12); + bsp->vertices[i].position.y = read_f32(data + i * 12 + 4); + bsp->vertices[i].position.z = read_f32(data + i * 12 + 8); + } + } + + pxl8_bsp_chunk* edges_chunk = &header.chunks[CHUNK_EDGES]; + bsp->num_edges = edges_chunk->size / 4; + if (bsp->num_edges > 0) { + bsp->edges = (pxl8_bsp_edge*)SDL_calloc(bsp->num_edges, sizeof(pxl8_bsp_edge)); + const u8* data = file_data + edges_chunk->offset; + for (u32 i = 0; i < bsp->num_edges; i++) { + bsp->edges[i].vertex[0] = read_u16(data + i * 4); + bsp->edges[i].vertex[1] = read_u16(data + i * 4 + 2); + } + } + + pxl8_bsp_chunk* surfedges_chunk = &header.chunks[CHUNK_SURFEDGES]; + bsp->num_surfedges = surfedges_chunk->size / 4; + if (bsp->num_surfedges > 0) { + bsp->surfedges = (i32*)SDL_calloc(bsp->num_surfedges, sizeof(i32)); + const u8* data = file_data + surfedges_chunk->offset; + for (u32 i = 0; i < bsp->num_surfedges; i++) { + bsp->surfedges[i] = read_i32(data + i * 4); + } + } + + pxl8_bsp_chunk* planes_chunk = &header.chunks[CHUNK_PLANES]; + bsp->num_planes = planes_chunk->size / 20; + if (bsp->num_planes > 0) { + bsp->planes = (pxl8_bsp_plane*)SDL_calloc(bsp->num_planes, sizeof(pxl8_bsp_plane)); + const u8* data = file_data + planes_chunk->offset; + for (u32 i = 0; i < bsp->num_planes; i++) { + bsp->planes[i].normal.x = read_f32(data + i * 20); + bsp->planes[i].normal.y = read_f32(data + i * 20 + 4); + bsp->planes[i].normal.z = read_f32(data + i * 20 + 8); + bsp->planes[i].dist = read_f32(data + i * 20 + 12); + bsp->planes[i].type = read_i32(data + i * 20 + 16); + } + } + + pxl8_bsp_chunk* texinfo_chunk = &header.chunks[CHUNK_TEXINFO]; + bsp->num_texinfo = texinfo_chunk->size / 40; + if (bsp->num_texinfo > 0) { + bsp->texinfo = (pxl8_bsp_texinfo*)SDL_calloc(bsp->num_texinfo, sizeof(pxl8_bsp_texinfo)); + const u8* data = file_data + texinfo_chunk->offset; + for (u32 i = 0; i < bsp->num_texinfo; i++) { + bsp->texinfo[i].u_axis.x = read_f32(data + i * 40); + bsp->texinfo[i].u_axis.y = read_f32(data + i * 40 + 4); + bsp->texinfo[i].u_axis.z = read_f32(data + i * 40 + 8); + bsp->texinfo[i].u_offset = read_f32(data + i * 40 + 12); + bsp->texinfo[i].v_axis.x = read_f32(data + i * 40 + 16); + bsp->texinfo[i].v_axis.y = read_f32(data + i * 40 + 20); + bsp->texinfo[i].v_axis.z = read_f32(data + i * 40 + 24); + bsp->texinfo[i].v_offset = read_f32(data + i * 40 + 28); + bsp->texinfo[i].miptex = read_u32(data + i * 40 + 32); + } + } + + pxl8_bsp_chunk* faces_chunk = &header.chunks[CHUNK_FACES]; + bsp->num_faces = faces_chunk->size / 20; + if (bsp->num_faces > 0) { + bsp->faces = (pxl8_bsp_face*)SDL_calloc(bsp->num_faces, sizeof(pxl8_bsp_face)); + const u8* data = file_data + faces_chunk->offset; + for (u32 i = 0; i < bsp->num_faces; i++) { + bsp->faces[i].plane_id = read_u16(data + i * 20); + bsp->faces[i].side = read_u16(data + i * 20 + 2); + bsp->faces[i].first_edge = read_u32(data + i * 20 + 4); + bsp->faces[i].num_edges = read_u16(data + i * 20 + 8); + bsp->faces[i].texinfo_id = read_u16(data + i * 20 + 10); + bsp->faces[i].styles[0] = data[i * 20 + 12]; + bsp->faces[i].styles[1] = data[i * 20 + 13]; + bsp->faces[i].styles[2] = data[i * 20 + 14]; + bsp->faces[i].styles[3] = data[i * 20 + 15]; + bsp->faces[i].lightmap_offset = read_u32(data + i * 20 + 16); + } + } + + pxl8_bsp_chunk* nodes_chunk = &header.chunks[CHUNK_NODES]; + bsp->num_nodes = nodes_chunk->size / 24; + if (bsp->num_nodes > 0) { + bsp->nodes = (pxl8_bsp_node*)SDL_calloc(bsp->num_nodes, sizeof(pxl8_bsp_node)); + const u8* data = file_data + nodes_chunk->offset; + for (u32 i = 0; i < bsp->num_nodes; i++) { + bsp->nodes[i].plane_id = read_u32(data + i * 24); + bsp->nodes[i].children[0] = read_i16(data + i * 24 + 4); + bsp->nodes[i].children[1] = read_i16(data + i * 24 + 6); + bsp->nodes[i].mins[0] = read_i16(data + i * 24 + 8); + bsp->nodes[i].mins[1] = read_i16(data + i * 24 + 10); + bsp->nodes[i].mins[2] = read_i16(data + i * 24 + 12); + bsp->nodes[i].maxs[0] = read_i16(data + i * 24 + 14); + bsp->nodes[i].maxs[1] = read_i16(data + i * 24 + 16); + bsp->nodes[i].maxs[2] = read_i16(data + i * 24 + 18); + bsp->nodes[i].first_face = read_u16(data + i * 24 + 20); + bsp->nodes[i].num_faces = read_u16(data + i * 24 + 22); + } + } + + pxl8_bsp_chunk* leafs_chunk = &header.chunks[CHUNK_LEAFS]; + bsp->num_leafs = leafs_chunk->size / 28; + if (bsp->num_leafs > 0) { + bsp->leafs = (pxl8_bsp_leaf*)SDL_calloc(bsp->num_leafs, sizeof(pxl8_bsp_leaf)); + const u8* data = file_data + leafs_chunk->offset; + for (u32 i = 0; i < bsp->num_leafs; i++) { + bsp->leafs[i].contents = read_i32(data + i * 28); + bsp->leafs[i].visofs = read_i32(data + i * 28 + 4); + bsp->leafs[i].mins[0] = read_i16(data + i * 28 + 8); + bsp->leafs[i].mins[1] = read_i16(data + i * 28 + 10); + bsp->leafs[i].mins[2] = read_i16(data + i * 28 + 12); + bsp->leafs[i].maxs[0] = read_i16(data + i * 28 + 14); + bsp->leafs[i].maxs[1] = read_i16(data + i * 28 + 16); + bsp->leafs[i].maxs[2] = read_i16(data + i * 28 + 18); + bsp->leafs[i].first_marksurface = read_u16(data + i * 28 + 20); + bsp->leafs[i].num_marksurfaces = read_u16(data + i * 28 + 22); + bsp->leafs[i].ambient_level[0] = data[i * 28 + 24]; + bsp->leafs[i].ambient_level[1] = data[i * 28 + 25]; + bsp->leafs[i].ambient_level[2] = data[i * 28 + 26]; + bsp->leafs[i].ambient_level[3] = data[i * 28 + 27]; + } + } + + pxl8_bsp_chunk* marksurfaces_chunk = &header.chunks[CHUNK_MARKSURFACES]; + bsp->num_marksurfaces = marksurfaces_chunk->size / 2; + if (bsp->num_marksurfaces > 0) { + bsp->marksurfaces = (u16*)SDL_calloc(bsp->num_marksurfaces, sizeof(u16)); + const u8* data = file_data + marksurfaces_chunk->offset; + for (u32 i = 0; i < bsp->num_marksurfaces; i++) { + bsp->marksurfaces[i] = read_u16(data + i * 2); + } + } + + pxl8_bsp_chunk* models_chunk = &header.chunks[CHUNK_MODELS]; + bsp->num_models = models_chunk->size / 64; + if (bsp->num_models > 0) { + bsp->models = (pxl8_bsp_model*)SDL_calloc(bsp->num_models, sizeof(pxl8_bsp_model)); + const u8* data = file_data + models_chunk->offset; + for (u32 i = 0; i < bsp->num_models; i++) { + bsp->models[i].mins[0] = read_f32(data + i * 64); + bsp->models[i].mins[1] = read_f32(data + i * 64 + 4); + bsp->models[i].mins[2] = read_f32(data + i * 64 + 8); + bsp->models[i].maxs[0] = read_f32(data + i * 64 + 12); + bsp->models[i].maxs[1] = read_f32(data + i * 64 + 16); + bsp->models[i].maxs[2] = read_f32(data + i * 64 + 20); + bsp->models[i].origin.x = read_f32(data + i * 64 + 24); + bsp->models[i].origin.y = read_f32(data + i * 64 + 28); + bsp->models[i].origin.z = read_f32(data + i * 64 + 32); + bsp->models[i].headnode[0] = read_i32(data + i * 64 + 36); + bsp->models[i].headnode[1] = read_i32(data + i * 64 + 40); + bsp->models[i].headnode[2] = read_i32(data + i * 64 + 44); + bsp->models[i].headnode[3] = read_i32(data + i * 64 + 48); + bsp->models[i].visleafs = read_i32(data + i * 64 + 52); + bsp->models[i].first_face = read_i32(data + i * 64 + 56); + bsp->models[i].num_faces = read_i32(data + i * 64 + 60); + } + } + + pxl8_bsp_chunk* vis_chunk = &header.chunks[CHUNK_VISIBILITY]; + bsp->visdata_size = vis_chunk->size; + if (bsp->visdata_size > 0) { + bsp->visdata = (u8*)SDL_malloc(bsp->visdata_size); + memcpy(bsp->visdata, file_data + vis_chunk->offset, bsp->visdata_size); + } + + pxl8_bsp_chunk* light_chunk = &header.chunks[CHUNK_LIGHTING]; + bsp->lightdata_size = light_chunk->size; + if (bsp->lightdata_size > 0) { + bsp->lightdata = (u8*)SDL_malloc(bsp->lightdata_size); + memcpy(bsp->lightdata, file_data + light_chunk->offset, bsp->lightdata_size); + } + + SDL_free(file_data); + + 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; +} + +void pxl8_bsp_destroy(pxl8_bsp* bsp) { + if (!bsp) return; + + SDL_free(bsp->edges); + SDL_free(bsp->faces); + SDL_free(bsp->leafs); + SDL_free(bsp->lightdata); + SDL_free(bsp->marksurfaces); + SDL_free(bsp->models); + SDL_free(bsp->nodes); + SDL_free(bsp->planes); + SDL_free(bsp->surfedges); + SDL_free(bsp->texinfo); + SDL_free(bsp->vertices); + SDL_free(bsp->visdata); + + memset(bsp, 0, sizeof(*bsp)); +} + +i32 pxl8_bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos) { + if (!bsp || bsp->num_nodes == 0) return -1; + + i32 node_id = 0; + + while (node_id >= 0) { + const pxl8_bsp_node* node = &bsp->nodes[node_id]; + const pxl8_bsp_plane* plane = &bsp->planes[node->plane_id]; + + f32 dist = pxl8_vec3_dot(pos, plane->normal) - plane->dist; + node_id = node->children[dist < 0 ? 1 : 0]; + } + + return -(node_id + 1); +} + +bool pxl8_bsp_is_leaf_visible(const pxl8_bsp* bsp, i32 leaf_from, i32 leaf_to) { + if (!bsp || !bsp->visdata || leaf_from < 0 || leaf_to < 0) return true; + if ((u32)leaf_from >= bsp->num_leafs || (u32)leaf_to >= bsp->num_leafs) return true; + + i32 visofs = bsp->leafs[leaf_from].visofs; + if (visofs < 0) return true; + + i32 byte_idx = leaf_to >> 3; + i32 bit_idx = leaf_to & 7; + + if ((u32)visofs + byte_idx >= bsp->visdata_size) return true; + + return (bsp->visdata[visofs + byte_idx] & (1 << bit_idx)) != 0; +} + +void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 color) { + if (!gfx || !bsp || face_id >= bsp->num_faces) return; + + const pxl8_bsp_face* face = &bsp->faces[face_id]; + if (face->num_edges < 3) return; + + pxl8_vec3 verts[64]; + u32 num_verts = 0; + + for (u32 i = 0; i < face->num_edges && num_verts < 64; i++) { + i32 surfedge_idx = face->first_edge + i; + 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; + verts[num_verts++] = bsp->vertices[vert_idx].position; + } + + if (num_verts < 3) return; + + for (u32 i = 1; i < num_verts - 1; i++) { + pxl8_3d_draw_triangle_raw(gfx, verts[0], verts[i], verts[i + 1], color); + } +} + +void pxl8_bsp_render_wireframe( + pxl8_gfx* gfx, + const pxl8_bsp* bsp, + pxl8_vec3 camera_pos, + u32 color +) { + if (!gfx || !bsp) return; + + i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos); + + for (u32 leaf_id = 0; leaf_id < bsp->num_leafs; leaf_id++) { + if (camera_leaf >= 0 && !pxl8_bsp_is_leaf_visible(bsp, camera_leaf, leaf_id)) continue; + + const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id]; + + for (u32 i = 0; i < leaf->num_marksurfaces; i++) { + u32 surf_idx = leaf->first_marksurface + i; + if (surf_idx >= bsp->num_marksurfaces) continue; + + u32 face_id = bsp->marksurfaces[surf_idx]; + if (face_id >= bsp->num_faces) continue; + + const pxl8_bsp_face* face = &bsp->faces[face_id]; + + for (u32 e = 0; e < face->num_edges; e++) { + i32 surfedge_idx = face->first_edge + e; + i32 next_surfedge_idx = face->first_edge + ((e + 1) % face->num_edges); + + if (surfedge_idx >= (i32)bsp->num_surfedges || + next_surfedge_idx >= (i32)bsp->num_surfedges) continue; + + i32 edge_idx = bsp->surfedges[surfedge_idx]; + u32 v0_idx, v1_idx; + + if (edge_idx >= 0) { + if ((u32)edge_idx >= bsp->num_edges) continue; + v0_idx = bsp->edges[edge_idx].vertex[0]; + v1_idx = bsp->edges[edge_idx].vertex[1]; + } else { + edge_idx = -edge_idx; + if ((u32)edge_idx >= bsp->num_edges) continue; + v0_idx = bsp->edges[edge_idx].vertex[1]; + v1_idx = bsp->edges[edge_idx].vertex[0]; + } + + if (v0_idx >= bsp->num_vertices || v1_idx >= bsp->num_vertices) continue; + + pxl8_vec3 p0 = bsp->vertices[v0_idx].position; + pxl8_vec3 p1 = bsp->vertices[v1_idx].position; + + pxl8_3d_draw_line_3d(gfx, p0, p1, color); + } + } + } +} diff --git a/src/pxl8_bsp.h b/src/pxl8_bsp.h new file mode 100644 index 0000000..5fc1273 --- /dev/null +++ b/src/pxl8_bsp.h @@ -0,0 +1,130 @@ +#pragma once + +#include "pxl8_gfx.h" +#include "pxl8_math.h" +#include "pxl8_types.h" + +typedef struct pxl8_bsp_vertex { + pxl8_vec3 position; +} pxl8_bsp_vertex; + +typedef struct pxl8_bsp_edge { + u16 vertex[2]; +} pxl8_bsp_edge; + +typedef struct pxl8_bsp_plane { + f32 dist; + pxl8_vec3 normal; + i32 type; +} pxl8_bsp_plane; + +typedef struct pxl8_bsp_texinfo { + u32 miptex; + + f32 u_offset; + pxl8_vec3 u_axis; + + f32 v_offset; + pxl8_vec3 v_axis; +} pxl8_bsp_texinfo; + +typedef struct pxl8_bsp_face { + u32 first_edge; + u32 lightmap_offset; + u16 num_edges; + u16 plane_id; + u16 side; + + u8 styles[4]; + u16 texinfo_id; +} pxl8_bsp_face; + +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_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 { + pxl8_bsp_edge* edges; + pxl8_bsp_face* faces; + pxl8_bsp_leaf* leafs; + u8* lightdata; + u16* marksurfaces; + pxl8_bsp_model* models; + pxl8_bsp_node* nodes; + pxl8_bsp_plane* planes; + i32* surfedges; + pxl8_bsp_texinfo* texinfo; + pxl8_bsp_vertex* vertices; + u8* visdata; + + u32 lightdata_size; + u32 num_edges; + u32 num_faces; + u32 num_leafs; + u32 num_marksurfaces; + u32 num_models; + u32 num_nodes; + u32 num_planes; + u32 num_surfedges; + u32 num_texinfo; + u32 num_vertices; + u32 visdata_size; +} pxl8_bsp; + +#ifdef __cplusplus +extern "C" { +#endif + +pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp); +void pxl8_bsp_destroy(pxl8_bsp* bsp); + +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); + +void pxl8_bsp_render_face( + pxl8_gfx* gfx, + const pxl8_bsp* bsp, + u32 face_id, + u32 color +); + +void pxl8_bsp_render_wireframe( + pxl8_gfx* gfx, + const pxl8_bsp* bsp, + pxl8_vec3 camera_pos, + u32 color +); + +#ifdef __cplusplus +} +#endif diff --git a/src/pxl8_script.c b/src/pxl8_script.c index 2045d36..468e474 100644 --- a/src/pxl8_script.c +++ b/src/pxl8_script.c @@ -158,6 +158,14 @@ static const char* pxl8_ffi_cdefs = "pxl8_mat4 pxl8_mat4_scale(float x, float y, float z);\n" "pxl8_mat4 pxl8_mat4_translate(float x, float y, float z);\n" "\n" +"typedef struct pxl8_world pxl8_world;\n" +"pxl8_world* pxl8_world_create(void);\n" +"void pxl8_world_destroy(pxl8_world* world);\n" +"int pxl8_world_load(pxl8_world* world, const char* path);\n" +"void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);\n" +"void pxl8_world_unload(pxl8_world* world);\n" +"bool pxl8_world_is_loaded(const pxl8_world* world);\n" +"\n" "typedef struct pxl8_ui pxl8_ui;\n" "typedef struct { unsigned char bg_color; unsigned int sprite_id; int corner_size; int edge_size; int padding; } pxl8_frame_theme;\n" "typedef struct { bool enabled; const char* label; } pxl8_menu_item;\n" diff --git a/src/pxl8_world.c b/src/pxl8_world.c new file mode 100644 index 0000000..54f3a67 --- /dev/null +++ b/src/pxl8_world.c @@ -0,0 +1,75 @@ +#include + +#include + +#include "pxl8_bsp.h" +#include "pxl8_macros.h" +#include "pxl8_world.h" + +struct pxl8_world { + pxl8_bsp bsp; + bool loaded; + u32 wireframe_color; +}; + +pxl8_world* pxl8_world_create(void) { + pxl8_world* world = (pxl8_world*)SDL_calloc(1, sizeof(pxl8_world)); + if (!world) { + pxl8_error("Failed to allocate world"); + return NULL; + } + + world->loaded = false; + world->wireframe_color = 15; + + return world; +} + +void pxl8_world_destroy(pxl8_world* world) { + if (!world) return; + + if (world->loaded) { + pxl8_bsp_destroy(&world->bsp); + } + + SDL_free(world); +} + +pxl8_result pxl8_world_load(pxl8_world* world, const char* path) { + if (!world || !path) return PXL8_ERROR_INVALID_ARGUMENT; + + if (world->loaded) { + pxl8_bsp_destroy(&world->bsp); + world->loaded = false; + } + + memset(&world->bsp, 0, sizeof(pxl8_bsp)); + + pxl8_result result = pxl8_bsp_load(path, &world->bsp); + if (result != PXL8_OK) { + pxl8_error("Failed to load world: %s", path); + return result; + } + + world->loaded = true; + pxl8_info("Loaded world: %s", path); + + return PXL8_OK; +} + +void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) { + if (!world || !gfx || !world->loaded) return; + + pxl8_bsp_render_wireframe(gfx, &world->bsp, camera_pos, world->wireframe_color); +} + +void pxl8_world_unload(pxl8_world* world) { + if (!world || !world->loaded) return; + + pxl8_bsp_destroy(&world->bsp); + world->loaded = false; +} + +bool pxl8_world_is_loaded(const pxl8_world* world) { + return world && world->loaded; +} diff --git a/src/pxl8_world.h b/src/pxl8_world.h new file mode 100644 index 0000000..3743be2 --- /dev/null +++ b/src/pxl8_world.h @@ -0,0 +1,24 @@ +#pragma once + +#include "pxl8_gfx.h" +#include "pxl8_math.h" +#include "pxl8_types.h" + +typedef struct pxl8_world pxl8_world; + +#ifdef __cplusplus +extern "C" { +#endif + +pxl8_world* pxl8_world_create(void); +void pxl8_world_destroy(pxl8_world* world); + +pxl8_result pxl8_world_load(pxl8_world* world, const char* path); +void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos); +void pxl8_world_unload(pxl8_world* world); + +bool pxl8_world_is_loaded(const pxl8_world* world); + +#ifdef __cplusplus +} +#endif