wip procgen

This commit is contained in:
asrael 2025-11-09 06:30:17 -06:00
parent a653eae745
commit 79a678f162
No known key found for this signature in database
GPG key ID: 2786557804DFAE24
18 changed files with 1317 additions and 127 deletions

View file

@ -1,9 +1,9 @@
(local pxl8 (require :pxl8)) (local pxl8 (require :pxl8))
(local bsp_world (require :mod.bsp_world))
(local cube3d (require :mod.cube3d)) (local cube3d (require :mod.cube3d))
(local worldgen (require :mod.worldgen))
(var time 0) (var time 0)
(var current-effect 1) (var active-demo :logo)
(var particles nil) (var particles nil)
(var fire-init false) (var fire-init false)
(var rain-init false) (var rain-init false)
@ -17,77 +17,68 @@
(var logo-sprite nil) (var logo-sprite nil)
(global init (fn [] (global init (fn []
(bsp_world.init)
(cube3d.init) (cube3d.init)
(worldgen.init)
(pxl8.load_palette "res/sprites/pxl8_logo.ase") (pxl8.load_palette "res/sprites/pxl8_logo.ase")
(set logo-sprite (pxl8.load_sprite "res/sprites/pxl8_logo.ase")) (set logo-sprite (pxl8.load_sprite "res/sprites/pxl8_logo.ase"))
(set particles (pxl8.particles_new 1000)))) (set particles (pxl8.particles_new 1000))))
(global update (fn [dt] (global update (fn [dt]
(bsp_world.update dt)
(set time (+ time dt)) (set time (+ time dt))
(when (pxl8.key_pressed "1") (when (pxl8.key_pressed "1") (set active-demo :logo))
(set current-effect 1)) (when (pxl8.key_pressed "2") (set active-demo :plasma))
(when (pxl8.key_pressed "2") (when (pxl8.key_pressed "3") (set active-demo :tunnel))
(set current-effect 2)) (when (pxl8.key_pressed "4") (set active-demo :raster))
(when (pxl8.key_pressed "3")
(set current-effect 3))
(when (pxl8.key_pressed "4")
(set current-effect 4))
(when (pxl8.key_pressed "5") (when (pxl8.key_pressed "5")
(set current-effect 5) (set active-demo :fire)
(set fire-init false)) (set fire-init false))
(when (pxl8.key_pressed "6") (when (pxl8.key_pressed "6")
(set current-effect 6) (set active-demo :rain)
(set rain-init false)) (set rain-init false))
(when (pxl8.key_pressed "7") (when (pxl8.key_pressed "7")
(set current-effect 7) (set active-demo :snow)
(set snow-init false)) (set snow-init false))
(when (pxl8.key_pressed "8") (when (pxl8.key_pressed "8") (set active-demo :cube3d))
(set current-effect 8)) (when (pxl8.key_pressed "9") (set active-demo :worldgen))
(when (pxl8.key_pressed "9") (when (pxl8.key_pressed "=")
(set current-effect 0))
(when (pxl8.key_pressed "0")
(set use-famicube-palette (not use-famicube-palette)) (set use-famicube-palette (not use-famicube-palette))
(local palette-path (if use-famicube-palette "res/palettes/famicube.ase" "res/sprites/pxl8_logo.ase")) (local palette-path (if use-famicube-palette "res/palettes/famicube.ase" "res/sprites/pxl8_logo.ase"))
(pxl8.load_palette palette-path)) (pxl8.load_palette palette-path))
(case current-effect (case active-demo
1 (do :logo (do
(set logo-x (+ logo-x (* logo-dx dt))) (set logo-x (+ logo-x (* logo-dx dt)))
(set logo-y (+ logo-y (* logo-dy dt))) (set logo-y (+ logo-y (* logo-dy dt)))
(when (or (< logo-x 0) (> logo-x 512)) (when (or (< logo-x 0) (> logo-x 512))
(set logo-dx (- logo-dx))) (set logo-dx (- logo-dx)))
(when (or (< logo-y 0) (> logo-y 296)) (when (or (< logo-y 0) (> logo-y 296))
(set logo-dy (- logo-dy)))) (set logo-dy (- logo-dy))))
8 (cube3d.update dt) :cube3d (cube3d.update dt)
_ nil) :worldgen (worldgen.update dt))
(when particles (when particles
(pxl8.particles_update particles dt)))) (pxl8.particles_update particles dt))))
(global frame (fn [] (global frame (fn []
(case current-effect (case active-demo
0 (bsp_world.frame) :logo (do
1 (do
(pxl8.clr 0) (pxl8.clr 0)
(when logo-sprite (when logo-sprite
(pxl8.sprite logo-sprite logo-x logo-y 128 64))) (pxl8.sprite logo-sprite logo-x logo-y 128 64)))
2 (pxl8.vfx_plasma time 0.10 0.04 1) :plasma (pxl8.vfx_plasma time 0.10 0.04 1)
3 (pxl8.vfx_tunnel time 2.0 0.25) :tunnel (pxl8.vfx_tunnel time 2.0 0.25)
4 (do :raster (do
(pxl8.clr 0) (pxl8.clr 0)
(local bars [{:base_y 60 :amplitude 30 :height 16 :speed 2.0 :phase 0 :color 1 :fade_color 18} (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 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}]) {:base_y 300 :amplitude 25 :height 16 :speed 2.2 :phase 4.0 :color 1 :fade_color 24}])
(pxl8.vfx_raster_bars bars time)) (pxl8.vfx_raster_bars bars time))
5 (do :fire (do
(pxl8.clr 0) (pxl8.clr 0)
(when particles (when particles
(when (not fire-init) (when (not fire-init)
@ -96,7 +87,7 @@
(set fire-init true)) (set fire-init true))
(pxl8.particles_render particles))) (pxl8.particles_render particles)))
6 (do :rain (do
(pxl8.clr 0) (pxl8.clr 0)
(when particles (when particles
(when (not rain-init) (when (not rain-init)
@ -105,7 +96,7 @@
(set rain-init true)) (set rain-init true))
(pxl8.particles_render particles))) (pxl8.particles_render particles)))
7 (do :snow (do
(pxl8.clr 0) (pxl8.clr 0)
(when particles (when particles
(when (not snow-init) (when (not snow-init)
@ -114,6 +105,8 @@
(set snow-init true)) (set snow-init true))
(pxl8.particles_render particles))) (pxl8.particles_render particles)))
8 (cube3d.frame) :cube3d (cube3d.frame)
:worldgen (worldgen.frame)
_ (pxl8.clr 0)))) _ (pxl8.clr 0))))

