refactor: decouple sim from framework, remove voxel geometry

This commit is contained in:
asrael 2026-02-27 01:22:35 -06:00
parent c538641ec8
commit 5a565844dd
41 changed files with 477 additions and 2407 deletions

View file

@ -391,18 +391,6 @@ pxl8_result pxl8_update(pxl8* sys) {
}
#ifdef PXL8_ASYNC_THREADS
if (game->world) {
pxl8_input_msg msg = {0};
msg.move_x = (pxl8_key_down(&game->input, "d") ? 1.0f : 0.0f) - (pxl8_key_down(&game->input, "a") ? 1.0f : 0.0f);
msg.move_y = (pxl8_key_down(&game->input, "w") ? 1.0f : 0.0f) - (pxl8_key_down(&game->input, "s") ? 1.0f : 0.0f);
if (game->input.mouse_relative_mode) {
msg.look_dx = (f32)pxl8_mouse_dx(&game->input);
msg.look_dy = (f32)pxl8_mouse_dy(&game->input);
}
msg.buttons = pxl8_key_down(&game->input, "space") ? 1 : 0;
pxl8_world_push_input(game->world, &msg);
}
pxl8_net_update(game->net, dt);
#else
if (game->net) {
@ -410,7 +398,7 @@ pxl8_result pxl8_update(pxl8* sys) {
pxl8_net_update(game->net, dt);
pxl8_world_sync(game->world, game->net);
}
pxl8_world_update(game->world, &game->input, dt);
pxl8_world_update(game->world, dt);
#endif
pxl8_gfx_update(game->gfx, dt);

View file

@ -167,6 +167,13 @@ bool pxl8_key_down(const pxl8_input_state* input, const char* key_name) {
return input->keys_down[key];
}
void pxl8_set_key_down(pxl8_input_state* input, const char* key_name, bool down) {
if (!input) return;
i32 key = pxl8_key_code(key_name);
if (key < 0 || key >= PXL8_MAX_KEYS) return;
input->keys_down[key] = down;
}
bool pxl8_key_pressed(const pxl8_input_state* input, const char* key_name) {
if (!input) return false;
i32 key = pxl8_key_code(key_name);

View file

@ -23,6 +23,7 @@ pxl8_result pxl8_io_write_file(const char* path, const char* content, usize size
bool pxl8_key_down(const pxl8_input_state* input, const char* key_name);
bool pxl8_key_pressed(const pxl8_input_state* input, const char* key_name);
bool pxl8_key_released(const pxl8_input_state* input, const char* key_name);
void pxl8_set_key_down(pxl8_input_state* input, const char* key_name, bool down);
i32 pxl8_mouse_dx(const pxl8_input_state* input);
i32 pxl8_mouse_dy(const pxl8_input_state* input);

View file

@ -1028,15 +1028,6 @@ u8 pxl8_gfx_find_closest_color(pxl8_gfx* gfx, u8 r, u8 g, u8 b) {
return pxl8_palette_find_closest(gfx->palette, r, g, b);
}
u8 pxl8_gfx_ui_color(pxl8_gfx* gfx, u8 index) {
if (!gfx || !gfx->palette || index >= PXL8_UI_PALETTE_SIZE) return 0;
u32 abgr = pxl8_ui_palette[index];
u8 r = (abgr >> 0) & 0xFF;
u8 g = (abgr >> 8) & 0xFF;
u8 b = (abgr >> 16) & 0xFF;
return pxl8_palette_find_closest(gfx->palette, r, g, b);
}
void pxl8_gfx_set_wireframe(pxl8_gfx* gfx, bool enabled) {
if (gfx) gfx->wireframe = enabled;
}

View file

@ -7,7 +7,6 @@
#include "pxl8_colormap.h"
#include "pxl8_palette.h"
#include "pxl8_types.h"
#include "pxl8_gui_palette.h"
typedef struct pxl8_gfx pxl8_gfx;
@ -77,7 +76,6 @@ void pxl8_gfx_colormap_update(pxl8_gfx* gfx);
void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx);
u8 pxl8_gfx_find_closest_color(pxl8_gfx* gfx, u8 r, u8 g, u8 b);
u8 pxl8_gfx_ui_color(pxl8_gfx* gfx, u8 index);
void pxl8_gfx_set_wireframe(pxl8_gfx* gfx, bool enabled);
bool pxl8_gfx_get_wireframe(const pxl8_gfx* gfx);

View file

@ -4,6 +4,7 @@
#include <string.h>
#include "pxl8_gfx.h"
#include "pxl8_gui_palette.h"
#include "pxl8_mem.h"
pxl8_gui_state* pxl8_gui_state_create(void) {
@ -83,16 +84,16 @@ bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y,
i32 offset_y = 0;
if (is_active) {
bg_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG3);
border_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG2);
bg_color = pxl8_gui_color(gfx, PXL8_UI_BG3);
border_color = pxl8_gui_color(gfx, PXL8_UI_BG2);
offset_x = 1;
offset_y = 1;
} else if (is_hot || cursor_over) {
bg_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG3);
border_color = pxl8_gfx_ui_color(gfx, PXL8_UI_FG0);
bg_color = pxl8_gui_color(gfx, PXL8_UI_BG3);
border_color = pxl8_gui_color(gfx, PXL8_UI_FG0);
} else {
bg_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG2);
border_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG3);
bg_color = pxl8_gui_color(gfx, PXL8_UI_BG2);
border_color = pxl8_gui_color(gfx, PXL8_UI_BG3);
}
pxl8_2d_rect_fill(gfx, x, y, w, h, bg_color);
@ -101,11 +102,20 @@ bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y,
i32 text_len = (i32)strlen(label);
i32 text_x = x + (w / 2) - ((text_len * 8) / 2) + offset_x;
i32 text_y = y + (h / 2) - 5 + offset_y;
pxl8_2d_text(gfx, label, text_x, text_y, pxl8_gfx_ui_color(gfx, PXL8_UI_FG1));
pxl8_2d_text(gfx, label, text_x, text_y, pxl8_gui_color(gfx, PXL8_UI_FG0));
return clicked;
}
u8 pxl8_gui_color(pxl8_gfx* gfx, u8 index) {
if (!gfx || index >= PXL8_UI_PALETTE_SIZE) return 0;
u32 abgr = pxl8_ui_palette[index];
u8 r = (abgr >> 0) & 0xFF;
u8 g = (abgr >> 8) & 0xFF;
u8 b = (abgr >> 16) & 0xFF;
return pxl8_gfx_find_closest_color(gfx, r, g, b);
}
bool pxl8_gui_slider(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, f32* value, f32 min_val, f32 max_val) {
if (!state || !gfx || !value) return false;
@ -132,9 +142,9 @@ bool pxl8_gui_slider(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y,
}
}
u8 bg_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG1);
u8 fill_color = pxl8_gfx_ui_color(gfx, is_active ? PXL8_UI_FG0 : PXL8_UI_BG3);
u8 handle_color = pxl8_gfx_ui_color(gfx, PXL8_UI_FG1);
u8 bg_color = pxl8_gui_color(gfx, PXL8_UI_BG1);
u8 fill_color = pxl8_gui_color(gfx, is_active ? PXL8_UI_FG0 : PXL8_UI_BG3);
u8 handle_color = pxl8_gui_color(gfx, PXL8_UI_FG0);
pxl8_2d_rect_fill(gfx, x, y, w, h, bg_color);
@ -166,10 +176,10 @@ bool pxl8_gui_slider_int(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i3
void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title) {
if (!gfx || !title) return;
u8 title_bg = pxl8_gfx_ui_color(gfx, PXL8_UI_BG1);
u8 body_bg = pxl8_gfx_ui_color(gfx, PXL8_UI_BG2);
u8 border = pxl8_gfx_ui_color(gfx, PXL8_UI_BG3);
u8 title_fg = pxl8_gfx_ui_color(gfx, PXL8_UI_FG0);
u8 title_bg = pxl8_gui_color(gfx, PXL8_UI_BG1);
u8 body_bg = pxl8_gui_color(gfx, PXL8_UI_BG2);
u8 border = pxl8_gui_color(gfx, PXL8_UI_BG3);
u8 title_fg = pxl8_gui_color(gfx, PXL8_UI_FG0);
pxl8_2d_rect_fill(gfx, x, y, w, 28, title_bg);
pxl8_2d_rect_fill(gfx, x, y + 28, w, h - 28, body_bg);

View file

@ -30,9 +30,10 @@ void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y);
void pxl8_gui_cursor_up(pxl8_gui_state* state);
bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, const char* label);
u8 pxl8_gui_color(pxl8_gfx* gfx, u8 index);
void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color);
bool pxl8_gui_slider(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, f32* value, f32 min_val, f32 max_val);
bool pxl8_gui_slider_int(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, i32* value, i32 min_val, i32 max_val);
void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color);
void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title);
#ifdef __cplusplus

View file

@ -22,7 +22,7 @@
#define PXL8_UI_GRAY 15
static const u32 pxl8_ui_palette[PXL8_UI_PALETTE_SIZE] = {
0xFF282828,
0xFF21201d,
0xFF3c3836,
0xFF504945,
0xFF665c54,

View file

@ -68,6 +68,7 @@ pxl8.pop_target = gfx.pop_target
pxl8.key_down = input.key_down
pxl8.key_pressed = input.key_pressed
pxl8.key_released = input.key_released
pxl8.set_key_down = input.set_key_down
pxl8.mouse_dx = input.mouse_dx
pxl8.mouse_dy = input.mouse_dy
pxl8.mouse_wheel_x = input.mouse_wheel_x
@ -132,6 +133,7 @@ pxl8.Gui = gui.Gui
pxl8.create_gui = gui.Gui.new
pxl8.gui_label = gui.label
pxl8.gui_window = gui.window
pxl8.gui_color = gui.color
pxl8.mat4_identity = math.mat4_identity
pxl8.mat4_lookat = math.mat4_lookat
@ -235,8 +237,12 @@ 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.get_world = world.World.get
pxl8.sim_config = world.sim_config
pxl8.sim_move_player = world.sim_move_player
pxl8.sim_trace = world.sim_trace
pxl8.sim_check_ground = world.sim_check_ground
pxl8.make_input_msg = world.make_input_msg
return pxl8

View file

@ -79,4 +79,8 @@ function gui.window(x, y, w, h, title)
C.pxl8_gui_window(core.gfx, x, y, w, h, title)
end
function gui.color(index)
return C.pxl8_gui_color(core.gfx, index)
end
return gui

View file

@ -16,6 +16,10 @@ function input.key_released(key)
return C.pxl8_key_released(core.input, key)
end
function input.set_key_down(key, down)
C.pxl8_set_key_down(core.input, key, down)
end
function input.mouse_wheel_x()
return C.pxl8_mouse_wheel_x(core.input)
end

View file

@ -23,12 +23,8 @@ function Net:connected()
return C.pxl8_net_connected(self._ptr)
end
function Net:enter_chunk(chunk_id)
return C.pxl8_net_enter_chunk(self._ptr, chunk_id or 1) == 0
end
function Net:exit_chunk(x, y, z)
return C.pxl8_net_exit_chunk(self._ptr, x or 0, y or 0, z or 0) == 0
function Net:enter_scene(chunk_type, chunk_id, x, y, z)
return C.pxl8_net_enter_scene(self._ptr, chunk_type or 0, chunk_id or 0, x or 0, y or 0, z or 0) == 0
end
function Net:send_input(input)
@ -44,10 +40,6 @@ function Net:send_input(input)
return C.pxl8_net_send_input(self._ptr, msg) == 0
end
function Net:set_chunk_settings(render_distance, sim_distance)
return C.pxl8_net_send_chunk_settings(self._ptr, render_distance or 3, sim_distance or 4) == 0
end
function Net:spawn(x, y, z, yaw, pitch)
return C.pxl8_net_spawn(self._ptr, x or 0, y or 0, z or 0, yaw or 0, pitch or 0) == 0
end

View file

@ -4,9 +4,6 @@ local core = require("pxl8.core")
local world = {}
world.CHUNK_VXL = 0
world.CHUNK_BSP = 1
local Bsp = {}
Bsp.__index = Bsp
@ -33,25 +30,12 @@ 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)
if self._ptr.bsp == nil then return nil end
return setmetatable({ _ptr = self._ptr.bsp }, Bsp)
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.voxels ~= nil
end
return false
end
function Chunk:type()
if self._ptr == nil then return nil end
return self._ptr.type
return self._ptr ~= nil and self._ptr.bsp ~= nil
end
function Chunk:version()
@ -76,14 +60,6 @@ function World:active_chunk()
return setmetatable({ _ptr = ptr }, Chunk)
end
function World:get_render_distance()
return C.pxl8_world_get_render_distance(self._ptr)
end
function World:get_sim_distance()
return C.pxl8_world_get_sim_distance(self._ptr)
end
function World:init_local_player(x, y, z)
C.pxl8_world_init_local_player(self._ptr, x, y, z)
end
@ -113,20 +89,67 @@ function World:set_bsp_material(material_id, material)
C.pxl8_world_set_bsp_material(self._ptr, material_id, material._ptr)
end
function World:set_render_distance(distance)
C.pxl8_world_set_render_distance(self._ptr, distance)
end
function World:set_sim_distance(distance)
C.pxl8_world_set_sim_distance(self._ptr, distance)
end
function World:sweep(from_x, from_y, from_z, to_x, to_y, to_z, radius)
local from = ffi.new("pxl8_vec3", {x = from_x, y = from_y, z = from_z})
local to = ffi.new("pxl8_vec3", {x = to_x, y = to_y, z = to_z})
return C.pxl8_world_sweep(self._ptr, from, to, radius)
end
function World:set_sim_config(config)
C.pxl8_world_set_sim_config(self._ptr, config)
end
function World:push_input(input_msg)
C.pxl8_world_push_input(self._ptr, input_msg)
end
world.World = World
function world.sim_config(opts)
opts = opts or {}
return ffi.new("pxl8_sim_config", {
move_speed = opts.move_speed or 180.0,
ground_accel = opts.ground_accel or 10.0,
air_accel = opts.air_accel or 1.0,
stop_speed = opts.stop_speed or 100.0,
friction = opts.friction or 6.0,
gravity = opts.gravity or 800.0,
jump_velocity = opts.jump_velocity or 200.0,
player_radius = opts.player_radius or 16.0,
player_height = opts.player_height or 72.0,
max_pitch = opts.max_pitch or 1.5,
})
end
function world.sim_move_player(entity, input_msg, sim_world, config, dt)
C.pxl8_sim_move_player(entity, input_msg, sim_world, config, dt)
end
function world.sim_trace(sim_world, from_x, from_y, from_z, to_x, to_y, to_z, radius, height)
local from = ffi.new("pxl8_vec3", {x = from_x, y = from_y, z = from_z})
local to = ffi.new("pxl8_vec3", {x = to_x, y = to_y, z = to_z})
return C.pxl8_sim_trace(sim_world, from, to, radius, height)
end
function world.sim_check_ground(sim_world, x, y, z, radius)
local pos = ffi.new("pxl8_vec3", {x = x, y = y, z = z})
return C.pxl8_sim_check_ground(sim_world, pos, radius)
end
function World:sim_world(x, y, z)
local pos = ffi.new("pxl8_vec3", {x = x, y = y, z = z})
return C.pxl8_world_sim_world(self._ptr, pos)
end
function world.make_input_msg(opts)
opts = opts or {}
return ffi.new("pxl8_input_msg", {
move_x = opts.move_x or 0,
move_y = opts.move_y or 0,
look_dx = opts.look_dx or 0,
look_dy = opts.look_dy or 0,
buttons = opts.buttons or 0,
})
end
return world

View file

@ -402,23 +402,10 @@ u32 pxl8_net_chunk_id(const pxl8_net* net) {
}
u8 pxl8_net_chunk_type(const pxl8_net* net) {
if (!net) return PXL8_CHUNK_TYPE_VXL;
if (!net) return PXL8_CHUNK_TYPE_BSP;
return net->chunk_type;
}
pxl8_result pxl8_net_send_chunk_settings(pxl8_net* net, i32 render_distance, i32 sim_distance) {
if (!net) return PXL8_ERROR_NULL_POINTER;
if (!net->connected) return PXL8_ERROR_NOT_CONNECTED;
pxl8_command_msg cmd = {0};
cmd.cmd_type = PXL8_CMD_SET_CHUNK_SETTINGS;
pxl8_pack_i32_be(cmd.payload, 0, render_distance);
pxl8_pack_i32_be(cmd.payload, 4, sim_distance);
cmd.payload_size = 8;
return pxl8_net_send_command(net, &cmd);
}
pxl8_result pxl8_net_spawn(pxl8_net* net, f32 x, f32 y, f32 z, f32 yaw, f32 pitch) {
if (!net) return PXL8_ERROR_NULL_POINTER;
if (!net->connected) return PXL8_ERROR_NOT_CONNECTED;
@ -435,28 +422,18 @@ pxl8_result pxl8_net_spawn(pxl8_net* net, f32 x, f32 y, f32 z, f32 yaw, f32 pitc
return pxl8_net_send_command(net, &cmd);
}
pxl8_result pxl8_net_exit_chunk(pxl8_net* net, f32 x, f32 y, f32 z) {
pxl8_result pxl8_net_enter_scene(pxl8_net* net, u8 chunk_type, u32 chunk_id, f32 x, f32 y, f32 z) {
if (!net) return PXL8_ERROR_NULL_POINTER;
if (!net->connected) return PXL8_ERROR_NOT_CONNECTED;
pxl8_command_msg cmd = {0};
cmd.cmd_type = PXL8_CMD_EXIT_CHUNK;
pxl8_pack_f32_be(cmd.payload, 0, x);
pxl8_pack_f32_be(cmd.payload, 4, y);
pxl8_pack_f32_be(cmd.payload, 8, z);
cmd.payload_size = 12;
return pxl8_net_send_command(net, &cmd);
}
pxl8_result pxl8_net_enter_chunk(pxl8_net* net, u32 chunk_id) {
if (!net) return PXL8_ERROR_NULL_POINTER;
if (!net->connected) return PXL8_ERROR_NOT_CONNECTED;
pxl8_command_msg cmd = {0};
cmd.cmd_type = PXL8_CMD_ENTER_CHUNK;
cmd.cmd_type = PXL8_CMD_ENTER_SCENE;
pxl8_pack_u32_be(cmd.payload, 0, chunk_id);
cmd.payload_size = 4;
cmd.payload[4] = chunk_type;
pxl8_pack_f32_be(cmd.payload, 8, x);
pxl8_pack_f32_be(cmd.payload, 12, y);
pxl8_pack_f32_be(cmd.payload, 16, z);
cmd.payload_size = 20;
return pxl8_net_send_command(net, &cmd);
}

View file

@ -57,11 +57,8 @@ void pxl8_net_set_world(pxl8_net* net, pxl8_world* world);
u32 pxl8_net_chunk_id(const pxl8_net* net);
u8 pxl8_net_chunk_type(const pxl8_net* net);
pxl8_result pxl8_net_send_chunk_settings(pxl8_net* net, i32 render_distance, i32 sim_distance);
pxl8_result pxl8_net_spawn(pxl8_net* net, f32 x, f32 y, f32 z, f32 yaw, f32 pitch);
pxl8_result pxl8_net_exit_chunk(pxl8_net* net, f32 x, f32 y, f32 z);
pxl8_result pxl8_net_enter_chunk(pxl8_net* net, u32 chunk_id);
pxl8_result pxl8_net_enter_scene(pxl8_net* net, u8 chunk_type, u32 chunk_id, f32 x, f32 y, f32 z);
#ifdef PXL8_ASYNC_THREADS
void pxl8_net_start_thread(pxl8_net* net);

View file

@ -35,9 +35,7 @@ typedef struct pxl8_msg_header {
typedef enum pxl8_cmd_type {
PXL8_CMD_NONE = 0,
PXL8_CMD_SPAWN_ENTITY,
PXL8_CMD_EXIT_CHUNK,
PXL8_CMD_ENTER_CHUNK,
PXL8_CMD_SET_CHUNK_SETTINGS,
PXL8_CMD_ENTER_SCENE,
} pxl8_cmd_type;
typedef struct pxl8_input_msg {
@ -76,7 +74,6 @@ 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

View file

@ -73,6 +73,7 @@ static const char* pxl8_ffi_cdefs =
"bool pxl8_key_down(const pxl8_input_state* input, const char* key_name);\n"
"bool pxl8_key_pressed(const pxl8_input_state* input, const char* key_name);\n"
"bool pxl8_key_released(const pxl8_input_state* input, const char* key_name);\n"
"void pxl8_set_key_down(pxl8_input_state* input, const char* key_name, bool down);\n"
"bool pxl8_mouse_pressed(const pxl8_input_state* input, i32 button);\n"
"bool pxl8_mouse_released(const pxl8_input_state* input, i32 button);\n"
"int pxl8_mouse_wheel_x(const pxl8_input_state* input);\n"
@ -397,23 +398,15 @@ 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_vxl_chunk pxl8_vxl_chunk;\n"
"\n"
"u32 pxl8_bsp_face_count(const pxl8_bsp* bsp);\n"
"pxl8_vec3 pxl8_bsp_face_normal(const pxl8_bsp* bsp, u32 face_id);\n"
"u8 pxl8_bsp_light_at(const pxl8_bsp* bsp, f32 x, f32 y, f32 z, u8 ambient);\n"
"\n"
"typedef enum { PXL8_WORLD_CHUNK_VXL = 0, PXL8_WORLD_CHUNK_BSP = 1 } pxl8_world_chunk_type;\n"
"\n"
"typedef struct pxl8_world_chunk {\n"
" pxl8_world_chunk_type type;\n"
" u32 id;\n"
" u32 version;\n"
" i32 cx, cy, cz;\n"
" union {\n"
" pxl8_bsp* bsp;\n"
" pxl8_vxl_chunk* voxels;\n"
" };\n"
" pxl8_bsp* bsp;\n"
"} pxl8_world_chunk;\n"
"\n"
"typedef struct pxl8_world pxl8_world;\n"
@ -432,10 +425,6 @@ static const char* pxl8_ffi_cdefs =
"pxl8_ray pxl8_world_sweep(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, float radius);\n"
"void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);\n"
"void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_gfx_material* material);\n"
"i32 pxl8_world_get_render_distance(const pxl8_world* world);\n"
"void pxl8_world_set_render_distance(pxl8_world* world, i32 distance);\n"
"i32 pxl8_world_get_sim_distance(const pxl8_world* world);\n"
"void pxl8_world_set_sim_distance(pxl8_world* world, i32 distance);\n"
"\n"
"typedef struct pxl8_sim_entity {\n"
" pxl8_vec3 pos;\n"
@ -463,6 +452,7 @@ static const char* pxl8_ffi_cdefs =
"bool pxl8_gui_slider_int(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, i32* value, i32 min_val, i32 max_val);\n"
"void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title);\n"
"void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color);\n"
"u8 pxl8_gui_color(pxl8_gfx* gfx, u8 index);\n"
"bool pxl8_gui_is_hovering(const pxl8_gui_state* state);\n"
"void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y);\n"
"\n"
@ -524,7 +514,7 @@ static const char* pxl8_ffi_cdefs =
"\n"
"typedef struct pxl8_net pxl8_net;\n"
"typedef struct pxl8_net_config { const char* address; u16 port; } pxl8_net_config;\n"
"typedef enum pxl8_cmd_type { PXL8_CMD_NONE = 0, PXL8_CMD_SPAWN_ENTITY, PXL8_CMD_EXIT_CHUNK, PXL8_CMD_ENTER_CHUNK } pxl8_cmd_type;\n"
"typedef enum pxl8_cmd_type { PXL8_CMD_NONE = 0, PXL8_CMD_SPAWN_ENTITY, PXL8_CMD_ENTER_SCENE } pxl8_cmd_type;\n"
"\n"
"typedef struct pxl8_command_msg {\n"
" u16 cmd_type;\n"
@ -544,6 +534,32 @@ static const char* pxl8_ffi_cdefs =
" u64 timestamp;\n"
"} pxl8_input_msg;\n"
"\n"
"typedef struct pxl8_sim_config {\n"
" f32 move_speed;\n"
" f32 ground_accel;\n"
" f32 air_accel;\n"
" f32 stop_speed;\n"
" f32 friction;\n"
" f32 gravity;\n"
" f32 jump_velocity;\n"
" f32 player_radius;\n"
" f32 player_height;\n"
" f32 max_pitch;\n"
"} pxl8_sim_config;\n"
"\n"
"typedef struct pxl8_sim_world {\n"
" const pxl8_bsp* bsp;\n"
"} pxl8_sim_world;\n"
"\n"
"void pxl8_sim_move_player(pxl8_sim_entity* ent, const pxl8_input_msg* input, const pxl8_sim_world* world, const pxl8_sim_config* cfg, f32 dt);\n"
"void pxl8_sim_integrate(pxl8_sim_entity* ent, const pxl8_sim_world* world, const pxl8_sim_config* cfg, f32 dt);\n"
"pxl8_vec3 pxl8_sim_trace(const pxl8_sim_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius, f32 height);\n"
"bool pxl8_sim_check_ground(const pxl8_sim_world* world, pxl8_vec3 pos, f32 radius);\n"
"\n"
"pxl8_sim_world pxl8_world_sim_world(const pxl8_world* world, pxl8_vec3 pos);\n"
"void pxl8_world_set_sim_config(pxl8_world* world, const pxl8_sim_config* config);\n"
"void pxl8_world_push_input(pxl8_world* world, const pxl8_input_msg* input);\n"
"\n"
"typedef struct pxl8_entity_state {\n"
" u64 entity_id;\n"
" u8 userdata[56];\n"
@ -578,10 +594,8 @@ static const char* pxl8_ffi_cdefs =
"void pxl8_net_predicted_tick_set(pxl8_net* net, u64 tick);\n"
"i32 pxl8_net_send_command(pxl8_net* net, const pxl8_command_msg* cmd);\n"
"i32 pxl8_net_send_input(pxl8_net* net, const pxl8_input_msg* input);\n"
"i32 pxl8_net_send_chunk_settings(pxl8_net* net, i32 render_distance, i32 sim_distance);\n"
"i32 pxl8_net_spawn(pxl8_net* net, f32 x, f32 y, f32 z, f32 yaw, f32 pitch);\n"
"i32 pxl8_net_exit_chunk(pxl8_net* net, f32 x, f32 y, f32 z);\n"
"i32 pxl8_net_enter_chunk(pxl8_net* net, u32 chunk_id);\n"
"i32 pxl8_net_enter_scene(pxl8_net* net, u8 chunk_type, u32 chunk_id, f32 x, f32 y, f32 z);\n"
"const pxl8_snapshot_header* pxl8_net_snapshot(const pxl8_net* net);\n"
"u64 pxl8_net_tick(const pxl8_net* net);\n"
"void pxl8_net_update(pxl8_net* net, f32 dt);\n"

View file

@ -2,15 +2,6 @@
#include <math.h>
static usize vxl_world_to_local(f32 x, i32 chunk_coord) {
f32 chunk_base = chunk_coord * PXL8_VXL_WORLD_CHUNK_SIZE;
f32 local_world = x - chunk_base;
i32 local_voxel = (i32)floorf(local_world / PXL8_VXL_SCALE);
if (local_voxel < 0) return 0;
if (local_voxel >= PXL8_VXL_CHUNK_SIZE) return PXL8_VXL_CHUNK_SIZE - 1;
return (usize)local_voxel;
}
static i32 bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos) {
if (!bsp || bsp->num_nodes == 0) return -1;
@ -59,82 +50,36 @@ pxl8_vec3 pxl8_bsp_trace(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to, f32
return result;
}
bool pxl8_vxl_point_solid(const pxl8_vxl_chunk* chunk, i32 cx, i32 cy, i32 cz,
f32 x, f32 y, f32 z) {
if (!chunk) return false;
usize lx = vxl_world_to_local(x, cx);
usize ly = vxl_world_to_local(y, cy);
usize lz = vxl_world_to_local(z, cz);
return pxl8_vxl_block_get(chunk, (i32)lx, (i32)ly, (i32)lz) != PXL8_VXL_BLOCK_AIR;
}
static bool vxl_point_clear(const pxl8_vxl_chunk* chunk, i32 cx, i32 cy, i32 cz,
f32 x, f32 y, f32 z, f32 radius) {
if (pxl8_vxl_point_solid(chunk, cx, cy, cz, x - radius, y, z)) return false;
if (pxl8_vxl_point_solid(chunk, cx, cy, cz, x + radius, y, z)) return false;
if (pxl8_vxl_point_solid(chunk, cx, cy, cz, x, y - radius, z)) return false;
if (pxl8_vxl_point_solid(chunk, cx, cy, cz, x, y + radius, z)) return false;
if (pxl8_vxl_point_solid(chunk, cx, cy, cz, x, y, z - radius)) return false;
if (pxl8_vxl_point_solid(chunk, cx, cy, cz, x, y, z + radius)) return false;
return true;
}
pxl8_vec3 pxl8_vxl_trace(const pxl8_vxl_chunk* chunk, i32 cx, i32 cy, i32 cz,
pxl8_vec3 from, pxl8_vec3 to, f32 radius) {
if (!chunk) return to;
if (vxl_point_clear(chunk, cx, cy, cz, to.x, to.y, to.z, radius)) {
return to;
}
bool x_ok = vxl_point_clear(chunk, cx, cy, cz, to.x, from.y, from.z, radius);
bool y_ok = vxl_point_clear(chunk, cx, cy, cz, from.x, to.y, from.z, radius);
bool z_ok = vxl_point_clear(chunk, cx, cy, cz, from.x, from.y, to.z, radius);
pxl8_vec3 result = from;
if (x_ok) result.x = to.x;
if (y_ok) result.y = to.y;
if (z_ok) result.z = to.z;
return result;
}
pxl8_vec3 pxl8_sim_trace(const pxl8_sim_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius) {
pxl8_vec3 pxl8_sim_trace(const pxl8_sim_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius, f32 height) {
(void)height;
if (!world) return to;
if (world->bsp) {
return pxl8_bsp_trace(world->bsp, from, to, radius);
}
if (world->vxl) {
return pxl8_vxl_trace(world->vxl, world->vxl_cx, world->vxl_cy, world->vxl_cz,
from, to, radius);
}
return to;
}
bool pxl8_sim_check_ground(const pxl8_sim_world* world, pxl8_vec3 pos, f32 radius) {
if (!world) return true;
if (!world->bsp && !world->vxl) return true;
if (!world->bsp) return true;
pxl8_vec3 down = {pos.x, pos.y - 2.0f, pos.z};
pxl8_vec3 result = pxl8_sim_trace(world, pos, down, radius);
pxl8_vec3 result = pxl8_sim_trace(world, pos, down, radius, 0.0f);
return result.y > down.y;
}
void pxl8_sim_move_player(pxl8_sim_entity* ent, const pxl8_input_msg* input,
const pxl8_sim_world* world, f32 dt) {
if (!ent || !input) return;
const pxl8_sim_world* world, const pxl8_sim_config* cfg, f32 dt) {
if (!ent || !input || !cfg) return;
if (!(ent->flags & PXL8_SIM_FLAG_ALIVE)) return;
ent->yaw -= input->look_dx * 0.008f;
f32 new_pitch = ent->pitch - input->look_dy * 0.008f;
if (new_pitch > PXL8_SIM_MAX_PITCH) new_pitch = PXL8_SIM_MAX_PITCH;
if (new_pitch < -PXL8_SIM_MAX_PITCH) new_pitch = -PXL8_SIM_MAX_PITCH;
if (new_pitch > cfg->max_pitch) new_pitch = cfg->max_pitch;
if (new_pitch < -cfg->max_pitch) new_pitch = -cfg->max_pitch;
ent->pitch = new_pitch;
f32 sin_yaw = sinf(ent->yaw);
@ -150,7 +95,7 @@ void pxl8_sim_move_player(pxl8_sim_entity* ent, const pxl8_input_msg* input,
f32 ny = input->move_y / input_len;
move_dir.x = cos_yaw * nx - sin_yaw * ny;
move_dir.z = -sin_yaw * nx - cos_yaw * ny;
target_speed = PXL8_SIM_MOVE_SPEED;
target_speed = cfg->move_speed;
}
ent->vel.x = move_dir.x * target_speed;
@ -159,13 +104,13 @@ void pxl8_sim_move_player(pxl8_sim_entity* ent, const pxl8_input_msg* input,
bool grounded = (ent->flags & PXL8_SIM_FLAG_GROUNDED) != 0;
if (grounded && (input->buttons & 1)) {
ent->vel.y = PXL8_SIM_JUMP_VELOCITY;
ent->vel.y = cfg->jump_velocity;
ent->flags &= ~PXL8_SIM_FLAG_GROUNDED;
grounded = false;
}
if (!grounded) {
ent->vel.y -= PXL8_SIM_GRAVITY * dt;
ent->vel.y -= cfg->gravity * dt;
}
pxl8_vec3 target = {
@ -174,10 +119,27 @@ void pxl8_sim_move_player(pxl8_sim_entity* ent, const pxl8_input_msg* input,
ent->pos.z + ent->vel.z * dt
};
pxl8_vec3 new_pos = pxl8_sim_trace(world, ent->pos, target, PXL8_SIM_PLAYER_RADIUS);
pxl8_vec3 new_pos = pxl8_sim_trace(world, ent->pos, target, cfg->player_radius, cfg->player_height);
if (ent->vel.y < 0 && new_pos.y > target.y + 0.01f) {
f32 hi = new_pos.y;
f32 lo = target.y;
for (i32 i = 0; i < 8; i++) {
f32 mid = (hi + lo) * 0.5f;
pxl8_vec3 test = {new_pos.x, mid, new_pos.z};
pxl8_vec3 result = pxl8_sim_trace(world, new_pos, test, cfg->player_radius, cfg->player_height);
if (result.y > mid + 0.01f) {
lo = mid;
} else {
hi = mid;
}
}
new_pos.y = hi;
}
ent->pos = new_pos;
if (pxl8_sim_check_ground(world, ent->pos, PXL8_SIM_PLAYER_RADIUS)) {
if (pxl8_sim_check_ground(world, ent->pos, cfg->player_radius)) {
ent->flags |= PXL8_SIM_FLAG_GROUNDED;
if (ent->vel.y < 0) ent->vel.y = 0;
} else {
@ -185,19 +147,20 @@ void pxl8_sim_move_player(pxl8_sim_entity* ent, const pxl8_input_msg* input,
}
}
void pxl8_sim_integrate(pxl8_sim_entity* ent, const pxl8_sim_world* world, f32 dt) {
if (!ent) return;
void pxl8_sim_integrate(pxl8_sim_entity* ent, const pxl8_sim_world* world,
const pxl8_sim_config* cfg, f32 dt) {
if (!ent || !cfg) return;
if (!(ent->flags & PXL8_SIM_FLAG_ALIVE)) return;
if (ent->flags & PXL8_SIM_FLAG_PLAYER) return;
bool grounded = (ent->flags & PXL8_SIM_FLAG_GROUNDED) != 0;
ent->vel.y -= PXL8_SIM_GRAVITY * dt;
ent->vel.y -= cfg->gravity * dt;
if (grounded) {
f32 speed = sqrtf(ent->vel.x * ent->vel.x + ent->vel.z * ent->vel.z);
if (speed > 0.0f) {
f32 drop = speed * PXL8_SIM_FRICTION * dt;
f32 drop = speed * cfg->friction * dt;
f32 new_speed = speed - drop;
if (new_speed < 0.0f) new_speed = 0.0f;
f32 scale = new_speed / speed;
@ -212,10 +175,27 @@ void pxl8_sim_integrate(pxl8_sim_entity* ent, const pxl8_sim_world* world, f32 d
ent->pos.z + ent->vel.z * dt
};
pxl8_vec3 new_pos = pxl8_sim_trace(world, ent->pos, target, PXL8_SIM_PLAYER_RADIUS);
pxl8_vec3 new_pos = pxl8_sim_trace(world, ent->pos, target, cfg->player_radius, cfg->player_height);
if (ent->vel.y < 0 && new_pos.y > target.y + 0.01f) {
f32 hi = new_pos.y;
f32 lo = target.y;
for (i32 i = 0; i < 8; i++) {
f32 mid = (hi + lo) * 0.5f;
pxl8_vec3 test = {new_pos.x, mid, new_pos.z};
pxl8_vec3 result = pxl8_sim_trace(world, new_pos, test, cfg->player_radius, cfg->player_height);
if (result.y > mid + 0.01f) {
lo = mid;
} else {
hi = mid;
}
}
new_pos.y = hi;
}
ent->pos = new_pos;
if (pxl8_sim_check_ground(world, ent->pos, PXL8_SIM_PLAYER_RADIUS)) {
if (pxl8_sim_check_ground(world, ent->pos, cfg->player_radius)) {
ent->flags |= PXL8_SIM_FLAG_GROUNDED;
if (ent->vel.y < 0.0f) ent->vel.y = 0.0f;
} else {

View file

@ -4,7 +4,6 @@
#include "pxl8_math.h"
#include "pxl8_protocol.h"
#include "pxl8_types.h"
#include "pxl8_vxl.h"
#ifdef __cplusplus
extern "C" {
@ -14,15 +13,18 @@ extern "C" {
#define PXL8_SIM_FLAG_PLAYER (1 << 1)
#define PXL8_SIM_FLAG_GROUNDED (1 << 2)
#define PXL8_SIM_MOVE_SPEED 180.0f
#define PXL8_SIM_GROUND_ACCEL 10.0f
#define PXL8_SIM_AIR_ACCEL 1.0f
#define PXL8_SIM_STOP_SPEED 100.0f
#define PXL8_SIM_FRICTION 6.0f
#define PXL8_SIM_GRAVITY 800.0f
#define PXL8_SIM_JUMP_VELOCITY 200.0f
#define PXL8_SIM_PLAYER_RADIUS 16.0f
#define PXL8_SIM_MAX_PITCH 1.5f
typedef struct pxl8_sim_config {
f32 move_speed;
f32 ground_accel;
f32 air_accel;
f32 stop_speed;
f32 friction;
f32 gravity;
f32 jump_velocity;
f32 player_radius;
f32 player_height;
f32 max_pitch;
} pxl8_sim_config;
typedef struct pxl8_sim_entity {
pxl8_vec3 pos;
@ -36,20 +38,15 @@ typedef struct pxl8_sim_entity {
typedef struct pxl8_sim_world {
const pxl8_bsp* bsp;
const pxl8_vxl_chunk* vxl;
i32 vxl_cx, vxl_cy, vxl_cz;
} pxl8_sim_world;
bool pxl8_bsp_point_solid(const pxl8_bsp* bsp, pxl8_vec3 pos);
pxl8_vec3 pxl8_bsp_trace(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to, f32 radius);
bool pxl8_vxl_point_solid(const pxl8_vxl_chunk* chunk, i32 cx, i32 cy, i32 cz, f32 x, f32 y, f32 z);
pxl8_vec3 pxl8_vxl_trace(const pxl8_vxl_chunk* chunk, i32 cx, i32 cy, i32 cz, pxl8_vec3 from, pxl8_vec3 to, f32 radius);
pxl8_vec3 pxl8_sim_trace(const pxl8_sim_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius);
pxl8_vec3 pxl8_sim_trace(const pxl8_sim_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius, f32 height);
bool pxl8_sim_check_ground(const pxl8_sim_world* world, pxl8_vec3 pos, f32 radius);
void pxl8_sim_move_player(pxl8_sim_entity* ent, const pxl8_input_msg* input, const pxl8_sim_world* world, f32 dt);
void pxl8_sim_integrate(pxl8_sim_entity* ent, const pxl8_sim_world* world, f32 dt);
void pxl8_sim_move_player(pxl8_sim_entity* ent, const pxl8_input_msg* input, const pxl8_sim_world* world, const pxl8_sim_config* cfg, f32 dt);
void pxl8_sim_integrate(pxl8_sim_entity* ent, const pxl8_sim_world* world, const pxl8_sim_config* cfg, f32 dt);
#ifdef __cplusplus
}

View file

@ -1,319 +0,0 @@
#include "pxl8_vxl.h"
#include <string.h>
#include "pxl8_mem.h"
typedef struct pxl8_vxl_block_def {
char name[32];
u8 texture_top;
u8 texture_side;
u8 texture_bottom;
pxl8_vxl_geometry geometry;
bool registered;
} pxl8_vxl_block_def;
struct pxl8_vxl_block_registry {
pxl8_vxl_block_def blocks[PXL8_VXL_BLOCK_COUNT];
};
static inline bool vxl_in_bounds(i32 x, i32 y, i32 z) {
return x >= 0 && x < PXL8_VXL_CHUNK_SIZE &&
y >= 0 && y < PXL8_VXL_CHUNK_SIZE &&
z >= 0 && z < PXL8_VXL_CHUNK_SIZE;
}
static inline u32 vxl_index(i32 x, i32 y, i32 z) {
return (u32)(x + y * PXL8_VXL_CHUNK_SIZE + z * PXL8_VXL_CHUNK_SIZE * PXL8_VXL_CHUNK_SIZE);
}
static pxl8_vxl_octree_node* octree_alloc_children(void) {
pxl8_vxl_octree_node* children = pxl8_calloc(8, sizeof(pxl8_vxl_octree_node));
for (i32 i = 0; i < 8; i++) {
children[i].type = PXL8_VXL_OCTREE_UNIFORM;
children[i].block = PXL8_VXL_BLOCK_AIR;
}
return children;
}
static void octree_free_node(pxl8_vxl_octree_node* node) {
if (node->type == PXL8_VXL_OCTREE_BRANCH && node->children) {
for (i32 i = 0; i < 8; i++) {
octree_free_node(&node->children[i]);
}
pxl8_free(node->children);
node->children = NULL;
}
node->type = PXL8_VXL_OCTREE_UNIFORM;
node->block = PXL8_VXL_BLOCK_AIR;
}
static u8 octree_get(const pxl8_vxl_octree_node* node, i32 depth, i32 x, i32 y, i32 z) {
if (node->type == PXL8_VXL_OCTREE_UNIFORM) {
return node->block;
}
if (depth == 0 || !node->children) {
return PXL8_VXL_BLOCK_AIR;
}
i32 half = 1 << (depth - 1);
i32 idx = ((x >= half) ? 1 : 0) |
((y >= half) ? 2 : 0) |
((z >= half) ? 4 : 0);
return octree_get(&node->children[idx], depth - 1,
x & (half - 1), y & (half - 1), z & (half - 1));
}
static void octree_subdivide(pxl8_vxl_octree_node* node) {
if (node->type == PXL8_VXL_OCTREE_BRANCH) return;
u8 block = node->block;
node->type = PXL8_VXL_OCTREE_BRANCH;
node->children = octree_alloc_children();
for (i32 i = 0; i < 8; i++) {
node->children[i].type = PXL8_VXL_OCTREE_UNIFORM;
node->children[i].block = block;
}
}
static bool octree_try_simplify(pxl8_vxl_octree_node* node) {
if (node->type != PXL8_VXL_OCTREE_BRANCH || !node->children) {
return false;
}
for (i32 i = 0; i < 8; i++) {
if (node->children[i].type != PXL8_VXL_OCTREE_UNIFORM) {
return false;
}
}
u8 first_block = node->children[0].block;
for (i32 i = 1; i < 8; i++) {
if (node->children[i].block != first_block) {
return false;
}
}
pxl8_free(node->children);
node->children = NULL;
node->type = PXL8_VXL_OCTREE_UNIFORM;
node->block = first_block;
return true;
}
static void octree_set(pxl8_vxl_octree_node* node, i32 depth, i32 x, i32 y, i32 z, u8 block) {
if (depth == 0) {
if (node->type == PXL8_VXL_OCTREE_BRANCH) {
octree_free_node(node);
}
node->type = PXL8_VXL_OCTREE_UNIFORM;
node->block = block;
return;
}
if (node->type == PXL8_VXL_OCTREE_UNIFORM) {
if (node->block == block) {
return;
}
octree_subdivide(node);
}
i32 half = 1 << (depth - 1);
i32 idx = ((x >= half) ? 1 : 0) |
((y >= half) ? 2 : 0) |
((z >= half) ? 4 : 0);
octree_set(&node->children[idx], depth - 1,
x & (half - 1), y & (half - 1), z & (half - 1), block);
octree_try_simplify(node);
}
static void octree_linearize_recursive(const pxl8_vxl_octree_node* node, i32 depth,
i32 ox, i32 oy, i32 oz, u8* out) {
i32 size = 1 << depth;
if (node->type == PXL8_VXL_OCTREE_UNIFORM) {
for (i32 z = 0; z < size; z++) {
for (i32 y = 0; y < size; y++) {
for (i32 x = 0; x < size; x++) {
out[vxl_index(ox + x, oy + y, oz + z)] = node->block;
}
}
}
return;
}
if (depth == 0 || !node->children) return;
i32 half = size / 2;
for (i32 i = 0; i < 8; i++) {
i32 cx = ox + ((i & 1) ? half : 0);
i32 cy = oy + ((i & 2) ? half : 0);
i32 cz = oz + ((i & 4) ? half : 0);
octree_linearize_recursive(&node->children[i], depth - 1, cx, cy, cz, out);
}
}
static void octree_build_recursive(pxl8_vxl_octree_node* node, i32 depth,
i32 ox, i32 oy, i32 oz, const u8* data) {
i32 size = 1 << depth;
if (depth == 0) {
node->type = PXL8_VXL_OCTREE_UNIFORM;
node->block = data[vxl_index(ox, oy, oz)];
return;
}
u8 first_block = data[vxl_index(ox, oy, oz)];
bool all_same = true;
for (i32 z = 0; z < size && all_same; z++) {
for (i32 y = 0; y < size && all_same; y++) {
for (i32 x = 0; x < size && all_same; x++) {
if (data[vxl_index(ox + x, oy + y, oz + z)] != first_block) {
all_same = false;
}
}
}
}
if (all_same) {
node->type = PXL8_VXL_OCTREE_UNIFORM;
node->block = first_block;
return;
}
node->type = PXL8_VXL_OCTREE_BRANCH;
node->children = octree_alloc_children();
i32 half = size / 2;
for (i32 i = 0; i < 8; i++) {
i32 cx = ox + ((i & 1) ? half : 0);
i32 cy = oy + ((i & 2) ? half : 0);
i32 cz = oz + ((i & 4) ? half : 0);
octree_build_recursive(&node->children[i], depth - 1, cx, cy, cz, data);
}
octree_try_simplify(node);
}
pxl8_vxl_chunk* pxl8_vxl_chunk_create(void) {
pxl8_vxl_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_vxl_chunk));
if (!chunk) return NULL;
chunk->root.type = PXL8_VXL_OCTREE_UNIFORM;
chunk->root.block = PXL8_VXL_BLOCK_AIR;
return chunk;
}
void pxl8_vxl_chunk_destroy(pxl8_vxl_chunk* chunk) {
if (!chunk) return;
octree_free_node(&chunk->root);
pxl8_free(chunk);
}
u8 pxl8_vxl_block_get(const pxl8_vxl_chunk* chunk, i32 x, i32 y, i32 z) {
if (!chunk || !vxl_in_bounds(x, y, z)) return PXL8_VXL_BLOCK_AIR;
return octree_get(&chunk->root, PXL8_VXL_OCTREE_DEPTH, x, y, z);
}
void pxl8_vxl_block_set(pxl8_vxl_chunk* chunk, i32 x, i32 y, i32 z, u8 block) {
if (!chunk || !vxl_in_bounds(x, y, z)) return;
octree_set(&chunk->root, PXL8_VXL_OCTREE_DEPTH, x, y, z, block);
}
void pxl8_vxl_block_fill(pxl8_vxl_chunk* chunk, u8 block) {
if (!chunk) return;
octree_free_node(&chunk->root);
chunk->root.type = PXL8_VXL_OCTREE_UNIFORM;
chunk->root.block = block;
}
void pxl8_vxl_block_clear(pxl8_vxl_chunk* chunk) {
pxl8_vxl_block_fill(chunk, PXL8_VXL_BLOCK_AIR);
}
void pxl8_vxl_chunk_linearize(const pxl8_vxl_chunk* chunk, u8* out) {
if (!chunk || !out) return;
octree_linearize_recursive(&chunk->root, PXL8_VXL_OCTREE_DEPTH, 0, 0, 0, out);
}
void pxl8_vxl_chunk_from_linear(pxl8_vxl_chunk* chunk, const u8* data) {
if (!chunk || !data) return;
octree_free_node(&chunk->root);
octree_build_recursive(&chunk->root, PXL8_VXL_OCTREE_DEPTH, 0, 0, 0, data);
}
bool pxl8_vxl_chunk_is_uniform(const pxl8_vxl_chunk* chunk) {
if (!chunk) return true;
return chunk->root.type == PXL8_VXL_OCTREE_UNIFORM;
}
u8 pxl8_vxl_chunk_uniform_block(const pxl8_vxl_chunk* chunk) {
if (!chunk) return PXL8_VXL_BLOCK_AIR;
if (chunk->root.type == PXL8_VXL_OCTREE_UNIFORM) {
return chunk->root.block;
}
return PXL8_VXL_BLOCK_AIR;
}
pxl8_vxl_block_registry* pxl8_vxl_block_registry_create(void) {
pxl8_vxl_block_registry* registry = pxl8_calloc(1, sizeof(pxl8_vxl_block_registry));
if (!registry) return NULL;
pxl8_vxl_block_registry_register(registry, 1, "stone", 8, PXL8_VXL_GEOMETRY_CUBE);
pxl8_vxl_block_registry_register(registry, 2, "dirt", 52, PXL8_VXL_GEOMETRY_CUBE);
pxl8_vxl_block_registry_register_ex(registry, 3, "grass", 200, 52, 52, PXL8_VXL_GEOMETRY_CUBE);
pxl8_vxl_block_registry_register_ex(registry, 4, "grass_slope_n", 200, 52, 52, PXL8_VXL_GEOMETRY_SLOPE_NORTH);
pxl8_vxl_block_registry_register_ex(registry, 5, "grass_slope_s", 200, 52, 52, PXL8_VXL_GEOMETRY_SLOPE_SOUTH);
pxl8_vxl_block_registry_register_ex(registry, 6, "grass_slope_e", 200, 52, 52, PXL8_VXL_GEOMETRY_SLOPE_EAST);
pxl8_vxl_block_registry_register_ex(registry, 7, "grass_slope_w", 200, 52, 52, PXL8_VXL_GEOMETRY_SLOPE_WEST);
return registry;
}
void pxl8_vxl_block_registry_destroy(pxl8_vxl_block_registry* registry) {
pxl8_free(registry);
}
void pxl8_vxl_block_registry_register(pxl8_vxl_block_registry* registry, u8 id, const char* name, u8 texture_id, pxl8_vxl_geometry geo) {
pxl8_vxl_block_registry_register_ex(registry, id, name, texture_id, texture_id, texture_id, geo);
}
void pxl8_vxl_block_registry_register_ex(pxl8_vxl_block_registry* registry, u8 id, const char* name,
u8 texture_top, u8 texture_side, u8 texture_bottom, pxl8_vxl_geometry geo) {
if (!registry || id == PXL8_VXL_BLOCK_AIR) return;
pxl8_vxl_block_def* def = &registry->blocks[id];
strncpy(def->name, name ? name : "", sizeof(def->name) - 1);
def->texture_top = texture_top;
def->texture_side = texture_side;
def->texture_bottom = texture_bottom;
def->geometry = geo;
def->registered = true;
}
const char* pxl8_vxl_block_registry_name(const pxl8_vxl_block_registry* registry, u8 id) {
if (!registry || !registry->blocks[id].registered) return NULL;
return registry->blocks[id].name;
}
pxl8_vxl_geometry pxl8_vxl_block_registry_geometry(const pxl8_vxl_block_registry* registry, u8 id) {
if (!registry || !registry->blocks[id].registered) return PXL8_VXL_GEOMETRY_CUBE;
return registry->blocks[id].geometry;
}
u8 pxl8_vxl_block_registry_texture(const pxl8_vxl_block_registry* registry, u8 id) {
if (!registry || !registry->blocks[id].registered) return 0;
return registry->blocks[id].texture_top;
}
u8 pxl8_vxl_block_registry_texture_for_face(const pxl8_vxl_block_registry* registry, u8 id, i32 face) {
if (!registry || !registry->blocks[id].registered) return 0;
if (face == 3) return registry->blocks[id].texture_top;
if (face == 2) return registry->blocks[id].texture_bottom;
return registry->blocks[id].texture_side;
}

View file

@ -1,78 +0,0 @@
#pragma once
#include "pxl8_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#define PXL8_VXL_BLOCK_COUNT 256
#define PXL8_VXL_CHUNK_SIZE 32
#define PXL8_VXL_CHUNK_VOLUME (PXL8_VXL_CHUNK_SIZE * PXL8_VXL_CHUNK_SIZE * PXL8_VXL_CHUNK_SIZE)
#define PXL8_VXL_SCALE 16.0f
#define PXL8_VXL_WORLD_CHUNK_SIZE (PXL8_VXL_CHUNK_SIZE * PXL8_VXL_SCALE)
#define PXL8_VXL_OCTREE_DEPTH 5
#define PXL8_VXL_BLOCK_AIR 0
#define PXL8_VXL_BLOCK_UNKNOWN 255
typedef struct pxl8_vxl_block_registry pxl8_vxl_block_registry;
typedef enum pxl8_vxl_octree_type {
PXL8_VXL_OCTREE_UNIFORM = 0,
PXL8_VXL_OCTREE_BRANCH = 1
} pxl8_vxl_octree_type;
typedef struct pxl8_vxl_octree_node {
u8 type;
union {
u8 block;
struct pxl8_vxl_octree_node* children;
};
} pxl8_vxl_octree_node;
typedef struct pxl8_vxl_chunk {
pxl8_vxl_octree_node root;
} pxl8_vxl_chunk;
typedef enum pxl8_vxl_geometry {
PXL8_VXL_GEOMETRY_CUBE,
PXL8_VXL_GEOMETRY_SLAB_BOTTOM,
PXL8_VXL_GEOMETRY_SLAB_TOP,
PXL8_VXL_GEOMETRY_SLOPE_NORTH,
PXL8_VXL_GEOMETRY_SLOPE_SOUTH,
PXL8_VXL_GEOMETRY_SLOPE_EAST,
PXL8_VXL_GEOMETRY_SLOPE_WEST,
PXL8_VXL_GEOMETRY_STAIRS_NORTH,
PXL8_VXL_GEOMETRY_STAIRS_SOUTH,
PXL8_VXL_GEOMETRY_STAIRS_EAST,
PXL8_VXL_GEOMETRY_STAIRS_WEST
} pxl8_vxl_geometry;
pxl8_vxl_chunk* pxl8_vxl_chunk_create(void);
void pxl8_vxl_chunk_destroy(pxl8_vxl_chunk* chunk);
u8 pxl8_vxl_block_get(const pxl8_vxl_chunk* chunk, i32 x, i32 y, i32 z);
void pxl8_vxl_block_set(pxl8_vxl_chunk* chunk, i32 x, i32 y, i32 z, u8 block);
void pxl8_vxl_block_fill(pxl8_vxl_chunk* chunk, u8 block);
void pxl8_vxl_block_clear(pxl8_vxl_chunk* chunk);
void pxl8_vxl_chunk_linearize(const pxl8_vxl_chunk* chunk, u8* out);
void pxl8_vxl_chunk_from_linear(pxl8_vxl_chunk* chunk, const u8* data);
bool pxl8_vxl_chunk_is_uniform(const pxl8_vxl_chunk* chunk);
u8 pxl8_vxl_chunk_uniform_block(const pxl8_vxl_chunk* chunk);
pxl8_vxl_block_registry* pxl8_vxl_block_registry_create(void);
void pxl8_vxl_block_registry_destroy(pxl8_vxl_block_registry* registry);
void pxl8_vxl_block_registry_register(pxl8_vxl_block_registry* registry, u8 id, const char* name, u8 texture_id, pxl8_vxl_geometry geo);
void pxl8_vxl_block_registry_register_ex(pxl8_vxl_block_registry* registry, u8 id, const char* name,
u8 texture_top, u8 texture_side, u8 texture_bottom, pxl8_vxl_geometry geo);
const char* pxl8_vxl_block_registry_name(const pxl8_vxl_block_registry* registry, u8 id);
pxl8_vxl_geometry pxl8_vxl_block_registry_geometry(const pxl8_vxl_block_registry* registry, u8 id);
u8 pxl8_vxl_block_registry_texture(const pxl8_vxl_block_registry* registry, u8 id);
u8 pxl8_vxl_block_registry_texture_for_face(const pxl8_vxl_block_registry* registry, u8 id, i32 face);
#ifdef __cplusplus
}
#endif

View file

@ -1,571 +0,0 @@
#include "pxl8_vxl_render.h"
#include <string.h>
#include "pxl8_mem.h"
#include "pxl8_noise.h"
#include "pxl8_vxl.h"
typedef struct pxl8_vxl_greedy_mask {
u8 block[PXL8_VXL_CHUNK_SIZE][PXL8_VXL_CHUNK_SIZE];
bool done[PXL8_VXL_CHUNK_SIZE][PXL8_VXL_CHUNK_SIZE];
} pxl8_vxl_greedy_mask;
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_dirs[6][3] = {
{-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1}
};
static inline bool vxl_in_bounds(i32 x, i32 y, i32 z) {
return x >= 0 && x < PXL8_VXL_CHUNK_SIZE &&
y >= 0 && y < PXL8_VXL_CHUNK_SIZE &&
z >= 0 && z < PXL8_VXL_CHUNK_SIZE;
}
static bool block_is_opaque(u8 block) {
return block != PXL8_VXL_BLOCK_AIR && block != PXL8_VXL_BLOCK_UNKNOWN;
}
static bool block_is_full_cube(u8 block, const pxl8_vxl_block_registry* registry) {
if (block == PXL8_VXL_BLOCK_AIR) return false;
pxl8_vxl_geometry geo = pxl8_vxl_block_registry_geometry(registry, block);
return geo == PXL8_VXL_GEOMETRY_CUBE;
}
static u8 get_block_or_neighbor(const pxl8_vxl_chunk* chunk, const pxl8_vxl_chunk** neighbors, i32 x, i32 y, i32 z) {
if (vxl_in_bounds(x, y, z)) {
return pxl8_vxl_block_get(chunk, x, y, z);
}
i32 nx = x, ny = y, nz = z;
i32 neighbor_idx = -1;
if (x < 0) { neighbor_idx = 0; nx = x + PXL8_VXL_CHUNK_SIZE; }
else if (x >= PXL8_VXL_CHUNK_SIZE) { neighbor_idx = 1; nx = x - PXL8_VXL_CHUNK_SIZE; }
else if (y < 0) { neighbor_idx = 2; ny = y + PXL8_VXL_CHUNK_SIZE; }
else if (y >= PXL8_VXL_CHUNK_SIZE) { neighbor_idx = 3; ny = y - PXL8_VXL_CHUNK_SIZE; }
else if (z < 0) { neighbor_idx = 4; nz = z + PXL8_VXL_CHUNK_SIZE; }
else if (z >= PXL8_VXL_CHUNK_SIZE) { neighbor_idx = 5; nz = z - PXL8_VXL_CHUNK_SIZE; }
if (neighbor_idx >= 0 && neighbors && neighbors[neighbor_idx]) {
return pxl8_vxl_block_get(neighbors[neighbor_idx], nx, ny, nz);
}
return PXL8_VXL_BLOCK_UNKNOWN;
}
static f32 compute_ao(const pxl8_vxl_chunk* chunk, const pxl8_vxl_chunk** neighbors,
i32 x, i32 y, i32 z, i32 dx1, i32 dy1, i32 dz1, i32 dx2, i32 dy2, i32 dz2) {
u8 s1 = get_block_or_neighbor(chunk, neighbors, x + dx1, y + dy1, z + dz1);
u8 s2 = get_block_or_neighbor(chunk, neighbors, x + dx2, y + dy2, z + dz2);
u8 corner = get_block_or_neighbor(chunk, neighbors, x + dx1 + dx2, y + dy1 + dy2, z + dz1 + dz2);
bool side1 = block_is_opaque(s1);
bool side2 = block_is_opaque(s2);
bool has_corner = block_is_opaque(corner);
if (side1 && side2) return 0.0f;
return (3.0f - (f32)side1 - (f32)side2 - (f32)has_corner) / 3.0f;
}
static f32 sample_corner_displacement(const pxl8_vxl_chunk* chunk, const pxl8_vxl_chunk** neighbors,
i32 cx, i32 cz, i32 base_y) {
f32 sum = 0.0f;
i32 count = 0;
for (i32 dz = -1; dz <= 0; dz++) {
for (i32 dx = -1; dx <= 0; dx++) {
i32 x = cx + dx;
i32 z = cz + dz;
u8 block = get_block_or_neighbor(chunk, neighbors, x, base_y, z);
u8 above = get_block_or_neighbor(chunk, neighbors, x, base_y + 1, z);
if (block_is_opaque(block) && !block_is_opaque(above)) {
sum += 1.0f;
} else if (!block_is_opaque(block)) {
sum -= 1.0f;
}
count++;
}
}
return count > 0 ? (sum / (f32)count) * 0.15f : 0.0f;
}
static void slice_to_world(i32 face, i32 slice, i32 u, i32 v, i32* x, i32* y, i32* z) {
switch (face) {
case 0: *x = slice; *y = v; *z = u; break;
case 1: *x = slice; *y = v; *z = u; break;
case 2: *x = u; *y = slice; *z = v; break;
case 3: *x = u; *y = slice; *z = v; break;
case 4: *x = u; *y = v; *z = slice; break;
case 5: *x = u; *y = v; *z = slice; break;
}
}
static f32 terrain_noise(f32 wx, f32 wz, u64 seed) {
f32 noise = pxl8_fbm(wx * 0.08f, wz * 0.08f, seed, 3);
return (noise - 0.5f) * 0.4f;
}
static void emit_greedy_quad(pxl8_mesh* mesh, const pxl8_vxl_chunk* chunk,
const pxl8_vxl_chunk** neighbors,
const pxl8_vxl_mesh_config* config,
i32 face, i32 slice,
i32 u, i32 v, i32 width, i32 height,
u8 texture_id, f32 ao_avg, f32 ao_strength) {
pxl8_vec3 normal = face_normals[face];
u8 light = (u8)(255.0f * (1.0f - ao_strength * (1.0f - ao_avg)));
f32 p[4][3];
switch (face) {
case 0:
p[0][0] = (f32)slice; p[0][1] = (f32)v; p[0][2] = (f32)(u + width);
p[1][0] = (f32)slice; p[1][1] = (f32)v; p[1][2] = (f32)u;
p[2][0] = (f32)slice; p[2][1] = (f32)(v + height); p[2][2] = (f32)u;
p[3][0] = (f32)slice; p[3][1] = (f32)(v + height); p[3][2] = (f32)(u + width);
break;
case 1:
p[0][0] = (f32)(slice + 1); p[0][1] = (f32)v; p[0][2] = (f32)u;
p[1][0] = (f32)(slice + 1); p[1][1] = (f32)v; p[1][2] = (f32)(u + width);
p[2][0] = (f32)(slice + 1); p[2][1] = (f32)(v + height); p[2][2] = (f32)(u + width);
p[3][0] = (f32)(slice + 1); p[3][1] = (f32)(v + height); p[3][2] = (f32)u;
break;
case 2:
p[0][0] = (f32)u; p[0][1] = (f32)slice; p[0][2] = (f32)v;
p[1][0] = (f32)(u + width); p[1][1] = (f32)slice; p[1][2] = (f32)v;
p[2][0] = (f32)(u + width); p[2][1] = (f32)slice; p[2][2] = (f32)(v + height);
p[3][0] = (f32)u; p[3][1] = (f32)slice; p[3][2] = (f32)(v + height);
break;
case 3: {
f32 base_y = (f32)(slice + 1);
f32 wx0 = (f32)(config->chunk_x * PXL8_VXL_CHUNK_SIZE + u);
f32 wz0 = (f32)(config->chunk_z * PXL8_VXL_CHUNK_SIZE + v);
f32 d0 = sample_corner_displacement(chunk, neighbors, u, v + height, slice);
f32 d1 = sample_corner_displacement(chunk, neighbors, u + width, v + height, slice);
f32 d2 = sample_corner_displacement(chunk, neighbors, u + width, v, slice);
f32 d3 = sample_corner_displacement(chunk, neighbors, u, v, slice);
f32 n0 = terrain_noise(wx0, wz0 + (f32)height, config->seed);
f32 n1 = terrain_noise(wx0 + (f32)width, wz0 + (f32)height, config->seed);
f32 n2 = terrain_noise(wx0 + (f32)width, wz0, config->seed);
f32 n3 = terrain_noise(wx0, wz0, config->seed);
p[0][0] = (f32)u; p[0][1] = base_y + d0 + n0; p[0][2] = (f32)(v + height);
p[1][0] = (f32)(u + width); p[1][1] = base_y + d1 + n1; p[1][2] = (f32)(v + height);
p[2][0] = (f32)(u + width); p[2][1] = base_y + d2 + n2; p[2][2] = (f32)v;
p[3][0] = (f32)u; p[3][1] = base_y + d3 + n3; p[3][2] = (f32)v;
pxl8_vec3 e1 = {p[1][0] - p[0][0], p[1][1] - p[0][1], p[1][2] - p[0][2]};
pxl8_vec3 e2 = {p[3][0] - p[0][0], p[3][1] - p[0][1], p[3][2] - p[0][2]};
normal = (pxl8_vec3){
e1.y * e2.z - e1.z * e2.y,
e1.z * e2.x - e1.x * e2.z,
e1.x * e2.y - e1.y * e2.x
};
f32 len_sq = normal.x * normal.x + normal.y * normal.y + normal.z * normal.z;
if (len_sq > 0.0001f) {
f32 inv_len = pxl8_fast_inv_sqrt(len_sq);
normal.x *= inv_len; normal.y *= inv_len; normal.z *= inv_len;
} else {
normal = (pxl8_vec3){0, 1, 0};
}
break;
}
case 4:
p[0][0] = (f32)u; p[0][1] = (f32)v; p[0][2] = (f32)slice;
p[1][0] = (f32)u; p[1][1] = (f32)(v + height); p[1][2] = (f32)slice;
p[2][0] = (f32)(u + width); p[2][1] = (f32)(v + height); p[2][2] = (f32)slice;
p[3][0] = (f32)(u + width); p[3][1] = (f32)v; p[3][2] = (f32)slice;
break;
case 5:
p[0][0] = (f32)(u + width); p[0][1] = (f32)v; p[0][2] = (f32)(slice + 1);
p[1][0] = (f32)(u + width); p[1][1] = (f32)(v + height); p[1][2] = (f32)(slice + 1);
p[2][0] = (f32)u; p[2][1] = (f32)(v + height); p[2][2] = (f32)(slice + 1);
p[3][0] = (f32)u; p[3][1] = (f32)v; p[3][2] = (f32)(slice + 1);
break;
}
f32 tex_u = (f32)width;
f32 tex_v = (f32)height;
pxl8_vertex verts[4];
for (i32 i = 0; i < 4; i++) {
verts[i].position.x = p[i][0];
verts[i].position.y = p[i][1];
verts[i].position.z = p[i][2];
verts[i].normal = normal;
verts[i].color = texture_id;
verts[i].light = light;
}
verts[0].u = 0; verts[0].v = tex_v;
verts[1].u = tex_u; verts[1].v = tex_v;
verts[2].u = tex_u; verts[2].v = 0;
verts[3].u = 0; verts[3].v = 0;
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);
}
static void build_greedy_slice(const pxl8_vxl_chunk* chunk,
const pxl8_vxl_chunk** neighbors,
const pxl8_vxl_block_registry* registry,
const pxl8_vxl_mesh_config* config,
i32 face, i32 slice,
pxl8_mesh* mesh) {
pxl8_vxl_greedy_mask mask;
memset(&mask, 0, sizeof(mask));
i32 dx = face_dirs[face][0];
i32 dy = face_dirs[face][1];
i32 dz = face_dirs[face][2];
for (i32 v = 0; v < PXL8_VXL_CHUNK_SIZE; v++) {
for (i32 u = 0; u < PXL8_VXL_CHUNK_SIZE; u++) {
i32 x, y, z;
slice_to_world(face, slice, u, v, &x, &y, &z);
u8 block = pxl8_vxl_block_get(chunk, x, y, z);
if (block == PXL8_VXL_BLOCK_AIR) continue;
pxl8_vxl_geometry geo = pxl8_vxl_block_registry_geometry(registry, block);
if (geo != PXL8_VXL_GEOMETRY_CUBE) continue;
i32 nx = x + dx;
i32 ny = y + dy;
i32 nz = z + dz;
u8 neighbor = get_block_or_neighbor(chunk, neighbors, nx, ny, nz);
if (neighbor == PXL8_VXL_BLOCK_UNKNOWN) continue;
if (block_is_full_cube(neighbor, registry)) continue;
mask.block[u][v] = block;
}
}
f32 ao_strength = config->ambient_occlusion ? config->ao_strength : 0.0f;
for (i32 v = 0; v < PXL8_VXL_CHUNK_SIZE; v++) {
for (i32 u = 0; u < PXL8_VXL_CHUNK_SIZE; u++) {
if (mask.done[u][v] || mask.block[u][v] == 0) continue;
u8 block = mask.block[u][v];
u8 texture_id = pxl8_vxl_block_registry_texture_for_face(registry, block, face);
i32 width = 1;
while (u + width < PXL8_VXL_CHUNK_SIZE &&
!mask.done[u + width][v] &&
mask.block[u + width][v] == block) {
width++;
}
i32 height = 1;
bool can_expand = true;
while (can_expand && v + height < PXL8_VXL_CHUNK_SIZE) {
for (i32 wu = 0; wu < width; wu++) {
if (mask.done[u + wu][v + height] ||
mask.block[u + wu][v + height] != block) {
can_expand = false;
break;
}
}
if (can_expand) height++;
}
for (i32 dv = 0; dv < height; dv++) {
for (i32 du = 0; du < width; du++) {
mask.done[u + du][v + dv] = true;
}
}
f32 ao_sum = 0.0f;
i32 ao_count = 0;
if (config->ambient_occlusion) {
i32 cx, cy, cz;
slice_to_world(face, slice, u, v, &cx, &cy, &cz);
i32 fx = cx + dx;
i32 fy = cy + dy;
i32 fz = cz + dz;
static const i32 ao_offsets[6][4][2][3] = {
[0] = {{{0,-1,0}, {0,0,1}}, {{0,-1,0}, {0,0,-1}}, {{0,1,0}, {0,0,-1}}, {{0,1,0}, {0,0,1}}},
[1] = {{{0,-1,0}, {0,0,-1}}, {{0,-1,0}, {0,0,1}}, {{0,1,0}, {0,0,1}}, {{0,1,0}, {0,0,-1}}},
[2] = {{{-1,0,0}, {0,0,-1}}, {{1,0,0}, {0,0,-1}}, {{1,0,0}, {0,0,1}}, {{-1,0,0}, {0,0,1}}},
[3] = {{{-1,0,0}, {0,0,1}}, {{1,0,0}, {0,0,1}}, {{1,0,0}, {0,0,-1}}, {{-1,0,0}, {0,0,-1}}},
[4] = {{{-1,0,0}, {0,1,0}}, {{-1,0,0}, {0,-1,0}}, {{1,0,0}, {0,-1,0}}, {{1,0,0}, {0,1,0}}},
[5] = {{{1,0,0}, {0,-1,0}}, {{1,0,0}, {0,1,0}}, {{-1,0,0}, {0,1,0}}, {{-1,0,0}, {0,-1,0}}}
};
for (i32 corner = 0; corner < 4; corner++) {
f32 ao = compute_ao(chunk, neighbors, fx, fy, fz,
ao_offsets[face][corner][0][0],
ao_offsets[face][corner][0][1],
ao_offsets[face][corner][0][2],
ao_offsets[face][corner][1][0],
ao_offsets[face][corner][1][1],
ao_offsets[face][corner][1][2]);
ao_sum += ao;
ao_count++;
}
}
f32 ao_avg = ao_count > 0 ? ao_sum / (f32)ao_count : 1.0f;
emit_greedy_quad(mesh, chunk, neighbors, config, face, slice, u, v, width, height,
texture_id, ao_avg, ao_strength);
}
}
}
static void add_face_vertices(pxl8_mesh* mesh, const pxl8_vxl_mesh_config* config,
pxl8_vec3 pos, i32 face, u8 texture_id,
f32 ao0, f32 ao1, f32 ao2, f32 ao3) {
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_slab_faces(pxl8_mesh* mesh, const pxl8_vxl_chunk* chunk,
const pxl8_vxl_chunk** neighbors, const pxl8_vxl_block_registry* registry,
const pxl8_vxl_mesh_config* config,
i32 x, i32 y, i32 z, u8 block, bool top) {
u8 texture_id = pxl8_vxl_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};
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];
u8 neighbor = get_block_or_neighbor(chunk, neighbors, nx, y, nz);
if (neighbor == PXL8_VXL_BLOCK_UNKNOWN) continue;
if (block_is_full_cube(neighbor, registry)) 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) {
u8 above = get_block_or_neighbor(chunk, neighbors, x, y + 1, z);
if (above != PXL8_VXL_BLOCK_UNKNOWN && !block_is_full_cube(above, registry)) {
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);
u8 below = get_block_or_neighbor(chunk, neighbors, x, y - 1, z);
if (below != PXL8_VXL_BLOCK_UNKNOWN && !block_is_full_cube(below, registry)) {
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_vxl_chunk* chunk,
const pxl8_vxl_chunk** neighbors, const pxl8_vxl_block_registry* registry,
const pxl8_vxl_mesh_config* config,
i32 x, i32 y, i32 z, u8 block, i32 direction) {
u8 texture_top = pxl8_vxl_block_registry_texture_for_face(registry, block, 3);
u8 texture_side = pxl8_vxl_block_registry_texture_for_face(registry, block, 0);
pxl8_vec3 pos = {(f32)x, (f32)y, (f32)z};
u8 below = get_block_or_neighbor(chunk, neighbors, x, y - 1, z);
if (below != PXL8_VXL_BLOCK_UNKNOWN && !block_is_full_cube(below, registry)) {
add_face_vertices(mesh, config, pos, 2, texture_side, 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];
i32 bx = x + face_dirs[back_face][0];
i32 bz = z + face_dirs[back_face][2];
u8 back_neighbor = get_block_or_neighbor(chunk, neighbors, bx, y, bz);
if (back_neighbor != PXL8_VXL_BLOCK_UNKNOWN && !block_is_full_cube(back_neighbor, registry)) {
add_face_vertices(mesh, config, pos, back_face, texture_side, 1.0f, 1.0f, 1.0f, 1.0f);
}
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}}
};
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}
};
pxl8_vertex verts[4];
memset(verts, 0, sizeof(verts));
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].normal = slope_normals[direction];
verts[i].color = texture_top;
verts[i].light = 255;
}
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);
static const i32 side_faces[4][2] = {{0, 1}, {0, 1}, {4, 5}, {4, 5}};
static const f32 side_tris[4][2][3][3] = {
{{{0,0,0}, {0,1,1}, {0,0,1}}, {{1,0,0}, {1,0,1}, {1,1,1}}},
{{{0,0,0}, {0,0,1}, {0,1,0}}, {{1,0,0}, {1,1,0}, {1,0,1}}},
{{{0,0,0}, {0,0,1}, {0,1,1}}, {{1,0,0}, {1,1,0}, {1,0,1}}},
{{{0,0,0}, {0,1,0}, {0,0,1}}, {{1,0,0}, {1,0,1}, {1,1,1}}}
};
static const pxl8_vec3 side_normals[6] = {
{-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1}
};
for (i32 s = 0; s < 2; s++) {
i32 side_face = side_faces[direction][s];
i32 snx = x + face_dirs[side_face][0];
i32 snz = z + face_dirs[side_face][2];
u8 side_neighbor = get_block_or_neighbor(chunk, neighbors, snx, y, snz);
if (side_neighbor != PXL8_VXL_BLOCK_UNKNOWN && !block_is_full_cube(side_neighbor, registry)) {
pxl8_vertex tv[3];
memset(tv, 0, sizeof(tv));
for (i32 vi = 0; vi < 3; vi++) {
tv[vi].position.x = pos.x + side_tris[direction][s][vi][0];
tv[vi].position.y = pos.y + side_tris[direction][s][vi][1];
tv[vi].position.z = pos.z + side_tris[direction][s][vi][2];
tv[vi].normal = side_normals[side_face];
tv[vi].color = texture_side;
tv[vi].light = 255;
}
u16 ti0 = pxl8_mesh_push_vertex(mesh, tv[0]);
u16 ti1 = pxl8_mesh_push_vertex(mesh, tv[1]);
u16 ti2 = pxl8_mesh_push_vertex(mesh, tv[2]);
pxl8_mesh_push_triangle(mesh, ti0, ti1, ti2);
}
}
}
pxl8_mesh* pxl8_vxl_build_mesh(const pxl8_vxl_chunk* chunk,
const pxl8_vxl_chunk** neighbors,
const pxl8_vxl_block_registry* registry,
const pxl8_vxl_mesh_config* config) {
if (!chunk || !registry) return NULL;
pxl8_vxl_mesh_config cfg = config ? *config : PXL8_VXL_MESH_CONFIG_DEFAULT;
u32 max_faces = PXL8_VXL_CHUNK_VOLUME * 6;
pxl8_mesh* mesh = pxl8_mesh_create(max_faces * 4, max_faces * 6);
if (!mesh) return NULL;
for (i32 face = 0; face < 6; face++) {
for (i32 slice = 0; slice < PXL8_VXL_CHUNK_SIZE; slice++) {
build_greedy_slice(chunk, neighbors, registry, &cfg, face, slice, mesh);
}
}
for (i32 z = 0; z < PXL8_VXL_CHUNK_SIZE; z++) {
for (i32 y = 0; y < PXL8_VXL_CHUNK_SIZE; y++) {
for (i32 x = 0; x < PXL8_VXL_CHUNK_SIZE; x++) {
u8 block = pxl8_vxl_block_get(chunk, x, y, z);
if (block == PXL8_VXL_BLOCK_AIR) continue;
pxl8_vxl_geometry geo = pxl8_vxl_block_registry_geometry(registry, block);
switch (geo) {
case PXL8_VXL_GEOMETRY_CUBE:
break;
case PXL8_VXL_GEOMETRY_SLAB_BOTTOM:
add_slab_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, false);
break;
case PXL8_VXL_GEOMETRY_SLAB_TOP:
add_slab_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, true);
break;
case PXL8_VXL_GEOMETRY_SLOPE_NORTH:
add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 0);
break;
case PXL8_VXL_GEOMETRY_SLOPE_SOUTH:
add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 1);
break;
case PXL8_VXL_GEOMETRY_SLOPE_EAST:
add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 2);
break;
case PXL8_VXL_GEOMETRY_SLOPE_WEST:
add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 3);
break;
case PXL8_VXL_GEOMETRY_STAIRS_NORTH:
case PXL8_VXL_GEOMETRY_STAIRS_SOUTH:
case PXL8_VXL_GEOMETRY_STAIRS_EAST:
case PXL8_VXL_GEOMETRY_STAIRS_WEST:
break;
}
}
}
}
return mesh;
}
pxl8_vxl_render_state* pxl8_vxl_render_state_create(void) {
return pxl8_calloc(1, sizeof(pxl8_vxl_render_state));
}
void pxl8_vxl_render_state_destroy(pxl8_vxl_render_state* state) {
pxl8_free(state);
}

View file

@ -1,47 +0,0 @@
#pragma once
#include "pxl8_mesh.h"
#include "pxl8_types.h"
#include "pxl8_vxl.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct pxl8_vxl_render_state {
u8 _unused;
} pxl8_vxl_render_state;
pxl8_vxl_render_state* pxl8_vxl_render_state_create(void);
void pxl8_vxl_render_state_destroy(pxl8_vxl_render_state* state);
typedef struct pxl8_vxl_mesh_config {
bool ambient_occlusion;
bool vertex_heights;
f32 ao_strength;
f32 texture_scale;
i32 chunk_x;
i32 chunk_y;
i32 chunk_z;
u64 seed;
} pxl8_vxl_mesh_config;
#define PXL8_VXL_MESH_CONFIG_DEFAULT ((pxl8_vxl_mesh_config){ \
.ambient_occlusion = true, \
.vertex_heights = true, \
.ao_strength = 0.2f, \
.texture_scale = 1.0f, \
.chunk_x = 0, \
.chunk_y = 0, \
.chunk_z = 0, \
.seed = 12345 \
})
pxl8_mesh* pxl8_vxl_build_mesh(const pxl8_vxl_chunk* chunk,
const pxl8_vxl_chunk** neighbors,
const pxl8_vxl_block_registry* registry,
const pxl8_vxl_mesh_config* config);
#ifdef __cplusplus
}
#endif

View file

@ -15,28 +15,20 @@
#include "pxl8_log.h"
#include "pxl8_mem.h"
#include "pxl8_sim.h"
#include "pxl8_vxl.h"
#include "pxl8_vxl_render.h"
#define PXL8_WORLD_ENTITY_CAPACITY 256
#define VOXEL_SCALE 16.0f
#define VOXEL_CHUNK_SIZE 32.0f
#define WORLD_CHUNK_SIZE (VOXEL_CHUNK_SIZE * VOXEL_SCALE)
struct pxl8_world {
pxl8_world_chunk* active_chunk;
pxl8_vxl_block_registry* block_registry;
pxl8_world_chunk_cache* chunk_cache;
pxl8_entity_pool* entities;
pxl8_bsp_render_state* bsp_render_state;
pxl8_vxl_render_state* vxl_render_state;
i32 render_distance;
i32 sim_distance;
pxl8_sim_entity local_player;
u64 client_tick;
pxl8_vec2 pointer_motion;
pxl8_sim_config sim_config;
#ifdef PXL8_ASYNC_THREADS
pxl8_sim_entity render_state[2];
@ -54,14 +46,22 @@ pxl8_world* pxl8_world_create(void) {
pxl8_world* world = pxl8_calloc(1, sizeof(pxl8_world));
if (!world) return NULL;
world->block_registry = pxl8_vxl_block_registry_create();
world->chunk_cache = pxl8_world_chunk_cache_create();
world->entities = pxl8_entity_pool_create(PXL8_WORLD_ENTITY_CAPACITY);
world->vxl_render_state = pxl8_vxl_render_state_create();
world->render_distance = 3;
world->sim_distance = 4;
world->sim_config = (pxl8_sim_config){
.move_speed = 180.0f,
.ground_accel = 10.0f,
.air_accel = 1.0f,
.stop_speed = 100.0f,
.friction = 6.0f,
.gravity = 800.0f,
.jump_velocity = 200.0f,
.player_radius = 16.0f,
.player_height = 72.0f,
.max_pitch = 1.5f,
};
if (!world->block_registry || !world->chunk_cache || !world->entities || !world->vxl_render_state) {
if (!world->chunk_cache || !world->entities) {
pxl8_world_destroy(world);
return NULL;
}
@ -72,11 +72,9 @@ pxl8_world* pxl8_world_create(void) {
void pxl8_world_destroy(pxl8_world* world) {
if (!world) return;
pxl8_vxl_block_registry_destroy(world->block_registry);
pxl8_world_chunk_cache_destroy(world->chunk_cache);
pxl8_entity_pool_destroy(world->entities);
pxl8_bsp_render_state_destroy(world->bsp_render_state);
pxl8_vxl_render_state_destroy(world->vxl_render_state);
pxl8_free(world);
}
@ -95,11 +93,6 @@ void pxl8_world_set_active_chunk(pxl8_world* world, pxl8_world_chunk* chunk) {
world->active_chunk = chunk;
}
pxl8_vxl_block_registry* pxl8_world_block_registry(pxl8_world* world) {
if (!world) return NULL;
return world->block_registry;
}
pxl8_entity_pool* pxl8_world_entities(pxl8_world* world) {
if (!world) return NULL;
return world->entities;
@ -110,53 +103,12 @@ pxl8_entity pxl8_world_spawn(pxl8_world* world) {
return pxl8_entity_spawn(world->entities);
}
static bool voxel_point_solid(pxl8_world_chunk_cache* cache, f32 x, f32 y, f32 z) {
i32 cx = (i32)floorf(x / WORLD_CHUNK_SIZE);
i32 cy = (i32)floorf(y / WORLD_CHUNK_SIZE);
i32 cz = (i32)floorf(z / WORLD_CHUNK_SIZE);
f32 local_x = (x - cx * WORLD_CHUNK_SIZE) / VOXEL_SCALE;
f32 local_y = (y - cy * WORLD_CHUNK_SIZE) / VOXEL_SCALE;
f32 local_z = (z - cz * WORLD_CHUNK_SIZE) / VOXEL_SCALE;
i32 lx = (i32)floorf(local_x);
i32 ly = (i32)floorf(local_y);
i32 lz = (i32)floorf(local_z);
lx = lx < 0 ? 0 : (lx >= 32 ? 31 : lx);
ly = ly < 0 ? 0 : (ly >= 32 ? 31 : ly);
lz = lz < 0 ? 0 : (lz >= 32 ? 31 : lz);
pxl8_world_chunk* chunk = pxl8_world_chunk_cache_get_vxl(cache, cx, cy, cz);
if (!chunk || !chunk->voxels) {
return ly < 8;
}
return pxl8_vxl_block_get(chunk->voxels, lx, ly, lz) != PXL8_VXL_BLOCK_AIR;
}
static pxl8_sim_world make_sim_world(const pxl8_world* world, pxl8_vec3 pos) {
pxl8_sim_world pxl8_world_sim_world(const pxl8_world* world, pxl8_vec3 pos) {
(void)pos;
pxl8_sim_world sim = {0};
if (world->active_chunk && world->active_chunk->type == PXL8_WORLD_CHUNK_BSP) {
if (world->active_chunk && world->active_chunk->bsp) {
sim.bsp = world->active_chunk->bsp;
return sim;
}
if (world->chunk_cache) {
i32 cx = (i32)floorf(pos.x / WORLD_CHUNK_SIZE);
i32 cy = (i32)floorf(pos.y / WORLD_CHUNK_SIZE);
i32 cz = (i32)floorf(pos.z / WORLD_CHUNK_SIZE);
pxl8_world_chunk* chunk = pxl8_world_chunk_cache_get_vxl(world->chunk_cache, cx, cy, cz);
if (chunk && chunk->voxels) {
sim.vxl = chunk->voxels;
sim.vxl_cx = cx;
sim.vxl_cy = cy;
sim.vxl_cz = cz;
}
}
return sim;
}
@ -191,12 +143,8 @@ static void userdata_to_entity(const u8* userdata, pxl8_sim_entity* ent) {
bool pxl8_world_point_solid(const pxl8_world* world, f32 x, f32 y, f32 z) {
if (!world) return false;
if (world->active_chunk) {
if (world->active_chunk->type == PXL8_WORLD_CHUNK_BSP && world->active_chunk->bsp) {
return pxl8_bsp_point_solid(world->active_chunk->bsp, (pxl8_vec3){x, y, z});
}
} else if (world->chunk_cache) {
return voxel_point_solid(world->chunk_cache, x, y, z);
if (world->active_chunk && world->active_chunk->bsp) {
return pxl8_bsp_point_solid(world->active_chunk->bsp, (pxl8_vec3){x, y, z});
}
return false;
@ -290,99 +238,30 @@ pxl8_ray pxl8_world_sweep(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to,
return result;
}
void pxl8_world_update(pxl8_world* world, const pxl8_input_state* input, f32 dt) {
void pxl8_world_update(pxl8_world* world, f32 dt) {
(void)dt;
if (!world) return;
pxl8_world_chunk_cache_tick(world->chunk_cache);
if (input && (world->local_player.flags & PXL8_SIM_FLAG_ALIVE)) {
pxl8_input_msg msg = {0};
msg.move_x = (pxl8_key_down(input, "d") ? 1.0f : 0.0f) - (pxl8_key_down(input, "a") ? 1.0f : 0.0f);
msg.move_y = (pxl8_key_down(input, "w") ? 1.0f : 0.0f) - (pxl8_key_down(input, "s") ? 1.0f : 0.0f);
msg.look_dx = (f32)pxl8_mouse_dx(input);
msg.look_dy = (f32)pxl8_mouse_dy(input);
msg.buttons = pxl8_key_down(input, "space") ? 1 : 0;
msg.tick = world->client_tick;
msg.timestamp = pxl8_get_ticks_ns();
if (world->net) {
pxl8_net_send_input(world->net, &msg);
}
pxl8_sim_world sim = make_sim_world(world, world->local_player.pos);
pxl8_sim_move_player(&world->local_player, &msg, &sim, dt);
world->client_tick++;
}
}
void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) {
if (!world || !gfx) return;
if (world->active_chunk) {
if (world->active_chunk->type == PXL8_WORLD_CHUNK_BSP && world->active_chunk->bsp) {
pxl8_3d_set_bsp(gfx, world->active_chunk->bsp);
pxl8_bsp_render(gfx, world->active_chunk->bsp,
world->bsp_render_state, camera_pos);
}
if (world->active_chunk && world->active_chunk->bsp) {
pxl8_3d_set_bsp(gfx, world->active_chunk->bsp);
pxl8_bsp_render(gfx, world->active_chunk->bsp,
world->bsp_render_state, camera_pos);
} else {
pxl8_3d_set_bsp(gfx, NULL);
i32 cx = (i32)floorf(camera_pos.x / WORLD_CHUNK_SIZE);
i32 cy = (i32)floorf(camera_pos.y / WORLD_CHUNK_SIZE);
i32 cz = (i32)floorf(camera_pos.z / WORLD_CHUNK_SIZE);
const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx);
pxl8_gfx_material mat = {
.texture_id = 0,
.dynamic_lighting = true,
.alpha = 255,
};
i32 r = world->render_distance;
for (i32 dy = -r; dy <= r; dy++) {
for (i32 dz = -r; dz <= r; dz++) {
for (i32 dx = -r; dx <= r; dx++) {
i32 chunk_cx = cx + dx;
i32 chunk_cy = cy + dy;
i32 chunk_cz = cz + dz;
f32 chunk_x = chunk_cx * WORLD_CHUNK_SIZE;
f32 chunk_y = chunk_cy * WORLD_CHUNK_SIZE;
f32 chunk_z = chunk_cz * WORLD_CHUNK_SIZE;
if (frustum) {
pxl8_vec3 aabb_min = {chunk_x, chunk_y, chunk_z};
pxl8_vec3 aabb_max = {chunk_x + WORLD_CHUNK_SIZE, chunk_y + WORLD_CHUNK_SIZE, chunk_z + WORLD_CHUNK_SIZE};
if (!pxl8_frustum_test_aabb(frustum, aabb_min, aabb_max)) continue;
}
pxl8_vxl_mesh_config config = PXL8_VXL_MESH_CONFIG_DEFAULT;
config.chunk_x = chunk_cx;
config.chunk_y = chunk_cy;
config.chunk_z = chunk_cz;
pxl8_mesh* mesh = pxl8_world_chunk_cache_get_mesh(
world->chunk_cache,
chunk_cx, chunk_cy, chunk_cz,
world->block_registry, &config);
if (mesh && mesh->index_count > 0) {
pxl8_mat4 scale = pxl8_mat4_scale(VOXEL_SCALE, VOXEL_SCALE, VOXEL_SCALE);
pxl8_mat4 translate = pxl8_mat4_translate(chunk_x, chunk_y, chunk_z);
pxl8_mat4 model = pxl8_mat4_multiply(translate, scale);
pxl8_3d_draw_mesh(gfx, mesh, &model, &mat);
}
}
}
}
}
}
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) {
if (chunk_id != 0) {
pxl8_world_chunk* chunk = pxl8_world_chunk_cache_get_bsp(world->chunk_cache, chunk_id);
if (chunk && chunk->bsp) {
if (world->active_chunk != chunk) {
@ -397,7 +276,7 @@ void pxl8_world_sync(pxl8_world* world, pxl8_net* net) {
world->bsp_render_state = pxl8_bsp_render_state_create(chunk->bsp->num_faces);
}
}
} else if (chunk_id == 0 && world->active_chunk != NULL) {
} else if (world->active_chunk != NULL) {
world->active_chunk = NULL;
if (world->bsp_render_state) {
pxl8_bsp_render_state_destroy(world->bsp_render_state);
@ -409,7 +288,6 @@ void pxl8_world_sync(pxl8_world* world, pxl8_net* net) {
static void ensure_bsp_render_state(pxl8_world* world) {
if (!world || world->bsp_render_state) return;
if (!world->active_chunk) return;
if (world->active_chunk->type != PXL8_WORLD_CHUNK_BSP) return;
if (!world->active_chunk->bsp) return;
world->bsp_render_state = pxl8_bsp_render_state_create(world->active_chunk->bsp->num_faces);
@ -424,28 +302,9 @@ void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_
pxl8_bsp_set_material(world->bsp_render_state, material_id, material);
}
i32 pxl8_world_get_render_distance(const pxl8_world* world) {
if (!world) return 3;
return world->render_distance;
}
void pxl8_world_set_render_distance(pxl8_world* world, i32 distance) {
if (!world) return;
if (distance < 1) distance = 1;
if (distance > 8) distance = 8;
world->render_distance = distance;
}
i32 pxl8_world_get_sim_distance(const pxl8_world* world) {
if (!world) return 4;
return world->sim_distance;
}
void pxl8_world_set_sim_distance(pxl8_world* world, i32 distance) {
if (!world) return;
if (distance < 1) distance = 1;
if (distance > 8) distance = 8;
world->sim_distance = distance;
void pxl8_world_set_sim_config(pxl8_world* world, const pxl8_sim_config* config) {
if (!world || !config) return;
world->sim_config = *config;
}
void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z) {
@ -481,8 +340,8 @@ void pxl8_world_predict(pxl8_world* world, pxl8_net* net, const pxl8_input_msg*
if (!world || !net || !input) return;
if (!(world->local_player.flags & PXL8_SIM_FLAG_ALIVE)) return;
pxl8_sim_world sim = make_sim_world(world, world->local_player.pos);
pxl8_sim_move_player(&world->local_player, input, &sim, dt);
pxl8_sim_world sim = pxl8_world_sim_world(world, world->local_player.pos);
pxl8_sim_move_player(&world->local_player, input, &sim, &world->sim_config, dt);
world->client_tick++;
@ -515,8 +374,8 @@ void pxl8_world_reconcile(pxl8_world* world, pxl8_net* net, f32 dt) {
const pxl8_input_msg* input = pxl8_net_input_at(net, tick);
if (!input) continue;
pxl8_sim_world sim = make_sim_world(world, world->local_player.pos);
pxl8_sim_move_player(&world->local_player, input, &sim, dt);
pxl8_sim_world sim = pxl8_world_sim_world(world, world->local_player.pos);
pxl8_sim_move_player(&world->local_player, input, &sim, &world->sim_config, dt);
}
entity_to_userdata(&world->local_player, pxl8_net_predicted_state(net));
@ -562,8 +421,8 @@ static void pxl8_world_sim_tick(pxl8_world* world, f32 dt) {
merged.look_dx = 0;
merged.look_dy = 0;
pxl8_sim_world sim = make_sim_world(world, world->local_player.pos);
pxl8_sim_move_player(&world->local_player, &merged, &sim, dt);
pxl8_sim_world sim = pxl8_world_sim_world(world, world->local_player.pos);
pxl8_sim_move_player(&world->local_player, &merged, &sim, &world->sim_config, dt);
if (world->net) {
entity_to_userdata(&world->local_player, pxl8_net_predicted_state(world->net));
@ -672,8 +531,8 @@ void pxl8_world_push_input(pxl8_world* world, const pxl8_input_msg* input) {
world->pointer_motion.yaw -= input->look_dx * 0.008f;
f32 pitch = world->pointer_motion.pitch - input->look_dy * 0.008f;
if (pitch > PXL8_SIM_MAX_PITCH) pitch = PXL8_SIM_MAX_PITCH;
if (pitch < -PXL8_SIM_MAX_PITCH) pitch = -PXL8_SIM_MAX_PITCH;
if (pitch > world->sim_config.max_pitch) pitch = world->sim_config.max_pitch;
if (pitch < -world->sim_config.max_pitch) pitch = -world->sim_config.max_pitch;
world->pointer_motion.pitch = pitch;
pxl8_input_msg* copy = pxl8_malloc(sizeof(pxl8_input_msg));

View file

@ -22,7 +22,6 @@ pxl8_world_chunk_cache* pxl8_world_get_chunk_cache(pxl8_world* world);
pxl8_world_chunk* pxl8_world_active_chunk(pxl8_world* world);
void pxl8_world_set_active_chunk(pxl8_world* world, pxl8_world_chunk* chunk);
pxl8_vxl_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);
@ -30,19 +29,16 @@ bool pxl8_world_point_solid(const pxl8_world* world, f32 x, f32 y, f32 z);
pxl8_ray pxl8_world_ray(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to);
pxl8_ray pxl8_world_sweep(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius);
void pxl8_world_update(pxl8_world* world, const pxl8_input_state* input, f32 dt);
void pxl8_world_update(pxl8_world* world, f32 dt);
void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);
void pxl8_world_sync(pxl8_world* world, pxl8_net* net);
void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_gfx_material* material);
i32 pxl8_world_get_render_distance(const pxl8_world* world);
void pxl8_world_set_render_distance(pxl8_world* world, i32 distance);
i32 pxl8_world_get_sim_distance(const pxl8_world* world);
void pxl8_world_set_sim_distance(pxl8_world* world, i32 distance);
void pxl8_world_set_sim_config(pxl8_world* world, const pxl8_sim_config* config);
void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z);
pxl8_sim_entity* pxl8_world_local_player(pxl8_world* world);
pxl8_sim_world pxl8_world_sim_world(const pxl8_world* world, pxl8_vec3 pos);
void pxl8_world_predict(pxl8_world* world, pxl8_net* net, const pxl8_input_msg* input, f32 dt);
void pxl8_world_reconcile(pxl8_world* world, pxl8_net* net, f32 dt);

View file

@ -2,45 +2,16 @@
#include "pxl8_bsp.h"
#include "pxl8_mem.h"
#include "pxl8_vxl.h"
pxl8_world_chunk* pxl8_world_chunk_create_vxl(i32 cx, i32 cy, i32 cz) {
pxl8_world_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_world_chunk));
if (!chunk) return NULL;
chunk->type = PXL8_WORLD_CHUNK_VXL;
chunk->cx = cx;
chunk->cy = cy;
chunk->cz = cz;
chunk->voxels = pxl8_vxl_chunk_create();
if (!chunk->voxels) {
pxl8_free(chunk);
return NULL;
}
return chunk;
}
pxl8_world_chunk* pxl8_world_chunk_create_bsp(u32 id) {
pxl8_world_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_world_chunk));
if (!chunk) return NULL;
chunk->type = PXL8_WORLD_CHUNK_BSP;
chunk->id = id;
chunk->bsp = NULL;
return chunk;
}
void pxl8_world_chunk_destroy(pxl8_world_chunk* chunk) {
if (!chunk) return;
if (chunk->type == PXL8_WORLD_CHUNK_VXL && chunk->voxels) {
pxl8_vxl_chunk_destroy(chunk->voxels);
} else if (chunk->type == PXL8_WORLD_CHUNK_BSP && chunk->bsp) {
pxl8_bsp_destroy(chunk->bsp);
}
if (chunk->bsp) pxl8_bsp_destroy(chunk->bsp);
pxl8_free(chunk);
}

View file

@ -2,29 +2,17 @@
#include "pxl8_bsp.h"
#include "pxl8_types.h"
#include "pxl8_vxl.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum pxl8_world_chunk_type {
PXL8_WORLD_CHUNK_VXL,
PXL8_WORLD_CHUNK_BSP
} pxl8_world_chunk_type;
typedef struct pxl8_world_chunk {
pxl8_world_chunk_type type;
u32 id;
u32 version;
i32 cx, cy, cz;
union {
pxl8_bsp* bsp;
pxl8_vxl_chunk* voxels;
};
pxl8_bsp* bsp;
} pxl8_world_chunk;
pxl8_world_chunk* pxl8_world_chunk_create_vxl(i32 cx, i32 cy, i32 cz);
pxl8_world_chunk* pxl8_world_chunk_create_bsp(u32 id);
void pxl8_world_chunk_destroy(pxl8_world_chunk* chunk);

View file

@ -7,24 +7,10 @@
#include "pxl8_log.h"
#include "pxl8_mem.h"
#define PXL8_VXL_CHUNK_VOLUME (PXL8_VXL_CHUNK_SIZE * PXL8_VXL_CHUNK_SIZE * PXL8_VXL_CHUNK_SIZE)
static pxl8_world_chunk_cache_entry* find_entry_vxl(pxl8_world_chunk_cache* cache, i32 cx, i32 cy, i32 cz) {
for (u32 i = 0; i < cache->entry_count; i++) {
pxl8_world_chunk_cache_entry* e = &cache->entries[i];
if (e->valid && e->chunk && e->chunk->type == PXL8_WORLD_CHUNK_VXL &&
e->chunk->cx == cx && e->chunk->cy == cy && e->chunk->cz == cz) {
return e;
}
}
return NULL;
}
static pxl8_world_chunk_cache_entry* find_entry_bsp(pxl8_world_chunk_cache* cache, u32 id) {
for (u32 i = 0; i < cache->entry_count; i++) {
pxl8_world_chunk_cache_entry* e = &cache->entries[i];
if (e->valid && e->chunk && e->chunk->type == PXL8_WORLD_CHUNK_BSP &&
e->chunk->id == id) {
if (e->valid && e->chunk && e->chunk->id == id) {
return e;
}
}
@ -56,14 +42,14 @@ static pxl8_world_chunk_cache_entry* alloc_entry(pxl8_world_chunk_cache* cache)
}
pxl8_world_chunk_cache_entry* e = &cache->entries[slot];
if (e->chunk) pxl8_world_chunk_destroy(e->chunk);
if (e->mesh) pxl8_mesh_destroy(e->mesh);
if (e->chunk) {
pxl8_world_chunk_destroy(e->chunk);
}
memset(e, 0, sizeof(*e));
return e;
}
static void assembly_reset(pxl8_world_chunk_assembly* a) {
a->type = PXL8_WORLD_CHUNK_VXL;
a->id = 0;
a->cx = 0;
a->cy = 0;
@ -78,7 +64,6 @@ static void assembly_reset(pxl8_world_chunk_assembly* a) {
static void assembly_init(pxl8_world_chunk_assembly* a, const pxl8_chunk_msg_header* hdr) {
assembly_reset(a);
a->type = hdr->chunk_type == PXL8_CHUNK_TYPE_BSP ? PXL8_WORLD_CHUNK_BSP : PXL8_WORLD_CHUNK_VXL;
a->id = hdr->id;
a->cx = hdr->cx;
a->cy = hdr->cy;
@ -94,33 +79,6 @@ static void assembly_init(pxl8_world_chunk_assembly* a, const pxl8_chunk_msg_hea
}
}
static bool rle_decode_voxel(const u8* src, usize src_len, pxl8_vxl_chunk* chunk) {
u8* linear = pxl8_malloc(PXL8_VXL_CHUNK_VOLUME);
if (!linear) return false;
usize src_pos = 0;
usize dst_pos = 0;
while (src_pos + 1 < src_len && dst_pos < PXL8_VXL_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_VXL_CHUNK_VOLUME; i++) {
linear[dst_pos++] = block;
}
}
if (dst_pos != PXL8_VXL_CHUNK_VOLUME) {
pxl8_free(linear);
return false;
}
pxl8_vxl_chunk_from_linear(chunk, linear);
pxl8_free(linear);
return true;
}
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);
@ -321,34 +279,6 @@ static pxl8_bsp* assembly_to_bsp(pxl8_world_chunk_assembly* a) {
return bsp;
}
static pxl8_result assemble_vxl(pxl8_world_chunk_cache* cache, pxl8_world_chunk_assembly* a) {
pxl8_world_chunk_cache_entry* entry = find_entry_vxl(cache, a->cx, a->cy, a->cz);
if (!entry) {
entry = alloc_entry(cache);
entry->chunk = pxl8_world_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_vxl_block_clear(entry->chunk->voxels);
if (!rle_decode_voxel(a->data, a->data_size, entry->chunk->voxels)) {
pxl8_error("[CLIENT] RLE decode failed for chunk (%d,%d,%d)", a->cx, a->cy, a->cz);
return PXL8_ERROR_INVALID_ARGUMENT;
}
assembly_reset(a);
return PXL8_OK;
}
static pxl8_result assemble_bsp(pxl8_world_chunk_cache* cache, pxl8_world_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);
@ -391,7 +321,6 @@ void pxl8_world_chunk_cache_destroy(pxl8_world_chunk_cache* cache) {
for (u32 i = 0; i < cache->entry_count; i++) {
pxl8_world_chunk_cache_entry* e = &cache->entries[i];
if (e->chunk) pxl8_world_chunk_destroy(e->chunk);
if (e->mesh) pxl8_mesh_destroy(e->mesh);
}
pxl8_free(cache->assembly.data);
@ -406,9 +335,7 @@ pxl8_result pxl8_world_chunk_cache_receive(pxl8_world_chunk_cache* cache,
pxl8_world_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->id != hdr->id ||
a->version != hdr->version;
if (new_assembly) {
@ -436,26 +363,12 @@ pxl8_result pxl8_world_chunk_cache_receive(pxl8_world_chunk_cache* cache,
if (hdr->flags & PXL8_CHUNK_FLAG_FINAL) {
a->complete = true;
if (a->type == PXL8_WORLD_CHUNK_BSP) {
return assemble_bsp(cache, a);
} else {
return assemble_vxl(cache, a);
}
return assemble_bsp(cache, a);
}
return PXL8_OK;
}
pxl8_world_chunk* pxl8_world_chunk_cache_get_vxl(pxl8_world_chunk_cache* cache, i32 cx, i32 cy, i32 cz) {
if (!cache) return NULL;
pxl8_world_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_world_chunk* pxl8_world_chunk_cache_get_bsp(pxl8_world_chunk_cache* cache, u32 id) {
if (!cache) return NULL;
pxl8_world_chunk_cache_entry* e = find_entry_bsp(cache, id);
@ -466,84 +379,7 @@ pxl8_world_chunk* pxl8_world_chunk_cache_get_bsp(pxl8_world_chunk_cache* cache,
return NULL;
}
pxl8_mesh* pxl8_world_chunk_cache_get_mesh(pxl8_world_chunk_cache* cache,
i32 cx, i32 cy, i32 cz,
const pxl8_vxl_block_registry* registry,
const pxl8_vxl_mesh_config* config) {
if (!cache || !registry) return NULL;
pxl8_world_chunk_cache_entry* entry = find_entry_vxl(cache, cx, cy, cz);
if (!entry || !entry->chunk || !entry->chunk->voxels) return NULL;
pxl8_world_chunk* nx = pxl8_world_chunk_cache_get_vxl(cache, cx - 1, cy, cz);
pxl8_world_chunk* px = pxl8_world_chunk_cache_get_vxl(cache, cx + 1, cy, cz);
pxl8_world_chunk* ny = pxl8_world_chunk_cache_get_vxl(cache, cx, cy - 1, cz);
pxl8_world_chunk* py = pxl8_world_chunk_cache_get_vxl(cache, cx, cy + 1, cz);
pxl8_world_chunk* nz = pxl8_world_chunk_cache_get_vxl(cache, cx, cy, cz - 1);
pxl8_world_chunk* pz = pxl8_world_chunk_cache_get_vxl(cache, cx, cy, cz + 1);
bool all_neighbors = nx && px && ny && py && nz && pz;
if (entry->mesh && !entry->mesh_dirty) {
if (entry->has_all_neighbors == all_neighbors) {
return entry->mesh;
}
}
if (entry->mesh) {
pxl8_mesh_destroy(entry->mesh);
entry->mesh = NULL;
}
const pxl8_vxl_chunk* neighbors[6] = {
nx ? nx->voxels : NULL,
px ? px->voxels : NULL,
ny ? ny->voxels : NULL,
py ? py->voxels : NULL,
nz ? nz->voxels : NULL,
pz ? pz->voxels : NULL
};
pxl8_vxl_mesh_config local_config = config ? *config : PXL8_VXL_MESH_CONFIG_DEFAULT;
entry->mesh = pxl8_vxl_build_mesh(entry->chunk->voxels, neighbors, registry, &local_config);
entry->mesh_dirty = false;
entry->has_all_neighbors = all_neighbors;
return entry->mesh;
}
void pxl8_world_chunk_cache_tick(pxl8_world_chunk_cache* cache) {
if (!cache) return;
cache->frame_counter++;
}
void pxl8_world_chunk_cache_evict_distant(pxl8_world_chunk_cache* cache,
i32 cx, i32 cy, i32 cz, i32 radius) {
if (!cache) return;
for (u32 i = 0; i < cache->entry_count; i++) {
pxl8_world_chunk_cache_entry* e = &cache->entries[i];
if (!e->valid || !e->chunk || e->chunk->type != PXL8_WORLD_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_world_chunk_destroy(e->chunk);
if (e->mesh) pxl8_mesh_destroy(e->mesh);
e->chunk = NULL;
e->mesh = NULL;
e->valid = false;
}
}
}
void pxl8_world_chunk_cache_invalidate_meshes(pxl8_world_chunk_cache* cache) {
if (!cache) return;
for (u32 i = 0; i < cache->entry_count; i++) {
cache->entries[i].mesh_dirty = true;
}
}

View file

@ -3,7 +3,6 @@
#include "pxl8_mesh.h"
#include "pxl8_protocol.h"
#include "pxl8_types.h"
#include "pxl8_vxl_render.h"
#include "pxl8_world_chunk.h"
#ifdef __cplusplus
@ -16,15 +15,11 @@ extern "C" {
typedef struct pxl8_world_chunk_cache_entry {
pxl8_world_chunk* chunk;
pxl8_mesh* mesh;
u64 last_used;
bool mesh_dirty;
bool valid;
bool has_all_neighbors;
} pxl8_world_chunk_cache_entry;
typedef struct pxl8_world_chunk_assembly {
pxl8_world_chunk_type type;
u32 id;
i32 cx, cy, cz;
u32 version;
@ -51,18 +46,9 @@ pxl8_result pxl8_world_chunk_cache_receive(pxl8_world_chunk_cache* cache,
const pxl8_chunk_msg_header* hdr,
const u8* payload, usize len);
pxl8_world_chunk* pxl8_world_chunk_cache_get_vxl(pxl8_world_chunk_cache* cache, i32 cx, i32 cy, i32 cz);
pxl8_world_chunk* pxl8_world_chunk_cache_get_bsp(pxl8_world_chunk_cache* cache, u32 id);
pxl8_mesh* pxl8_world_chunk_cache_get_mesh(pxl8_world_chunk_cache* cache,
i32 cx, i32 cy, i32 cz,
const pxl8_vxl_block_registry* registry,
const pxl8_vxl_mesh_config* config);
void pxl8_world_chunk_cache_tick(pxl8_world_chunk_cache* cache);
void pxl8_world_chunk_cache_evict_distant(pxl8_world_chunk_cache* cache,
i32 cx, i32 cy, i32 cz, i32 radius);
void pxl8_world_chunk_cache_invalidate_meshes(pxl8_world_chunk_cache* cache);
#ifdef __cplusplus
}