2026-04-14 01:28:38 -05:00
|
|
|
#include "demo3d_bsp_render.h"
|
2026-01-31 09:31:17 -06:00
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
|
|
#include "pxl8_gfx3d.h"
|
|
|
|
|
#include "pxl8_log.h"
|
|
|
|
|
#include "pxl8_mem.h"
|
|
|
|
|
#include "pxl8_mesh.h"
|
|
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
static inline bool face_in_frustum(const demo3d_bsp* bsp, u32 face_id, const pxl8_frustum* frustum) {
|
|
|
|
|
const demo3d_bsp_face* face = &bsp->faces[face_id];
|
2026-01-31 09:31:17 -06:00
|
|
|
return pxl8_frustum_test_aabb(frustum, face->aabb_min, face->aabb_max);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
static void collect_face_to_mesh(const demo3d_bsp* bsp, const demo3d_bsp_render_state* state,
|
2026-02-02 17:48:25 -06:00
|
|
|
u32 face_id, pxl8_mesh* mesh, u8 ambient) {
|
2026-04-14 01:28:38 -05:00
|
|
|
const demo3d_bsp_face* face = &bsp->faces[face_id];
|
2026-01-31 09:31:17 -06:00
|
|
|
if (face->num_edges < 3) return;
|
|
|
|
|
|
|
|
|
|
pxl8_vec3 normal = {0, 1, 0};
|
|
|
|
|
if (face->plane_id < bsp->num_planes) {
|
|
|
|
|
normal = bsp->planes[face->plane_id].normal;
|
|
|
|
|
if (face->side) {
|
|
|
|
|
normal.x = -normal.x;
|
|
|
|
|
normal.y = -normal.y;
|
|
|
|
|
normal.z = -normal.z;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const pxl8_gfx_material* material = NULL;
|
|
|
|
|
f32 tex_scale = 64.0f;
|
|
|
|
|
if (state && face->material_id < state->num_materials) {
|
|
|
|
|
material = &state->materials[face->material_id];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_vec3 u_axis = {1.0f, 0.0f, 0.0f};
|
|
|
|
|
pxl8_vec3 v_axis = {0.0f, 0.0f, 1.0f};
|
|
|
|
|
f32 u_offset = 0.0f, v_offset = 0.0f;
|
|
|
|
|
if (material) {
|
|
|
|
|
u_offset = material->u_offset;
|
|
|
|
|
v_offset = material->v_offset;
|
|
|
|
|
}
|
|
|
|
|
f32 abs_ny = normal.y < 0 ? -normal.y : normal.y;
|
|
|
|
|
if (abs_ny > 0.7f) {
|
|
|
|
|
u_axis = (pxl8_vec3){1.0f, 0.0f, 0.0f};
|
|
|
|
|
v_axis = (pxl8_vec3){0.0f, 0.0f, 1.0f};
|
|
|
|
|
} else {
|
|
|
|
|
f32 abs_nx = normal.x < 0 ? -normal.x : normal.x;
|
|
|
|
|
f32 abs_nz = normal.z < 0 ? -normal.z : normal.z;
|
|
|
|
|
if (abs_nx > abs_nz) {
|
|
|
|
|
u_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};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
u16 base_idx = (u16)mesh->vertex_count;
|
|
|
|
|
u32 num_verts = 0;
|
|
|
|
|
|
|
|
|
|
for (u32 i = 0; i < face->num_edges && num_verts < 64; i++) {
|
|
|
|
|
u32 edge_i = face->side ? (face->num_edges - 1 - i) : i;
|
|
|
|
|
i32 surfedge_idx = face->first_edge + edge_i;
|
|
|
|
|
u32 vert_idx;
|
|
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
if (!demo3d_bsp_get_edge_vertex(bsp, surfedge_idx, &vert_idx)) {
|
2026-01-31 09:31:17 -06:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_vec3 pos = bsp->vertices[vert_idx].position;
|
|
|
|
|
|
|
|
|
|
f32 u = (pxl8_vec3_dot(pos, u_axis) + u_offset) / tex_scale;
|
|
|
|
|
f32 v = (pxl8_vec3_dot(pos, v_axis) + v_offset) / tex_scale;
|
|
|
|
|
|
|
|
|
|
u8 light = 255;
|
|
|
|
|
if (bsp->vertex_lights && vert_idx < bsp->num_vertex_lights) {
|
2026-02-02 17:48:25 -06:00
|
|
|
u32 packed = bsp->vertex_lights[vert_idx];
|
|
|
|
|
u8 direct = (packed >> 24) & 0xFF;
|
|
|
|
|
u8 ao = (packed >> 16) & 0xFF;
|
|
|
|
|
f32 combined = (f32)direct + ((f32)ambient / 255.0f) * (f32)ao;
|
|
|
|
|
light = (u8)(combined > 255.0f ? 255.0f : combined);
|
2026-01-31 09:31:17 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_vertex vtx = {
|
|
|
|
|
.position = pos,
|
|
|
|
|
.normal = normal,
|
|
|
|
|
.u = u,
|
|
|
|
|
.v = v,
|
|
|
|
|
.color = 15,
|
|
|
|
|
.light = light,
|
|
|
|
|
};
|
|
|
|
|
pxl8_mesh_push_vertex(mesh, vtx);
|
|
|
|
|
num_verts++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (num_verts < 3) return;
|
|
|
|
|
|
|
|
|
|
for (u32 i = 1; i < num_verts - 1; i++) {
|
|
|
|
|
pxl8_mesh_push_triangle(mesh, base_idx, base_idx + i, base_idx + i + 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
demo3d_bsp_render_state* demo3d_bsp_render_state_create(u32 num_faces) {
|
|
|
|
|
demo3d_bsp_render_state* state = pxl8_calloc(1, sizeof(demo3d_bsp_render_state));
|
2026-01-31 09:31:17 -06:00
|
|
|
if (!state) return NULL;
|
|
|
|
|
|
|
|
|
|
state->num_faces = num_faces;
|
|
|
|
|
if (num_faces > 0) {
|
|
|
|
|
state->render_face_flags = pxl8_calloc(num_faces, 1);
|
|
|
|
|
if (!state->render_face_flags) {
|
|
|
|
|
pxl8_free(state);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 13:16:47 -05:00
|
|
|
state->mesh = pxl8_mesh_create(8192, 16384);
|
|
|
|
|
if (!state->mesh) {
|
|
|
|
|
pxl8_free(state->render_face_flags);
|
|
|
|
|
pxl8_free(state);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-31 09:31:17 -06:00
|
|
|
return state;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
void demo3d_bsp_render_state_destroy(demo3d_bsp_render_state* state) {
|
2026-01-31 09:31:17 -06:00
|
|
|
if (!state) return;
|
|
|
|
|
pxl8_free(state->materials);
|
2026-04-14 13:16:47 -05:00
|
|
|
pxl8_mesh_destroy(state->mesh);
|
2026-01-31 09:31:17 -06:00
|
|
|
pxl8_free(state->render_face_flags);
|
|
|
|
|
pxl8_free(state);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
void demo3d_bsp_render(pxl8_gfx* gfx, const demo3d_bsp* bsp,
|
|
|
|
|
demo3d_bsp_render_state* state,
|
2026-02-27 06:50:49 -06:00
|
|
|
const pxl8_gfx_draw_opts* opts) {
|
2026-02-02 17:48:25 -06:00
|
|
|
if (!gfx || !bsp || !state || bsp->num_faces == 0) return;
|
|
|
|
|
if (!state->materials || state->num_materials == 0) return;
|
2026-02-27 06:50:49 -06:00
|
|
|
if (!state->render_face_flags) return;
|
2026-01-31 09:31:17 -06:00
|
|
|
|
|
|
|
|
const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx);
|
2026-02-27 06:50:49 -06:00
|
|
|
if (!frustum) return;
|
2026-01-31 09:31:17 -06:00
|
|
|
|
2026-04-14 13:16:47 -05:00
|
|
|
pxl8_mesh* mesh = state->mesh;
|
2026-02-27 06:50:49 -06:00
|
|
|
if (!mesh) return;
|
2026-02-02 17:48:25 -06:00
|
|
|
|
2026-02-27 06:50:49 -06:00
|
|
|
u8 ambient = pxl8_gfx_get_ambient(gfx);
|
|
|
|
|
pxl8_mat4 identity = pxl8_mat4_identity();
|
2026-02-02 17:48:25 -06:00
|
|
|
|
2026-04-14 13:16:47 -05:00
|
|
|
u8 mat_has_faces[256] = {0};
|
|
|
|
|
for (u32 face_id = 0; face_id < bsp->num_faces; face_id++) {
|
|
|
|
|
if (!state->render_face_flags[face_id]) continue;
|
|
|
|
|
u16 mid = bsp->faces[face_id].material_id;
|
|
|
|
|
if (mid < state->num_materials) mat_has_faces[mid] = 1;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-27 06:50:49 -06:00
|
|
|
for (u32 mat = 0; mat < state->num_materials; mat++) {
|
2026-04-14 13:16:47 -05:00
|
|
|
if (!mat_has_faces[mat]) continue;
|
2026-02-02 17:48:25 -06:00
|
|
|
for (u32 face_id = 0; face_id < bsp->num_faces; face_id++) {
|
2026-02-27 06:50:49 -06:00
|
|
|
if (!state->render_face_flags[face_id]) continue;
|
2026-04-14 13:16:47 -05:00
|
|
|
if (bsp->faces[face_id].material_id != mat) continue;
|
2026-01-31 09:31:17 -06:00
|
|
|
if (!face_in_frustum(bsp, face_id, frustum)) continue;
|
2026-02-27 06:50:49 -06:00
|
|
|
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);
|
2026-01-31 09:31:17 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
void demo3d_bsp_set_material(demo3d_bsp_render_state* state, u16 material_id, const pxl8_gfx_material* material) {
|
2026-01-31 09:31:17 -06:00
|
|
|
if (!state || !material) return;
|
|
|
|
|
|
|
|
|
|
if (material_id >= state->num_materials) {
|
|
|
|
|
u32 new_count = material_id + 1;
|
|
|
|
|
pxl8_gfx_material* new_materials = pxl8_realloc(state->materials, new_count * sizeof(pxl8_gfx_material));
|
|
|
|
|
if (!new_materials) return;
|
|
|
|
|
|
|
|
|
|
for (u32 i = state->num_materials; i < new_count; i++) {
|
|
|
|
|
memset(&new_materials[i], 0, sizeof(pxl8_gfx_material));
|
|
|
|
|
new_materials[i].u_axis = (pxl8_vec3){1.0f, 0.0f, 0.0f};
|
|
|
|
|
if (i == 0 || i == 2) {
|
|
|
|
|
new_materials[i].v_axis = (pxl8_vec3){0.0f, 0.0f, 1.0f};
|
|
|
|
|
} else {
|
|
|
|
|
new_materials[i].v_axis = (pxl8_vec3){0.0f, 1.0f, 0.0f};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state->materials = new_materials;
|
|
|
|
|
state->num_materials = new_count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_vec3 u_axis = state->materials[material_id].u_axis;
|
|
|
|
|
pxl8_vec3 v_axis = state->materials[material_id].v_axis;
|
|
|
|
|
f32 u_offset = state->materials[material_id].u_offset;
|
|
|
|
|
f32 v_offset = state->materials[material_id].v_offset;
|
|
|
|
|
|
|
|
|
|
state->materials[material_id] = *material;
|
|
|
|
|
|
|
|
|
|
state->materials[material_id].u_axis = u_axis;
|
|
|
|
|
state->materials[material_id].v_axis = v_axis;
|
|
|
|
|
state->materials[material_id].u_offset = u_offset;
|
|
|
|
|
state->materials[material_id].v_offset = v_offset;
|
|
|
|
|
}
|