diff --git a/demo/main.fnl b/demo/main.fnl index 916b873..51543c1 100644 --- a/demo/main.fnl +++ b/demo/main.fnl @@ -39,6 +39,8 @@ (pxl8.transition_update transition dt) (when (pxl8.transition_is_complete transition) (when transition-pending + (when (and (= active-demo :worldgen) (not= transition-pending :worldgen)) + (pxl8.set_relative_mouse_mode false)) (set active-demo transition-pending) (set transition-pending nil) (when (= active-demo :fire) (set fire-init? false)) diff --git a/demo/mod/cube3d.fnl b/demo/mod/cube3d.fnl index 0c0aaaa..a6ec01e 100644 --- a/demo/mod/cube3d.fnl +++ b/demo/mod/cube3d.fnl @@ -5,7 +5,7 @@ (var angle-y 0) (var angle-z 0) (var auto-rotate? true) -(var orthographic? true) +(var orthographic? false) (var wireframe? true) (var time 0) (var zoom 5.0) @@ -27,7 +27,7 @@ (set angle-y 0) (set angle-z 0) (set auto-rotate? true) - (set orthographic? true) + (set orthographic? false) (set wireframe? true) (set time 0) (set zoom 5.0) @@ -169,12 +169,11 @@ (fn draw-cube [pos scale rotation-offset] (let [[x y z] pos - model (-> (pxl8.mat4_identity) - (pxl8.mat4_multiply (pxl8.mat4_scale scale scale scale)) - (pxl8.mat4_multiply (pxl8.mat4_rotate_x (+ angle-x rotation-offset))) - (pxl8.mat4_multiply (pxl8.mat4_rotate_y (+ angle-y (* rotation-offset 1.3)))) + model (-> (pxl8.mat4_translate x y z) (pxl8.mat4_multiply (pxl8.mat4_rotate_z (+ angle-z (* rotation-offset 0.7)))) - (pxl8.mat4_multiply (pxl8.mat4_translate x y z)))] + (pxl8.mat4_multiply (pxl8.mat4_rotate_y (+ angle-y (* rotation-offset 1.3)))) + (pxl8.mat4_multiply (pxl8.mat4_rotate_x (+ angle-x rotation-offset))) + (pxl8.mat4_multiply (pxl8.mat4_scale scale scale scale)))] (pxl8.set_model model)) (let [vertices (make-cube-vertices)] diff --git a/demo/mod/worldgen.fnl b/demo/mod/worldgen.fnl index d3be007..914e29b 100644 --- a/demo/mod/worldgen.fnl +++ b/demo/mod/worldgen.fnl @@ -3,21 +3,23 @@ (local bob-amount 4.0) (local bob-speed 8.0) (var bob-time 0) +(var cam-pitch 0) (var cam-x 1000) (var cam-y 64) -(var cam-z 1000) -(var cam-pitch 0) (var cam-yaw 0) +(var cam-z 1000) (local cell-size 64) (local gravity -800) (local grid-size 32) (var grounded? true) (local ground-y 64) (local jump-force 175) -(var land-squash 0) -(local land-squash-amount -4) (local land-recovery-speed 20) +(local land-squash-amount -4) +(var land-squash 0) (local max-pitch 1.5) +(var mouse-look? true) +(local mouse-sensitivity 0.008) (local move-speed 200) (local turn-speed 2.0) (var velocity-y 0) @@ -66,6 +68,11 @@ (pxl8.error (.. "Failed to apply textures - result: " result)))))))) (fn update [dt] + (pxl8.set_relative_mouse_mode mouse-look?) + + (when (pxl8.key_pressed "escape") + (set mouse-look? (not mouse-look?))) + (when (pxl8.world_is_loaded world) (let [forward-x (- (math.sin cam-yaw)) forward-z (- (math.cos cam-yaw)) @@ -84,10 +91,10 @@ (when (pxl8.key_down "s") (set move-forward (- move-forward 1))) - (when (pxl8.key_down "q") + (when (pxl8.key_down "a") (set move-right (- move-right 1))) - (when (pxl8.key_down "e") + (when (pxl8.key_down "d") (set move-right (+ move-right 1))) (set moving (or (not= move-forward 0) (not= move-right 0))) @@ -107,16 +114,24 @@ (set cam-x new-x) (set cam-z new-z)) - (when (or (pxl8.key_down "left") (pxl8.key_down "a")) + (when mouse-look? + (let [dx (pxl8.mouse_dx) + dy (pxl8.mouse_dy)] + (set cam-yaw (- cam-yaw (* dx mouse-sensitivity))) + (set cam-pitch (math.max (- max-pitch) + (math.min max-pitch + (- cam-pitch (* dy mouse-sensitivity))))))) + + (when (and (not mouse-look?) (pxl8.key_down "left")) (set cam-yaw (+ cam-yaw (* turn-speed dt)))) - (when (or (pxl8.key_down "right") (pxl8.key_down "d")) + (when (and (not mouse-look?) (pxl8.key_down "right")) (set cam-yaw (- cam-yaw (* turn-speed dt)))) - (when (pxl8.key_down "up") + (when (and (not mouse-look?) (pxl8.key_down "up")) (set cam-pitch (math.min max-pitch (+ cam-pitch (* turn-speed dt))))) - (when (pxl8.key_down "down") + (when (and (not mouse-look?) (pxl8.key_down "down")) (set cam-pitch (math.max (- max-pitch) (- cam-pitch (* turn-speed dt))))) (when (and (pxl8.key_pressed "space") grounded?) @@ -170,10 +185,10 @@ (pxl8.world_render world [cam-x eye-y cam-z]) - (pxl8.text (.. "Pos: " (string.format "%.0f" cam-x) "," + (pxl8.text (.. "fps: " (string.format "%.1f" (pxl8.get_fps))) 5 5 12) + (pxl8.text (.. "pos: " (string.format "%.0f" cam-x) "," (string.format "%.0f" cam-y) "," - (string.format "%.0f" cam-z)) 10 25 12) - (pxl8.text (.. "FPS: " (string.format "%.1f" (pxl8.get_fps))) 10 40 12)))) + (string.format "%.0f" cam-z)) 5 15 12)))) {:init init :update update diff --git a/src/lua/pxl8.lua b/src/lua/pxl8.lua index 3827340..3e6a97b 100644 --- a/src/lua/pxl8.lua +++ b/src/lua/pxl8.lua @@ -45,10 +45,13 @@ pxl8.gfx_fade_palette = gfx2d.fade_palette pxl8.key_down = input.key_down pxl8.key_pressed = input.key_pressed pxl8.key_released = input.key_released +pxl8.mouse_dx = input.mouse_dx +pxl8.mouse_dy = input.mouse_dy pxl8.mouse_wheel_x = input.mouse_wheel_x pxl8.mouse_wheel_y = input.mouse_wheel_y pxl8.mouse_x = input.mouse_x pxl8.mouse_y = input.mouse_y +pxl8.set_relative_mouse_mode = input.set_relative_mouse_mode pxl8.vfx_raster_bars = vfx.raster_bars pxl8.vfx_plasma = vfx.plasma diff --git a/src/lua/pxl8/input.lua b/src/lua/pxl8/input.lua index a9c9637..416c86b 100644 --- a/src/lua/pxl8/input.lua +++ b/src/lua/pxl8/input.lua @@ -32,4 +32,16 @@ function input.mouse_y() return C.pxl8_mouse_y(core.input) end +function input.mouse_dx() + return C.pxl8_mouse_dx(core.input) +end + +function input.mouse_dy() + return C.pxl8_mouse_dy(core.input) +end + +function input.set_relative_mouse_mode(enabled) + C.pxl8_set_relative_mouse_mode(core.sys, enabled) +end + return input diff --git a/src/pxl8.c b/src/pxl8.c index 8347b34..94cbf13 100644 --- a/src/pxl8.c +++ b/src/pxl8.c @@ -253,9 +253,6 @@ pxl8_result pxl8_update(pxl8* sys) { } if (game->ui) { - i32 render_width, render_height; - pxl8_gfx_get_resolution_dimensions(game->resolution, &render_width, &render_height); - pxl8_ui_input_mousemove(game->ui, game->input.mouse_x, game->input.mouse_y); if (game->input.mouse_wheel_x != 0 || game->input.mouse_wheel_y != 0) { @@ -306,25 +303,23 @@ pxl8_result pxl8_frame(pxl8* sys) { } else { pxl8_clear(game->gfx, 32); - i32 render_width, render_height; - pxl8_gfx_get_resolution_dimensions(game->resolution, &render_width, &render_height); + pxl8_size render_size = pxl8_get_resolution_dimensions(game->resolution); - for (i32 y = 0; y < render_height; y += 24) { - for (i32 x = 0; x < render_width; x += 32) { + for (i32 y = 0; y < render_size.h; y += 24) { + for (i32 x = 0; x < render_size.w; x += 32) { u32 color = ((x / 32) + (y / 24) + (i32)(game->time * 2)) % 8; pxl8_rect_fill(game->gfx, x, y, 31, 23, color); } } } - i32 render_width, render_height; - pxl8_gfx_get_resolution_dimensions(game->resolution, &render_width, &render_height); + pxl8_size render_size = pxl8_get_resolution_dimensions(game->resolution); if (game->ui) { pxl8_ui_frame_end(game->ui); } - pxl8_gfx_set_viewport(game->gfx, pxl8_gfx_viewport(bounds, render_width, render_height)); + pxl8_gfx_set_viewport(game->gfx, pxl8_gfx_viewport(bounds, render_size.w, render_size.h)); pxl8_gfx_upload_framebuffer(game->gfx); pxl8_gfx_upload_atlas(game->gfx); pxl8_gfx_present(game->gfx); @@ -333,8 +328,12 @@ pxl8_result pxl8_frame(pxl8* sys) { memset(game->input.keys_released, 0, sizeof(game->input.keys_released)); memset(game->input.mouse_buttons_pressed, 0, sizeof(game->input.mouse_buttons_pressed)); memset(game->input.mouse_buttons_released, 0, sizeof(game->input.mouse_buttons_released)); + game->input.mouse_dx = 0; + game->input.mouse_dy = 0; game->input.mouse_wheel_x = 0; game->input.mouse_wheel_y = 0; + game->input.mouse_x = 0; + game->input.mouse_y = 0; game->frame_count++; @@ -378,14 +377,46 @@ f32 pxl8_get_fps(const pxl8* sys) { return (sys && sys->game) ? sys->game->fps : 0.0f; } -pxl8_gfx* pxl8_get_gfx(pxl8* sys) { +pxl8_gfx* pxl8_get_gfx(const pxl8* sys) { return (sys && sys->game) ? sys->game->gfx : NULL; } -pxl8_input_state* pxl8_get_input(pxl8* sys) { +pxl8_input_state* pxl8_get_input(const pxl8* sys) { return (sys && sys->game) ? &sys->game->input : NULL; } -pxl8_resolution pxl8_get_resolution(pxl8* sys) { +pxl8_resolution pxl8_get_resolution(const pxl8* sys) { return (sys && sys->game) ? sys->game->resolution : PXL8_RESOLUTION_640x360; } + +void pxl8_set_relative_mouse_mode(pxl8* sys, bool enabled) { + if (!sys || !sys->hal || !sys->hal->set_relative_mouse_mode) return; + sys->hal->set_relative_mouse_mode(sys->platform_data, enabled); + if (sys->game) { + sys->game->input.mouse_relative_mode = enabled; + } +} + +u32 pxl8_get_palette_size(pxl8_color_mode mode) { + switch (mode) { + case PXL8_COLOR_MODE_HICOLOR: return 0; + case PXL8_COLOR_MODE_FAMI: return 64; + case PXL8_COLOR_MODE_MEGA: return 512; + case PXL8_COLOR_MODE_GBA: return 32768; + case PXL8_COLOR_MODE_SNES: return 32768; + default: return 256; + } +} + +pxl8_size pxl8_get_resolution_dimensions(pxl8_resolution resolution) { + switch (resolution) { + case PXL8_RESOLUTION_240x160: return (pxl8_size){240, 160}; + case PXL8_RESOLUTION_320x180: return (pxl8_size){320, 180}; + case PXL8_RESOLUTION_320x240: return (pxl8_size){320, 240}; + case PXL8_RESOLUTION_640x360: return (pxl8_size){640, 360}; + case PXL8_RESOLUTION_640x480: return (pxl8_size){640, 480}; + case PXL8_RESOLUTION_800x600: return (pxl8_size){800, 600}; + case PXL8_RESOLUTION_960x540: return (pxl8_size){960, 540}; + default: return (pxl8_size){640, 360}; + } +} diff --git a/src/pxl8_bsp.c b/src/pxl8_bsp.c index 2ad26e5..56d3e1b 100644 --- a/src/pxl8_bsp.c +++ b/src/pxl8_bsp.c @@ -55,6 +55,44 @@ static bool validate_chunk(const pxl8_bsp_chunk* chunk, u32 element_size, size_t return true; } +static inline bool pxl8_bsp_get_edge_vertex(const pxl8_bsp* bsp, i32 surfedge_idx, u32* out_vert_idx) { + if (surfedge_idx >= (i32)bsp->num_surfedges) return false; + + i32 edge_idx = bsp->surfedges[surfedge_idx]; + u32 vertex_index; + + if (edge_idx >= 0) { + if ((u32)edge_idx >= bsp->num_edges) return false; + vertex_index = 0; + } else { + edge_idx = -edge_idx; + if ((u32)edge_idx >= bsp->num_edges) return false; + vertex_index = 1; + } + + *out_vert_idx = bsp->edges[edge_idx].vertex[vertex_index]; + return *out_vert_idx < bsp->num_vertices; +} + +static inline bool pxl8_bsp_get_edge_vertices(const pxl8_bsp* bsp, i32 surfedge_idx, u32* out_v0_idx, u32* out_v1_idx) { + if (surfedge_idx >= (i32)bsp->num_surfedges) return false; + + i32 edge_idx = bsp->surfedges[surfedge_idx]; + + if (edge_idx >= 0) { + if ((u32)edge_idx >= bsp->num_edges) return false; + *out_v0_idx = bsp->edges[edge_idx].vertex[0]; + *out_v1_idx = bsp->edges[edge_idx].vertex[1]; + } else { + edge_idx = -edge_idx; + if ((u32)edge_idx >= bsp->num_edges) return false; + *out_v0_idx = bsp->edges[edge_idx].vertex[1]; + *out_v1_idx = bsp->edges[edge_idx].vertex[0]; + } + + return *out_v0_idx < bsp->num_vertices && *out_v1_idx < bsp->num_vertices; +} + pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) { if (!path || !bsp) return PXL8_ERROR_INVALID_ARGUMENT; @@ -280,21 +318,9 @@ pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) { for (u32 j = 0; j < face->num_edges; j++) { i32 surfedge_idx = face->first_edge + j; - 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; + if (!pxl8_bsp_get_edge_vertex(bsp, surfedge_idx, &vert_idx)) continue; pxl8_vec3 v = bsp->vertices[vert_idx].position; @@ -392,21 +418,10 @@ void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 t for (u32 i = 0; i < face->num_edges && num_verts < 64; i++) { i32 surfedge_idx = face->first_edge + i; - if (surfedge_idx >= (i32)bsp->num_surfedges) continue; - - i32 edge_idx = bsp->surfedges[surfedge_idx]; u32 vert_idx; - if (edge_idx >= 0) { - if ((u32)edge_idx >= bsp->num_edges) continue; - vert_idx = bsp->edges[edge_idx].vertex[0]; - } else { - edge_idx = -edge_idx; - if ((u32)edge_idx >= bsp->num_edges) continue; - vert_idx = bsp->edges[edge_idx].vertex[1]; - } + if (!pxl8_bsp_get_edge_vertex(bsp, surfedge_idx, &vert_idx)) continue; - if (vert_idx >= bsp->num_vertices) continue; verts[num_verts++] = bsp->vertices[vert_idx].position; } @@ -462,7 +477,9 @@ void pxl8_bsp_render_textured( 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; + if (camera_leaf >= 0 && !pxl8_bsp_is_leaf_visible(bsp, camera_leaf, leaf_id)) { + continue; + } const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id]; @@ -476,7 +493,9 @@ void pxl8_bsp_render_textured( if (rendered_faces[face_id]) continue; rendered_faces[face_id] = 1; - if (!face_in_frustum(bsp, face_id, frustum)) continue; + if (!face_in_frustum(bsp, face_id, frustum)) { + continue; + } const pxl8_bsp_face* face = &bsp->faces[face_id]; u32 texture_id = 0; @@ -494,6 +513,7 @@ void pxl8_bsp_render_textured( pxl8_bsp_render_face(gfx, bsp, face_id, texture_id); } } + } void pxl8_bsp_render_wireframe( @@ -532,21 +552,9 @@ void pxl8_bsp_render_wireframe( if (surfedge_idx >= (i32)bsp->num_surfedges || next_surfedge_idx >= (i32)bsp->num_surfedges) continue; - i32 edge_idx = bsp->surfedges[surfedge_idx]; u32 v0_idx, v1_idx; - if (edge_idx >= 0) { - if ((u32)edge_idx >= bsp->num_edges) continue; - v0_idx = bsp->edges[edge_idx].vertex[0]; - v1_idx = bsp->edges[edge_idx].vertex[1]; - } else { - edge_idx = -edge_idx; - if ((u32)edge_idx >= bsp->num_edges) continue; - v0_idx = bsp->edges[edge_idx].vertex[1]; - v1_idx = bsp->edges[edge_idx].vertex[0]; - } - - if (v0_idx >= bsp->num_vertices || v1_idx >= bsp->num_vertices) continue; + if (!pxl8_bsp_get_edge_vertices(bsp, surfedge_idx, &v0_idx, &v1_idx)) continue; pxl8_vec3 p0 = bsp->vertices[v0_idx].position; pxl8_vec3 p1 = bsp->vertices[v1_idx].position; diff --git a/src/pxl8_gfx.c b/src/pxl8_gfx.c index 3380d04..0fd4892 100644 --- a/src/pxl8_gfx.c +++ b/src/pxl8_gfx.c @@ -10,6 +10,7 @@ #include "pxl8_hal.h" #include "pxl8_macros.h" #include "pxl8_math.h" +#include "pxl8_sys.h" #include "pxl8_types.h" typedef struct pxl8_sprite_cache_entry { @@ -52,8 +53,6 @@ struct pxl8_gfx { 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) { @@ -71,38 +70,6 @@ static inline u8 pxl8_color_lerp_channel(u8 c1, u8 c2, f32 t) { return c1 + (i32)((c2 - c1) * t); } -static u32 pxl8_get_palette_size(pxl8_color_mode mode) { - switch (mode) { - case PXL8_COLOR_MODE_HICOLOR: return 0; - case PXL8_COLOR_MODE_FAMI: return 64; - case PXL8_COLOR_MODE_MEGA: return 512; - case PXL8_COLOR_MODE_GBA: return 32768; - case PXL8_COLOR_MODE_SNES: return 32768; - default: return 256; - } -} - -void pxl8_gfx_get_resolution_dimensions(pxl8_resolution resolution, i32* width, i32* height) { - switch (resolution) { - case PXL8_RESOLUTION_240x160: - *width = 240; *height = 160; break; - case PXL8_RESOLUTION_320x180: - *width = 320; *height = 180; break; - case PXL8_RESOLUTION_320x240: - *width = 320; *height = 240; break; - case PXL8_RESOLUTION_640x360: - *width = 640; *height = 360; break; - case PXL8_RESOLUTION_640x480: - *width = 640; *height = 480; break; - case PXL8_RESOLUTION_800x600: - *width = 800; *height = 600; break; - case PXL8_RESOLUTION_960x540: - *width = 960; *height = 540; break; - default: - *width = 640; *height = 360; break; - } -} - pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx) { pxl8_bounds bounds = {0}; if (!gfx) { @@ -149,11 +116,9 @@ pxl8_gfx* pxl8_gfx_create( gfx->platform_data = platform_data; gfx->color_mode = mode; - pxl8_gfx_get_resolution_dimensions( - resolution, - &gfx->framebuffer_width, - &gfx->framebuffer_height - ); + pxl8_size size = pxl8_get_resolution_dimensions(resolution); + gfx->framebuffer_width = size.w; + gfx->framebuffer_height = size.h; if (!gfx->platform_data) { pxl8_error("Platform data cannot be NULL"); @@ -404,18 +369,6 @@ void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom) { void pxl8_clear(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_trace("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; @@ -903,7 +856,7 @@ 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_hicolor(pxl8_gfx* gfx, i32 y, i32 xs, i32 xe, f32 z0, f32 z1, u32 color) { +static inline void pxl8_fill_scanline(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; @@ -915,109 +868,57 @@ static inline void pxl8_fill_scanline_hicolor(pxl8_gfx* gfx, i32 y, i32 xs, i32 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++) { - 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; - } -} - -static inline u32 pxl8_sample_texture(pxl8_gfx* gfx, u32 texture_id, f32 u, f32 v) { - if (!gfx->atlas) return 0; - - const pxl8_atlas_entry* entry = pxl8_atlas_get_entry(gfx->atlas, texture_id); - if (!entry || !entry->active) { - static bool warned = false; - if (!warned) { - pxl8_warn("Texture sampling failed: texture_id=%u entry=%p active=%d", - texture_id, (void*)entry, entry ? entry->active : 0); - warned = true; - } - return 0; - } - - i32 tex_u = (i32)(u * entry->w); - i32 tex_v = (i32)(v * entry->h); - - i32 tx = tex_u % entry->w; - if (tx < 0) tx += entry->w; - i32 ty = tex_v % entry->h; - if (ty < 0) ty += entry->h; - - i32 atlas_x = entry->x + tx; - i32 atlas_y = entry->y + ty; - u32 atlas_width = pxl8_atlas_get_width(gfx->atlas); - const u8* atlas_pixels = pxl8_atlas_get_pixels(gfx->atlas); - i32 idx = atlas_y * atlas_width + atlas_x; if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) { - return ((const u32*)atlas_pixels)[idx]; + u32* fb = (u32*)gfx->framebuffer; + if (width == 0) { + i32 idx = zbuf_offset + xs; + if (z0 <= gfx->zbuffer[idx]) { + gfx->zbuffer[idx] = z0; + fb[fb_offset + xs] = color; + } + return; + } + + f32 dz = (z1 - z0) / (f32)width; + f32 z = z0; + + 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; + } } else { - return atlas_pixels[idx]; + u8 idx_color = color & 0xFF; + if (width == 0) { + i32 idx = zbuf_offset + xs; + if (z0 <= gfx->zbuffer[idx]) { + gfx->zbuffer[idx] = z0; + gfx->framebuffer[fb_offset + xs] = idx_color; + } + return; + } + + f32 dz = (z1 - z0) / (f32)width; + f32 z = z0; + + 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; + } } } -static inline void pxl8_fill_scanline_textured_hicolor( +static inline void pxl8_fill_scanline_textured( pxl8_gfx* gfx, i32 y, i32 xs, i32 xe, f32 z0, f32 z1, f32 u0, f32 v0, f32 w0, @@ -1034,22 +935,58 @@ static inline void pxl8_fill_scanline_textured_hicolor( tmpf = w0; w0 = w1; w1 = tmpf; } + const pxl8_atlas_entry* entry = pxl8_atlas_get_entry(gfx->atlas, texture_id); + if (!entry || !entry->active) return; + + const u8* atlas_pixels = pxl8_atlas_get_pixels(gfx->atlas); + u32 atlas_width = pxl8_atlas_get_width(gfx->atlas); + i32 tex_w = entry->w; + i32 tex_h = entry->h; + bool is_pow2 = ((tex_w & (tex_w - 1)) == 0) && ((tex_h & (tex_h - 1)) == 0) && (tex_w == tex_h); + i32 tex_mask = tex_w - 1; + i32 atlas_x_base = entry->x; + i32 atlas_y_base = entry->y; + bool is_hicolor = (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR); + bool affine = gfx->affine_textures; + 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) { + f32 tex_u = u0, tex_v = v0; + if (!affine && fabsf(w0) > 1e-6f) { f32 perspective_w = 1.0f / w0; - u *= perspective_w; - v *= perspective_w; + tex_u *= perspective_w; + tex_v *= perspective_w; } - u32 color = pxl8_sample_texture(gfx, texture_id, u, v); - if (color & 0xFF000000) { - gfx->zbuffer[idx] = z0; - pxl8_pixel(gfx, xs, y, color); + + i32 tu = (i32)(tex_u * tex_w); + i32 tv = (i32)(tex_v * tex_h); + i32 tx, ty; + if (is_pow2) { + tx = tu & tex_mask; + ty = tv & tex_mask; + } else { + tx = tu % tex_w; + if (tx < 0) tx += tex_w; + ty = tv % tex_h; + if (ty < 0) ty += tex_h; + } + + i32 atlas_idx = (atlas_y_base + ty) * atlas_width + (atlas_x_base + tx); + u32 color = is_hicolor ? ((const u32*)atlas_pixels)[atlas_idx] : atlas_pixels[atlas_idx]; + + if (is_hicolor) { + if (color & 0xFF000000) { + gfx->zbuffer[idx] = z0; + ((u32*)gfx->framebuffer)[y * gfx->framebuffer_width + xs] = color; + } + } else { + if (color != 0) { + gfx->zbuffer[idx] = z0; + gfx->framebuffer[y * gfx->framebuffer_width + xs] = (u8)color; + } } } } @@ -1071,92 +1008,40 @@ static inline void pxl8_fill_scanline_textured_hicolor( if (x >= 0 && x < gfx->framebuffer_width) { i32 idx = y * gfx->zbuffer_width + x; if (z <= gfx->zbuffer[idx]) { - gfx->frame_pixel_count++; + f32 tex_u = u, tex_v = v; - if (!gfx->affine_textures && fabsf(w) > 1e-6f) { + if (!affine && fabsf(w) > 1e-6f) { f32 perspective_w = 1.0f / w; tex_u *= perspective_w; tex_v *= perspective_w; } - u32 color = pxl8_sample_texture(gfx, texture_id, tex_u, tex_v); - if (color & 0xFF000000) { - gfx->zbuffer[idx] = z; - pxl8_pixel(gfx, x, y, color); + + i32 tu = (i32)(tex_u * tex_w); + i32 tv = (i32)(tex_v * tex_h); + i32 tx, ty; + if (is_pow2) { + tx = tu & tex_mask; + ty = tv & tex_mask; + } else { + tx = tu % tex_w; + if (tx < 0) tx += tex_w; + ty = tv % tex_h; + if (ty < 0) ty += tex_h; } - } - } - z += dz; - u += du; - v += dv; - w += dw; - } -} -static inline void pxl8_fill_scanline_textured_indexed( - pxl8_gfx* gfx, i32 y, i32 xs, i32 xe, - f32 z0, f32 z1, - f32 u0, f32 v0, f32 w0, - f32 u1, f32 v1, f32 w1, - u32 texture_id -) { - if (y < 0 || y >= gfx->framebuffer_height) return; - if (xs > xe) { - i32 tmp = xs; xs = xe; xe = tmp; - f32 tmpf; - tmpf = z0; z0 = z1; z1 = tmpf; - tmpf = u0; u0 = u1; u1 = tmpf; - tmpf = v0; v0 = v1; v1 = tmpf; - tmpf = w0; w0 = w1; w1 = tmpf; - } + i32 atlas_idx = (atlas_y_base + ty) * atlas_width + (atlas_x_base + tx); + u32 color = is_hicolor ? ((const u32*)atlas_pixels)[atlas_idx] : atlas_pixels[atlas_idx]; - 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) { - f32 perspective_w = 1.0f / w0; - u *= perspective_w; - v *= perspective_w; - } - u32 color = pxl8_sample_texture(gfx, texture_id, u, v); - if (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; - - 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++; - f32 tex_u = u, tex_v = v; - if (!gfx->affine_textures && fabsf(w) > 1e-6f) { - f32 perspective_w = 1.0f / w; - tex_u *= perspective_w; - tex_v *= perspective_w; - } - u32 color = pxl8_sample_texture(gfx, texture_id, tex_u, tex_v); - if (color != 0) { - gfx->zbuffer[idx] = z; - pxl8_pixel(gfx, x, y, color); + if (is_hicolor) { + if (color & 0xFF000000) { + gfx->zbuffer[idx] = z; + ((u32*)gfx->framebuffer)[y * gfx->framebuffer_width + x] = color; + } + } else { + if (color != 0) { + gfx->zbuffer[idx] = z; + gfx->framebuffer[y * gfx->framebuffer_width + x] = (u8)color; + } } } } @@ -1228,30 +1113,40 @@ 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); - if (v0.w <= 0 || v1.w <= 0 || v2.w <= 0) return; + if (v0.w <= 0 || v1.w <= 0 || v2.w <= 0) { + pxl8_debug("Triangle rejected: w <= 0 (w0=%.2f, w1=%.2f, w2=%.2f)", v0.w, v1.w, v2.w); + return; + } + + pxl8_vec4 tv0 = v0; + pxl8_vec4 tv1 = v1; + pxl8_vec4 tv2 = v2; + u32 tc0 = tri.v[0].color; i32 x0, y0, x1, y1, x2, y2; f32 z0, z1, z2; - pxl8_project_to_screen(gfx, v0, &x0, &y0, &z0); - pxl8_project_to_screen(gfx, v1, &x1, &y1, &z1); - pxl8_project_to_screen(gfx, v2, &x2, &y2, &z2); + pxl8_project_to_screen(gfx, tv0, &x0, &y0, &z0); + pxl8_project_to_screen(gfx, tv1, &x1, &y1, &z1); + pxl8_project_to_screen(gfx, tv2, &x2, &y2, &z2); if (gfx->backface_culling) { i32 cross = (x1 - x0) * (y2 - y0) - (y1 - y0) * (x2 - x0); - if (cross >= 0) return; + if (cross >= 0) { + pxl8_debug("Triangle rejected: backface (cross=%d, screen coords: (%d,%d) (%d,%d) (%d,%d))", + cross, x0, y0, x1, y1, x2, y2); + return; + } } if (gfx->wireframe) { - u32 color = tri.v[0].color; - pxl8_line(gfx, x0, y0, x1, y1, color); - pxl8_line(gfx, x1, y1, x2, y2, color); - pxl8_line(gfx, x2, y2, x0, y0, color); + pxl8_line(gfx, x0, y0, x1, y1, tc0); + pxl8_line(gfx, x1, y1, x2, y2, tc0); + pxl8_line(gfx, x2, y2, x0, y0, tc0); return; } @@ -1271,23 +1166,19 @@ void pxl8_3d_draw_triangle(pxl8_gfx* gfx, pxl8_triangle tri) { f32 tmp_f = z1; z1 = z2; z2 = tmp_f; } - 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; + pxl8_scanline_func fill_scanline = pxl8_fill_scanline; if (y1 == y2) { - pxl8_scan_triangle(gfx, x0, y0, z0, x1, y1, z1, x2, y2, z2, color, fill_scanline, true); + pxl8_scan_triangle(gfx, x0, y0, z0, x1, y1, z1, x2, y2, z2, tc0, fill_scanline, true); } else if (y0 == y1) { - pxl8_scan_triangle(gfx, x0, y0, z0, x1, y1, z1, x2, y2, z2, color, fill_scanline, false); + pxl8_scan_triangle(gfx, x0, y0, z0, x1, y1, z1, x2, y2, z2, tc0, fill_scanline, false); } 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_scan_triangle(gfx, x0, y0, z0, x1, y1, z1, x3, y3, z3, color, fill_scanline, true); - pxl8_scan_triangle(gfx, x1, y1, z1, x3, y3, z3, x2, y2, z2, color, fill_scanline, false); + pxl8_scan_triangle(gfx, x0, y0, z0, x1, y1, z1, x3, y3, z3, tc0, fill_scanline, true); + pxl8_scan_triangle(gfx, x1, y1, z1, x3, y3, z3, x2, y2, z2, tc0, fill_scanline, false); } } @@ -1312,7 +1203,7 @@ static void pxl8_scan_triangle_textured( f32 inv_slope_u1, inv_slope_u2, inv_slope_v1, inv_slope_v2, inv_slope_w1, inv_slope_w2; if (is_bottom) { - if (v1.y == v0.y || v1.y < v0.y || v1.y - v0.y > gfx->framebuffer_height) return; + if (v1.y == v0.y || v1.y < v0.y) return; y_start = v0.y; y_end = v1.y; y_step = 1; @@ -1332,7 +1223,7 @@ static void pxl8_scan_triangle_textured( inv_slope_w1 = (v1.w - v0.w) / (f32)(v1.y - v0.y); inv_slope_w2 = (v2.w - v0.w) / (f32)(v2.y - v0.y); } else { - if (v2.y == v0.y || v2.y < v0.y || v2.y - v0.y > gfx->framebuffer_height) return; + if (v2.y == v0.y || v2.y < v0.y) return; y_start = v2.y; y_end = v0.y; y_step = -1; @@ -1381,6 +1272,83 @@ static void pxl8_scan_triangle_textured( } } +typedef struct { + pxl8_vec4 clip; + f32 u, v; +} pxl8_clip_vert; + +typedef struct { + i32 x, y; + f32 z, u, v, w; +} pxl8_screen_vert; + +static void pxl8_clip_screen_axis(pxl8_screen_vert* in, i32 in_count, pxl8_screen_vert* out, i32* out_count, i32 plane_pos, bool is_x, bool is_min) { + *out_count = 0; + + for (i32 i = 0; i < in_count; i++) { + pxl8_screen_vert curr = in[i]; + pxl8_screen_vert next = in[(i + 1) % in_count]; + + i32 curr_val = is_x ? curr.x : curr.y; + i32 next_val = is_x ? next.x : next.y; + + bool curr_inside = is_min ? (curr_val >= plane_pos) : (curr_val <= plane_pos); + bool next_inside = is_min ? (next_val >= plane_pos) : (next_val <= plane_pos); + + if (curr_inside) { + out[(*out_count)++] = curr; + } + + if (curr_inside != next_inside) { + i32 denom = next_val - curr_val; + if (denom != 0) { + f32 t = (f32)(plane_pos - curr_val) / (f32)denom; + out[*out_count] = (pxl8_screen_vert){ + .x = curr.x + (i32)(t * (next.x - curr.x)), + .y = curr.y + (i32)(t * (next.y - curr.y)), + .z = curr.z + t * (next.z - curr.z), + .u = curr.u + t * (next.u - curr.u), + .v = curr.v + t * (next.v - curr.v), + .w = curr.w + t * (next.w - curr.w) + }; + (*out_count)++; + } + } + } +} + +static void pxl8_clip_near_plane(pxl8_clip_vert* in, i32 in_count, pxl8_clip_vert* out, i32* out_count, f32 near) { + *out_count = 0; + + for (i32 i = 0; i < in_count; i++) { + pxl8_clip_vert curr = in[i]; + pxl8_clip_vert next = in[(i + 1) % in_count]; + + bool curr_inside = curr.clip.w >= near; + bool next_inside = next.clip.w >= near; + + if (curr_inside) { + out[(*out_count)++] = curr; + } + + if (curr_inside != next_inside) { + f32 t = (near - curr.clip.w) / (next.clip.w - curr.clip.w); + + out[*out_count] = (pxl8_clip_vert){ + .clip = { + curr.clip.x + t * (next.clip.x - curr.clip.x), + curr.clip.y + t * (next.clip.y - curr.clip.y), + curr.clip.z + t * (next.clip.z - curr.clip.z), + near + }, + .u = curr.u + t * (next.u - curr.u), + .v = curr.v + t * (next.v - curr.v) + }; + (*out_count)++; + } + } +} + void pxl8_3d_draw_triangle_textured( pxl8_gfx* gfx, pxl8_vec3 v0, @@ -1396,67 +1364,114 @@ void pxl8_3d_draw_triangle_textured( ) { if (!gfx || !pxl8_3d_init_zbuffer(gfx)) return; + pxl8_vec4 cv0 = pxl8_transform_vertex(gfx, v0); pxl8_vec4 cv1 = pxl8_transform_vertex(gfx, v1); pxl8_vec4 cv2 = pxl8_transform_vertex(gfx, v2); - if (cv0.w <= 0 || cv1.w <= 0 || cv2.w <= 0) return; + pxl8_clip_vert input[3] = { + {cv0, u0, v0f}, + {cv1, u1, v1f}, + {cv2, u2, v2f} + }; - pxl8_textured_vertex tv[3]; - f32 z_tmp; - pxl8_project_to_screen(gfx, cv0, &tv[0].x, &tv[0].y, &z_tmp); - tv[0].z = z_tmp; - tv[0].u = gfx->affine_textures ? u0 : u0 / cv0.w; - tv[0].v = gfx->affine_textures ? v0f : v0f / cv0.w; - tv[0].w = gfx->affine_textures ? 1.0f : 1.0f / cv0.w; + pxl8_clip_vert clipped[8]; + i32 clipped_count = 0; - pxl8_project_to_screen(gfx, cv1, &tv[1].x, &tv[1].y, &z_tmp); - tv[1].z = z_tmp; - tv[1].u = gfx->affine_textures ? u1 : u1 / cv1.w; - tv[1].v = gfx->affine_textures ? v1f : v1f / cv1.w; - tv[1].w = gfx->affine_textures ? 1.0f : 1.0f / cv1.w; + f32 near_clip = 0.1f; + pxl8_clip_near_plane(input, 3, clipped, &clipped_count, near_clip); - pxl8_project_to_screen(gfx, cv2, &tv[2].x, &tv[2].y, &z_tmp); - tv[2].z = z_tmp; - tv[2].u = gfx->affine_textures ? u2 : u2 / cv2.w; - tv[2].v = gfx->affine_textures ? v2f : v2f / cv2.w; - tv[2].w = gfx->affine_textures ? 1.0f : 1.0f / cv2.w; + if (clipped_count < 3) return; - if (gfx->backface_culling) { - i32 cross = - (tv[1].x - tv[0].x) * (tv[2].y - tv[0].y) - (tv[1].y - tv[0].y) * (tv[2].x - tv[0].x); - if (cross >= 0) return; - } + i32 guard_band = (gfx->framebuffer_width > gfx->framebuffer_height ? gfx->framebuffer_width : gfx->framebuffer_height) * 2; - if (tv[0].y > tv[1].y) { - pxl8_textured_vertex tmp = tv[0]; tv[0] = tv[1]; tv[1] = tmp; - } - if (tv[0].y > tv[2].y) { - pxl8_textured_vertex tmp = tv[0]; tv[0] = tv[2]; tv[2] = tmp; - } - if (tv[1].y > tv[2].y) { - pxl8_textured_vertex tmp = tv[1]; tv[1] = tv[2]; tv[2] = tmp; - } + for (i32 tri = 0; tri < clipped_count - 2; tri++) { + pxl8_vec4 c0 = clipped[0].clip; + pxl8_vec4 c1 = clipped[tri + 1].clip; + pxl8_vec4 c2 = clipped[tri + 2].clip; + f32 tu0 = clipped[0].u; + f32 tv0 = clipped[0].v; + f32 tu1 = clipped[tri + 1].u; + f32 tv1 = clipped[tri + 1].v; + f32 tu2 = clipped[tri + 2].u; + f32 tv2 = clipped[tri + 2].v; - pxl8_scanline_textured_func fill_scanline = (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) - ? pxl8_fill_scanline_textured_hicolor - : pxl8_fill_scanline_textured_indexed; + i32 sx0, sy0, sx1, sy1, sx2, sy2; + f32 z0, z1, z2; + pxl8_project_to_screen(gfx, c0, &sx0, &sy0, &z0); + pxl8_project_to_screen(gfx, c1, &sx1, &sy1, &z1); + pxl8_project_to_screen(gfx, c2, &sx2, &sy2, &z2); - if (tv[1].y == tv[2].y) { - pxl8_scan_triangle_textured(gfx, tv[0], tv[1], tv[2], texture_id, fill_scanline, true); - } else if (tv[0].y == tv[1].y) { - pxl8_scan_triangle_textured(gfx, tv[0], tv[1], tv[2], texture_id, fill_scanline, false); - } else { - f32 t = (f32)(tv[1].y - tv[0].y) / (f32)(tv[2].y - tv[0].y); - pxl8_textured_vertex v3; - v3.x = tv[0].x + (i32)(t * (tv[2].x - tv[0].x)); - v3.y = tv[1].y; - v3.z = tv[0].z + t * (tv[2].z - tv[0].z); - v3.u = tv[0].u + t * (tv[2].u - tv[0].u); - v3.v = tv[0].v + t * (tv[2].v - tv[0].v); - v3.w = tv[0].w + t * (tv[2].w - tv[0].w); + f32 w0_recip = 1.0f / c0.w; + f32 w1_recip = 1.0f / c1.w; + f32 w2_recip = 1.0f / c2.w; - pxl8_scan_triangle_textured(gfx, tv[0], tv[1], v3, texture_id, fill_scanline, true); - pxl8_scan_triangle_textured(gfx, tv[1], v3, tv[2], texture_id, fill_scanline, false); + pxl8_screen_vert screen_tri[3] = { + {sx0, sy0, z0, tu0 * w0_recip, tv0 * w0_recip, w0_recip}, + {sx1, sy1, z1, tu1 * w1_recip, tv1 * w1_recip, w1_recip}, + {sx2, sy2, z2, tu2 * w2_recip, tv2 * w2_recip, w2_recip} + }; + + pxl8_screen_vert temp1[8], temp2[8]; + i32 count1, count2; + + pxl8_clip_screen_axis(screen_tri, 3, temp1, &count1, -guard_band, true, true); + if (count1 < 3) continue; + pxl8_clip_screen_axis(temp1, count1, temp2, &count2, guard_band, true, false); + if (count2 < 3) continue; + pxl8_clip_screen_axis(temp2, count2, temp1, &count1, -guard_band, false, true); + if (count1 < 3) continue; + pxl8_clip_screen_axis(temp1, count1, temp2, &count2, guard_band, false, false); + if (count2 < 3) continue; + + for (i32 i = 1; i < count2 - 1; i++) { + pxl8_screen_vert v0 = temp2[0]; + pxl8_screen_vert v1 = temp2[i]; + pxl8_screen_vert v2 = temp2[i + 1]; + + pxl8_textured_vertex tv[3]; + tv[0].x = v0.x; tv[0].y = v0.y; tv[0].z = v0.z; + tv[0].u = v0.u; tv[0].v = v0.v; tv[0].w = v0.w; + tv[1].x = v1.x; tv[1].y = v1.y; tv[1].z = v1.z; + tv[1].u = v1.u; tv[1].v = v1.v; tv[1].w = v1.w; + tv[2].x = v2.x; tv[2].y = v2.y; tv[2].z = v2.z; + tv[2].u = v2.u; tv[2].v = v2.v; tv[2].w = v2.w; + + if (gfx->backface_culling) { + i32 cross = + (tv[1].x - tv[0].x) * (tv[2].y - tv[0].y) - (tv[1].y - tv[0].y) * (tv[2].x - tv[0].x); + if (cross >= 0) continue; + } + + if (tv[0].y > tv[1].y) { + pxl8_textured_vertex tmp = tv[0]; tv[0] = tv[1]; tv[1] = tmp; + } + if (tv[0].y > tv[2].y) { + pxl8_textured_vertex tmp = tv[0]; tv[0] = tv[2]; tv[2] = tmp; + } + if (tv[1].y > tv[2].y) { + pxl8_textured_vertex tmp = tv[1]; tv[1] = tv[2]; tv[2] = tmp; + } + + pxl8_scanline_textured_func fill_scanline = pxl8_fill_scanline_textured; + + if (tv[1].y == tv[2].y) { + pxl8_scan_triangle_textured(gfx, tv[0], tv[1], tv[2], texture_id, fill_scanline, true); + } else if (tv[0].y == tv[1].y) { + pxl8_scan_triangle_textured(gfx, tv[0], tv[1], tv[2], texture_id, fill_scanline, false); + } else { + f32 t = (f32)(tv[1].y - tv[0].y) / (f32)(tv[2].y - tv[0].y); + pxl8_textured_vertex v3; + v3.x = tv[0].x + (i32)(t * (tv[2].x - tv[0].x)); + v3.y = tv[1].y; + v3.z = tv[0].z + t * (tv[2].z - tv[0].z); + v3.u = tv[0].u + t * (tv[2].u - tv[0].u); + v3.v = tv[0].v + t * (tv[2].v - tv[0].v); + v3.w = tv[0].w + t * (tv[2].w - tv[0].w); + + pxl8_scan_triangle_textured(gfx, tv[0], tv[1], v3, texture_id, fill_scanline, true); + pxl8_scan_triangle_textured(gfx, tv[1], v3, tv[2], texture_id, fill_scanline, false); + } + } } } diff --git a/src/pxl8_gfx.h b/src/pxl8_gfx.h index e3f81ac..2a3b704 100644 --- a/src/pxl8_gfx.h +++ b/src/pxl8_gfx.h @@ -57,7 +57,6 @@ pxl8_color_mode pxl8_gfx_get_color_mode(pxl8_gfx* gfx); u8* pxl8_gfx_get_framebuffer(pxl8_gfx* gfx); i32 pxl8_gfx_get_height(const pxl8_gfx* gfx); u32 pxl8_gfx_get_palette_size(const pxl8_gfx* gfx); -void pxl8_gfx_get_resolution_dimensions(pxl8_resolution resolution, i32* width, i32* height); i32 pxl8_gfx_get_width(const pxl8_gfx* gfx); pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx); pxl8_result pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* path); diff --git a/src/pxl8_hal.h b/src/pxl8_hal.h index 69a95d3..9006116 100644 --- a/src/pxl8_hal.h +++ b/src/pxl8_hal.h @@ -13,6 +13,7 @@ typedef struct pxl8_hal { u64 (*get_ticks)(void); void (*present)(void* platform_data); + void (*set_relative_mouse_mode)(void* platform_data, bool enabled); void (*upload_atlas)(void* platform_data, const pxl8_atlas* atlas, const u32* palette, pxl8_color_mode mode); void (*upload_framebuffer)(void* platform_data, const u8* fb, diff --git a/src/pxl8_io.c b/src/pxl8_io.c index 79baec6..7d63133 100644 --- a/src/pxl8_io.c +++ b/src/pxl8_io.c @@ -179,3 +179,13 @@ i32 pxl8_mouse_y(const pxl8_input_state* input) { if (!input) return 0; return input->mouse_y; } + +i32 pxl8_mouse_dx(const pxl8_input_state* input) { + if (!input) return 0; + return input->mouse_dx; +} + +i32 pxl8_mouse_dy(const pxl8_input_state* input) { + if (!input) return 0; + return input->mouse_dy; +} diff --git a/src/pxl8_io.h b/src/pxl8_io.h index 3d1d1c4..e11efdc 100644 --- a/src/pxl8_io.h +++ b/src/pxl8_io.h @@ -104,6 +104,8 @@ i32 pxl8_mouse_wheel_x(const pxl8_input_state* input); i32 pxl8_mouse_wheel_y(const pxl8_input_state* input); i32 pxl8_mouse_x(const pxl8_input_state* input); i32 pxl8_mouse_y(const pxl8_input_state* input); +i32 pxl8_mouse_dx(const pxl8_input_state* input); +i32 pxl8_mouse_dy(const pxl8_input_state* input); #ifdef __cplusplus } diff --git a/src/pxl8_math.c b/src/pxl8_math.c index 56f423c..f26ebd4 100644 --- a/src/pxl8_math.c +++ b/src/pxl8_math.c @@ -96,13 +96,13 @@ pxl8_mat4 pxl8_mat4_identity(void) { pxl8_mat4 pxl8_mat4_multiply(pxl8_mat4 a, pxl8_mat4 b) { pxl8_mat4 mat = {0}; - for (i32 i = 0; i < 4; i++) { - for (i32 j = 0; j < 4; j++) { - mat.m[i * 4 + j] = - a.m[i * 4 + 0] * b.m[0 * 4 + j] + - a.m[i * 4 + 1] * b.m[1 * 4 + j] + - a.m[i * 4 + 2] * b.m[2 * 4 + j] + - a.m[i * 4 + 3] * b.m[3 * 4 + j]; + for (i32 col = 0; col < 4; col++) { + for (i32 row = 0; row < 4; row++) { + mat.m[col * 4 + row] = + a.m[0 * 4 + row] * b.m[col * 4 + 0] + + a.m[1 * 4 + row] * b.m[col * 4 + 1] + + a.m[2 * 4 + row] * b.m[col * 4 + 2] + + a.m[3 * 4 + row] * b.m[col * 4 + 3]; } } @@ -111,19 +111,19 @@ pxl8_mat4 pxl8_mat4_multiply(pxl8_mat4 a, pxl8_mat4 b) { pxl8_vec4 pxl8_mat4_multiply_vec4(pxl8_mat4 m, pxl8_vec4 v) { return (pxl8_vec4){ - .x = m.m[0] * v.x + m.m[1] * v.y + m.m[2] * v.z + m.m[3] * v.w, - .y = m.m[4] * v.x + m.m[5] * v.y + m.m[6] * v.z + m.m[7] * v.w, - .z = m.m[8] * v.x + m.m[9] * v.y + m.m[10] * v.z + m.m[11] * v.w, - .w = m.m[12] * v.x + m.m[13] * v.y + m.m[14] * v.z + m.m[15] * v.w, + .x = m.m[0] * v.x + m.m[4] * v.y + m.m[8] * v.z + m.m[12] * v.w, + .y = m.m[1] * v.x + m.m[5] * v.y + m.m[9] * v.z + m.m[13] * v.w, + .z = m.m[2] * v.x + m.m[6] * v.y + m.m[10] * v.z + m.m[14] * v.w, + .w = m.m[3] * v.x + m.m[7] * v.y + m.m[11] * v.z + m.m[15] * v.w, }; } pxl8_mat4 pxl8_mat4_translate(f32 x, f32 y, f32 z) { pxl8_mat4 mat = pxl8_mat4_identity(); - mat.m[3] = x; - mat.m[7] = y; - mat.m[11] = z; + mat.m[12] = x; + mat.m[13] = y; + mat.m[14] = z; return mat; } @@ -134,8 +134,8 @@ pxl8_mat4 pxl8_mat4_rotate_x(f32 angle) { f32 s = sinf(angle); mat.m[5] = c; - mat.m[6] = -s; - mat.m[9] = s; + mat.m[9] = -s; + mat.m[6] = s; mat.m[10] = c; return mat; @@ -147,8 +147,8 @@ pxl8_mat4 pxl8_mat4_rotate_y(f32 angle) { f32 s = sinf(angle); mat.m[0] = c; - mat.m[2] = s; - mat.m[8] = -s; + mat.m[8] = s; + mat.m[2] = -s; mat.m[10] = c; return mat; @@ -160,8 +160,8 @@ pxl8_mat4 pxl8_mat4_rotate_z(f32 angle) { f32 s = sinf(angle); mat.m[0] = c; - mat.m[1] = -s; - mat.m[4] = s; + mat.m[4] = -s; + mat.m[1] = s; mat.m[5] = c; return mat; @@ -183,9 +183,9 @@ pxl8_mat4 pxl8_mat4_ortho(f32 left, f32 right, f32 bottom, f32 top, f32 near, f3 mat.m[0] = 2.0f / (right - left); mat.m[5] = 2.0f / (top - bottom); mat.m[10] = -2.0f / (far - near); - mat.m[3] = -(right + left) / (right - left); - mat.m[7] = -(top + bottom) / (top - bottom); - mat.m[11] = -(far + near) / (far - near); + mat.m[12] = -(right + left) / (right - left); + mat.m[13] = -(top + bottom) / (top - bottom); + mat.m[14] = -(far + near) / (far - near); mat.m[15] = 1.0f; return mat; @@ -198,8 +198,8 @@ pxl8_mat4 pxl8_mat4_perspective(f32 fov, f32 aspect, f32 near, f32 far) { mat.m[0] = 1.0f / (aspect * tan_half_fov); mat.m[5] = 1.0f / tan_half_fov; mat.m[10] = -(far + near) / (far - near); - mat.m[11] = -(2.0f * far * near) / (far - near); - mat.m[14] = -1.0f; + mat.m[14] = -(2.0f * far * near) / (far - near); + mat.m[11] = -1.0f; return mat; } @@ -211,17 +211,17 @@ pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up) { pxl8_vec3 u = pxl8_vec3_cross(s, f); mat.m[0] = s.x; - mat.m[1] = s.y; - mat.m[2] = s.z; - mat.m[4] = u.x; + mat.m[4] = s.y; + mat.m[8] = s.z; + mat.m[1] = u.x; mat.m[5] = u.y; - mat.m[6] = u.z; - mat.m[8] = -f.x; - mat.m[9] = -f.y; + mat.m[9] = u.z; + mat.m[2] = -f.x; + mat.m[6] = -f.y; mat.m[10] = -f.z; - mat.m[3] = -pxl8_vec3_dot(s, eye); - mat.m[7] = -pxl8_vec3_dot(u, eye); - mat.m[11] = pxl8_vec3_dot(f, eye); + mat.m[12] = -pxl8_vec3_dot(s, eye); + mat.m[13] = -pxl8_vec3_dot(u, eye); + mat.m[14] = pxl8_vec3_dot(f, eye); return mat; } @@ -230,35 +230,35 @@ pxl8_frustum pxl8_frustum_from_matrix(pxl8_mat4 vp) { pxl8_frustum frustum; const f32* m = vp.m; - frustum.planes[0].normal.x = m[12] - m[0]; - frustum.planes[0].normal.y = m[13] - m[1]; - frustum.planes[0].normal.z = m[14] - m[2]; - frustum.planes[0].distance = m[15] - m[3]; + 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[12] + m[0]; - frustum.planes[1].normal.y = m[13] + m[1]; - frustum.planes[1].normal.z = m[14] + m[2]; - frustum.planes[1].distance = m[15] + m[3]; + 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[12] + m[4]; - frustum.planes[2].normal.y = m[13] + m[5]; - frustum.planes[2].normal.z = m[14] + m[6]; - frustum.planes[2].distance = m[15] + m[7]; + 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[12] - m[4]; - frustum.planes[3].normal.y = m[13] - m[5]; - frustum.planes[3].normal.z = m[14] - m[6]; - frustum.planes[3].distance = m[15] - m[7]; + 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[12] - m[8]; - frustum.planes[4].normal.y = m[13] - m[9]; - frustum.planes[4].normal.z = m[14] - m[10]; - frustum.planes[4].distance = m[15] - m[11]; + 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[12] + m[8]; - frustum.planes[5].normal.y = m[13] + m[9]; - frustum.planes[5].normal.z = m[14] + m[10]; - frustum.planes[5].distance = m[15] + m[11]; + 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); @@ -283,22 +283,11 @@ bool pxl8_frustum_test_aabb(const pxl8_frustum* frustum, pxl8_vec3 min, pxl8_vec (normal.z >= 0.0f) ? max.z : min.z }; - pxl8_vec3 n_vertex = { - (normal.x >= 0.0f) ? min.x : max.x, - (normal.y >= 0.0f) ? min.y : max.y, - (normal.z >= 0.0f) ? min.z : max.z - }; - f32 p_dist = pxl8_vec3_dot(normal, p_vertex) + d; - f32 n_dist = pxl8_vec3_dot(normal, n_vertex) + d; - if (p_dist < -0.1f) { + if (p_dist < 0.0f) { return false; } - - if (n_dist > 0.1f) { - continue; - } } return true; diff --git a/src/pxl8_script.c b/src/pxl8_script.c index 88f51d7..36d56c1 100644 --- a/src/pxl8_script.c +++ b/src/pxl8_script.c @@ -113,6 +113,9 @@ static const char* pxl8_ffi_cdefs = "int pxl8_mouse_wheel_y(const pxl8_input_state* input);\n" "int pxl8_mouse_x(const pxl8_input_state* input);\n" "int pxl8_mouse_y(const pxl8_input_state* input);\n" +"int pxl8_mouse_dx(const pxl8_input_state* input);\n" +"int pxl8_mouse_dy(const pxl8_input_state* input);\n" +"void pxl8_set_relative_mouse_mode(pxl8* sys, bool enabled);\n" "void pxl8_lua_debug(const char* msg);\n" "void pxl8_lua_error(const char* msg);\n" "void pxl8_lua_info(const char* msg);\n" diff --git a/src/pxl8_sdl3.c b/src/pxl8_sdl3.c index a29a24b..9550bc6 100644 --- a/src/pxl8_sdl3.c +++ b/src/pxl8_sdl3.c @@ -31,17 +31,7 @@ static void* sdl3_create(pxl8_color_mode mode, pxl8_resolution resolution, return NULL; } - i32 fb_w, fb_h; - switch (resolution) { - case PXL8_RESOLUTION_240x160: fb_w = 240; fb_h = 160; break; - case PXL8_RESOLUTION_320x180: fb_w = 320; fb_h = 180; break; - case PXL8_RESOLUTION_320x240: fb_w = 320; fb_h = 240; break; - case PXL8_RESOLUTION_640x360: fb_w = 640; fb_h = 360; break; - case PXL8_RESOLUTION_640x480: fb_w = 640; fb_h = 480; break; - case PXL8_RESOLUTION_800x600: fb_w = 800; fb_h = 600; break; - case PXL8_RESOLUTION_960x540: fb_w = 960; fb_h = 540; break; - default: fb_w = 640; fb_h = 360; break; - } + pxl8_size fb_size = pxl8_get_resolution_dimensions(resolution); ctx->window = SDL_CreateWindow(title, win_w, win_h, SDL_WINDOW_RESIZABLE); if (!ctx->window) { @@ -65,7 +55,7 @@ static void* sdl3_create(pxl8_color_mode mode, pxl8_resolution resolution, ctx->framebuffer_texture = SDL_CreateTexture(ctx->renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, - fb_w, fb_h); + fb_size.w, fb_size.h); if (!ctx->framebuffer_texture) { pxl8_error("Failed to create framebuffer texture: %s", SDL_GetError()); SDL_DestroyRenderer(ctx->renderer); @@ -145,14 +135,7 @@ static void sdl3_upload_framebuffer(void* platform_data, const u8* fb, if (!ctx->rgba_buffer) return; - u32 palette_size; - switch (mode) { - case PXL8_COLOR_MODE_FAMI: palette_size = 64; break; - case PXL8_COLOR_MODE_MEGA: palette_size = 512; break; - case PXL8_COLOR_MODE_GBA: palette_size = 32768; break; - case PXL8_COLOR_MODE_SNES: palette_size = 32768; break; - default: palette_size = 256; break; - } + u32 palette_size = pxl8_get_palette_size(mode); for (i32 i = 0; i < w * h; i++) { u8 index = fb[i]; @@ -212,14 +195,7 @@ static void sdl3_upload_atlas(void* platform_data, const pxl8_atlas* atlas, if (!ctx->rgba_buffer) return; - u32 palette_size; - switch (mode) { - case PXL8_COLOR_MODE_FAMI: palette_size = 64; break; - case PXL8_COLOR_MODE_MEGA: palette_size = 512; break; - case PXL8_COLOR_MODE_GBA: palette_size = 32768; break; - case PXL8_COLOR_MODE_SNES: palette_size = 32768; break; - default: palette_size = 256; break; - } + u32 palette_size = pxl8_get_palette_size(mode); for (u32 i = 0; i < atlas_w * atlas_h; i++) { u8 index = atlas_pixels[i]; @@ -290,12 +266,6 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) { break; case SDL_EVENT_KEY_DOWN: { -#ifdef DEBUG - if (event->key.key == SDLK_ESCAPE) { - pxl8_set_running(sys, false); - } -#endif - SDL_Scancode scancode = event->key.scancode; if (scancode < 256) { if (!input->keys_down[scancode]) { @@ -343,6 +313,9 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) { pxl8_gfx* gfx = pxl8_get_gfx(sys); if (!gfx) break; + input->mouse_dx = (i32)event->motion.xrel; + input->mouse_dy = (i32)event->motion.yrel; + i32 window_mouse_x = (i32)event->motion.x; i32 window_mouse_y = (i32)event->motion.y; @@ -352,12 +325,11 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) { i32 window_width, window_height; SDL_GetWindowSize(window, &window_width, &window_height); - i32 render_width, render_height; pxl8_resolution resolution = pxl8_get_resolution(sys); - pxl8_gfx_get_resolution_dimensions(resolution, &render_width, &render_height); + pxl8_size render_size = pxl8_get_resolution_dimensions(resolution); pxl8_bounds window_bounds = {0, 0, window_width, window_height}; - pxl8_viewport vp = pxl8_gfx_viewport(window_bounds, render_width, render_height); + pxl8_viewport vp = pxl8_gfx_viewport(window_bounds, render_size.w, render_size.h); input->mouse_x = (i32)((window_mouse_x - vp.offset_x) / vp.scale); input->mouse_y = (i32)((window_mouse_y - vp.offset_y) / vp.scale); @@ -386,6 +358,15 @@ void SDL_AppQuit(void* appstate, SDL_AppResult result) { SDL_Quit(); } +static void sdl3_set_relative_mouse_mode(void* platform_data, bool enabled) { + if (!platform_data) return; + + pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data; + if (!SDL_SetWindowRelativeMouseMode(ctx->window, enabled)) { + pxl8_error("Failed to set relative mouse mode: %s", SDL_GetError()); + } +} + const pxl8_hal pxl8_hal_sdl3 = { .create = sdl3_create, .destroy = sdl3_destroy, @@ -393,4 +374,5 @@ const pxl8_hal pxl8_hal_sdl3 = { .present = sdl3_present, .upload_atlas = sdl3_upload_atlas, .upload_framebuffer = sdl3_upload_framebuffer, + .set_relative_mouse_mode = sdl3_set_relative_mouse_mode, }; diff --git a/src/pxl8_sys.h b/src/pxl8_sys.h index 207b991..fb27ec0 100644 --- a/src/pxl8_sys.h +++ b/src/pxl8_sys.h @@ -14,11 +14,16 @@ bool pxl8_is_running(const pxl8* sys); void pxl8_set_running(pxl8* sys, bool running); f32 pxl8_get_fps(const pxl8* sys); -pxl8_gfx* pxl8_get_gfx(pxl8* sys); -pxl8_input_state* pxl8_get_input(pxl8* sys); -pxl8_resolution pxl8_get_resolution(pxl8* sys); +pxl8_gfx* pxl8_get_gfx(const pxl8* sys); +pxl8_input_state* pxl8_get_input(const pxl8* sys); +pxl8_resolution pxl8_get_resolution(const pxl8* sys); + +void pxl8_set_relative_mouse_mode(pxl8* sys, bool enabled); pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]); pxl8_result pxl8_update(pxl8* sys); pxl8_result pxl8_frame(pxl8* sys); void pxl8_quit(pxl8* sys); + +u32 pxl8_get_palette_size(pxl8_color_mode mode); +pxl8_size pxl8_get_resolution_dimensions(pxl8_resolution resolution); diff --git a/src/pxl8_types.h b/src/pxl8_types.h index e07536f..302aa1d 100644 --- a/src/pxl8_types.h +++ b/src/pxl8_types.h @@ -75,12 +75,19 @@ typedef struct pxl8_input_state { i32 mouse_wheel_y; i32 mouse_x; i32 mouse_y; + i32 mouse_dx; + i32 mouse_dy; + bool mouse_relative_mode; } pxl8_input_state; typedef struct pxl8_point { i32 x, y; } pxl8_point; +typedef struct pxl8_size { + i32 w, h; +} pxl8_size; + typedef struct pxl8_viewport { i32 offset_x, offset_y; i32 scaled_width, scaled_height;