better lighting
This commit is contained in:
parent
805a2713a3
commit
6ed4e17065
75 changed files with 6417 additions and 3667 deletions
|
|
@ -1,980 +0,0 @@
|
|||
#include "pxl8_bsp.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_color.h"
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_io.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_mem.h"
|
||||
|
||||
#define BSP_VERSION 29
|
||||
|
||||
typedef enum {
|
||||
CHUNK_ENTITIES = 0,
|
||||
CHUNK_PLANES = 1,
|
||||
CHUNK_TEXTURES = 2,
|
||||
CHUNK_VERTICES = 3,
|
||||
CHUNK_VISIBILITY = 4,
|
||||
CHUNK_NODES = 5,
|
||||
CHUNK_TEXINFO = 6,
|
||||
CHUNK_FACES = 7,
|
||||
CHUNK_LIGHTING = 8,
|
||||
CHUNK_CLIPNODES = 9,
|
||||
CHUNK_LEAFS = 10,
|
||||
CHUNK_MARKSURFACES = 11,
|
||||
CHUNK_EDGES = 12,
|
||||
CHUNK_SURFEDGES = 13,
|
||||
CHUNK_MODELS = 14,
|
||||
CHUNK_COUNT = 15
|
||||
} pxl8_bsp_chunk_type;
|
||||
|
||||
typedef struct {
|
||||
u32 offset;
|
||||
u32 size;
|
||||
} pxl8_bsp_chunk;
|
||||
|
||||
typedef struct {
|
||||
u32 version;
|
||||
pxl8_bsp_chunk chunks[CHUNK_COUNT];
|
||||
} pxl8_bsp_header;
|
||||
|
||||
typedef struct {
|
||||
f32 x0, y0, x1, y1;
|
||||
} screen_rect;
|
||||
|
||||
typedef struct {
|
||||
u32 leaf;
|
||||
screen_rect window;
|
||||
} portal_queue_entry;
|
||||
|
||||
static inline pxl8_vec3 read_vec3(pxl8_stream* stream) {
|
||||
f32 x = pxl8_read_f32(stream);
|
||||
f32 y = pxl8_read_f32(stream);
|
||||
f32 z = pxl8_read_f32(stream);
|
||||
return (pxl8_vec3){x, z, y};
|
||||
}
|
||||
|
||||
static bool validate_chunk(const pxl8_bsp_chunk* chunk, u32 element_size, usize file_size) {
|
||||
if (chunk->size == 0) return true;
|
||||
if (chunk->offset >= file_size) return false;
|
||||
if (chunk->offset + chunk->size > file_size) return false;
|
||||
if (chunk->size % element_size != 0) return false;
|
||||
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;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
|
||||
if (!path || !bsp) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
memset(bsp, 0, sizeof(*bsp));
|
||||
|
||||
u8* file_data = NULL;
|
||||
usize file_size = 0;
|
||||
pxl8_result result = pxl8_io_read_binary_file(path, &file_data, &file_size);
|
||||
if (result != PXL8_OK) {
|
||||
pxl8_error("Failed to load BSP file: %s", path);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (file_size < sizeof(pxl8_bsp_header)) {
|
||||
pxl8_error("BSP file too small: %s", path);
|
||||
pxl8_free(file_data);
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
pxl8_stream stream = pxl8_stream_create(file_data, (u32)file_size);
|
||||
|
||||
pxl8_bsp_header header;
|
||||
header.version = pxl8_read_u32(&stream);
|
||||
|
||||
if (header.version != BSP_VERSION) {
|
||||
pxl8_error("Invalid BSP version: %u (expected %d)", header.version, BSP_VERSION);
|
||||
pxl8_free(file_data);
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
for (i32 i = 0; i < CHUNK_COUNT; i++) {
|
||||
header.chunks[i].offset = pxl8_read_u32(&stream);
|
||||
header.chunks[i].size = pxl8_read_u32(&stream);
|
||||
}
|
||||
|
||||
pxl8_bsp_chunk* chunk = &header.chunks[CHUNK_VERTICES];
|
||||
if (!validate_chunk(chunk, 12, file_size)) goto error_cleanup;
|
||||
bsp->num_vertices = chunk->size / 12;
|
||||
if (bsp->num_vertices > 0) {
|
||||
bsp->vertices = pxl8_calloc(bsp->num_vertices, sizeof(pxl8_bsp_vertex));
|
||||
if (!bsp->vertices) goto error_cleanup;
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_vertices; i++) {
|
||||
bsp->vertices[i].position = read_vec3(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_EDGES];
|
||||
if (!validate_chunk(chunk, 4, file_size)) goto error_cleanup;
|
||||
bsp->num_edges = chunk->size / 4;
|
||||
if (bsp->num_edges > 0) {
|
||||
bsp->edges = pxl8_calloc(bsp->num_edges, sizeof(pxl8_bsp_edge));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_edges; i++) {
|
||||
bsp->edges[i].vertex[0] = pxl8_read_u16(&stream);
|
||||
bsp->edges[i].vertex[1] = pxl8_read_u16(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_SURFEDGES];
|
||||
if (!validate_chunk(chunk, 4, file_size)) goto error_cleanup;
|
||||
bsp->num_surfedges = chunk->size / 4;
|
||||
if (bsp->num_surfedges > 0) {
|
||||
bsp->surfedges = pxl8_calloc(bsp->num_surfedges, sizeof(i32));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_surfedges; i++) {
|
||||
bsp->surfedges[i] = pxl8_read_i32(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_PLANES];
|
||||
if (!validate_chunk(chunk, 20, file_size)) goto error_cleanup;
|
||||
bsp->num_planes = chunk->size / 20;
|
||||
if (bsp->num_planes > 0) {
|
||||
bsp->planes = pxl8_calloc(bsp->num_planes, sizeof(pxl8_bsp_plane));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_planes; i++) {
|
||||
bsp->planes[i].normal = read_vec3(&stream);
|
||||
bsp->planes[i].dist = pxl8_read_f32(&stream);
|
||||
bsp->planes[i].type = pxl8_read_i32(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_TEXINFO];
|
||||
if (!validate_chunk(chunk, 40, file_size)) goto error_cleanup;
|
||||
bsp->num_materials = chunk->size / 40;
|
||||
if (bsp->num_materials > 0) {
|
||||
bsp->materials = pxl8_calloc(bsp->num_materials, sizeof(pxl8_gfx_material));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_materials; i++) {
|
||||
bsp->materials[i].u_axis = read_vec3(&stream);
|
||||
bsp->materials[i].u_offset = pxl8_read_f32(&stream);
|
||||
bsp->materials[i].v_axis = read_vec3(&stream);
|
||||
bsp->materials[i].v_offset = pxl8_read_f32(&stream);
|
||||
bsp->materials[i].texture_id = pxl8_read_u32(&stream);
|
||||
bsp->materials[i].alpha = 255;
|
||||
bsp->materials[i].dither = true;
|
||||
bsp->materials[i].dynamic_lighting = true;
|
||||
bsp->materials[i].double_sided = true;
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_FACES];
|
||||
if (!validate_chunk(chunk, 20, file_size)) goto error_cleanup;
|
||||
bsp->num_faces = chunk->size / 20;
|
||||
if (bsp->num_faces > 0) {
|
||||
bsp->faces = pxl8_calloc(bsp->num_faces, sizeof(pxl8_bsp_face));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_faces; i++) {
|
||||
bsp->faces[i].plane_id = pxl8_read_u16(&stream);
|
||||
bsp->faces[i].side = pxl8_read_u16(&stream);
|
||||
bsp->faces[i].first_edge = pxl8_read_u32(&stream);
|
||||
bsp->faces[i].num_edges = pxl8_read_u16(&stream);
|
||||
bsp->faces[i].material_id = pxl8_read_u16(&stream);
|
||||
bsp->faces[i].styles[0] = pxl8_read_u8(&stream);
|
||||
bsp->faces[i].styles[1] = pxl8_read_u8(&stream);
|
||||
bsp->faces[i].styles[2] = pxl8_read_u8(&stream);
|
||||
bsp->faces[i].styles[3] = pxl8_read_u8(&stream);
|
||||
bsp->faces[i].lightmap_offset = pxl8_read_u32(&stream);
|
||||
|
||||
bsp->faces[i].aabb_min = (pxl8_vec3){1e30f, 1e30f, 1e30f};
|
||||
bsp->faces[i].aabb_max = (pxl8_vec3){-1e30f, -1e30f, -1e30f};
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_NODES];
|
||||
if (!validate_chunk(chunk, 24, file_size)) goto error_cleanup;
|
||||
bsp->num_nodes = chunk->size / 24;
|
||||
if (bsp->num_nodes > 0) {
|
||||
bsp->nodes = pxl8_calloc(bsp->num_nodes, sizeof(pxl8_bsp_node));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_nodes; i++) {
|
||||
bsp->nodes[i].plane_id = pxl8_read_u32(&stream);
|
||||
bsp->nodes[i].children[0] = pxl8_read_i16(&stream);
|
||||
bsp->nodes[i].children[1] = pxl8_read_i16(&stream);
|
||||
i16 nx = pxl8_read_i16(&stream);
|
||||
i16 ny = pxl8_read_i16(&stream);
|
||||
i16 nz = pxl8_read_i16(&stream);
|
||||
bsp->nodes[i].mins[0] = nx;
|
||||
bsp->nodes[i].mins[1] = nz;
|
||||
bsp->nodes[i].mins[2] = ny;
|
||||
i16 mx = pxl8_read_i16(&stream);
|
||||
i16 my = pxl8_read_i16(&stream);
|
||||
i16 mz = pxl8_read_i16(&stream);
|
||||
bsp->nodes[i].maxs[0] = mx;
|
||||
bsp->nodes[i].maxs[1] = mz;
|
||||
bsp->nodes[i].maxs[2] = my;
|
||||
bsp->nodes[i].first_face = pxl8_read_u16(&stream);
|
||||
bsp->nodes[i].num_faces = pxl8_read_u16(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_LEAFS];
|
||||
if (!validate_chunk(chunk, 28, file_size)) goto error_cleanup;
|
||||
bsp->num_leafs = chunk->size / 28;
|
||||
if (bsp->num_leafs > 0) {
|
||||
bsp->leafs = pxl8_calloc(bsp->num_leafs, sizeof(pxl8_bsp_leaf));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_leafs; i++) {
|
||||
bsp->leafs[i].contents = pxl8_read_i32(&stream);
|
||||
bsp->leafs[i].visofs = pxl8_read_i32(&stream);
|
||||
i16 nx = pxl8_read_i16(&stream);
|
||||
i16 ny = pxl8_read_i16(&stream);
|
||||
i16 nz = pxl8_read_i16(&stream);
|
||||
bsp->leafs[i].mins[0] = nx;
|
||||
bsp->leafs[i].mins[1] = nz;
|
||||
bsp->leafs[i].mins[2] = ny;
|
||||
i16 mx = pxl8_read_i16(&stream);
|
||||
i16 my = pxl8_read_i16(&stream);
|
||||
i16 mz = pxl8_read_i16(&stream);
|
||||
bsp->leafs[i].maxs[0] = mx;
|
||||
bsp->leafs[i].maxs[1] = mz;
|
||||
bsp->leafs[i].maxs[2] = my;
|
||||
bsp->leafs[i].first_marksurface = pxl8_read_u16(&stream);
|
||||
bsp->leafs[i].num_marksurfaces = pxl8_read_u16(&stream);
|
||||
for (u32 j = 0; j < 4; j++) bsp->leafs[i].ambient_level[j] = pxl8_read_u8(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_MARKSURFACES];
|
||||
if (!validate_chunk(chunk, 2, file_size)) goto error_cleanup;
|
||||
bsp->num_marksurfaces = chunk->size / 2;
|
||||
if (bsp->num_marksurfaces > 0) {
|
||||
bsp->marksurfaces = pxl8_calloc(bsp->num_marksurfaces, sizeof(u16));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_marksurfaces; i++) {
|
||||
bsp->marksurfaces[i] = pxl8_read_u16(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_MODELS];
|
||||
if (!validate_chunk(chunk, 64, file_size)) goto error_cleanup;
|
||||
bsp->num_models = chunk->size / 64;
|
||||
if (bsp->num_models > 0) {
|
||||
bsp->models = pxl8_calloc(bsp->num_models, sizeof(pxl8_bsp_model));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_models; i++) {
|
||||
f32 minx = pxl8_read_f32(&stream);
|
||||
f32 miny = pxl8_read_f32(&stream);
|
||||
f32 minz = pxl8_read_f32(&stream);
|
||||
bsp->models[i].mins[0] = minx;
|
||||
bsp->models[i].mins[1] = minz;
|
||||
bsp->models[i].mins[2] = miny;
|
||||
f32 maxx = pxl8_read_f32(&stream);
|
||||
f32 maxy = pxl8_read_f32(&stream);
|
||||
f32 maxz = pxl8_read_f32(&stream);
|
||||
bsp->models[i].maxs[0] = maxx;
|
||||
bsp->models[i].maxs[1] = maxz;
|
||||
bsp->models[i].maxs[2] = maxy;
|
||||
bsp->models[i].origin = read_vec3(&stream);
|
||||
for (u32 j = 0; j < 4; j++) bsp->models[i].headnode[j] = pxl8_read_i32(&stream);
|
||||
bsp->models[i].visleafs = pxl8_read_i32(&stream);
|
||||
bsp->models[i].first_face = pxl8_read_i32(&stream);
|
||||
bsp->models[i].num_faces = pxl8_read_i32(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_VISIBILITY];
|
||||
if (!validate_chunk(chunk, 1, file_size)) goto error_cleanup;
|
||||
bsp->visdata_size = chunk->size;
|
||||
if (bsp->visdata_size > 0) {
|
||||
bsp->visdata = pxl8_malloc(bsp->visdata_size);
|
||||
memcpy(bsp->visdata, file_data + chunk->offset, bsp->visdata_size);
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_LIGHTING];
|
||||
if (!validate_chunk(chunk, 1, file_size)) goto error_cleanup;
|
||||
bsp->lightdata_size = chunk->size;
|
||||
if (bsp->lightdata_size > 0) {
|
||||
bsp->lightdata = pxl8_malloc(bsp->lightdata_size);
|
||||
memcpy(bsp->lightdata, file_data + chunk->offset, bsp->lightdata_size);
|
||||
}
|
||||
|
||||
pxl8_free(file_data);
|
||||
|
||||
for (u32 i = 0; i < bsp->num_faces; i++) {
|
||||
pxl8_bsp_face* face = &bsp->faces[i];
|
||||
f32 min_x = 1e30f, min_y = 1e30f, min_z = 1e30f;
|
||||
f32 max_x = -1e30f, max_y = -1e30f, max_z = -1e30f;
|
||||
|
||||
for (u32 j = 0; j < face->num_edges; j++) {
|
||||
i32 surfedge_idx = face->first_edge + j;
|
||||
u32 vert_idx;
|
||||
|
||||
if (!pxl8_bsp_get_edge_vertex(bsp, surfedge_idx, &vert_idx)) 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;
|
||||
}
|
||||
|
||||
face->aabb_min = (pxl8_vec3){min_x, min_y, min_z};
|
||||
face->aabb_max = (pxl8_vec3){max_x, max_y, max_z};
|
||||
}
|
||||
|
||||
pxl8_debug("Loaded BSP: %u verts, %u faces, %u nodes, %u leafs",
|
||||
bsp->num_vertices, bsp->num_faces, bsp->num_nodes, bsp->num_leafs);
|
||||
|
||||
return PXL8_OK;
|
||||
|
||||
error_cleanup:
|
||||
pxl8_error("BSP chunk validation failed: %s", path);
|
||||
pxl8_free(file_data);
|
||||
pxl8_bsp_destroy(bsp);
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
void pxl8_bsp_destroy(pxl8_bsp* bsp) {
|
||||
if (!bsp) return;
|
||||
|
||||
pxl8_free(bsp->cell_portals);
|
||||
pxl8_free(bsp->edges);
|
||||
pxl8_free(bsp->faces);
|
||||
pxl8_free(bsp->leafs);
|
||||
pxl8_free(bsp->lightdata);
|
||||
pxl8_free(bsp->marksurfaces);
|
||||
pxl8_free(bsp->materials);
|
||||
pxl8_free(bsp->models);
|
||||
pxl8_free(bsp->nodes);
|
||||
pxl8_free(bsp->planes);
|
||||
pxl8_free(bsp->render_face_flags);
|
||||
pxl8_free(bsp->surfedges);
|
||||
pxl8_free(bsp->vertex_lights);
|
||||
pxl8_free(bsp->vertices);
|
||||
pxl8_free(bsp->visdata);
|
||||
|
||||
memset(bsp, 0, sizeof(*bsp));
|
||||
}
|
||||
|
||||
i32 pxl8_bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos) {
|
||||
if (!bsp || bsp->num_nodes == 0) return -1;
|
||||
|
||||
i32 node_id = 0;
|
||||
|
||||
while (node_id >= 0) {
|
||||
const pxl8_bsp_node* node = &bsp->nodes[node_id];
|
||||
const pxl8_bsp_plane* plane = &bsp->planes[node->plane_id];
|
||||
|
||||
f32 dist = pxl8_vec3_dot(pos, plane->normal) - plane->dist;
|
||||
node_id = node->children[dist < 0 ? 1 : 0];
|
||||
}
|
||||
|
||||
return -(node_id + 1);
|
||||
}
|
||||
|
||||
bool pxl8_bsp_point_solid(const pxl8_bsp* bsp, pxl8_vec3 pos) {
|
||||
i32 leaf = pxl8_bsp_find_leaf(bsp, pos);
|
||||
if (leaf < 0 || (u32)leaf >= bsp->num_leafs) return true;
|
||||
return bsp->leafs[leaf].contents == -1;
|
||||
}
|
||||
|
||||
static bool point_clear(const pxl8_bsp* bsp, f32 x, f32 y, f32 z, f32 radius) {
|
||||
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z})) return false;
|
||||
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x + radius, y, z})) return false;
|
||||
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x - radius, y, z})) return false;
|
||||
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z + radius})) return false;
|
||||
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z - radius})) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
pxl8_vec3 pxl8_bsp_trace(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to, f32 radius) {
|
||||
if (!bsp || bsp->num_nodes == 0) return to;
|
||||
|
||||
if (point_clear(bsp, to.x, to.y, to.z, radius)) {
|
||||
return to;
|
||||
}
|
||||
|
||||
bool x_ok = point_clear(bsp, to.x, from.y, from.z, radius);
|
||||
bool z_ok = point_clear(bsp, from.x, from.y, to.z, radius);
|
||||
|
||||
if (x_ok && z_ok) {
|
||||
f32 dx = to.x - from.x;
|
||||
f32 dz = to.z - from.z;
|
||||
if (dx * dx > dz * dz) {
|
||||
return (pxl8_vec3){to.x, from.y, from.z};
|
||||
} else {
|
||||
return (pxl8_vec3){from.x, from.y, to.z};
|
||||
}
|
||||
} else if (x_ok) {
|
||||
return (pxl8_vec3){to.x, from.y, from.z};
|
||||
} else if (z_ok) {
|
||||
return (pxl8_vec3){from.x, from.y, to.z};
|
||||
}
|
||||
|
||||
return from;
|
||||
}
|
||||
|
||||
bool pxl8_bsp_is_leaf_visible(const pxl8_bsp* bsp, i32 leaf_from, i32 leaf_to) {
|
||||
if (!bsp || !bsp->visdata || bsp->visdata_size == 0) return true;
|
||||
if (leaf_from < 0 || leaf_to < 0) return true;
|
||||
if ((u32)leaf_from >= bsp->num_leafs || (u32)leaf_to >= bsp->num_leafs) return true;
|
||||
|
||||
i32 visofs = bsp->leafs[leaf_from].visofs;
|
||||
if (visofs < 0) return true;
|
||||
|
||||
u32 row_size = (bsp->num_leafs + 7) >> 3;
|
||||
u32 byte_idx = (u32)leaf_to >> 3;
|
||||
u32 bit_idx = (u32)leaf_to & 7;
|
||||
|
||||
u8* vis = bsp->visdata + visofs;
|
||||
u8* vis_end = bsp->visdata + bsp->visdata_size;
|
||||
u32 out = 0;
|
||||
|
||||
while (out < row_size && vis < vis_end) {
|
||||
if (*vis) {
|
||||
if (out == byte_idx) {
|
||||
return (*vis & (1 << bit_idx)) != 0;
|
||||
}
|
||||
out++;
|
||||
vis++;
|
||||
} else {
|
||||
vis++;
|
||||
if (vis >= vis_end) break;
|
||||
u32 count = *vis++;
|
||||
if (out + count > byte_idx && byte_idx >= out) {
|
||||
return false;
|
||||
}
|
||||
out += count;
|
||||
}
|
||||
}
|
||||
|
||||
return out > byte_idx ? false : true;
|
||||
}
|
||||
|
||||
pxl8_bsp_pvs pxl8_bsp_decompress_pvs(const pxl8_bsp* bsp, i32 leaf) {
|
||||
pxl8_bsp_pvs pvs = {0};
|
||||
|
||||
u32 row = (bsp->num_leafs + 7) >> 3;
|
||||
pvs.data = pxl8_malloc(row);
|
||||
pvs.size = row;
|
||||
|
||||
if (!pvs.data) return pvs;
|
||||
|
||||
if (!bsp || leaf < 0 || (u32)leaf >= bsp->num_leafs) {
|
||||
memset(pvs.data, 0xFF, row);
|
||||
return pvs;
|
||||
}
|
||||
|
||||
i32 visofs = bsp->leafs[leaf].visofs;
|
||||
if (visofs < 0 || !bsp->visdata || bsp->visdata_size == 0) {
|
||||
memset(pvs.data, 0xFF, row);
|
||||
return pvs;
|
||||
}
|
||||
|
||||
u8* in = bsp->visdata + visofs;
|
||||
u8* out = pvs.data;
|
||||
u8* out_end = pvs.data + row;
|
||||
|
||||
do {
|
||||
if (*in) {
|
||||
*out++ = *in++;
|
||||
} else {
|
||||
in++;
|
||||
i32 c = *in++;
|
||||
while (c > 0 && out < out_end) {
|
||||
*out++ = 0;
|
||||
c--;
|
||||
}
|
||||
}
|
||||
} while (out < out_end);
|
||||
|
||||
return pvs;
|
||||
}
|
||||
|
||||
void pxl8_bsp_pvs_destroy(pxl8_bsp_pvs* pvs) {
|
||||
if (pvs) {
|
||||
pxl8_free(pvs->data);
|
||||
pvs->data = NULL;
|
||||
pvs->size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool pxl8_bsp_pvs_is_visible(const pxl8_bsp_pvs* pvs, i32 leaf) {
|
||||
if (!pvs || !pvs->data || leaf < 0) return true;
|
||||
u32 byte_idx = (u32)leaf >> 3;
|
||||
u32 bit_idx = (u32)leaf & 7;
|
||||
if (byte_idx >= pvs->size) return true;
|
||||
return (pvs->data[byte_idx] & (1 << bit_idx)) != 0;
|
||||
}
|
||||
|
||||
pxl8_bsp_lightmap pxl8_bsp_lightmap_uniform(u8 r, u8 g, u8 b) {
|
||||
return (pxl8_bsp_lightmap){
|
||||
.color = {r, g, b},
|
||||
.height = 0,
|
||||
.offset = 0,
|
||||
.width = 0,
|
||||
};
|
||||
}
|
||||
|
||||
pxl8_bsp_lightmap pxl8_bsp_lightmap_mapped(u8 width, u8 height, u32 offset) {
|
||||
return (pxl8_bsp_lightmap){
|
||||
.color = {0, 0, 0},
|
||||
.height = height,
|
||||
.offset = offset,
|
||||
.width = width,
|
||||
};
|
||||
}
|
||||
|
||||
pxl8_bsp_lightmap_sample pxl8_bsp_sample_lightmap(const pxl8_bsp* bsp, u32 face_idx, f32 u, f32 v) {
|
||||
pxl8_bsp_lightmap_sample white = {255, 255, 255};
|
||||
|
||||
if (!bsp || !bsp->lightmaps || face_idx >= bsp->num_lightmaps) {
|
||||
return white;
|
||||
}
|
||||
|
||||
const pxl8_bsp_lightmap* lm = &bsp->lightmaps[face_idx];
|
||||
|
||||
if (lm->width == 0) {
|
||||
return (pxl8_bsp_lightmap_sample){lm->color[2], lm->color[1], lm->color[0]};
|
||||
}
|
||||
|
||||
if (!bsp->lightdata || bsp->lightdata_size == 0) {
|
||||
return white;
|
||||
}
|
||||
|
||||
f32 w = (f32)lm->width;
|
||||
f32 h = (f32)lm->height;
|
||||
f32 fx = u * w;
|
||||
f32 fy = v * h;
|
||||
|
||||
if (fx < 0) fx = 0;
|
||||
if (fx > w - 1.001f) fx = w - 1.001f;
|
||||
if (fy < 0) fy = 0;
|
||||
if (fy > h - 1.001f) fy = h - 1.001f;
|
||||
|
||||
u32 x0 = (u32)fx;
|
||||
u32 y0 = (u32)fy;
|
||||
u32 x1 = x0 + 1;
|
||||
u32 y1 = y0 + 1;
|
||||
if (x1 >= lm->width) x1 = lm->width - 1;
|
||||
if (y1 >= lm->height) y1 = lm->height - 1;
|
||||
|
||||
f32 frac_x = fx - (f32)x0;
|
||||
f32 frac_y = fy - (f32)y0;
|
||||
|
||||
u32 stride = lm->width;
|
||||
u32 base = lm->offset;
|
||||
|
||||
u32 idx00 = base + y0 * stride + x0;
|
||||
u32 idx10 = base + y0 * stride + x1;
|
||||
u32 idx01 = base + y1 * stride + x0;
|
||||
u32 idx11 = base + y1 * stride + x1;
|
||||
|
||||
u8 r00, g00, b00, r10, g10, b10, r01, g01, b01, r11, g11, b11;
|
||||
|
||||
if (idx00 < bsp->lightdata_size) pxl8_rgb332_unpack(bsp->lightdata[idx00], &r00, &g00, &b00);
|
||||
else { r00 = g00 = b00 = 255; }
|
||||
if (idx10 < bsp->lightdata_size) pxl8_rgb332_unpack(bsp->lightdata[idx10], &r10, &g10, &b10);
|
||||
else { r10 = g10 = b10 = 255; }
|
||||
if (idx01 < bsp->lightdata_size) pxl8_rgb332_unpack(bsp->lightdata[idx01], &r01, &g01, &b01);
|
||||
else { r01 = g01 = b01 = 255; }
|
||||
if (idx11 < bsp->lightdata_size) pxl8_rgb332_unpack(bsp->lightdata[idx11], &r11, &g11, &b11);
|
||||
else { r11 = g11 = b11 = 255; }
|
||||
|
||||
f32 inv_x = 1.0f - frac_x;
|
||||
f32 inv_y = 1.0f - frac_y;
|
||||
|
||||
u8 r = (u8)(r00 * inv_x * inv_y + r10 * frac_x * inv_y + r01 * inv_x * frac_y + r11 * frac_x * frac_y);
|
||||
u8 g = (u8)(g00 * inv_x * inv_y + g10 * frac_x * inv_y + g01 * inv_x * frac_y + g11 * frac_x * frac_y);
|
||||
u8 b = (u8)(b00 * inv_x * inv_y + b10 * frac_x * inv_y + b01 * inv_x * frac_y + b11 * frac_x * frac_y);
|
||||
|
||||
return (pxl8_bsp_lightmap_sample){b, g, r};
|
||||
}
|
||||
|
||||
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];
|
||||
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,
|
||||
u32 face_id,
|
||||
pxl8_mesh* mesh
|
||||
) {
|
||||
const pxl8_bsp_face* face = &bsp->faces[face_id];
|
||||
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 (face->material_id < bsp->num_materials) {
|
||||
material = &bsp->materials[face->material_id];
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (!pxl8_bsp_get_edge_vertex(bsp, surfedge_idx, &vert_idx)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pxl8_vec3 pos = bsp->vertices[vert_idx].position;
|
||||
|
||||
f32 u = 0.0f, v = 0.0f;
|
||||
if (material) {
|
||||
u = (pxl8_vec3_dot(pos, material->u_axis) + material->u_offset) / tex_scale;
|
||||
v = (pxl8_vec3_dot(pos, material->v_axis) + material->v_offset) / tex_scale;
|
||||
}
|
||||
|
||||
u8 light = 255;
|
||||
if (bsp->vertex_lights && vert_idx < bsp->num_vertex_lights) {
|
||||
light = (bsp->vertex_lights[vert_idx] >> 24) & 0xFF;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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, face_id, mesh);
|
||||
|
||||
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_vec3 camera_pos) {
|
||||
if (!gfx || !bsp || bsp->num_faces == 0) {
|
||||
return;
|
||||
}
|
||||
if (!bsp->cell_portals || bsp->num_cell_portals == 0) {
|
||||
pxl8_debug("[BSP] render: no cell_portals (ptr=%p count=%u)", (void*)bsp->cell_portals, bsp->num_cell_portals);
|
||||
return;
|
||||
}
|
||||
if (!bsp->materials || bsp->num_materials == 0) {
|
||||
pxl8_debug("[BSP] render: no materials (ptr=%p count=%u)", (void*)bsp->materials, bsp->num_materials);
|
||||
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;
|
||||
|
||||
pxl8_bsp* bsp_mut = (pxl8_bsp*)bsp;
|
||||
if (!bsp_mut->render_face_flags) {
|
||||
bsp_mut->render_face_flags = pxl8_calloc(bsp->num_faces, 1);
|
||||
if (!bsp_mut->render_face_flags) return;
|
||||
}
|
||||
memset(bsp_mut->render_face_flags, 0, bsp->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};
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_mesh* mesh = pxl8_mesh_create(8192, 16384);
|
||||
if (!mesh) {
|
||||
pxl8_free(visited);
|
||||
pxl8_free(cell_windows);
|
||||
pxl8_free(queue);
|
||||
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 (bsp_mut->render_face_flags[face_id]) continue;
|
||||
bsp_mut->render_face_flags[face_id] = 1;
|
||||
|
||||
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 >= bsp->num_materials) continue;
|
||||
|
||||
if (material_id != current_material && mesh->index_count > 0 && current_material < bsp->num_materials) {
|
||||
pxl8_mat4 identity = pxl8_mat4_identity();
|
||||
pxl8_3d_draw_mesh(gfx, mesh, &identity, &bsp->materials[current_material]);
|
||||
pxl8_mesh_clear(mesh);
|
||||
}
|
||||
|
||||
current_material = material_id;
|
||||
collect_face_to_mesh(bsp, face_id, mesh);
|
||||
}
|
||||
}
|
||||
|
||||
if (mesh->index_count > 0 && current_material < bsp->num_materials) {
|
||||
pxl8_mat4 identity = pxl8_mat4_identity();
|
||||
pxl8_3d_draw_mesh(gfx, mesh, &identity, &bsp->materials[current_material]);
|
||||
}
|
||||
|
||||
pxl8_bsp_pvs_destroy(&pvs);
|
||||
pxl8_free(visited);
|
||||
pxl8_free(cell_windows);
|
||||
pxl8_free(queue);
|
||||
pxl8_mesh_destroy(mesh);
|
||||
}
|
||||
|
||||
u32 pxl8_bsp_face_count(const pxl8_bsp* bsp) {
|
||||
if (!bsp) return 0;
|
||||
return bsp->num_faces;
|
||||
}
|
||||
|
||||
pxl8_vec3 pxl8_bsp_face_normal(const pxl8_bsp* bsp, u32 face_id) {
|
||||
pxl8_vec3 up = {0, 1, 0};
|
||||
if (!bsp || face_id >= bsp->num_faces) return up;
|
||||
|
||||
const pxl8_bsp_face* face = &bsp->faces[face_id];
|
||||
if (face->plane_id >= bsp->num_planes) return up;
|
||||
|
||||
pxl8_vec3 normal = bsp->planes[face->plane_id].normal;
|
||||
if (face->side) {
|
||||
normal.x = -normal.x;
|
||||
normal.y = -normal.y;
|
||||
normal.z = -normal.z;
|
||||
}
|
||||
return normal;
|
||||
}
|
||||
|
||||
void pxl8_bsp_face_set_material(pxl8_bsp* bsp, u32 face_id, u16 material_id) {
|
||||
if (!bsp || face_id >= bsp->num_faces) return;
|
||||
bsp->faces[face_id].material_id = material_id;
|
||||
}
|
||||
|
||||
void pxl8_bsp_set_material(pxl8_bsp* bsp, u16 material_id, const pxl8_gfx_material* material) {
|
||||
if (!bsp || !material) return;
|
||||
|
||||
if (material_id >= bsp->num_materials) {
|
||||
u32 new_count = material_id + 1;
|
||||
pxl8_gfx_material* new_materials = pxl8_realloc(bsp->materials, new_count * sizeof(pxl8_gfx_material));
|
||||
if (!new_materials) return;
|
||||
|
||||
for (u32 i = bsp->num_materials; i < new_count; i++) {
|
||||
memset(&new_materials[i], 0, sizeof(pxl8_gfx_material));
|
||||
}
|
||||
|
||||
bsp->materials = new_materials;
|
||||
bsp->num_materials = new_count;
|
||||
}
|
||||
|
||||
bsp->materials[material_id] = *material;
|
||||
}
|
||||
|
||||
void pxl8_bsp_set_wireframe(pxl8_bsp* bsp, bool wireframe) {
|
||||
if (!bsp || !bsp->materials) return;
|
||||
for (u32 i = 0; i < bsp->num_materials; i++) {
|
||||
bsp->materials[i].wireframe = wireframe;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue