stream world data from pxl8d to pxl8

This commit is contained in:
asrael 2026-01-25 09:26:30 -06:00
parent 39ee0fefb7
commit a71a9840b2
55 changed files with 5290 additions and 2131 deletions

View file

@ -55,6 +55,10 @@ static const char embed_pxl8_particles[] = {
#embed "src/lua/pxl8/particles.lua"
, 0 };
static const char embed_pxl8_procgen[] = {
#embed "src/lua/pxl8/procgen.lua"
, 0 };
static const char embed_pxl8_sfx[] = {
#embed "src/lua/pxl8/sfx.lua"
, 0 };
@ -89,6 +93,7 @@ static const pxl8_embed pxl8_embeds[] = {
PXL8_EMBED_ENTRY(embed_pxl8_math, "pxl8.math"),
PXL8_EMBED_ENTRY(embed_pxl8_net, "pxl8.net"),
PXL8_EMBED_ENTRY(embed_pxl8_particles, "pxl8.particles"),
PXL8_EMBED_ENTRY(embed_pxl8_procgen, "pxl8.procgen"),
PXL8_EMBED_ENTRY(embed_pxl8_sfx, "pxl8.sfx"),
PXL8_EMBED_ENTRY(embed_pxl8_tilemap, "pxl8.tilemap"),
PXL8_EMBED_ENTRY(embed_pxl8_transition, "pxl8.transition"),

View file

@ -237,6 +237,19 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
pxl8_rng_seed(&game->rng, (u32)sys->hal->get_ticks());
game->world = pxl8_world_create();
if (!game->world) {
pxl8_error("failed to create world");
return PXL8_ERROR_INITIALIZATION_FAILED;
}
pxl8_net_config net_cfg = { .address = "127.0.0.1", .port = 7777 };
game->net = pxl8_net_create(&net_cfg);
if (game->net) {
pxl8_net_set_chunk_cache(game->net, pxl8_world_chunk_cache(game->world));
pxl8_net_connect(game->net);
}
#ifndef NDEBUG
game->debug_replay = pxl8_replay_create_buffer(60, 60);
pxl8_sfx_mixer_set_event_callback(game->mixer, pxl8_audio_event_callback, game);
@ -338,6 +351,13 @@ pxl8_result pxl8_update(pxl8* sys) {
}
}
if (game->net) {
while (pxl8_net_poll(game->net)) {}
pxl8_net_update(game->net, dt);
pxl8_world_sync(game->world, game->net);
}
pxl8_world_update(game->world, dt);
pxl8_gfx_update(game->gfx, dt);
pxl8_sfx_mixer_process(game->mixer);
@ -418,6 +438,9 @@ void pxl8_quit(pxl8* sys) {
pxl8_replay_destroy(game->debug_replay);
#endif
if (game->net) pxl8_net_destroy(game->net);
if (game->world) pxl8_world_destroy(game->world);
pxl8_sfx_mixer_destroy(game->mixer);
pxl8_gfx_destroy(game->gfx);
pxl8_script_destroy(game->script);
@ -437,6 +460,10 @@ void pxl8_set_running(pxl8* sys, bool running) {
}
}
pxl8_world* pxl8_get_world(pxl8* sys) {
return (sys && sys->game) ? sys->game->world : NULL;
}
f32 pxl8_get_fps(const pxl8* sys) {
return (sys && sys->game) ? sys->game->fps : 0.0f;
}
@ -449,6 +476,10 @@ pxl8_input_state* pxl8_get_input(const pxl8* sys) {
return (sys && sys->game) ? &sys->game->input : NULL;
}
pxl8_net* pxl8_get_net(const pxl8* sys) {
return (sys && sys->game) ? sys->game->net : NULL;
}
pxl8_sfx_mixer* pxl8_get_sfx_mixer(const pxl8* sys) {
return (sys && sys->game) ? sys->game->mixer : NULL;
}

View file

