From 79a678f1625937212a4e0819c58326f2390f5057 Mon Sep 17 00:00:00 2001 From: asrael Date: Sun, 9 Nov 2025 06:30:17 -0600 Subject: [PATCH] wip procgen --- demo/main.fnl | 131 ++++++------ demo/mod/worldgen.fnl | 151 ++++++++++++++ src/lua/pxl8.lua | 17 ++ src/pxl8.c | 11 + src/pxl8_bsp.c | 126 ++++++++++- src/pxl8_bsp.h | 9 +- src/pxl8_game.h | 4 + src/pxl8_gfx.c | 300 ++++++++++++++++++++++----- src/pxl8_gfx.h | 1 + src/pxl8_io.c | 1 + src/pxl8_math.c | 67 ++++++ src/pxl8_math.h | 12 ++ src/pxl8_procgen.c | 472 ++++++++++++++++++++++++++++++++++++++++++ src/pxl8_procgen.h | 56 +++++ src/pxl8_script.c | 20 +- src/pxl8_sdl3.c | 2 + src/pxl8_world.c | 59 +++++- src/pxl8_world.h | 5 +- 18 files changed, 1317 insertions(+), 127 deletions(-) create mode 100644 demo/mod/worldgen.fnl create mode 100644 src/pxl8_procgen.c create mode 100644 src/pxl8_procgen.h diff --git a/demo/main.fnl b/demo/main.fnl index 29b73ae..6228e39 100644 --- a/demo/main.fnl +++ b/demo/main.fnl @@ -1,9 +1,9 @@ (local pxl8 (require :pxl8)) -(local bsp_world (require :mod.bsp_world)) (local cube3d (require :mod.cube3d)) +(local worldgen (require :mod.worldgen)) (var time 0) -(var current-effect 1) +(var active-demo :logo) (var particles nil) (var fire-init false) (var rain-init false) @@ -17,103 +17,96 @@ (var logo-sprite nil) (global init (fn [] - (bsp_world.init) (cube3d.init) + (worldgen.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") - (set current-effect 1)) - (when (pxl8.key_pressed "2") - (set current-effect 2)) - (when (pxl8.key_pressed "3") - (set current-effect 3)) - (when (pxl8.key_pressed "4") - (set current-effect 4)) + (when (pxl8.key_pressed "1") (set active-demo :logo)) + (when (pxl8.key_pressed "2") (set active-demo :plasma)) + (when (pxl8.key_pressed "3") (set active-demo :tunnel)) + (when (pxl8.key_pressed "4") (set active-demo :raster)) (when (pxl8.key_pressed "5") - (set current-effect 5) + (set active-demo :fire) (set fire-init false)) (when (pxl8.key_pressed "6") - (set current-effect 6) + (set active-demo :rain) (set rain-init false)) (when (pxl8.key_pressed "7") - (set current-effect 7) + (set active-demo :snow) (set snow-init false)) - (when (pxl8.key_pressed "8") - (set current-effect 8)) - (when (pxl8.key_pressed "9") - (set current-effect 0)) - (when (pxl8.key_pressed "0") + (when (pxl8.key_pressed "8") (set active-demo :cube3d)) + (when (pxl8.key_pressed "9") (set active-demo :worldgen)) + (when (pxl8.key_pressed "=") (set use-famicube-palette (not use-famicube-palette)) (local palette-path (if use-famicube-palette "res/palettes/famicube.ase" "res/sprites/pxl8_logo.ase")) (pxl8.load_palette palette-path)) - (case current-effect - 1 (do - (set logo-x (+ logo-x (* logo-dx dt))) - (set logo-y (+ logo-y (* logo-dy dt))) - (when (or (< logo-x 0) (> logo-x 512)) - (set logo-dx (- logo-dx))) - (when (or (< logo-y 0) (> logo-y 296)) - (set logo-dy (- logo-dy)))) - 8 (cube3d.update dt) - _ nil) + (case active-demo + :logo (do + (set logo-x (+ logo-x (* logo-dx dt))) + (set logo-y (+ logo-y (* logo-dy dt))) + (when (or (< logo-x 0) (> logo-x 512)) + (set logo-dx (- logo-dx))) + (when (or (< logo-y 0) (> logo-y 296)) + (set logo-dy (- logo-dy)))) + :cube3d (cube3d.update dt) + :worldgen (worldgen.update dt)) (when particles (pxl8.particles_update particles dt)))) (global frame (fn [] - (case current-effect - 0 (bsp_world.frame) + (case active-demo + :logo (do + (pxl8.clr 0) + (when logo-sprite + (pxl8.sprite logo-sprite logo-x logo-y 128 64))) - 1 (do - (pxl8.clr 0) - (when logo-sprite - (pxl8.sprite logo-sprite logo-x logo-y 128 64))) + :plasma (pxl8.vfx_plasma time 0.10 0.04 1) - 2 (pxl8.vfx_plasma time 0.10 0.04 1) + :tunnel (pxl8.vfx_tunnel time 2.0 0.25) - 3 (pxl8.vfx_tunnel time 2.0 0.25) + :raster (do + (pxl8.clr 0) + (local bars [{:base_y 60 :amplitude 30 :height 16 :speed 2.0 :phase 0 :color 1 :fade_color 18} + {:base_y 180 :amplitude 35 :height 16 :speed 1.8 :phase 2.0 :color 1 :fade_color 27} + {:base_y 300 :amplitude 25 :height 16 :speed 2.2 :phase 4.0 :color 1 :fade_color 24}]) + (pxl8.vfx_raster_bars bars time)) - 4 (do - (pxl8.clr 0) - (local bars [{:base_y 60 :amplitude 30 :height 16 :speed 2.0 :phase 0 :color 1 :fade_color 18} - {:base_y 180 :amplitude 35 :height 16 :speed 1.8 :phase 2.0 :color 1 :fade_color 27} - {:base_y 300 :amplitude 25 :height 16 :speed 2.2 :phase 4.0 :color 1 :fade_color 24}]) - (pxl8.vfx_raster_bars bars time)) + :fire (do + (pxl8.clr 0) + (when particles + (when (not fire-init) + (pxl8.particles_clear particles) + (pxl8.vfx_fire particles 160 140 100 12) + (set fire-init true)) + (pxl8.particles_render particles))) - 5 (do - (pxl8.clr 0) - (when particles - (when (not fire-init) - (pxl8.particles_clear particles) - (pxl8.vfx_fire particles 160 140 100 12) - (set fire-init true)) - (pxl8.particles_render particles))) + :rain (do + (pxl8.clr 0) + (when particles + (when (not rain-init) + (pxl8.particles_clear particles) + (pxl8.vfx_rain particles 320 10.0) + (set rain-init true)) + (pxl8.particles_render particles))) - 6 (do - (pxl8.clr 0) - (when particles - (when (not rain-init) - (pxl8.particles_clear particles) - (pxl8.vfx_rain particles 320 10.0) - (set rain-init true)) - (pxl8.particles_render particles))) + :snow (do + (pxl8.clr 0) + (when particles + (when (not snow-init) + (pxl8.particles_clear particles) + (pxl8.vfx_snow particles 320 5.0) + (set snow-init true)) + (pxl8.particles_render particles))) - 7 (do - (pxl8.clr 0) - (when particles - (when (not snow-init) - (pxl8.particles_clear particles) - (pxl8.vfx_snow particles 320 5.0) - (set snow-init true)) - (pxl8.particles_render particles))) + :cube3d (cube3d.frame) - 8 (cube3d.frame) + :worldgen (worldgen.frame) _ (pxl8.clr 0)))) diff --git a/demo/mod/worldgen.fnl b/demo/mod/worldgen.fnl new file mode 100644 index 0000000..0519d88 --- /dev/null +++ b/demo/mod/worldgen.fnl @@ -0,0 +1,151 @@ +(local pxl8 (require :pxl8)) +(local debug-ui (require :mod.debug_ui)) + +(var world nil) +(var cam-x 1000) +(var cam-y 64) +(var cam-z 1000) +(var cam-yaw 0) +(var cam-pitch 0) +(var bob-time 0) +(var show-debug-ui false) +(var affine false) +(var fps 0) +(var fps-accumulator 0) +(var fps-frame-count 0) + +(local move-speed 200) +(local turn-speed 2.0) +(local bob-speed 8.0) +(local bob-amount 4.0) + +(fn init [] + (set world (pxl8.world_new)) + (let [result (pxl8.world_generate world { + :type pxl8.PROCGEN_CAVE + :width 32 + :height 32 + :seed 42 + :density 0.45 + :iterations 4})] + (if (< result 0) + (pxl8.error (.. "Failed to generate cave - result: " result)) + (pxl8.info "Generated procedural cave")))) + +(fn update [dt] + (set fps-accumulator (+ fps-accumulator dt)) + (set fps-frame-count (+ fps-frame-count 1)) + + (when (>= fps-accumulator 0.25) + (set fps (/ fps-frame-count fps-accumulator)) + (set fps-accumulator 0) + (set fps-frame-count 0)) + + (when (pxl8.key_pressed "F8") + (set show-debug-ui (not show-debug-ui)) + (pxl8.ui_window_set_open "Debug Menu (F8)" show-debug-ui)) + + (when (pxl8.world_is_loaded world) + (let [forward-x (- (math.sin cam-yaw)) + forward-z (- (math.cos cam-yaw)) + right-x (math.cos cam-yaw) + right-z (- (math.sin cam-yaw)) + cell-size 64 + grid-min 0 + grid-max (* 32 cell-size)] + + (var moving false) + (var new-x cam-x) + (var new-z cam-z) + + (when (pxl8.key_down "w") + (set new-x (+ new-x (* forward-x move-speed dt))) + (set new-z (+ new-z (* forward-z move-speed dt))) + (set moving true)) + + (when (pxl8.key_down "s") + (set new-x (- new-x (* forward-x move-speed dt))) + (set new-z (- new-z (* forward-z move-speed dt))) + (set moving true)) + + (when (pxl8.key_down "q") + (set new-x (- new-x (* right-x move-speed dt))) + (set new-z (- new-z (* right-z move-speed dt))) + (set moving true)) + + (when (pxl8.key_down "e") + (set new-x (+ new-x (* right-x move-speed dt))) + (set new-z (+ new-z (* right-z move-speed dt))) + (set moving true)) + + (when (and (>= new-x grid-min) (<= new-x grid-max) + (>= new-z grid-min) (<= new-z grid-max)) + (set cam-x new-x) + (set cam-z new-z)) + + (when (or (pxl8.key_down "left") (pxl8.key_down "a")) + (set cam-yaw (+ cam-yaw (* turn-speed dt)))) + + (when (or (pxl8.key_down "right") (pxl8.key_down "d")) + (set cam-yaw (- cam-yaw (* turn-speed dt)))) + + (when (pxl8.key_down "up") + (set cam-pitch (+ cam-pitch (* turn-speed dt))) + (when (> cam-pitch 1.5) (set cam-pitch 1.5))) + + (when (pxl8.key_down "down") + (set cam-pitch (- cam-pitch (* turn-speed dt))) + (when (< cam-pitch -1.5) (set cam-pitch -1.5))) + + (if moving + (set bob-time (+ bob-time (* dt bob-speed))) + (let [target-phase (* (math.floor (/ bob-time math.pi)) math.pi)] + (set bob-time (+ (* bob-time 0.8) (* target-phase 0.2)))))))) + +(fn frame [] + (pxl8.clr 0) + + (when (pxl8.world_is_loaded world) + (let [bob-offset (* (math.sin bob-time) bob-amount) + eye-y (+ cam-y bob-offset) + forward-x (- (math.sin cam-yaw)) + forward-z (- (math.cos cam-yaw)) + target-x (+ cam-x forward-x) + target-y (+ eye-y (math.sin cam-pitch)) + target-z (+ cam-z forward-z)] + + (pxl8.text (.. "Pos: " (string.format "%.0f" cam-x) "," + (string.format "%.0f" cam-y) "," + (string.format "%.0f" cam-z)) 10 25 12) + + (pxl8.clear_zbuffer) + (pxl8.set_backface_culling true) + (pxl8.set_wireframe false) + + (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 eye-y cam-z] + [target-x target-y target-z] + [0 1 0])) + + (pxl8.set_model (pxl8.mat4_identity)) + + (pxl8.set_affine_textures affine) + (pxl8.world_render world [cam-x eye-y cam-z])) + + (let [new-state (debug-ui.render {:show-debug-ui show-debug-ui + :fps fps + :wireframe false + :auto-rotate false + :orthographic false + :use-texture true + :affine affine})] + (when (not= new-state.show-debug-ui nil) (set show-debug-ui new-state.show-debug-ui)) + (when (not= new-state.affine nil) (set affine new-state.affine))))) + +{:init init + :update update + :frame frame} diff --git a/src/lua/pxl8.lua b/src/lua/pxl8.lua index fe04eff..65fddfe 100644 --- a/src/lua/pxl8.lua +++ b/src/lua/pxl8.lua @@ -459,4 +459,21 @@ function pxl8.world_is_loaded(world) return C.pxl8_world_is_loaded(world) end +function pxl8.world_generate(world, params) + local c_params = ffi.new("pxl8_procgen_params") + c_params.type = params.type or C.PXL8_PROCGEN_CAVE + c_params.width = params.width or 32 + c_params.height = params.height or 32 + c_params.depth = params.depth or 0 + c_params.seed = params.seed or 0 + c_params.density = params.density or 0.45 + c_params.iterations = params.iterations or 4 + c_params.type_params = nil + return C.pxl8_world_generate(world, c_params) +end + +pxl8.PROCGEN_CAVE = C.PXL8_PROCGEN_CAVE +pxl8.PROCGEN_DUNGEON = C.PXL8_PROCGEN_DUNGEON +pxl8.PROCGEN_TERRAIN = C.PXL8_PROCGEN_TERRAIN + return pxl8 diff --git a/src/pxl8.c b/src/pxl8.c index 7d7e4ee..6f7e5ca 100644 --- a/src/pxl8.c +++ b/src/pxl8.c @@ -147,6 +147,17 @@ pxl8_game_result pxl8_update(pxl8_game* game) { game->last_time = current_time; game->time += dt; + game->fps_accumulator += dt; + game->fps_frame_count++; + if (game->fps_accumulator >= 1.0f) { + game->fps = (f32)game->fps_frame_count / game->fps_accumulator; + if (!game->repl_mode) { + pxl8_debug("FPS: %.1f (%.2fms)", game->fps, (game->fps_accumulator / game->fps_frame_count) * 1000.0f); + } + game->fps_accumulator = 0.0f; + game->fps_frame_count = 0; + } + pxl8_script_check_reload(game->script); if (game->repl_mode && !game->repl_started) { diff --git a/src/pxl8_bsp.c b/src/pxl8_bsp.c index a61d0f8..6b22818 100644 --- a/src/pxl8_bsp.c +++ b/src/pxl8_bsp.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -331,7 +332,47 @@ bool pxl8_bsp_is_leaf_visible(const pxl8_bsp* bsp, i32 leaf_from, i32 leaf_to) { 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) { +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]; + + f32 min_x = 1e30f, min_y = 1e30f, min_z = 1e30f; + f32 max_x = -1e30f, max_y = -1e30f, max_z = -1e30f; + + for (u32 i = 0; i < face->num_edges; 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; + + 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; + } + + pxl8_vec3 aabb_min = {min_x, min_y, min_z}; + pxl8_vec3 aabb_max = {max_x, max_y, max_z}; + + return pxl8_frustum_test_aabb(frustum, aabb_min, aabb_max); +} + +void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 texture_id) { if (!gfx || !bsp || face_id >= bsp->num_faces) return; const pxl8_bsp_face* face = &bsp->faces[face_id]; @@ -340,6 +381,19 @@ void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 c pxl8_vec3 verts[64]; u32 num_verts = 0; + const pxl8_bsp_plane* plane = &bsp->planes[face->plane_id]; + + u32 color; + bool use_texture = false; + + if (fabsf(plane->normal.y) > 0.7f) { + color = (plane->normal.y > 0) ? 4 : 3; + } else { + color = 15; + use_texture = (texture_id > 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; @@ -362,11 +416,77 @@ void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 c 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); + if (use_texture && face->texinfo_id < bsp->num_texinfo) { + const pxl8_bsp_texinfo* texinfo = &bsp->texinfo[face->texinfo_id]; + f32 tex_scale = 64.0f; + + for (u32 tri_idx = 1; tri_idx < num_verts - 1; tri_idx++) { + pxl8_vec3 v0 = verts[0]; + pxl8_vec3 v1 = verts[tri_idx]; + pxl8_vec3 v2 = verts[tri_idx + 1]; + + f32 u0 = (pxl8_vec3_dot(v0, texinfo->u_axis) + texinfo->u_offset) / tex_scale; + f32 v0_uv = (pxl8_vec3_dot(v0, texinfo->v_axis) + texinfo->v_offset) / tex_scale; + f32 u1 = (pxl8_vec3_dot(v1, texinfo->u_axis) + texinfo->u_offset) / tex_scale; + f32 v1_uv = (pxl8_vec3_dot(v1, texinfo->v_axis) + texinfo->v_offset) / tex_scale; + f32 u2 = (pxl8_vec3_dot(v2, texinfo->u_axis) + texinfo->u_offset) / tex_scale; + f32 v2_uv = (pxl8_vec3_dot(v2, texinfo->v_axis) + texinfo->v_offset) / tex_scale; + + pxl8_3d_draw_triangle_textured(gfx, v0, v1, v2, u0, v0_uv, u1, v1_uv, u2, v2_uv, texture_id); + } + } else{ + for (u32 i = 1; i < num_verts - 1; i++) { + pxl8_3d_draw_triangle_raw(gfx, verts[0], verts[i], verts[i + 1], color); + } } } +void pxl8_bsp_render_solid( + pxl8_gfx* gfx, + const pxl8_bsp* bsp, + pxl8_vec3 camera_pos, + u32 texture_id +) { + if (!gfx || !bsp || bsp->num_faces == 0) return; + + const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx); + if (!frustum) return; + + i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos); + + static u8* rendered_faces = NULL; + static u32 rendered_faces_capacity = 0; + + if (rendered_faces_capacity < bsp->num_faces) { + rendered_faces = realloc(rendered_faces, bsp->num_faces); + if (!rendered_faces) return; + rendered_faces_capacity = bsp->num_faces; + pxl8_debug("Allocated face tracking buffer: %u bytes", bsp->num_faces); + } + + memset(rendered_faces, 0, bsp->num_faces); + + for (u32 leaf_id = 0; leaf_id < bsp->num_leafs; leaf_id++) { + if (camera_leaf >= 0 && !pxl8_bsp_is_leaf_visible(bsp, camera_leaf, leaf_id)) continue; + + const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id]; + + for (u32 i = 0; i < leaf->num_marksurfaces; i++) { + u32 surf_idx = leaf->first_marksurface + i; + if (surf_idx >= bsp->num_marksurfaces) continue; + + u32 face_id = bsp->marksurfaces[surf_idx]; + if (face_id >= bsp->num_faces) continue; + + if (rendered_faces[face_id]) continue; + rendered_faces[face_id] = 1; + + pxl8_bsp_render_face(gfx, bsp, face_id, texture_id); + } + } + +} + void pxl8_bsp_render_wireframe( pxl8_gfx* gfx, const pxl8_bsp* bsp, diff --git a/src/pxl8_bsp.h b/src/pxl8_bsp.h index 5fc1273..74cfbc1 100644 --- a/src/pxl8_bsp.h +++ b/src/pxl8_bsp.h @@ -115,7 +115,14 @@ void pxl8_bsp_render_face( pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, - u32 color + u32 texture_id +); + +void pxl8_bsp_render_solid( + pxl8_gfx* gfx, + const pxl8_bsp* bsp, + pxl8_vec3 camera_pos, + u32 texture_id ); void pxl8_bsp_render_wireframe( diff --git a/src/pxl8_game.h b/src/pxl8_game.h index 8d9b865..0077915 100644 --- a/src/pxl8_game.h +++ b/src/pxl8_game.h @@ -27,6 +27,10 @@ typedef struct pxl8_game { u64 last_time; f32 time; + f32 fps_accumulator; + i32 fps_frame_count; + f32 fps; + bool repl_mode; bool repl_started; bool running; diff --git a/src/pxl8_gfx.c b/src/pxl8_gfx.c index 668dd34..1089c64 100644 --- a/src/pxl8_gfx.c +++ b/src/pxl8_gfx.c @@ -42,12 +42,18 @@ struct pxl8_gfx { pxl8_mat4 model; pxl8_mat4 projection; pxl8_mat4 view; + pxl8_mat4 mvp; + bool mvp_dirty; + pxl8_frustum frustum; bool wireframe; f32* zbuffer; i32 zbuffer_height; i32 zbuffer_width; bool affine_textures; + + u32 frame_triangle_count; + u32 frame_pixel_count; }; static inline void pxl8_color_unpack(u32 color, u8* r, u8* g, u8* b, u8* a) { @@ -191,6 +197,7 @@ pxl8_gfx* pxl8_gfx_create( gfx->model = pxl8_mat4_identity(); gfx->projection = pxl8_mat4_identity(); gfx->view = pxl8_mat4_identity(); + gfx->mvp_dirty = true; gfx->wireframe = false; gfx->zbuffer = NULL; gfx->zbuffer_height = 0; @@ -403,10 +410,23 @@ void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom) { void pxl8_clr(pxl8_gfx* gfx, u32 color) { if (!gfx || !gfx->framebuffer) return; - + + static u32 frame_count = 0; + if (gfx->frame_triangle_count > 0) { + if (frame_count % 60 == 0) { + i32 fb_pixels = gfx->framebuffer_width * gfx->framebuffer_height; + f32 overdraw = (f32)gfx->frame_pixel_count / (f32)fb_pixels; + pxl8_debug("Frame triangles: %u, pixels: %u, overdraw: %.2fx", + gfx->frame_triangle_count, gfx->frame_pixel_count, overdraw); + } + frame_count++; + } + gfx->frame_triangle_count = 0; + gfx->frame_pixel_count = 0; + i32 bytes_per_pixel = (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1; i32 size = gfx->framebuffer_width * gfx->framebuffer_height; - + if (bytes_per_pixel == 4) { u32* fb32 = (u32*)gfx->framebuffer; for (i32 i = 0; i < size; i++) { @@ -786,9 +806,33 @@ static bool pxl8_3d_init_zbuffer(pxl8_gfx* gfx) { void pxl8_3d_clear_zbuffer(pxl8_gfx* gfx) { if (!gfx || !gfx->zbuffer) return; - for (i32 i = 0; i < gfx->zbuffer_width * gfx->zbuffer_height; i++) { - gfx->zbuffer[i] = 1e30f; + + i32 count = gfx->zbuffer_width * gfx->zbuffer_height; + const f32 far_z = 1e30f; + +#if defined(PXL8_SIMD_NEON) + float32x4_t far_vec = vdupq_n_f32(far_z); + i32 i = 0; + for (; i + 3 < count; i += 4) { + vst1q_f32(&gfx->zbuffer[i], far_vec); } + for (; i < count; i++) { + gfx->zbuffer[i] = far_z; + } +#elif defined(PXL8_SIMD_SSE2) + __m128 far_vec = _mm_set1_ps(far_z); + i32 i = 0; + for (; i + 3 < count; i += 4) { + _mm_store_ps(&gfx->zbuffer[i], far_vec); + } + for (; i < count; i++) { + gfx->zbuffer[i] = far_z; + } +#else + for (i32 i = 0; i < count; i++) { + gfx->zbuffer[i] = far_z; + } +#endif } void pxl8_3d_set_backface_culling(pxl8_gfx* gfx, bool culling) { @@ -799,16 +843,19 @@ void pxl8_3d_set_backface_culling(pxl8_gfx* gfx, bool culling) { void pxl8_3d_set_model(pxl8_gfx* gfx, pxl8_mat4 mat) { if (!gfx) return; gfx->model = mat; + gfx->mvp_dirty = true; } void pxl8_3d_set_projection(pxl8_gfx* gfx, pxl8_mat4 mat) { if (!gfx) return; gfx->projection = mat; + gfx->mvp_dirty = true; } void pxl8_3d_set_view(pxl8_gfx* gfx, pxl8_mat4 mat) { if (!gfx) return; gfx->view = mat; + gfx->mvp_dirty = true; } void pxl8_3d_set_wireframe(pxl8_gfx* gfx, bool wireframe) { @@ -821,7 +868,27 @@ void pxl8_3d_set_affine_textures(pxl8_gfx* gfx, bool affine) { gfx->affine_textures = affine; } +static inline void pxl8_update_mvp(pxl8_gfx* gfx) { + if (!gfx->mvp_dirty) return; + + gfx->mvp = pxl8_mat4_multiply(gfx->projection, + pxl8_mat4_multiply(gfx->view, gfx->model)); + + pxl8_mat4 vp = pxl8_mat4_multiply(gfx->projection, gfx->view); + gfx->frustum = pxl8_frustum_from_matrix(vp); + + gfx->mvp_dirty = false; +} + +const pxl8_frustum* pxl8_3d_get_frustum(pxl8_gfx* gfx) { + if (!gfx) return NULL; + pxl8_update_mvp(gfx); + return &gfx->frustum; +} + static inline pxl8_vec4 pxl8_transform_vertex(pxl8_gfx* gfx, pxl8_vec3 pos) { + pxl8_update_mvp(gfx); + pxl8_vec4 v = { .x = pos.x, .y = pos.y, @@ -829,10 +896,7 @@ static inline pxl8_vec4 pxl8_transform_vertex(pxl8_gfx* gfx, pxl8_vec3 pos) { .w = 1.0f, }; - pxl8_mat4 mvp = pxl8_mat4_multiply(gfx->projection, - pxl8_mat4_multiply(gfx->view, gfx->model)); - - return pxl8_mat4_multiply_vec4(mvp, v); + return pxl8_mat4_multiply_vec4(gfx->mvp, v); } static inline void pxl8_project_to_screen(pxl8_gfx* gfx, pxl8_vec4 clip, i32* x, i32* y, f32* z) { @@ -865,23 +929,82 @@ void pxl8_3d_draw_line_3d(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u32 color) pxl8_line(gfx, x0, y0, x1, y1, color); } -static inline void pxl8_fill_scanline(pxl8_gfx* gfx, i32 y, i32 xs, i32 xe, f32 z0, f32 z1, u32 color) { +static inline void pxl8_fill_scanline_hicolor(pxl8_gfx* gfx, i32 y, i32 xs, i32 xe, f32 z0, f32 z1, u32 color) { if (y < 0 || y >= gfx->framebuffer_height) return; if (xs > xe) { i32 tmp = xs; xs = xe; xe = tmp; + f32 tmpz = z0; z0 = z1; z1 = tmpz; } - for (i32 x = xs; x <= xe; x++) { - if (x >= 0 && x < gfx->framebuffer_width) { - f32 t = (xe == xs) ? 0.0f : (f32)(x - xs) / (f32)(xe - xs); - f32 z = z0 + t * (z1 - z0); + if (xs < 0) xs = 0; + if (xe >= gfx->framebuffer_width) xe = gfx->framebuffer_width - 1; + if (xs > xe) return; - i32 idx = y * gfx->zbuffer_width + x; - if (z <= gfx->zbuffer[idx]) { - gfx->zbuffer[idx] = z; - pxl8_pixel(gfx, x, y, color); - } + i32 width = xe - xs; + if (width == 0) { + i32 idx = y * gfx->zbuffer_width + xs; + if (z0 <= gfx->zbuffer[idx]) { + gfx->zbuffer[idx] = z0; + i32 fb_idx = y * gfx->framebuffer_width + xs; + ((u32*)gfx->framebuffer)[fb_idx] = color; } + return; + } + + f32 dz = (z1 - z0) / (f32)width; + f32 z = z0; + + i32 zbuf_offset = y * gfx->zbuffer_width; + i32 fb_offset = y * gfx->framebuffer_width; + u32* fb = (u32*)gfx->framebuffer; + + for (i32 x = xs; x <= xe; x++) { + i32 idx = zbuf_offset + x; + if (z <= gfx->zbuffer[idx]) { + gfx->zbuffer[idx] = z; + fb[fb_offset + x] = color; + } + z += dz; + } +} + +static inline void pxl8_fill_scanline_indexed(pxl8_gfx* gfx, i32 y, i32 xs, i32 xe, f32 z0, f32 z1, u32 color) { + if (y < 0 || y >= gfx->framebuffer_height) return; + if (xs > xe) { + i32 tmp = xs; xs = xe; xe = tmp; + f32 tmpz = z0; z0 = z1; z1 = tmpz; + } + + if (xs < 0) xs = 0; + if (xe >= gfx->framebuffer_width) xe = gfx->framebuffer_width - 1; + if (xs > xe) return; + + i32 width = xe - xs; + u8 idx_color = color & 0xFF; + + if (width == 0) { + i32 idx = y * gfx->zbuffer_width + xs; + if (z0 <= gfx->zbuffer[idx]) { + gfx->zbuffer[idx] = z0; + i32 fb_idx = y * gfx->framebuffer_width + xs; + gfx->framebuffer[fb_idx] = idx_color; + } + return; + } + + f32 dz = (z1 - z0) / (f32)width; + f32 z = z0; + + i32 zbuf_offset = y * gfx->zbuffer_width; + i32 fb_offset = y * gfx->framebuffer_width; + + for (i32 x = xs; x <= xe; x++) { + i32 idx = zbuf_offset + x; + if (z <= gfx->zbuffer[idx]) { + gfx->zbuffer[idx] = z; + gfx->framebuffer[fb_offset + x] = idx_color; + } + z += dz; } } @@ -927,47 +1050,117 @@ static inline void pxl8_fill_scanline_textured( tmpf = w0; w0 = w1; w1 = tmpf; } - for (i32 x = xs; x <= xe; x++) { - if (x >= 0 && x < gfx->framebuffer_width) { - f32 t = (xe == xs) ? 0.0f : (f32)(x - xs) / (f32)(xe - xs); - f32 z = z0 + t * (z1 - z0); - - i32 idx = y * gfx->zbuffer_width + x; - if (z <= gfx->zbuffer[idx]) { - f32 u = u0 + t * (u1 - u0); - f32 v = v0 + t * (v1 - v0); - - if (!gfx->affine_textures) { - f32 w = w0 + t * (w1 - w0); - if (fabsf(w) > 1e-6f) { - u /= w; - v /= w; - } + i32 span = xe - xs; + if (span <= 0) { + if (xs >= 0 && xs < gfx->framebuffer_width) { + i32 idx = y * gfx->zbuffer_width + xs; + if (z0 <= gfx->zbuffer[idx]) { + gfx->frame_pixel_count++; + f32 u = u0, v = v0; + if (!gfx->affine_textures && fabsf(w0) > 1e-6f) { + u /= w0; + v /= w0; } - u32 color = pxl8_sample_texture(gfx, texture_id, u, v); - if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) { - if (color & 0xFF000000) { - gfx->zbuffer[idx] = z; - pxl8_pixel(gfx, x, y, color); - } - } else { - if (color != 0) { + if ((gfx->color_mode == PXL8_COLOR_MODE_HICOLOR && (color & 0xFF000000)) || + (gfx->color_mode != PXL8_COLOR_MODE_HICOLOR && color != 0)) { + gfx->zbuffer[idx] = z0; + pxl8_pixel(gfx, xs, y, color); + } + } + } + return; + } + + f32 inv_span = 1.0f / (f32)span; + f32 dz = (z1 - z0) * inv_span; + f32 du = (u1 - u0) * inv_span; + f32 dv = (v1 - v0) * inv_span; + f32 dw = (w1 - w0) * inv_span; + + f32 z = z0; + f32 u = u0; + f32 v = v0; + f32 w = w0; + + if (gfx->affine_textures) { + for (i32 x = xs; x <= xe; x++) { + if (x >= 0 && x < gfx->framebuffer_width) { + i32 idx = y * gfx->zbuffer_width + x; + if (z <= gfx->zbuffer[idx]) { + gfx->frame_pixel_count++; + u32 color = pxl8_sample_texture(gfx, texture_id, u, v); + if ((gfx->color_mode == PXL8_COLOR_MODE_HICOLOR && (color & 0xFF000000)) || + (gfx->color_mode != PXL8_COLOR_MODE_HICOLOR && color != 0)) { gfx->zbuffer[idx] = z; pxl8_pixel(gfx, x, y, color); } } } + z += dz; + u += du; + v += dv; + } + } else { + i32 x = xs; + while (x <= xe) { + f32 w_inv = (fabsf(w) > 1e-6f) ? (1.0f / w) : 0.0f; + f32 u_corrected = u * w_inv; + f32 v_corrected = v * w_inv; + + i32 span_end = (x + 16 <= xe) ? (x + 16) : (xe + 1); + i32 span_len = span_end - x; + + f32 next_u, next_v, affine_du, affine_dv; + if (span_len > 1) { + f32 next_w = w + span_len * dw; + f32 next_w_inv = (fabsf(next_w) > 1e-6f) ? (1.0f / next_w) : 0.0f; + next_u = (u + span_len * du) * next_w_inv; + next_v = (v + span_len * dv) * next_w_inv; + f32 inv_span = 1.0f / (f32)span_len; + affine_du = (next_u - u_corrected) * inv_span; + affine_dv = (next_v - v_corrected) * inv_span; + } else { + affine_du = 0; + affine_dv = 0; + } + + f32 affine_u = u_corrected; + f32 affine_v = v_corrected; + + for (; x < span_end; x++) { + if (x >= 0 && x < gfx->framebuffer_width) { + i32 idx = y * gfx->zbuffer_width + x; + if (z <= gfx->zbuffer[idx]) { + gfx->frame_pixel_count++; + u32 color = pxl8_sample_texture(gfx, texture_id, affine_u, affine_v); + if ((gfx->color_mode == PXL8_COLOR_MODE_HICOLOR && (color & 0xFF000000)) || + (gfx->color_mode != PXL8_COLOR_MODE_HICOLOR && color != 0)) { + gfx->zbuffer[idx] = z; + pxl8_pixel(gfx, x, y, color); + } + } + } + z += dz; + u += du; + v += dv; + w += dw; + affine_u += affine_du; + affine_v += affine_dv; + } } } } +typedef void (*pxl8_scanline_func)(pxl8_gfx*, i32, i32, i32, f32, f32, u32); + static void pxl8_draw_flat_bottom_triangle( pxl8_gfx* gfx, i32 x0, i32 y0, f32 z0, i32 x1, i32 y1, f32 z1, i32 x2, i32 y2, f32 z2, - u32 color + u32 color, + pxl8_scanline_func fill_scanline ) { (void)z2; if (y1 == y0) return; @@ -979,7 +1172,7 @@ static void pxl8_draw_flat_bottom_triangle( f32 cur_x2 = (f32)x0; for (i32 y = y0; y <= y1; y++) { - pxl8_fill_scanline(gfx, y, (i32)cur_x1, (i32)cur_x2, z0, z1, color); + fill_scanline(gfx, y, (i32)cur_x1, (i32)cur_x2, z0, z1, color); cur_x1 += inv_slope_1; cur_x2 += inv_slope_2; } @@ -990,7 +1183,8 @@ static void pxl8_draw_flat_top_triangle( i32 x0, i32 y0, f32 z0, i32 x1, i32 y1, f32 z1, i32 x2, i32 y2, f32 z2, - u32 color + u32 color, + pxl8_scanline_func fill_scanline ) { (void)z2; if (y2 == y0) return; @@ -1002,7 +1196,7 @@ static void pxl8_draw_flat_top_triangle( f32 cur_x2 = (f32)x2; for (i32 y = y2; y > y0; y--) { - pxl8_fill_scanline(gfx, y, (i32)cur_x1, (i32)cur_x2, z0, z1, color); + fill_scanline(gfx, y, (i32)cur_x1, (i32)cur_x2, z0, z1, color); cur_x1 -= inv_slope_1; cur_x2 -= inv_slope_2; } @@ -1022,6 +1216,8 @@ void pxl8_3d_draw_triangle_raw(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_v void pxl8_3d_draw_triangle(pxl8_gfx* gfx, pxl8_triangle tri) { if (!gfx || !pxl8_3d_init_zbuffer(gfx)) return; + gfx->frame_triangle_count++; + pxl8_vec4 v0 = pxl8_transform_vertex(gfx, tri.v[0].position); pxl8_vec4 v1 = pxl8_transform_vertex(gfx, tri.v[1].position); pxl8_vec4 v2 = pxl8_transform_vertex(gfx, tri.v[2].position); @@ -1065,17 +1261,21 @@ void pxl8_3d_draw_triangle(pxl8_gfx* gfx, pxl8_triangle tri) { u32 color = tri.v[0].color; + pxl8_scanline_func fill_scanline = (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) + ? pxl8_fill_scanline_hicolor + : pxl8_fill_scanline_indexed; + if (y1 == y2) { - pxl8_draw_flat_bottom_triangle(gfx, x0, y0, z0, x1, y1, z1, x2, y2, z2, color); + pxl8_draw_flat_bottom_triangle(gfx, x0, y0, z0, x1, y1, z1, x2, y2, z2, color, fill_scanline); } else if (y0 == y1) { - pxl8_draw_flat_top_triangle(gfx, x0, y0, z0, x1, y1, z1, x2, y2, z2, color); + pxl8_draw_flat_top_triangle(gfx, x0, y0, z0, x1, y1, z1, x2, y2, z2, color, fill_scanline); } else { i32 x3 = x0 + (i32)(((f32)(y1 - y0) / (f32)(y2 - y0)) * (x2 - x0)); i32 y3 = y1; f32 z3 = z0 + ((f32)(y1 - y0) / (f32)(y2 - y0)) * (z2 - z0); - pxl8_draw_flat_bottom_triangle(gfx, x0, y0, z0, x1, y1, z1, x3, y3, z3, color); - pxl8_draw_flat_top_triangle(gfx, x1, y1, z1, x3, y3, z3, x2, y2, z2, color); + pxl8_draw_flat_bottom_triangle(gfx, x0, y0, z0, x1, y1, z1, x3, y3, z3, color, fill_scanline); + pxl8_draw_flat_top_triangle(gfx, x1, y1, z1, x3, y3, z3, x2, y2, z2, color, fill_scanline); } } diff --git a/src/pxl8_gfx.h b/src/pxl8_gfx.h index f989445..933d108 100644 --- a/src/pxl8_gfx.h +++ b/src/pxl8_gfx.h @@ -101,6 +101,7 @@ void pxl8_3d_draw_line_3d(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u32 color); void pxl8_3d_draw_triangle(pxl8_gfx* gfx, pxl8_triangle tri); void pxl8_3d_draw_triangle_raw(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, u32 color); void pxl8_3d_draw_triangle_textured(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, f32 u0, f32 v0f, f32 u1, f32 v1f, f32 u2, f32 v2f, u32 texture_id); +const pxl8_frustum* pxl8_3d_get_frustum(pxl8_gfx* gfx); void pxl8_3d_set_affine_textures(pxl8_gfx* gfx, bool affine); void pxl8_3d_set_backface_culling(pxl8_gfx* gfx, bool culling); void pxl8_3d_set_model(pxl8_gfx* gfx, pxl8_mat4 mat); diff --git a/src/pxl8_io.c b/src/pxl8_io.c index b32441f..2eacef1 100644 --- a/src/pxl8_io.c +++ b/src/pxl8_io.c @@ -115,6 +115,7 @@ static i32 pxl8_key_code(const char* key_name) { {"1", 30}, {"2", 31}, {"3", 32}, {"4", 33}, {"5", 34}, {"6", 35}, {"7", 36}, {"8", 37}, {"9", 38}, {"0", 39}, {"return", 40}, {"escape", 41}, {"backspace", 42}, {"tab", 43}, {"space", 44}, + {"-", 45}, {"=", 46}, {"left", 80}, {"right", 79}, {"up", 82}, {"down", 81}, {"f1", 58}, {"f2", 59}, {"f3", 60}, {"f4", 61}, {"f5", 62}, {"f6", 63}, {"f7", 64}, {"f8", 65}, {"f9", 66}, {"f10", 67}, {"f11", 68}, {"f12", 69}, diff --git a/src/pxl8_math.c b/src/pxl8_math.c index 60016e2..37b8006 100644 --- a/src/pxl8_math.c +++ b/src/pxl8_math.c @@ -250,3 +250,70 @@ pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up) { return mat; } + +pxl8_frustum pxl8_frustum_from_matrix(pxl8_mat4 vp) { + pxl8_frustum frustum; + const f32* m = vp.m; + + frustum.planes[0].normal.x = m[3] - m[0]; + frustum.planes[0].normal.y = m[7] - m[4]; + frustum.planes[0].normal.z = m[11] - m[8]; + frustum.planes[0].distance = m[15] - m[12]; + + frustum.planes[1].normal.x = m[3] + m[0]; + frustum.planes[1].normal.y = m[7] + m[4]; + frustum.planes[1].normal.z = m[11] + m[8]; + frustum.planes[1].distance = m[15] + m[12]; + + frustum.planes[2].normal.x = m[3] + m[1]; + frustum.planes[2].normal.y = m[7] + m[5]; + frustum.planes[2].normal.z = m[11] + m[9]; + frustum.planes[2].distance = m[15] + m[13]; + + frustum.planes[3].normal.x = m[3] - m[1]; + frustum.planes[3].normal.y = m[7] - m[5]; + frustum.planes[3].normal.z = m[11] - m[9]; + frustum.planes[3].distance = m[15] - m[13]; + + frustum.planes[4].normal.x = m[3] - m[2]; + frustum.planes[4].normal.y = m[7] - m[6]; + frustum.planes[4].normal.z = m[11] - m[10]; + frustum.planes[4].distance = m[15] - m[14]; + + frustum.planes[5].normal.x = m[3] + m[2]; + frustum.planes[5].normal.y = m[7] + m[6]; + frustum.planes[5].normal.z = m[11] + m[10]; + frustum.planes[5].distance = m[15] + m[14]; + + for (i32 i = 0; i < 6; i++) { + f32 len = pxl8_vec3_length(frustum.planes[i].normal); + if (len > 1e-6f) { + f32 inv_len = 1.0f / len; + frustum.planes[i].normal = pxl8_vec3_scale(frustum.planes[i].normal, inv_len); + frustum.planes[i].distance *= inv_len; + } + } + + return frustum; +} + +bool pxl8_frustum_test_aabb(const pxl8_frustum* frustum, pxl8_vec3 min, pxl8_vec3 max) { + for (i32 i = 0; i < 6; i++) { + pxl8_vec3 normal = frustum->planes[i].normal; + f32 d = frustum->planes[i].distance; + + pxl8_vec3 p_vertex = { + (normal.x >= 0.0f) ? max.x : min.x, + (normal.y >= 0.0f) ? max.y : min.y, + (normal.z >= 0.0f) ? max.z : min.z + }; + + f32 dist = pxl8_vec3_dot(normal, p_vertex) + d; + + if (dist < 0.0f) { + return false; + } + } + + return true; +} diff --git a/src/pxl8_math.h b/src/pxl8_math.h index e10f17d..773cb9a 100644 --- a/src/pxl8_math.h +++ b/src/pxl8_math.h @@ -18,6 +18,15 @@ typedef struct pxl8_mat4 { f32 m[16]; } pxl8_mat4; +typedef struct pxl8_plane { + pxl8_vec3 normal; + f32 distance; +} pxl8_plane; + +typedef struct pxl8_frustum { + pxl8_plane planes[6]; +} pxl8_frustum; + #ifdef __cplusplus extern "C" { #endif @@ -49,6 +58,9 @@ pxl8_mat4 pxl8_mat4_rotate_z(f32 angle); pxl8_mat4 pxl8_mat4_scale(f32 x, f32 y, f32 z); pxl8_mat4 pxl8_mat4_translate(f32 x, f32 y, f32 z); +pxl8_frustum pxl8_frustum_from_matrix(pxl8_mat4 vp); +bool pxl8_frustum_test_aabb(const pxl8_frustum* frustum, pxl8_vec3 min, pxl8_vec3 max); + #ifdef __cplusplus } #endif diff --git a/src/pxl8_procgen.c b/src/pxl8_procgen.c new file mode 100644 index 0000000..38ff452 --- /dev/null +++ b/src/pxl8_procgen.c @@ -0,0 +1,472 @@ +#include "pxl8_procgen.h" + +#include "pxl8_macros.h" + +#include +#include +#include + +typedef struct cave_grid { + u8* cells; + i32 width; + i32 height; +} cave_grid; + +static u32 prng_state = 0; + +static void prng_seed(u32 seed) { + prng_state = seed; +} + +static u32 prng_next(void) { + prng_state ^= prng_state << 13; + prng_state ^= prng_state >> 17; + prng_state ^= prng_state << 5; + return prng_state; +} + +static f32 prng_float(void) { + return (f32)prng_next() / (f32)0xFFFFFFFF; +} + +static cave_grid* cave_grid_create(i32 width, i32 height) { + cave_grid* grid = malloc(sizeof(cave_grid)); + if (!grid) return NULL; + + grid->width = width; + grid->height = height; + grid->cells = calloc(width * height, sizeof(u8)); + + if (!grid->cells) { + free(grid); + return NULL; + } + + return grid; +} + +static void cave_grid_destroy(cave_grid* grid) { + if (!grid) return; + free(grid->cells); + free(grid); +} + +static u8 cave_grid_get(const cave_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 cave_grid_set(cave_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 i32 cave_grid_count_neighbors(const cave_grid* grid, i32 x, i32 y) { + i32 count = 0; + for (i32 dy = -1; dy <= 1; dy++) { + for (i32 dx = -1; dx <= 1; dx++) { + if (dx == 0 && dy == 0) continue; + if (cave_grid_get(grid, x + dx, y + dy)) { + count++; + } + } + } + return count; +} + +static void calculate_texture_axes(const pxl8_vec3 normal, pxl8_vec3* u_axis, pxl8_vec3* v_axis) { + if (fabsf(normal.y) > 0.9f) { + *u_axis = (pxl8_vec3){1.0f, 0.0f, 0.0f}; + *v_axis = (pxl8_vec3){0.0f, 0.0f, 1.0f}; + } else if (fabsf(normal.x) > 0.7f) { + *u_axis = (pxl8_vec3){0.0f, 1.0f, 0.0f}; + *v_axis = (pxl8_vec3){0.0f, 0.0f, 1.0f}; + } else { + *u_axis = (pxl8_vec3){1.0f, 0.0f, 0.0f}; + *v_axis = (pxl8_vec3){0.0f, 1.0f, 0.0f}; + } +} + +static void cave_grid_initialize(cave_grid* grid, f32 density) { + for (i32 y = 0; y < grid->height; y++) { + for (i32 x = 0; x < grid->width; x++) { + u8 value = (prng_float() < density) ? 1 : 0; + cave_grid_set(grid, x, y, value); + } + } +} + +static void cave_grid_smooth(cave_grid* grid) { + cave_grid* temp = cave_grid_create(grid->width, grid->height); + if (!temp) return; + + for (i32 y = 0; y < grid->height; y++) { + for (i32 x = 0; x < grid->width; x++) { + i32 neighbors = cave_grid_count_neighbors(grid, x, y); + u8 value = (neighbors > 4) ? 1 : 0; + cave_grid_set(temp, x, y, value); + } + } + + memcpy(grid->cells, temp->cells, grid->width * grid->height); + cave_grid_destroy(temp); +} + +static pxl8_result cave_to_bsp(pxl8_bsp* bsp, const cave_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 (cave_grid_get(grid, x, y) == 0) { + if (cave_grid_get(grid, x - 1, y) == 1) face_count++; + if (cave_grid_get(grid, x + 1, y) == 1) face_count++; + if (cave_grid_get(grid, x, y - 1) == 1) face_count++; + if (cave_grid_get(grid, x, y + 1) == 1) face_count++; + floor_ceiling_count++; + } + } + } + + face_count += floor_ceiling_count * 2; + vertex_count = face_count * 4; + + pxl8_debug("Cave generation: %dx%d grid -> %d faces, %d vertices", + grid->width, grid->height, face_count, vertex_count); + + bsp->vertices = calloc(vertex_count, sizeof(pxl8_bsp_vertex)); + bsp->faces = calloc(face_count, sizeof(pxl8_bsp_face)); + bsp->planes = calloc(face_count, sizeof(pxl8_bsp_plane)); + bsp->edges = calloc(vertex_count, sizeof(pxl8_bsp_edge)); + bsp->surfedges = calloc(vertex_count, sizeof(i32)); + bsp->texinfo = calloc(face_count, sizeof(pxl8_bsp_texinfo)); + + if (!bsp->vertices || !bsp->faces || !bsp->planes || !bsp->edges || !bsp->surfedges || !bsp->texinfo) { + return PXL8_ERROR_OUT_OF_MEMORY; + } + + i32 vert_idx = 0; + i32 face_idx = 0; + i32 edge_idx = 0; + + const f32 cell_size = 64.0f; + const f32 wall_height = 128.0f; + + for (i32 y = 0; y < grid->height; y++) { + for (i32 x = 0; x < grid->width; x++) { + if (cave_grid_get(grid, x, y) == 0) { + f32 fx = (f32)x * cell_size; + f32 fy = (f32)y * cell_size; + + if (cave_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].texinfo_id = face_idx; + + calculate_texture_axes(bsp->planes[face_idx].normal, + &bsp->texinfo[face_idx].u_axis, + &bsp->texinfo[face_idx].v_axis); + bsp->texinfo[face_idx].u_offset = 0.0f; + bsp->texinfo[face_idx].v_offset = 0.0f; + + 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; + } + + vert_idx += 4; + edge_idx += 4; + face_idx++; + } + + if (cave_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].texinfo_id = face_idx; + + calculate_texture_axes(bsp->planes[face_idx].normal, + &bsp->texinfo[face_idx].u_axis, + &bsp->texinfo[face_idx].v_axis); + bsp->texinfo[face_idx].u_offset = 0.0f; + bsp->texinfo[face_idx].v_offset = 0.0f; + + 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; + } + + vert_idx += 4; + edge_idx += 4; + face_idx++; + } + + if (cave_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].texinfo_id = face_idx; + + calculate_texture_axes(bsp->planes[face_idx].normal, + &bsp->texinfo[face_idx].u_axis, + &bsp->texinfo[face_idx].v_axis); + bsp->texinfo[face_idx].u_offset = 0.0f; + bsp->texinfo[face_idx].v_offset = 0.0f; + + 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; + } + + vert_idx += 4; + edge_idx += 4; + face_idx++; + } + + if (cave_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].texinfo_id = face_idx; + + calculate_texture_axes(bsp->planes[face_idx].normal, + &bsp->texinfo[face_idx].u_axis, + &bsp->texinfo[face_idx].v_axis); + bsp->texinfo[face_idx].u_offset = 0.0f; + bsp->texinfo[face_idx].v_offset = 0.0f; + + 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; + } + + 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 (cave_grid_get(grid, x, y) == 0) { + f32 fx = (f32)x * cell_size; + f32 fy = (f32)y * cell_size; + + 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].texinfo_id = face_idx; + + calculate_texture_axes(bsp->planes[face_idx].normal, + &bsp->texinfo[face_idx].u_axis, + &bsp->texinfo[face_idx].v_axis); + bsp->texinfo[face_idx].u_offset = 0.0f; + bsp->texinfo[face_idx].v_offset = 0.0f; + + 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; + } + + vert_idx += 4; + edge_idx += 4; + face_idx++; + + bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, wall_height, fy}; + bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx + cell_size, wall_height, fy}; + bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy + cell_size}; + bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx, wall_height, fy + cell_size}; + + bsp->planes[face_idx].normal = (pxl8_vec3){0, -1, 0}; + bsp->planes[face_idx].dist = -wall_height; + + bsp->faces[face_idx].plane_id = face_idx; + bsp->faces[face_idx].num_edges = 4; + bsp->faces[face_idx].first_edge = edge_idx; + bsp->faces[face_idx].texinfo_id = face_idx; + + calculate_texture_axes(bsp->planes[face_idx].normal, + &bsp->texinfo[face_idx].u_axis, + &bsp->texinfo[face_idx].v_axis); + bsp->texinfo[face_idx].u_offset = 0.0f; + bsp->texinfo[face_idx].v_offset = 0.0f; + + 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; + } + + vert_idx += 4; + edge_idx += 4; + face_idx++; + } + } + } + + bsp->num_vertices = vertex_count; + bsp->num_faces = face_count; + bsp->num_planes = face_count; + bsp->num_edges = vertex_count; + bsp->num_surfedges = vertex_count; + bsp->num_texinfo = face_count; + + bsp->leafs = calloc(1, sizeof(pxl8_bsp_leaf)); + bsp->marksurfaces = calloc(face_count, sizeof(u16)); + + if (!bsp->leafs || !bsp->marksurfaces) { + return PXL8_ERROR_OUT_OF_MEMORY; + } + + bsp->num_leafs = 1; + bsp->num_marksurfaces = face_count; + + bsp->leafs[0].first_marksurface = 0; + bsp->leafs[0].num_marksurfaces = face_count; + bsp->leafs[0].contents = -2; + + for (i32 i = 0; i < face_count; i++) { + bsp->marksurfaces[i] = i; + } + + return PXL8_OK; +} + +static pxl8_result procgen_cave(pxl8_bsp* bsp, const pxl8_procgen_params* params) { + prng_seed(params->seed); + + cave_grid* grid = cave_grid_create(params->width, params->height); + if (!grid) { + return PXL8_ERROR_OUT_OF_MEMORY; + } + + cave_grid_initialize(grid, params->density); + + for (i32 i = 0; i < params->iterations; i++) { + cave_grid_smooth(grid); + } + + pxl8_result result = cave_to_bsp(bsp, grid); + cave_grid_destroy(grid); + + 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_CAVE: + return procgen_cave(bsp, params); + + case PXL8_PROCGEN_DUNGEON: + pxl8_error("Dungeon generation not yet implemented"); + return PXL8_ERROR_NOT_INITIALIZED; + + 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; + } +} + +void pxl8_procgen_tex(u8* buffer, const pxl8_procgen_tex_params* params) { + if (!buffer || !params) return; + + prng_seed(params->seed); + + u8 min_val = 255, max_val = 0; + + for (i32 y = 0; y < params->height; y++) { + for (i32 x = 0; x < params->width; x++) { + i32 ix = (i32)((f32)x * params->scale); + i32 iy = (i32)((f32)y * params->scale); + + u32 block_hash = (ix * 374761393 + iy * 668265263) ^ params->seed; + block_hash ^= block_hash >> 13; + block_hash ^= block_hash << 17; + block_hash ^= block_hash >> 5; + + u32 pixel_hash = (x * 1597334677 + y * 3812015801) ^ params->seed; + pixel_hash ^= pixel_hash >> 13; + pixel_hash ^= pixel_hash << 17; + pixel_hash ^= pixel_hash >> 5; + + u32 combined = (block_hash * 3 + pixel_hash) / 4; + + u32 value_range = params->variation + 1; + i32 value = params->base_color + (combined % value_range); + + if (value < 0) value = 0; + if (value > 15) value = 15; + + u8 final_value = (u8)value; + buffer[y * params->width + x] = final_value; + + if (final_value < min_val) min_val = final_value; + if (final_value > max_val) max_val = final_value; + } + } + + pxl8_debug("Generated texture %dx%d: values range %u-%u (base=%u, variation=%u)", + params->width, params->height, min_val, max_val, + params->base_color, params->variation); +} diff --git a/src/pxl8_procgen.h b/src/pxl8_procgen.h new file mode 100644 index 0000000..9ee1515 --- /dev/null +++ b/src/pxl8_procgen.h @@ -0,0 +1,56 @@ +#pragma once + +#include "pxl8_bsp.h" +#include "pxl8_types.h" + +typedef enum pxl8_procgen_type { + PXL8_PROCGEN_CAVE, + PXL8_PROCGEN_DUNGEON, + PXL8_PROCGEN_TERRAIN +} pxl8_procgen_type; + +typedef struct pxl8_procgen_params { + pxl8_procgen_type type; + + i32 width; + i32 height; + i32 depth; + u32 seed; + + f32 density; + i32 iterations; + + void* type_params; +} pxl8_procgen_params; + +typedef struct pxl8_procgen_cave_params { + i32 min_cave_size; +} pxl8_procgen_cave_params; + +typedef struct pxl8_procgen_dungeon_params { + i32 room_count; + i32 min_room_size; + i32 max_room_size; + i32 corridor_width; +} pxl8_procgen_dungeon_params; + +typedef struct pxl8_procgen_tex_params { + u32 seed; + i32 width; + i32 height; + f32 scale; + f32 roughness; + u8 base_color; + u8 variation; +} pxl8_procgen_tex_params; + +#ifdef __cplusplus +extern "C" { +#endif + +pxl8_result pxl8_procgen(pxl8_bsp* bsp, const pxl8_procgen_params* params); +void pxl8_procgen_tex(u8* buffer, const pxl8_procgen_tex_params* params); + +#ifdef __cplusplus +} +#endif diff --git a/src/pxl8_script.c b/src/pxl8_script.c index 92d40e8..3192ea8 100644 --- a/src/pxl8_script.c +++ b/src/pxl8_script.c @@ -208,13 +208,31 @@ 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 enum pxl8_procgen_type {\n" +" PXL8_PROCGEN_CAVE = 0,\n" +" PXL8_PROCGEN_DUNGEON = 1,\n" +" PXL8_PROCGEN_TERRAIN = 2\n" +"} pxl8_procgen_type;\n" +"\n" +"typedef struct pxl8_procgen_params {\n" +" pxl8_procgen_type type;\n" +" int width;\n" +" int height;\n" +" int depth;\n" +" unsigned int seed;\n" +" float density;\n" +" int iterations;\n" +" void* type_params;\n" +"} pxl8_procgen_params;\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_generate(pxl8_world* world, const pxl8_procgen_params* params);\n" +"bool pxl8_world_is_loaded(const 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" diff --git a/src/pxl8_sdl3.c b/src/pxl8_sdl3.c index 4c0317a..6a72f23 100644 --- a/src/pxl8_sdl3.c +++ b/src/pxl8_sdl3.c @@ -294,9 +294,11 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) { break; case SDL_EVENT_KEY_DOWN: { +#ifdef DEBUG if (event->key.key == SDLK_ESCAPE) { game->running = false; } +#endif SDL_Scancode scancode = event->key.scancode; if (scancode < 256) { diff --git a/src/pxl8_world.c b/src/pxl8_world.c index 7ccb9e5..dd0ca41 100644 --- a/src/pxl8_world.c +++ b/src/pxl8_world.c @@ -3,11 +3,13 @@ #include "pxl8_bsp.h" #include "pxl8_macros.h" +#include "pxl8_procgen.h" #include "pxl8_world.h" struct pxl8_world { pxl8_bsp bsp; bool loaded; + bool wireframe; u32 wireframe_color; }; @@ -34,6 +36,29 @@ void pxl8_world_destroy(pxl8_world* world) { free(world); } +pxl8_result pxl8_world_generate(pxl8_world* world, const pxl8_procgen_params* params) { + if (!world || !params) 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_procgen(&world->bsp, params); + if (result != PXL8_OK) { + pxl8_error("Failed to generate world"); + pxl8_bsp_destroy(&world->bsp); + return result; + } + + world->loaded = true; + pxl8_info("Generated world"); + + return PXL8_OK; +} + pxl8_result pxl8_world_load(pxl8_world* world, const char* path) { if (!world || !path) return PXL8_ERROR_INVALID_ARGUMENT; @@ -59,7 +84,39 @@ 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) { if (!world || !gfx || !world->loaded) return; - pxl8_bsp_render_wireframe(gfx, &world->bsp, camera_pos, world->wireframe_color); + static bool texture_generated = false; + static u32 wall_texture_id = 0; + if (!texture_generated) { + u8 texture_data[64 * 64]; + + pxl8_procgen_tex_params tex_params = { + .seed = 12345, + .width = 64, + .height = 64, + .scale = 0.25f, + .roughness = 0.5f, + .base_color = 1, + .variation = 3 + }; + + pxl8_procgen_tex(texture_data, &tex_params); + + pxl8_result result = pxl8_gfx_create_texture(gfx, texture_data, 64, 64); + if (result >= 0) { + wall_texture_id = (u32)result; + pxl8_gfx_upload_atlas(gfx); + texture_generated = true; + pxl8_info("Generated stone texture with ID: %u", wall_texture_id); + } else { + pxl8_error("Failed to create wall texture: %d", result); + } + } + + if (world->wireframe) { + pxl8_bsp_render_wireframe(gfx, &world->bsp, camera_pos, world->wireframe_color); + } else { + pxl8_bsp_render_solid(gfx, &world->bsp, camera_pos, wall_texture_id); + } } void pxl8_world_unload(pxl8_world* world) { diff --git a/src/pxl8_world.h b/src/pxl8_world.h index 3743be2..15289c1 100644 --- a/src/pxl8_world.h +++ b/src/pxl8_world.h @@ -2,6 +2,7 @@ #include "pxl8_gfx.h" #include "pxl8_math.h" +#include "pxl8_procgen.h" #include "pxl8_types.h" typedef struct pxl8_world pxl8_world; @@ -13,12 +14,12 @@ extern "C" { pxl8_world* pxl8_world_create(void); void pxl8_world_destroy(pxl8_world* world); +pxl8_result pxl8_world_generate(pxl8_world* world, const pxl8_procgen_params* params); +bool pxl8_world_is_loaded(const 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