refactor separate framework from game code, add demo3d

This commit is contained in:
asrael 2026-04-14 01:28:38 -05:00
parent 19ae869769
commit 40f5cdcaa5
92 changed files with 2665 additions and 6547 deletions

View file

@ -67,10 +67,6 @@ static const char embed_pxl8_transition[] = {
#embed "src/lua/pxl8/transition.lua"
, 0 };
static const char embed_pxl8_world[] = {
#embed "src/lua/pxl8/world.lua"
, 0 };
#define PXL8_EMBED_ENTRY(var, name) {name, var, sizeof(var) - 1}
typedef struct { const char* name; const char* data; u32 size; } pxl8_embed;
@ -92,7 +88,6 @@ static const pxl8_embed pxl8_embeds[] = {
PXL8_EMBED_ENTRY(embed_pxl8_sfx, "pxl8.sfx"),
PXL8_EMBED_ENTRY(embed_pxl8_tilemap, "pxl8.tilemap"),
PXL8_EMBED_ENTRY(embed_pxl8_transition, "pxl8.transition"),
PXL8_EMBED_ENTRY(embed_pxl8_world, "pxl8.world"),
{0}
};

View file

@ -1,594 +0,0 @@
#include "pxl8_bsp.h"
#include <string.h>
#include "pxl8_color.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;
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_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->lightmaps);
pxl8_free(bsp->marksurfaces);
pxl8_free(bsp->models);
pxl8_free(bsp->nodes);
pxl8_free(bsp->planes);
pxl8_free(bsp->surfedges);
pxl8_free(bsp->vertex_lights);
pxl8_free(bsp->vertices);
pxl8_free(bsp->visdata);
pxl8_free(bsp->heightfield);
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_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,
};
}
u8 pxl8_bsp_light_at(const pxl8_bsp* bsp, f32 x, f32 y, f32 z, u8 ambient) {
if (!bsp || !bsp->vertices || !bsp->vertex_lights) return 255;
f32 best_dist = FLT_MAX;
u32 best_idx = 0;
for (u32 i = 0; i < bsp->num_vertices; i++) {
f32 dx = bsp->vertices[i].position.x - x;
f32 dy = bsp->vertices[i].position.y - y;
f32 dz = bsp->vertices[i].position.z - z;
f32 dist = dx * dx + dy * dy + dz * dz;
if (dist < best_dist) {
best_dist = dist;
best_idx = i;
}
}
if (best_idx >= bsp->num_vertex_lights) return 255;
u32 packed = bsp->vertex_lights[best_idx];
u8 direct = (packed >> 24) & 0xFF;
u8 ao = (packed >> 16) & 0xFF;
f32 combined = (f32)direct + ((f32)ambient / 255.0f) * (f32)ao;
return (u8)(combined > 255.0f ? 255.0f : combined);
}
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};
}
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;
}

View file

@ -1,162 +0,0 @@
#pragma once
#include "pxl8_math.h"
#include "pxl8_types.h"
typedef struct pxl8_bsp_edge {
u16 vertex[2];
} pxl8_bsp_edge;
typedef struct pxl8_bsp_face {
u32 first_edge;
u32 lightmap_offset;
u16 num_edges;
u16 plane_id;
u16 side;
u8 styles[4];
u16 material_id;
pxl8_vec3 aabb_min;
pxl8_vec3 aabb_max;
} pxl8_bsp_face;
typedef struct pxl8_bsp_leaf {
u8 ambient_level[4];
i32 contents;
u16 first_marksurface;
i16 maxs[3];
i16 mins[3];
u16 num_marksurfaces;
i32 visofs;
} pxl8_bsp_leaf;
typedef struct pxl8_bsp_model {
i32 first_face;
i32 headnode[4];
f32 maxs[3];
f32 mins[3];
i32 num_faces;
pxl8_vec3 origin;
i32 visleafs;
} pxl8_bsp_model;
typedef struct pxl8_bsp_node {
i32 children[2];
u16 first_face;
i16 maxs[3];
i16 mins[3];
u16 num_faces;
u32 plane_id;
} pxl8_bsp_node;
typedef struct pxl8_bsp_plane {
f32 dist;
pxl8_vec3 normal;
i32 type;
} pxl8_bsp_plane;
typedef struct pxl8_bsp_vertex {
pxl8_vec3 position;
} pxl8_bsp_vertex;
typedef struct pxl8_bsp_lightmap {
u8 color[3];
u8 height;
u32 offset;
u8 width;
} pxl8_bsp_lightmap;
typedef struct pxl8_bsp_lightmap_sample {
u8 b;
u8 g;
u8 r;
} pxl8_bsp_lightmap_sample;
typedef struct pxl8_bsp_pvs {
u8* data;
u32 size;
} pxl8_bsp_pvs;
typedef struct pxl8_bsp_portal {
f32 x0, z0;
f32 x1, z1;
u32 target_leaf;
} pxl8_bsp_portal;
typedef struct pxl8_bsp_cell_portals {
pxl8_bsp_portal portals[4];
u8 num_portals;
} pxl8_bsp_cell_portals;
typedef struct pxl8_bsp {
pxl8_bsp_cell_portals* cell_portals;
pxl8_bsp_edge* edges;
pxl8_bsp_face* faces;
pxl8_bsp_leaf* leafs;
u8* lightdata;
pxl8_bsp_lightmap* lightmaps;
u16* marksurfaces;
pxl8_bsp_model* models;
pxl8_bsp_node* nodes;
pxl8_bsp_plane* planes;
i32* surfedges;
u32* vertex_lights;
pxl8_bsp_vertex* vertices;
u8* visdata;
f32* heightfield;
u32 lightdata_size;
u32 num_cell_portals;
u32 num_edges;
u32 num_faces;
u32 num_leafs;
u32 num_lightmaps;
u32 num_marksurfaces;
u32 num_models;
u32 num_nodes;
u32 num_planes;
u32 num_surfedges;
u32 num_vertex_lights;
u32 num_vertices;
u32 num_heightfield;
f32 heightfield_ox;
f32 heightfield_oz;
f32 heightfield_cell_size;
u16 heightfield_w;
u16 heightfield_h;
u32 visdata_size;
f32 bounds_min_x;
f32 bounds_min_z;
f32 bounds_max_x;
f32 bounds_max_z;
} pxl8_bsp;
#ifdef __cplusplus
extern "C" {
#endif
pxl8_bsp_pvs pxl8_bsp_decompress_pvs(const pxl8_bsp* bsp, i32 leaf);
void pxl8_bsp_destroy(pxl8_bsp* bsp);
u32 pxl8_bsp_face_count(const pxl8_bsp* bsp);
pxl8_vec3 pxl8_bsp_face_normal(const pxl8_bsp* bsp, u32 face_id);
void pxl8_bsp_face_set_material(pxl8_bsp* bsp, u32 face_id, u16 material_id);
i32 pxl8_bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos);
bool pxl8_bsp_is_leaf_visible(const pxl8_bsp* bsp, i32 leaf_from, i32 leaf_to);
pxl8_bsp_lightmap pxl8_bsp_lightmap_mapped(u8 width, u8 height, u32 offset);
pxl8_bsp_lightmap pxl8_bsp_lightmap_uniform(u8 r, u8 g, u8 b);
pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp);
void pxl8_bsp_pvs_destroy(pxl8_bsp_pvs* pvs);
bool pxl8_bsp_pvs_is_visible(const pxl8_bsp_pvs* pvs, i32 leaf);
u8 pxl8_bsp_light_at(const pxl8_bsp* bsp, f32 x, f32 y, f32 z, u8 ambient);
pxl8_bsp_lightmap_sample pxl8_bsp_sample_lightmap(const pxl8_bsp* bsp, u32 face_idx, f32 u, f32 v);
#ifdef __cplusplus
}
#endif

View file

@ -1,213 +0,0 @@
#include "pxl8_bsp_render.h"
#include <string.h>
#include "pxl8_gfx3d.h"
#include "pxl8_log.h"
#include "pxl8_mem.h"
#include "pxl8_mesh.h"
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 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 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];
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;
if (!pxl8_bsp_get_edge_vertex(bsp, surfedge_idx, &vert_idx)) {
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) {
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);
}
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);
}
}
pxl8_bsp_render_state* pxl8_bsp_render_state_create(u32 num_faces) {
pxl8_bsp_render_state* state = pxl8_calloc(1, sizeof(pxl8_bsp_render_state));
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;
}
}
return state;
}
void pxl8_bsp_render_state_destroy(pxl8_bsp_render_state* state) {
if (!state) return;
pxl8_free(state->materials);
pxl8_free(state->render_face_flags);
pxl8_free(state);
}
void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp,
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);
if (!frustum) return;
pxl8_mesh* mesh = pxl8_mesh_create(8192, 16384);
if (!mesh) return;
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];
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);
}
}
pxl8_mesh_destroy(mesh);
}
void pxl8_bsp_set_material(pxl8_bsp_render_state* state, u16 material_id, const pxl8_gfx_material* material) {
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;
}

View file

@ -1,29 +0,0 @@
#pragma once
#include "pxl8_bsp.h"
#include "pxl8_gfx.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct pxl8_bsp_render_state {
pxl8_gfx_material* materials;
u8* render_face_flags;
u32 num_materials;
u32 num_faces;
bool exterior;
} pxl8_bsp_render_state;
pxl8_bsp_render_state* pxl8_bsp_render_state_create(u32 num_faces);
void pxl8_bsp_render_state_destroy(pxl8_bsp_render_state* state);
void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp,
pxl8_bsp_render_state* state,
const pxl8_gfx_draw_opts* opts);
void pxl8_bsp_set_material(pxl8_bsp_render_state* state, u16 material_id,
const pxl8_gfx_material* material);
#ifdef __cplusplus
}
#endif

View file

@ -7,7 +7,7 @@
#include "pxl8_ase.h"
#include "pxl8_game.h"
#include "pxl8_hal.h"
#include "pxl8_platform.h"
#include "pxl8_log.h"
#include "pxl8_macros.h"
#include "pxl8_mem.h"
@ -16,14 +16,13 @@
#include "pxl8_script.h"
#include "pxl8_sfx.h"
#include "pxl8_sys.h"
#include "pxl8_world.h"
struct pxl8 {
pxl8_cart* cart;
pxl8_game* game;
pxl8_repl* repl;
pxl8_log log;
const pxl8_hal* hal;
const pxl8_platform* platform;
void* platform_data;
};
@ -36,19 +35,19 @@ static void pxl8_audio_event_callback(u8 event_type, u8 context_id, u8 note, f32
}
#endif
pxl8* pxl8_create(const pxl8_hal* hal) {
pxl8* pxl8_create(const pxl8_platform* platform) {
pxl8* sys = (pxl8*)pxl8_calloc(1, sizeof(pxl8));
if (!sys) return NULL;
pxl8_log_init(&sys->log);
if (!hal) {
pxl8_error("hal cannot be null");
if (!platform) {
pxl8_error("platform cannot be null");
pxl8_free(sys);
return NULL;
}
sys->hal = hal;
sys->platform = platform;
sys->game = (pxl8_game*)pxl8_calloc(1, sizeof(pxl8_game));
if (!sys->game) {
@ -65,7 +64,7 @@ void pxl8_destroy(pxl8* sys) {
if (sys->game) pxl8_free(sys->game);
if (sys->cart) pxl8_cart_destroy(sys->cart);
if (sys->hal && sys->platform_data) sys->hal->destroy(sys->platform_data);
if (sys->platform && sys->platform_data) sys->platform->destroy(sys->platform_data);
pxl8_free(sys);
}
@ -227,42 +226,35 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
pxl8_size window_size = pxl8_cart_get_window_size(sys->cart);
pxl8_size render_size = pxl8_get_resolution_dimensions(resolution);
sys->platform_data = sys->hal->create(render_size.w, render_size.h, window_title, window_size.w, window_size.h);
sys->platform_data = sys->platform->create(render_size.w, render_size.h, window_title, window_size.w, window_size.h);
if (!sys->platform_data) {
pxl8_error("failed to create platform context");
return PXL8_ERROR_INITIALIZATION_FAILED;
}
game->gfx = pxl8_gfx_create(sys->hal, sys->platform_data, resolution);
game->gfx = pxl8_gfx_create(sys->platform, sys->platform_data, resolution);
if (!game->gfx) {
pxl8_error("failed to create graphics context");
return PXL8_ERROR_INITIALIZATION_FAILED;
}
game->mixer = pxl8_sfx_mixer_create(sys->hal);
game->mixer = pxl8_sfx_mixer_create(sys->platform);
if (!game->mixer) {
pxl8_error("failed to create audio mixer");
return PXL8_ERROR_INITIALIZATION_FAILED;
}
pxl8_rng_seed(&game->rng, (u32)sys->hal->get_ticks());
pxl8_rng_seed(&game->rng, (u32)sys->platform->get_ticks());
game->world = pxl8_world_create();
if (!game->world) {
pxl8_error("failed to create world");
return PXL8_ERROR_INITIALIZATION_FAILED;
}
pxl8_net_config net_cfg = { .address = "127.0.0.1", .port = 7777 };
game->net = pxl8_net_create(&net_cfg);
if (game->net) {
pxl8_net_set_chunk_cache(game->net, pxl8_world_get_chunk_cache(game->world));
pxl8_net_set_world(game->net, game->world);
pxl8_net_connect(game->net);
if (game->hooks.init) {
pxl8_result hook_result = game->hooks.init(game, game->hooks.userdata);
if (hook_result != PXL8_OK) {
return hook_result;
}
}
#ifndef NDEBUG
game->debug_stats = true;
game->debug_stats = false;
game->debug_replay = pxl8_replay_create_buffer(60, 60);
pxl8_sfx_mixer_set_event_callback(game->mixer, pxl8_audio_event_callback, game);
#endif
@ -289,18 +281,9 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
}
}
game->last_time = sys->hal->get_ticks();
game->last_time = sys->platform->get_ticks();
game->running = true;
#ifdef PXL8_ASYNC_THREADS
if (game->net) {
pxl8_net_start_thread(game->net);
}
if (game->world) {
pxl8_world_start_sim_thread(game->world, game->net);
}
#endif
return PXL8_OK;
}
@ -310,7 +293,7 @@ pxl8_result pxl8_update(pxl8* sys) {
}
pxl8_game* game = sys->game;
u64 current_time = sys->hal->get_ticks();
u64 current_time = sys->platform->get_ticks();
f32 dt = (f32)(current_time - game->last_time) / 1000000000.0f;
game->last_time = current_time;
@ -364,16 +347,9 @@ pxl8_result pxl8_update(pxl8* sys) {
}
}
#ifdef PXL8_ASYNC_THREADS
pxl8_net_update(game->net, dt);
#else
if (game->net) {
while (pxl8_net_poll(game->net)) {}
pxl8_net_update(game->net, dt);
pxl8_world_sync(game->world, game->net);
if (game->hooks.update) {
game->hooks.update(game, dt, game->hooks.userdata);
}
pxl8_world_update(game->world, dt);
#endif
pxl8_gfx_update(game->gfx, dt);
pxl8_sfx_mixer_process(game->mixer);
@ -412,30 +388,10 @@ pxl8_result pxl8_frame(pxl8* sys) {
if (game->debug_stats) {
const pxl8_gfx_stats* stats = pxl8_gfx_get_stats(game->gfx);
char buf[64];
i32 y = 4;
if (stats) {
char buf[32];
snprintf(buf, sizeof(buf), "FPS: %.0f", stats->fps);
pxl8_2d_text(game->gfx, buf, 4, y, 15);
y += 10;
pxl8_sim_entity* player = pxl8_world_local_player(game->world);
if (player) {
snprintf(buf, sizeof(buf), "Pos: %.0f,%.0f,%.0f",
player->pos.x, player->pos.y, player->pos.z);
pxl8_2d_text(game->gfx, buf, 4, y, 15);
y += 10;
}
snprintf(buf, sizeof(buf), "Draw: %llu Tri: %llu",
(unsigned long long)stats->draw_calls,
(unsigned long long)stats->triangles);
pxl8_2d_text(game->gfx, buf, 4, y, 15);
y += 10;
snprintf(buf, sizeof(buf), "Raster: %.2fms", stats->raster_ms);
pxl8_2d_text(game->gfx, buf, 4, y, 15);
pxl8_2d_text(game->gfx, buf, 4, 4, 15);
}
}
@ -478,14 +434,9 @@ void pxl8_quit(pxl8* sys) {
pxl8_info("Shutting down");
#ifdef PXL8_ASYNC_THREADS
if (game->world) {
pxl8_world_stop_sim_thread(game->world);
if (game->hooks.quit) {
game->hooks.quit(game, game->hooks.userdata);
}
if (game->net) {
pxl8_net_stop_thread(game->net);
}
#endif
if (sys->cart) {
pxl8_cart_unmount(sys->cart);
@ -495,9 +446,6 @@ void pxl8_quit(pxl8* sys) {
pxl8_replay_destroy(game->debug_replay);
#endif
if (game->net) pxl8_net_destroy(game->net);
if (game->world) pxl8_world_destroy(game->world);
pxl8_sfx_mixer_destroy(game->mixer);
pxl8_gfx_destroy(game->gfx);
pxl8_script_destroy(game->script);
@ -517,16 +465,16 @@ void pxl8_set_running(pxl8* sys, bool running) {
}
}
pxl8_world* pxl8_get_world(pxl8* sys) {
return (sys && sys->game) ? sys->game->world : NULL;
}
f32 pxl8_get_fps(const pxl8* sys) {
if (!sys || !sys->game) return 0.0f;
const pxl8_gfx_stats* stats = pxl8_gfx_get_stats(sys->game->gfx);
return stats ? stats->fps : 0.0f;
}
pxl8_game* pxl8_get_game(pxl8* sys) {
return sys ? sys->game : NULL;
}
pxl8_gfx* pxl8_get_gfx(const pxl8* sys) {
return (sys && sys->game) ? sys->game->gfx : NULL;
}
@ -535,27 +483,23 @@ pxl8_input_state* pxl8_get_input(const pxl8* sys) {
return (sys && sys->game) ? &sys->game->input : NULL;
}
pxl8_net* pxl8_get_net(const pxl8* sys) {
return (sys && sys->game) ? sys->game->net : NULL;
}
pxl8_sfx_mixer* pxl8_get_sfx_mixer(const pxl8* sys) {
return (sys && sys->game) ? sys->game->mixer : NULL;
}
void pxl8_center_cursor(pxl8* sys) {
if (!sys || !sys->hal || !sys->hal->center_cursor) return;
sys->hal->center_cursor(sys->platform_data);
if (!sys || !sys->platform || !sys->platform->center_cursor) return;
sys->platform->center_cursor(sys->platform_data);
}
void pxl8_set_cursor(pxl8* sys, pxl8_cursor cursor) {
if (!sys || !sys->hal || !sys->hal->set_cursor) return;
sys->hal->set_cursor(sys->platform_data, cursor);
if (!sys || !sys->platform || !sys->platform->set_cursor) return;
sys->platform->set_cursor(sys->platform_data, cursor);
}
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 || !sys->platform || !sys->platform->set_relative_mouse_mode) return;
sys->platform->set_relative_mouse_mode(sys->platform_data, enabled);
if (sys->game) {
sys->game->input.mouse_relative_mode = enabled;
}
@ -573,3 +517,13 @@ pxl8_size pxl8_get_resolution_dimensions(pxl8_resolution resolution) {
default: return (pxl8_size){640, 360};
}
}
__attribute__((weak)) void pxl8_register_game(pxl8* sys) {
(void)sys;
}
void pxl8_set_game_hooks(pxl8* sys, pxl8_game_hooks hooks) {
if (sys && sys->game) {
sys->game->hooks = hooks;
}
}

View file

@ -1,15 +1,22 @@
#pragma once
#include "pxl8_gfx.h"
#include "pxl8_net.h"
#include "pxl8_rng.h"
#include "pxl8_script.h"
#include "pxl8_sfx.h"
#include "pxl8_types.h"
#include "pxl8_world.h"
typedef struct pxl8_replay pxl8_replay;
typedef struct pxl8_game pxl8_game;
typedef struct pxl8_game_hooks {
pxl8_result (*init)(pxl8_game* game, void* userdata);
void (*update)(pxl8_game* game, f32 dt, void* userdata);
void (*quit)(pxl8_game* game, void* userdata);
void* userdata;
} pxl8_game_hooks;
typedef struct pxl8_game {
#ifndef NDEBUG
pxl8_replay* debug_replay;
@ -19,10 +26,10 @@ typedef struct pxl8_game {
f32 dt;
i32 frame_count;
pxl8_gfx* gfx;
pxl8_game_hooks hooks;
pxl8_input_state input;
u64 last_time;
pxl8_sfx_mixer* mixer;
pxl8_net* net;
pxl8_input_state prev_input;
bool repl_mode;
bool repl_started;
@ -32,5 +39,5 @@ typedef struct pxl8_game {
bool script_loaded;
char script_path[256];
f32 time;
pxl8_world* world;
void* userdata;
} pxl8_game;

View file

@ -1,9 +1,9 @@
#pragma once
#include "pxl8_game.h"
#include "pxl8_gfx.h"
#include "pxl8_hal.h"
#include "pxl8_platform.h"
#include "pxl8_io.h"
#include "pxl8_net.h"
#include "pxl8_sfx.h"
#include "pxl8_types.h"
@ -13,7 +13,7 @@ typedef struct pxl8 pxl8;
extern "C" {
#endif
pxl8* pxl8_create(const pxl8_hal* hal);
pxl8* pxl8_create(const pxl8_platform* platform);
void pxl8_destroy(pxl8* sys);
pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]);
@ -22,15 +22,18 @@ pxl8_result pxl8_frame(pxl8* sys);
void pxl8_quit(pxl8* sys);
f32 pxl8_get_fps(const pxl8* sys);
pxl8_game* pxl8_get_game(pxl8* sys);
pxl8_gfx* pxl8_get_gfx(const pxl8* sys);
pxl8_input_state* pxl8_get_input(const pxl8* sys);
pxl8_net* pxl8_get_net(const pxl8* sys);
pxl8_size pxl8_get_resolution_dimensions(pxl8_resolution resolution);
pxl8_sfx_mixer* pxl8_get_sfx_mixer(const pxl8* sys);
bool pxl8_is_running(const pxl8* sys);
void pxl8_register_game(pxl8* sys);
void pxl8_center_cursor(pxl8* sys);
void pxl8_set_cursor(pxl8* sys, pxl8_cursor cursor);
void pxl8_set_game_hooks(pxl8* sys, pxl8_game_hooks hooks);
void pxl8_set_relative_mouse_mode(pxl8* sys, bool enabled);
void pxl8_set_running(pxl8* sys, bool running);

View file

@ -1,41 +0,0 @@
#pragma once
#include "pxl8_math.h"
#include "pxl8_types.h"
typedef enum pxl8_3d_camera_mode {
PXL8_3D_CAMERA_ORTHO,
PXL8_3D_CAMERA_PERSPECTIVE
} pxl8_3d_camera_mode;
typedef struct pxl8_3d_camera pxl8_3d_camera;
#ifdef __cplusplus
extern "C" {
#endif
pxl8_3d_camera* pxl8_3d_camera_create(void);
void pxl8_3d_camera_destroy(pxl8_3d_camera* cam);
void pxl8_3d_camera_lookat(pxl8_3d_camera* cam, pxl8_vec3 eye, pxl8_vec3 target, pxl8_vec3 up);
void pxl8_3d_camera_set_ortho(pxl8_3d_camera* cam, f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far);
void pxl8_3d_camera_set_perspective(pxl8_3d_camera* cam, f32 fov, f32 aspect, f32 near, f32 far);
void pxl8_3d_camera_set_position(pxl8_3d_camera* cam, pxl8_vec3 pos);
void pxl8_3d_camera_set_rotation(pxl8_3d_camera* cam, f32 pitch, f32 yaw, f32 roll);
pxl8_vec3 pxl8_3d_camera_get_forward(const pxl8_3d_camera* cam);
pxl8_vec3 pxl8_3d_camera_get_position(const pxl8_3d_camera* cam);
pxl8_mat4 pxl8_3d_camera_get_projection(const pxl8_3d_camera* cam);
pxl8_vec3 pxl8_3d_camera_get_right(const pxl8_3d_camera* cam);
pxl8_vec3 pxl8_3d_camera_get_up(const pxl8_3d_camera* cam);
pxl8_mat4 pxl8_3d_camera_get_view(const pxl8_3d_camera* cam);
void pxl8_3d_camera_blend(pxl8_3d_camera* dest, const pxl8_3d_camera* a, const pxl8_3d_camera* b, f32 t);
void pxl8_3d_camera_follow(pxl8_3d_camera* cam, pxl8_vec3 target, pxl8_vec3 offset, f32 smoothing, f32 dt);
pxl8_projected_point pxl8_3d_camera_world_to_screen(const pxl8_3d_camera* cam, pxl8_vec3 world_pos, u32 screen_width, u32 screen_height);
void pxl8_3d_camera_shake(pxl8_3d_camera* cam, f32 intensity, f32 duration);
void pxl8_3d_camera_update(pxl8_3d_camera* cam, f32 dt);
#ifdef __cplusplus
}
#endif

View file

@ -1,8 +1,8 @@
#include "pxl8_render.h"
#include "pxl8_blit3d.h"
#include "pxl8_atlas.h"
#include "pxl8_colormap.h"
#include "pxl8_dither.h"
#include "pxl8_hal.h"
#include "pxl8_platform.h"
#include "pxl8_log.h"
#include "pxl8_mem.h"
#include "pxl8_mesh.h"
@ -794,7 +794,7 @@ typedef struct {
bool active;
} texture_slot;
struct pxl8_renderer {
struct pxl8_blit3d {
u32 width;
u32 height;
u8* stencil;
@ -828,8 +828,8 @@ struct pxl8_gfx_cmdbuf {
u32 count;
};
pxl8_renderer* pxl8_renderer_create(u32 width, u32 height) {
pxl8_renderer* r = pxl8_calloc(1, sizeof(pxl8_renderer));
pxl8_blit3d* pxl8_blit3d_create(u32 width, u32 height) {
pxl8_blit3d* r = pxl8_calloc(1, sizeof(pxl8_blit3d));
r->width = width;
r->height = height;
r->stencil = pxl8_calloc(width * height, 1);
@ -840,7 +840,7 @@ pxl8_renderer* pxl8_renderer_create(u32 width, u32 height) {
return r;
}
void pxl8_renderer_destroy(pxl8_renderer* r) {
void pxl8_blit3d_destroy(pxl8_blit3d* r) {
if (!r) return;
for (u32 i = 0; i < PXL8_GFX_MAX_TEXTURES; i++) {
if (r->textures[i].data) pxl8_free(r->textures[i].data);
@ -852,7 +852,7 @@ void pxl8_renderer_destroy(pxl8_renderer* r) {
pxl8_free(r);
}
void pxl8_renderer_update_stats(pxl8_renderer* r, f32 dt) {
void pxl8_blit3d_update_stats(pxl8_blit3d* r, f32 dt) {
if (!r) return;
r->stats.dt_accumulator += dt;
@ -870,7 +870,7 @@ void pxl8_renderer_update_stats(pxl8_renderer* r, f32 dt) {
r->stats.triangles = 0;
}
pxl8_gfx_stats* pxl8_renderer_get_stats(pxl8_renderer* r) {
pxl8_gfx_stats* pxl8_blit3d_get_stats(pxl8_blit3d* r) {
return r ? &r->stats : NULL;
}
@ -882,7 +882,7 @@ static u32 texture_byte_size(pxl8_gfx_texture_format fmt, u32 w, u32 h) {
return 0;
}
pxl8_gfx_texture pxl8_create_texture(pxl8_renderer* r, const pxl8_gfx_texture_desc* desc) {
pxl8_gfx_texture pxl8_create_texture(pxl8_blit3d* r, const pxl8_gfx_texture_desc* desc) {
for (u32 i = 0; i < PXL8_GFX_MAX_TEXTURES; i++) {
if (!r->textures[i].active) {
texture_slot* s = &r->textures[i];
@ -906,7 +906,7 @@ pxl8_gfx_texture pxl8_create_texture(pxl8_renderer* r, const pxl8_gfx_texture_de
return (pxl8_gfx_texture){ PXL8_GFX_INVALID_ID };
}
pxl8_gfx_buffer pxl8_create_buffer(pxl8_renderer* r, const pxl8_gfx_buffer_desc* desc) {
pxl8_gfx_buffer pxl8_create_buffer(pxl8_blit3d* r, const pxl8_gfx_buffer_desc* desc) {
for (u32 i = 0; i < PXL8_GFX_MAX_BUFFERS; i++) {
if (!r->buffers[i].active) {
buffer_slot* s = &r->buffers[i];
@ -933,7 +933,7 @@ pxl8_gfx_buffer pxl8_create_buffer(pxl8_renderer* r, const pxl8_gfx_buffer_desc*
return (pxl8_gfx_buffer){ PXL8_GFX_INVALID_ID };
}
pxl8_gfx_pipeline pxl8_create_pipeline(pxl8_renderer* r, const pxl8_gfx_pipeline_desc* desc) {
pxl8_gfx_pipeline pxl8_create_pipeline(pxl8_blit3d* r, const pxl8_gfx_pipeline_desc* desc) {
u32 hash = pipeline_desc_hash(desc);
u32 bucket = hash & (PXL8_PIPELINE_CACHE_SIZE - 1);
pipeline_cache_entry* ce = &r->pipeline_cache[bucket];
@ -974,7 +974,7 @@ pxl8_gfx_pipeline pxl8_create_pipeline(pxl8_renderer* r, const pxl8_gfx_pipeline
return (pxl8_gfx_pipeline){ PXL8_GFX_INVALID_ID };
}
pxl8_gfx_bindings pxl8_create_bindings(pxl8_renderer* r, const pxl8_gfx_bindings_desc* desc) {
pxl8_gfx_bindings pxl8_create_bindings(pxl8_blit3d* r, const pxl8_gfx_bindings_desc* desc) {
for (u32 i = 0; i < PXL8_GFX_MAX_BINDINGS; i++) {
if (!r->bindings[i].active) {
bindings_slot* s = &r->bindings[i];
@ -988,7 +988,7 @@ pxl8_gfx_bindings pxl8_create_bindings(pxl8_renderer* r, const pxl8_gfx_bindings
return (pxl8_gfx_bindings){ PXL8_GFX_INVALID_ID };
}
pxl8_gfx_pass pxl8_create_pass(pxl8_renderer* r, const pxl8_gfx_pass_desc* desc) {
pxl8_gfx_pass pxl8_create_pass(pxl8_blit3d* r, const pxl8_gfx_pass_desc* desc) {
for (u32 i = 0; i < PXL8_GFX_MAX_PASSES; i++) {
if (!r->passes[i].active) {
pass_slot* s = &r->passes[i];
@ -1022,7 +1022,7 @@ pxl8_gfx_pass pxl8_create_pass(pxl8_renderer* r, const pxl8_gfx_pass_desc* desc)
r->bindings[SLOT_INDEX((h).id)].active && \
r->bindings[SLOT_INDEX((h).id)].generation == SLOT_GEN((h).id))
void pxl8_destroy_texture(pxl8_renderer* r, pxl8_gfx_texture tex) {
void pxl8_destroy_texture(pxl8_blit3d* r, pxl8_gfx_texture tex) {
if (!VALID_TEX(r, tex)) return;
texture_slot* s = &r->textures[SLOT_INDEX(tex.id)];
pxl8_free(s->data);
@ -1030,7 +1030,7 @@ void pxl8_destroy_texture(pxl8_renderer* r, pxl8_gfx_texture tex) {
s->active = false;
}
void pxl8_destroy_buffer(pxl8_renderer* r, pxl8_gfx_buffer buf) {
void pxl8_destroy_buffer(pxl8_blit3d* r, pxl8_gfx_buffer buf) {
if (!VALID_BUF(r, buf)) return;
buffer_slot* s = &r->buffers[SLOT_INDEX(buf.id)];
pxl8_free(s->data);
@ -1038,7 +1038,7 @@ void pxl8_destroy_buffer(pxl8_renderer* r, pxl8_gfx_buffer buf) {
s->active = false;
}
void pxl8_destroy_pipeline(pxl8_renderer* r, pxl8_gfx_pipeline pip) {
void pxl8_destroy_pipeline(pxl8_blit3d* r, pxl8_gfx_pipeline pip) {
u32 idx = SLOT_INDEX(pip.id);
if (idx < PXL8_GFX_MAX_PIPELINES && r->pipelines[idx].generation == SLOT_GEN(pip.id)) {
if (!r->pipelines[idx].cached)
@ -1046,28 +1046,28 @@ void pxl8_destroy_pipeline(pxl8_renderer* r, pxl8_gfx_pipeline pip) {
}
}
void pxl8_destroy_bindings(pxl8_renderer* r, pxl8_gfx_bindings bnd) {
void pxl8_destroy_bindings(pxl8_blit3d* r, pxl8_gfx_bindings bnd) {
u32 idx = SLOT_INDEX(bnd.id);
if (idx < PXL8_GFX_MAX_BINDINGS && r->bindings[idx].generation == SLOT_GEN(bnd.id)) {
r->bindings[idx].active = false;
}
}
void pxl8_destroy_pass(pxl8_renderer* r, pxl8_gfx_pass pass) {
void pxl8_destroy_pass(pxl8_blit3d* r, pxl8_gfx_pass pass) {
u32 idx = SLOT_INDEX(pass.id);
if (idx < PXL8_GFX_MAX_PASSES && r->passes[idx].generation == SLOT_GEN(pass.id)) {
r->passes[idx].active = false;
}
}
void pxl8_update_buffer(pxl8_renderer* r, pxl8_gfx_buffer buf, const pxl8_gfx_range* data) {
void pxl8_update_buffer(pxl8_blit3d* r, pxl8_gfx_buffer buf, const pxl8_gfx_range* data) {
if (!VALID_BUF(r, buf)) return;
buffer_slot* s = &r->buffers[SLOT_INDEX(buf.id)];
u32 copy_size = data->size < s->size ? data->size : s->size;
memcpy(s->data, data->ptr, copy_size);
}
i32 pxl8_append_buffer(pxl8_renderer* r, pxl8_gfx_buffer buf, const pxl8_gfx_range* data) {
i32 pxl8_append_buffer(pxl8_blit3d* r, pxl8_gfx_buffer buf, const pxl8_gfx_range* data) {
if (!VALID_BUF(r, buf)) return -1;
buffer_slot* s = &r->buffers[SLOT_INDEX(buf.id)];
if (s->append_pos + data->size > s->size) return -1;
@ -1077,7 +1077,7 @@ i32 pxl8_append_buffer(pxl8_renderer* r, pxl8_gfx_buffer buf, const pxl8_gfx_ran
return offset;
}
void pxl8_update_texture(pxl8_renderer* r, pxl8_gfx_texture tex, const pxl8_gfx_range* data, u32 x, u32 y, u32 w, u32 h) {
void pxl8_update_texture(pxl8_blit3d* r, pxl8_gfx_texture tex, const pxl8_gfx_range* data, u32 x, u32 y, u32 w, u32 h) {
if (!VALID_TEX(r, tex)) return;
texture_slot* s = &r->textures[SLOT_INDEX(tex.id)];
u32 bpp = (s->format == PXL8_GFX_FORMAT_INDEXED8) ? 1 :
@ -1091,17 +1091,17 @@ void pxl8_update_texture(pxl8_renderer* r, pxl8_gfx_texture tex, const pxl8_gfx_
}
}
void* pxl8_texture_get_data(pxl8_renderer* r, pxl8_gfx_texture tex) {
void* pxl8_texture_get_data(pxl8_blit3d* r, pxl8_gfx_texture tex) {
if (!VALID_TEX(r, tex)) return NULL;
return r->textures[SLOT_INDEX(tex.id)].data;
}
u32 pxl8_texture_get_width(pxl8_renderer* r, pxl8_gfx_texture tex) {
u32 pxl8_texture_get_width(pxl8_blit3d* r, pxl8_gfx_texture tex) {
if (!VALID_TEX(r, tex)) return 0;
return r->textures[SLOT_INDEX(tex.id)].width;
}
u32 pxl8_texture_get_height(pxl8_renderer* r, pxl8_gfx_texture tex) {
u32 pxl8_texture_get_height(pxl8_blit3d* r, pxl8_gfx_texture tex) {
if (!VALID_TEX(r, tex)) return 0;
return r->textures[SLOT_INDEX(tex.id)].height;
}
@ -1198,7 +1198,7 @@ void pxl8_draw(pxl8_gfx_cmdbuf* cb, pxl8_gfx_buffer vb, pxl8_gfx_buffer ib, u32
}
static void execute_draw(
pxl8_renderer* r,
pxl8_blit3d* r,
const pxl8_gfx_cmd_draw* cmd
) {
r->stats.draw_calls++;
@ -1416,7 +1416,7 @@ static void execute_draw(
}
}
void pxl8_gfx_submit(pxl8_renderer* r, pxl8_gfx_cmdbuf* cb) {
void pxl8_gfx_submit(pxl8_blit3d* r, pxl8_gfx_cmdbuf* cb) {
for (u32 i = 0; i < cb->count; i++) {
pxl8_gfx_cmd* cmd = &cb->commands[i];
switch (cmd->type) {
@ -1475,7 +1475,7 @@ void pxl8_gfx_submit(pxl8_renderer* r, pxl8_gfx_cmdbuf* cb) {
r->frame_counter++;
}
void pxl8_clear(pxl8_renderer* r, pxl8_gfx_texture target, u8 color) {
void pxl8_clear(pxl8_blit3d* r, pxl8_gfx_texture target, u8 color) {
if (!VALID_TEX(r, target)) return;
texture_slot* s = &r->textures[SLOT_INDEX(target.id)];
if (s->format == PXL8_GFX_FORMAT_INDEXED8) {
@ -1483,7 +1483,7 @@ void pxl8_clear(pxl8_renderer* r, pxl8_gfx_texture target, u8 color) {
}
}
void pxl8_clear_depth(pxl8_renderer* r, pxl8_gfx_texture target) {
void pxl8_clear_depth(pxl8_blit3d* r, pxl8_gfx_texture target) {
if (!VALID_TEX(r, target)) return;
texture_slot* s = &r->textures[SLOT_INDEX(target.id)];
if (s->format == PXL8_GFX_FORMAT_DEPTH16) {
@ -1491,13 +1491,13 @@ void pxl8_clear_depth(pxl8_renderer* r, pxl8_gfx_texture target) {
}
}
void pxl8_clear_stencil(pxl8_renderer* r, u8 value) {
void pxl8_clear_stencil(pxl8_blit3d* r, u8 value) {
if (!r || !r->stencil) return;
memset(r->stencil, value, r->width * r->height);
}
void pxl8_draw_pixel(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y, u8 color) {
void pxl8_draw_pixel(pxl8_blit3d* r, pxl8_gfx_texture target, i32 x, i32 y, u8 color) {
if (!VALID_TEX(r, target)) return;
texture_slot* s = &r->textures[SLOT_INDEX(target.id)];
if (x < 0 || y < 0 || (u32)x >= s->width || (u32)y >= s->height) return;
@ -1506,7 +1506,7 @@ void pxl8_draw_pixel(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y, u8
}
}
u8 pxl8_get_pixel(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y) {
u8 pxl8_get_pixel(pxl8_blit3d* r, pxl8_gfx_texture target, i32 x, i32 y) {
if (!VALID_TEX(r, target)) return 0;
texture_slot* s = &r->textures[SLOT_INDEX(target.id)];
if (x < 0 || y < 0 || (u32)x >= s->width || (u32)y >= s->height) return 0;
@ -1516,7 +1516,7 @@ u8 pxl8_get_pixel(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y) {
return 0;
}
void pxl8_draw_line(pxl8_renderer* r, pxl8_gfx_texture target, i32 x0, i32 y0, i32 x1, i32 y1, u8 color) {
void pxl8_draw_line(pxl8_blit3d* r, pxl8_gfx_texture target, i32 x0, i32 y0, i32 x1, i32 y1, u8 color) {
if (!VALID_TEX(r, target)) return;
texture_slot* s = &r->textures[SLOT_INDEX(target.id)];
if (s->format != PXL8_GFX_FORMAT_INDEXED8) return;
@ -1542,14 +1542,14 @@ void pxl8_draw_line(pxl8_renderer* r, pxl8_gfx_texture target, i32 x0, i32 y0, i
}
}
void pxl8_draw_rect(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y, i32 w, i32 h, u8 color) {
void pxl8_draw_rect(pxl8_blit3d* r, pxl8_gfx_texture target, i32 x, i32 y, i32 w, i32 h, u8 color) {
pxl8_draw_line(r, target, x, y, x + w - 1, y, color);
pxl8_draw_line(r, target, x + w - 1, y, x + w - 1, y + h - 1, color);
pxl8_draw_line(r, target, x + w - 1, y + h - 1, x, y + h - 1, color);
pxl8_draw_line(r, target, x, y + h - 1, x, y, color);
}
void pxl8_draw_rect_fill(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y, i32 w, i32 h, u8 color) {
void pxl8_draw_rect_fill(pxl8_blit3d* r, pxl8_gfx_texture target, i32 x, i32 y, i32 w, i32 h, u8 color) {
if (!VALID_TEX(r, target)) return;
texture_slot* s = &r->textures[SLOT_INDEX(target.id)];
if (s->format != PXL8_GFX_FORMAT_INDEXED8) return;
@ -1568,7 +1568,7 @@ void pxl8_draw_rect_fill(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y
}
}
void pxl8_draw_circle(pxl8_renderer* r, pxl8_gfx_texture target, i32 cx, i32 cy, i32 radius, u8 color) {
void pxl8_draw_circle(pxl8_blit3d* r, pxl8_gfx_texture target, i32 cx, i32 cy, i32 radius, u8 color) {
if (!VALID_TEX(r, target)) return;
texture_slot* s = &r->textures[SLOT_INDEX(target.id)];
if (s->format != PXL8_GFX_FORMAT_INDEXED8) return;
@ -1594,7 +1594,7 @@ void pxl8_draw_circle(pxl8_renderer* r, pxl8_gfx_texture target, i32 cx, i32 cy,
#undef PLOT
}
void pxl8_draw_circle_fill(pxl8_renderer* r, pxl8_gfx_texture target, i32 cx, i32 cy, i32 radius, u8 color) {
void pxl8_draw_circle_fill(pxl8_blit3d* r, pxl8_gfx_texture target, i32 cx, i32 cy, i32 radius, u8 color) {
if (!VALID_TEX(r, target)) return;
texture_slot* s = &r->textures[SLOT_INDEX(target.id)];
if (s->format != PXL8_GFX_FORMAT_INDEXED8) return;
@ -1618,7 +1618,7 @@ void pxl8_draw_circle_fill(pxl8_renderer* r, pxl8_gfx_texture target, i32 cx, i3
}
}
void pxl8_resolve_to_rgba(pxl8_renderer* r, pxl8_gfx_texture color, const u32* palette, u32* output) {
void pxl8_resolve_to_rgba(pxl8_blit3d* r, pxl8_gfx_texture color, const u32* palette, u32* output) {
if (!VALID_TEX(r, color)) return;
texture_slot* cs = &r->textures[SLOT_INDEX(color.id)];

70
src/gfx/pxl8_blit3d.h Normal file
View file

@ -0,0 +1,70 @@
#pragma once
#include "pxl8_colormap.h"
#include "pxl8_gfx.h"
#include "pxl8_blit3d_types.h"
#include "pxl8_shader.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct pxl8_blit3d pxl8_blit3d;
typedef struct pxl8_gfx_cmdbuf pxl8_gfx_cmdbuf;
pxl8_blit3d* pxl8_blit3d_create(u32 width, u32 height);
void pxl8_blit3d_destroy(pxl8_blit3d* r);
pxl8_gfx_bindings pxl8_create_bindings(pxl8_blit3d* r, const pxl8_gfx_bindings_desc* desc);
pxl8_gfx_buffer pxl8_create_buffer(pxl8_blit3d* r, const pxl8_gfx_buffer_desc* desc);
pxl8_gfx_pass pxl8_create_pass(pxl8_blit3d* r, const pxl8_gfx_pass_desc* desc);
pxl8_gfx_pipeline pxl8_create_pipeline(pxl8_blit3d* r, const pxl8_gfx_pipeline_desc* desc);
pxl8_gfx_texture pxl8_create_texture(pxl8_blit3d* r, const pxl8_gfx_texture_desc* desc);
void pxl8_destroy_bindings(pxl8_blit3d* r, pxl8_gfx_bindings bnd);
void pxl8_destroy_buffer(pxl8_blit3d* r, pxl8_gfx_buffer buf);
void pxl8_destroy_pass(pxl8_blit3d* r, pxl8_gfx_pass pass);
void pxl8_destroy_pipeline(pxl8_blit3d* r, pxl8_gfx_pipeline pip);
void pxl8_destroy_texture(pxl8_blit3d* r, pxl8_gfx_texture tex);
void pxl8_update_buffer(pxl8_blit3d* r, pxl8_gfx_buffer buf, const pxl8_gfx_range* data);
i32 pxl8_append_buffer(pxl8_blit3d* r, pxl8_gfx_buffer buf, const pxl8_gfx_range* data);
void pxl8_update_texture(pxl8_blit3d* r, pxl8_gfx_texture tex, const pxl8_gfx_range* data, u32 x, u32 y, u32 w, u32 h);
void* pxl8_texture_get_data(pxl8_blit3d* r, pxl8_gfx_texture tex);
u32 pxl8_texture_get_width(pxl8_blit3d* r, pxl8_gfx_texture tex);
u32 pxl8_texture_get_height(pxl8_blit3d* r, pxl8_gfx_texture tex);
pxl8_gfx_cmdbuf* pxl8_cmdbuf_create(u32 capacity);
void pxl8_cmdbuf_destroy(pxl8_gfx_cmdbuf* cb);
void pxl8_cmdbuf_reset(pxl8_gfx_cmdbuf* cb);
void pxl8_begin_pass(pxl8_gfx_cmdbuf* cb, pxl8_gfx_pass pass);
void pxl8_cmdbuf_clear_depth(pxl8_gfx_cmdbuf* cb, pxl8_gfx_texture texture);
void pxl8_end_pass(pxl8_gfx_cmdbuf* cb);
void pxl8_set_bindings(pxl8_gfx_cmdbuf* cb, pxl8_gfx_bindings bindings);
void pxl8_set_draw_params(pxl8_gfx_cmdbuf* cb, const pxl8_gfx_cmd_draw_params* p);
void pxl8_set_pipeline(pxl8_gfx_cmdbuf* cb, pxl8_gfx_pipeline pipeline);
void pxl8_set_scissor(pxl8_gfx_cmdbuf* cb, i32 x, i32 y, u32 w, u32 h);
void pxl8_set_viewport(pxl8_gfx_cmdbuf* cb, i32 x, i32 y, u32 w, u32 h);
void pxl8_draw(pxl8_gfx_cmdbuf* cb, pxl8_gfx_buffer vb, pxl8_gfx_buffer ib, u32 first, u32 count, u32 base_vertex);
void pxl8_gfx_submit(pxl8_blit3d* r, pxl8_gfx_cmdbuf* cb);
void pxl8_clear(pxl8_blit3d* r, pxl8_gfx_texture target, u8 color);
void pxl8_clear_depth(pxl8_blit3d* r, pxl8_gfx_texture target);
void pxl8_clear_stencil(pxl8_blit3d* r, u8 value);
void pxl8_draw_pixel(pxl8_blit3d* r, pxl8_gfx_texture target, i32 x, i32 y, u8 color);
u8 pxl8_get_pixel(pxl8_blit3d* r, pxl8_gfx_texture target, i32 x, i32 y);
void pxl8_draw_line(pxl8_blit3d* r, pxl8_gfx_texture target, i32 x0, i32 y0, i32 x1, i32 y1, u8 color);
void pxl8_draw_rect(pxl8_blit3d* r, pxl8_gfx_texture target, i32 x, i32 y, i32 w, i32 h, u8 color);
void pxl8_draw_rect_fill(pxl8_blit3d* r, pxl8_gfx_texture target, i32 x, i32 y, i32 w, i32 h, u8 color);
void pxl8_draw_circle(pxl8_blit3d* r, pxl8_gfx_texture target, i32 cx, i32 cy, i32 radius, u8 color);
void pxl8_draw_circle_fill(pxl8_blit3d* r, pxl8_gfx_texture target, i32 cx, i32 cy, i32 radius, u8 color);
void pxl8_resolve_to_rgba(pxl8_blit3d* r, pxl8_gfx_texture color, const u32* palette, u32* output);
#ifdef __cplusplus
}
#endif

View file

@ -1,16 +1,16 @@
#include "pxl8_3d_camera.h"
#include "pxl8_camera3d.h"
#include "pxl8_mem.h"
#include <math.h>
#include <stdlib.h>
struct pxl8_3d_camera {
struct pxl8_camera3d {
pxl8_vec3 position;
f32 pitch;
f32 roll;
f32 yaw;
pxl8_3d_camera_mode mode;
pxl8_camera3d_mode mode;
f32 aspect;
f32 far;
@ -28,8 +28,8 @@ struct pxl8_3d_camera {
f32 shake_timer;
};
pxl8_3d_camera* pxl8_3d_camera_create(void) {
pxl8_3d_camera* cam = pxl8_calloc(1, sizeof(pxl8_3d_camera));
pxl8_camera3d* pxl8_camera3d_create(void) {
pxl8_camera3d* cam = pxl8_calloc(1, sizeof(pxl8_camera3d));
if (!cam) return NULL;
cam->position = (pxl8_vec3){0, 0, 0};
@ -37,7 +37,7 @@ pxl8_3d_camera* pxl8_3d_camera_create(void) {
cam->yaw = 0;
cam->roll = 0;
cam->mode = PXL8_3D_CAMERA_PERSPECTIVE;
cam->mode = PXL8_CAMERA3D_PERSPECTIVE;
cam->fov = 1.0f;
cam->aspect = 16.0f / 9.0f;
cam->near = 1.0f;
@ -46,11 +46,11 @@ pxl8_3d_camera* pxl8_3d_camera_create(void) {
return cam;
}
void pxl8_3d_camera_destroy(pxl8_3d_camera* cam) {
void pxl8_camera3d_destroy(pxl8_camera3d* cam) {
pxl8_free(cam);
}
void pxl8_3d_camera_lookat(pxl8_3d_camera* cam, pxl8_vec3 eye, pxl8_vec3 target, pxl8_vec3 up) {
void pxl8_camera3d_lookat(pxl8_camera3d* cam, pxl8_vec3 eye, pxl8_vec3 target, pxl8_vec3 up) {
if (!cam) return;
cam->position = eye;
@ -64,9 +64,9 @@ void pxl8_3d_camera_lookat(pxl8_3d_camera* cam, pxl8_vec3 eye, pxl8_vec3 target,
(void)up;
}
void pxl8_3d_camera_set_ortho(pxl8_3d_camera* cam, f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far) {
void pxl8_camera3d_set_ortho(pxl8_camera3d* cam, f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far) {
if (!cam) return;
cam->mode = PXL8_3D_CAMERA_ORTHO;
cam->mode = PXL8_CAMERA3D_ORTHO;
cam->ortho_left = left;
cam->ortho_right = right;
cam->ortho_bottom = bottom;
@ -75,28 +75,28 @@ void pxl8_3d_camera_set_ortho(pxl8_3d_camera* cam, f32 left, f32 right, f32 bott
cam->far = far;
}
void pxl8_3d_camera_set_perspective(pxl8_3d_camera* cam, f32 fov, f32 aspect, f32 near, f32 far) {
void pxl8_camera3d_set_perspective(pxl8_camera3d* cam, f32 fov, f32 aspect, f32 near, f32 far) {
if (!cam) return;
cam->mode = PXL8_3D_CAMERA_PERSPECTIVE;
cam->mode = PXL8_CAMERA3D_PERSPECTIVE;
cam->fov = fov;
cam->aspect = aspect;
cam->near = near;
cam->far = far;
}
void pxl8_3d_camera_set_position(pxl8_3d_camera* cam, pxl8_vec3 pos) {
void pxl8_camera3d_set_position(pxl8_camera3d* cam, pxl8_vec3 pos) {
if (!cam) return;
cam->position = pos;
}
void pxl8_3d_camera_set_rotation(pxl8_3d_camera* cam, f32 pitch, f32 yaw, f32 roll) {
void pxl8_camera3d_set_rotation(pxl8_camera3d* cam, f32 pitch, f32 yaw, f32 roll) {
if (!cam) return;
cam->pitch = pitch;
cam->yaw = yaw;
cam->roll = roll;
}
pxl8_vec3 pxl8_3d_camera_get_forward(const pxl8_3d_camera* cam) {
pxl8_vec3 pxl8_camera3d_get_forward(const pxl8_camera3d* cam) {
if (!cam) return (pxl8_vec3){0, 0, -1};
f32 cp = cosf(cam->pitch);
@ -111,15 +111,15 @@ pxl8_vec3 pxl8_3d_camera_get_forward(const pxl8_3d_camera* cam) {
};
}
pxl8_vec3 pxl8_3d_camera_get_position(const pxl8_3d_camera* cam) {
pxl8_vec3 pxl8_camera3d_get_position(const pxl8_camera3d* cam) {
if (!cam) return (pxl8_vec3){0, 0, 0};
return pxl8_vec3_add(cam->position, cam->shake_offset);
}
pxl8_mat4 pxl8_3d_camera_get_projection(const pxl8_3d_camera* cam) {
pxl8_mat4 pxl8_camera3d_get_projection(const pxl8_camera3d* cam) {
if (!cam) return pxl8_mat4_identity();
if (cam->mode == PXL8_3D_CAMERA_PERSPECTIVE) {
if (cam->mode == PXL8_CAMERA3D_PERSPECTIVE) {
return pxl8_mat4_perspective(cam->fov, cam->aspect, cam->near, cam->far);
} else {
return pxl8_mat4_orthographic(
@ -130,7 +130,7 @@ pxl8_mat4 pxl8_3d_camera_get_projection(const pxl8_3d_camera* cam) {
}
}
pxl8_vec3 pxl8_3d_camera_get_right(const pxl8_3d_camera* cam) {
pxl8_vec3 pxl8_camera3d_get_right(const pxl8_camera3d* cam) {
if (!cam) return (pxl8_vec3){1, 0, 0};
f32 cy = cosf(cam->yaw);
@ -139,27 +139,27 @@ pxl8_vec3 pxl8_3d_camera_get_right(const pxl8_3d_camera* cam) {
return (pxl8_vec3){cy, 0, -sy};
}
pxl8_vec3 pxl8_3d_camera_get_up(const pxl8_3d_camera* cam) {
pxl8_vec3 pxl8_camera3d_get_up(const pxl8_camera3d* cam) {
if (!cam) return (pxl8_vec3){0, 1, 0};
pxl8_vec3 forward = pxl8_3d_camera_get_forward(cam);
pxl8_vec3 right = pxl8_3d_camera_get_right(cam);
pxl8_vec3 forward = pxl8_camera3d_get_forward(cam);
pxl8_vec3 right = pxl8_camera3d_get_right(cam);
return pxl8_vec3_cross(forward, right);
}
pxl8_mat4 pxl8_3d_camera_get_view(const pxl8_3d_camera* cam) {
pxl8_mat4 pxl8_camera3d_get_view(const pxl8_camera3d* cam) {
if (!cam) return pxl8_mat4_identity();
pxl8_vec3 pos = pxl8_3d_camera_get_position(cam);
pxl8_vec3 forward = pxl8_3d_camera_get_forward(cam);
pxl8_vec3 pos = pxl8_camera3d_get_position(cam);
pxl8_vec3 forward = pxl8_camera3d_get_forward(cam);
pxl8_vec3 target = pxl8_vec3_add(pos, forward);
pxl8_vec3 up = (pxl8_vec3){0, 1, 0};
return pxl8_mat4_lookat(pos, target, up);
}
void pxl8_3d_camera_blend(pxl8_3d_camera* dest, const pxl8_3d_camera* a, const pxl8_3d_camera* b, f32 t) {
void pxl8_camera3d_blend(pxl8_camera3d* dest, const pxl8_camera3d* a, const pxl8_camera3d* b, f32 t) {
if (!dest || !a || !b) return;
dest->position = pxl8_vec3_lerp(a->position, b->position, t);
@ -175,7 +175,7 @@ void pxl8_3d_camera_blend(pxl8_3d_camera* dest, const pxl8_3d_camera* a, const p
dest->mode = (t < 0.5f) ? a->mode : b->mode;
}
void pxl8_3d_camera_follow(pxl8_3d_camera* cam, pxl8_vec3 target, pxl8_vec3 offset, f32 smoothing, f32 dt) {
void pxl8_camera3d_follow(pxl8_camera3d* cam, pxl8_vec3 target, pxl8_vec3 offset, f32 smoothing, f32 dt) {
if (!cam) return;
pxl8_vec3 desired = pxl8_vec3_add(target, offset);
@ -188,14 +188,14 @@ void pxl8_3d_camera_follow(pxl8_3d_camera* cam, pxl8_vec3 target, pxl8_vec3 offs
cam->yaw = atan2f(-forward.x, -forward.z);
}
void pxl8_3d_camera_shake(pxl8_3d_camera* cam, f32 intensity, f32 duration) {
void pxl8_camera3d_shake(pxl8_camera3d* cam, f32 intensity, f32 duration) {
if (!cam) return;
cam->shake_intensity = intensity;
cam->shake_duration = duration;
cam->shake_timer = duration;
}
void pxl8_3d_camera_update(pxl8_3d_camera* cam, f32 dt) {
void pxl8_camera3d_update(pxl8_camera3d* cam, f32 dt) {
if (!cam) return;
if (cam->shake_timer > 0) {
@ -214,12 +214,12 @@ void pxl8_3d_camera_update(pxl8_3d_camera* cam, f32 dt) {
}
}
pxl8_projected_point pxl8_3d_camera_world_to_screen(const pxl8_3d_camera* cam, pxl8_vec3 world_pos, u32 screen_width, u32 screen_height) {
pxl8_projected_point pxl8_camera3d_world_to_screen(const pxl8_camera3d* cam, pxl8_vec3 world_pos, u32 screen_width, u32 screen_height) {
pxl8_projected_point result = {0, 0, 0.0f, false};
if (!cam) return result;
pxl8_mat4 view = pxl8_3d_camera_get_view(cam);
pxl8_mat4 proj = pxl8_3d_camera_get_projection(cam);
pxl8_mat4 view = pxl8_camera3d_get_view(cam);
pxl8_mat4 proj = pxl8_camera3d_get_projection(cam);
pxl8_mat4 vp = pxl8_mat4_multiply(proj, view);
pxl8_vec4 clip = pxl8_mat4_multiply_vec4(vp, (pxl8_vec4){world_pos.x, world_pos.y, world_pos.z, 1.0f});

41
src/gfx/pxl8_camera3d.h Normal file
View file

@ -0,0 +1,41 @@
#pragma once
#include "pxl8_math.h"
#include "pxl8_types.h"
typedef enum pxl8_camera3d_mode {
PXL8_CAMERA3D_ORTHO,
PXL8_CAMERA3D_PERSPECTIVE
} pxl8_camera3d_mode;
typedef struct pxl8_camera3d pxl8_camera3d;
#ifdef __cplusplus
extern "C" {
#endif
pxl8_camera3d* pxl8_camera3d_create(void);
void pxl8_camera3d_destroy(pxl8_camera3d* cam);
void pxl8_camera3d_lookat(pxl8_camera3d* cam, pxl8_vec3 eye, pxl8_vec3 target, pxl8_vec3 up);
void pxl8_camera3d_set_ortho(pxl8_camera3d* cam, f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far);
void pxl8_camera3d_set_perspective(pxl8_camera3d* cam, f32 fov, f32 aspect, f32 near, f32 far);
void pxl8_camera3d_set_position(pxl8_camera3d* cam, pxl8_vec3 pos);
void pxl8_camera3d_set_rotation(pxl8_camera3d* cam, f32 pitch, f32 yaw, f32 roll);
pxl8_vec3 pxl8_camera3d_get_forward(const pxl8_camera3d* cam);
pxl8_vec3 pxl8_camera3d_get_position(const pxl8_camera3d* cam);
pxl8_mat4 pxl8_camera3d_get_projection(const pxl8_camera3d* cam);
pxl8_vec3 pxl8_camera3d_get_right(const pxl8_camera3d* cam);
pxl8_vec3 pxl8_camera3d_get_up(const pxl8_camera3d* cam);
pxl8_mat4 pxl8_camera3d_get_view(const pxl8_camera3d* cam);
void pxl8_camera3d_blend(pxl8_camera3d* dest, const pxl8_camera3d* a, const pxl8_camera3d* b, f32 t);
void pxl8_camera3d_follow(pxl8_camera3d* cam, pxl8_vec3 target, pxl8_vec3 offset, f32 smoothing, f32 dt);
pxl8_projected_point pxl8_camera3d_world_to_screen(const pxl8_camera3d* cam, pxl8_vec3 world_pos, u32 screen_width, u32 screen_height);
void pxl8_camera3d_shake(pxl8_camera3d* cam, f32 intensity, f32 duration);
void pxl8_camera3d_update(pxl8_camera3d* cam, f32 dt);
#ifdef __cplusplus
}
#endif

View file

@ -9,18 +9,18 @@
#include "pxl8_colormap.h"
#include "pxl8_font.h"
#include "pxl8_glows.h"
#include "pxl8_hal.h"
#include "pxl8_platform.h"
#include "pxl8_log.h"
#include "pxl8_macros.h"
#include "pxl8_math.h"
#include "pxl8_mem.h"
#include "pxl8_render.h"
#include "pxl8_blit3d.h"
#include "pxl8_shader_registry.h"
#include "pxl8_sys.h"
#include "pxl8_types.h"
void pxl8_renderer_update_stats(pxl8_renderer* r, f32 dt);
pxl8_gfx_stats* pxl8_renderer_get_stats(pxl8_renderer* r);
void pxl8_blit3d_update_stats(pxl8_blit3d* r, f32 dt);
pxl8_gfx_stats* pxl8_blit3d_get_stats(pxl8_blit3d* r);
#define PXL8_MAX_TARGET_STACK 8
@ -53,18 +53,17 @@ typedef struct pxl8_frame_resources {
struct pxl8_gfx {
pxl8_atlas* atlas;
pxl8_renderer* renderer;
pxl8_blit3d* renderer;
pxl8_gfx_texture color_target;
pxl8_gfx_texture depth_target;
pxl8_gfx_cmdbuf* cmdbuf;
pxl8_target_entry target_stack[PXL8_MAX_TARGET_STACK];
u32 target_stack_depth;
const pxl8_bsp* bsp;
pxl8_colormap* colormap;
i32 framebuffer_height;
i32 framebuffer_width;
pxl8_frustum frustum;
const pxl8_hal* hal;
const pxl8_platform* platform;
bool initialized;
u32* output;
pxl8_palette* palette;
@ -170,7 +169,7 @@ i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath) {
}
pxl8_gfx* pxl8_gfx_create(
const pxl8_hal* hal,
const pxl8_platform* platform,
void* platform_data,
pxl8_resolution resolution
) {
@ -182,7 +181,7 @@ pxl8_gfx* pxl8_gfx_create(
return NULL;
}
gfx->hal = hal;
gfx->platform = platform;
gfx->platform_data = platform_data;
pxl8_size size = pxl8_get_resolution_dimensions(resolution);
@ -197,7 +196,7 @@ pxl8_gfx* pxl8_gfx_create(
gfx->palette = pxl8_palette_create();
gfx->renderer = pxl8_renderer_create(gfx->framebuffer_width, gfx->framebuffer_height);
gfx->renderer = pxl8_blit3d_create(gfx->framebuffer_width, gfx->framebuffer_height);
if (!gfx->renderer) {
pxl8_error("Failed to create renderer");
pxl8_gfx_destroy(gfx);
@ -281,7 +280,7 @@ void pxl8_gfx_destroy(pxl8_gfx* gfx) {
pxl8_palette_cube_destroy(gfx->palette_cube);
pxl8_palette_destroy(gfx->palette);
pxl8_free(gfx->sprite_cache);
pxl8_renderer_destroy(gfx->renderer);
pxl8_blit3d_destroy(gfx->renderer);
pxl8_free(gfx);
}
@ -393,10 +392,10 @@ pxl8_atlas* pxl8_gfx_get_atlas(pxl8_gfx* gfx) {
}
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) {
if (!gfx || !gfx->initialized || !gfx->hal) return;
if (!gfx || !gfx->initialized || !gfx->platform) return;
pxl8_gfx_resolve(gfx);
gfx->hal->upload_texture(
gfx->platform->upload_texture(
gfx->platform_data,
gfx->output,
gfx->framebuffer_width,
@ -407,9 +406,9 @@ void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) {
}
void pxl8_gfx_present(pxl8_gfx* gfx) {
if (!gfx || !gfx->initialized || !gfx->hal) return;
if (!gfx || !gfx->initialized || !gfx->platform) return;
gfx->hal->present(gfx->platform_data);
gfx->platform->present(gfx->platform_data);
}
pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height) {
@ -618,14 +617,14 @@ void pxl8_gfx_update(pxl8_gfx* gfx, f32 dt) {
}
}
static pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_shader_uniforms* uniforms) {
static pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_camera3d* camera, const pxl8_shader_uniforms* uniforms) {
pxl8_3d_frame frame = {0};
if (!camera) return frame;
frame.view = pxl8_3d_camera_get_view(camera);
frame.projection = pxl8_3d_camera_get_projection(camera);
frame.camera_pos = pxl8_3d_camera_get_position(camera);
frame.camera_dir = pxl8_3d_camera_get_forward(camera);
frame.view = pxl8_camera3d_get_view(camera);
frame.projection = pxl8_camera3d_get_projection(camera);
frame.camera_pos = pxl8_camera3d_get_position(camera);
frame.camera_dir = pxl8_camera3d_get_forward(camera);
frame.near_clip = 1.0f;
frame.far_clip = 4096.0f;
@ -636,16 +635,10 @@ static pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, con
return frame;
}
void pxl8_3d_set_bsp(pxl8_gfx* gfx, const pxl8_bsp* bsp) {
if (!gfx) return;
gfx->bsp = bsp;
}
void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_shader_uniforms* uniforms) {
void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_camera3d* camera, const pxl8_lights* lights, const pxl8_shader_uniforms* uniforms) {
if (!gfx || !camera) return;
pxl8_3d_frame frame = pxl8_3d_frame_from_camera(camera, uniforms);
frame.bsp = gfx->bsp;
frame.uniforms.camera_pos = frame.camera_pos;
frame.uniforms.lights = lights ? pxl8_lights_data(lights) : NULL;
frame.uniforms.lights_count = lights ? pxl8_lights_count(lights) : 0;
@ -1102,9 +1095,9 @@ u8 pxl8_gfx_get_ambient(const pxl8_gfx* gfx) {
}
const pxl8_gfx_stats* pxl8_gfx_get_stats(const pxl8_gfx* gfx) {
return gfx ? pxl8_renderer_get_stats(gfx->renderer) : NULL;
return gfx ? pxl8_blit3d_get_stats(gfx->renderer) : NULL;
}
void pxl8_gfx_update_stats(pxl8_gfx* gfx, f32 dt) {
if (gfx) pxl8_renderer_update_stats(gfx->renderer, dt);
if (gfx) pxl8_blit3d_update_stats(gfx->renderer, dt);
}

View file

@ -3,7 +3,7 @@
#include "pxl8_gfx2d.h"
#include "pxl8_gfx3d.h"
#include "pxl8_glows.h"
#include "pxl8_hal.h"
#include "pxl8_platform.h"
#include "pxl8_colormap.h"
#include "pxl8_palette.h"
#include "pxl8_types.h"
@ -36,7 +36,7 @@ typedef enum pxl8_gfx_effect {
extern "C" {
#endif
pxl8_gfx* pxl8_gfx_create(const pxl8_hal* hal, void* platform_data, pxl8_resolution resolution);
pxl8_gfx* pxl8_gfx_create(const pxl8_platform* platform, void* platform_data, pxl8_resolution resolution);
void pxl8_gfx_destroy(pxl8_gfx* gfx);
void pxl8_gfx_present(pxl8_gfx* gfx);

View file

@ -1,18 +1,16 @@
#pragma once
#include "pxl8_3d_camera.h"
#include "pxl8_camera3d.h"
#include "pxl8_lights.h"
#include "pxl8_math.h"
#include "pxl8_mesh.h"
#include "pxl8_render_types.h"
#include "pxl8_blit3d_types.h"
#include "pxl8_shader.h"
#include "pxl8_types.h"
typedef struct pxl8_bsp pxl8_bsp;
typedef struct pxl8_gfx pxl8_gfx;
typedef struct pxl8_3d_frame {
const pxl8_bsp* bsp;
pxl8_vec3 camera_dir;
pxl8_vec3 camera_pos;
f32 far_clip;
@ -26,8 +24,7 @@ typedef struct pxl8_3d_frame {
extern "C" {
#endif
void pxl8_3d_set_bsp(pxl8_gfx* gfx, const pxl8_bsp* bsp);
void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_shader_uniforms* uniforms);
void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_camera3d* camera, const pxl8_lights* lights, const pxl8_shader_uniforms* uniforms);
void pxl8_3d_clear(pxl8_gfx* gfx, u8 color);
void pxl8_3d_clear_depth(pxl8_gfx* gfx);
void pxl8_3d_clear_stencil(pxl8_gfx* gfx, u8 value);

View file

@ -1,70 +0,0 @@
#pragma once
#include "pxl8_colormap.h"
#include "pxl8_gfx.h"
#include "pxl8_render_types.h"
#include "pxl8_shader.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct pxl8_renderer pxl8_renderer;
typedef struct pxl8_gfx_cmdbuf pxl8_gfx_cmdbuf;
pxl8_renderer* pxl8_renderer_create(u32 width, u32 height);
void pxl8_renderer_destroy(pxl8_renderer* r);
pxl8_gfx_bindings pxl8_create_bindings(pxl8_renderer* r, const pxl8_gfx_bindings_desc* desc);
pxl8_gfx_buffer pxl8_create_buffer(pxl8_renderer* r, const pxl8_gfx_buffer_desc* desc);
pxl8_gfx_pass pxl8_create_pass(pxl8_renderer* r, const pxl8_gfx_pass_desc* desc);
pxl8_gfx_pipeline pxl8_create_pipeline(pxl8_renderer* r, const pxl8_gfx_pipeline_desc* desc);
pxl8_gfx_texture pxl8_create_texture(pxl8_renderer* r, const pxl8_gfx_texture_desc* desc);
void pxl8_destroy_bindings(pxl8_renderer* r, pxl8_gfx_bindings bnd);
void pxl8_destroy_buffer(pxl8_renderer* r, pxl8_gfx_buffer buf);
void pxl8_destroy_pass(pxl8_renderer* r, pxl8_gfx_pass pass);
void pxl8_destroy_pipeline(pxl8_renderer* r, pxl8_gfx_pipeline pip);
void pxl8_destroy_texture(pxl8_renderer* r, pxl8_gfx_texture tex);
void pxl8_update_buffer(pxl8_renderer* r, pxl8_gfx_buffer buf, const pxl8_gfx_range* data);
i32 pxl8_append_buffer(pxl8_renderer* r, pxl8_gfx_buffer buf, const pxl8_gfx_range* data);
void pxl8_update_texture(pxl8_renderer* r, pxl8_gfx_texture tex, const pxl8_gfx_range* data, u32 x, u32 y, u32 w, u32 h);
void* pxl8_texture_get_data(pxl8_renderer* r, pxl8_gfx_texture tex);
u32 pxl8_texture_get_width(pxl8_renderer* r, pxl8_gfx_texture tex);
u32 pxl8_texture_get_height(pxl8_renderer* r, pxl8_gfx_texture tex);
pxl8_gfx_cmdbuf* pxl8_cmdbuf_create(u32 capacity);
void pxl8_cmdbuf_destroy(pxl8_gfx_cmdbuf* cb);
void pxl8_cmdbuf_reset(pxl8_gfx_cmdbuf* cb);
void pxl8_begin_pass(pxl8_gfx_cmdbuf* cb, pxl8_gfx_pass pass);
void pxl8_cmdbuf_clear_depth(pxl8_gfx_cmdbuf* cb, pxl8_gfx_texture texture);
void pxl8_end_pass(pxl8_gfx_cmdbuf* cb);
void pxl8_set_bindings(pxl8_gfx_cmdbuf* cb, pxl8_gfx_bindings bindings);
void pxl8_set_draw_params(pxl8_gfx_cmdbuf* cb, const pxl8_gfx_cmd_draw_params* p);
void pxl8_set_pipeline(pxl8_gfx_cmdbuf* cb, pxl8_gfx_pipeline pipeline);
void pxl8_set_scissor(pxl8_gfx_cmdbuf* cb, i32 x, i32 y, u32 w, u32 h);
void pxl8_set_viewport(pxl8_gfx_cmdbuf* cb, i32 x, i32 y, u32 w, u32 h);
void pxl8_draw(pxl8_gfx_cmdbuf* cb, pxl8_gfx_buffer vb, pxl8_gfx_buffer ib, u32 first, u32 count, u32 base_vertex);
void pxl8_gfx_submit(pxl8_renderer* r, pxl8_gfx_cmdbuf* cb);
void pxl8_clear(pxl8_renderer* r, pxl8_gfx_texture target, u8 color);
void pxl8_clear_depth(pxl8_renderer* r, pxl8_gfx_texture target);
void pxl8_clear_stencil(pxl8_renderer* r, u8 value);
void pxl8_draw_pixel(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y, u8 color);
u8 pxl8_get_pixel(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y);
void pxl8_draw_line(pxl8_renderer* r, pxl8_gfx_texture target, i32 x0, i32 y0, i32 x1, i32 y1, u8 color);
void pxl8_draw_rect(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y, i32 w, i32 h, u8 color);
void pxl8_draw_rect_fill(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y, i32 w, i32 h, u8 color);
void pxl8_draw_circle(pxl8_renderer* r, pxl8_gfx_texture target, i32 cx, i32 cy, i32 radius, u8 color);
void pxl8_draw_circle_fill(pxl8_renderer* r, pxl8_gfx_texture target, i32 cx, i32 cy, i32 radius, u8 color);
void pxl8_resolve_to_rgba(pxl8_renderer* r, pxl8_gfx_texture color, const u32* palette, u32* output);
#ifdef __cplusplus
}
#endif

View file

@ -12,7 +12,6 @@ local procgen = require("pxl8.procgen")
local sfx = require("pxl8.sfx")
local tilemap = require("pxl8.tilemap")
local transition = require("pxl8.transition")
local world = require("pxl8.world")
local pxl8 = {}
core.init(pxl8_gfx, pxl8_input, pxl8_rng, pxl8_sfx, pxl8_sys)
@ -149,7 +148,7 @@ pxl8.mat4_rotate_z = math.mat4_rotate_z
pxl8.mat4_scale = math.mat4_scale
pxl8.mat4_translate = math.mat4_translate
pxl8.get_net = net.get
pxl8.net = net
pxl8.pack_f32_be = bytes.pack_f32_be
pxl8.pack_f32_le = bytes.pack_f32_le
@ -251,14 +250,4 @@ pxl8.STENCIL_NOTEQUAL = 5
pxl8.STENCIL_GEQUAL = 6
pxl8.STENCIL_ALWAYS = 7
pxl8.Bsp = world.Bsp
pxl8.Chunk = world.Chunk
pxl8.World = world.World
pxl8.get_world = world.World.get
pxl8.sim_config = world.sim_config
pxl8.sim_move_player = world.sim_move_player
pxl8.sim_trace = world.sim_trace
pxl8.sim_check_ground = world.sim_check_ground
pxl8.make_input_msg = world.make_input_msg
return pxl8

View file

@ -109,7 +109,7 @@ local Camera3D = {}
Camera3D.__index = Camera3D
function Camera3D.new()
local cam = C.pxl8_3d_camera_create()
local cam = C.pxl8_camera3d_create()
if cam == nil then
return nil
end
@ -118,37 +118,37 @@ end
function Camera3D:destroy()
if self._ptr then
C.pxl8_3d_camera_destroy(self._ptr)
C.pxl8_camera3d_destroy(self._ptr)
self._ptr = nil
end
end
function Camera3D:get_forward()
local v = C.pxl8_3d_camera_get_forward(self._ptr)
local v = C.pxl8_camera3d_get_forward(self._ptr)
return {v.x, v.y, v.z}
end
function Camera3D:get_position()
local v = C.pxl8_3d_camera_get_position(self._ptr)
local v = C.pxl8_camera3d_get_position(self._ptr)
return {v.x, v.y, v.z}
end
function Camera3D:get_right()
local v = C.pxl8_3d_camera_get_right(self._ptr)
local v = C.pxl8_camera3d_get_right(self._ptr)
return {v.x, v.y, v.z}
end
function Camera3D:get_up()
local v = C.pxl8_3d_camera_get_up(self._ptr)
local v = C.pxl8_camera3d_get_up(self._ptr)
return {v.x, v.y, v.z}
end
function Camera3D:get_view()
return C.pxl8_3d_camera_get_view(self._ptr)
return C.pxl8_camera3d_get_view(self._ptr)
end
function Camera3D:get_projection()
return C.pxl8_3d_camera_get_projection(self._ptr)
return C.pxl8_camera3d_get_projection(self._ptr)
end
function Camera3D:lookat(eye, target, up)
@ -156,29 +156,29 @@ function Camera3D:lookat(eye, target, up)
local eye_vec = ffi.new("pxl8_vec3", {x = eye[1], y = eye[2], z = eye[3]})
local target_vec = ffi.new("pxl8_vec3", {x = target[1], y = target[2], z = target[3]})
local up_vec = ffi.new("pxl8_vec3", {x = up[1], y = up[2], z = up[3]})
C.pxl8_3d_camera_lookat(self._ptr, eye_vec, target_vec, up_vec)
C.pxl8_camera3d_lookat(self._ptr, eye_vec, target_vec, up_vec)
end
function Camera3D:set_perspective(fov, aspect, near, far)
C.pxl8_3d_camera_set_perspective(self._ptr, fov, aspect, near, far)
C.pxl8_camera3d_set_perspective(self._ptr, fov, aspect, near, far)
end
function Camera3D:set_position(x, y, z)
local pos = ffi.new("pxl8_vec3", {x = x, y = y, z = z})
C.pxl8_3d_camera_set_position(self._ptr, pos)
C.pxl8_camera3d_set_position(self._ptr, pos)
end
function Camera3D:set_rotation(pitch, yaw, roll)
C.pxl8_3d_camera_set_rotation(self._ptr, pitch, yaw or 0, roll or 0)
C.pxl8_camera3d_set_rotation(self._ptr, pitch, yaw or 0, roll or 0)
end
function Camera3D:update(dt)
C.pxl8_3d_camera_update(self._ptr, dt)
C.pxl8_camera3d_update(self._ptr, dt)
end
function Camera3D:world_to_screen(x, y, z, width, height)
local pos = ffi.new("pxl8_vec3", {x = x, y = y, z = z})
local result = C.pxl8_3d_camera_world_to_screen(self._ptr, pos, width, height)
local result = C.pxl8_camera3d_world_to_screen(self._ptr, pos, width, height)
if result.visible then
return {x = result.x, y = result.y, depth = result.depth}
end

View file

@ -1,51 +1,41 @@
local ffi = require("ffi")
local C = ffi.C
local core = require("pxl8.core")
local net = {}
local Net = {}
Net.__index = Net
function net.get()
local ptr = C.pxl8_get_net(core.sys)
function net.create(config)
local cfg = ffi.new("pxl8_net_config")
cfg.address = config.address or "127.0.0.1"
cfg.port = config.port or 7777
local ptr = C.pxl8_net_create(cfg)
if ptr == nil then
return nil
end
return setmetatable({ _ptr = ptr }, Net)
end
function Net:chunk_cx()
return C.pxl8_net_chunk_cx(self._ptr)
function Net:destroy()
C.pxl8_net_destroy(self._ptr)
self._ptr = nil
end
function Net:chunk_cz()
return C.pxl8_net_chunk_cz(self._ptr)
end
function Net:has_chunk()
return C.pxl8_net_has_chunk(self._ptr)
function Net:connect()
return C.pxl8_net_connect(self._ptr) == 0
end
function Net:connected()
return C.pxl8_net_connected(self._ptr)
end
function Net:send_input(input)
local msg = ffi.new("pxl8_input_msg")
msg.buttons = input.buttons or 0
msg.look_dx = input.look_dx or 0
msg.look_dy = input.look_dy or 0
msg.move_x = input.move_x or 0
msg.move_y = input.move_y or 0
msg.yaw = input.yaw or 0
msg.tick = input.tick or 0
msg.timestamp = input.timestamp or 0
return C.pxl8_net_send_input(self._ptr, msg) == 0
function Net:disconnect()
C.pxl8_net_disconnect(self._ptr)
end
function Net:spawn(x, y, z, yaw, pitch)
return C.pxl8_net_spawn(self._ptr, x or 0, y or 0, z or 0, yaw or 0, pitch or 0) == 0
function Net:poll()
return C.pxl8_net_poll(self._ptr)
end
return net

View file

@ -1,159 +0,0 @@
local ffi = require("ffi")
local C = ffi.C
local core = require("pxl8.core")
local world = {}
local Bsp = {}
Bsp.__index = Bsp
function Bsp:face_count()
return C.pxl8_bsp_face_count(self._ptr)
end
function Bsp:face_normal(face_id)
return C.pxl8_bsp_face_normal(self._ptr, face_id)
end
function Bsp:light_at(x, y, z, ambient)
return C.pxl8_bsp_light_at(self._ptr, x, y, z, ambient or 0)
end
function Bsp:face_set_material(face_id, material_id)
C.pxl8_bsp_face_set_material(self._ptr, face_id, material_id)
end
world.Bsp = Bsp
local Chunk = {}
Chunk.__index = Chunk
function Chunk:bsp()
if self._ptr == nil then return nil end
if self._ptr.bsp == nil then return nil end
return setmetatable({ _ptr = self._ptr.bsp }, Bsp)
end
function Chunk:ready()
return self._ptr ~= nil and self._ptr.bsp ~= nil
end
function Chunk:version()
if self._ptr == nil then return 0 end
return self._ptr.version
end
world.Chunk = Chunk
local World = {}
World.__index = World
function World.get()
local w = C.pxl8_get_world(core.sys)
if w == nil then return nil end
return setmetatable({ _ptr = w }, World)
end
function World:active_chunk()
local ptr = C.pxl8_world_active_chunk(self._ptr)
if ptr == nil then return nil end
return setmetatable({ _ptr = ptr }, Chunk)
end
function World:init_local_player(x, y, z)
C.pxl8_world_init_local_player(self._ptr, x, y, z)
end
function World:set_look(yaw, pitch)
C.pxl8_world_set_look(self._ptr, yaw, pitch or 0)
end
function World:local_player()
local ptr = C.pxl8_world_local_player(self._ptr)
if ptr == nil then return nil end
return ptr
end
function World:point_solid(x, y, z)
return C.pxl8_world_point_solid(self._ptr, x, y, z)
end
function World:ray(from_x, from_y, from_z, to_x, to_y, to_z)
local from = ffi.new("pxl8_vec3", {x = from_x, y = from_y, z = from_z})
local to = ffi.new("pxl8_vec3", {x = to_x, y = to_y, z = to_z})
return C.pxl8_world_ray(self._ptr, from, to)
end
function World:render(camera_pos)
local vec = ffi.new("pxl8_vec3", {x = camera_pos[1], y = camera_pos[2], z = camera_pos[3]})
C.pxl8_world_render(self._ptr, core.gfx, vec)
end
function World:set_bsp_material(material_id, material)
C.pxl8_world_set_bsp_material(self._ptr, material_id, material._ptr)
end
function World:sweep(from_x, from_y, from_z, to_x, to_y, to_z, radius)
local from = ffi.new("pxl8_vec3", {x = from_x, y = from_y, z = from_z})
local to = ffi.new("pxl8_vec3", {x = to_x, y = to_y, z = to_z})
return C.pxl8_world_sweep(self._ptr, from, to, radius)
end
function World:set_sim_config(config)
C.pxl8_world_set_sim_config(self._ptr, config)
end
function World:push_input(input_msg)
C.pxl8_world_push_input(self._ptr, input_msg)
end
world.World = World
function world.sim_config(opts)
opts = opts or {}
return ffi.new("pxl8_sim_config", {
move_speed = opts.move_speed or 180.0,
ground_accel = opts.ground_accel or 10.0,
air_accel = opts.air_accel or 1.0,
stop_speed = opts.stop_speed or 100.0,
friction = opts.friction or 6.0,
gravity = opts.gravity or 800.0,
jump_velocity = opts.jump_velocity or 200.0,
player_radius = opts.player_radius or 16.0,
player_height = opts.player_height or 72.0,
max_pitch = opts.max_pitch or 1.5,
})
end
function world.sim_move_player(entity, input_msg, sim_world, config, dt)
C.pxl8_sim_move_player(entity, input_msg, sim_world, config, dt)
end
function world.sim_trace(sim_world, from_x, from_y, from_z, to_x, to_y, to_z, radius, height)
local from = ffi.new("pxl8_vec3", {x = from_x, y = from_y, z = from_z})
local to = ffi.new("pxl8_vec3", {x = to_x, y = to_y, z = to_z})
return C.pxl8_sim_trace(sim_world, from, to, radius, height)
end
function world.sim_check_ground(sim_world, x, y, z, radius)
local pos = ffi.new("pxl8_vec3", {x = x, y = y, z = z})
return C.pxl8_sim_check_ground(sim_world, pos, radius)
end
function World:sim_world(x, y, z)
local pos = ffi.new("pxl8_vec3", {x = x, y = y, z = z})
return C.pxl8_world_sim_world(self._ptr, pos)
end
function world.make_input_msg(opts)
opts = opts or {}
return ffi.new("pxl8_input_msg", {
move_x = opts.move_x or 0,
move_y = opts.move_y or 0,
look_dx = opts.look_dx or 0,
look_dy = opts.look_dy or 0,
buttons = opts.buttons or 0,
})
end
return world

View file

@ -3,18 +3,15 @@
#include <stdlib.h>
#include <string.h>
#include "pxl8_hal.h"
#include "pxl8_platform.h"
#ifdef PXL8_ASYNC_THREADS
#include <stdatomic.h>
#include "pxl8_queue.h"
#endif
#include "pxl8_bytes.h"
#include "pxl8_log.h"
#include "pxl8_mem.h"
#include "pxl8_world.h"
#include "pxl8_world_chunk_cache.h"
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
@ -37,7 +34,6 @@
#endif
#define PXL8_NET_DEFAULT_PORT 7777
#define PXL8_NET_TICK_RATE 30.0f
struct pxl8_net {
char address[256];
@ -46,27 +42,6 @@ struct pxl8_net {
struct sockaddr_in server_addr;
socket_t sock;
pxl8_world_chunk_cache* chunk_cache;
i32 chunk_cx;
i32 chunk_cz;
bool has_chunk;
pxl8_world* world;
u64 highest_tick;
f32 interp_time;
pxl8_entity_state entities[PXL8_MAX_SNAPSHOT_ENTITIES];
pxl8_entity_state prev_entities[PXL8_MAX_SNAPSHOT_ENTITIES];
pxl8_snapshot_header prev_snapshot;
pxl8_snapshot_header snapshot;
u64 input_head;
pxl8_input_msg input_history[PXL8_NET_INPUT_HISTORY_SIZE];
u64 input_oldest_tick;
u8 predicted_state[PXL8_NET_USERDATA_SIZE];
u64 predicted_tick;
u8 recv_buf[4096];
u8 send_buf[4096];
@ -77,13 +52,6 @@ struct pxl8_net {
#endif
};
static const pxl8_entity_state* find_entity(const pxl8_entity_state* entities, u16 count, u64 id) {
for (u16 i = 0; i < count; i++) {
if (entities[i].entity_id == id) return &entities[i];
}
return NULL;
}
pxl8_result pxl8_net_connect(pxl8_net* net) {
if (!net) return PXL8_ERROR_INVALID_ARGUMENT;
if (net->connected) return PXL8_OK;
@ -161,154 +129,11 @@ void pxl8_net_disconnect(pxl8_net* net) {
net->connected = false;
}
const pxl8_entity_state* pxl8_net_entities(const pxl8_net* net) {
if (!net) return NULL;
return net->entities;
}
const u8* pxl8_net_entity_prev_userdata(const pxl8_net* net, u64 entity_id) {
if (!net) return NULL;
const pxl8_entity_state* e = find_entity(net->prev_entities, net->prev_snapshot.entity_count, entity_id);
return e ? e->userdata : NULL;
}
const u8* pxl8_net_entity_userdata(const pxl8_net* net, u64 entity_id) {
if (!net) return NULL;
const pxl8_entity_state* e = find_entity(net->entities, net->snapshot.entity_count, entity_id);
return e ? e->userdata : NULL;
}
const pxl8_input_msg* pxl8_net_input_at(const pxl8_net* net, u64 tick) {
if (!net) return NULL;
for (u64 i = 0; i < PXL8_NET_INPUT_HISTORY_SIZE; i++) {
if (net->input_history[i].tick == tick) {
return &net->input_history[i];
}
}
return NULL;
}
u64 pxl8_net_input_oldest_tick(const pxl8_net* net) {
if (!net) return 0;
return net->input_oldest_tick;
}
void pxl8_net_input_push(pxl8_net* net, const pxl8_input_msg* input) {
if (!net || !input) return;
u64 idx = net->input_head % PXL8_NET_INPUT_HISTORY_SIZE;
net->input_history[idx] = *input;
net->input_head++;
if (net->input_oldest_tick == 0 || input->tick < net->input_oldest_tick) {
net->input_oldest_tick = input->tick;
}
}
f32 pxl8_net_lerp_alpha(const pxl8_net* net) {
if (!net) return 1.0f;
f32 tick_duration = 1.0f / PXL8_NET_TICK_RATE;
f32 alpha = net->interp_time / tick_duration;
return alpha > 1.0f ? 1.0f : alpha;
}
bool pxl8_net_needs_correction(const pxl8_net* net) {
if (!net) return false;
if (net->snapshot.tick == 0) return false;
if (net->predicted_tick == 0) return false;
if (net->snapshot.tick > net->predicted_tick) return true;
const u8* server = pxl8_net_entity_userdata(net, net->snapshot.player_id);
if (!server) return false;
return memcmp(server, net->predicted_state, PXL8_NET_USERDATA_SIZE) != 0;
}
u64 pxl8_net_player_id(const pxl8_net* net) {
if (!net) return 0;
return net->snapshot.player_id;
}
static bool dispatch_message(pxl8_net* net, const u8* data, usize len) {
if (len < sizeof(pxl8_msg_header)) return false;
pxl8_msg_header hdr;
usize offset = pxl8_protocol_deserialize_header(data, len, &hdr);
if (hdr.type == PXL8_MSG_CHUNK) {
if (!net->chunk_cache) return false;
pxl8_chunk_msg_header chunk_hdr;
offset += pxl8_protocol_deserialize_chunk_msg_header(data + offset, len - offset, &chunk_hdr);
const u8* payload = data + offset;
usize payload_len = chunk_hdr.payload_size;
if (payload_len > len - offset) {
payload_len = len - offset;
}
pxl8_world_chunk_cache_receive(net->chunk_cache, &chunk_hdr, payload, payload_len);
return true;
}
if (hdr.type == PXL8_MSG_CHUNK_ENTER) {
pxl8_chunk_enter_msg chunk_msg;
pxl8_protocol_deserialize_chunk_enter(data + offset, len - offset, &chunk_msg);
net->chunk_cx = chunk_msg.cx;
net->chunk_cz = chunk_msg.cz;
net->has_chunk = true;
pxl8_debug("[CLIENT] Received CHUNK_ENTER cx=%d cz=%d", chunk_msg.cx, chunk_msg.cz);
return true;
}
if (hdr.type == PXL8_MSG_CHUNK_EXIT) {
net->has_chunk = false;
return true;
}
if (hdr.type != PXL8_MSG_SNAPSHOT) return false;
pxl8_snapshot_header snap;
offset += pxl8_protocol_deserialize_snapshot_header(data + offset, len - offset, &snap);
if (snap.tick <= net->highest_tick) return false;
memcpy(net->prev_entities, net->entities, sizeof(net->entities));
net->prev_snapshot = net->snapshot;
net->highest_tick = snap.tick;
net->snapshot = snap;
net->interp_time = 0.0f;
u16 count = snap.entity_count;
if (count > PXL8_MAX_SNAPSHOT_ENTITIES) count = PXL8_MAX_SNAPSHOT_ENTITIES;
for (u16 i = 0; i < count; i++) {
offset += pxl8_protocol_deserialize_entity_state(
data + offset, len - offset, &net->entities[i]);
}
return true;
}
bool pxl8_net_poll(pxl8_net* net) {
if (!net || !net->connected) return false;
usize len = pxl8_net_recv(net, net->recv_buf, sizeof(net->recv_buf));
u64 prev_tick = net->highest_tick;
if (!dispatch_message(net, net->recv_buf, len)) return false;
if (net->highest_tick > prev_tick && net->world) {
pxl8_world_reconcile(net->world, net, 1.0f / 30.0f);
}
return true;
}
u8* pxl8_net_predicted_state(pxl8_net* net) {
if (!net) return NULL;
return net->predicted_state;
}
void pxl8_net_predicted_tick_set(pxl8_net* net, u64 tick) {
if (!net) return;
net->predicted_tick = tick;
return len > 0;
}
usize pxl8_net_recv(pxl8_net* net, u8* buf, usize len) {
@ -330,103 +155,6 @@ pxl8_result pxl8_net_send(pxl8_net* net, const u8* data, usize len) {
return (sent > 0) ? PXL8_OK : PXL8_ERROR_SYSTEM_FAILURE;
}
pxl8_result pxl8_net_send_command(pxl8_net* net, const pxl8_command_msg* cmd) {
if (!net || !net->connected) return PXL8_ERROR_INVALID_ARGUMENT;
u8 buf[sizeof(pxl8_msg_header) + sizeof(pxl8_command_msg)];
pxl8_msg_header hdr = {
.type = PXL8_MSG_COMMAND,
.version = PXL8_PROTOCOL_VERSION,
.size = sizeof(pxl8_command_msg),
.sequence = 0
};
usize offset = pxl8_protocol_serialize_header(&hdr, buf, sizeof(buf));
offset += pxl8_protocol_serialize_command(cmd, buf + offset, sizeof(buf) - offset);
return pxl8_net_send(net, buf, offset);
}
pxl8_result pxl8_net_send_input(pxl8_net* net, const pxl8_input_msg* input) {
if (!net || !net->connected) return PXL8_ERROR_INVALID_ARGUMENT;
pxl8_net_input_push(net, input);
u8 buf[sizeof(pxl8_msg_header) + sizeof(pxl8_input_msg)];
pxl8_msg_header hdr = {
.type = PXL8_MSG_INPUT,
.version = PXL8_PROTOCOL_VERSION,
.size = sizeof(pxl8_input_msg),
.sequence = 0
};
usize offset = pxl8_protocol_serialize_header(&hdr, buf, sizeof(buf));
offset += pxl8_protocol_serialize_input(input, buf + offset, sizeof(buf) - offset);
return pxl8_net_send(net, buf, offset);
}
const pxl8_snapshot_header* pxl8_net_snapshot(const pxl8_net* net) {
if (!net) return NULL;
return &net->snapshot;
}
u64 pxl8_net_tick(const pxl8_net* net) {
if (!net) return 0;
return net->snapshot.tick;
}
void pxl8_net_update(pxl8_net* net, f32 dt) {
if (!net) return;
net->interp_time += dt;
}
void pxl8_net_set_chunk_cache(pxl8_net* net, pxl8_world_chunk_cache* cache) {
if (!net) return;
net->chunk_cache = cache;
}
pxl8_world_chunk_cache* pxl8_net_chunk_cache(pxl8_net* net) {
if (!net) return NULL;
return net->chunk_cache;
}
void pxl8_net_set_world(pxl8_net* net, pxl8_world* world) {
if (!net) return;
net->world = world;
}
i32 pxl8_net_chunk_cx(const pxl8_net* net) {
if (!net) return 0;
return net->chunk_cx;
}
i32 pxl8_net_chunk_cz(const pxl8_net* net) {
if (!net) return 0;
return net->chunk_cz;
}
bool pxl8_net_has_chunk(const pxl8_net* net) {
if (!net) return false;
return net->has_chunk;
}
pxl8_result pxl8_net_spawn(pxl8_net* net, f32 x, f32 y, f32 z, f32 yaw, f32 pitch) {
if (!net) return PXL8_ERROR_NULL_POINTER;
if (!net->connected) return PXL8_ERROR_NOT_CONNECTED;
pxl8_command_msg cmd = {0};
cmd.cmd_type = PXL8_CMD_SPAWN_ENTITY;
pxl8_pack_f32_be(cmd.payload, 0, x);
pxl8_pack_f32_be(cmd.payload, 4, y);
pxl8_pack_f32_be(cmd.payload, 8, z);
pxl8_pack_f32_be(cmd.payload, 12, yaw);
pxl8_pack_f32_be(cmd.payload, 16, pitch);
cmd.payload_size = 20;
return pxl8_net_send_command(net, &cmd);
}
#ifdef PXL8_ASYNC_THREADS
static int pxl8_net_recv_thread(void* data) {
@ -494,7 +222,9 @@ void pxl8_net_packet_free(pxl8_packet* pkt) {
bool pxl8_net_process_packet(pxl8_net* net, const pxl8_packet* pkt) {
if (!net || !pkt) return false;
return dispatch_message(net, pkt->data, pkt->len);
(void)net;
(void)pkt;
return false;
}
#endif

View file

@ -1,19 +1,14 @@
#pragma once
#include "pxl8_protocol.h"
#include "pxl8_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#define PXL8_NET_INPUT_HISTORY_SIZE 64
#define PXL8_NET_USERDATA_SIZE 56
#define PXL8_NET_PACKET_MAX_SIZE 2048
typedef struct pxl8_net pxl8_net;
typedef struct pxl8_world pxl8_world;
typedef struct pxl8_world_chunk_cache pxl8_world_chunk_cache;
typedef struct pxl8_packet {
u8 data[PXL8_NET_PACKET_MAX_SIZE];
@ -30,34 +25,9 @@ bool pxl8_net_connected(const pxl8_net* net);
pxl8_net* pxl8_net_create(const pxl8_net_config* config);
void pxl8_net_destroy(pxl8_net* net);
void pxl8_net_disconnect(pxl8_net* net);
const pxl8_entity_state* pxl8_net_entities(const pxl8_net* net);
const u8* pxl8_net_entity_prev_userdata(const pxl8_net* net, u64 entity_id);
const u8* pxl8_net_entity_userdata(const pxl8_net* net, u64 entity_id);
const pxl8_input_msg* pxl8_net_input_at(const pxl8_net* net, u64 tick);
u64 pxl8_net_input_oldest_tick(const pxl8_net* net);
void pxl8_net_input_push(pxl8_net* net, const pxl8_input_msg* input);
f32 pxl8_net_lerp_alpha(const pxl8_net* net);
bool pxl8_net_needs_correction(const pxl8_net* net);
u64 pxl8_net_player_id(const pxl8_net* net);
bool pxl8_net_poll(pxl8_net* net);
u8* pxl8_net_predicted_state(pxl8_net* net);
void pxl8_net_predicted_tick_set(pxl8_net* net, u64 tick);
usize pxl8_net_recv(pxl8_net* net, u8* buf, usize len);
pxl8_result pxl8_net_send(pxl8_net* net, const u8* data, usize len);
pxl8_result pxl8_net_send_command(pxl8_net* net, const pxl8_command_msg* cmd);
pxl8_result pxl8_net_send_input(pxl8_net* net, const pxl8_input_msg* input);
const pxl8_snapshot_header* pxl8_net_snapshot(const pxl8_net* net);
u64 pxl8_net_tick(const pxl8_net* net);
void pxl8_net_update(pxl8_net* net, f32 dt);
void pxl8_net_set_chunk_cache(pxl8_net* net, pxl8_world_chunk_cache* cache);
pxl8_world_chunk_cache* pxl8_net_chunk_cache(pxl8_net* net);
void pxl8_net_set_world(pxl8_net* net, pxl8_world* world);
i32 pxl8_net_chunk_cx(const pxl8_net* net);
i32 pxl8_net_chunk_cz(const pxl8_net* net);
bool pxl8_net_has_chunk(const pxl8_net* net);
pxl8_result pxl8_net_spawn(pxl8_net* net, f32 x, f32 y, f32 z, f32 yaw, f32 pitch);
#ifdef PXL8_ASYNC_THREADS
void pxl8_net_start_thread(pxl8_net* net);

View file

@ -1,197 +0,0 @@
#include "pxl8_protocol.h"
#include "pxl8_bytes.h"
usize pxl8_protocol_serialize_header(const pxl8_msg_header* msg, u8* buf, usize len) {
if (len < sizeof(pxl8_msg_header)) return 0;
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
pxl8_write_u32_be(&s, msg->sequence);
pxl8_write_u16_be(&s, msg->size);
pxl8_write_u8(&s, msg->type);
pxl8_write_u8(&s, msg->version);
return s.offset;
}
usize pxl8_protocol_deserialize_header(const u8* buf, usize len, pxl8_msg_header* msg) {
if (len < sizeof(pxl8_msg_header)) return 0;
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
msg->sequence = pxl8_read_u32_be(&s);
msg->size = pxl8_read_u16_be(&s);
msg->type = pxl8_read_u8(&s);
msg->version = pxl8_read_u8(&s);
return s.offset;
}
usize pxl8_protocol_serialize_input(const pxl8_input_msg* msg, u8* buf, usize len) {
if (len < sizeof(pxl8_input_msg)) return 0;
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
pxl8_write_u32_be(&s, msg->buttons);
pxl8_write_f32_be(&s, msg->look_dx);
pxl8_write_f32_be(&s, msg->look_dy);
pxl8_write_f32_be(&s, msg->move_x);
pxl8_write_f32_be(&s, msg->move_y);
pxl8_write_f32_be(&s, msg->yaw);
pxl8_write_u64_be(&s, msg->tick);
pxl8_write_u64_be(&s, msg->timestamp);
return s.offset;
}
usize pxl8_protocol_deserialize_input(const u8* buf, usize len, pxl8_input_msg* msg) {
if (len < sizeof(pxl8_input_msg)) return 0;
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
msg->buttons = pxl8_read_u32_be(&s);
msg->look_dx = pxl8_read_f32_be(&s);
msg->look_dy = pxl8_read_f32_be(&s);
msg->move_x = pxl8_read_f32_be(&s);
msg->move_y = pxl8_read_f32_be(&s);
msg->yaw = pxl8_read_f32_be(&s);
msg->tick = pxl8_read_u64_be(&s);
msg->timestamp = pxl8_read_u64_be(&s);
return s.offset;
}
usize pxl8_protocol_serialize_command(const pxl8_command_msg* msg, u8* buf, usize len) {
if (len < sizeof(pxl8_command_msg)) return 0;
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
pxl8_write_u16_be(&s, msg->cmd_type);
pxl8_write_bytes(&s, msg->payload, PXL8_COMMAND_PAYLOAD_SIZE);
pxl8_write_u16_be(&s, msg->payload_size);
pxl8_write_u64_be(&s, msg->tick);
return s.offset;
}
usize pxl8_protocol_deserialize_command(const u8* buf, usize len, pxl8_command_msg* msg) {
if (len < sizeof(pxl8_command_msg)) return 0;
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
msg->cmd_type = pxl8_read_u16_be(&s);
pxl8_read_bytes(&s, msg->payload, PXL8_COMMAND_PAYLOAD_SIZE);
msg->payload_size = pxl8_read_u16_be(&s);
msg->tick = pxl8_read_u64_be(&s);
return s.offset;
}
usize pxl8_protocol_serialize_entity_state(const pxl8_entity_state* state, u8* buf, usize len) {
if (len < sizeof(pxl8_entity_state)) return 0;
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
pxl8_write_u64_be(&s, state->entity_id);
pxl8_write_bytes(&s, state->userdata, 56);
return s.offset;
}
usize pxl8_protocol_deserialize_entity_state(const u8* buf, usize len, pxl8_entity_state* state) {
if (len < sizeof(pxl8_entity_state)) return 0;
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
state->entity_id = pxl8_read_u64_be(&s);
pxl8_read_bytes(&s, state->userdata, 56);
return s.offset;
}
usize pxl8_protocol_serialize_event(const pxl8_event_msg* msg, u8* buf, usize len) {
if (len < sizeof(pxl8_event_msg)) return 0;
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
pxl8_write_u8(&s, msg->event_type);
pxl8_write_bytes(&s, msg->payload, PXL8_EVENT_PAYLOAD_SIZE);
return s.offset;
}
usize pxl8_protocol_deserialize_event(const u8* buf, usize len, pxl8_event_msg* msg) {
if (len < sizeof(pxl8_event_msg)) return 0;
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
msg->event_type = pxl8_read_u8(&s);
pxl8_read_bytes(&s, msg->payload, PXL8_EVENT_PAYLOAD_SIZE);
return s.offset;
}
usize pxl8_protocol_serialize_snapshot_header(const pxl8_snapshot_header* hdr, u8* buf, usize len) {
if (len < sizeof(pxl8_snapshot_header)) return 0;
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
pxl8_write_u16_be(&s, hdr->entity_count);
pxl8_write_u16_be(&s, hdr->event_count);
pxl8_write_u64_be(&s, hdr->player_id);
pxl8_write_u64_be(&s, hdr->tick);
pxl8_write_f32_be(&s, hdr->time);
return s.offset;
}
usize pxl8_protocol_deserialize_snapshot_header(const u8* buf, usize len, pxl8_snapshot_header* hdr) {
if (len < sizeof(pxl8_snapshot_header)) return 0;
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
hdr->entity_count = pxl8_read_u16_be(&s);
hdr->event_count = pxl8_read_u16_be(&s);
hdr->player_id = pxl8_read_u64_be(&s);
hdr->tick = pxl8_read_u64_be(&s);
hdr->time = pxl8_read_f32_be(&s);
return s.offset;
}
usize pxl8_protocol_serialize_chunk_msg_header(const pxl8_chunk_msg_header* hdr, u8* buf, usize len) {
if (len < 24) return 0;
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
pxl8_write_u8(&s, hdr->chunk_type);
pxl8_write_u8(&s, hdr->flags);
pxl8_write_u8(&s, hdr->fragment_idx);
pxl8_write_u8(&s, hdr->fragment_count);
pxl8_write_u32_be(&s, hdr->id);
pxl8_write_u32_be(&s, (u32)hdr->cx);
pxl8_write_u32_be(&s, (u32)hdr->cy);
pxl8_write_u32_be(&s, (u32)hdr->cz);
pxl8_write_u32_be(&s, hdr->version);
pxl8_write_u16_be(&s, hdr->payload_size);
pxl8_write_u16_be(&s, hdr->reserved);
return s.offset;
}
usize pxl8_protocol_deserialize_chunk_msg_header(const u8* buf, usize len, pxl8_chunk_msg_header* hdr) {
if (len < 24) return 0;
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
hdr->chunk_type = pxl8_read_u8(&s);
hdr->flags = pxl8_read_u8(&s);
hdr->fragment_idx = pxl8_read_u8(&s);
hdr->fragment_count = pxl8_read_u8(&s);
hdr->id = pxl8_read_u32_be(&s);
hdr->cx = (i32)pxl8_read_u32_be(&s);
hdr->cy = (i32)pxl8_read_u32_be(&s);
hdr->cz = (i32)pxl8_read_u32_be(&s);
hdr->version = pxl8_read_u32_be(&s);
hdr->payload_size = pxl8_read_u16_be(&s);
hdr->reserved = pxl8_read_u16_be(&s);
return s.offset;
}
usize pxl8_protocol_deserialize_bsp_wire_header(const u8* buf, usize len, pxl8_bsp_wire_header* hdr) {
if (len < 48) return 0;
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
hdr->num_vertices = pxl8_read_u32_be(&s);
hdr->num_edges = pxl8_read_u32_be(&s);
hdr->num_faces = pxl8_read_u32_be(&s);
hdr->num_planes = pxl8_read_u32_be(&s);
hdr->num_nodes = pxl8_read_u32_be(&s);
hdr->num_leafs = pxl8_read_u32_be(&s);
hdr->num_surfedges = pxl8_read_u32_be(&s);
hdr->num_marksurfaces = pxl8_read_u32_be(&s);
hdr->num_cell_portals = pxl8_read_u32_be(&s);
hdr->visdata_size = pxl8_read_u32_be(&s);
hdr->num_vertex_lights = pxl8_read_u32_be(&s);
hdr->num_heightfield = pxl8_read_u32_be(&s);
return s.offset;
}
usize pxl8_protocol_serialize_chunk_enter(const pxl8_chunk_enter_msg* msg, u8* buf, usize len) {
if (len < 8) return 0;
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
pxl8_write_u32_be(&s, (u32)msg->cx);
pxl8_write_u32_be(&s, (u32)msg->cz);
return s.offset;
}
usize pxl8_protocol_deserialize_chunk_enter(const u8* buf, usize len, pxl8_chunk_enter_msg* msg) {
if (len < 8) return 0;
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
msg->cx = (i32)pxl8_read_u32_be(&s);
msg->cz = (i32)pxl8_read_u32_be(&s);
return s.offset;
}
u32 pxl8_chunk_hash(i32 cx, i32 cz) {
u32 h = (u32)cx * 374761393u + (u32)cz * 668265263u;
return h ^ (h >> 16);
}

View file

@ -1,143 +0,0 @@
#pragma once
#include "pxl8_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#define PXL8_PROTOCOL_VERSION 1
#define PXL8_MAX_SNAPSHOT_ENTITIES 256
#define PXL8_MAX_SNAPSHOT_EVENTS 32
#define PXL8_COMMAND_PAYLOAD_SIZE 64
#define PXL8_EVENT_PAYLOAD_SIZE 15
typedef enum pxl8_msg_type {
PXL8_MSG_NONE = 0,
PXL8_MSG_CHUNK,
PXL8_MSG_CHUNK_ENTER,
PXL8_MSG_CHUNK_EXIT,
PXL8_MSG_COMMAND,
PXL8_MSG_CONNECT,
PXL8_MSG_DISCONNECT,
PXL8_MSG_EVENT,
PXL8_MSG_INPUT,
PXL8_MSG_SNAPSHOT
} pxl8_msg_type;
typedef struct pxl8_msg_header {
u32 sequence;
u16 size;
u8 type;
u8 version;
} pxl8_msg_header;
typedef enum pxl8_cmd_type {
PXL8_CMD_NONE = 0,
PXL8_CMD_SPAWN_ENTITY,
} pxl8_cmd_type;
typedef struct pxl8_input_msg {
u32 buttons;
f32 look_dx;
f32 look_dy;
f32 move_x;
f32 move_y;
f32 yaw;
u64 tick;
u64 timestamp;
} pxl8_input_msg;
typedef struct pxl8_command_msg {
u16 cmd_type;
u8 payload[PXL8_COMMAND_PAYLOAD_SIZE];
u16 payload_size;
u64 tick;
} pxl8_command_msg;
typedef struct pxl8_entity_state {
u64 entity_id;
u8 userdata[56];
} pxl8_entity_state;
typedef struct pxl8_event_msg {
u8 event_type;
u8 payload[PXL8_EVENT_PAYLOAD_SIZE];
} pxl8_event_msg;
typedef struct pxl8_snapshot_header {
u16 entity_count;
u16 event_count;
u64 player_id;
u64 tick;
f32 time;
} pxl8_snapshot_header;
#define PXL8_CHUNK_TYPE_BSP 1
#define PXL8_CHUNK_FLAG_FINAL 0x04
#define PXL8_CHUNK_MAX_PAYLOAD 1400
typedef struct pxl8_chunk_msg_header {
u8 chunk_type;
u8 flags;
u8 fragment_idx;
u8 fragment_count;
u32 id;
i32 cx, cy, cz;
u32 version;
u16 payload_size;
u16 reserved;
} pxl8_chunk_msg_header;
typedef struct pxl8_bsp_wire_header {
u32 num_vertices;
u32 num_edges;
u32 num_faces;
u32 num_planes;
u32 num_nodes;
u32 num_leafs;
u32 num_surfedges;
u32 num_marksurfaces;
u32 num_cell_portals;
u32 visdata_size;
u32 num_vertex_lights;
u32 num_heightfield;
} pxl8_bsp_wire_header;
usize pxl8_protocol_serialize_header(const pxl8_msg_header* msg, u8* buf, usize len);
usize pxl8_protocol_deserialize_header(const u8* buf, usize len, pxl8_msg_header* msg);
usize pxl8_protocol_serialize_input(const pxl8_input_msg* msg, u8* buf, usize len);
usize pxl8_protocol_deserialize_input(const u8* buf, usize len, pxl8_input_msg* msg);
usize pxl8_protocol_serialize_command(const pxl8_command_msg* msg, u8* buf, usize len);
usize pxl8_protocol_deserialize_command(const u8* buf, usize len, pxl8_command_msg* msg);
usize pxl8_protocol_serialize_entity_state(const pxl8_entity_state* state, u8* buf, usize len);
usize pxl8_protocol_deserialize_entity_state(const u8* buf, usize len, pxl8_entity_state* state);
usize pxl8_protocol_serialize_event(const pxl8_event_msg* msg, u8* buf, usize len);
usize pxl8_protocol_deserialize_event(const u8* buf, usize len, pxl8_event_msg* msg);
usize pxl8_protocol_serialize_snapshot_header(const pxl8_snapshot_header* hdr, u8* buf, usize len);
usize pxl8_protocol_deserialize_snapshot_header(const u8* buf, usize len, pxl8_snapshot_header* hdr);
usize pxl8_protocol_serialize_chunk_msg_header(const pxl8_chunk_msg_header* hdr, u8* buf, usize len);
usize pxl8_protocol_deserialize_chunk_msg_header(const u8* buf, usize len, pxl8_chunk_msg_header* hdr);
usize pxl8_protocol_deserialize_bsp_wire_header(const u8* buf, usize len, pxl8_bsp_wire_header* hdr);
typedef struct pxl8_chunk_enter_msg {
i32 cx;
i32 cz;
} pxl8_chunk_enter_msg;
usize pxl8_protocol_serialize_chunk_enter(const pxl8_chunk_enter_msg* msg, u8* buf, usize len);
usize pxl8_protocol_deserialize_chunk_enter(const u8* buf, usize len, pxl8_chunk_enter_msg* msg);
u32 pxl8_chunk_hash(i32 cx, i32 cz);
#ifdef __cplusplus
}
#endif

View file

@ -30,7 +30,7 @@ void pxl8_cond_wait(pxl8_cond* cond, pxl8_mutex* mutex);
u64 pxl8_get_ticks_ns(void);
void pxl8_sleep_ms(u32 ms);
typedef struct pxl8_hal {
typedef struct pxl8_platform {
void* (*create)(i32 render_w, i32 render_h,
const char* title, i32 win_w, i32 win_h);
void (*destroy)(void* platform_data);
@ -48,4 +48,4 @@ typedef struct pxl8_hal {
void (*audio_stop)(void* audio_handle);
bool (*upload_audio)(void* audio_handle, const f32* stereo_samples, i32 sample_count);
i32 (*audio_queued)(void* audio_handle);
} pxl8_hal;
} pxl8_platform;

View file

@ -1,4 +1,4 @@
#include "pxl8_hal.h"
#include "pxl8_platform.h"
#define SDL_MAIN_USE_CALLBACKS
#include <SDL3/SDL.h>
@ -8,7 +8,7 @@
#include "pxl8_log.h"
#include "pxl8_sys.h"
extern const pxl8_hal pxl8_hal_sdl3;
extern const pxl8_platform pxl8_platform_sdl3;
typedef struct pxl8_sdl3_context {
SDL_Texture* framebuffer;
@ -139,13 +139,15 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {
return SDL_APP_FAILURE;
}
pxl8* sys = pxl8_create(&pxl8_hal_sdl3);
pxl8* sys = pxl8_create(&pxl8_platform_sdl3);
if (!sys) {
pxl8_error("Failed to create pxl8 system");
SDL_Quit();
return SDL_APP_FAILURE;
}
pxl8_register_game(sys);
pxl8_result result = pxl8_init(sys, argc, argv);
if (result != PXL8_OK) {
pxl8_destroy(sys);
@ -395,7 +397,7 @@ static i32 sdl3_audio_queued(void* audio_handle) {
return bytes / (audio->channels * sizeof(f32));
}
const pxl8_hal pxl8_hal_sdl3 = {
const pxl8_platform pxl8_platform_sdl3 = {
.create = sdl3_create,
.destroy = sdl3_destroy,
.get_ticks = sdl3_get_ticks,

View file

@ -1,4 +1,4 @@
#include "pxl8_hal.h"
#include "pxl8_platform.h"
#include <SDL3/SDL.h>

View file

@ -21,6 +21,8 @@
#include "pxl8_mem.h"
#include "pxl8_script_ffi.h"
#define PXL8_SCRIPT_MAX_FFI_EXTENSIONS 8
struct pxl8_script {
lua_State* L;
pxl8_gfx* gfx;
@ -32,6 +34,8 @@ struct pxl8_script {
f64 latest_mod_time;
int repl_env_ref;
bool repl_mode;
const char* ffi_extensions[PXL8_SCRIPT_MAX_FFI_EXTENSIONS];
u32 ffi_extension_count;
};
#define PXL8_MAX_REPL_COMMAND_SIZE 4096
@ -354,6 +358,33 @@ void pxl8_script_destroy(pxl8_script* script) {
pxl8_free(script);
}
void pxl8_script_add_ffi(pxl8_script* script, const char* cdefs) {
if (!script || !cdefs) return;
if (script->ffi_extension_count >= PXL8_SCRIPT_MAX_FFI_EXTENSIONS) return;
script->ffi_extensions[script->ffi_extension_count++] = cdefs;
if (script->L) {
lua_getglobal(script->L, "require");
lua_pushstring(script->L, "ffi");
if (lua_pcall(script->L, 1, 1, 0) != 0) {
pxl8_error("FFI require failed: %s", lua_tostring(script->L, -1));
lua_pop(script->L, 1);
return;
}
lua_getfield(script->L, -1, "cdef");
lua_pushstring(script->L, cdefs);
if (lua_pcall(script->L, 1, 0, 0) != 0) {
pxl8_error("FFI extension cdef failed: %s", lua_tostring(script->L, -1));
lua_pop(script->L, 1);
return;
}
lua_pop(script->L, 1);
}
}
void pxl8_script_set_gfx(pxl8_script* script, pxl8_gfx* gfx) {
if (!script) return;
script->gfx = gfx;

View file

@ -13,6 +13,7 @@ extern "C" {
pxl8_script* pxl8_script_create(bool repl_mode);
void pxl8_script_destroy(pxl8_script* script);
void pxl8_script_add_ffi(pxl8_script* script, const char* cdefs);
const char* pxl8_script_get_last_error(pxl8_script* script);
bool pxl8_script_is_incomplete_input(pxl8_script* script);

View file

@ -238,27 +238,27 @@ static const char* pxl8_ffi_cdefs =
" f32 time;\n"
"} pxl8_3d_uniforms;\n"
"\n"
"typedef struct pxl8_3d_camera pxl8_3d_camera;\n"
"pxl8_3d_camera* pxl8_3d_camera_create(void);\n"
"void pxl8_3d_camera_destroy(pxl8_3d_camera* cam);\n"
"void pxl8_3d_camera_set_perspective(pxl8_3d_camera* cam, f32 fov, f32 aspect, f32 near, f32 far);\n"
"void pxl8_3d_camera_set_position(pxl8_3d_camera* cam, pxl8_vec3 pos);\n"
"void pxl8_3d_camera_set_rotation(pxl8_3d_camera* cam, f32 pitch, f32 yaw, f32 roll);\n"
"void pxl8_3d_camera_lookat(pxl8_3d_camera* cam, pxl8_vec3 eye, pxl8_vec3 target, pxl8_vec3 up);\n"
"pxl8_vec3 pxl8_3d_camera_get_forward(const pxl8_3d_camera* cam);\n"
"pxl8_vec3 pxl8_3d_camera_get_position(const pxl8_3d_camera* cam);\n"
"pxl8_vec3 pxl8_3d_camera_get_right(const pxl8_3d_camera* cam);\n"
"pxl8_vec3 pxl8_3d_camera_get_up(const pxl8_3d_camera* cam);\n"
"pxl8_mat4 pxl8_3d_camera_get_view(const pxl8_3d_camera* cam);\n"
"pxl8_mat4 pxl8_3d_camera_get_projection(const pxl8_3d_camera* cam);\n"
"void pxl8_3d_camera_update(pxl8_3d_camera* cam, f32 dt);\n"
"typedef struct pxl8_camera3d pxl8_camera3d;\n"
"pxl8_camera3d* pxl8_camera3d_create(void);\n"
"void pxl8_camera3d_destroy(pxl8_camera3d* cam);\n"
"void pxl8_camera3d_set_perspective(pxl8_camera3d* cam, f32 fov, f32 aspect, f32 near, f32 far);\n"
"void pxl8_camera3d_set_position(pxl8_camera3d* cam, pxl8_vec3 pos);\n"
"void pxl8_camera3d_set_rotation(pxl8_camera3d* cam, f32 pitch, f32 yaw, f32 roll);\n"
"void pxl8_camera3d_lookat(pxl8_camera3d* cam, pxl8_vec3 eye, pxl8_vec3 target, pxl8_vec3 up);\n"
"pxl8_vec3 pxl8_camera3d_get_forward(const pxl8_camera3d* cam);\n"
"pxl8_vec3 pxl8_camera3d_get_position(const pxl8_camera3d* cam);\n"
"pxl8_vec3 pxl8_camera3d_get_right(const pxl8_camera3d* cam);\n"
"pxl8_vec3 pxl8_camera3d_get_up(const pxl8_camera3d* cam);\n"
"pxl8_mat4 pxl8_camera3d_get_view(const pxl8_camera3d* cam);\n"
"pxl8_mat4 pxl8_camera3d_get_projection(const pxl8_camera3d* cam);\n"
"void pxl8_camera3d_update(pxl8_camera3d* cam, f32 dt);\n"
"typedef struct pxl8_projected_point { i32 x; i32 y; f32 depth; bool visible; } pxl8_projected_point;\n"
"pxl8_projected_point pxl8_3d_camera_world_to_screen(const pxl8_3d_camera* cam, pxl8_vec3 world_pos, u32 screen_width, u32 screen_height);\n"
"pxl8_projected_point pxl8_camera3d_world_to_screen(const pxl8_camera3d* cam, pxl8_vec3 world_pos, u32 screen_width, u32 screen_height);\n"
"\n"
"void pxl8_gfx_blend_tables_update(pxl8_gfx* gfx);\n"
"void pxl8_gfx_colormap_update(pxl8_gfx* gfx);\n"
"\n"
"void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_3d_uniforms* uniforms);\n"
"void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_camera3d* camera, const pxl8_lights* lights, const pxl8_3d_uniforms* uniforms);\n"
"void pxl8_3d_clear(pxl8_gfx* gfx, u8 color);\n"
"void pxl8_3d_clear_depth(pxl8_gfx* gfx);\n"
"void pxl8_3d_clear_stencil(pxl8_gfx* gfx, uint8_t value);\n"
@ -408,50 +408,6 @@ static const char* pxl8_ffi_cdefs =
"void pxl8_graph_set_seed(pxl8_graph* graph, u32 seed);\n"
"void pxl8_graph_eval_texture(const pxl8_graph* graph, u8* buffer, i32 width, i32 height);\n"
"\n"
"typedef struct pxl8_bsp pxl8_bsp;\n"
"\n"
"u32 pxl8_bsp_face_count(const pxl8_bsp* bsp);\n"
"pxl8_vec3 pxl8_bsp_face_normal(const pxl8_bsp* bsp, u32 face_id);\n"
"void pxl8_bsp_face_set_material(pxl8_bsp* bsp, u32 face_id, u16 material_id);\n"
"u8 pxl8_bsp_light_at(const pxl8_bsp* bsp, f32 x, f32 y, f32 z, u8 ambient);\n"
"\n"
"typedef struct pxl8_world_chunk {\n"
" u32 id;\n"
" u32 version;\n"
" pxl8_bsp* bsp;\n"
"} pxl8_world_chunk;\n"
"\n"
"typedef struct pxl8_world pxl8_world;\n"
"\n"
"typedef struct pxl8_ray {\n"
" pxl8_vec3 normal;\n"
" pxl8_vec3 point;\n"
" float fraction;\n"
" bool hit;\n"
"} pxl8_ray;\n"
"\n"
"pxl8_world* pxl8_get_world(pxl8* sys);\n"
"pxl8_world_chunk* pxl8_world_active_chunk(pxl8_world* world);\n"
"bool pxl8_world_point_solid(const pxl8_world* world, float x, float y, float z);\n"
"pxl8_ray pxl8_world_ray(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to);\n"
"pxl8_ray pxl8_world_sweep(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, float radius);\n"
"void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);\n"
"void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_gfx_material* material);\n"
"\n"
"typedef struct pxl8_sim_entity {\n"
" pxl8_vec3 pos;\n"
" pxl8_vec3 vel;\n"
" f32 yaw;\n"
" f32 pitch;\n"
" u32 flags;\n"
" u16 kind;\n"
" u16 _pad;\n"
"} pxl8_sim_entity;\n"
"\n"
"void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z);\n"
"void pxl8_world_set_look(pxl8_world* world, f32 yaw, f32 pitch);\n"
"pxl8_sim_entity* pxl8_world_local_player(pxl8_world* world);\n"
"\n"
"typedef struct { i32 cursor_x; i32 cursor_y; bool cursor_down; bool cursor_clicked; u32 hot_id; u32 active_id; } pxl8_gui_state;\n"
"pxl8_gui_state* pxl8_gui_state_create(void);\n"
"void pxl8_gui_state_destroy(pxl8_gui_state* state);\n"
@ -533,95 +489,12 @@ static const char* pxl8_ffi_cdefs =
"\n"
"typedef struct pxl8_net pxl8_net;\n"
"typedef struct pxl8_net_config { const char* address; u16 port; } pxl8_net_config;\n"
"typedef enum pxl8_cmd_type { PXL8_CMD_NONE = 0, PXL8_CMD_SPAWN_ENTITY } pxl8_cmd_type;\n"
"\n"
"typedef struct pxl8_command_msg {\n"
" u16 cmd_type;\n"
" u8 payload[64];\n"
" u16 payload_size;\n"
" u64 tick;\n"
"} pxl8_command_msg;\n"
"\n"
"typedef struct pxl8_input_msg {\n"
" u32 buttons;\n"
" f32 look_dx;\n"
" f32 look_dy;\n"
" f32 move_x;\n"
" f32 move_y;\n"
" f32 yaw;\n"
" u64 tick;\n"
" u64 timestamp;\n"
"} pxl8_input_msg;\n"
"\n"
"typedef struct pxl8_sim_config {\n"
" f32 move_speed;\n"
" f32 ground_accel;\n"
" f32 air_accel;\n"
" f32 stop_speed;\n"
" f32 friction;\n"
" f32 gravity;\n"
" f32 jump_velocity;\n"
" f32 player_radius;\n"
" f32 player_height;\n"
" f32 max_pitch;\n"
"} pxl8_sim_config;\n"
"\n"
"typedef struct pxl8_sim_world {\n"
" const pxl8_bsp* chunks[9];\n"
" i32 center_cx;\n"
" i32 center_cz;\n"
" f32 chunk_size;\n"
"} pxl8_sim_world;\n"
"\n"
"void pxl8_sim_move_player(pxl8_sim_entity* ent, const pxl8_input_msg* input, const pxl8_sim_world* world, const pxl8_sim_config* cfg, f32 dt);\n"
"void pxl8_sim_integrate(pxl8_sim_entity* ent, const pxl8_sim_world* world, const pxl8_sim_config* cfg, f32 dt);\n"
"pxl8_vec3 pxl8_sim_trace(const pxl8_sim_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius, f32 height);\n"
"bool pxl8_sim_check_ground(const pxl8_sim_world* world, pxl8_vec3 pos, f32 radius);\n"
"\n"
"pxl8_sim_world pxl8_world_sim_world(const pxl8_world* world, pxl8_vec3 pos);\n"
"void pxl8_world_set_sim_config(pxl8_world* world, const pxl8_sim_config* config);\n"
"void pxl8_world_push_input(pxl8_world* world, const pxl8_input_msg* input);\n"
"\n"
"typedef struct pxl8_entity_state {\n"
" u64 entity_id;\n"
" u8 userdata[56];\n"
"} pxl8_entity_state;\n"
"\n"
"typedef struct pxl8_snapshot_header {\n"
" u16 entity_count;\n"
" u16 event_count;\n"
" u64 player_id;\n"
" u64 tick;\n"
" f32 time;\n"
"} pxl8_snapshot_header;\n"
"\n"
"i32 pxl8_net_connect(pxl8_net* net);\n"
"bool pxl8_net_connected(const pxl8_net* net);\n"
"pxl8_net* pxl8_net_create(const pxl8_net_config* config);\n"
"void pxl8_net_destroy(pxl8_net* net);\n"
"void pxl8_net_disconnect(pxl8_net* net);\n"
"const pxl8_entity_state* pxl8_net_entities(const pxl8_net* net);\n"
"const u8* pxl8_net_entity_prev_userdata(const pxl8_net* net, u64 entity_id);\n"
"const u8* pxl8_net_entity_userdata(const pxl8_net* net, u64 entity_id);\n"
"const pxl8_input_msg* pxl8_net_input_at(const pxl8_net* net, u64 tick);\n"
"u64 pxl8_net_input_oldest_tick(const pxl8_net* net);\n"
"void pxl8_net_input_push(pxl8_net* net, const pxl8_input_msg* input);\n"
"f32 pxl8_net_lerp_alpha(const pxl8_net* net);\n"
"bool pxl8_net_needs_correction(const pxl8_net* net);\n"
"u64 pxl8_net_player_id(const pxl8_net* net);\n"
"i32 pxl8_net_chunk_cx(const pxl8_net* net);\n"
"i32 pxl8_net_chunk_cz(const pxl8_net* net);\n"
"bool pxl8_net_has_chunk(const pxl8_net* net);\n"
"bool pxl8_net_poll(pxl8_net* net);\n"
"u8* pxl8_net_predicted_state(pxl8_net* net);\n"
"void pxl8_net_predicted_tick_set(pxl8_net* net, u64 tick);\n"
"i32 pxl8_net_send_command(pxl8_net* net, const pxl8_command_msg* cmd);\n"
"i32 pxl8_net_send_input(pxl8_net* net, const pxl8_input_msg* input);\n"
"i32 pxl8_net_spawn(pxl8_net* net, f32 x, f32 y, f32 z, f32 yaw, f32 pitch);\n"
"const pxl8_snapshot_header* pxl8_net_snapshot(const pxl8_net* net);\n"
"u64 pxl8_net_tick(const pxl8_net* net);\n"
"void pxl8_net_update(pxl8_net* net, f32 dt);\n"
"pxl8_net* pxl8_get_net(const pxl8* sys);\n"
"\n"
"void pxl8_bit_clear(u32* val, u8 bit);\n"
"u32 pxl8_bit_count(u32 val);\n"

View file

@ -3,7 +3,7 @@
#include <stdlib.h>
#include <string.h>
#include "pxl8_hal.h"
#include "pxl8_platform.h"
#include "pxl8_log.h"
#include "pxl8_math.h"
#include "pxl8_mem.h"
@ -140,7 +140,7 @@ struct pxl8_sfx_context {
};
struct pxl8_sfx_mixer {
const pxl8_hal* hal;
const pxl8_platform* platform;
void* audio_handle;
pxl8_sfx_context* contexts[PXL8_SFX_MAX_CONTEXTS];
f32 master_volume;
@ -652,8 +652,8 @@ static void context_process_sample(pxl8_sfx_context* ctx, f32* out_left, f32* ou
*out_right = right;
}
pxl8_sfx_mixer* pxl8_sfx_mixer_create(const pxl8_hal* hal) {
if (!hal || !hal->audio_create) return NULL;
pxl8_sfx_mixer* pxl8_sfx_mixer_create(const pxl8_platform* platform) {
if (!platform || !platform->audio_create) return NULL;
pxl8_sfx_mixer* mixer = (pxl8_sfx_mixer*)pxl8_calloc(1, sizeof(pxl8_sfx_mixer));
if (!mixer) return NULL;
@ -664,17 +664,17 @@ pxl8_sfx_mixer* pxl8_sfx_mixer_create(const pxl8_hal* hal) {
return NULL;
}
mixer->hal = hal;
mixer->platform = platform;
mixer->master_volume = 0.8f;
mixer->audio_handle = hal->audio_create(PXL8_SFX_SAMPLE_RATE, 2);
mixer->audio_handle = platform->audio_create(PXL8_SFX_SAMPLE_RATE, 2);
if (!mixer->audio_handle) {
pxl8_free(mixer->output_buffer);
pxl8_free(mixer);
return NULL;
}
hal->audio_start(mixer->audio_handle);
platform->audio_start(mixer->audio_handle);
pxl8_info("Audio mixer initialized: %d Hz, stereo, %d context slots", PXL8_SFX_SAMPLE_RATE, PXL8_SFX_MAX_CONTEXTS);
return mixer;
@ -683,8 +683,8 @@ pxl8_sfx_mixer* pxl8_sfx_mixer_create(const pxl8_hal* hal) {
void pxl8_sfx_mixer_destroy(pxl8_sfx_mixer* mixer) {
if (!mixer) return;
if (mixer->hal && mixer->audio_handle) {
mixer->hal->audio_destroy(mixer->audio_handle);
if (mixer->platform && mixer->audio_handle) {
mixer->platform->audio_destroy(mixer->audio_handle);
}
pxl8_free(mixer->output_buffer);
@ -692,9 +692,9 @@ void pxl8_sfx_mixer_destroy(pxl8_sfx_mixer* mixer) {
}
void pxl8_sfx_mixer_process(pxl8_sfx_mixer* mixer) {
if (!mixer || !mixer->hal || !mixer->audio_handle || !mixer->output_buffer) return;
if (!mixer || !mixer->platform || !mixer->audio_handle || !mixer->output_buffer) return;
i32 queued = mixer->hal->audio_queued(mixer->audio_handle);
i32 queued = mixer->platform->audio_queued(mixer->audio_handle);
i32 target = PXL8_SFX_SAMPLE_RATE / 10;
while (queued < target) {
@ -729,7 +729,7 @@ void pxl8_sfx_mixer_process(pxl8_sfx_mixer* mixer) {
mixer->output_buffer[i * 2 + 1] = right;
}
mixer->hal->upload_audio(mixer->audio_handle, mixer->output_buffer, samples_to_generate);
mixer->platform->upload_audio(mixer->audio_handle, mixer->output_buffer, samples_to_generate);
queued += samples_to_generate;
}
}

View file

@ -1,6 +1,6 @@
#pragma once
#include "pxl8_hal.h"
#include "pxl8_platform.h"
#include "pxl8_types.h"
#define PXL8_SFX_BUFFER_SIZE 1024
@ -115,7 +115,7 @@ typedef void (*pxl8_sfx_event_callback)(u8 event_type, u8 context_id, u8 note, f
void pxl8_sfx_mixer_attach(pxl8_sfx_mixer* mixer, pxl8_sfx_context* ctx);
void pxl8_sfx_mixer_clear(pxl8_sfx_mixer* mixer);
pxl8_sfx_mixer* pxl8_sfx_mixer_create(const pxl8_hal* hal);
pxl8_sfx_mixer* pxl8_sfx_mixer_create(const pxl8_platform* platform);
void pxl8_sfx_mixer_destroy(pxl8_sfx_mixer* mixer);
void pxl8_sfx_mixer_detach(pxl8_sfx_mixer* mixer, pxl8_sfx_context* ctx);
f32 pxl8_sfx_mixer_get_master_volume(const pxl8_sfx_mixer* mixer);

View file

@ -1,448 +0,0 @@
#include "pxl8_sim.h"
#include <math.h>
#define DIST_EPSILON 0.03125f
typedef struct {
f32 fraction;
pxl8_vec3 normal;
bool all_solid;
bool start_solid;
} trace_result;
static i32 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);
}
static i32 bsp_contents_from(const pxl8_bsp* bsp, i32 node_id, pxl8_vec3 pos) {
while (node_id >= 0) {
const pxl8_bsp_node* node = &bsp->nodes[node_id];
const pxl8_bsp_plane* plane = &bsp->planes[node->plane_id];
f32 d = pxl8_vec3_dot(pos, plane->normal) - plane->dist;
node_id = node->children[d < 0 ? 1 : 0];
}
i32 leaf_idx = -(node_id + 1);
if (leaf_idx < 0 || (u32)leaf_idx >= bsp->num_leafs) return -1;
return bsp->leafs[leaf_idx].contents;
}
static bool bsp_recursive_trace(const pxl8_bsp* bsp, i32 node_id,
f32 p1f, f32 p2f,
pxl8_vec3 p1, pxl8_vec3 p2,
trace_result* tr) {
if (node_id < 0) {
i32 leaf_idx = -(node_id + 1);
if (leaf_idx >= 0 && (u32)leaf_idx < bsp->num_leafs &&
bsp->leafs[leaf_idx].contents == -1) {
tr->start_solid = true;
} else {
tr->all_solid = false;
}
return true;
}
const pxl8_bsp_node* node = &bsp->nodes[node_id];
const pxl8_bsp_plane* plane = &bsp->planes[node->plane_id];
f32 t1 = pxl8_vec3_dot(p1, plane->normal) - plane->dist;
f32 t2 = pxl8_vec3_dot(p2, plane->normal) - plane->dist;
if (t1 >= 0 && t2 >= 0)
return bsp_recursive_trace(bsp, node->children[0], p1f, p2f, p1, p2, tr);
if (t1 < 0 && t2 < 0)
return bsp_recursive_trace(bsp, node->children[1], p1f, p2f, p1, p2, tr);
i32 side;
f32 frac;
if (t1 < 0) {
frac = (t1 + DIST_EPSILON) / (t1 - t2);
side = 1;
} else {
frac = (t1 - DIST_EPSILON) / (t1 - t2);
side = 0;
}
if (frac < 0) frac = 0;
if (frac > 1) frac = 1;
f32 midf = p1f + (p2f - p1f) * frac;
pxl8_vec3 mid = {
p1.x + frac * (p2.x - p1.x),
p1.y + frac * (p2.y - p1.y),
p1.z + frac * (p2.z - p1.z),
};
if (!bsp_recursive_trace(bsp, node->children[side], p1f, midf, p1, mid, tr))
return false;
if (bsp_contents_from(bsp, node->children[side ^ 1], mid) != -1)
return bsp_recursive_trace(bsp, node->children[side ^ 1], midf, p2f, mid, p2, tr);
if (tr->all_solid)
return false;
if (midf < tr->fraction) {
tr->fraction = midf;
if (side == 0) {
tr->normal = plane->normal;
} else {
tr->normal.x = -plane->normal.x;
tr->normal.y = -plane->normal.y;
tr->normal.z = -plane->normal.z;
}
}
return false;
}
static trace_result bsp_trace_line(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to) {
trace_result tr = { .fraction = 1.0f, .all_solid = true };
if (!bsp || bsp->num_nodes == 0) {
tr.all_solid = false;
return tr;
}
bsp_recursive_trace(bsp, 0, 0.0f, 1.0f, from, to, &tr);
if (tr.all_solid) {
tr.fraction = 0.0f;
tr.start_solid = true;
}
return tr;
}
bool pxl8_bsp_point_solid(const pxl8_bsp* bsp, pxl8_vec3 pos) {
if (!bsp) return false;
if (bsp->bounds_max_x > bsp->bounds_min_x &&
(pos.x < bsp->bounds_min_x || pos.x >= bsp->bounds_max_x ||
pos.z < bsp->bounds_min_z || pos.z >= bsp->bounds_max_z))
return false;
i32 leaf = bsp_find_leaf(bsp, pos);
if (leaf < 0 || (u32)leaf >= bsp->num_leafs) return true;
return bsp->leafs[leaf].contents == -1;
}
static void trace_offsets(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to,
f32 radius, f32* out_frac, pxl8_vec3* out_normal) {
f32 d = radius * 0.7071f;
pxl8_vec3 offsets[9] = {
{0, 0, 0},
{radius, 0, 0}, {-radius, 0, 0},
{0, 0, radius}, {0, 0, -radius},
{d, 0, d}, {d, 0, -d},
{-d, 0, d}, {-d, 0, -d},
};
*out_frac = 1.0f;
*out_normal = (pxl8_vec3){0, 0, 0};
for (i32 i = 0; i < 9; i++) {
pxl8_vec3 s = { from.x + offsets[i].x, from.y + offsets[i].y, from.z + offsets[i].z };
pxl8_vec3 e = { to.x + offsets[i].x, to.y + offsets[i].y, to.z + offsets[i].z };
trace_result tr = bsp_trace_line(bsp, s, e);
if (tr.fraction < *out_frac && !tr.start_solid) {
*out_frac = tr.fraction;
*out_normal = tr.normal;
}
}
}
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;
f32 frac;
pxl8_vec3 normal;
trace_offsets(bsp, from, to, radius, &frac, &normal);
if (frac >= 1.0f) return to;
pxl8_vec3 delta = { to.x - from.x, to.y - from.y, to.z - from.z };
pxl8_vec3 hit = {
from.x + delta.x * frac,
from.y + delta.y * frac,
from.z + delta.z * frac,
};
f32 remaining = 1.0f - frac;
if (remaining <= 0.0f) return hit;
f32 backoff = pxl8_vec3_dot(delta, normal) * remaining;
pxl8_vec3 slide_target = {
hit.x + delta.x * remaining - normal.x * backoff,
hit.y + delta.y * remaining - normal.y * backoff,
hit.z + delta.z * remaining - normal.z * backoff,
};
f32 slide_frac;
pxl8_vec3 slide_normal;
trace_offsets(bsp, hit, slide_target, radius, &slide_frac, &slide_normal);
if (slide_frac >= 1.0f) return slide_target;
pxl8_vec3 slide_delta = {
slide_target.x - hit.x,
slide_target.y - hit.y,
slide_target.z - hit.z,
};
return (pxl8_vec3){
hit.x + slide_delta.x * slide_frac,
hit.y + slide_delta.y * slide_frac,
hit.z + slide_delta.z * slide_frac,
};
}
static const pxl8_bsp* sim_bsp_at(const pxl8_sim_world* world, f32 x, f32 z) {
i32 cx = (i32)floorf(x / world->chunk_size);
i32 cz = (i32)floorf(z / world->chunk_size);
i32 dx = cx - world->center_cx + 1;
i32 dz = cz - world->center_cz + 1;
if (dx < 0 || dx > 2 || dz < 0 || dz > 2) return NULL;
return world->chunks[dz * 3 + dx];
}
static f32 bsp_terrain_height(const pxl8_bsp* bsp, f32 x, f32 z) {
if (!bsp || !bsp->heightfield || bsp->heightfield_cell_size <= 0) return -1e9f;
f32 lx = (x - bsp->heightfield_ox) / bsp->heightfield_cell_size;
f32 lz = (z - bsp->heightfield_oz) / bsp->heightfield_cell_size;
i32 ix = (i32)floorf(lx);
i32 iz = (i32)floorf(lz);
if (ix < 0 || ix >= bsp->heightfield_w - 1 || iz < 0 || iz >= bsp->heightfield_h - 1) return -1e9f;
f32 fx = lx - ix;
f32 fz = lz - iz;
i32 w = bsp->heightfield_w;
f32 h00 = bsp->heightfield[iz * w + ix];
f32 h10 = bsp->heightfield[iz * w + ix + 1];
f32 h01 = bsp->heightfield[(iz + 1) * w + ix];
f32 h11 = bsp->heightfield[(iz + 1) * w + ix + 1];
f32 h0 = h00 + (h10 - h00) * fx;
f32 h1 = h01 + (h11 - h01) * fx;
return h0 + (h1 - h0) * fz;
}
static f32 sim_terrain_height(const pxl8_sim_world* world, f32 x, f32 z) {
const pxl8_bsp* bsp = sim_bsp_at(world, x, z);
return bsp_terrain_height(bsp, x, z);
}
static void sim_trace_offsets(const pxl8_sim_world* world, pxl8_vec3 from, pxl8_vec3 to,
f32 radius, f32* out_frac, pxl8_vec3* out_normal) {
f32 d = radius * 0.7071f;
pxl8_vec3 offsets[9] = {
{0, 0, 0},
{radius, 0, 0}, {-radius, 0, 0},
{0, 0, radius}, {0, 0, -radius},
{d, 0, d}, {d, 0, -d},
{-d, 0, d}, {-d, 0, -d},
};
*out_frac = 1.0f;
*out_normal = (pxl8_vec3){0, 0, 0};
for (i32 i = 0; i < 9; i++) {
pxl8_vec3 s = { from.x + offsets[i].x, from.y + offsets[i].y, from.z + offsets[i].z };
pxl8_vec3 e = { to.x + offsets[i].x, to.y + offsets[i].y, to.z + offsets[i].z };
const pxl8_bsp* bsp_s = sim_bsp_at(world, s.x, s.z);
const pxl8_bsp* bsp_e = sim_bsp_at(world, e.x, e.z);
if (bsp_s) {
trace_result tr = bsp_trace_line(bsp_s, s, e);
if (tr.fraction < *out_frac && !tr.start_solid) {
*out_frac = tr.fraction;
*out_normal = tr.normal;
}
}
if (bsp_e && bsp_e != bsp_s) {
bool inside_bounds = !(bsp_e->bounds_max_x > bsp_e->bounds_min_x) ||
(s.x >= bsp_e->bounds_min_x && s.x < bsp_e->bounds_max_x &&
s.z >= bsp_e->bounds_min_z && s.z < bsp_e->bounds_max_z);
if (inside_bounds) {
trace_result tr = bsp_trace_line(bsp_e, s, e);
if (tr.fraction < *out_frac && !tr.start_solid) {
*out_frac = tr.fraction;
*out_normal = tr.normal;
}
}
}
}
}
pxl8_vec3 pxl8_sim_trace(const pxl8_sim_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius, f32 height) {
(void)height;
if (!world || world->chunk_size <= 0) return to;
f32 frac;
pxl8_vec3 normal;
sim_trace_offsets(world, from, to, radius, &frac, &normal);
if (frac >= 1.0f) return to;
pxl8_vec3 delta = { to.x - from.x, to.y - from.y, to.z - from.z };
pxl8_vec3 hit = {
from.x + delta.x * frac,
from.y + delta.y * frac,
from.z + delta.z * frac,
};
f32 remaining = 1.0f - frac;
if (remaining <= 0.0f) return hit;
f32 backoff = pxl8_vec3_dot(delta, normal) * remaining;
pxl8_vec3 slide_target = {
hit.x + delta.x * remaining - normal.x * backoff,
hit.y + delta.y * remaining - normal.y * backoff,
hit.z + delta.z * remaining - normal.z * backoff,
};
f32 slide_frac;
pxl8_vec3 slide_normal;
sim_trace_offsets(world, hit, slide_target, radius, &slide_frac, &slide_normal);
if (slide_frac >= 1.0f) return slide_target;
pxl8_vec3 slide_delta = {
slide_target.x - hit.x,
slide_target.y - hit.y,
slide_target.z - hit.z,
};
return (pxl8_vec3){
hit.x + slide_delta.x * slide_frac,
hit.y + slide_delta.y * slide_frac,
hit.z + slide_delta.z * slide_frac,
};
}
bool pxl8_sim_check_ground(const pxl8_sim_world* world, pxl8_vec3 pos, f32 radius) {
if (!world || world->chunk_size <= 0) return true;
f32 th = sim_terrain_height(world, pos.x, pos.z);
if (th > -1e8f && pos.y - th < 2.0f) return true;
pxl8_vec3 down = {pos.x, pos.y - 2.0f, pos.z};
pxl8_vec3 result = pxl8_sim_trace(world, pos, down, radius, 0.0f);
return result.y > down.y;
}
void pxl8_sim_move_player(pxl8_sim_entity* ent, const pxl8_input_msg* input,
const pxl8_sim_world* world, const pxl8_sim_config* cfg, f32 dt) {
if (!ent || !input || !cfg) return;
if (!(ent->flags & PXL8_SIM_FLAG_ALIVE)) return;
ent->yaw -= input->look_dx * 0.008f;
f32 new_pitch = ent->pitch - input->look_dy * 0.008f;
if (new_pitch > cfg->max_pitch) new_pitch = cfg->max_pitch;
if (new_pitch < -cfg->max_pitch) new_pitch = -cfg->max_pitch;
ent->pitch = new_pitch;
f32 sin_yaw = sinf(ent->yaw);
f32 cos_yaw = cosf(ent->yaw);
f32 input_len = sqrtf(input->move_x * input->move_x + input->move_y * input->move_y);
pxl8_vec3 move_dir = {0, 0, 0};
f32 target_speed = 0.0f;
if (input_len > 0.0f) {
f32 nx = input->move_x / input_len;
f32 ny = input->move_y / input_len;
move_dir.x = cos_yaw * nx - sin_yaw * ny;
move_dir.z = -sin_yaw * nx - cos_yaw * ny;
target_speed = cfg->move_speed;
}
ent->vel.x = move_dir.x * target_speed;
ent->vel.z = move_dir.z * target_speed;
bool grounded = (ent->flags & PXL8_SIM_FLAG_GROUNDED) != 0;
if (grounded && (input->buttons & 1)) {
ent->vel.y = cfg->jump_velocity;
ent->flags &= ~PXL8_SIM_FLAG_GROUNDED;
grounded = false;
}
if (!grounded) {
ent->vel.y -= cfg->gravity * dt;
}
pxl8_vec3 target = {
ent->pos.x + ent->vel.x * dt,
ent->pos.y + ent->vel.y * dt,
ent->pos.z + ent->vel.z * dt
};
if (grounded) {
f32 th_dest = sim_terrain_height(world, target.x, target.z);
if (th_dest > -1e8f) target.y = th_dest;
}
pxl8_vec3 new_pos = pxl8_sim_trace(world, ent->pos, target, cfg->player_radius, cfg->player_height);
f32 th = sim_terrain_height(world, new_pos.x, new_pos.z);
if (th > -1e8f && new_pos.y < th) {
new_pos.y = th;
if (ent->vel.y < 0) ent->vel.y = 0;
}
ent->pos = new_pos;
if (pxl8_sim_check_ground(world, ent->pos, cfg->player_radius)) {
ent->flags |= PXL8_SIM_FLAG_GROUNDED;
if (ent->vel.y < 0) ent->vel.y = 0;
} else {
ent->flags &= ~PXL8_SIM_FLAG_GROUNDED;
}
}
void pxl8_sim_integrate(pxl8_sim_entity* ent, const pxl8_sim_world* world,
const pxl8_sim_config* cfg, f32 dt) {
if (!ent || !cfg) return;
if (!(ent->flags & PXL8_SIM_FLAG_ALIVE)) return;
if (ent->flags & PXL8_SIM_FLAG_PLAYER) return;
bool grounded = (ent->flags & PXL8_SIM_FLAG_GROUNDED) != 0;
ent->vel.y -= cfg->gravity * dt;
if (grounded) {
f32 speed = sqrtf(ent->vel.x * ent->vel.x + ent->vel.z * ent->vel.z);
if (speed > 0.0f) {
f32 drop = speed * cfg->friction * dt;
f32 new_speed = speed - drop;
if (new_speed < 0.0f) new_speed = 0.0f;
f32 scale = new_speed / speed;
ent->vel.x *= scale;
ent->vel.z *= scale;
}
}
pxl8_vec3 target = {
ent->pos.x + ent->vel.x * dt,
ent->pos.y + ent->vel.y * dt,
ent->pos.z + ent->vel.z * dt
};
if (grounded) {
f32 th_dest = sim_terrain_height(world, target.x, target.z);
if (th_dest > -1e8f) target.y = th_dest;
}
pxl8_vec3 new_pos = pxl8_sim_trace(world, ent->pos, target, cfg->player_radius, cfg->player_height);
f32 th2 = sim_terrain_height(world, new_pos.x, new_pos.z);
if (th2 > -1e8f && new_pos.y < th2) {
new_pos.y = th2;
if (ent->vel.y < 0.0f) ent->vel.y = 0.0f;
}
ent->pos = new_pos;
if (pxl8_sim_check_ground(world, ent->pos, cfg->player_radius)) {
ent->flags |= PXL8_SIM_FLAG_GROUNDED;
if (ent->vel.y < 0.0f) ent->vel.y = 0.0f;
} else {
ent->flags &= ~PXL8_SIM_FLAG_GROUNDED;
}
}

View file

@ -1,56 +0,0 @@
#pragma once
#include "pxl8_bsp.h"
#include "pxl8_math.h"
#include "pxl8_protocol.h"
#include "pxl8_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#define PXL8_SIM_FLAG_ALIVE (1 << 0)
#define PXL8_SIM_FLAG_PLAYER (1 << 1)
#define PXL8_SIM_FLAG_GROUNDED (1 << 2)
typedef struct pxl8_sim_config {
f32 move_speed;
f32 ground_accel;
f32 air_accel;
f32 stop_speed;
f32 friction;
f32 gravity;
f32 jump_velocity;
f32 player_radius;
f32 player_height;
f32 max_pitch;
} pxl8_sim_config;
typedef struct pxl8_sim_entity {
pxl8_vec3 pos;
pxl8_vec3 vel;
f32 yaw;
f32 pitch;
u32 flags;
u16 kind;
u16 _pad;
} pxl8_sim_entity;
typedef struct pxl8_sim_world {
const pxl8_bsp* chunks[9];
i32 center_cx;
i32 center_cz;
f32 chunk_size;
} pxl8_sim_world;
bool pxl8_bsp_point_solid(const pxl8_bsp* bsp, pxl8_vec3 pos);
pxl8_vec3 pxl8_bsp_trace(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to, f32 radius);
pxl8_vec3 pxl8_sim_trace(const pxl8_sim_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius, f32 height);
bool pxl8_sim_check_ground(const pxl8_sim_world* world, pxl8_vec3 pos, f32 radius);
void pxl8_sim_move_player(pxl8_sim_entity* ent, const pxl8_input_msg* input, const pxl8_sim_world* world, const pxl8_sim_config* cfg, f32 dt);
void pxl8_sim_integrate(pxl8_sim_entity* ent, const pxl8_sim_world* world, const pxl8_sim_config* cfg, f32 dt);
#ifdef __cplusplus
}
#endif

View file

@ -1,496 +0,0 @@
#include "pxl8_entity.h"
#include <string.h>
#include "pxl8_mem.h"
#define PXL8_ENTITY_COMPONENT_NAME_MAX 32
#define PXL8_ENTITY_RELATIONSHIP_NAME_MAX 32
typedef struct pxl8_component_type {
char name[PXL8_ENTITY_COMPONENT_NAME_MAX];
u32 size;
} pxl8_component_type;
typedef struct pxl8_component_storage {
u32* sparse;
void* dense_data;
pxl8_entity* dense_entities;
u32 count;
} pxl8_component_storage;
typedef struct pxl8_relationship_type {
char name[PXL8_ENTITY_RELATIONSHIP_NAME_MAX];
} pxl8_relationship_type;
typedef struct pxl8_relationship_entry {
pxl8_entity subject;
pxl8_entity object;
pxl8_entity_relationship rel;
u32 next_by_subject;
u32 next_by_object;
} pxl8_relationship_entry;
struct pxl8_entity_pool {
u32* generations;
u32* free_list;
u32 free_count;
u32 capacity;
u32 alive_count;
pxl8_component_type* component_types;
pxl8_component_storage* component_storage;
u32 component_type_count;
u32 component_type_capacity;
pxl8_relationship_type* relationship_types;
u32 relationship_type_count;
u32 relationship_type_capacity;
pxl8_relationship_entry* relationships;
u32* rel_by_subject;
u32* rel_by_object;
u32 relationship_count;
u32 relationship_capacity;
};
pxl8_entity_pool* pxl8_entity_pool_create(u32 capacity) {
pxl8_entity_pool* pool = pxl8_calloc(1, sizeof(pxl8_entity_pool));
if (!pool) return NULL;
pool->capacity = capacity;
pool->generations = pxl8_calloc(capacity, sizeof(u32));
pool->free_list = pxl8_malloc(capacity * sizeof(u32));
if (!pool->generations || !pool->free_list) {
pxl8_entity_pool_destroy(pool);
return NULL;
}
for (u32 i = 0; i < capacity; i++) {
pool->free_list[i] = capacity - 1 - i;
}
pool->free_count = capacity;
pool->component_type_capacity = 16;
pool->component_types = pxl8_calloc(pool->component_type_capacity, sizeof(pxl8_component_type));
pool->component_storage = pxl8_calloc(pool->component_type_capacity, sizeof(pxl8_component_storage));
pool->relationship_type_capacity = 16;
pool->relationship_types = pxl8_calloc(pool->relationship_type_capacity, sizeof(pxl8_relationship_type));
pool->relationship_capacity = 256;
pool->relationships = pxl8_malloc(pool->relationship_capacity * sizeof(pxl8_relationship_entry));
pool->rel_by_subject = pxl8_malloc(capacity * sizeof(u32));
pool->rel_by_object = pxl8_malloc(capacity * sizeof(u32));
for (u32 i = 0; i < capacity; i++) {
pool->rel_by_subject[i] = UINT32_MAX;
pool->rel_by_object[i] = UINT32_MAX;
}
return pool;
}
void pxl8_entity_pool_clear(pxl8_entity_pool* pool) {
if (!pool) return;
for (u32 i = 0; i < pool->capacity; i++) {
pool->generations[i] = 0;
pool->free_list[i] = pool->capacity - 1 - i;
pool->rel_by_subject[i] = UINT32_MAX;
pool->rel_by_object[i] = UINT32_MAX;
}
pool->free_count = pool->capacity;
pool->alive_count = 0;
for (u32 i = 0; i < pool->component_type_count; i++) {
pool->component_storage[i].count = 0;
}
pool->relationship_count = 0;
}
void pxl8_entity_pool_destroy(pxl8_entity_pool* pool) {
if (!pool) return;
for (u32 i = 0; i < pool->component_type_count; i++) {
pxl8_free(pool->component_storage[i].sparse);
pxl8_free(pool->component_storage[i].dense_data);
pxl8_free(pool->component_storage[i].dense_entities);
}
pxl8_free(pool->component_types);
pxl8_free(pool->component_storage);
pxl8_free(pool->relationship_types);
pxl8_free(pool->relationships);
pxl8_free(pool->rel_by_subject);
pxl8_free(pool->rel_by_object);
pxl8_free(pool->generations);
pxl8_free(pool->free_list);
pxl8_free(pool);
}
pxl8_entity pxl8_entity_spawn(pxl8_entity_pool* pool) {
if (!pool || pool->free_count == 0) return PXL8_ENTITY_INVALID;
u32 idx = pool->free_list[--pool->free_count];
pool->generations[idx]++;
pool->alive_count++;
return (pxl8_entity){ .idx = idx, .gen = pool->generations[idx] };
}
void pxl8_entity_despawn(pxl8_entity_pool* pool, pxl8_entity e) {
if (!pool || !pxl8_entity_alive(pool, e)) return;
for (u32 i = 0; i < pool->component_type_count; i++) {
pxl8_entity_component_remove(pool, e, i + 1);
}
pool->free_list[pool->free_count++] = e.idx;
pool->generations[e.idx]++;
pool->alive_count--;
}
bool pxl8_entity_alive(const pxl8_entity_pool* pool, pxl8_entity e) {
if (!pool || e.idx >= pool->capacity) return false;
return pool->generations[e.idx] == e.gen && e.gen != 0;
}
u32 pxl8_entity_count(const pxl8_entity_pool* pool) {
return pool ? pool->alive_count : 0;
}
pxl8_entity_component pxl8_entity_component_register(pxl8_entity_pool* pool, const char* name, u32 size) {
if (!pool || !name || size == 0) return PXL8_ENTITY_COMPONENT_INVALID;
pxl8_entity_component existing = pxl8_entity_component_find(pool, name);
if (existing != PXL8_ENTITY_COMPONENT_INVALID) return existing;
if (pool->component_type_count >= pool->component_type_capacity) {
u32 new_capacity = pool->component_type_capacity * 2;
pxl8_component_type* new_types = pxl8_realloc(pool->component_types, new_capacity * sizeof(pxl8_component_type));
pxl8_component_storage* new_storage = pxl8_realloc(pool->component_storage, new_capacity * sizeof(pxl8_component_storage));
if (!new_types || !new_storage) return PXL8_ENTITY_COMPONENT_INVALID;
pool->component_types = new_types;
pool->component_storage = new_storage;
memset(&pool->component_types[pool->component_type_capacity], 0, (new_capacity - pool->component_type_capacity) * sizeof(pxl8_component_type));
memset(&pool->component_storage[pool->component_type_capacity], 0, (new_capacity - pool->component_type_capacity) * sizeof(pxl8_component_storage));
pool->component_type_capacity = new_capacity;
}
u32 type_idx = pool->component_type_count++;
strncpy(pool->component_types[type_idx].name, name, PXL8_ENTITY_COMPONENT_NAME_MAX - 1);
pool->component_types[type_idx].size = size;
pxl8_component_storage* storage = &pool->component_storage[type_idx];
storage->sparse = pxl8_malloc(pool->capacity * sizeof(u32));
storage->dense_data = pxl8_malloc(pool->capacity * size);
storage->dense_entities = pxl8_malloc(pool->capacity * sizeof(pxl8_entity));
storage->count = 0;
for (u32 i = 0; i < pool->capacity; i++) {
storage->sparse[i] = UINT32_MAX;
}
return type_idx + 1;
}
pxl8_entity_component pxl8_entity_component_find(const pxl8_entity_pool* pool, const char* name) {
if (!pool || !name) return PXL8_ENTITY_COMPONENT_INVALID;
for (u32 i = 0; i < pool->component_type_count; i++) {
if (strcmp(pool->component_types[i].name, name) == 0) {
return i + 1;
}
}
return PXL8_ENTITY_COMPONENT_INVALID;
}
const char* pxl8_entity_component_name(const pxl8_entity_pool* pool, pxl8_entity_component comp) {
if (!pool || comp == 0 || comp > pool->component_type_count) return NULL;
return pool->component_types[comp - 1].name;
}
void* pxl8_entity_component_add(pxl8_entity_pool* pool, pxl8_entity e, pxl8_entity_component comp) {
if (!pool || !pxl8_entity_alive(pool, e)) return NULL;
if (comp == 0 || comp > pool->component_type_count) return NULL;
u32 type_idx = comp - 1;
pxl8_component_storage* storage = &pool->component_storage[type_idx];
u32 size = pool->component_types[type_idx].size;
if (storage->sparse[e.idx] != UINT32_MAX) {
return (u8*)storage->dense_data + storage->sparse[e.idx] * size;
}
u32 dense_idx = storage->count++;
storage->sparse[e.idx] = dense_idx;
storage->dense_entities[dense_idx] = e;
void* data = (u8*)storage->dense_data + dense_idx * size;
memset(data, 0, size);
return data;
}
void* pxl8_entity_component_get(const pxl8_entity_pool* pool, pxl8_entity e, pxl8_entity_component comp) {
if (!pool || !pxl8_entity_alive(pool, e)) return NULL;
if (comp == 0 || comp > pool->component_type_count) return NULL;
u32 type_idx = comp - 1;
const pxl8_component_storage* storage = &pool->component_storage[type_idx];
if (storage->sparse[e.idx] == UINT32_MAX) return NULL;
u32 size = pool->component_types[type_idx].size;
return (u8*)storage->dense_data + storage->sparse[e.idx] * size;
}
void pxl8_entity_component_remove(pxl8_entity_pool* pool, pxl8_entity e, pxl8_entity_component comp) {
if (!pool || !pxl8_entity_alive(pool, e)) return;
if (comp == 0 || comp > pool->component_type_count) return;
u32 type_idx = comp - 1;
pxl8_component_storage* storage = &pool->component_storage[type_idx];
u32 size = pool->component_types[type_idx].size;
u32 dense_idx = storage->sparse[e.idx];
if (dense_idx == UINT32_MAX) return;
u32 last_idx = storage->count - 1;
if (dense_idx != last_idx) {
pxl8_entity last_entity = storage->dense_entities[last_idx];
memcpy((u8*)storage->dense_data + dense_idx * size,
(u8*)storage->dense_data + last_idx * size, size);
storage->dense_entities[dense_idx] = last_entity;
storage->sparse[last_entity.idx] = dense_idx;
}
storage->sparse[e.idx] = UINT32_MAX;
storage->count--;
}
bool pxl8_entity_component_has(const pxl8_entity_pool* pool, pxl8_entity e, pxl8_entity_component comp) {
if (!pool || !pxl8_entity_alive(pool, e)) return false;
if (comp == 0 || comp > pool->component_type_count) return false;
return pool->component_storage[comp - 1].sparse[e.idx] != UINT32_MAX;
}
pxl8_entity_relationship pxl8_entity_relationship_register(pxl8_entity_pool* pool, const char* name) {
if (!pool || !name) return PXL8_ENTITY_RELATIONSHIP_INVALID;
pxl8_entity_relationship existing = pxl8_entity_relationship_find(pool, name);
if (existing != PXL8_ENTITY_RELATIONSHIP_INVALID) return existing;
if (pool->relationship_type_count >= pool->relationship_type_capacity) {
u32 new_capacity = pool->relationship_type_capacity * 2;
pxl8_relationship_type* new_types = pxl8_realloc(pool->relationship_types, new_capacity * sizeof(pxl8_relationship_type));
if (!new_types) return PXL8_ENTITY_RELATIONSHIP_INVALID;
pool->relationship_types = new_types;
memset(&pool->relationship_types[pool->relationship_type_capacity], 0, (new_capacity - pool->relationship_type_capacity) * sizeof(pxl8_relationship_type));
pool->relationship_type_capacity = new_capacity;
}
u32 type_idx = pool->relationship_type_count++;
strncpy(pool->relationship_types[type_idx].name, name, PXL8_ENTITY_RELATIONSHIP_NAME_MAX - 1);
return type_idx + 1;
}
pxl8_entity_relationship pxl8_entity_relationship_find(const pxl8_entity_pool* pool, const char* name) {
if (!pool || !name) return PXL8_ENTITY_RELATIONSHIP_INVALID;
for (u32 i = 0; i < pool->relationship_type_count; i++) {
if (strcmp(pool->relationship_types[i].name, name) == 0) {
return i + 1;
}
}
return PXL8_ENTITY_RELATIONSHIP_INVALID;
}
const char* pxl8_entity_relationship_name(const pxl8_entity_pool* pool, pxl8_entity_relationship rel) {
if (!pool || rel == 0 || rel > pool->relationship_type_count) return NULL;
return pool->relationship_types[rel - 1].name;
}
void pxl8_entity_relationship_add(pxl8_entity_pool* pool, pxl8_entity subject, pxl8_entity_relationship rel, pxl8_entity object) {
if (!pool) return;
if (!pxl8_entity_alive(pool, subject) || !pxl8_entity_alive(pool, object)) return;
if (rel == 0 || rel > pool->relationship_type_count) return;
if (pxl8_entity_relationship_has(pool, subject, rel, object)) return;
if (pool->relationship_count >= pool->relationship_capacity) {
u32 new_capacity = pool->relationship_capacity * 2;
pxl8_relationship_entry* new_rels = pxl8_realloc(pool->relationships, new_capacity * sizeof(pxl8_relationship_entry));
if (!new_rels) return;
pool->relationships = new_rels;
pool->relationship_capacity = new_capacity;
}
u32 entry_idx = pool->relationship_count++;
pxl8_relationship_entry* entry = &pool->relationships[entry_idx];
entry->subject = subject;
entry->object = object;
entry->rel = rel;
entry->next_by_subject = pool->rel_by_subject[subject.idx];
pool->rel_by_subject[subject.idx] = entry_idx;
entry->next_by_object = pool->rel_by_object[object.idx];
pool->rel_by_object[object.idx] = entry_idx;
}
void pxl8_entity_relationship_remove(pxl8_entity_pool* pool, pxl8_entity subject, pxl8_entity_relationship rel, pxl8_entity object) {
if (!pool) return;
if (rel == 0 || rel > pool->relationship_type_count) return;
u32* prev_ptr = &pool->rel_by_subject[subject.idx];
u32 idx = *prev_ptr;
while (idx != UINT32_MAX) {
pxl8_relationship_entry* entry = &pool->relationships[idx];
if (pxl8_entity_eq(entry->subject, subject) &&
pxl8_entity_eq(entry->object, object) &&
entry->rel == rel) {
*prev_ptr = entry->next_by_subject;
u32* obj_prev = &pool->rel_by_object[object.idx];
while (*obj_prev != UINT32_MAX) {
if (*obj_prev == idx) {
*obj_prev = entry->next_by_object;
break;
}
obj_prev = &pool->relationships[*obj_prev].next_by_object;
}
if (idx != pool->relationship_count - 1) {
u32 last_idx = pool->relationship_count - 1;
pxl8_relationship_entry* last = &pool->relationships[last_idx];
u32* last_subj_prev = &pool->rel_by_subject[last->subject.idx];
while (*last_subj_prev != UINT32_MAX) {
if (*last_subj_prev == last_idx) {
*last_subj_prev = idx;
break;
}
last_subj_prev = &pool->relationships[*last_subj_prev].next_by_subject;
}
u32* last_obj_prev = &pool->rel_by_object[last->object.idx];
while (*last_obj_prev != UINT32_MAX) {
if (*last_obj_prev == last_idx) {
*last_obj_prev = idx;
break;
}
last_obj_prev = &pool->relationships[*last_obj_prev].next_by_object;
}
*entry = *last;
}
pool->relationship_count--;
return;
}
prev_ptr = &entry->next_by_subject;
idx = *prev_ptr;
}
}
bool pxl8_entity_relationship_has(const pxl8_entity_pool* pool, pxl8_entity subject, pxl8_entity_relationship rel, pxl8_entity object) {
if (!pool) return false;
if (rel == 0 || rel > pool->relationship_type_count) return false;
u32 idx = pool->rel_by_subject[subject.idx];
while (idx != UINT32_MAX) {
const pxl8_relationship_entry* entry = &pool->relationships[idx];
if (pxl8_entity_eq(entry->subject, subject) &&
pxl8_entity_eq(entry->object, object) &&
entry->rel == rel) {
return true;
}
idx = entry->next_by_subject;
}
return false;
}
u32 pxl8_entity_relationship_subjects(const pxl8_entity_pool* pool, pxl8_entity object, pxl8_entity_relationship rel, pxl8_entity* out, u32 max) {
if (!pool || !out || max == 0) return 0;
if (rel == 0 || rel > pool->relationship_type_count) return 0;
u32 count = 0;
u32 idx = pool->rel_by_object[object.idx];
while (idx != UINT32_MAX && count < max) {
const pxl8_relationship_entry* entry = &pool->relationships[idx];
if (pxl8_entity_eq(entry->object, object) && entry->rel == rel) {
out[count++] = entry->subject;
}
idx = entry->next_by_object;
}
return count;
}
u32 pxl8_entity_relationship_objects(const pxl8_entity_pool* pool, pxl8_entity subject, pxl8_entity_relationship rel, pxl8_entity* out, u32 max) {
if (!pool || !out || max == 0) return 0;
if (rel == 0 || rel > pool->relationship_type_count) return 0;
u32 count = 0;
u32 idx = pool->rel_by_subject[subject.idx];
while (idx != UINT32_MAX && count < max) {
const pxl8_relationship_entry* entry = &pool->relationships[idx];
if (pxl8_entity_eq(entry->subject, subject) && entry->rel == rel) {
out[count++] = entry->object;
}
idx = entry->next_by_subject;
}
return count;
}
void pxl8_entity_each(pxl8_entity_pool* pool, pxl8_entity_component comp, pxl8_entity_each_fn fn, void* ctx) {
if (!pool || !fn) return;
if (comp == 0 || comp > pool->component_type_count) return;
pxl8_component_storage* storage = &pool->component_storage[comp - 1];
for (u32 i = 0; i < storage->count; i++) {
pxl8_entity e = storage->dense_entities[i];
if (pxl8_entity_alive(pool, e)) {
fn(pool, e, ctx);
}
}
}
void pxl8_entity_each_with(pxl8_entity_pool* pool, const pxl8_entity_component* comps, u32 count, pxl8_entity_each_fn fn, void* ctx) {
if (!pool || !comps || !fn || count == 0) return;
u32 smallest_idx = 0;
u32 smallest_count = UINT32_MAX;
for (u32 i = 0; i < count; i++) {
if (comps[i] == 0 || comps[i] > pool->component_type_count) return;
u32 storage_count = pool->component_storage[comps[i] - 1].count;
if (storage_count < smallest_count) {
smallest_count = storage_count;
smallest_idx = i;
}
}
pxl8_component_storage* storage = &pool->component_storage[comps[smallest_idx] - 1];
for (u32 i = 0; i < storage->count; i++) {
pxl8_entity e = storage->dense_entities[i];
if (!pxl8_entity_alive(pool, e)) continue;
bool has_all = true;
for (u32 j = 0; j < count && has_all; j++) {
if (j != smallest_idx) {
has_all = pxl8_entity_component_has(pool, e, comps[j]);
}
}
if (has_all) {
fn(pool, e, ctx);
}
}
}

View file

@ -1,67 +0,0 @@
#pragma once
#include "pxl8_types.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct pxl8_entity_pool pxl8_entity_pool;
typedef struct pxl8_entity {
u32 idx;
u32 gen;
} pxl8_entity;
#define PXL8_ENTITY_INVALID ((pxl8_entity){0, 0})
typedef u32 pxl8_entity_component;
typedef u32 pxl8_entity_relationship;
#define PXL8_ENTITY_COMPONENT_INVALID 0
#define PXL8_ENTITY_RELATIONSHIP_INVALID 0
pxl8_entity_pool* pxl8_entity_pool_create(u32 capacity);
void pxl8_entity_pool_clear(pxl8_entity_pool* pool);
void pxl8_entity_pool_destroy(pxl8_entity_pool* pool);
pxl8_entity pxl8_entity_spawn(pxl8_entity_pool* pool);
void pxl8_entity_despawn(pxl8_entity_pool* pool, pxl8_entity e);
bool pxl8_entity_alive(const pxl8_entity_pool* pool, pxl8_entity e);
u32 pxl8_entity_count(const pxl8_entity_pool* pool);
pxl8_entity_component pxl8_entity_component_register(pxl8_entity_pool* pool, const char* name, u32 size);
pxl8_entity_component pxl8_entity_component_find(const pxl8_entity_pool* pool, const char* name);
const char* pxl8_entity_component_name(const pxl8_entity_pool* pool, pxl8_entity_component comp);
void* pxl8_entity_component_add(pxl8_entity_pool* pool, pxl8_entity e, pxl8_entity_component comp);
void* pxl8_entity_component_get(const pxl8_entity_pool* pool, pxl8_entity e, pxl8_entity_component comp);
void pxl8_entity_component_remove(pxl8_entity_pool* pool, pxl8_entity e, pxl8_entity_component comp);
bool pxl8_entity_component_has(const pxl8_entity_pool* pool, pxl8_entity e, pxl8_entity_component comp);
pxl8_entity_relationship pxl8_entity_relationship_register(pxl8_entity_pool* pool, const char* name);
pxl8_entity_relationship pxl8_entity_relationship_find(const pxl8_entity_pool* pool, const char* name);
const char* pxl8_entity_relationship_name(const pxl8_entity_pool* pool, pxl8_entity_relationship rel);
void pxl8_entity_relationship_add(pxl8_entity_pool* pool, pxl8_entity subject, pxl8_entity_relationship rel, pxl8_entity object);
void pxl8_entity_relationship_remove(pxl8_entity_pool* pool, pxl8_entity subject, pxl8_entity_relationship rel, pxl8_entity object);
bool pxl8_entity_relationship_has(const pxl8_entity_pool* pool, pxl8_entity subject, pxl8_entity_relationship rel, pxl8_entity object);
u32 pxl8_entity_relationship_subjects(const pxl8_entity_pool* pool, pxl8_entity object, pxl8_entity_relationship rel, pxl8_entity* out, u32 max);
u32 pxl8_entity_relationship_objects(const pxl8_entity_pool* pool, pxl8_entity subject, pxl8_entity_relationship rel, pxl8_entity* out, u32 max);
typedef void (*pxl8_entity_each_fn)(pxl8_entity_pool* pool, pxl8_entity e, void* ctx);
void pxl8_entity_each(pxl8_entity_pool* pool, pxl8_entity_component comp, pxl8_entity_each_fn fn, void* ctx);
void pxl8_entity_each_with(pxl8_entity_pool* pool, const pxl8_entity_component* comps, u32 count, pxl8_entity_each_fn fn, void* ctx);
static inline bool pxl8_entity_valid(pxl8_entity e) {
return e.idx != 0 || e.gen != 0;
}
static inline bool pxl8_entity_eq(pxl8_entity a, pxl8_entity b) {
return a.idx == b.idx && a.gen == b.gen;
}
#ifdef __cplusplus
}
#endif

View file

@ -1,986 +0,0 @@
#include "pxl8_world.h"
#include <string.h>
#include "pxl8_hal.h"
#ifdef PXL8_ASYNC_THREADS
#include <stdatomic.h>
#include "pxl8_queue.h"
#endif
#include "pxl8_bsp_render.h"
#include "pxl8_io.h"
#include "pxl8_gfx3d.h"
#include "pxl8_log.h"
#include "pxl8_mem.h"
#include "pxl8_protocol.h"
#include "pxl8_sim.h"
#define PXL8_VIS_MAX_NODES (PXL8_WORLD_MAX_LOADED_CHUNKS * 512)
#define PXL8_VIS_MAX_QUEUE (PXL8_VIS_MAX_NODES * 4)
#define PXL8_VIS_BYTES ((PXL8_VIS_MAX_NODES + 7) / 8)
#define PXL8_WORLD_ENTITY_CAPACITY 256
typedef struct {
u16 chunk_idx;
u16 leaf_idx;
pxl8_rect window;
} world_vis_node;
struct pxl8_world {
pxl8_loaded_chunk loaded[PXL8_WORLD_MAX_LOADED_CHUNKS];
u32 loaded_count;
pxl8_world_chunk* active_chunk;
pxl8_bsp_render_state* active_render_state;
pxl8_gfx_material shared_materials[16];
bool shared_material_set[16];
pxl8_world_chunk_cache* chunk_cache;
pxl8_entity_pool* entities;
pxl8_sim_entity local_player;
u64 client_tick;
pxl8_vec2 pointer_motion;
pxl8_sim_config sim_config;
u8 vis_bits[PXL8_VIS_BYTES];
u8* vis_ptrs[PXL8_WORLD_MAX_LOADED_CHUNKS];
pxl8_rect vis_windows[PXL8_VIS_MAX_NODES];
pxl8_rect* vis_win_ptrs[PXL8_WORLD_MAX_LOADED_CHUNKS];
world_vis_node vis_queue[PXL8_VIS_MAX_QUEUE];
#ifdef PXL8_ASYNC_THREADS
pxl8_sim_entity render_state[2];
atomic_uint active_buffer;
pxl8_thread* sim_thread;
atomic_bool sim_running;
atomic_bool sim_paused;
pxl8_net* net;
pxl8_queue input_queue;
f32 sim_accumulator;
#endif
};
pxl8_world* pxl8_world_create(void) {
pxl8_world* world = pxl8_calloc(1, sizeof(pxl8_world));
if (!world) return NULL;
world->chunk_cache = pxl8_world_chunk_cache_create();
world->entities = pxl8_entity_pool_create(PXL8_WORLD_ENTITY_CAPACITY);
world->sim_config = (pxl8_sim_config){
.move_speed = 180.0f,
.ground_accel = 10.0f,
.air_accel = 1.0f,
.stop_speed = 100.0f,
.friction = 6.0f,
.gravity = 800.0f,
.jump_velocity = 200.0f,
.player_radius = 16.0f,
.player_height = 72.0f,
.max_pitch = 1.5f,
};
if (!world->chunk_cache || !world->entities) {
pxl8_world_destroy(world);
return NULL;
}
return world;
}
void pxl8_world_destroy(pxl8_world* world) {
if (!world) return;
for (u32 i = 0; i < world->loaded_count; i++) {
pxl8_bsp_render_state_destroy(world->loaded[i].render_state);
}
pxl8_world_chunk_cache_destroy(world->chunk_cache);
pxl8_entity_pool_destroy(world->entities);
pxl8_free(world);
}
pxl8_world_chunk_cache* pxl8_world_get_chunk_cache(pxl8_world* world) {
if (!world) return NULL;
return world->chunk_cache;
}
pxl8_world_chunk* pxl8_world_active_chunk(pxl8_world* world) {
if (!world) return NULL;
return world->active_chunk;
}
pxl8_sim_world pxl8_world_sim_world(const pxl8_world* world, pxl8_vec3 pos) {
pxl8_sim_world sim = {0};
const f32 chunk_size = 16.0f * 64.0f;
sim.chunk_size = chunk_size;
i32 pcx = (i32)floorf(pos.x / chunk_size);
i32 pcz = (i32)floorf(pos.z / chunk_size);
sim.center_cx = pcx;
sim.center_cz = pcz;
for (u32 i = 0; i < world->loaded_count; i++) {
const pxl8_loaded_chunk* lc = &world->loaded[i];
if (!lc->chunk || !lc->chunk->bsp) continue;
i32 dx = lc->cx - pcx + 1;
i32 dz = lc->cz - pcz + 1;
if (dx >= 0 && dx <= 2 && dz >= 0 && dz <= 2) {
sim.chunks[dz * 3 + dx] = lc->chunk->bsp;
}
}
return sim;
}
static void entity_to_userdata(const pxl8_sim_entity* ent, u8* userdata) {
u8* p = userdata;
memcpy(p, &ent->pos.x, 4); p += 4;
memcpy(p, &ent->pos.y, 4); p += 4;
memcpy(p, &ent->pos.z, 4); p += 4;
memcpy(p, &ent->yaw, 4); p += 4;
memcpy(p, &ent->pitch, 4); p += 4;
memcpy(p, &ent->vel.x, 4); p += 4;
memcpy(p, &ent->vel.y, 4); p += 4;
memcpy(p, &ent->vel.z, 4); p += 4;
memcpy(p, &ent->flags, 4); p += 4;
memcpy(p, &ent->kind, 2);
}
static void userdata_to_entity(const u8* userdata, pxl8_sim_entity* ent) {
const u8* p = userdata;
memcpy(&ent->pos.x, p, 4); p += 4;
memcpy(&ent->pos.y, p, 4); p += 4;
memcpy(&ent->pos.z, p, 4); p += 4;
memcpy(&ent->yaw, p, 4); p += 4;
memcpy(&ent->pitch, p, 4); p += 4;
memcpy(&ent->vel.x, p, 4); p += 4;
memcpy(&ent->vel.y, p, 4); p += 4;
memcpy(&ent->vel.z, p, 4); p += 4;
memcpy(&ent->flags, p, 4); p += 4;
memcpy(&ent->kind, p, 2);
}
bool pxl8_world_point_solid(const pxl8_world* world, f32 x, f32 y, f32 z) {
if (!world) return false;
if (world->active_chunk && world->active_chunk->bsp) {
return pxl8_bsp_point_solid(world->active_chunk->bsp, (pxl8_vec3){x, y, z});
}
return false;
}
pxl8_ray pxl8_world_ray(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to) {
pxl8_ray result = {0};
if (!world) return result;
pxl8_vec3 dir = pxl8_vec3_sub(to, from);
f32 length = pxl8_vec3_length(dir);
if (length < 0.001f) return result;
f32 step_size = 1.0f;
f32 traveled = 0.0f;
while (traveled < length) {
f32 t = traveled / length;
pxl8_vec3 pos = {
from.x + dir.x * t,
from.y + dir.y * t,
from.z + dir.z * t
};
if (pxl8_world_point_solid(world, pos.x, pos.y, pos.z)) {
result.hit = true;
result.fraction = t;
result.point = pos;
f32 eps = 0.1f;
bool sx_neg = pxl8_world_point_solid(world, pos.x - eps, pos.y, pos.z);
bool sx_pos = pxl8_world_point_solid(world, pos.x + eps, pos.y, pos.z);
bool sy_neg = pxl8_world_point_solid(world, pos.x, pos.y - eps, pos.z);
bool sy_pos = pxl8_world_point_solid(world, pos.x, pos.y + eps, pos.z);
bool sz_neg = pxl8_world_point_solid(world, pos.x, pos.y, pos.z - eps);
bool sz_pos = pxl8_world_point_solid(world, pos.x, pos.y, pos.z + eps);
result.normal = (pxl8_vec3){
(sx_neg && !sx_pos) ? 1.0f : (!sx_neg && sx_pos) ? -1.0f : 0.0f,
(sy_neg && !sy_pos) ? 1.0f : (!sy_neg && sy_pos) ? -1.0f : 0.0f,
(sz_neg && !sz_pos) ? 1.0f : (!sz_neg && sz_pos) ? -1.0f : 0.0f
};
f32 nl = pxl8_vec3_length(result.normal);
if (nl > 0.001f) {
result.normal = pxl8_vec3_scale(result.normal, 1.0f / nl);
} else {
result.normal = (pxl8_vec3){0, 1, 0};
}
return result;
}
traveled += step_size;
}
return result;
}
pxl8_ray pxl8_world_sweep(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius) {
pxl8_ray result = {0};
if (!world) return result;
f32 diag = radius * 0.707f;
bool dest_blocked = pxl8_world_point_solid(world, to.x, to.y, to.z) ||
pxl8_world_point_solid(world, to.x + radius, to.y, to.z) ||
pxl8_world_point_solid(world, to.x - radius, to.y, to.z) ||
pxl8_world_point_solid(world, to.x, to.y, to.z + radius) ||
pxl8_world_point_solid(world, to.x, to.y, to.z - radius) ||
pxl8_world_point_solid(world, to.x + diag, to.y, to.z + diag) ||
pxl8_world_point_solid(world, to.x + diag, to.y, to.z - diag) ||
pxl8_world_point_solid(world, to.x - diag, to.y, to.z + diag) ||
pxl8_world_point_solid(world, to.x - diag, to.y, to.z - diag);
if (dest_blocked) {
result.hit = true;
result.fraction = 0;
result.point = from;
pxl8_vec3 dir = pxl8_vec3_sub(to, from);
f32 length = pxl8_vec3_length(dir);
if (length > 0.001f) {
result.normal = pxl8_vec3_scale(dir, -1.0f / length);
} else {
result.normal = (pxl8_vec3){0, 1, 0};
}
}
return result;
}
void pxl8_world_update(pxl8_world* world, f32 dt) {
(void)dt;
if (!world) return;
pxl8_world_chunk_cache_tick(world->chunk_cache);
}
static inline bool vr_valid(pxl8_rect r) {
return r.x0 < r.x1 && r.y0 < r.y1;
}
static inline pxl8_rect vr_intersect(pxl8_rect a, pxl8_rect b) {
return (pxl8_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 pxl8_rect project_portal(f32 px0, f32 pz0, f32 px1, f32 pz1,
f32 y_lo, f32 y_hi, const pxl8_mat4* vp) {
pxl8_vec3 corners[4] = {
{px0, y_lo, pz0}, {px1, y_lo, pz1},
{px1, y_hi, pz1}, {px0, y_hi, pz0},
};
const f32 NEAR_W = 0.001f;
pxl8_vec4 clip[4];
bool front[4];
i32 fc = 0;
for (i32 i = 0; i < 4; i++) {
clip[i] = pxl8_mat4_multiply_vec4(*vp, (pxl8_vec4){
corners[i].x, corners[i].y, corners[i].z, 1.0f});
front[i] = clip[i].w > NEAR_W;
if (front[i]) fc++;
}
if (fc == 0) return (pxl8_rect){0, 0, 0, 0};
if (fc < 4) return (pxl8_rect){-1.0f, -1.0f, 1.0f, 1.0f};
pxl8_rect r = {1e30f, 1e30f, -1e30f, -1e30f};
for (i32 i = 0; i < 4; i++) {
f32 iw = 1.0f / clip[i].w;
f32 nx = clip[i].x * iw, ny = clip[i].y * iw;
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;
}
if (r.x0 < -1.0f) r.x0 = -1.0f;
if (r.y0 < -1.0f) r.y0 = -1.0f;
if (r.x1 > 1.0f) r.x1 = 1.0f;
if (r.y1 > 1.0f) r.y1 = 1.0f;
return r;
}
static void compute_edge_leafs(pxl8_loaded_chunk* lc) {
const f32 CHUNK_SIZE = 16.0f * 64.0f;
const pxl8_bsp* bsp = lc->chunk->bsp;
f32 cx0 = lc->cx * CHUNK_SIZE;
f32 cz0 = lc->cz * CHUNK_SIZE;
f32 cx1 = cx0 + CHUNK_SIZE;
f32 cz1 = cz0 + CHUNK_SIZE;
memset(lc->edges, 0, sizeof(lc->edges));
for (u32 i = 0; i < bsp->num_leafs; i++) {
const pxl8_bsp_leaf* leaf = &bsp->leafs[i];
if (bsp->leafs[i].contents == -1) continue;
if ((f32)leaf->mins[2] <= (f32)((i16)cz0) + 1.0f && lc->edges[0].count < 16)
lc->edges[0].leafs[lc->edges[0].count++] = (u16)i;
if ((f32)leaf->maxs[2] >= (f32)((i16)cz1) - 1.0f && lc->edges[1].count < 16)
lc->edges[1].leafs[lc->edges[1].count++] = (u16)i;
if ((f32)leaf->mins[0] <= (f32)((i16)cx0) + 1.0f && lc->edges[2].count < 16)
lc->edges[2].leafs[lc->edges[2].count++] = (u16)i;
if ((f32)leaf->maxs[0] >= (f32)((i16)cx1) - 1.0f && lc->edges[3].count < 16)
lc->edges[3].leafs[lc->edges[3].count++] = (u16)i;
}
}
static i32 world_find_chunk(const pxl8_world* world, i32 cx, i32 cz) {
for (u32 i = 0; i < world->loaded_count; i++) {
if (world->loaded[i].cx == cx && world->loaded[i].cz == cz &&
world->loaded[i].chunk && world->loaded[i].chunk->bsp)
return (i32)i;
}
return -1;
}
static void world_mark_leaf_faces(const pxl8_bsp* bsp, pxl8_bsp_render_state* rs, u32 leaf_idx) {
const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_idx];
for (u32 i = 0; i < leaf->num_marksurfaces; i++) {
u32 si = leaf->first_marksurface + i;
if (si < bsp->num_marksurfaces) {
u32 fi = bsp->marksurfaces[si];
if (fi < bsp->num_faces && rs)
rs->render_face_flags[fi] = 1;
}
}
}
static bool vis_try_enqueue(u8** vis, pxl8_rect** windows, world_vis_node* queue,
u32* tail, u32 max_queue,
u16 ci, u16 li, pxl8_rect nw) {
if (!vis[ci] || !windows[ci]) return false;
u32 byte = li >> 3;
u32 bit = 1 << (li & 7);
if (vis[ci][byte] & bit) {
pxl8_rect* ex = &windows[ci][li];
bool expanded = false;
if (nw.x0 < ex->x0) { ex->x0 = nw.x0; expanded = true; }
if (nw.y0 < ex->y0) { ex->y0 = nw.y0; expanded = true; }
if (nw.x1 > ex->x1) { ex->x1 = nw.x1; expanded = true; }
if (nw.y1 > ex->y1) { ex->y1 = nw.y1; expanded = true; }
if (expanded && *tail < max_queue)
queue[(*tail)++] = (world_vis_node){ci, li, *ex};
return expanded;
}
vis[ci][byte] |= bit;
windows[ci][li] = nw;
if (*tail < max_queue)
queue[(*tail)++] = (world_vis_node){ci, li, nw};
return true;
}
static void world_compute_visibility(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) {
const f32 CHUNK_SIZE = 16.0f * 64.0f;
const f32 PORTAL_Y_HI = 192.0f;
const pxl8_mat4* vp = pxl8_3d_get_view_proj(gfx);
i32 cam_ci = -1, cam_li = -1;
for (u32 i = 0; i < world->loaded_count; i++) {
pxl8_loaded_chunk* lc = &world->loaded[i];
if (!lc->chunk || !lc->chunk->bsp) continue;
const pxl8_bsp* bsp = lc->chunk->bsp;
f32 cx0 = lc->cx * CHUNK_SIZE;
f32 cz0 = lc->cz * CHUNK_SIZE;
if (camera_pos.x < cx0 || camera_pos.x >= cx0 + CHUNK_SIZE ||
camera_pos.z < cz0 || camera_pos.z >= cz0 + CHUNK_SIZE) continue;
if (bsp->bounds_max_x > bsp->bounds_min_x &&
(camera_pos.x < bsp->bounds_min_x || camera_pos.x >= bsp->bounds_max_x ||
camera_pos.z < bsp->bounds_min_z || camera_pos.z >= bsp->bounds_max_z))
continue;
i32 leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
if (leaf >= 0 && (u32)leaf < bsp->num_leafs &&
bsp->leafs[leaf].contents != -1 &&
(!(bsp->bounds_max_x > bsp->bounds_min_x) || bsp->leafs[leaf].contents == -2)) {
cam_ci = (i32)i;
cam_li = leaf;
break;
}
}
if (cam_ci < 0) {
for (u32 i = 0; i < world->loaded_count; i++) {
pxl8_loaded_chunk* lc = &world->loaded[i];
if (!lc->chunk || !lc->chunk->bsp) continue;
const pxl8_bsp* bsp = lc->chunk->bsp;
if (bsp->bounds_max_x > bsp->bounds_min_x) continue;
i32 leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
if (leaf < 0 || (u32)leaf >= bsp->num_leafs) continue;
const pxl8_bsp_leaf* l = &bsp->leafs[leaf];
if (l->contents == -1) continue;
if (camera_pos.x < (f32)l->mins[0] || camera_pos.x > (f32)l->maxs[0] ||
camera_pos.z < (f32)l->mins[2] || camera_pos.z > (f32)l->maxs[2]) continue;
cam_ci = (i32)i;
cam_li = leaf;
break;
}
}
if (cam_ci < 0 || !vp) {
for (u32 i = 0; i < world->loaded_count; i++) {
pxl8_bsp_render_state* rs = world->loaded[i].render_state;
if (!rs) continue;
if (rs->render_face_flags)
memset(rs->render_face_flags, 1, rs->num_faces);
rs->exterior = true;
}
return;
}
memset(world->vis_bits, 0, sizeof(world->vis_bits));
memset(world->vis_ptrs, 0, sizeof(world->vis_ptrs));
memset(world->vis_win_ptrs, 0, sizeof(world->vis_win_ptrs));
u32 voff = 0, woff = 0;
for (u32 i = 0; i < world->loaded_count; i++) {
if (world->loaded[i].chunk && world->loaded[i].chunk->bsp) {
u32 nl = world->loaded[i].chunk->bsp->num_leafs;
u32 vbytes = (nl + 7) / 8;
if (voff + vbytes > PXL8_VIS_BYTES || woff + nl > PXL8_VIS_MAX_NODES)
continue;
world->vis_ptrs[i] = world->vis_bits + voff;
voff += vbytes;
world->vis_win_ptrs[i] = world->vis_windows + woff;
woff += nl;
}
}
if (!world->vis_ptrs[cam_ci] || !world->vis_win_ptrs[cam_ci]) return;
for (u32 i = 0; i < world->loaded_count; i++) {
pxl8_bsp_render_state* rs = world->loaded[i].render_state;
if (!rs) continue;
if (rs->render_face_flags)
memset(rs->render_face_flags, 0, rs->num_faces);
rs->exterior = false;
}
u32 head = 0, tail = 0;
pxl8_rect full_screen = {-1.0f, -1.0f, 1.0f, 1.0f};
const pxl8_bsp* cam_bsp = world->loaded[cam_ci].chunk->bsp;
bool cam_exterior = cam_bsp->leafs[cam_li].contents == 0 &&
!(cam_bsp->bounds_max_x > cam_bsp->bounds_min_x);
world->vis_ptrs[cam_ci][cam_li >> 3] |= (1 << (cam_li & 7));
world->vis_win_ptrs[cam_ci][cam_li] = full_screen;
world->vis_queue[tail++] = (world_vis_node){(u16)cam_ci, (u16)cam_li, full_screen};
while (head < tail) {
world_vis_node cur = world->vis_queue[head++];
pxl8_loaded_chunk* lc = &world->loaded[cur.chunk_idx];
const pxl8_bsp* bsp = lc->chunk->bsp;
world_mark_leaf_faces(bsp, lc->render_state, cur.leaf_idx);
if (bsp->cell_portals && cur.leaf_idx < bsp->num_cell_portals) {
bool is_cam_leaf = (cur.chunk_idx == (u16)cam_ci && cur.leaf_idx == (u16)cam_li);
bool is_cam_chunk = (cur.chunk_idx == (u16)cam_ci);
const pxl8_bsp_cell_portals* cp = &bsp->cell_portals[cur.leaf_idx];
for (u8 pi = 0; pi < cp->num_portals; pi++) {
const pxl8_bsp_portal* p = &cp->portals[pi];
u32 target = p->target_leaf;
if (target >= bsp->num_leafs) continue;
if (bsp->leafs[target].contents == -1) continue;
if (is_cam_chunk && !pxl8_bsp_is_leaf_visible(bsp, cam_li, (i32)target)) continue;
if (is_cam_leaf || cam_exterior) {
vis_try_enqueue(world->vis_ptrs, world->vis_win_ptrs,
world->vis_queue, &tail, PXL8_VIS_MAX_QUEUE,
cur.chunk_idx, (u16)target, full_screen);
continue;
}
pxl8_rect psr = project_portal(p->x0, p->z0, p->x1, p->z1,
0.0f, PORTAL_Y_HI, vp);
if (!vr_valid(psr)) continue;
pxl8_rect nw = vr_intersect(cur.window, psr);
if (!vr_valid(nw)) continue;
vis_try_enqueue(world->vis_ptrs, world->vis_win_ptrs,
world->vis_queue, &tail, PXL8_VIS_MAX_QUEUE,
cur.chunk_idx, (u16)target, nw);
}
}
const pxl8_bsp_leaf* leaf = &bsp->leafs[cur.leaf_idx];
f32 chunk_x0 = lc->cx * CHUNK_SIZE;
f32 chunk_z0 = lc->cz * CHUNK_SIZE;
f32 chunk_x1 = chunk_x0 + CHUNK_SIZE;
f32 chunk_z1 = chunk_z0 + CHUNK_SIZE;
struct { i32 dx, dz; bool at_edge; f32 bnd; } dirs[4] = {
{ 0, -1, (f32)leaf->mins[2] <= (f32)((i16)chunk_z0) + 1.0f, chunk_z0 },
{ 0, 1, (f32)leaf->maxs[2] >= (f32)((i16)chunk_z1) - 1.0f, chunk_z1 },
{-1, 0, (f32)leaf->mins[0] <= (f32)((i16)chunk_x0) + 1.0f, chunk_x0 },
{ 1, 0, (f32)leaf->maxs[0] >= (f32)((i16)chunk_x1) - 1.0f, chunk_x1 },
};
static const u8 opposite_edge[4] = {1, 0, 3, 2};
for (u32 d = 0; d < 4; d++) {
if (!dirs[d].at_edge) continue;
i32 nci = world_find_chunk(world, lc->cx + dirs[d].dx, lc->cz + dirs[d].dz);
if (nci < 0) continue;
const pxl8_bsp* nbsp = world->loaded[nci].chunk->bsp;
const pxl8_edge_leafs* nedge = &world->loaded[nci].edges[opposite_edge[d]];
for (u8 k = 0; k < nedge->count; k++) {
u16 nl = nedge->leafs[k];
const pxl8_bsp_leaf* nleaf = &nbsp->leafs[nl];
bool overlaps = false;
if (dirs[d].dx != 0) {
overlaps = leaf->maxs[2] > nleaf->mins[2] && leaf->mins[2] < nleaf->maxs[2];
} else {
overlaps = leaf->maxs[0] > nleaf->mins[0] && leaf->mins[0] < nleaf->maxs[0];
}
if (!overlaps) continue;
f32 bpx0, bpz0, bpx1, bpz1;
if (dirs[d].dx != 0) {
bpx0 = dirs[d].bnd; bpx1 = dirs[d].bnd;
i16 zlo = leaf->mins[2] > nleaf->mins[2] ? leaf->mins[2] : nleaf->mins[2];
i16 zhi = leaf->maxs[2] < nleaf->maxs[2] ? leaf->maxs[2] : nleaf->maxs[2];
bpz0 = (f32)zlo; bpz1 = (f32)zhi;
} else {
bpz0 = dirs[d].bnd; bpz1 = dirs[d].bnd;
i16 xlo = leaf->mins[0] > nleaf->mins[0] ? leaf->mins[0] : nleaf->mins[0];
i16 xhi = leaf->maxs[0] < nleaf->maxs[0] ? leaf->maxs[0] : nleaf->maxs[0];
bpx0 = (f32)xlo; bpx1 = (f32)xhi;
}
if (cam_exterior) {
vis_try_enqueue(world->vis_ptrs, world->vis_win_ptrs,
world->vis_queue, &tail, PXL8_VIS_MAX_QUEUE,
(u16)nci, (u16)nl, full_screen);
continue;
}
pxl8_rect psr = project_portal(bpx0, bpz0, bpx1, bpz1,
0.0f, PORTAL_Y_HI, vp);
if (!vr_valid(psr)) continue;
pxl8_rect nw = vr_intersect(cur.window, psr);
if (!vr_valid(nw)) continue;
vis_try_enqueue(world->vis_ptrs, world->vis_win_ptrs,
world->vis_queue, &tail, PXL8_VIS_MAX_QUEUE,
(u16)nci, (u16)nl, nw);
}
}
}
}
void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) {
if (!world || !gfx) return;
if (world->active_chunk && world->active_chunk->bsp)
pxl8_3d_set_bsp(gfx, world->active_chunk->bsp);
else
pxl8_3d_set_bsp(gfx, NULL);
world_compute_visibility(world, gfx, camera_pos);
for (u32 i = 0; i < world->loaded_count; i++) {
pxl8_loaded_chunk* lc = &world->loaded[i];
if (!lc->chunk || !lc->chunk->bsp || !lc->render_state) continue;
pxl8_bsp_render(gfx, lc->chunk->bsp, lc->render_state, NULL);
}
}
static void apply_shared_materials(pxl8_world* world, pxl8_bsp_render_state* rs) {
for (u16 i = 0; i < 16; i++) {
if (world->shared_material_set[i]) {
pxl8_bsp_set_material(rs, i, &world->shared_materials[i]);
}
}
}
void pxl8_world_sync(pxl8_world* world, pxl8_net* net) {
if (!world || !net) return;
if (!pxl8_net_has_chunk(net)) {
u32 old_count = world->loaded_count;
world->loaded_count = 0;
world->active_chunk = NULL;
world->active_render_state = NULL;
for (u32 i = 0; i < old_count; i++) {
pxl8_bsp_render_state_destroy(world->loaded[i].render_state);
}
return;
}
i32 center_cx = pxl8_net_chunk_cx(net);
i32 center_cz = pxl8_net_chunk_cz(net);
pxl8_loaded_chunk old_loaded[PXL8_WORLD_MAX_LOADED_CHUNKS];
u32 old_count = world->loaded_count;
memcpy(old_loaded, world->loaded, sizeof(old_loaded));
pxl8_loaded_chunk new_loaded[PXL8_WORLD_MAX_LOADED_CHUNKS];
u32 new_count = 0;
pxl8_world_chunk* new_active_chunk = NULL;
pxl8_bsp_render_state* new_active_rs = NULL;
for (i32 dz = -2; dz <= 2; dz++) {
for (i32 dx = -2; dx <= 2; dx++) {
i32 cx = center_cx + dx;
i32 cz = center_cz + dz;
u32 id = pxl8_chunk_hash(cx, cz);
pxl8_world_chunk* chunk = pxl8_world_chunk_cache_get_bsp(world->chunk_cache, id);
if (!chunk || !chunk->bsp) continue;
pxl8_bsp_render_state* rs = NULL;
for (u32 j = 0; j < old_count; j++) {
if (old_loaded[j].active && old_loaded[j].cx == cx && old_loaded[j].cz == cz &&
old_loaded[j].chunk == chunk) {
rs = old_loaded[j].render_state;
old_loaded[j].active = false;
break;
}
}
if (!rs) {
rs = pxl8_bsp_render_state_create(chunk->bsp->num_faces);
if (rs && rs->render_face_flags)
memset(rs->render_face_flags, 1, rs->num_faces);
apply_shared_materials(world, rs);
}
u32 idx = new_count++;
new_loaded[idx] = (pxl8_loaded_chunk){
.chunk = chunk,
.render_state = rs,
.cx = cx,
.cz = cz,
.active = true,
};
compute_edge_leafs(&new_loaded[idx]);
if (dx == 0 && dz == 0) {
new_active_chunk = chunk;
new_active_rs = rs;
}
}
}
memcpy(world->loaded, new_loaded, sizeof(new_loaded));
world->active_chunk = new_active_chunk;
world->active_render_state = new_active_rs;
world->loaded_count = new_count;
for (u32 j = 0; j < old_count; j++) {
if (old_loaded[j].active) {
pxl8_bsp_render_state_destroy(old_loaded[j].render_state);
}
}
}
void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_gfx_material* material) {
if (!world || !material || material_id >= 16) return;
world->shared_materials[material_id] = *material;
world->shared_material_set[material_id] = true;
for (u32 i = 0; i < world->loaded_count; i++) {
if (world->loaded[i].render_state) {
pxl8_bsp_set_material(world->loaded[i].render_state, material_id, material);
}
}
}
void pxl8_world_set_sim_config(pxl8_world* world, const pxl8_sim_config* config) {
if (!world || !config) return;
world->sim_config = *config;
}
void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z) {
if (!world) return;
world->local_player.pos = (pxl8_vec3){x, y, z};
world->local_player.vel = (pxl8_vec3){0, 0, 0};
world->local_player.yaw = 0;
world->local_player.pitch = 0;
world->local_player.flags = PXL8_SIM_FLAG_ALIVE | PXL8_SIM_FLAG_PLAYER | PXL8_SIM_FLAG_GROUNDED;
world->local_player.kind = 0;
world->client_tick = 0;
world->pointer_motion = (pxl8_vec2){0};
#ifdef PXL8_ASYNC_THREADS
world->render_state[0] = world->local_player;
world->render_state[1] = world->local_player;
#endif
}
void pxl8_world_set_look(pxl8_world* world, f32 yaw, f32 pitch) {
if (!world) return;
world->local_player.yaw = yaw;
world->local_player.pitch = pitch;
world->pointer_motion = (pxl8_vec2){.yaw = yaw, .pitch = pitch};
#ifdef PXL8_ASYNC_THREADS
world->render_state[0].yaw = yaw;
world->render_state[0].pitch = pitch;
world->render_state[1].yaw = yaw;
world->render_state[1].pitch = pitch;
#endif
}
pxl8_sim_entity* pxl8_world_local_player(pxl8_world* world) {
if (!world) return NULL;
#ifdef PXL8_ASYNC_THREADS
const pxl8_sim_entity* state = pxl8_world_get_render_state(world);
if (!state) return NULL;
if (!(state->flags & PXL8_SIM_FLAG_ALIVE)) return NULL;
return (pxl8_sim_entity*)state;
#else
if (!(world->local_player.flags & PXL8_SIM_FLAG_ALIVE)) return NULL;
return &world->local_player;
#endif
}
void pxl8_world_predict(pxl8_world* world, pxl8_net* net, const pxl8_input_msg* input, f32 dt) {
if (!world || !net || !input) return;
if (!(world->local_player.flags & PXL8_SIM_FLAG_ALIVE)) return;
pxl8_sim_world sim = pxl8_world_sim_world(world, world->local_player.pos);
pxl8_sim_move_player(&world->local_player, input, &sim, &world->sim_config, dt);
world->client_tick++;
entity_to_userdata(&world->local_player, pxl8_net_predicted_state(net));
pxl8_net_predicted_tick_set(net, world->client_tick);
}
void pxl8_world_reconcile(pxl8_world* world, pxl8_net* net, f32 dt) {
if (!world || !net) return;
if (!(world->local_player.flags & PXL8_SIM_FLAG_ALIVE)) return;
if (!pxl8_net_needs_correction(net)) return;
u64 player_id = pxl8_net_player_id(net);
const u8* server_state = pxl8_net_entity_userdata(net, player_id);
if (!server_state) return;
pxl8_sim_entity server_player = {0};
userdata_to_entity(server_state, &server_player);
if (!(server_player.flags & PXL8_SIM_FLAG_ALIVE)) {
return;
}
world->local_player = server_player;
const pxl8_snapshot_header* snap = pxl8_net_snapshot(net);
u64 server_tick = snap ? snap->tick : 0;
for (u64 tick = server_tick + 1; tick <= world->client_tick; tick++) {
const pxl8_input_msg* input = pxl8_net_input_at(net, tick);
if (!input) continue;
pxl8_sim_world sim = pxl8_world_sim_world(world, world->local_player.pos);
pxl8_sim_move_player(&world->local_player, input, &sim, &world->sim_config, dt);
}
entity_to_userdata(&world->local_player, pxl8_net_predicted_state(net));
}
#ifdef PXL8_ASYNC_THREADS
#define SIM_TIMESTEP (1.0f / 60.0f)
static void pxl8_world_sim_tick(pxl8_world* world, f32 dt) {
bool alive = (world->local_player.flags & PXL8_SIM_FLAG_ALIVE) != 0;
pxl8_input_msg merged = {0};
pxl8_input_msg* input = NULL;
while ((input = pxl8_queue_pop(&world->input_queue))) {
merged.look_dx += input->look_dx;
merged.look_dy += input->look_dy;
merged.move_x = input->move_x;
merged.move_y = input->move_y;
merged.buttons |= input->buttons;
pxl8_free(input);
}
if (!alive) {
return;
}
const f32 MAX_LOOK_DELTA = 100.0f;
if (merged.look_dx > MAX_LOOK_DELTA) merged.look_dx = MAX_LOOK_DELTA;
if (merged.look_dx < -MAX_LOOK_DELTA) merged.look_dx = -MAX_LOOK_DELTA;
if (merged.look_dy > MAX_LOOK_DELTA) merged.look_dy = MAX_LOOK_DELTA;
if (merged.look_dy < -MAX_LOOK_DELTA) merged.look_dy = -MAX_LOOK_DELTA;
merged.tick = world->client_tick;
merged.timestamp = pxl8_get_ticks_ns();
merged.yaw = world->pointer_motion.yaw;
if (world->net) {
pxl8_net_send_input(world->net, &merged);
}
world->local_player.yaw = world->pointer_motion.yaw;
world->local_player.pitch = world->pointer_motion.pitch;
merged.look_dx = 0;
merged.look_dy = 0;
pxl8_sim_world sim = pxl8_world_sim_world(world, world->local_player.pos);
pxl8_sim_move_player(&world->local_player, &merged, &sim, &world->sim_config, dt);
if (world->net) {
entity_to_userdata(&world->local_player, pxl8_net_predicted_state(world->net));
pxl8_net_predicted_tick_set(world->net, world->client_tick);
}
world->client_tick++;
}
static void pxl8_world_swap_buffers(pxl8_world* world) {
u32 back = atomic_load(&world->active_buffer) ^ 1;
world->render_state[back] = world->local_player;
atomic_store(&world->active_buffer, back);
}
static int pxl8_world_sim_thread(void* data) {
pxl8_world* world = (pxl8_world*)data;
u64 last_time = pxl8_get_ticks_ns();
while (atomic_load(&world->sim_running)) {
if (atomic_load(&world->sim_paused)) {
last_time = pxl8_get_ticks_ns();
pxl8_sleep_ms(1);
continue;
}
u64 now = pxl8_get_ticks_ns();
f32 dt = (f32)(now - last_time) / 1e9f;
last_time = now;
if (dt > 0.1f) dt = 0.1f;
if (dt < 0.0001f) dt = 0.0001f;
world->sim_accumulator += dt;
while (world->sim_accumulator >= SIM_TIMESTEP) {
pxl8_world_chunk_cache_tick(world->chunk_cache);
if (world->net) {
pxl8_packet* pkt;
while ((pkt = pxl8_net_pop_packet(world->net))) {
pxl8_net_process_packet(world->net, pkt);
pxl8_net_packet_free(pkt);
}
pxl8_world_sync(world, world->net);
pxl8_world_reconcile(world, world->net, SIM_TIMESTEP);
}
pxl8_world_sim_tick(world, SIM_TIMESTEP);
world->sim_accumulator -= SIM_TIMESTEP;
}
pxl8_world_swap_buffers(world);
pxl8_sleep_ms(1);
}
return 0;
}
void pxl8_world_start_sim_thread(pxl8_world* world, pxl8_net* net) {
if (!world || world->sim_thread) return;
world->net = net;
pxl8_queue_init(&world->input_queue);
atomic_store(&world->active_buffer, 0);
atomic_store(&world->sim_running, true);
world->sim_accumulator = 0.0f;
world->render_state[0] = world->local_player;
world->render_state[1] = world->local_player;
world->sim_thread = pxl8_thread_create(pxl8_world_sim_thread, "pxl8_sim", world);
}
void pxl8_world_stop_sim_thread(pxl8_world* world) {
if (!world || !world->sim_thread) return;
atomic_store(&world->sim_running, false);
pxl8_thread_wait(world->sim_thread, NULL);
world->sim_thread = NULL;
pxl8_input_msg* input;
while ((input = pxl8_queue_pop(&world->input_queue))) {
pxl8_free(input);
}
}
void pxl8_world_pause_sim(pxl8_world* world, bool paused) {
if (!world) return;
if (paused) {
atomic_store(&world->sim_paused, true);
} else {
pxl8_input_msg* input;
while ((input = pxl8_queue_pop(&world->input_queue))) {
pxl8_free(input);
}
world->sim_accumulator = 0.0f;
atomic_store(&world->sim_paused, false);
}
}
void pxl8_world_push_input(pxl8_world* world, const pxl8_input_msg* input) {
if (!world || !input) return;
world->pointer_motion.yaw -= input->look_dx * 0.008f;
f32 pitch = world->pointer_motion.pitch - input->look_dy * 0.008f;
if (pitch > world->sim_config.max_pitch) pitch = world->sim_config.max_pitch;
if (pitch < -world->sim_config.max_pitch) pitch = -world->sim_config.max_pitch;
world->pointer_motion.pitch = pitch;
pxl8_input_msg* copy = pxl8_malloc(sizeof(pxl8_input_msg));
if (copy) {
*copy = *input;
if (!pxl8_queue_push(&world->input_queue, copy)) {
pxl8_free(copy);
}
}
}
const pxl8_sim_entity* pxl8_world_get_render_state(const pxl8_world* world) {
if (!world) return NULL;
u32 front = atomic_load(&((pxl8_world*)world)->active_buffer);
return &world->render_state[front];
}
f32 pxl8_world_get_interp_alpha(const pxl8_world* world) {
if (!world) return 1.0f;
return world->sim_accumulator / SIM_TIMESTEP;
}
#endif

View file

@ -1,71 +0,0 @@
#pragma once
#include "pxl8_entity.h"
#include "pxl8_gfx.h"
#include "pxl8_gfx3d.h"
#include "pxl8_math.h"
#include "pxl8_net.h"
#include "pxl8_sim.h"
#include "pxl8_types.h"
#include "pxl8_world_chunk.h"
#include "pxl8_world_chunk_cache.h"
#include "pxl8_bsp_render.h"
#ifdef __cplusplus
extern "C" {
#endif
#define PXL8_WORLD_MAX_LOADED_CHUNKS 25
typedef struct {
u16 leafs[16];
u8 count;
} pxl8_edge_leafs;
typedef struct pxl8_loaded_chunk {
pxl8_world_chunk* chunk;
pxl8_bsp_render_state* render_state;
pxl8_edge_leafs edges[4];
i32 cx;
i32 cz;
bool active;
} pxl8_loaded_chunk;
typedef struct pxl8_world pxl8_world;
pxl8_world* pxl8_world_create(void);
void pxl8_world_destroy(pxl8_world* world);
pxl8_world_chunk_cache* pxl8_world_get_chunk_cache(pxl8_world* world);
pxl8_world_chunk* pxl8_world_active_chunk(pxl8_world* world);
bool pxl8_world_point_solid(const pxl8_world* world, f32 x, f32 y, f32 z);
pxl8_ray pxl8_world_ray(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to);
pxl8_ray pxl8_world_sweep(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius);
void pxl8_world_update(pxl8_world* world, f32 dt);
void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);
void pxl8_world_sync(pxl8_world* world, pxl8_net* net);
void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_gfx_material* material);
void pxl8_world_set_sim_config(pxl8_world* world, const pxl8_sim_config* config);
void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z);
void pxl8_world_set_look(pxl8_world* world, f32 yaw, f32 pitch);
pxl8_sim_entity* pxl8_world_local_player(pxl8_world* world);
pxl8_sim_world pxl8_world_sim_world(const pxl8_world* world, pxl8_vec3 pos);
void pxl8_world_predict(pxl8_world* world, pxl8_net* net, const pxl8_input_msg* input, f32 dt);
void pxl8_world_reconcile(pxl8_world* world, pxl8_net* net, f32 dt);
#ifdef PXL8_ASYNC_THREADS
void pxl8_world_start_sim_thread(pxl8_world* world, pxl8_net* net);
void pxl8_world_stop_sim_thread(pxl8_world* world);
void pxl8_world_pause_sim(pxl8_world* world, bool paused);
void pxl8_world_push_input(pxl8_world* world, const pxl8_input_msg* input);
const pxl8_sim_entity* pxl8_world_get_render_state(const pxl8_world* world);
f32 pxl8_world_get_interp_alpha(const pxl8_world* world);
#endif
#ifdef __cplusplus
}
#endif

View file

@ -1,17 +0,0 @@
#include "pxl8_world_chunk.h"
#include "pxl8_bsp.h"
#include "pxl8_mem.h"
pxl8_world_chunk* pxl8_world_chunk_create_bsp(u32 id) {
pxl8_world_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_world_chunk));
if (!chunk) return NULL;
chunk->id = id;
return chunk;
}
void pxl8_world_chunk_destroy(pxl8_world_chunk* chunk) {
if (!chunk) return;
if (chunk->bsp) pxl8_bsp_destroy(chunk->bsp);
pxl8_free(chunk);
}

View file

@ -1,21 +0,0 @@
#pragma once
#include "pxl8_bsp.h"
#include "pxl8_types.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct pxl8_world_chunk {
u32 id;
u32 version;
pxl8_bsp* bsp;
} pxl8_world_chunk;
pxl8_world_chunk* pxl8_world_chunk_create_bsp(u32 id);
void pxl8_world_chunk_destroy(pxl8_world_chunk* chunk);
#ifdef __cplusplus
}
#endif

View file

@ -1,401 +0,0 @@
#include "pxl8_world_chunk_cache.h"
#include <stdlib.h>
#include <string.h>
#include "pxl8_bytes.h"
#include "pxl8_log.h"
#include "pxl8_mem.h"
static pxl8_world_chunk_cache_entry* find_entry_bsp(pxl8_world_chunk_cache* cache, u32 id) {
for (u32 i = 0; i < cache->entry_count; i++) {
pxl8_world_chunk_cache_entry* e = &cache->entries[i];
if (e->valid && e->chunk && e->chunk->id == id) {
return e;
}
}
return NULL;
}
static pxl8_world_chunk_cache_entry* alloc_entry(pxl8_world_chunk_cache* cache) {
if (cache->entry_count < PXL8_WORLD_CHUNK_CACHE_SIZE) {
pxl8_world_chunk_cache_entry* e = &cache->entries[cache->entry_count++];
memset(e, 0, sizeof(*e));
return e;
}
for (u32 i = 0; i < PXL8_WORLD_CHUNK_CACHE_SIZE; i++) {
if (!cache->entries[i].valid) {
pxl8_world_chunk_cache_entry* e = &cache->entries[i];
memset(e, 0, sizeof(*e));
return e;
}
}
u64 oldest = cache->entries[0].last_used;
u32 slot = 0;
for (u32 i = 1; i < PXL8_WORLD_CHUNK_CACHE_SIZE; i++) {
if (cache->entries[i].last_used < oldest) {
oldest = cache->entries[i].last_used;
slot = i;
}
}
pxl8_world_chunk_cache_entry* e = &cache->entries[slot];
if (e->chunk) {
pxl8_world_chunk_destroy(e->chunk);
}
memset(e, 0, sizeof(*e));
return e;
}
static void assembly_reset(pxl8_world_chunk_assembly* a) {
a->id = 0;
a->cx = 0;
a->cy = 0;
a->cz = 0;
a->version = 0;
a->fragment_count = 0;
a->fragments_received = 0;
a->data_size = 0;
a->active = false;
a->complete = false;
}
static void assembly_init(pxl8_world_chunk_assembly* a, const pxl8_chunk_msg_header* hdr) {
assembly_reset(a);
a->id = hdr->id;
a->cx = hdr->cx;
a->cy = hdr->cy;
a->cz = hdr->cz;
a->version = hdr->version;
a->fragment_count = hdr->fragment_count;
a->active = true;
u32 needed = PXL8_CHUNK_MAX_PAYLOAD * hdr->fragment_count;
if (a->data_capacity < needed) {
a->data_capacity = needed;
a->data = pxl8_realloc(a->data, a->data_capacity);
}
}
static pxl8_result deserialize_vertex(pxl8_stream* s, pxl8_bsp_vertex* v) {
v->position.x = pxl8_read_f32_be(s);
v->position.y = pxl8_read_f32_be(s);
v->position.z = pxl8_read_f32_be(s);
return PXL8_OK;
}
static pxl8_result deserialize_edge(pxl8_stream* s, pxl8_bsp_edge* e) {
e->vertex[0] = pxl8_read_u16_be(s);
e->vertex[1] = pxl8_read_u16_be(s);
return PXL8_OK;
}
static pxl8_result deserialize_plane(pxl8_stream* s, pxl8_bsp_plane* p) {
p->normal.x = pxl8_read_f32_be(s);
p->normal.y = pxl8_read_f32_be(s);
p->normal.z = pxl8_read_f32_be(s);
p->dist = pxl8_read_f32_be(s);
p->type = (i32)pxl8_read_u32_be(s);
return PXL8_OK;
}
static pxl8_result deserialize_face(pxl8_stream* s, pxl8_bsp_face* f) {
f->first_edge = pxl8_read_u32_be(s);
f->lightmap_offset = pxl8_read_u32_be(s);
f->num_edges = pxl8_read_u16_be(s);
f->plane_id = pxl8_read_u16_be(s);
f->side = pxl8_read_u16_be(s);
pxl8_read_bytes(s, f->styles, 4);
f->material_id = pxl8_read_u16_be(s);
f->aabb_min.x = pxl8_read_f32_be(s);
f->aabb_min.y = pxl8_read_f32_be(s);
f->aabb_min.z = pxl8_read_f32_be(s);
f->aabb_max.x = pxl8_read_f32_be(s);
f->aabb_max.y = pxl8_read_f32_be(s);
f->aabb_max.z = pxl8_read_f32_be(s);
return PXL8_OK;
}
static pxl8_result deserialize_node(pxl8_stream* s, pxl8_bsp_node* n) {
n->children[0] = (i32)pxl8_read_u32_be(s);
n->children[1] = (i32)pxl8_read_u32_be(s);
n->first_face = pxl8_read_u16_be(s);
n->maxs[0] = (i16)pxl8_read_u16_be(s);
n->maxs[1] = (i16)pxl8_read_u16_be(s);
n->maxs[2] = (i16)pxl8_read_u16_be(s);
n->mins[0] = (i16)pxl8_read_u16_be(s);
n->mins[1] = (i16)pxl8_read_u16_be(s);
n->mins[2] = (i16)pxl8_read_u16_be(s);
n->num_faces = pxl8_read_u16_be(s);
n->plane_id = pxl8_read_u32_be(s);
return PXL8_OK;
}
static pxl8_result deserialize_leaf(pxl8_stream* s, pxl8_bsp_leaf* l) {
pxl8_read_bytes(s, l->ambient_level, 4);
l->contents = (i32)pxl8_read_u32_be(s);
l->first_marksurface = pxl8_read_u16_be(s);
l->maxs[0] = (i16)pxl8_read_u16_be(s);
l->maxs[1] = (i16)pxl8_read_u16_be(s);
l->maxs[2] = (i16)pxl8_read_u16_be(s);
l->mins[0] = (i16)pxl8_read_u16_be(s);
l->mins[1] = (i16)pxl8_read_u16_be(s);
l->mins[2] = (i16)pxl8_read_u16_be(s);
l->num_marksurfaces = pxl8_read_u16_be(s);
l->visofs = (i32)pxl8_read_u32_be(s);
return PXL8_OK;
}
static pxl8_result deserialize_portal(pxl8_stream* s, pxl8_bsp_portal* p) {
p->x0 = pxl8_read_f32_be(s);
p->z0 = pxl8_read_f32_be(s);
p->x1 = pxl8_read_f32_be(s);
p->z1 = pxl8_read_f32_be(s);
p->target_leaf = pxl8_read_u32_be(s);
return PXL8_OK;
}
static pxl8_result deserialize_cell_portals(pxl8_stream* s, pxl8_bsp_cell_portals* cp) {
cp->num_portals = pxl8_read_u8(s);
pxl8_read_u8(s);
pxl8_read_u8(s);
pxl8_read_u8(s);
for (int i = 0; i < 4; i++) {
deserialize_portal(s, &cp->portals[i]);
}
return PXL8_OK;
}
static pxl8_bsp* assembly_to_bsp(pxl8_world_chunk_assembly* a) {
if (!a->complete || a->data_size < 48) {
return NULL;
}
pxl8_bsp* bsp = pxl8_calloc(1, sizeof(pxl8_bsp));
if (!bsp) return NULL;
pxl8_stream s = pxl8_stream_create(a->data, (u32)a->data_size);
pxl8_bsp_wire_header wire_hdr;
pxl8_protocol_deserialize_bsp_wire_header(a->data, 48, &wire_hdr);
s.offset = 48;
if (wire_hdr.num_vertices > 0) {
bsp->vertices = pxl8_calloc(wire_hdr.num_vertices, sizeof(pxl8_bsp_vertex));
bsp->num_vertices = wire_hdr.num_vertices;
for (u32 i = 0; i < wire_hdr.num_vertices; i++) {
deserialize_vertex(&s, &bsp->vertices[i]);
}
}
if (wire_hdr.num_edges > 0) {
bsp->edges = pxl8_calloc(wire_hdr.num_edges, sizeof(pxl8_bsp_edge));
bsp->num_edges = wire_hdr.num_edges;
for (u32 i = 0; i < wire_hdr.num_edges; i++) {
deserialize_edge(&s, &bsp->edges[i]);
}
}
if (wire_hdr.num_surfedges > 0) {
bsp->surfedges = pxl8_calloc(wire_hdr.num_surfedges, sizeof(i32));
bsp->num_surfedges = wire_hdr.num_surfedges;
for (u32 i = 0; i < wire_hdr.num_surfedges; i++) {
bsp->surfedges[i] = (i32)pxl8_read_u32_be(&s);
}
}
if (wire_hdr.num_planes > 0) {
bsp->planes = pxl8_calloc(wire_hdr.num_planes, sizeof(pxl8_bsp_plane));
bsp->num_planes = wire_hdr.num_planes;
for (u32 i = 0; i < wire_hdr.num_planes; i++) {
deserialize_plane(&s, &bsp->planes[i]);
}
}
if (wire_hdr.num_faces > 0) {
bsp->faces = pxl8_calloc(wire_hdr.num_faces, sizeof(pxl8_bsp_face));
bsp->num_faces = wire_hdr.num_faces;
for (u32 i = 0; i < wire_hdr.num_faces; i++) {
deserialize_face(&s, &bsp->faces[i]);
}
}
if (wire_hdr.num_nodes > 0) {
bsp->nodes = pxl8_calloc(wire_hdr.num_nodes, sizeof(pxl8_bsp_node));
bsp->num_nodes = wire_hdr.num_nodes;
for (u32 i = 0; i < wire_hdr.num_nodes; i++) {
deserialize_node(&s, &bsp->nodes[i]);
}
}
if (wire_hdr.num_leafs > 0) {
bsp->leafs = pxl8_calloc(wire_hdr.num_leafs, sizeof(pxl8_bsp_leaf));
bsp->num_leafs = wire_hdr.num_leafs;
for (u32 i = 0; i < wire_hdr.num_leafs; i++) {
deserialize_leaf(&s, &bsp->leafs[i]);
}
}
if (wire_hdr.num_marksurfaces > 0) {
bsp->marksurfaces = pxl8_calloc(wire_hdr.num_marksurfaces, sizeof(u16));
bsp->num_marksurfaces = wire_hdr.num_marksurfaces;
for (u32 i = 0; i < wire_hdr.num_marksurfaces; i++) {
bsp->marksurfaces[i] = pxl8_read_u16_be(&s);
}
}
if (wire_hdr.num_cell_portals > 0) {
bsp->cell_portals = pxl8_calloc(wire_hdr.num_cell_portals, sizeof(pxl8_bsp_cell_portals));
bsp->num_cell_portals = wire_hdr.num_cell_portals;
for (u32 i = 0; i < wire_hdr.num_cell_portals; i++) {
deserialize_cell_portals(&s, &bsp->cell_portals[i]);
}
}
if (wire_hdr.visdata_size > 0) {
bsp->visdata = pxl8_malloc(wire_hdr.visdata_size);
bsp->visdata_size = wire_hdr.visdata_size;
pxl8_read_bytes(&s, bsp->visdata, wire_hdr.visdata_size);
}
if (wire_hdr.num_vertex_lights > 0) {
bsp->vertex_lights = pxl8_calloc(wire_hdr.num_vertex_lights, sizeof(u32));
bsp->num_vertex_lights = wire_hdr.num_vertex_lights;
for (u32 i = 0; i < wire_hdr.num_vertex_lights; i++) {
bsp->vertex_lights[i] = pxl8_read_u32_be(&s);
}
}
if (wire_hdr.num_heightfield > 0) {
bsp->heightfield = pxl8_calloc(wire_hdr.num_heightfield, sizeof(f32));
bsp->num_heightfield = wire_hdr.num_heightfield;
for (u32 i = 0; i < wire_hdr.num_heightfield; i++) {
u32 raw = pxl8_read_u32_be(&s);
memcpy(&bsp->heightfield[i], &raw, sizeof(f32));
}
bsp->heightfield_w = pxl8_read_u16_be(&s);
bsp->heightfield_h = pxl8_read_u16_be(&s);
u32 ox_raw = pxl8_read_u32_be(&s);
memcpy(&bsp->heightfield_ox, &ox_raw, sizeof(f32));
u32 oz_raw = pxl8_read_u32_be(&s);
memcpy(&bsp->heightfield_oz, &oz_raw, sizeof(f32));
u32 cs_raw = pxl8_read_u32_be(&s);
memcpy(&bsp->heightfield_cell_size, &cs_raw, sizeof(f32));
}
u32 raw;
raw = pxl8_read_u32_be(&s);
memcpy(&bsp->bounds_min_x, &raw, sizeof(f32));
raw = pxl8_read_u32_be(&s);
memcpy(&bsp->bounds_min_z, &raw, sizeof(f32));
raw = pxl8_read_u32_be(&s);
memcpy(&bsp->bounds_max_x, &raw, sizeof(f32));
raw = pxl8_read_u32_be(&s);
memcpy(&bsp->bounds_max_z, &raw, sizeof(f32));
return bsp;
}
static pxl8_result assemble_bsp(pxl8_world_chunk_cache* cache, pxl8_world_chunk_assembly* a) {
pxl8_bsp* bsp = assembly_to_bsp(a);
if (!bsp) {
assembly_reset(a);
return PXL8_ERROR_INVALID_ARGUMENT;
}
pxl8_world_chunk_cache_entry* entry = find_entry_bsp(cache, a->id);
if (entry) {
if (entry->chunk && entry->chunk->bsp) {
pxl8_bsp_destroy(entry->chunk->bsp);
}
} else {
entry = alloc_entry(cache);
entry->chunk = pxl8_world_chunk_create_bsp(a->id);
entry->valid = true;
}
entry->chunk->bsp = bsp;
entry->chunk->version = a->version;
entry->last_used = cache->frame_counter;
assembly_reset(a);
return PXL8_OK;
}
pxl8_world_chunk_cache* pxl8_world_chunk_cache_create(void) {
pxl8_world_chunk_cache* cache = pxl8_calloc(1, sizeof(pxl8_world_chunk_cache));
if (!cache) return NULL;
assembly_reset(&cache->assembly);
return cache;
}
void pxl8_world_chunk_cache_destroy(pxl8_world_chunk_cache* cache) {
if (!cache) return;
for (u32 i = 0; i < cache->entry_count; i++) {
pxl8_world_chunk_cache_entry* e = &cache->entries[i];
if (e->chunk) pxl8_world_chunk_destroy(e->chunk);
}
pxl8_free(cache->assembly.data);
pxl8_free(cache);
}
pxl8_result pxl8_world_chunk_cache_receive(pxl8_world_chunk_cache* cache,
const pxl8_chunk_msg_header* hdr,
const u8* payload, usize len) {
if (!cache || !hdr || !payload) return PXL8_ERROR_INVALID_ARGUMENT;
pxl8_world_chunk_assembly* a = &cache->assembly;
bool new_assembly = !a->active ||
a->id != hdr->id ||
a->version != hdr->version;
if (new_assembly) {
assembly_init(a, hdr);
}
if (hdr->fragment_idx >= PXL8_WORLD_CHUNK_MAX_FRAGMENTS) {
return PXL8_ERROR_INVALID_ARGUMENT;
}
u32 offset = (u32)hdr->fragment_idx * PXL8_CHUNK_MAX_PAYLOAD;
u32 required = offset + (u32)len;
if (required > a->data_capacity) {
return PXL8_ERROR_OUT_OF_MEMORY;
}
memcpy(a->data + offset, payload, len);
if (required > a->data_size) {
a->data_size = required;
}
a->fragments_received++;
if (hdr->flags & PXL8_CHUNK_FLAG_FINAL) {
a->complete = true;
return assemble_bsp(cache, a);
}
return PXL8_OK;
}
pxl8_world_chunk* pxl8_world_chunk_cache_get_bsp(pxl8_world_chunk_cache* cache, u32 id) {
if (!cache) return NULL;
pxl8_world_chunk_cache_entry* e = find_entry_bsp(cache, id);
if (e) {
e->last_used = cache->frame_counter;
return e->chunk;
}
return NULL;
}
void pxl8_world_chunk_cache_tick(pxl8_world_chunk_cache* cache) {
if (!cache) return;
cache->frame_counter++;
}

View file

@ -1,55 +0,0 @@
#pragma once
#include "pxl8_mesh.h"
#include "pxl8_protocol.h"
#include "pxl8_types.h"
#include "pxl8_world_chunk.h"
#ifdef __cplusplus
extern "C" {
#endif
#define PXL8_WORLD_CHUNK_CACHE_SIZE 512
#define PXL8_WORLD_CHUNK_MAX_FRAGMENTS 255
#define PXL8_WORLD_CHUNK_MAX_DATA_SIZE 131072
typedef struct pxl8_world_chunk_cache_entry {
pxl8_world_chunk* chunk;
u64 last_used;
bool valid;
} pxl8_world_chunk_cache_entry;
typedef struct pxl8_world_chunk_assembly {
u32 id;
i32 cx, cy, cz;
u32 version;
u8 fragment_count;
u8 fragments_received;
u8* data;
u32 data_size;
u32 data_capacity;
bool active;
bool complete;
} pxl8_world_chunk_assembly;
typedef struct pxl8_world_chunk_cache {
pxl8_world_chunk_cache_entry entries[PXL8_WORLD_CHUNK_CACHE_SIZE];
pxl8_world_chunk_assembly assembly;
u32 entry_count;
u64 frame_counter;
} pxl8_world_chunk_cache;
pxl8_world_chunk_cache* pxl8_world_chunk_cache_create(void);
void pxl8_world_chunk_cache_destroy(pxl8_world_chunk_cache* cache);
pxl8_result pxl8_world_chunk_cache_receive(pxl8_world_chunk_cache* cache,
const pxl8_chunk_msg_header* hdr,
const u8* payload, usize len);
pxl8_world_chunk* pxl8_world_chunk_cache_get_bsp(pxl8_world_chunk_cache* cache, u32 id);
void pxl8_world_chunk_cache_tick(pxl8_world_chunk_cache* cache);
#ifdef __cplusplus
}
#endif