@ -1,37 +1,37 @@
#pragma once
#include "pxl8_gfx.h"
#include "pxl8_net.h"
#include "pxl8_rng.h"
#include "pxl8_script.h"
#include "pxl8_sfx.h"
#include "pxl8_types.h"
#include "pxl8_world.h"
typedef struct pxl8_replay pxl8_replay;
typedef struct pxl8_game {
pxl8_gfx* gfx;
pxl8_script* script;
pxl8_sfx_mixer* mixer;
pxl8_rng rng;
i32 frame_count;
u64 last_time;
f32 time;
f32 fps_accumulator;
i32 fps_frame_count;
f32 fps;
pxl8_input_state input;
pxl8_input_state prev_input;
#ifndef NDEBUG
pxl8_replay* debug_replay;
#endif
f32 fps;
f32 fps_accumulator;
i32 fps_frame_count;
i32 frame_count;
pxl8_gfx* gfx;
pxl8_input_state input;
u64 last_time;
pxl8_sfx_mixer* mixer;
pxl8_net* net;
pxl8_input_state prev_input;
bool repl_mode;
bool repl_started;
pxl8_rng rng;
bool running;
pxl8_script* script;
bool script_loaded;
char script_path[256];
f32 time;
pxl8_world* world;
} pxl8_game;

View file

@ -1,5 +1,5 @@
#include "pxl8_log.h"
#include "pxl8_repl.h"
#include "pxl8_types.h"
#include <stdarg.h>
#include <stdio.h>
@ -18,6 +18,7 @@ static pxl8_log* g_log = NULL;
void pxl8_log_init(pxl8_log* log) {
g_log = log;
g_log->handler = NULL;
g_log->level = PXL8_LOG_LEVEL_DEBUG;
const char* env_level = getenv("PXL8_LOG_LEVEL");
@ -30,6 +31,10 @@ void pxl8_log_init(pxl8_log* log) {
}
}
void pxl8_log_set_handler(pxl8_log_handler handler) {
if (g_log) g_log->handler = handler;
}
void pxl8_log_set_level(pxl8_log_level level) {
if (g_log) g_log->level = level;
}
@ -61,7 +66,7 @@ static void log_output(const char* color, const char* level,
strncat(buffer, "\n", sizeof(buffer) - strlen(buffer) - 1);
if (!pxl8_repl_push_log(buffer)) {
if (!g_log->handler || !g_log->handler(buffer)) {
printf("%s", buffer);
fflush(stdout);
}

View file

@ -10,11 +10,15 @@ typedef enum {
PXL8_LOG_LEVEL_ERROR = 4,
} pxl8_log_level;
typedef bool (*pxl8_log_handler)(const char* message);
typedef struct pxl8_log {
pxl8_log_handler handler;
pxl8_log_level level;
} pxl8_log;
void pxl8_log_init(pxl8_log* log);
void pxl8_log_set_handler(pxl8_log_handler handler);
void pxl8_log_set_level(pxl8_log_level level);
void pxl8_log_write_trace(const char* file, int line, const char* fmt, ...);

View file

@ -3,6 +3,7 @@
#include "pxl8_gfx.h"
#include "pxl8_hal.h"
#include "pxl8_io.h"
#include "pxl8_net.h"
#include "pxl8_sfx.h"
#include "pxl8_types.h"
@ -23,6 +24,7 @@ void pxl8_quit(pxl8* sys);
f32 pxl8_get_fps(const pxl8* sys);
pxl8_gfx* pxl8_get_gfx(const pxl8* sys);
pxl8_input_state* pxl8_get_input(const pxl8* sys);
pxl8_net* pxl8_get_net(const pxl8* sys);
pxl8_size pxl8_get_resolution_dimensions(pxl8_resolution resolution);
pxl8_sfx_mixer* pxl8_get_sfx_mixer(const pxl8* sys);
bool pxl8_is_running(const pxl8* sys);

View file

@ -87,11 +87,13 @@ pxl8.create_anim_from_ase = anim.Anim.from_ase
pxl8.bounds = math.bounds
pxl8.Camera3D = gfx3d.Camera3D
pxl8.Material = gfx3d.Material
pxl8.Mesh = gfx3d.Mesh
pxl8.begin_frame_3d = gfx3d.begin_frame
pxl8.clear_3d = gfx3d.clear
pxl8.clear_depth = gfx3d.clear_depth
pxl8.create_camera_3d = gfx3d.Camera3D.new
pxl8.create_material = gfx3d.create_material
pxl8.create_mesh = gfx3d.Mesh.new
pxl8.create_vec3_array = gfx3d.create_vec3_array
pxl8.draw_line_3d = gfx3d.draw_line
@ -132,10 +134,7 @@ pxl8.mat4_rotate_z = math.mat4_rotate_z
pxl8.mat4_scale = math.mat4_scale
pxl8.mat4_translate = math.mat4_translate
pxl8.Net = net.Net
pxl8.create_net = net.Net.new
pxl8.NET_MODE_LOCAL = net.MODE_LOCAL
pxl8.NET_MODE_REMOTE = net.MODE_REMOTE
pxl8.get_net = net.get
pxl8.pack_f32_be = bytes.pack_f32_be
pxl8.pack_f32_le = bytes.pack_f32_le
@ -221,7 +220,11 @@ pxl8.unpack_u32_le = bytes.unpack_u32_le
pxl8.unpack_u64_be = bytes.unpack_u64_be
pxl8.unpack_u64_le = bytes.unpack_u64_le
pxl8.Bsp = world.Bsp
pxl8.Chunk = world.Chunk
pxl8.CHUNK_BSP = world.CHUNK_BSP
pxl8.CHUNK_VXL = world.CHUNK_VXL
pxl8.World = world.World
pxl8.create_world = world.World.new
pxl8.get_world = world.World.get
return pxl8

View file

@ -205,4 +205,27 @@ function gfx3d.create_vec3_array(count)
return ffi.new("pxl8_vec3[?]", count)
end
local Material = {}
Material.__index = Material
function Material.new(opts)
opts = opts or {}
local mat = ffi.new("pxl8_gfx_material", {
texture_id = opts.texture or 0,
alpha = opts.alpha or 255,
blend_mode = opts.blend_mode or 0,
dither = opts.dither ~= false,
double_sided = opts.double_sided or false,
dynamic_lighting = opts.lighting or false,
per_pixel = opts.per_pixel or false,
vertex_color_passthrough = opts.passthrough or false,
wireframe = opts.wireframe or false,
emissive_intensity = opts.emissive or 0.0,
})
return setmetatable({ _ptr = mat }, Material)
end
gfx3d.Material = Material
gfx3d.create_material = Material.new
return gfx3d

View file

@ -1,47 +1,24 @@
local ffi = require("ffi")
local C = ffi.C
local core = require("pxl8.core")
local net = {}
local Net = {}
Net.__index = Net
net.MODE_LOCAL = C.PXL8_NET_LOCAL
net.MODE_REMOTE = C.PXL8_NET_REMOTE
function Net.new(config)
config = config or {}
local cfg = ffi.new("pxl8_net_config")
cfg.address = config.address or "127.0.0.1"
cfg.mode = config.mode or C.PXL8_NET_REMOTE
cfg.port = config.port or 7777
local n = C.pxl8_net_create(cfg)
if n == nil then
function net.get()
local ptr = C.pxl8_get_net(core.sys)
if ptr == nil then
return nil
end
return setmetatable({ _ptr = n }, Net)
end
function Net:connect()
return C.pxl8_net_connect(self._ptr) == 0
return setmetatable({ _ptr = ptr }, Net)
end
function Net:connected()
return C.pxl8_net_connected(self._ptr)
end
function Net:destroy()
if self._ptr then
C.pxl8_net_destroy(self._ptr)
self._ptr = nil
end
end
function Net:disconnect()
C.pxl8_net_disconnect(self._ptr)
end
function Net:entities()
local snap = C.pxl8_net_snapshot(self._ptr)
if snap == nil then
@ -113,10 +90,6 @@ function Net:player_id()
return tonumber(C.pxl8_net_player_id(self._ptr))
end
function Net:poll()
return C.pxl8_net_poll(self._ptr)
end
function Net:predicted_state()
return C.pxl8_net_predicted_state(self._ptr)
end
@ -173,10 +146,4 @@ function Net:tick()
return tonumber(C.pxl8_net_tick(self._ptr))
end
function Net:update(dt)
C.pxl8_net_update(self._ptr, dt)
end
net.Net = Net
return net

View file

@ -4,40 +4,77 @@ local core = require("pxl8.core")
local world = {}
world.CHUNK_VXL = 0
world.CHUNK_BSP = 1
world.PROCGEN_ROOMS = C.PXL8_PROCGEN_ROOMS
world.PROCGEN_TERRAIN = C.PXL8_PROCGEN_TERRAIN
local Bsp = {}
Bsp.__index = Bsp
function Bsp:face_count()
return C.pxl8_bsp_face_count(self._ptr)
end
function Bsp:face_normal(face_id)
return C.pxl8_bsp_face_normal(self._ptr, face_id)
end
function Bsp:face_set_material(face_id, material_id)
C.pxl8_bsp_face_set_material(self._ptr, face_id, material_id)
end
function Bsp:set_material(material_id, material)
C.pxl8_bsp_set_material(self._ptr, material_id, material._ptr)
end
function Bsp:set_wireframe(wireframe)
C.pxl8_bsp_set_wireframe(self._ptr, wireframe)
end
world.Bsp = Bsp
local Chunk = {}
Chunk.__index = Chunk
function Chunk:bsp()
if self._ptr == nil then return nil end
if self._ptr.type ~= world.CHUNK_BSP then return nil end
local ptr = self._ptr.bsp
if ptr == nil then return nil end
return setmetatable({ _ptr = ptr }, Bsp)
end
function Chunk:type()
if self._ptr == nil then return nil end
return self._ptr.type
end
function Chunk:ready()
if self._ptr == nil then return false end
if self._ptr.type == world.CHUNK_BSP then
return self._ptr.bsp ~= nil
elseif self._ptr.type == world.CHUNK_VXL then
return self._ptr.voxel ~= nil
end
return false
end
world.Chunk = Chunk
local World = {}
World.__index = World
function World.new()
local w = C.pxl8_world_create()
if w == nil then
return nil
end
function World.get()
local w = C.pxl8_get_world(core.sys)
if w == nil then return nil end
return setmetatable({ _ptr = w }, World)
end
function World:apply_textures(texture_defs)
local count = #texture_defs
local textures = ffi.new("pxl8_world_texture[?]", count)
for i, def in ipairs(texture_defs) do
local idx = i - 1
ffi.copy(textures[idx].name, def.name or "", math.min(#(def.name or ""), 15))
textures[idx].texture_id = def.texture_id or 0
if def.rule then
textures[idx].rule = ffi.cast("bool (*)(const pxl8_vec3*, const pxl8_bsp_face*, const pxl8_bsp*)",
function(normal, face, bsp)
return def.rule(normal[0], face, bsp)
end)
else
textures[idx].rule = nil
end
end
return C.pxl8_world_apply_textures(self._ptr, textures, count)
function World:active_chunk()
local ptr = C.pxl8_world_active_chunk(self._ptr)
if ptr == nil then return nil end
return setmetatable({ _ptr = ptr }, Chunk)
end
function World:check_collision(x, y, z, radius)
@ -45,34 +82,6 @@ function World:check_collision(x, y, z, radius)
return C.pxl8_world_check_collision(self._ptr, pos, radius)
end
function World:destroy()
if self._ptr then
C.pxl8_world_destroy(self._ptr)
self._ptr = nil
end
end
function World:generate(params)
local c_params = ffi.new("pxl8_procgen_params")
c_params.type = params.type or C.PXL8_PROCGEN_ROOMS
c_params.width = params.width or 32
c_params.height = params.height or 32
c_params.depth = params.depth or 0
c_params.seed = params.seed or 0
c_params.min_room_size = params.min_room_size or 5
c_params.max_room_size = params.max_room_size or 10
c_params.num_rooms = params.num_rooms or 8
return C.pxl8_world_generate(self._ptr, core.gfx, c_params)
end
function World:is_loaded()
return C.pxl8_world_is_loaded(self._ptr)
end
function World:load(filepath)
return C.pxl8_world_load(self._ptr, filepath)
end
function World:render(camera_pos)
local vec = ffi.new("pxl8_vec3", {x = camera_pos[1], y = camera_pos[2], z = camera_pos[3]})
C.pxl8_world_render(self._ptr, core.gfx, vec)
@ -85,14 +94,6 @@ function World:resolve_collision(from_x, from_y, from_z, to_x, to_y, to_z, radiu
return result.x, result.y, result.z
end
function World:set_wireframe(enabled)
C.pxl8_world_set_wireframe(self._ptr, enabled)
end
function World:unload()
C.pxl8_world_unload(self._ptr)
end
world.World = World
return world

View file

@ -1,4 +1,6 @@
#include "pxl8_net.h"
#include "pxl8_chunk_cache.h"
#include "pxl8_log.h"
#include "pxl8_mem.h"
#include <stdlib.h>
@ -28,6 +30,9 @@
struct pxl8_net {
char address[256];
u32 chunk_id;
u8 chunk_type;
pxl8_chunk_cache* chunk_cache;
bool connected;
pxl8_entity_state entities[PXL8_MAX_SNAPSHOT_ENTITIES];
pxl8_event_msg events[PXL8_MAX_SNAPSHOT_EVENTS];
@ -36,7 +41,6 @@ struct pxl8_net {
u64 input_head;
u64 input_oldest_tick;
f32 interp_time;
pxl8_net_mode mode;
u16 port;
u8 predicted_state[PXL8_NET_USERDATA_SIZE];
u64 predicted_tick;
@ -98,7 +102,6 @@ pxl8_net* pxl8_net_create(const pxl8_net_config* config) {
pxl8_net* net = pxl8_calloc(1, sizeof(pxl8_net));
if (!net) return NULL;
net->mode = config->mode;
net->port = config->port ? config->port : PXL8_NET_DEFAULT_PORT;
net->sock = INVALID_SOCK;
net->connected = false;
@ -209,6 +212,32 @@ bool pxl8_net_poll(pxl8_net* net) {
pxl8_msg_header hdr;
usize offset = pxl8_protocol_deserialize_header(net->recv_buf, len, &hdr);
if (hdr.type == PXL8_MSG_CHUNK) {
if (!net->chunk_cache) return false;
pxl8_chunk_msg_header chunk_hdr;
offset += pxl8_protocol_deserialize_chunk_msg_header(net->recv_buf + offset, len - offset, &chunk_hdr);
const u8* payload = net->recv_buf + offset;
usize payload_len = chunk_hdr.payload_size;
if (payload_len > len - offset) {
payload_len = len - offset;
}
pxl8_chunk_cache_receive(net->chunk_cache, &chunk_hdr, payload, payload_len);
return true;
}
if (hdr.type == PXL8_MSG_CHUNK_ENTER) {
pxl8_chunk_enter_msg chunk_msg;
pxl8_protocol_deserialize_chunk_enter(net->recv_buf + offset, len - offset, &chunk_msg);
net->chunk_id = chunk_msg.chunk_id;
net->chunk_type = chunk_msg.chunk_type;
pxl8_debug("[CLIENT] Received CHUNK_ENTER type=%u id=%u", chunk_msg.chunk_type, chunk_msg.chunk_id);
return true;
}
if (hdr.type != PXL8_MSG_SNAPSHOT) return false;
pxl8_snapshot_header snap;
@ -311,3 +340,23 @@ void pxl8_net_update(pxl8_net* net, f32 dt) {
if (!net) return;
net->interp_time += dt;
}
void pxl8_net_set_chunk_cache(pxl8_net* net, pxl8_chunk_cache* cache) {
if (!net) return;
net->chunk_cache = cache;
}
pxl8_chunk_cache* pxl8_net_chunk_cache(pxl8_net* net) {
if (!net) return NULL;
return net->chunk_cache;
}
u32 pxl8_net_chunk_id(const pxl8_net* net) {
if (!net) return 0;
return net->chunk_id;
}
u8 pxl8_net_chunk_type(const pxl8_net* net) {
if (!net) return PXL8_CHUNK_TYPE_VXL;
return net->chunk_type;
}

View file

@ -11,15 +11,10 @@ extern "C" {
#define PXL8_NET_USERDATA_SIZE 56
typedef struct pxl8_net pxl8_net;
typedef enum pxl8_net_mode {
PXL8_NET_LOCAL = 0,
PXL8_NET_REMOTE
} pxl8_net_mode;
typedef struct pxl8_chunk_cache pxl8_chunk_cache;
typedef struct pxl8_net_config {
const char* address;
pxl8_net_mode mode;
u16 port;
} pxl8_net_config;
@ -48,6 +43,11 @@ pxl8_result pxl8_net_send_input(pxl8_net* net, const pxl8_input_msg* input);
const pxl8_snapshot_header* pxl8_net_snapshot(const pxl8_net* net);
u64 pxl8_net_tick(const pxl8_net* net);
void pxl8_net_update(pxl8_net* net, f32 dt);
void pxl8_net_set_chunk_cache(pxl8_net* net, pxl8_chunk_cache* cache);
pxl8_chunk_cache* pxl8_net_chunk_cache(pxl8_net* net);
u32 pxl8_net_chunk_id(const pxl8_net* net);
u8 pxl8_net_chunk_type(const pxl8_net* net);
#ifdef __cplusplus
}

View file

@ -122,3 +122,76 @@ usize pxl8_protocol_deserialize_snapshot_header(const u8* buf, usize len, pxl8_s
hdr->time = pxl8_read_f32_be(&s);
return s.offset;
}
usize pxl8_protocol_serialize_chunk_msg_header(const pxl8_chunk_msg_header* hdr, u8* buf, usize len) {
if (len < 24) return 0;
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
pxl8_write_u8(&s, hdr->chunk_type);
pxl8_write_u8(&s, hdr->flags);
pxl8_write_u8(&s, hdr->fragment_idx);
pxl8_write_u8(&s, hdr->fragment_count);
pxl8_write_u32_be(&s, hdr->id);
pxl8_write_u32_be(&s, (u32)hdr->cx);
pxl8_write_u32_be(&s, (u32)hdr->cy);
pxl8_write_u32_be(&s, (u32)hdr->cz);
pxl8_write_u32_be(&s, hdr->version);
pxl8_write_u16_be(&s, hdr->payload_size);
pxl8_write_u16_be(&s, hdr->reserved);
return s.offset;
}
usize pxl8_protocol_deserialize_chunk_msg_header(const u8* buf, usize len, pxl8_chunk_msg_header* hdr) {
if (len < 24) return 0;
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
hdr->chunk_type = pxl8_read_u8(&s);
hdr->flags = pxl8_read_u8(&s);
hdr->fragment_idx = pxl8_read_u8(&s);
hdr->fragment_count = pxl8_read_u8(&s);
hdr->id = pxl8_read_u32_be(&s);
hdr->cx = (i32)pxl8_read_u32_be(&s);
hdr->cy = (i32)pxl8_read_u32_be(&s);
hdr->cz = (i32)pxl8_read_u32_be(&s);
hdr->version = pxl8_read_u32_be(&s);
hdr->payload_size = pxl8_read_u16_be(&s);
hdr->reserved = pxl8_read_u16_be(&s);
return s.offset;
}
usize pxl8_protocol_deserialize_bsp_wire_header(const u8* buf, usize len, pxl8_bsp_wire_header* hdr) {
if (len < 44) return 0;
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
hdr->num_vertices = pxl8_read_u32_be(&s);
hdr->num_edges = pxl8_read_u32_be(&s);
hdr->num_faces = pxl8_read_u32_be(&s);
hdr->num_planes = pxl8_read_u32_be(&s);
hdr->num_nodes = pxl8_read_u32_be(&s);
hdr->num_leafs = pxl8_read_u32_be(&s);
hdr->num_surfedges = pxl8_read_u32_be(&s);
hdr->num_marksurfaces = pxl8_read_u32_be(&s);
hdr->num_cell_portals = pxl8_read_u32_be(&s);
hdr->visdata_size = pxl8_read_u32_be(&s);
hdr->num_vertex_lights = pxl8_read_u32_be(&s);
return s.offset;
}
usize pxl8_protocol_serialize_chunk_enter(const pxl8_chunk_enter_msg* msg, u8* buf, usize len) {
if (len < 8) return 0;
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
pxl8_write_u32_be(&s, msg->chunk_id);
pxl8_write_u8(&s, msg->chunk_type);
pxl8_write_u8(&s, msg->reserved[0]);
pxl8_write_u8(&s, msg->reserved[1]);
pxl8_write_u8(&s, msg->reserved[2]);
return s.offset;
}
usize pxl8_protocol_deserialize_chunk_enter(const u8* buf, usize len, pxl8_chunk_enter_msg* msg) {
if (len < 8) return 0;
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
msg->chunk_id = pxl8_read_u32_be(&s);
msg->chunk_type = pxl8_read_u8(&s);
msg->reserved[0] = pxl8_read_u8(&s);
msg->reserved[1] = pxl8_read_u8(&s);
msg->reserved[2] = pxl8_read_u8(&s);
return s.offset;
}

View file

@ -14,12 +14,14 @@ extern "C" {
typedef enum pxl8_msg_type {
PXL8_MSG_NONE = 0,
PXL8_MSG_CHUNK,
PXL8_MSG_CHUNK_ENTER,
PXL8_MSG_COMMAND,
PXL8_MSG_CONNECT,
PXL8_MSG_DISCONNECT,
PXL8_MSG_EVENT,
PXL8_MSG_INPUT,
PXL8_MSG_COMMAND,
PXL8_MSG_SNAPSHOT,
PXL8_MSG_EVENT
PXL8_MSG_SNAPSHOT
} pxl8_msg_type;
typedef struct pxl8_msg_header {
@ -70,6 +72,39 @@ typedef struct pxl8_snapshot_header {
f32 time;
} pxl8_snapshot_header;
#define PXL8_CHUNK_TYPE_VXL 0
#define PXL8_CHUNK_TYPE_BSP 1
#define PXL8_CHUNK_FLAG_RLE 0x01
#define PXL8_CHUNK_FLAG_FINAL 0x04
#define PXL8_CHUNK_MAX_PAYLOAD 1400
typedef struct pxl8_chunk_msg_header {
u8 chunk_type;
u8 flags;
u8 fragment_idx;
u8 fragment_count;
u32 id;
i32 cx, cy, cz;
u32 version;
u16 payload_size;
u16 reserved;
} pxl8_chunk_msg_header;
typedef struct pxl8_bsp_wire_header {
u32 num_vertices;
u32 num_edges;
u32 num_faces;
u32 num_planes;
u32 num_nodes;
u32 num_leafs;
u32 num_surfedges;
u32 num_marksurfaces;
u32 num_cell_portals;
u32 visdata_size;
u32 num_vertex_lights;
} pxl8_bsp_wire_header;
usize pxl8_protocol_serialize_header(const pxl8_msg_header* msg, u8* buf, usize len);
usize pxl8_protocol_deserialize_header(const u8* buf, usize len, pxl8_msg_header* msg);
@ -88,6 +123,20 @@ usize pxl8_protocol_deserialize_event(const u8* buf, usize len, pxl8_event_msg*
usize pxl8_protocol_serialize_snapshot_header(const pxl8_snapshot_header* hdr, u8* buf, usize len);
usize pxl8_protocol_deserialize_snapshot_header(const u8* buf, usize len, pxl8_snapshot_header* hdr);
usize pxl8_protocol_serialize_chunk_msg_header(const pxl8_chunk_msg_header* hdr, u8* buf, usize len);
usize pxl8_protocol_deserialize_chunk_msg_header(const u8* buf, usize len, pxl8_chunk_msg_header* hdr);
usize pxl8_protocol_deserialize_bsp_wire_header(const u8* buf, usize len, pxl8_bsp_wire_header* hdr);
typedef struct pxl8_chunk_enter_msg {
u32 chunk_id;
u8 chunk_type;
u8 reserved[3];
} pxl8_chunk_enter_msg;
usize pxl8_protocol_serialize_chunk_enter(const pxl8_chunk_enter_msg* msg, u8* buf, usize len);
usize pxl8_protocol_deserialize_chunk_enter(const u8* buf, usize len, pxl8_chunk_enter_msg* msg);
#ifdef __cplusplus
}
#endif

View file

@ -1,15 +1,16 @@
#include "pxl8_repl.h"
#include "pxl8_mem.h"
#include <linenoise.h>
#include <poll.h>
#include <SDL3/SDL.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <SDL3/SDL.h>
#include <linenoise.h>
#include "pxl8_log.h"
#include "pxl8_mem.h"
#define PXL8_MAX_REPL_COMMAND_SIZE 4096
#define PXL8_REPL_QUEUE_SIZE 8
@ -235,6 +236,7 @@ pxl8_repl* pxl8_repl_create(void) {
linenoiseHistoryLoad(".pxl8_history");
g_repl = repl;
pxl8_log_set_handler(pxl8_repl_push_log);
repl->thread = SDL_CreateThread(pxl8_repl_thread, "pxl8-repl", repl);
if (!repl->thread) {
@ -260,6 +262,7 @@ void pxl8_repl_destroy(pxl8_repl* repl) {
pxl8_repl_flush_logs(repl);
g_repl = NULL;
pxl8_log_set_handler(NULL);
system("stty sane 2>/dev/null");
pxl8_free(repl);

View file

@ -424,27 +424,33 @@ static const char* pxl8_ffi_cdefs =
"void pxl8_graph_eval_texture(const pxl8_graph* graph, u8* buffer, i32 width, i32 height);\n"
"\n"
"typedef struct pxl8_bsp pxl8_bsp;\n"
"typedef struct pxl8_bsp_face pxl8_bsp_face;\n"
"typedef bool (*pxl8_texture_rule)(const pxl8_vec3* normal, const pxl8_bsp_face* face, const pxl8_bsp* bsp);\n"
"\n"
"typedef struct pxl8_world_texture {\n"
" char name[16];\n"
" unsigned int texture_id;\n"
" pxl8_texture_rule rule;\n"
"} pxl8_world_texture;\n"
"u32 pxl8_bsp_face_count(const pxl8_bsp* bsp);\n"
"pxl8_vec3 pxl8_bsp_face_normal(const pxl8_bsp* bsp, u32 face_id);\n"
"void pxl8_bsp_face_set_material(pxl8_bsp* bsp, u32 face_id, u16 material_id);\n"
"void pxl8_bsp_set_material(pxl8_bsp* bsp, u16 material_id, const pxl8_gfx_material* material);\n"
"void pxl8_bsp_set_wireframe(pxl8_bsp* bsp, bool wireframe);\n"
"\n"
"typedef enum { PXL8_CHUNK_VXL = 0, PXL8_CHUNK_BSP = 1 } pxl8_chunk_type;\n"
"\n"
"typedef struct pxl8_chunk {\n"
" pxl8_chunk_type type;\n"
" u32 id;\n"
" u32 version;\n"
" i32 cx, cy, cz;\n"
" union {\n"
" void* voxel;\n"
" pxl8_bsp* bsp;\n"
" };\n"
"} pxl8_chunk;\n"
"\n"
"typedef struct pxl8_world pxl8_world;\n"
"pxl8_world* pxl8_world_create(void);\n"
"void pxl8_world_destroy(pxl8_world* world);\n"
"int pxl8_world_generate(pxl8_world* world, pxl8_gfx* gfx, const pxl8_procgen_params* params);\n"
"int pxl8_world_load(pxl8_world* world, const char* path);\n"
"void pxl8_world_unload(pxl8_world* world);\n"
"int pxl8_world_apply_textures(pxl8_world* world, const pxl8_world_texture* textures, unsigned int count);\n"
"\n"
"pxl8_world* pxl8_get_world(pxl8* sys);\n"
"pxl8_chunk* pxl8_world_active_chunk(pxl8_world* world);\n"
"bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, float radius);\n"
"bool pxl8_world_is_loaded(const pxl8_world* world);\n"
"void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);\n"
"pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, float radius);\n"
"void pxl8_world_set_wireframe(pxl8_world* world, bool enabled);\n"
"\n"
"typedef struct { i32 cursor_x; i32 cursor_y; bool cursor_down; bool cursor_clicked; u32 hot_id; u32 active_id; } pxl8_gui_state;\n"
"pxl8_gui_state* pxl8_gui_state_create(void);\n"
@ -517,8 +523,7 @@ static const char* pxl8_ffi_cdefs =
"void pxl8_sfx_stop_voice(pxl8_sfx_context* ctx, u16 voice_id);\n"
"\n"
"typedef struct pxl8_net pxl8_net;\n"
"typedef enum pxl8_net_mode { PXL8_NET_LOCAL = 0, PXL8_NET_REMOTE } pxl8_net_mode;\n"
"typedef struct pxl8_net_config { const char* address; pxl8_net_mode mode; u16 port; } pxl8_net_config;\n"
"typedef struct pxl8_net_config { const char* address; u16 port; } pxl8_net_config;\n"
"typedef enum pxl8_cmd_type { PXL8_CMD_NONE = 0, PXL8_CMD_SPAWN_ENTITY } pxl8_cmd_type;\n"
"\n"
"typedef struct pxl8_command_msg {\n"
@ -574,6 +579,7 @@ static const char* pxl8_ffi_cdefs =
"const pxl8_snapshot_header* pxl8_net_snapshot(const pxl8_net* net);\n"
"u64 pxl8_net_tick(const pxl8_net* net);\n"
"void pxl8_net_update(pxl8_net* net, f32 dt);\n"
"pxl8_net* pxl8_get_net(const pxl8* sys);\n"
"\n"
"void pxl8_bit_clear(u32* val, u8 bit);\n"
"u32 pxl8_bit_count(u32 val);\n"

View file

@ -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;
}
}

View file

@ -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
View 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
View 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

View 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;
}
}

View 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
View 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
View 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
View 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 = &registry->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
View 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

View file

@ -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;
}
}

View file

@ -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
}