feat(gui): add toolbar widget

feat(gui): add grid_select, toggle, panel, status_bar, image widgets
fix(bsp): fill in exterior cells
This commit is contained in:
asrael 2026-02-27 06:50:49 -06:00
parent 5a565844dd
commit 8d491612ab
63 changed files with 3150 additions and 1686 deletions

View file

@ -7,15 +7,6 @@
#include "pxl8_mem.h"
#include "pxl8_mesh.h"
typedef struct {
f32 x0, y0, x1, y1;
} screen_rect;
typedef struct {
u32 leaf;
screen_rect window;
} portal_queue_entry;
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;
@ -40,83 +31,6 @@ static inline bool face_in_frustum(const pxl8_bsp* bsp, u32 face_id, const pxl8_
return pxl8_frustum_test_aabb(frustum, face->aabb_min, face->aabb_max);
}
static inline bool screen_rect_valid(screen_rect r) {
return r.x0 < r.x1 && r.y0 < r.y1;
}
static inline screen_rect screen_rect_intersect(screen_rect a, screen_rect b) {
return (screen_rect){
.x0 = (a.x0 > b.x0) ? a.x0 : b.x0,
.y0 = (a.y0 > b.y0) ? a.y0 : b.y0,
.x1 = (a.x1 < b.x1) ? a.x1 : b.x1,
.y1 = (a.y1 < b.y1) ? a.y1 : b.y1,
};
}
static inline void expand_rect_with_ndc(screen_rect* r, f32 nx, f32 ny) {
if (nx < r->x0) r->x0 = nx;
if (nx > r->x1) r->x1 = nx;
if (ny < r->y0) r->y0 = ny;
if (ny > r->y1) r->y1 = ny;
}
static screen_rect project_portal_to_screen(const pxl8_bsp_portal* portal, const pxl8_mat4* vp, f32 wall_height) {
pxl8_vec3 world_corners[4] = {
{portal->x0, 0, portal->z0},
{portal->x1, 0, portal->z1},
{portal->x1, wall_height, portal->z1},
{portal->x0, wall_height, portal->z0},
};
pxl8_vec4 clip[4];
bool in_front[4];
i32 front_count = 0;
const f32 NEAR_W = 0.001f;
for (i32 i = 0; i < 4; i++) {
clip[i] = pxl8_mat4_multiply_vec4(*vp, (pxl8_vec4){world_corners[i].x, world_corners[i].y, world_corners[i].z, 1.0f});
in_front[i] = clip[i].w > NEAR_W;
if (in_front[i]) front_count++;
}
if (front_count == 0) return (screen_rect){0, 0, 0, 0};
screen_rect result = {1e30f, 1e30f, -1e30f, -1e30f};
for (i32 i = 0; i < 4; i++) {
i32 j = (i + 1) % 4;
pxl8_vec4 a = clip[i];
pxl8_vec4 b = clip[j];
bool a_in = in_front[i];
bool b_in = in_front[j];
if (a_in) {
f32 inv_w = 1.0f / a.w;
expand_rect_with_ndc(&result, a.x * inv_w, a.y * inv_w);
}
if (a_in != b_in) {
f32 t = (NEAR_W - a.w) / (b.w - a.w);
pxl8_vec4 intersection = {
a.x + t * (b.x - a.x),
a.y + t * (b.y - a.y),
a.z + t * (b.z - a.z),
NEAR_W
};
f32 inv_w = 1.0f / intersection.w;
expand_rect_with_ndc(&result, intersection.x * inv_w, intersection.y * inv_w);
}
}
if (result.x0 < -1.0f) result.x0 = -1.0f;
if (result.y0 < -1.0f) result.y0 = -1.0f;
if (result.x1 > 1.0f) result.x1 = 1.0f;
if (result.y1 > 1.0f) result.y1 = 1.0f;
return result;
}
static void collect_face_to_mesh(const pxl8_bsp* bsp, const pxl8_bsp_render_state* state,
u32 face_id, pxl8_mesh* mesh, u8 ambient) {
const pxl8_bsp_face* face = &bsp->faces[face_id];
@ -228,190 +142,38 @@ void pxl8_bsp_render_state_destroy(pxl8_bsp_render_state* state) {
pxl8_free(state);
}
void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const pxl8_gfx_material* material) {
if (!gfx || !bsp || face_id >= bsp->num_faces || !material) return;
pxl8_mesh* mesh = pxl8_mesh_create(64, 192);
if (!mesh) return;
collect_face_to_mesh(bsp, NULL, face_id, mesh, pxl8_gfx_get_ambient(gfx));
if (mesh->index_count > 0) {
pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_3d_draw_mesh(gfx, mesh, &identity, material);
}
pxl8_mesh_destroy(mesh);
}
void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp,
pxl8_bsp_render_state* state, pxl8_vec3 camera_pos) {
pxl8_bsp_render_state* state,
const pxl8_gfx_draw_opts* opts) {
if (!gfx || !bsp || !state || bsp->num_faces == 0) return;
if (!state->materials || state->num_materials == 0) return;
if (!state->render_face_flags) return;
const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx);
const pxl8_mat4* vp = pxl8_3d_get_view_proj(gfx);
if (!frustum || !vp) return;
i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
if (camera_leaf < 0 || (u32)camera_leaf >= bsp->num_leafs) return;
if (!bsp->cell_portals || bsp->num_cell_portals == 0) {
pxl8_mesh* mesh = pxl8_mesh_create(8192, 16384);
if (!mesh) return;
u32 current_material = 0xFFFFFFFF;
for (u32 face_id = 0; face_id < bsp->num_faces; face_id++) {
if (!face_in_frustum(bsp, face_id, frustum)) continue;
const pxl8_bsp_face* face = &bsp->faces[face_id];
u32 material_id = face->material_id;
if (material_id >= state->num_materials) continue;
if (material_id != current_material && mesh->index_count > 0 && current_material < state->num_materials) {
pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_3d_draw_mesh(gfx, mesh, &identity, &state->materials[current_material]);
pxl8_mesh_clear(mesh);
}
current_material = material_id;
collect_face_to_mesh(bsp, state, face_id, mesh, pxl8_gfx_get_ambient(gfx));
}
if (mesh->index_count > 0 && current_material < state->num_materials) {
pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_3d_draw_mesh(gfx, mesh, &identity, &state->materials[current_material]);
}
pxl8_mesh_destroy(mesh);
return;
}
if (!state->render_face_flags && state->num_faces > 0) {
state->render_face_flags = pxl8_calloc(state->num_faces, 1);
if (!state->render_face_flags) return;
}
memset(state->render_face_flags, 0, state->num_faces);
pxl8_bsp_pvs pvs = pxl8_bsp_decompress_pvs(bsp, camera_leaf);
u32 visited_bytes = (bsp->num_leafs + 7) / 8;
u8* visited = pxl8_calloc(visited_bytes, 1);
screen_rect* cell_windows = pxl8_calloc(bsp->num_leafs, sizeof(screen_rect));
portal_queue_entry* queue = pxl8_malloc(bsp->num_leafs * 4 * sizeof(portal_queue_entry));
if (!visited || !cell_windows || !queue) {
pxl8_free(visited);
pxl8_free(cell_windows);
pxl8_free(queue);
pxl8_bsp_pvs_destroy(&pvs);
return;
}
u32 head = 0, tail = 0;
screen_rect full_screen = {-1.0f, -1.0f, 1.0f, 1.0f};
visited[camera_leaf >> 3] |= (1 << (camera_leaf & 7));
cell_windows[camera_leaf] = full_screen;
queue[tail++] = (portal_queue_entry){camera_leaf, full_screen};
f32 wall_height = 128.0f;
while (head < tail) {
portal_queue_entry entry = queue[head++];
u32 leaf_id = entry.leaf;
screen_rect window = entry.window;
if (leaf_id >= bsp->num_cell_portals) continue;
const pxl8_bsp_cell_portals* cp = &bsp->cell_portals[leaf_id];
for (u8 i = 0; i < cp->num_portals; i++) {
const pxl8_bsp_portal* portal = &cp->portals[i];
u32 target = portal->target_leaf;
if (target >= bsp->num_leafs) continue;
if (bsp->leafs[target].contents == -1) continue;
if (!pxl8_bsp_pvs_is_visible(&pvs, target)) continue;
screen_rect portal_rect = project_portal_to_screen(portal, vp, wall_height);
if (!screen_rect_valid(portal_rect)) continue;
screen_rect new_window = screen_rect_intersect(window, portal_rect);
if (!screen_rect_valid(new_window)) continue;
u32 byte = target >> 3;
u32 bit = target & 7;
if (visited[byte] & (1 << bit)) {
screen_rect existing = cell_windows[target];
bool expanded = false;
if (new_window.x0 < existing.x0) { cell_windows[target].x0 = new_window.x0; expanded = true; }
if (new_window.y0 < existing.y0) { cell_windows[target].y0 = new_window.y0; expanded = true; }
if (new_window.x1 > existing.x1) { cell_windows[target].x1 = new_window.x1; expanded = true; }
if (new_window.y1 > existing.y1) { cell_windows[target].y1 = new_window.y1; expanded = true; }
if (expanded && tail < bsp->num_leafs * 4) {
queue[tail++] = (portal_queue_entry){target, cell_windows[target]};
}
continue;
}
visited[byte] |= (1 << bit);
cell_windows[target] = new_window;
queue[tail++] = (portal_queue_entry){target, new_window};
}
}
if (!frustum) return;
pxl8_mesh* mesh = pxl8_mesh_create(8192, 16384);
if (!mesh) {
pxl8_free(visited);
pxl8_free(cell_windows);
pxl8_free(queue);
return;
}
if (!mesh) return;
u32 current_material = 0xFFFFFFFF;
for (u32 leaf_id = 0; leaf_id < bsp->num_leafs; leaf_id++) {
if (!(visited[leaf_id >> 3] & (1 << (leaf_id & 7)))) continue;
if (bsp->leafs[leaf_id].contents == -1) continue;
const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id];
for (u32 i = 0; i < leaf->num_marksurfaces; i++) {
u32 surf_idx = leaf->first_marksurface + i;
if (surf_idx >= bsp->num_marksurfaces) continue;
u32 face_id = bsp->marksurfaces[surf_idx];
if (face_id >= bsp->num_faces) continue;
if (state->render_face_flags[face_id]) continue;
state->render_face_flags[face_id] = 1;
if (!face_in_frustum(bsp, face_id, frustum)) continue;
u8 ambient = pxl8_gfx_get_ambient(gfx);
pxl8_mat4 identity = pxl8_mat4_identity();
for (u32 mat = 0; mat < state->num_materials; mat++) {
for (u32 face_id = 0; face_id < bsp->num_faces; face_id++) {
if (!state->render_face_flags[face_id]) continue;
const pxl8_bsp_face* face = &bsp->faces[face_id];
u32 material_id = face->material_id;
if (material_id >= state->num_materials) continue;
if (material_id != current_material && mesh->index_count > 0 && current_material < state->num_materials) {
pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_3d_draw_mesh(gfx, mesh, &identity, &state->materials[current_material]);
pxl8_mesh_clear(mesh);
}
current_material = material_id;
collect_face_to_mesh(bsp, state, face_id, mesh, pxl8_gfx_get_ambient(gfx));
if (face->material_id != mat) continue;
if (!face_in_frustum(bsp, face_id, frustum)) continue;
collect_face_to_mesh(bsp, state, face_id, mesh, ambient);
}
if (mesh->index_count > 0) {
pxl8_gfx_material mat_copy = state->materials[mat];
if (state->exterior) mat_copy.double_sided = true;
pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat_copy, opts);
pxl8_mesh_clear(mesh);
}
}
if (mesh->index_count > 0 && current_material < state->num_materials) {
pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_3d_draw_mesh(gfx, mesh, &identity, &state->materials[current_material]);
}
pxl8_bsp_pvs_destroy(&pvs);
pxl8_free(visited);
pxl8_free(cell_windows);
pxl8_free(queue);
pxl8_mesh_destroy(mesh);
}