151
demo/mod/worldgen.fnl Normal file
View file

@ -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}

View file

@ -459,4 +459,21 @@ function pxl8.world_is_loaded(world)
return C.pxl8_world_is_loaded(world) return C.pxl8_world_is_loaded(world)
end 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 return pxl8

View file

@ -147,6 +147,17 @@ pxl8_game_result pxl8_update(pxl8_game* game) {
game->last_time = current_time; game->last_time = current_time;
game->time += dt; 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); pxl8_script_check_reload(game->script);
if (game->repl_mode && !game->repl_started) { if (game->repl_mode && !game->repl_started) {

View file

@ -1,3 +1,4 @@
#include <math.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -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; 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; if (!gfx || !bsp || face_id >= bsp->num_faces) return;
const pxl8_bsp_face* face = &bsp->faces[face_id]; 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]; pxl8_vec3 verts[64];
u32 num_verts = 0; 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++) { for (u32 i = 0; i < face->num_edges && num_verts < 64; i++) {
i32 surfedge_idx = face->first_edge + i; i32 surfedge_idx = face->first_edge + i;
if (surfedge_idx >= (i32)bsp->num_surfedges) continue; if (surfedge_idx >= (i32)bsp->num_surfedges) continue;
@ -362,10 +416,76 @@ void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 c
if (num_verts < 3) return; if (num_verts < 3) return;
if (use_texture && face->texinfo_id < bsp->num_texinfo) {
const pxl8_bsp_texinfo* texinfo = &bsp->texinfo[face->texinfo_id];
f32 tex_scale = 64.0f;
for (u32 tri_idx = 1; tri_idx < num_verts - 1; tri_idx++) {
pxl8_vec3 v0 = verts[0];
pxl8_vec3 v1 = verts[tri_idx];
pxl8_vec3 v2 = verts[tri_idx + 1];
f32 u0 = (pxl8_vec3_dot(v0, texinfo->u_axis) + texinfo->u_offset) / tex_scale;
f32 v0_uv = (pxl8_vec3_dot(v0, texinfo->v_axis) + texinfo->v_offset) / tex_scale;
f32 u1 = (pxl8_vec3_dot(v1, texinfo->u_axis) + texinfo->u_offset) / tex_scale;
f32 v1_uv = (pxl8_vec3_dot(v1, texinfo->v_axis) + texinfo->v_offset) / tex_scale;
f32 u2 = (pxl8_vec3_dot(v2, texinfo->u_axis) + texinfo->u_offset) / tex_scale;
f32 v2_uv = (pxl8_vec3_dot(v2, texinfo->v_axis) + texinfo->v_offset) / tex_scale;
pxl8_3d_draw_triangle_textured(gfx, v0, v1, v2, u0, v0_uv, u1, v1_uv, u2, v2_uv, texture_id);
}
} else{
for (u32 i = 1; i < num_verts - 1; i++) { for (u32 i = 1; i < num_verts - 1; i++) {
pxl8_3d_draw_triangle_raw(gfx, verts[0], verts[i], verts[i + 1], color); 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( void pxl8_bsp_render_wireframe(
pxl8_gfx* gfx, pxl8_gfx* gfx,

View file

@ -115,7 +115,14 @@ void pxl8_bsp_render_face(
pxl8_gfx* gfx, pxl8_gfx* gfx,
const pxl8_bsp* bsp, const pxl8_bsp* bsp,
u32 face_id, 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( void pxl8_bsp_render_wireframe(

View file

@ -27,6 +27,10 @@ typedef struct pxl8_game {
u64 last_time; u64 last_time;
f32 time; f32 time;
f32 fps_accumulator;
i32 fps_frame_count;
f32 fps;
bool repl_mode; bool repl_mode;
bool repl_started; bool repl_started;
bool running; bool running;

View file

@ -42,12 +42,18 @@ struct pxl8_gfx {
pxl8_mat4 model; pxl8_mat4 model;
pxl8_mat4 projection; pxl8_mat4 projection;
pxl8_mat4 view; pxl8_mat4 view;
pxl8_mat4 mvp;
bool mvp_dirty;
pxl8_frustum frustum;
bool wireframe; bool wireframe;
f32* zbuffer; f32* zbuffer;
i32 zbuffer_height; i32 zbuffer_height;
i32 zbuffer_width; i32 zbuffer_width;
bool affine_textures; 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) { 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->model = pxl8_mat4_identity();
gfx->projection = pxl8_mat4_identity(); gfx->projection = pxl8_mat4_identity();
gfx->view = pxl8_mat4_identity(); gfx->view = pxl8_mat4_identity();
gfx->mvp_dirty = true;
gfx->wireframe = false; gfx->wireframe = false;
gfx->zbuffer = NULL; gfx->zbuffer = NULL;
gfx->zbuffer_height = 0; gfx->zbuffer_height = 0;
@ -404,6 +411,19 @@ void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom) {
void pxl8_clr(pxl8_gfx* gfx, u32 color) { void pxl8_clr(pxl8_gfx* gfx, u32 color) {
if (!gfx || !gfx->framebuffer) return; 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 bytes_per_pixel = (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1;
i32 size = gfx->framebuffer_width * gfx->framebuffer_height; i32 size = gfx->framebuffer_width * gfx->framebuffer_height;
@ -786,9 +806,33 @@ static bool pxl8_3d_init_zbuffer(pxl8_gfx* gfx) {
void pxl8_3d_clear_zbuffer(pxl8_gfx* gfx) { void pxl8_3d_clear_zbuffer(pxl8_gfx* gfx) {
if (!gfx || !gfx->zbuffer) return; 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) { 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) { void pxl8_3d_set_model(pxl8_gfx* gfx, pxl8_mat4 mat) {
if (!gfx) return; if (!gfx) return;
gfx->model = mat; gfx->model = mat;
gfx->mvp_dirty = true;
} }
void pxl8_3d_set_projection(pxl8_gfx* gfx, pxl8_mat4 mat) { void pxl8_3d_set_projection(pxl8_gfx* gfx, pxl8_mat4 mat) {
if (!gfx) return; if (!gfx) return;
gfx->projection = mat; gfx->projection = mat;
gfx->mvp_dirty = true;
} }
void pxl8_3d_set_view(pxl8_gfx* gfx, pxl8_mat4 mat) { void pxl8_3d_set_view(pxl8_gfx* gfx, pxl8_mat4 mat) {
if (!gfx) return; if (!gfx) return;
gfx->view = mat; gfx->view = mat;
gfx->mvp_dirty = true;
} }
void pxl8_3d_set_wireframe(pxl8_gfx* gfx, bool wireframe) { 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; 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) { static inline pxl8_vec4 pxl8_transform_vertex(pxl8_gfx* gfx, pxl8_vec3 pos) {
pxl8_update_mvp(gfx);
pxl8_vec4 v = { pxl8_vec4 v = {
.x = pos.x, .x = pos.x,
.y = pos.y, .y = pos.y,
@ -829,10 +896,7 @@ static inline pxl8_vec4 pxl8_transform_vertex(pxl8_gfx* gfx, pxl8_vec3 pos) {
.w = 1.0f, .w = 1.0f,
}; };
pxl8_mat4 mvp = pxl8_mat4_multiply(gfx->projection, return pxl8_mat4_multiply_vec4(gfx->mvp, v);
pxl8_mat4_multiply(gfx->view, gfx->model));
return pxl8_mat4_multiply_vec4(mvp, v);
} }
static inline void pxl8_project_to_screen(pxl8_gfx* gfx, pxl8_vec4 clip, i32* x, i32* y, f32* z) { 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); 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 (y < 0 || y >= gfx->framebuffer_height) return;
if (xs > xe) { if (xs > xe) {
i32 tmp = xs; xs = xe; xe = tmp; 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;
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++) { for (i32 x = xs; x <= xe; x++) {
if (x >= 0 && x < gfx->framebuffer_width) { i32 idx = zbuf_offset + x;
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]) { if (z <= gfx->zbuffer[idx]) {
gfx->zbuffer[idx] = z; gfx->zbuffer[idx] = z;
pxl8_pixel(gfx, x, y, color); 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; tmpf = w0; w0 = w1; w1 = tmpf;
} }
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 && (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++) { for (i32 x = xs; x <= xe; x++) {
if (x >= 0 && x < gfx->framebuffer_width) { 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; i32 idx = y * gfx->zbuffer_width + x;
if (z <= gfx->zbuffer[idx]) { if (z <= gfx->zbuffer[idx]) {
f32 u = u0 + t * (u1 - u0); gfx->frame_pixel_count++;
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;
}
}
u32 color = pxl8_sample_texture(gfx, texture_id, u, v); u32 color = pxl8_sample_texture(gfx, texture_id, u, v);
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) { if ((gfx->color_mode == PXL8_COLOR_MODE_HICOLOR && (color & 0xFF000000)) ||
if (color & 0xFF000000) { (gfx->color_mode != PXL8_COLOR_MODE_HICOLOR && color != 0)) {
gfx->zbuffer[idx] = z; gfx->zbuffer[idx] = z;
pxl8_pixel(gfx, x, y, color); pxl8_pixel(gfx, x, y, color);
} }
}
}
z += dz;
u += du;
v += dv;
}
} else { } else {
if (color != 0) { 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; gfx->zbuffer[idx] = z;
pxl8_pixel(gfx, x, y, color); 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( static void pxl8_draw_flat_bottom_triangle(
pxl8_gfx* gfx, pxl8_gfx* gfx,
i32 x0, i32 y0, f32 z0, i32 x0, i32 y0, f32 z0,
i32 x1, i32 y1, f32 z1, i32 x1, i32 y1, f32 z1,
i32 x2, i32 y2, f32 z2, i32 x2, i32 y2, f32 z2,
u32 color u32 color,
pxl8_scanline_func fill_scanline
) { ) {
(void)z2; (void)z2;
if (y1 == y0) return; if (y1 == y0) return;
@ -979,7 +1172,7 @@ static void pxl8_draw_flat_bottom_triangle(
f32 cur_x2 = (f32)x0; f32 cur_x2 = (f32)x0;
for (i32 y = y0; y <= y1; y++) { 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_x1 += inv_slope_1;
cur_x2 += inv_slope_2; cur_x2 += inv_slope_2;
} }
@ -990,7 +1183,8 @@ static void pxl8_draw_flat_top_triangle(
i32 x0, i32 y0, f32 z0, i32 x0, i32 y0, f32 z0,
i32 x1, i32 y1, f32 z1, i32 x1, i32 y1, f32 z1,
i32 x2, i32 y2, f32 z2, i32 x2, i32 y2, f32 z2,
u32 color u32 color,
pxl8_scanline_func fill_scanline
) { ) {
(void)z2; (void)z2;
if (y2 == y0) return; if (y2 == y0) return;
@ -1002,7 +1196,7 @@ static void pxl8_draw_flat_top_triangle(
f32 cur_x2 = (f32)x2; f32 cur_x2 = (f32)x2;
for (i32 y = y2; y > y0; y--) { 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_x1 -= inv_slope_1;
cur_x2 -= inv_slope_2; 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) { void pxl8_3d_draw_triangle(pxl8_gfx* gfx, pxl8_triangle tri) {
if (!gfx || !pxl8_3d_init_zbuffer(gfx)) return; 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 v0 = pxl8_transform_vertex(gfx, tri.v[0].position);
pxl8_vec4 v1 = pxl8_transform_vertex(gfx, tri.v[1].position); pxl8_vec4 v1 = pxl8_transform_vertex(gfx, tri.v[1].position);
pxl8_vec4 v2 = pxl8_transform_vertex(gfx, tri.v[2].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; 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) { 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) { } 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 { } else {
i32 x3 = x0 + (i32)(((f32)(y1 - y0) / (f32)(y2 - y0)) * (x2 - x0)); i32 x3 = x0 + (i32)(((f32)(y1 - y0) / (f32)(y2 - y0)) * (x2 - x0));
i32 y3 = y1; i32 y3 = y1;
f32 z3 = z0 + ((f32)(y1 - y0) / (f32)(y2 - y0)) * (z2 - z0); 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_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); pxl8_draw_flat_top_triangle(gfx, x1, y1, z1, x3, y3, z3, x2, y2, z2, color, fill_scanline);
} }
} }

View file

@ -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(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_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); 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_affine_textures(pxl8_gfx* gfx, bool affine);
void pxl8_3d_set_backface_culling(pxl8_gfx* gfx, bool culling); void pxl8_3d_set_backface_culling(pxl8_gfx* gfx, bool culling);
void pxl8_3d_set_model(pxl8_gfx* gfx, pxl8_mat4 mat); void pxl8_3d_set_model(pxl8_gfx* gfx, pxl8_mat4 mat);

View file

@ -115,6 +115,7 @@ static i32 pxl8_key_code(const char* key_name) {
{"1", 30}, {"2", 31}, {"3", 32}, {"4", 33}, {"5", 34}, {"1", 30}, {"2", 31}, {"3", 32}, {"4", 33}, {"5", 34},
{"6", 35}, {"7", 36}, {"8", 37}, {"9", 38}, {"0", 39}, {"6", 35}, {"7", 36}, {"8", 37}, {"9", 38}, {"0", 39},
{"return", 40}, {"escape", 41}, {"backspace", 42}, {"tab", 43}, {"space", 44}, {"return", 40}, {"escape", 41}, {"backspace", 42}, {"tab", 43}, {"space", 44},
{"-", 45}, {"=", 46},
{"left", 80}, {"right", 79}, {"up", 82}, {"down", 81}, {"left", 80}, {"right", 79}, {"up", 82}, {"down", 81},
{"f1", 58}, {"f2", 59}, {"f3", 60}, {"f4", 61}, {"f5", 62}, {"f6", 63}, {"f1", 58}, {"f2", 59}, {"f3", 60}, {"f4", 61}, {"f5", 62}, {"f6", 63},
{"f7", 64}, {"f8", 65}, {"f9", 66}, {"f10", 67}, {"f11", 68}, {"f12", 69}, {"f7", 64}, {"f8", 65}, {"f9", 66}, {"f10", 67}, {"f11", 68}, {"f12", 69},

View file

@ -250,3 +250,70 @@ pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up) {
return mat; 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;
}

View file

@ -18,6 +18,15 @@ typedef struct pxl8_mat4 {
f32 m[16]; f32 m[16];
} pxl8_mat4; } 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 #ifdef __cplusplus
extern "C" { extern "C" {
#endif #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_scale(f32 x, f32 y, f32 z);
pxl8_mat4 pxl8_mat4_translate(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 #ifdef __cplusplus
} }
#endif #endif

472
src/pxl8_procgen.c Normal file
View file

@ -0,0 +1,472 @@
#include "pxl8_procgen.h"
#include "pxl8_macros.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>
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);
}

56
src/pxl8_procgen.h Normal file
View file

@ -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

View file

@ -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_scale(float x, float y, float z);\n"
"pxl8_mat4 pxl8_mat4_translate(float x, float y, float z);\n" "pxl8_mat4 pxl8_mat4_translate(float x, float y, float z);\n"
"\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" "typedef struct pxl8_world pxl8_world;\n"
"pxl8_world* pxl8_world_create(void);\n" "pxl8_world* pxl8_world_create(void);\n"
"void pxl8_world_destroy(pxl8_world* world);\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" "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_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);\n"
"void pxl8_world_unload(pxl8_world* world);\n" "void pxl8_world_unload(pxl8_world* world);\n"
"bool pxl8_world_is_loaded(const pxl8_world* world);\n"
"\n" "\n"
"typedef struct pxl8_ui pxl8_ui;\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 { unsigned char bg_color; unsigned int sprite_id; int corner_size; int edge_size; int padding; } pxl8_frame_theme;\n"

View file

@ -294,9 +294,11 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) {
break; break;
case SDL_EVENT_KEY_DOWN: { case SDL_EVENT_KEY_DOWN: {
#ifdef DEBUG
if (event->key.key == SDLK_ESCAPE) { if (event->key.key == SDLK_ESCAPE) {
game->running = false; game->running = false;
} }
#endif
SDL_Scancode scancode = event->key.scancode; SDL_Scancode scancode = event->key.scancode;
if (scancode < 256) { if (scancode < 256) {

View file

@ -3,11 +3,13 @@
#include "pxl8_bsp.h" #include "pxl8_bsp.h"
#include "pxl8_macros.h" #include "pxl8_macros.h"
#include "pxl8_procgen.h"
#include "pxl8_world.h" #include "pxl8_world.h"
struct pxl8_world { struct pxl8_world {
pxl8_bsp bsp; pxl8_bsp bsp;
bool loaded; bool loaded;
bool wireframe;
u32 wireframe_color; u32 wireframe_color;
}; };
@ -34,6 +36,29 @@ void pxl8_world_destroy(pxl8_world* world) {
free(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) { pxl8_result pxl8_world_load(pxl8_world* world, const char* path) {
if (!world || !path) return PXL8_ERROR_INVALID_ARGUMENT; 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) { void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) {
if (!world || !gfx || !world->loaded) return; if (!world || !gfx || !world->loaded) return;
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); 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) { void pxl8_world_unload(pxl8_world* world) {

View file

@ -2,6 +2,7 @@
#include "pxl8_gfx.h" #include "pxl8_gfx.h"
#include "pxl8_math.h" #include "pxl8_math.h"
#include "pxl8_procgen.h"
#include "pxl8_types.h" #include "pxl8_types.h"
typedef struct pxl8_world pxl8_world; typedef struct pxl8_world pxl8_world;
@ -13,12 +14,12 @@ extern "C" {
pxl8_world* pxl8_world_create(void); pxl8_world* pxl8_world_create(void);
void pxl8_world_destroy(pxl8_world* world); 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); 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_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);
void pxl8_world_unload(pxl8_world* world); void pxl8_world_unload(pxl8_world* world);
bool pxl8_world_is_loaded(const pxl8_world* world);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif