stream world data from pxl8d to pxl8
This commit is contained in:
parent
39ee0fefb7
commit
a71a9840b2
55 changed files with 5290 additions and 2131 deletions
|
|
@ -394,6 +394,48 @@ i32 pxl8_bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos) {
|
|||
return -(node_id + 1);
|
||||
}
|
||||
|
||||
bool pxl8_bsp_point_solid(const pxl8_bsp* bsp, pxl8_vec3 pos) {
|
||||
i32 leaf = pxl8_bsp_find_leaf(bsp, pos);
|
||||
if (leaf < 0 || (u32)leaf >= bsp->num_leafs) return true;
|
||||
return bsp->leafs[leaf].contents == -1;
|
||||
}
|
||||
|
||||
static bool point_clear(const pxl8_bsp* bsp, f32 x, f32 y, f32 z, f32 radius) {
|
||||
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z})) return false;
|
||||
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x + radius, y, z})) return false;
|
||||
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x - radius, y, z})) return false;
|
||||
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z + radius})) return false;
|
||||
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z - radius})) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
pxl8_vec3 pxl8_bsp_trace(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to, f32 radius) {
|
||||
if (!bsp || bsp->num_nodes == 0) return to;
|
||||
|
||||
if (point_clear(bsp, to.x, to.y, to.z, radius)) {
|
||||
return to;
|
||||
}
|
||||
|
||||
bool x_ok = point_clear(bsp, to.x, from.y, from.z, radius);
|
||||
bool z_ok = point_clear(bsp, from.x, from.y, to.z, radius);
|
||||
|
||||
if (x_ok && z_ok) {
|
||||
f32 dx = to.x - from.x;
|
||||
f32 dz = to.z - from.z;
|
||||
if (dx * dx > dz * dz) {
|
||||
return (pxl8_vec3){to.x, from.y, from.z};
|
||||
} else {
|
||||
return (pxl8_vec3){from.x, from.y, to.z};
|
||||
}
|
||||
} else if (x_ok) {
|
||||
return (pxl8_vec3){to.x, from.y, from.z};
|
||||
} else if (z_ok) {
|
||||
return (pxl8_vec3){from.x, from.y, to.z};
|
||||
}
|
||||
|
||||
return from;
|
||||
}
|
||||
|
||||
bool pxl8_bsp_is_leaf_visible(const pxl8_bsp* bsp, i32 leaf_from, i32 leaf_to) {
|
||||
if (!bsp || !bsp->visdata || bsp->visdata_size == 0) return true;
|
||||
if (leaf_from < 0 || leaf_to < 0) return true;
|
||||
|
|
@ -576,12 +618,6 @@ static inline bool face_in_frustum(const pxl8_bsp* bsp, u32 face_id, const pxl8_
|
|||
return pxl8_frustum_test_aabb(frustum, face->aabb_min, face->aabb_max);
|
||||
}
|
||||
|
||||
static inline bool leaf_in_frustum(const pxl8_bsp_leaf* leaf, const pxl8_frustum* frustum) {
|
||||
pxl8_vec3 mins = {(f32)leaf->mins[0], (f32)leaf->mins[1], (f32)leaf->mins[2]};
|
||||
pxl8_vec3 maxs = {(f32)leaf->maxs[0], (f32)leaf->maxs[1], (f32)leaf->maxs[2]};
|
||||
return pxl8_frustum_test_aabb(frustum, mins, maxs);
|
||||
}
|
||||
|
||||
static inline bool screen_rect_valid(screen_rect r) {
|
||||
return r.x0 < r.x1 && r.y0 < r.y1;
|
||||
}
|
||||
|
|
@ -744,16 +780,24 @@ void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const
|
|||
}
|
||||
|
||||
void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos) {
|
||||
if (!gfx || !bsp || bsp->num_faces == 0) return;
|
||||
if (!bsp->cell_portals || bsp->num_cell_portals == 0) return;
|
||||
if (!bsp->materials || bsp->num_materials == 0) return;
|
||||
if (!gfx || !bsp || bsp->num_faces == 0) {
|
||||
return;
|
||||
}
|
||||
if (!bsp->cell_portals || bsp->num_cell_portals == 0) {
|
||||
pxl8_debug("[BSP] render: no cell_portals (ptr=%p count=%u)", (void*)bsp->cell_portals, bsp->num_cell_portals);
|
||||
return;
|
||||
}
|
||||
if (!bsp->materials || bsp->num_materials == 0) {
|
||||
pxl8_debug("[BSP] render: no materials (ptr=%p count=%u)", (void*)bsp->materials, bsp->num_materials);
|
||||
return;
|
||||
}
|
||||
|
||||
const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx);
|
||||
const pxl8_mat4* vp = pxl8_3d_get_view_proj(gfx);
|
||||
if (!frustum || !vp) return;
|
||||
|
||||
i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
|
||||
if (camera_leaf < 0 || (u32)camera_leaf >= bsp->num_cell_portals) return;
|
||||
if (camera_leaf < 0 || (u32)camera_leaf >= bsp->num_leafs) return;
|
||||
|
||||
pxl8_bsp* bsp_mut = (pxl8_bsp*)bsp;
|
||||
if (!bsp_mut->render_face_flags) {
|
||||
|
|
@ -837,15 +881,12 @@ void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos) {
|
|||
}
|
||||
|
||||
u32 current_material = 0xFFFFFFFF;
|
||||
u32 total_faces = 0;
|
||||
|
||||
for (u32 leaf_id = 0; leaf_id < bsp->num_leafs; leaf_id++) {
|
||||
u32 byte = leaf_id >> 3;
|
||||
u32 bit = leaf_id & 7;
|
||||
if (!(visited[byte] & (1 << bit))) continue;
|
||||
if (!(visited[leaf_id >> 3] & (1 << (leaf_id & 7)))) continue;
|
||||
if (bsp->leafs[leaf_id].contents == -1) continue;
|
||||
|
||||
const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id];
|
||||
if (!leaf_in_frustum(leaf, frustum)) continue;
|
||||
|
||||
for (u32 i = 0; i < leaf->num_marksurfaces; i++) {
|
||||
u32 surf_idx = leaf->first_marksurface + i;
|
||||
|
|
@ -862,7 +903,6 @@ void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos) {
|
|||
const pxl8_bsp_face* face = &bsp->faces[face_id];
|
||||
u32 material_id = face->material_id;
|
||||
if (material_id >= bsp->num_materials) continue;
|
||||
total_faces++;
|
||||
|
||||
if (material_id != current_material && mesh->index_count > 0 && current_material < bsp->num_materials) {
|
||||
pxl8_mat4 identity = pxl8_mat4_identity();
|
||||
|
|
@ -880,19 +920,61 @@ void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos) {
|
|||
pxl8_3d_draw_mesh(gfx, mesh, &identity, &bsp->materials[current_material]);
|
||||
}
|
||||
|
||||
static u32 debug_count = 0;
|
||||
if (debug_count++ < 5) {
|
||||
pxl8_debug("BSP render: %u faces, mesh verts=%u indices=%u", total_faces, mesh->vertex_count, mesh->index_count);
|
||||
if (mesh->vertex_count > 0) {
|
||||
pxl8_vertex* v = &mesh->vertices[0];
|
||||
pxl8_debug(" vert[0]: pos=(%.1f,%.1f,%.1f) uv=(%.2f,%.2f)",
|
||||
v->position.x, v->position.y, v->position.z, v->u, v->v);
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_bsp_pvs_destroy(&pvs);
|
||||
pxl8_free(visited);
|
||||
pxl8_free(cell_windows);
|
||||
pxl8_free(queue);
|
||||
pxl8_mesh_destroy(mesh);
|
||||
}
|
||||
|
||||
u32 pxl8_bsp_face_count(const pxl8_bsp* bsp) {
|
||||
if (!bsp) return 0;
|
||||
return bsp->num_faces;
|
||||
}
|
||||
|
||||
pxl8_vec3 pxl8_bsp_face_normal(const pxl8_bsp* bsp, u32 face_id) {
|
||||
pxl8_vec3 up = {0, 1, 0};
|
||||
if (!bsp || face_id >= bsp->num_faces) return up;
|
||||
|
||||
const pxl8_bsp_face* face = &bsp->faces[face_id];
|
||||
if (face->plane_id >= bsp->num_planes) return up;
|
||||
|
||||
pxl8_vec3 normal = bsp->planes[face->plane_id].normal;
|
||||
if (face->side) {
|
||||
normal.x = -normal.x;
|
||||
normal.y = -normal.y;
|
||||
normal.z = -normal.z;
|
||||
}
|
||||
return normal;
|
||||
}
|
||||
|
||||
void pxl8_bsp_face_set_material(pxl8_bsp* bsp, u32 face_id, u16 material_id) {
|
||||
if (!bsp || face_id >= bsp->num_faces) return;
|
||||
bsp->faces[face_id].material_id = material_id;
|
||||
}
|
||||
|
||||
void pxl8_bsp_set_material(pxl8_bsp* bsp, u16 material_id, const pxl8_gfx_material* material) {
|
||||
if (!bsp || !material) return;
|
||||
|
||||
if (material_id >= bsp->num_materials) {
|
||||
u32 new_count = material_id + 1;
|
||||
pxl8_gfx_material* new_materials = pxl8_realloc(bsp->materials, new_count * sizeof(pxl8_gfx_material));
|
||||
if (!new_materials) return;
|
||||
|
||||
for (u32 i = bsp->num_materials; i < new_count; i++) {
|
||||
memset(&new_materials[i], 0, sizeof(pxl8_gfx_material));
|
||||
}
|
||||
|
||||
bsp->materials = new_materials;
|
||||
bsp->num_materials = new_count;
|
||||
}
|
||||
|
||||
bsp->materials[material_id] = *material;
|
||||
}
|
||||
|
||||
void pxl8_bsp_set_wireframe(pxl8_bsp* bsp, bool wireframe) {
|
||||
if (!bsp || !bsp->materials) return;
|
||||
for (u32 i = 0; i < bsp->num_materials; i++) {
|
||||
bsp->materials[i].wireframe = wireframe;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,22 +136,25 @@ typedef struct pxl8_bsp {
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp);
|
||||
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_pvs pxl8_bsp_decompress_pvs(const pxl8_bsp* bsp, i32 leaf);
|
||||
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_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);
|
||||
|
||||
pxl8_bsp_lightmap pxl8_bsp_lightmap_uniform(u8 r, u8 g, u8 b);
|
||||
pxl8_bsp_lightmap pxl8_bsp_lightmap_mapped(u8 width, u8 height, u32 offset);
|
||||
pxl8_bsp_lightmap_sample pxl8_bsp_sample_lightmap(const pxl8_bsp* bsp, u32 face_idx, f32 u, f32 v);
|
||||
|
||||
void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const pxl8_gfx_material* material);
|
||||
void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos);
|
||||
void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const pxl8_gfx_material* material);
|
||||
pxl8_bsp_lightmap_sample pxl8_bsp_sample_lightmap(const pxl8_bsp* bsp, u32 face_idx, f32 u, f32 v);
|
||||
void pxl8_bsp_set_material(pxl8_bsp* bsp, u16 material_id, const pxl8_gfx_material* material);
|
||||
void pxl8_bsp_set_wireframe(pxl8_bsp* bsp, bool wireframe);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
|||
44
src/world/pxl8_chunk.c
Normal file
44
src/world/pxl8_chunk.c
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#include "pxl8_chunk.h"
|
||||
|
||||
#include "pxl8_mem.h"
|
||||
|
||||
pxl8_chunk* pxl8_chunk_create_vxl(i32 cx, i32 cy, i32 cz) {
|
||||
pxl8_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_chunk));
|
||||
if (!chunk) return NULL;
|
||||
|
||||
chunk->type = PXL8_CHUNK_VXL;
|
||||
chunk->cx = cx;
|
||||
chunk->cy = cy;
|
||||
chunk->cz = cz;
|
||||
chunk->voxel = pxl8_voxel_chunk_create();
|
||||
|
||||
if (!chunk->voxel) {
|
||||
pxl8_free(chunk);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
pxl8_chunk* pxl8_chunk_create_bsp(u32 id) {
|
||||
pxl8_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_chunk));
|
||||
if (!chunk) return NULL;
|
||||
|
||||
chunk->type = PXL8_CHUNK_BSP;
|
||||
chunk->id = id;
|
||||
chunk->bsp = NULL;
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
void pxl8_chunk_destroy(pxl8_chunk* chunk) {
|
||||
if (!chunk) return;
|
||||
|
||||
if (chunk->type == PXL8_CHUNK_VXL && chunk->voxel) {
|
||||
pxl8_voxel_chunk_destroy(chunk->voxel);
|
||||
} else if (chunk->type == PXL8_CHUNK_BSP && chunk->bsp) {
|
||||
pxl8_bsp_destroy(chunk->bsp);
|
||||
}
|
||||
|
||||
pxl8_free(chunk);
|
||||
}
|
||||
33
src/world/pxl8_chunk.h
Normal file
33
src/world/pxl8_chunk.h
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_bsp.h"
|
||||
#include "pxl8_types.h"
|
||||
#include "pxl8_voxel.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum pxl8_chunk_type {
|
||||
PXL8_CHUNK_VXL,
|
||||
PXL8_CHUNK_BSP
|
||||
} pxl8_chunk_type;
|
||||
|
||||
typedef struct pxl8_chunk {
|
||||
pxl8_chunk_type type;
|
||||
u32 id;
|
||||
u32 version;
|
||||
i32 cx, cy, cz;
|
||||
union {
|
||||
pxl8_voxel_chunk* voxel;
|
||||
pxl8_bsp* bsp;
|
||||
};
|
||||
} pxl8_chunk;
|
||||
|
||||
pxl8_chunk* pxl8_chunk_create_vxl(i32 cx, i32 cy, i32 cz);
|
||||
pxl8_chunk* pxl8_chunk_create_bsp(u32 id);
|
||||
void pxl8_chunk_destroy(pxl8_chunk* chunk);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
538
src/world/pxl8_chunk_cache.c
Normal file
538
src/world/pxl8_chunk_cache.c
Normal file
|
|
@ -0,0 +1,538 @@
|
|||
#include "pxl8_chunk_cache.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_bytes.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_mem.h"
|
||||
|
||||
#define PXL8_VOXEL_CHUNK_VOLUME (PXL8_VOXEL_CHUNK_SIZE * PXL8_VOXEL_CHUNK_SIZE * PXL8_VOXEL_CHUNK_SIZE)
|
||||
|
||||
static pxl8_chunk_cache_entry* find_entry_vxl(pxl8_chunk_cache* cache, i32 cx, i32 cy, i32 cz) {
|
||||
for (u32 i = 0; i < cache->entry_count; i++) {
|
||||
pxl8_chunk_cache_entry* e = &cache->entries[i];
|
||||
if (e->valid && e->chunk && e->chunk->type == PXL8_CHUNK_VXL &&
|
||||
e->chunk->cx == cx && e->chunk->cy == cy && e->chunk->cz == cz) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static pxl8_chunk_cache_entry* find_entry_bsp(pxl8_chunk_cache* cache, u32 id) {
|
||||
for (u32 i = 0; i < cache->entry_count; i++) {
|
||||
pxl8_chunk_cache_entry* e = &cache->entries[i];
|
||||
if (e->valid && e->chunk && e->chunk->type == PXL8_CHUNK_BSP &&
|
||||
e->chunk->id == id) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static pxl8_chunk_cache_entry* alloc_entry(pxl8_chunk_cache* cache) {
|
||||
if (cache->entry_count < PXL8_CHUNK_CACHE_SIZE) {
|
||||
pxl8_chunk_cache_entry* e = &cache->entries[cache->entry_count++];
|
||||
memset(e, 0, sizeof(*e));
|
||||
return e;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < PXL8_CHUNK_CACHE_SIZE; i++) {
|
||||
if (!cache->entries[i].valid) {
|
||||
pxl8_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_CHUNK_CACHE_SIZE; i++) {
|
||||
if (cache->entries[i].last_used < oldest) {
|
||||
oldest = cache->entries[i].last_used;
|
||||
slot = i;
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_chunk_cache_entry* e = &cache->entries[slot];
|
||||
if (e->chunk) pxl8_chunk_destroy(e->chunk);
|
||||
if (e->mesh) pxl8_mesh_destroy(e->mesh);
|
||||
memset(e, 0, sizeof(*e));
|
||||
return e;
|
||||
}
|
||||
|
||||
static void assembly_reset(pxl8_chunk_assembly* a) {
|
||||
a->type = PXL8_CHUNK_VXL;
|
||||
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_chunk_assembly* a, const pxl8_chunk_msg_header* hdr) {
|
||||
assembly_reset(a);
|
||||
a->type = hdr->chunk_type == PXL8_CHUNK_TYPE_BSP ? PXL8_CHUNK_BSP : PXL8_CHUNK_VXL;
|
||||
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 bool rle_decode_voxel(const u8* src, usize src_len, pxl8_voxel_chunk* chunk) {
|
||||
usize src_pos = 0;
|
||||
usize dst_pos = 0;
|
||||
|
||||
while (src_pos + 1 < src_len && dst_pos < PXL8_VOXEL_CHUNK_VOLUME) {
|
||||
u8 block = src[src_pos++];
|
||||
u8 run_minus_one = src[src_pos++];
|
||||
usize run = (usize)run_minus_one + 1;
|
||||
|
||||
for (usize i = 0; i < run && dst_pos < PXL8_VOXEL_CHUNK_VOLUME; i++) {
|
||||
i32 x = (i32)(dst_pos % PXL8_VOXEL_CHUNK_SIZE);
|
||||
i32 y = (i32)((dst_pos / PXL8_VOXEL_CHUNK_SIZE) % PXL8_VOXEL_CHUNK_SIZE);
|
||||
i32 z = (i32)(dst_pos / (PXL8_VOXEL_CHUNK_SIZE * PXL8_VOXEL_CHUNK_SIZE));
|
||||
pxl8_voxel_set(chunk, x, y, z, block);
|
||||
dst_pos++;
|
||||
}
|
||||
}
|
||||
|
||||
return dst_pos == PXL8_VOXEL_CHUNK_VOLUME;
|
||||
}
|
||||
|
||||
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_chunk_assembly* a) {
|
||||
if (!a->complete || a->data_size < 44) {
|
||||
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, 44, &wire_hdr);
|
||||
s.offset = 44;
|
||||
|
||||
pxl8_debug("[CLIENT] Wire header: verts=%u edges=%u faces=%u planes=%u nodes=%u leafs=%u surfedges=%u visdata=%u",
|
||||
wire_hdr.num_vertices, wire_hdr.num_edges, wire_hdr.num_faces,
|
||||
wire_hdr.num_planes, wire_hdr.num_nodes, wire_hdr.num_leafs,
|
||||
wire_hdr.num_surfedges, wire_hdr.visdata_size);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_debug("Deserialized BSP: %u verts, %u faces, %u nodes, %u leafs",
|
||||
bsp->num_vertices, bsp->num_faces, bsp->num_nodes, bsp->num_leafs);
|
||||
|
||||
return bsp;
|
||||
}
|
||||
|
||||
static pxl8_result assemble_vxl(pxl8_chunk_cache* cache, pxl8_chunk_assembly* a) {
|
||||
pxl8_chunk_cache_entry* entry = find_entry_vxl(cache, a->cx, a->cy, a->cz);
|
||||
if (!entry) {
|
||||
entry = alloc_entry(cache);
|
||||
entry->chunk = pxl8_chunk_create_vxl(a->cx, a->cy, a->cz);
|
||||
entry->valid = true;
|
||||
}
|
||||
|
||||
entry->chunk->version = a->version;
|
||||
entry->mesh_dirty = true;
|
||||
entry->last_used = cache->frame_counter;
|
||||
|
||||
if (entry->mesh) {
|
||||
pxl8_mesh_destroy(entry->mesh);
|
||||
entry->mesh = NULL;
|
||||
}
|
||||
|
||||
pxl8_voxel_chunk_clear(entry->chunk->voxel);
|
||||
|
||||
if (!rle_decode_voxel(a->data, a->data_size, entry->chunk->voxel)) {
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
assembly_reset(a);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result assemble_bsp(pxl8_chunk_cache* cache, pxl8_chunk_assembly* a) {
|
||||
pxl8_debug("[CLIENT] assemble_bsp: id=%u data_size=%zu", a->id, a->data_size);
|
||||
pxl8_bsp* bsp = assembly_to_bsp(a);
|
||||
if (!bsp) {
|
||||
pxl8_debug("[CLIENT] assemble_bsp: assembly_to_bsp returned NULL!");
|
||||
assembly_reset(a);
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
pxl8_debug("[CLIENT] assemble_bsp: BSP created with %u verts %u faces", bsp->num_vertices, bsp->num_faces);
|
||||
|
||||
pxl8_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_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_chunk_cache* pxl8_chunk_cache_create(void) {
|
||||
pxl8_chunk_cache* cache = pxl8_calloc(1, sizeof(pxl8_chunk_cache));
|
||||
if (!cache) return NULL;
|
||||
assembly_reset(&cache->assembly);
|
||||
return cache;
|
||||
}
|
||||
|
||||
void pxl8_chunk_cache_destroy(pxl8_chunk_cache* cache) {
|
||||
if (!cache) return;
|
||||
|
||||
for (u32 i = 0; i < cache->entry_count; i++) {
|
||||
pxl8_chunk_cache_entry* e = &cache->entries[i];
|
||||
if (e->chunk) pxl8_chunk_destroy(e->chunk);
|
||||
if (e->mesh) pxl8_mesh_destroy(e->mesh);
|
||||
}
|
||||
|
||||
pxl8_free(cache->assembly.data);
|
||||
pxl8_free(cache);
|
||||
}
|
||||
|
||||
pxl8_result pxl8_chunk_cache_receive(pxl8_chunk_cache* cache,
|
||||
const pxl8_chunk_msg_header* hdr,
|
||||
const u8* payload, usize len) {
|
||||
if (!cache || !hdr || !payload) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
pxl8_chunk_assembly* a = &cache->assembly;
|
||||
|
||||
bool new_assembly = !a->active ||
|
||||
(hdr->chunk_type == PXL8_CHUNK_TYPE_BSP && a->id != hdr->id) ||
|
||||
(hdr->chunk_type == PXL8_CHUNK_TYPE_VXL &&
|
||||
(a->cx != hdr->cx || a->cy != hdr->cy || a->cz != hdr->cz)) ||
|
||||
a->version != hdr->version ||
|
||||
hdr->fragment_idx == 0;
|
||||
|
||||
if (new_assembly) {
|
||||
assembly_init(a, hdr);
|
||||
}
|
||||
|
||||
if (hdr->fragment_idx >= PXL8_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;
|
||||
pxl8_debug("[CLIENT] Final fragment received, assembling type=%d data_size=%zu", a->type, a->data_size);
|
||||
|
||||
if (a->type == PXL8_CHUNK_BSP) {
|
||||
return assemble_bsp(cache, a);
|
||||
} else {
|
||||
return assemble_vxl(cache, a);
|
||||
}
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_chunk* pxl8_chunk_cache_get_vxl(pxl8_chunk_cache* cache, i32 cx, i32 cy, i32 cz) {
|
||||
if (!cache) return NULL;
|
||||
pxl8_chunk_cache_entry* e = find_entry_vxl(cache, cx, cy, cz);
|
||||
if (e) {
|
||||
e->last_used = cache->frame_counter;
|
||||
return e->chunk;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pxl8_chunk* pxl8_chunk_cache_get_bsp(pxl8_chunk_cache* cache, u32 id) {
|
||||
if (!cache) return NULL;
|
||||
pxl8_chunk_cache_entry* e = find_entry_bsp(cache, id);
|
||||
if (e) {
|
||||
e->last_used = cache->frame_counter;
|
||||
return e->chunk;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pxl8_mesh* pxl8_chunk_cache_get_mesh(pxl8_chunk_cache* cache,
|
||||
i32 cx, i32 cy, i32 cz,
|
||||
const pxl8_block_registry* registry,
|
||||
const pxl8_voxel_mesh_config* config) {
|
||||
if (!cache || !registry) return NULL;
|
||||
|
||||
pxl8_chunk_cache_entry* entry = find_entry_vxl(cache, cx, cy, cz);
|
||||
if (!entry || !entry->chunk || !entry->chunk->voxel) return NULL;
|
||||
|
||||
if (entry->mesh && !entry->mesh_dirty) {
|
||||
return entry->mesh;
|
||||
}
|
||||
|
||||
if (entry->mesh) {
|
||||
pxl8_mesh_destroy(entry->mesh);
|
||||
entry->mesh = NULL;
|
||||
}
|
||||
|
||||
pxl8_chunk* nx = pxl8_chunk_cache_get_vxl(cache, cx - 1, cy, cz);
|
||||
pxl8_chunk* px = pxl8_chunk_cache_get_vxl(cache, cx + 1, cy, cz);
|
||||
pxl8_chunk* ny = pxl8_chunk_cache_get_vxl(cache, cx, cy - 1, cz);
|
||||
pxl8_chunk* py = pxl8_chunk_cache_get_vxl(cache, cx, cy + 1, cz);
|
||||
pxl8_chunk* nz = pxl8_chunk_cache_get_vxl(cache, cx, cy, cz - 1);
|
||||
pxl8_chunk* pz = pxl8_chunk_cache_get_vxl(cache, cx, cy, cz + 1);
|
||||
|
||||
const pxl8_voxel_chunk* neighbors[6] = {
|
||||
nx ? nx->voxel : NULL,
|
||||
px ? px->voxel : NULL,
|
||||
ny ? ny->voxel : NULL,
|
||||
py ? py->voxel : NULL,
|
||||
nz ? nz->voxel : NULL,
|
||||
pz ? pz->voxel : NULL
|
||||
};
|
||||
|
||||
entry->mesh = pxl8_voxel_build_mesh(entry->chunk->voxel, neighbors, registry, config);
|
||||
entry->mesh_dirty = false;
|
||||
|
||||
return entry->mesh;
|
||||
}
|
||||
|
||||
void pxl8_chunk_cache_tick(pxl8_chunk_cache* cache) {
|
||||
if (!cache) return;
|
||||
cache->frame_counter++;
|
||||
}
|
||||
|
||||
void pxl8_chunk_cache_evict_distant(pxl8_chunk_cache* cache,
|
||||
i32 cx, i32 cy, i32 cz, i32 radius) {
|
||||
if (!cache) return;
|
||||
|
||||
for (u32 i = 0; i < cache->entry_count; i++) {
|
||||
pxl8_chunk_cache_entry* e = &cache->entries[i];
|
||||
if (!e->valid || !e->chunk || e->chunk->type != PXL8_CHUNK_VXL) continue;
|
||||
|
||||
i32 dx = e->chunk->cx - cx;
|
||||
i32 dy = e->chunk->cy - cy;
|
||||
i32 dz = e->chunk->cz - cz;
|
||||
|
||||
if (abs(dx) > radius || abs(dy) > radius || abs(dz) > radius) {
|
||||
pxl8_chunk_destroy(e->chunk);
|
||||
if (e->mesh) pxl8_mesh_destroy(e->mesh);
|
||||
e->chunk = NULL;
|
||||
e->mesh = NULL;
|
||||
e->valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_chunk_cache_invalidate_meshes(pxl8_chunk_cache* cache) {
|
||||
if (!cache) return;
|
||||
|
||||
for (u32 i = 0; i < cache->entry_count; i++) {
|
||||
cache->entries[i].mesh_dirty = true;
|
||||
}
|
||||
}
|
||||
67
src/world/pxl8_chunk_cache.h
Normal file
67
src/world/pxl8_chunk_cache.h
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_chunk.h"
|
||||
#include "pxl8_mesh.h"
|
||||
#include "pxl8_protocol.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define PXL8_CHUNK_CACHE_SIZE 64
|
||||
#define PXL8_CHUNK_MAX_FRAGMENTS 64
|
||||
#define PXL8_CHUNK_MAX_DATA_SIZE 131072
|
||||
|
||||
typedef struct pxl8_chunk_cache_entry {
|
||||
pxl8_chunk* chunk;
|
||||
pxl8_mesh* mesh;
|
||||
u64 last_used;
|
||||
bool mesh_dirty;
|
||||
bool valid;
|
||||
} pxl8_chunk_cache_entry;
|
||||
|
||||
typedef struct pxl8_chunk_assembly {
|
||||
pxl8_chunk_type type;
|
||||
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_chunk_assembly;
|
||||
|
||||
typedef struct pxl8_chunk_cache {
|
||||
pxl8_chunk_cache_entry entries[PXL8_CHUNK_CACHE_SIZE];
|
||||
pxl8_chunk_assembly assembly;
|
||||
u32 entry_count;
|
||||
u64 frame_counter;
|
||||
} pxl8_chunk_cache;
|
||||
|
||||
pxl8_chunk_cache* pxl8_chunk_cache_create(void);
|
||||
void pxl8_chunk_cache_destroy(pxl8_chunk_cache* cache);
|
||||
|
||||
pxl8_result pxl8_chunk_cache_receive(pxl8_chunk_cache* cache,
|
||||
const pxl8_chunk_msg_header* hdr,
|
||||
const u8* payload, usize len);
|
||||
|
||||
pxl8_chunk* pxl8_chunk_cache_get_vxl(pxl8_chunk_cache* cache, i32 cx, i32 cy, i32 cz);
|
||||
pxl8_chunk* pxl8_chunk_cache_get_bsp(pxl8_chunk_cache* cache, u32 id);
|
||||
|
||||
pxl8_mesh* pxl8_chunk_cache_get_mesh(pxl8_chunk_cache* cache,
|
||||
i32 cx, i32 cy, i32 cz,
|
||||
const pxl8_block_registry* registry,
|
||||
const pxl8_voxel_mesh_config* config);
|
||||
|
||||
void pxl8_chunk_cache_tick(pxl8_chunk_cache* cache);
|
||||
void pxl8_chunk_cache_evict_distant(pxl8_chunk_cache* cache,
|
||||
i32 cx, i32 cy, i32 cz, i32 radius);
|
||||
void pxl8_chunk_cache_invalidate_meshes(pxl8_chunk_cache* cache);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
496
src/world/pxl8_entity.c
Normal file
496
src/world/pxl8_entity.c
Normal file
|
|
@ -0,0 +1,496 @@
|
|||
#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);
|
||||
}
|
||||
}
|
||||
}
|
||||
67
src/world/pxl8_entity.h
Normal file
67
src/world/pxl8_entity.h
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#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
|
||||
406
src/world/pxl8_voxel.c
Normal file
406
src/world/pxl8_voxel.c
Normal file
|
|
@ -0,0 +1,406 @@
|
|||
#include "pxl8_voxel.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_mem.h"
|
||||
|
||||
#define PXL8_VOXEL_CHUNK_VOLUME (PXL8_VOXEL_CHUNK_SIZE * PXL8_VOXEL_CHUNK_SIZE * PXL8_VOXEL_CHUNK_SIZE)
|
||||
|
||||
typedef struct pxl8_block_def {
|
||||
char name[32];
|
||||
u8 texture_id;
|
||||
pxl8_voxel_geometry geometry;
|
||||
bool registered;
|
||||
} pxl8_block_def;
|
||||
|
||||
struct pxl8_block_registry {
|
||||
pxl8_block_def blocks[PXL8_BLOCK_COUNT];
|
||||
};
|
||||
|
||||
struct pxl8_voxel_chunk {
|
||||
pxl8_block blocks[PXL8_VOXEL_CHUNK_VOLUME];
|
||||
};
|
||||
|
||||
static inline u32 voxel_index(i32 x, i32 y, i32 z) {
|
||||
return (u32)(x + y * PXL8_VOXEL_CHUNK_SIZE + z * PXL8_VOXEL_CHUNK_SIZE * PXL8_VOXEL_CHUNK_SIZE);
|
||||
}
|
||||
|
||||
static inline bool voxel_in_bounds(i32 x, i32 y, i32 z) {
|
||||
return x >= 0 && x < PXL8_VOXEL_CHUNK_SIZE &&
|
||||
y >= 0 && y < PXL8_VOXEL_CHUNK_SIZE &&
|
||||
z >= 0 && z < PXL8_VOXEL_CHUNK_SIZE;
|
||||
}
|
||||
|
||||
pxl8_voxel_chunk* pxl8_voxel_chunk_create(void) {
|
||||
pxl8_voxel_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_voxel_chunk));
|
||||
return chunk;
|
||||
}
|
||||
|
||||
void pxl8_voxel_chunk_clear(pxl8_voxel_chunk* chunk) {
|
||||
if (!chunk) return;
|
||||
memset(chunk->blocks, 0, sizeof(chunk->blocks));
|
||||
}
|
||||
|
||||
void pxl8_voxel_chunk_destroy(pxl8_voxel_chunk* chunk) {
|
||||
pxl8_free(chunk);
|
||||
}
|
||||
|
||||
pxl8_block pxl8_voxel_get(const pxl8_voxel_chunk* chunk, i32 x, i32 y, i32 z) {
|
||||
if (!chunk || !voxel_in_bounds(x, y, z)) return PXL8_BLOCK_AIR;
|
||||
return chunk->blocks[voxel_index(x, y, z)];
|
||||
}
|
||||
|
||||
void pxl8_voxel_set(pxl8_voxel_chunk* chunk, i32 x, i32 y, i32 z, pxl8_block block) {
|
||||
if (!chunk || !voxel_in_bounds(x, y, z)) return;
|
||||
chunk->blocks[voxel_index(x, y, z)] = block;
|
||||
}
|
||||
|
||||
void pxl8_voxel_fill(pxl8_voxel_chunk* chunk, pxl8_block block) {
|
||||
if (!chunk) return;
|
||||
memset(chunk->blocks, block, sizeof(chunk->blocks));
|
||||
}
|
||||
|
||||
pxl8_block_registry* pxl8_block_registry_create(void) {
|
||||
pxl8_block_registry* registry = pxl8_calloc(1, sizeof(pxl8_block_registry));
|
||||
return registry;
|
||||
}
|
||||
|
||||
void pxl8_block_registry_destroy(pxl8_block_registry* registry) {
|
||||
pxl8_free(registry);
|
||||
}
|
||||
|
||||
void pxl8_block_registry_register(pxl8_block_registry* registry, pxl8_block id, const char* name, u8 texture_id, pxl8_voxel_geometry geo) {
|
||||
if (!registry || id == PXL8_BLOCK_AIR) return;
|
||||
|
||||
pxl8_block_def* def = ®istry->blocks[id];
|
||||
strncpy(def->name, name ? name : "", sizeof(def->name) - 1);
|
||||
def->texture_id = texture_id;
|
||||
def->geometry = geo;
|
||||
def->registered = true;
|
||||
}
|
||||
|
||||
const char* pxl8_block_registry_name(const pxl8_block_registry* registry, pxl8_block id) {
|
||||
if (!registry || !registry->blocks[id].registered) return NULL;
|
||||
return registry->blocks[id].name;
|
||||
}
|
||||
|
||||
pxl8_voxel_geometry pxl8_block_registry_geometry(const pxl8_block_registry* registry, pxl8_block id) {
|
||||
if (!registry || !registry->blocks[id].registered) return PXL8_VOXEL_GEOMETRY_CUBE;
|
||||
return registry->blocks[id].geometry;
|
||||
}
|
||||
|
||||
u8 pxl8_block_registry_texture(const pxl8_block_registry* registry, pxl8_block id) {
|
||||
if (!registry || !registry->blocks[id].registered) return 0;
|
||||
return registry->blocks[id].texture_id;
|
||||
}
|
||||
|
||||
static bool block_is_opaque(pxl8_block block) {
|
||||
return block != PXL8_BLOCK_AIR;
|
||||
}
|
||||
|
||||
static pxl8_block get_block_or_neighbor(const pxl8_voxel_chunk* chunk, const pxl8_voxel_chunk** neighbors, i32 x, i32 y, i32 z) {
|
||||
if (voxel_in_bounds(x, y, z)) {
|
||||
return chunk->blocks[voxel_index(x, y, z)];
|
||||
}
|
||||
|
||||
i32 nx = x, ny = y, nz = z;
|
||||
i32 neighbor_idx = -1;
|
||||
|
||||
if (x < 0) { neighbor_idx = 0; nx = x + PXL8_VOXEL_CHUNK_SIZE; }
|
||||
else if (x >= PXL8_VOXEL_CHUNK_SIZE) { neighbor_idx = 1; nx = x - PXL8_VOXEL_CHUNK_SIZE; }
|
||||
else if (y < 0) { neighbor_idx = 2; ny = y + PXL8_VOXEL_CHUNK_SIZE; }
|
||||
else if (y >= PXL8_VOXEL_CHUNK_SIZE) { neighbor_idx = 3; ny = y - PXL8_VOXEL_CHUNK_SIZE; }
|
||||
else if (z < 0) { neighbor_idx = 4; nz = z + PXL8_VOXEL_CHUNK_SIZE; }
|
||||
else if (z >= PXL8_VOXEL_CHUNK_SIZE) { neighbor_idx = 5; nz = z - PXL8_VOXEL_CHUNK_SIZE; }
|
||||
|
||||
if (neighbor_idx >= 0 && neighbors && neighbors[neighbor_idx]) {
|
||||
return pxl8_voxel_get(neighbors[neighbor_idx], nx, ny, nz);
|
||||
}
|
||||
|
||||
return PXL8_BLOCK_AIR;
|
||||
}
|
||||
|
||||
static f32 compute_ao(const pxl8_voxel_chunk* chunk, const pxl8_voxel_chunk** neighbors,
|
||||
i32 x, i32 y, i32 z, i32 dx1, i32 dy1, i32 dz1, i32 dx2, i32 dy2, i32 dz2) {
|
||||
bool side1 = block_is_opaque(get_block_or_neighbor(chunk, neighbors, x + dx1, y + dy1, z + dz1));
|
||||
bool side2 = block_is_opaque(get_block_or_neighbor(chunk, neighbors, x + dx2, y + dy2, z + dz2));
|
||||
bool corner = block_is_opaque(get_block_or_neighbor(chunk, neighbors, x + dx1 + dx2, y + dy1 + dy2, z + dz1 + dz2));
|
||||
|
||||
if (side1 && side2) return 0.0f;
|
||||
return (3.0f - (f32)side1 - (f32)side2 - (f32)corner) / 3.0f;
|
||||
}
|
||||
|
||||
static void add_face_vertices(pxl8_mesh* mesh, const pxl8_voxel_mesh_config* config,
|
||||
pxl8_vec3 pos, i32 face, u8 texture_id,
|
||||
f32 ao0, f32 ao1, f32 ao2, f32 ao3) {
|
||||
static const pxl8_vec3 face_normals[6] = {
|
||||
{-1, 0, 0}, { 1, 0, 0},
|
||||
{ 0, -1, 0}, { 0, 1, 0},
|
||||
{ 0, 0, -1}, { 0, 0, 1}
|
||||
};
|
||||
|
||||
static const i32 face_vertices[6][4][3] = {
|
||||
{{0,0,1}, {0,0,0}, {0,1,0}, {0,1,1}},
|
||||
{{1,0,0}, {1,0,1}, {1,1,1}, {1,1,0}},
|
||||
{{0,0,0}, {1,0,0}, {1,0,1}, {0,0,1}},
|
||||
{{0,1,1}, {1,1,1}, {1,1,0}, {0,1,0}},
|
||||
{{0,0,0}, {0,1,0}, {1,1,0}, {1,0,0}},
|
||||
{{1,0,1}, {1,1,1}, {0,1,1}, {0,0,1}}
|
||||
};
|
||||
|
||||
static const f32 face_uvs[4][2] = {
|
||||
{0, 1}, {1, 1}, {1, 0}, {0, 0}
|
||||
};
|
||||
|
||||
f32 ao_values[4] = {ao0, ao1, ao2, ao3};
|
||||
f32 ao_strength = config->ambient_occlusion ? config->ao_strength : 0.0f;
|
||||
f32 tex_scale = config->texture_scale;
|
||||
|
||||
pxl8_vec3 normal = face_normals[face];
|
||||
u16 indices[4];
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
pxl8_vertex v = {0};
|
||||
v.position.x = pos.x + (f32)face_vertices[face][i][0];
|
||||
v.position.y = pos.y + (f32)face_vertices[face][i][1];
|
||||
v.position.z = pos.z + (f32)face_vertices[face][i][2];
|
||||
v.normal = normal;
|
||||
v.u = face_uvs[i][0] * tex_scale;
|
||||
v.v = face_uvs[i][1] * tex_scale;
|
||||
v.color = texture_id;
|
||||
v.light = (u8)(255.0f * (1.0f - ao_strength * (1.0f - ao_values[i])));
|
||||
|
||||
indices[i] = pxl8_mesh_push_vertex(mesh, v);
|
||||
}
|
||||
|
||||
if (ao0 + ao2 > ao1 + ao3) {
|
||||
pxl8_mesh_push_quad(mesh, indices[0], indices[1], indices[2], indices[3]);
|
||||
} else {
|
||||
pxl8_mesh_push_triangle(mesh, indices[0], indices[1], indices[2]);
|
||||
pxl8_mesh_push_triangle(mesh, indices[0], indices[2], indices[3]);
|
||||
}
|
||||
}
|
||||
|
||||
static void add_cube_faces(pxl8_mesh* mesh, const pxl8_voxel_chunk* chunk,
|
||||
const pxl8_voxel_chunk** neighbors, const pxl8_block_registry* registry,
|
||||
const pxl8_voxel_mesh_config* config,
|
||||
i32 x, i32 y, i32 z, pxl8_block block) {
|
||||
u8 texture_id = pxl8_block_registry_texture(registry, block);
|
||||
pxl8_vec3 pos = {(f32)x, (f32)y, (f32)z};
|
||||
|
||||
static const i32 face_dirs[6][3] = {
|
||||
{-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1}
|
||||
};
|
||||
|
||||
for (i32 face = 0; face < 6; face++) {
|
||||
i32 nx = x + face_dirs[face][0];
|
||||
i32 ny = y + face_dirs[face][1];
|
||||
i32 nz = z + face_dirs[face][2];
|
||||
|
||||
pxl8_block neighbor = get_block_or_neighbor(chunk, neighbors, nx, ny, nz);
|
||||
if (block_is_opaque(neighbor)) continue;
|
||||
|
||||
f32 ao[4] = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
|
||||
if (config->ambient_occlusion) {
|
||||
switch (face) {
|
||||
case 0:
|
||||
ao[0] = compute_ao(chunk, neighbors, x-1, y, z, 0, -1, 0, 0, 0, 1);
|
||||
ao[1] = compute_ao(chunk, neighbors, x-1, y, z, 0, -1, 0, 0, 0, -1);
|
||||
ao[2] = compute_ao(chunk, neighbors, x-1, y, z, 0, 1, 0, 0, 0, -1);
|
||||
ao[3] = compute_ao(chunk, neighbors, x-1, y, z, 0, 1, 0, 0, 0, 1);
|
||||
break;
|
||||
case 1:
|
||||
ao[0] = compute_ao(chunk, neighbors, x+1, y, z, 0, -1, 0, 0, 0, -1);
|
||||
ao[1] = compute_ao(chunk, neighbors, x+1, y, z, 0, -1, 0, 0, 0, 1);
|
||||
ao[2] = compute_ao(chunk, neighbors, x+1, y, z, 0, 1, 0, 0, 0, 1);
|
||||
ao[3] = compute_ao(chunk, neighbors, x+1, y, z, 0, 1, 0, 0, 0, -1);
|
||||
break;
|
||||
case 2:
|
||||
ao[0] = compute_ao(chunk, neighbors, x, y-1, z, -1, 0, 0, 0, 0, -1);
|
||||
ao[1] = compute_ao(chunk, neighbors, x, y-1, z, 1, 0, 0, 0, 0, -1);
|
||||
ao[2] = compute_ao(chunk, neighbors, x, y-1, z, 1, 0, 0, 0, 0, 1);
|
||||
ao[3] = compute_ao(chunk, neighbors, x, y-1, z, -1, 0, 0, 0, 0, 1);
|
||||
break;
|
||||
case 3:
|
||||
ao[0] = compute_ao(chunk, neighbors, x, y+1, z, -1, 0, 0, 0, 0, 1);
|
||||
ao[1] = compute_ao(chunk, neighbors, x, y+1, z, 1, 0, 0, 0, 0, 1);
|
||||
ao[2] = compute_ao(chunk, neighbors, x, y+1, z, 1, 0, 0, 0, 0, -1);
|
||||
ao[3] = compute_ao(chunk, neighbors, x, y+1, z, -1, 0, 0, 0, 0, -1);
|
||||
break;
|
||||
case 4:
|
||||
ao[0] = compute_ao(chunk, neighbors, x, y, z-1, -1, 0, 0, 0, 1, 0);
|
||||
ao[1] = compute_ao(chunk, neighbors, x, y, z-1, -1, 0, 0, 0, -1, 0);
|
||||
ao[2] = compute_ao(chunk, neighbors, x, y, z-1, 1, 0, 0, 0, -1, 0);
|
||||
ao[3] = compute_ao(chunk, neighbors, x, y, z-1, 1, 0, 0, 0, 1, 0);
|
||||
break;
|
||||
case 5:
|
||||
ao[0] = compute_ao(chunk, neighbors, x, y, z+1, 1, 0, 0, 0, -1, 0);
|
||||
ao[1] = compute_ao(chunk, neighbors, x, y, z+1, 1, 0, 0, 0, 1, 0);
|
||||
ao[2] = compute_ao(chunk, neighbors, x, y, z+1, -1, 0, 0, 0, 1, 0);
|
||||
ao[3] = compute_ao(chunk, neighbors, x, y, z+1, -1, 0, 0, 0, -1, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
add_face_vertices(mesh, config, pos, face, texture_id, ao[0], ao[1], ao[2], ao[3]);
|
||||
}
|
||||
}
|
||||
|
||||
static void add_slab_faces(pxl8_mesh* mesh, const pxl8_voxel_chunk* chunk,
|
||||
const pxl8_voxel_chunk** neighbors, const pxl8_block_registry* registry,
|
||||
const pxl8_voxel_mesh_config* config,
|
||||
i32 x, i32 y, i32 z, pxl8_block block, bool top) {
|
||||
u8 texture_id = pxl8_block_registry_texture(registry, block);
|
||||
f32 y_offset = top ? 0.5f : 0.0f;
|
||||
pxl8_vec3 pos = {(f32)x, (f32)y + y_offset, (f32)z};
|
||||
|
||||
static const i32 horiz_faces[4] = {0, 1, 4, 5};
|
||||
static const i32 face_dirs[6][3] = {
|
||||
{-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1}
|
||||
};
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
i32 face = horiz_faces[i];
|
||||
i32 nx = x + face_dirs[face][0];
|
||||
i32 nz = z + face_dirs[face][2];
|
||||
|
||||
pxl8_block neighbor = get_block_or_neighbor(chunk, neighbors, nx, y, nz);
|
||||
if (block_is_opaque(neighbor)) continue;
|
||||
|
||||
add_face_vertices(mesh, config, pos, face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
i32 top_face = 3;
|
||||
i32 bot_face = 2;
|
||||
|
||||
if (top) {
|
||||
pxl8_block above = get_block_or_neighbor(chunk, neighbors, x, y + 1, z);
|
||||
if (!block_is_opaque(above)) {
|
||||
add_face_vertices(mesh, config, pos, top_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
add_face_vertices(mesh, config, (pxl8_vec3){(f32)x, (f32)y + 0.5f, (f32)z}, bot_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
|
||||
} else {
|
||||
add_face_vertices(mesh, config, pos, top_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
|
||||
pxl8_block below = get_block_or_neighbor(chunk, neighbors, x, y - 1, z);
|
||||
if (!block_is_opaque(below)) {
|
||||
add_face_vertices(mesh, config, pos, bot_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void add_slope_faces(pxl8_mesh* mesh, const pxl8_voxel_chunk* chunk,
|
||||
const pxl8_voxel_chunk** neighbors, const pxl8_block_registry* registry,
|
||||
const pxl8_voxel_mesh_config* config,
|
||||
i32 x, i32 y, i32 z, pxl8_block block, i32 direction) {
|
||||
u8 texture_id = pxl8_block_registry_texture(registry, block);
|
||||
pxl8_vec3 pos = {(f32)x, (f32)y, (f32)z};
|
||||
|
||||
pxl8_block below = get_block_or_neighbor(chunk, neighbors, x, y - 1, z);
|
||||
if (!block_is_opaque(below)) {
|
||||
add_face_vertices(mesh, config, pos, 2, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
static const i32 dir_to_back_face[4] = {5, 4, 1, 0};
|
||||
i32 back_face = dir_to_back_face[direction];
|
||||
|
||||
static const i32 face_dirs[6][3] = {
|
||||
{-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1}
|
||||
};
|
||||
|
||||
i32 bx = x + face_dirs[back_face][0];
|
||||
i32 bz = z + face_dirs[back_face][2];
|
||||
pxl8_block back_neighbor = get_block_or_neighbor(chunk, neighbors, bx, y, bz);
|
||||
if (!block_is_opaque(back_neighbor)) {
|
||||
add_face_vertices(mesh, config, pos, back_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
pxl8_vertex verts[4];
|
||||
memset(verts, 0, sizeof(verts));
|
||||
|
||||
static const f32 slope_verts[4][4][3] = {
|
||||
{{0,0,0}, {1,0,0}, {1,1,1}, {0,1,1}},
|
||||
{{0,0,1}, {1,0,1}, {1,1,0}, {0,1,0}},
|
||||
{{1,0,0}, {1,0,1}, {0,1,1}, {0,1,0}},
|
||||
{{0,0,0}, {0,0,1}, {1,1,1}, {1,1,0}}
|
||||
};
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
verts[i].position.x = pos.x + slope_verts[direction][i][0];
|
||||
verts[i].position.y = pos.y + slope_verts[direction][i][1];
|
||||
verts[i].position.z = pos.z + slope_verts[direction][i][2];
|
||||
verts[i].color = texture_id;
|
||||
verts[i].light = 255;
|
||||
}
|
||||
|
||||
static const pxl8_vec3 slope_normals[4] = {
|
||||
{0, 0.707f, -0.707f}, {0, 0.707f, 0.707f},
|
||||
{-0.707f, 0.707f, 0}, {0.707f, 0.707f, 0}
|
||||
};
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
verts[i].normal = slope_normals[direction];
|
||||
}
|
||||
|
||||
u16 i0 = pxl8_mesh_push_vertex(mesh, verts[0]);
|
||||
u16 i1 = pxl8_mesh_push_vertex(mesh, verts[1]);
|
||||
u16 i2 = pxl8_mesh_push_vertex(mesh, verts[2]);
|
||||
u16 i3 = pxl8_mesh_push_vertex(mesh, verts[3]);
|
||||
pxl8_mesh_push_quad(mesh, i0, i1, i2, i3);
|
||||
}
|
||||
|
||||
pxl8_mesh* pxl8_voxel_build_mesh(const pxl8_voxel_chunk* chunk,
|
||||
const pxl8_voxel_chunk** neighbors,
|
||||
const pxl8_block_registry* registry,
|
||||
const pxl8_voxel_mesh_config* config) {
|
||||
if (!chunk || !registry) return NULL;
|
||||
|
||||
pxl8_voxel_mesh_config cfg = config ? *config : PXL8_VOXEL_MESH_CONFIG_DEFAULT;
|
||||
|
||||
u32 max_faces = PXL8_VOXEL_CHUNK_VOLUME * 6;
|
||||
pxl8_mesh* mesh = pxl8_mesh_create(max_faces * 4, max_faces * 6);
|
||||
if (!mesh) return NULL;
|
||||
|
||||
for (i32 z = 0; z < PXL8_VOXEL_CHUNK_SIZE; z++) {
|
||||
for (i32 y = 0; y < PXL8_VOXEL_CHUNK_SIZE; y++) {
|
||||
for (i32 x = 0; x < PXL8_VOXEL_CHUNK_SIZE; x++) {
|
||||
pxl8_block block = chunk->blocks[voxel_index(x, y, z)];
|
||||
if (block == PXL8_BLOCK_AIR) continue;
|
||||
|
||||
pxl8_voxel_geometry geo = pxl8_block_registry_geometry(registry, block);
|
||||
|
||||
switch (geo) {
|
||||
case PXL8_VOXEL_GEOMETRY_CUBE:
|
||||
add_cube_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block);
|
||||
break;
|
||||
case PXL8_VOXEL_GEOMETRY_SLAB_BOTTOM:
|
||||
add_slab_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, false);
|
||||
break;
|
||||
case PXL8_VOXEL_GEOMETRY_SLAB_TOP:
|
||||
add_slab_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, true);
|
||||
break;
|
||||
case PXL8_VOXEL_GEOMETRY_SLOPE_NORTH:
|
||||
add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 0);
|
||||
break;
|
||||
case PXL8_VOXEL_GEOMETRY_SLOPE_SOUTH:
|
||||
add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 1);
|
||||
break;
|
||||
case PXL8_VOXEL_GEOMETRY_SLOPE_EAST:
|
||||
add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 2);
|
||||
break;
|
||||
case PXL8_VOXEL_GEOMETRY_SLOPE_WEST:
|
||||
add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 3);
|
||||
break;
|
||||
case PXL8_VOXEL_GEOMETRY_STAIRS_NORTH:
|
||||
case PXL8_VOXEL_GEOMETRY_STAIRS_SOUTH:
|
||||
case PXL8_VOXEL_GEOMETRY_STAIRS_EAST:
|
||||
case PXL8_VOXEL_GEOMETRY_STAIRS_WEST:
|
||||
add_cube_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
67
src/world/pxl8_voxel.h
Normal file
67
src/world/pxl8_voxel.h
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_mesh.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define PXL8_VOXEL_CHUNK_SIZE 32
|
||||
#define PXL8_BLOCK_COUNT 256
|
||||
|
||||
typedef struct pxl8_voxel_chunk pxl8_voxel_chunk;
|
||||
typedef struct pxl8_block_registry pxl8_block_registry;
|
||||
typedef u8 pxl8_block;
|
||||
|
||||
#define PXL8_BLOCK_AIR 0
|
||||
|
||||
typedef enum pxl8_voxel_geometry {
|
||||
PXL8_VOXEL_GEOMETRY_CUBE,
|
||||
PXL8_VOXEL_GEOMETRY_SLAB_BOTTOM,
|
||||
PXL8_VOXEL_GEOMETRY_SLAB_TOP,
|
||||
PXL8_VOXEL_GEOMETRY_SLOPE_NORTH,
|
||||
PXL8_VOXEL_GEOMETRY_SLOPE_SOUTH,
|
||||
PXL8_VOXEL_GEOMETRY_SLOPE_EAST,
|
||||
PXL8_VOXEL_GEOMETRY_SLOPE_WEST,
|
||||
PXL8_VOXEL_GEOMETRY_STAIRS_NORTH,
|
||||
PXL8_VOXEL_GEOMETRY_STAIRS_SOUTH,
|
||||
PXL8_VOXEL_GEOMETRY_STAIRS_EAST,
|
||||
PXL8_VOXEL_GEOMETRY_STAIRS_WEST
|
||||
} pxl8_voxel_geometry;
|
||||
|
||||
typedef struct pxl8_voxel_mesh_config {
|
||||
bool ambient_occlusion;
|
||||
f32 ao_strength;
|
||||
f32 texture_scale;
|
||||
} pxl8_voxel_mesh_config;
|
||||
|
||||
#define PXL8_VOXEL_MESH_CONFIG_DEFAULT ((pxl8_voxel_mesh_config){ \
|
||||
.ambient_occlusion = true, \
|
||||
.ao_strength = 0.2f, \
|
||||
.texture_scale = 1.0f \
|
||||
})
|
||||
|
||||
pxl8_voxel_chunk* pxl8_voxel_chunk_create(void);
|
||||
void pxl8_voxel_chunk_clear(pxl8_voxel_chunk* chunk);
|
||||
void pxl8_voxel_chunk_destroy(pxl8_voxel_chunk* chunk);
|
||||
|
||||
pxl8_block pxl8_voxel_get(const pxl8_voxel_chunk* chunk, i32 x, i32 y, i32 z);
|
||||
void pxl8_voxel_set(pxl8_voxel_chunk* chunk, i32 x, i32 y, i32 z, pxl8_block block);
|
||||
void pxl8_voxel_fill(pxl8_voxel_chunk* chunk, pxl8_block block);
|
||||
|
||||
pxl8_block_registry* pxl8_block_registry_create(void);
|
||||
void pxl8_block_registry_destroy(pxl8_block_registry* registry);
|
||||
void pxl8_block_registry_register(pxl8_block_registry* registry, pxl8_block id, const char* name, u8 texture_id, pxl8_voxel_geometry geo);
|
||||
const char* pxl8_block_registry_name(const pxl8_block_registry* registry, pxl8_block id);
|
||||
pxl8_voxel_geometry pxl8_block_registry_geometry(const pxl8_block_registry* registry, pxl8_block id);
|
||||
u8 pxl8_block_registry_texture(const pxl8_block_registry* registry, pxl8_block id);
|
||||
|
||||
pxl8_mesh* pxl8_voxel_build_mesh(const pxl8_voxel_chunk* chunk,
|
||||
const pxl8_voxel_chunk** neighbors,
|
||||
const pxl8_block_registry* registry,
|
||||
const pxl8_voxel_mesh_config* config);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
@ -1,413 +1,125 @@
|
|||
#include "pxl8_world.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "pxl8_bsp.h"
|
||||
#include "pxl8_gen.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_math.h"
|
||||
#include "pxl8_mem.h"
|
||||
|
||||
#define PXL8_WORLD_ENTITY_CAPACITY 256
|
||||
|
||||
struct pxl8_world {
|
||||
pxl8_bsp bsp;
|
||||
bool loaded;
|
||||
pxl8_chunk* active_chunk;
|
||||
pxl8_block_registry* block_registry;
|
||||
pxl8_chunk_cache* chunk_cache;
|
||||
pxl8_entity_pool* entities;
|
||||
};
|
||||
|
||||
pxl8_world* pxl8_world_create(void) {
|
||||
pxl8_world* world = (pxl8_world*)pxl8_calloc(1, sizeof(pxl8_world));
|
||||
if (!world) {
|
||||
pxl8_error("Failed to allocate world");
|
||||
pxl8_world* world = pxl8_calloc(1, sizeof(pxl8_world));
|
||||
if (!world) return NULL;
|
||||
|
||||
world->block_registry = pxl8_block_registry_create();
|
||||
world->chunk_cache = pxl8_chunk_cache_create();
|
||||
world->entities = pxl8_entity_pool_create(PXL8_WORLD_ENTITY_CAPACITY);
|
||||
|
||||
if (!world->block_registry || !world->chunk_cache || !world->entities) {
|
||||
pxl8_world_destroy(world);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
world->loaded = false;
|
||||
|
||||
return world;
|
||||
}
|
||||
|
||||
void pxl8_world_destroy(pxl8_world* world) {
|
||||
if (!world) return;
|
||||
|
||||
if (world->loaded) {
|
||||
pxl8_bsp_destroy(&world->bsp);
|
||||
}
|
||||
|
||||
pxl8_block_registry_destroy(world->block_registry);
|
||||
pxl8_chunk_cache_destroy(world->chunk_cache);
|
||||
pxl8_entity_pool_destroy(world->entities);
|
||||
pxl8_free(world);
|
||||
}
|
||||
|
||||
pxl8_result pxl8_world_generate(pxl8_world* world, pxl8_gfx* gfx, const pxl8_procgen_params* params) {
|
||||
if (!world || !gfx || !params) {
|
||||
pxl8_error("Invalid arguments to pxl8_world_generate");
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (world->loaded) {
|
||||
pxl8_bsp_destroy(&world->bsp);
|
||||
world->loaded = false;
|
||||
}
|
||||
|
||||
memset(&world->bsp, 0, sizeof(pxl8_bsp));
|
||||
|
||||
pxl8_result result = pxl8_procgen(&world->bsp, params);
|
||||
if (result != PXL8_OK) {
|
||||
pxl8_error("Failed to generate world: %d", result);
|
||||
pxl8_bsp_destroy(&world->bsp);
|
||||
return result;
|
||||
}
|
||||
|
||||
world->loaded = true;
|
||||
return PXL8_OK;
|
||||
pxl8_chunk_cache* pxl8_world_chunk_cache(pxl8_world* world) {
|
||||
if (!world) return NULL;
|
||||
return world->chunk_cache;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_world_load(pxl8_world* world, const char* path) {
|
||||
if (!world || !path) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
if (world->loaded) {
|
||||
pxl8_bsp_destroy(&world->bsp);
|
||||
world->loaded = false;
|
||||
}
|
||||
|
||||
memset(&world->bsp, 0, sizeof(pxl8_bsp));
|
||||
|
||||
pxl8_result result = pxl8_bsp_load(path, &world->bsp);
|
||||
if (result != PXL8_OK) {
|
||||
pxl8_error("Failed to load world: %s", path);
|
||||
return result;
|
||||
}
|
||||
|
||||
world->loaded = true;
|
||||
pxl8_info("Loaded world: %s", path);
|
||||
|
||||
return PXL8_OK;
|
||||
pxl8_chunk* pxl8_world_active_chunk(pxl8_world* world) {
|
||||
if (!world) return NULL;
|
||||
return world->active_chunk;
|
||||
}
|
||||
|
||||
void pxl8_world_unload(pxl8_world* world) {
|
||||
if (!world || !world->loaded) return;
|
||||
|
||||
pxl8_bsp_destroy(&world->bsp);
|
||||
world->loaded = false;
|
||||
void pxl8_world_set_active_chunk(pxl8_world* world, pxl8_chunk* chunk) {
|
||||
if (!world) return;
|
||||
world->active_chunk = chunk;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_world_apply_textures(pxl8_world* world, const pxl8_world_texture* textures, u32 count) {
|
||||
if (!world || !world->loaded || !textures || count == 0) {
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
pxl8_block_registry* pxl8_world_block_registry(pxl8_world* world) {
|
||||
if (!world) return NULL;
|
||||
return world->block_registry;
|
||||
}
|
||||
|
||||
pxl8_bsp* bsp = &world->bsp;
|
||||
pxl8_entity_pool* pxl8_world_entities(pxl8_world* world) {
|
||||
if (!world) return NULL;
|
||||
return world->entities;
|
||||
}
|
||||
|
||||
u32 max_materials = count * 6;
|
||||
bsp->materials = pxl8_calloc(max_materials, sizeof(pxl8_gfx_material));
|
||||
if (!bsp->materials) {
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
bsp->num_materials = 0;
|
||||
|
||||
for (u32 face_idx = 0; face_idx < bsp->num_faces; face_idx++) {
|
||||
pxl8_bsp_face* face = &bsp->faces[face_idx];
|
||||
pxl8_vec3 normal = bsp->planes[face->plane_id].normal;
|
||||
|
||||
u32 matched_texture_idx = count;
|
||||
for (u32 tex_idx = 0; tex_idx < count; tex_idx++) {
|
||||
if (textures[tex_idx].rule && textures[tex_idx].rule(&normal, face, bsp)) {
|
||||
matched_texture_idx = tex_idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matched_texture_idx >= count) {
|
||||
pxl8_warn("No texture rule matched for face %u", face_idx);
|
||||
continue;
|
||||
}
|
||||
|
||||
const pxl8_world_texture* matched = &textures[matched_texture_idx];
|
||||
|
||||
pxl8_vec3 u_axis, v_axis;
|
||||
if (fabsf(normal.y) > 0.9f) {
|
||||
u_axis = (pxl8_vec3){1.0f, 0.0f, 0.0f};
|
||||
v_axis = (pxl8_vec3){0.0f, 0.0f, 1.0f};
|
||||
} else if (fabsf(normal.x) > 0.7f) {
|
||||
u_axis = (pxl8_vec3){0.0f, 1.0f, 0.0f};
|
||||
v_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};
|
||||
}
|
||||
|
||||
u32 material_idx = bsp->num_materials;
|
||||
bool found_existing = false;
|
||||
for (u32 i = 0; i < bsp->num_materials; i++) {
|
||||
if (strcmp(bsp->materials[i].name, matched->name) == 0 &&
|
||||
bsp->materials[i].texture_id == matched->texture_id &&
|
||||
bsp->materials[i].u_axis.x == u_axis.x &&
|
||||
bsp->materials[i].u_axis.y == u_axis.y &&
|
||||
bsp->materials[i].u_axis.z == u_axis.z &&
|
||||
bsp->materials[i].v_axis.x == v_axis.x &&
|
||||
bsp->materials[i].v_axis.y == v_axis.y &&
|
||||
bsp->materials[i].v_axis.z == v_axis.z) {
|
||||
material_idx = i;
|
||||
found_existing = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_existing) {
|
||||
if (bsp->num_materials >= max_materials) {
|
||||
pxl8_error("Too many unique material entries");
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
pxl8_gfx_material* mat = &bsp->materials[material_idx];
|
||||
memcpy(mat->name, matched->name, sizeof(mat->name));
|
||||
mat->name[sizeof(mat->name) - 1] = '\0';
|
||||
mat->texture_id = matched->texture_id;
|
||||
mat->u_offset = 0.0f;
|
||||
mat->v_offset = 0.0f;
|
||||
mat->u_axis = u_axis;
|
||||
mat->v_axis = v_axis;
|
||||
mat->alpha = 255;
|
||||
mat->dither = true;
|
||||
mat->double_sided = true;
|
||||
mat->dynamic_lighting = true;
|
||||
|
||||
bsp->num_materials++;
|
||||
}
|
||||
|
||||
face->material_id = material_idx;
|
||||
}
|
||||
|
||||
pxl8_info("Applied %u textures to %u faces, created %u materials",
|
||||
count, bsp->num_faces, bsp->num_materials);
|
||||
|
||||
return PXL8_OK;
|
||||
pxl8_entity pxl8_world_spawn(pxl8_world* world) {
|
||||
if (!world || !world->entities) return PXL8_ENTITY_INVALID;
|
||||
return pxl8_entity_spawn(world->entities);
|
||||
}
|
||||
|
||||
bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, f32 radius) {
|
||||
if (!world || !world->loaded) return false;
|
||||
(void)radius;
|
||||
|
||||
const pxl8_bsp* bsp = &world->bsp;
|
||||
if (!world || !world->active_chunk) return false;
|
||||
|
||||
for (u32 i = 0; i < bsp->num_faces; i++) {
|
||||
const pxl8_bsp_face* face = &bsp->faces[i];
|
||||
const pxl8_bsp_plane* plane = &bsp->planes[face->plane_id];
|
||||
|
||||
if (fabsf(plane->normal.y) > 0.7f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
f32 dist = plane->normal.x * pos.x +
|
||||
plane->normal.y * pos.y +
|
||||
plane->normal.z * pos.z - plane->dist;
|
||||
|
||||
if (fabsf(dist) > radius) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pxl8_vec3 closest_point = {
|
||||
pos.x - plane->normal.x * dist,
|
||||
pos.y - plane->normal.y * dist,
|
||||
pos.z - plane->normal.z * dist
|
||||
};
|
||||
|
||||
if (closest_point.x < face->aabb_min.x - radius || closest_point.x > face->aabb_max.x + radius ||
|
||||
closest_point.y < face->aabb_min.y - radius || closest_point.y > face->aabb_max.y + radius ||
|
||||
closest_point.z < face->aabb_min.z - radius || closest_point.z > face->aabb_max.z + radius) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return true;
|
||||
if (world->active_chunk->type == PXL8_CHUNK_BSP && world->active_chunk->bsp) {
|
||||
return pxl8_bsp_point_solid(world->active_chunk->bsp, pos);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool pxl8_world_is_loaded(const pxl8_world* world) {
|
||||
return world && world->loaded;
|
||||
pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius) {
|
||||
if (!world || !world->active_chunk) return to;
|
||||
|
||||
if (world->active_chunk->type == PXL8_CHUNK_BSP && world->active_chunk->bsp) {
|
||||
return pxl8_bsp_trace(world->active_chunk->bsp, from, to, radius);
|
||||
}
|
||||
|
||||
return to;
|
||||
}
|
||||
|
||||
pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius) {
|
||||
if (!world || !world->loaded) return to;
|
||||
void pxl8_world_update(pxl8_world* world, f32 dt) {
|
||||
(void)dt;
|
||||
if (!world) return;
|
||||
|
||||
const pxl8_bsp* bsp = &world->bsp;
|
||||
pxl8_vec3 pos = to;
|
||||
pxl8_vec3 original_velocity = {to.x - from.x, to.y - from.y, to.z - from.z};
|
||||
|
||||
pxl8_vec3 clip_planes[5];
|
||||
u32 num_planes = 0;
|
||||
|
||||
const f32 edge_epsilon = 1.2f;
|
||||
const f32 radius_min = -radius + edge_epsilon;
|
||||
const f32 radius_max = radius - edge_epsilon;
|
||||
|
||||
for (i32 iteration = 0; iteration < 4; iteration++) {
|
||||
bool collided = false;
|
||||
|
||||
for (u32 i = 0; i < bsp->num_faces; i++) {
|
||||
const pxl8_bsp_face* face = &bsp->faces[i];
|
||||
const pxl8_bsp_plane* plane = &bsp->planes[face->plane_id];
|
||||
|
||||
if (fabsf(plane->normal.y) > 0.7f) continue;
|
||||
|
||||
f32 dist = plane->normal.x * pos.x +
|
||||
plane->normal.y * pos.y +
|
||||
plane->normal.z * pos.z - plane->dist;
|
||||
|
||||
f32 abs_dist = fabsf(dist);
|
||||
if (abs_dist > radius) continue;
|
||||
|
||||
pxl8_vec3 closest_point = {
|
||||
pos.x - plane->normal.x * dist,
|
||||
pos.y - plane->normal.y * dist,
|
||||
pos.z - plane->normal.z * dist
|
||||
};
|
||||
|
||||
if (closest_point.x < face->aabb_min.x + radius_min || closest_point.x > face->aabb_max.x + radius_max ||
|
||||
closest_point.y < face->aabb_min.y + radius_min || closest_point.y > face->aabb_max.y + radius_max ||
|
||||
closest_point.z < face->aabb_min.z + radius_min || closest_point.z > face->aabb_max.z + radius_max) {
|
||||
continue;
|
||||
}
|
||||
|
||||
f32 penetration = radius - abs_dist;
|
||||
if (penetration > 0.01f) {
|
||||
pxl8_vec3 push_dir;
|
||||
if (dist < 0) {
|
||||
push_dir.x = -plane->normal.x;
|
||||
push_dir.y = -plane->normal.y;
|
||||
push_dir.z = -plane->normal.z;
|
||||
} else {
|
||||
push_dir.x = plane->normal.x;
|
||||
push_dir.y = plane->normal.y;
|
||||
push_dir.z = plane->normal.z;
|
||||
}
|
||||
|
||||
bool is_new_plane = true;
|
||||
for (u32 p = 0; p < num_planes; p++) {
|
||||
if (pxl8_vec3_dot(push_dir, clip_planes[p]) > 0.99f) {
|
||||
is_new_plane = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_new_plane && num_planes < 5) {
|
||||
clip_planes[num_planes++] = push_dir;
|
||||
}
|
||||
|
||||
pos.x += push_dir.x * penetration;
|
||||
pos.y += push_dir.y * penetration;
|
||||
pos.z += push_dir.z * penetration;
|
||||
|
||||
collided = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!collided) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (num_planes >= 3) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (num_planes == 2) {
|
||||
f32 orig_vel_len_sq = original_velocity.x * original_velocity.x +
|
||||
original_velocity.y * original_velocity.y +
|
||||
original_velocity.z * original_velocity.z;
|
||||
|
||||
if (orig_vel_len_sq > 0.000001f) {
|
||||
f32 vdot0 = pxl8_vec3_dot(original_velocity, clip_planes[0]);
|
||||
f32 vdot1 = pxl8_vec3_dot(original_velocity, clip_planes[1]);
|
||||
f32 dot0 = fabsf(vdot0);
|
||||
f32 dot1 = fabsf(vdot1);
|
||||
|
||||
pxl8_vec3 slide_vel;
|
||||
if (dot0 < dot1) {
|
||||
slide_vel.x = original_velocity.x - clip_planes[0].x * vdot0;
|
||||
slide_vel.y = original_velocity.y - clip_planes[0].y * vdot0;
|
||||
slide_vel.z = original_velocity.z - clip_planes[0].z * vdot0;
|
||||
} else {
|
||||
slide_vel.x = original_velocity.x - clip_planes[1].x * vdot1;
|
||||
slide_vel.y = original_velocity.y - clip_planes[1].y * vdot1;
|
||||
slide_vel.z = original_velocity.z - clip_planes[1].z * vdot1;
|
||||
}
|
||||
|
||||
f32 slide_len = sqrtf(slide_vel.x * slide_vel.x + slide_vel.y * slide_vel.y + slide_vel.z * slide_vel.z);
|
||||
|
||||
if (slide_len > 0.01f) {
|
||||
pos.x += slide_vel.x;
|
||||
pos.y += slide_vel.y;
|
||||
pos.z += slide_vel.z;
|
||||
|
||||
pxl8_vec3 crease_dir = pxl8_vec3_cross(clip_planes[0], clip_planes[1]);
|
||||
f32 crease_len = pxl8_vec3_length(crease_dir);
|
||||
if (crease_len > 0.01f && fabsf(crease_dir.y / crease_len) > 0.9f) {
|
||||
f32 bias0 = pxl8_vec3_dot(original_velocity, clip_planes[0]);
|
||||
f32 bias1 = pxl8_vec3_dot(original_velocity, clip_planes[1]);
|
||||
|
||||
if (bias0 < 0 && bias1 < 0) {
|
||||
const f32 corner_push = 0.1f;
|
||||
pxl8_vec3 push_away = {
|
||||
clip_planes[0].x + clip_planes[1].x,
|
||||
clip_planes[0].y + clip_planes[1].y,
|
||||
clip_planes[0].z + clip_planes[1].z
|
||||
};
|
||||
f32 push_len_sq = push_away.x * push_away.x + push_away.y * push_away.y + push_away.z * push_away.z;
|
||||
if (push_len_sq > 0.000001f) {
|
||||
f32 inv_push_len = corner_push / sqrtf(push_len_sq);
|
||||
pos.x += push_away.x * inv_push_len;
|
||||
pos.y += push_away.y * inv_push_len;
|
||||
pos.z += push_away.z * inv_push_len;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f32 horizontal_movement_sq = (pos.x - from.x) * (pos.x - from.x) +
|
||||
(pos.z - from.z) * (pos.z - from.z);
|
||||
f32 desired_horizontal_sq = (to.x - from.x) * (to.x - from.x) +
|
||||
(to.z - from.z) * (to.z - from.z);
|
||||
|
||||
if (desired_horizontal_sq > 0.01f && horizontal_movement_sq < desired_horizontal_sq * 0.25f) {
|
||||
const f32 max_step_height = 0.4f;
|
||||
|
||||
pxl8_vec3 step_up = pos;
|
||||
step_up.y += max_step_height;
|
||||
|
||||
if (!pxl8_world_check_collision(world, step_up, radius)) {
|
||||
pxl8_vec3 step_forward = {
|
||||
step_up.x + (to.x - pos.x),
|
||||
step_up.y,
|
||||
step_up.z + (to.z - pos.z)
|
||||
};
|
||||
|
||||
if (!pxl8_world_check_collision(world, step_forward, radius)) {
|
||||
pos = step_forward;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pos;
|
||||
pxl8_chunk_cache_tick(world->chunk_cache);
|
||||
}
|
||||
|
||||
void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) {
|
||||
if (!world || !gfx || !world->loaded) {
|
||||
static int count = 0;
|
||||
if (count++ < 10) {
|
||||
pxl8_debug("world_render: early return - world=%p, gfx=%p, loaded=%d",
|
||||
(void*)world, (void*)gfx, world ? world->loaded : -1);
|
||||
if (!world || !gfx || !world->active_chunk) return;
|
||||
|
||||
if (world->active_chunk->type == PXL8_CHUNK_BSP && world->active_chunk->bsp) {
|
||||
pxl8_bsp_render(gfx, world->active_chunk->bsp, camera_pos);
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_world_sync(pxl8_world* world, pxl8_net* net) {
|
||||
if (!world || !net) return;
|
||||
|
||||
u8 chunk_type = pxl8_net_chunk_type(net);
|
||||
u32 chunk_id = pxl8_net_chunk_id(net);
|
||||
|
||||
if (chunk_type == PXL8_CHUNK_TYPE_BSP && chunk_id != 0) {
|
||||
pxl8_chunk* chunk = pxl8_chunk_cache_get_bsp(world->chunk_cache, chunk_id);
|
||||
if (chunk && chunk->bsp) {
|
||||
if (world->active_chunk != chunk) {
|
||||
world->active_chunk = chunk;
|
||||
pxl8_debug("[CLIENT] Synced BSP chunk id=%u as active (verts=%u faces=%u)",
|
||||
chunk_id, chunk->bsp->num_vertices, chunk->bsp->num_faces);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
pxl8_bsp_render(gfx, &world->bsp, camera_pos);
|
||||
}
|
||||
|
||||
void pxl8_world_set_wireframe(pxl8_world* world, bool enabled) {
|
||||
if (!world || !world->loaded) return;
|
||||
|
||||
for (u32 i = 0; i < world->bsp.num_materials; i++) {
|
||||
world->bsp.materials[i].wireframe = enabled;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_bsp.h"
|
||||
#include "pxl8_gen.h"
|
||||
#include "pxl8_chunk.h"
|
||||
#include "pxl8_chunk_cache.h"
|
||||
#include "pxl8_entity.h"
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_math.h"
|
||||
#include "pxl8_net.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_world pxl8_world;
|
||||
|
||||
typedef bool (*pxl8_texture_rule)(const pxl8_vec3* normal, const pxl8_bsp_face* face, const pxl8_bsp* bsp);
|
||||
|
||||
typedef struct pxl8_world_texture {
|
||||
char name[16];
|
||||
u32 texture_id;
|
||||
pxl8_texture_rule rule;
|
||||
} pxl8_world_texture;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct pxl8_world pxl8_world;
|
||||
|
||||
pxl8_world* pxl8_world_create(void);
|
||||
void pxl8_world_destroy(pxl8_world* world);
|
||||
|
||||
pxl8_result pxl8_world_generate(pxl8_world* world, pxl8_gfx* gfx, const pxl8_procgen_params* params);
|
||||
pxl8_result pxl8_world_load(pxl8_world* world, const char* path);
|
||||
void pxl8_world_unload(pxl8_world* world);
|
||||
pxl8_chunk_cache* pxl8_world_chunk_cache(pxl8_world* world);
|
||||
pxl8_chunk* pxl8_world_active_chunk(pxl8_world* world);
|
||||
void pxl8_world_set_active_chunk(pxl8_world* world, pxl8_chunk* chunk);
|
||||
|
||||
pxl8_block_registry* pxl8_world_block_registry(pxl8_world* world);
|
||||
pxl8_entity_pool* pxl8_world_entities(pxl8_world* world);
|
||||
pxl8_entity pxl8_world_spawn(pxl8_world* world);
|
||||
|
||||
pxl8_result pxl8_world_apply_textures(pxl8_world* world, const pxl8_world_texture* textures, u32 count);
|
||||
bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, f32 radius);
|
||||
bool pxl8_world_is_loaded(const pxl8_world* world);
|
||||
void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);
|
||||
pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius);
|
||||
void pxl8_world_set_wireframe(pxl8_world* world, bool enabled);
|
||||
|
||||
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);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue