feat(gui): add toolbar widget

feat(gui): add grid_select, toggle, panel, status_bar, image widgets
fix(bsp): fill in exterior cells
This commit is contained in:
asrael 2026-02-27 06:50:49 -06:00
parent 5a565844dd
commit 8d491612ab
63 changed files with 3150 additions and 1686 deletions

41
.clang-format Normal file
View file

@ -0,0 +1,41 @@
BasedOnStyle: LLVM
Language: C
Standard: Latest
IndentWidth: 4
TabWidth: 4
UseTab: Never
ColumnLimit: 120
PointerAlignment: Left
ReferenceAlignment: Left
SpaceBeforeParens: ControlStatements
BreakBeforeBraces: Attach
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: WithoutElse
AllowShortLoopsOnASingleLine: false
AllowShortBlocksOnASingleLine: Empty
BinPackArguments: true
BinPackParameters: true
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: Consecutive
AlignEscapedNewlines: Left
AlignOperands: Align
IncludeBlocks: Preserve
SortIncludes: Never
SpaceAfterCStyleCast: false
SpacesInParens: Never
IndentCaseLabels: false
IndentPPDirectives: None
MaxEmptyLinesToKeep: 1
KeepEmptyLines:
AtEndOfFile: false
AtStartOfBlock: false
AtStartOfFile: false

30
.clangd Normal file
View file

@ -0,0 +1,30 @@
CompileFlags:
CompilationDatabase: .
Diagnostics:
UnusedIncludes: None
MissingIncludes: None
ClangTidy:
Add:
- bugprone-sizeof-expression
- bugprone-suspicious-memset-usage
- bugprone-integer-division
- bugprone-macro-parentheses
- bugprone-bool-pointer-implicit-conversion
- bugprone-multiple-statement-macro
- performance-type-promotion-in-math-fn
Remove:
- bugprone-easily-swappable-parameters
InlayHints:
Enabled: Yes
ParameterNames: Yes
DeducedTypes: Yes
Designators: Yes
BlockEnd: No
Hover:
ShowAKA: Yes
Completion:
AllScopes: Yes

9
.gitignore vendored
View file

@ -1,6 +1,5 @@
.ccls-cache/
.ccls
**.DS_Store
.pxl8_history
*.aseprite-extension
.cache
compile_commands.json
**.DS_Store
.pxl8_history

View file

@ -1,4 +1,3 @@
{:title "pxl8 demo"
:pixel-mode "indexed"
:resolution "640x360"
:window-size [1280 720]}

View file

@ -1,36 +1,9 @@
(local pxl8 (require :pxl8))
(local DOOR_HEIGHT 96)
(local DOOR_WIDTH 48)
(local DOOR_X 892)
(local DOOR_Z 416)
(local FIREBALL_COLOR 218)
(var door-mesh nil)
(var door-tex nil)
(var fireball-mesh nil)
(fn create-door-mesh [bsp ambient]
(let [verts []
indices []
hw (/ DOOR_WIDTH 2)
y0 0
y1 DOOR_HEIGHT
x DOOR_X
sample (fn [vx vy vz]
(if bsp (bsp:light_at vx vy vz (or ambient 0)) 200))]
(table.insert verts {:x x :y y0 :z (- DOOR_Z hw) :u 0 :v 1 :nx -1 :ny 0 :nz 0 :color 80 :light (sample x y0 (- DOOR_Z hw))})
(table.insert verts {:x x :y y0 :z (+ DOOR_Z hw) :u 1 :v 1 :nx -1 :ny 0 :nz 0 :color 80 :light (sample x y0 (+ DOOR_Z hw))})
(table.insert verts {:x x :y y1 :z (+ DOOR_Z hw) :u 1 :v 0 :nx -1 :ny 0 :nz 0 :color 80 :light (sample x y1 (+ DOOR_Z hw))})
(table.insert verts {:x x :y y1 :z (- DOOR_Z hw) :u 0 :v 0 :nx -1 :ny 0 :nz 0 :color 80 :light (sample x y1 (- DOOR_Z hw))})
(table.insert indices 0)
(table.insert indices 1)
(table.insert indices 2)
(table.insert indices 0)
(table.insert indices 2)
(table.insert indices 3)
(set door-mesh (pxl8.create_mesh verts indices))))
(fn create-fireball-mesh []
(let [verts []
indices []
@ -145,40 +118,15 @@
(set fireball-mesh (pxl8.create_mesh verts indices))))
(fn get-door-position []
(values DOOR_X DOOR_Z))
(fn get-door-radius []
20)
(fn init [textures]
(when (not fireball-mesh)
(create-fireball-mesh))
(when (and (not door-tex) textures)
(set door-tex (textures.door))))
(create-fireball-mesh)))
(fn setup-lighting [bsp ambient]
(when (and (not door-mesh) bsp)
(create-door-mesh bsp ambient)))
(fn render-door [wireframe floor-y]
(when (and door-mesh door-tex)
(pxl8.draw_mesh door-mesh {:x 0 :y (or floor-y 0) :z 0
:texture door-tex
:lighting true
:double_sided true
:wireframe wireframe})))
(fn render-fireball [x y z wireframe]
(fn render-fireball [x y z]
(when fireball-mesh
(pxl8.draw_mesh fireball-mesh {:x x :y y :z z
:emissive true
:wireframe wireframe})))
:emissive true})))
{:FIREBALL_COLOR FIREBALL_COLOR
:get-door-position get-door-position
:get-door-radius get-door-radius
:init init
:render-door render-door
:render-fireball render-fireball
:setup-lighting setup-lighting}
:render-fireball render-fireball}

View file

@ -8,8 +8,8 @@
(local sky (require :mod.sky))
(local textures (require :mod.textures))
(local bob-amount 4.0)
(local bob-speed 8.0)
(local bob-amount 3.0)
(local bob-speed 6.0)
(local cam-smoothing 0.25)
(local land-recovery-speed 20)
(local land-squash-amount -4)
@ -31,7 +31,6 @@
(var auto-run-cancel-key nil)
(var auto-run? false)
(var was-auto-running false)
(var bob-time 0)
(var cam-pitch 0)
(var day-time 0)
@ -40,29 +39,29 @@
(var cam-yaw 0)
(var cam-z 416)
(var camera nil)
(var ceiling-tex nil)
(var floor-tex nil)
(var land-squash 0)
(var last-dt 0.016)
(var light-time 0)
(var glows nil)
(var lights nil)
(var bsp-materials-setup false)
(var materials-set? false)
(var network nil)
(var portal-cooldown 0)
(var real-time 0)
(var smooth-cam-x 416)
(var smooth-cam-z 416)
(var was-grounded true)
(var trim-tex nil)
(var wall-tex nil)
(var world nil)
(local MOSS_COLOR 200)
(local PLASTER_COLOR 16)
(local GRASS_COLOR 200)
(local STONE_WALL_START 2)
(local WOOD_COLOR 88)
(local ASHLAR_COLOR 64)
(local ASHLAR_MOSS 68)
(local STONE_FLOOR_COLOR 72)
(local STONE_TRIM_COLOR 72)
(fn preload []
(when (not network)
(set network (net.get))
@ -71,8 +70,7 @@
(when (not world)
(set world (pxl8.get_world))
(when world
(world:set_sim_config sim-cfg)
(world:init_local_player cam-x cam-y cam-z))))
(world:set_sim_config sim-cfg))))
(fn is-connected []
(and network (network:connected)))
@ -111,29 +109,38 @@
(when world
(world:init_local_player cam-x cam-y cam-z)
(set smooth-cam-x cam-x)
(set smooth-cam-z cam-z))
(when (not ceiling-tex)
(set ceiling-tex (textures.plaster-wall 66666 PLASTER_COLOR)))
(when (not floor-tex)
(set floor-tex (textures.wood-planks 44444 WOOD_COLOR)))
(when (not trim-tex)
(set trim-tex (textures.wood-trim 77777 WOOD_COLOR)))
(when (not wall-tex)
(set wall-tex (textures.cobble-timber 55555 STONE_WALL_START MOSS_COLOR WOOD_COLOR))))
(set smooth-cam-z cam-z)))
(fn setup-materials []
(when (and world (not bsp-materials-setup) floor-tex trim-tex wall-tex)
(when (not world) (lua "return"))
(when (not network) (lua "return"))
(when materials-set? (lua "return"))
(when (not (network:has_chunk)) (lua "return"))
(let [chunk (world:active_chunk)]
(when (and chunk (chunk:ready))
(let [floor-mat (pxl8.create_material {:texture floor-tex :lighting true :double_sided true})
trim-mat (pxl8.create_material {:texture trim-tex :lighting true :double_sided true})
wall-mat (pxl8.create_material {:texture wall-tex :lighting true :double_sided true})]
(world:set_bsp_material 0 floor-mat)
(world:set_bsp_material 1 wall-mat)
(world:set_bsp_material 3 trim-mat)
(entities.setup-lighting (chunk:bsp) 2)
(set bsp-materials-setup true))))))
(when (not chunk) (lua "return"))
(when (not (chunk:ready)) (lua "return"))
(let [dungeon-floor-tex (textures.wood-planks 44444 WOOD_COLOR)
dungeon-wall-tex (textures.cobble-timber 55555 STONE_WALL_START MOSS_COLOR WOOD_COLOR)
dungeon-trim-tex (textures.wood-trim 77777 WOOD_COLOR)
court-floor-tex (textures.rough-stone 44442 STONE_FLOOR_COLOR)
court-wall-tex (textures.ashlar-wall 55552 ASHLAR_COLOR ASHLAR_MOSS)
court-trim-tex (textures.rough-stone 77772 STONE_TRIM_COLOR)
grass-tex (textures.grass-top 44447 GRASS_COLOR)
dungeon-floor-mat (pxl8.create_material {:texture dungeon-floor-tex :lighting true :double_sided true})
dungeon-wall-mat (pxl8.create_material {:texture dungeon-wall-tex :lighting true :double_sided true})
dungeon-trim-mat (pxl8.create_material {:texture dungeon-trim-tex :lighting true :double_sided true})
court-floor-mat (pxl8.create_material {:texture court-floor-tex :lighting true :double_sided true})
court-wall-mat (pxl8.create_material {:texture court-wall-tex :lighting true :double_sided true})
court-trim-mat (pxl8.create_material {:texture court-trim-tex :lighting true :double_sided true})
grass-mat (pxl8.create_material {:texture grass-tex :lighting true :double_sided true})]
(world:set_bsp_material 0 dungeon-floor-mat)
(world:set_bsp_material 1 dungeon-wall-mat)
(world:set_bsp_material 3 dungeon-trim-mat)
(world:set_bsp_material 4 court-floor-mat)
(world:set_bsp_material 5 court-wall-mat)
(world:set_bsp_material 6 court-trim-mat)
(world:set_bsp_material 7 grass-mat)
(set materials-set? true))))
(fn sample-input []
(when (pxl8.key_pressed "`")
@ -147,8 +154,6 @@
(when (and auto-run-cancel-key (not (pxl8.key_down auto-run-cancel-key)))
(set auto-run-cancel-key nil))
(set was-auto-running auto-run?)
(pxl8.make_input_msg
{:move_x (+ (if (pxl8.key_down "d") 1 0) (if (pxl8.key_down "a") -1 0))
:move_y (+ (if (or (pxl8.key_down "w") auto-run?) 1 0)
@ -161,41 +166,6 @@
(set last-dt dt)
(setup-materials)
(when (> portal-cooldown 0)
(set portal-cooldown (- portal-cooldown dt)))
(when (and world network (<= portal-cooldown 0))
(let [(door-x door-z) (entities.get-door-position)
door-radius (entities.get-door-radius)
dist-to-door (math.sqrt (+ (* (- cam-x door-x) (- cam-x door-x))
(* (- cam-z door-z) (- cam-z door-z))))]
(when (< dist-to-door door-radius)
(let [current-id (network:chunk_id)]
(if (= current-id 1)
(do
(pxl8.info "Door: BSP 1 -> BSP 2")
(network:enter_scene 1 2 416 0 416)
(world:init_local_player 416 0 416)
(set cam-x 416)
(set cam-y 0)
(set cam-z 416)
(set smooth-cam-x 416)
(set smooth-cam-z 416)
(set bsp-materials-setup false)
(set portal-cooldown 2.0))
(= current-id 2)
(do
(pxl8.info "Door: BSP 2 -> BSP 1")
(network:enter_scene 1 1 416 0 416)
(world:init_local_player 416 0 416)
(set cam-x 416)
(set cam-y 0)
(set cam-z 416)
(set smooth-cam-x 416)
(set smooth-cam-z 416)
(set bsp-materials-setup false)
(set portal-cooldown 2.0)))))))
(when world
(let [input-msg (sample-input)
player (world:local_player)]
@ -286,9 +256,7 @@
(world:render [smooth-cam-x eye-y smooth-cam-z])
(when chunk
(entities.render-fireball light-x light-y light-z (menu.is-wireframe)))
(entities.render-door (menu.is-wireframe) 0)
(entities.render-fireball light-x light-y light-z))
(pxl8.end_frame_3d)

39
pxl8.sh
View file

@ -43,10 +43,6 @@ NC='\033[0m'
RED='\033[38;2;251;73;52m'
YELLOW='\033[38;2;250;189;47m'
if [[ "$(uname)" == "Linux" ]]; then
CFLAGS="$CFLAGS -D_GNU_SOURCE"
fi
build_luajit() {
if [[ ! -f "lib/luajit/src/libluajit.a" ]]; then
print_info "Building LuaJIT"
@ -167,6 +163,37 @@ compile_source_file() {
fi
}
generate_compile_commands() {
local project_dir
project_dir="$(pwd)"
local tmpfile
tmpfile=$(mktemp)
local first=true
echo "[" > "$tmpfile"
for src_file in $LIB_SOURCE_FILES; do
src_file="${src_file## }"
src_file="${src_file%% }"
[[ -z "$src_file" ]] && continue
if $first; then first=false; else printf ",\n" >> "$tmpfile"; fi
printf ' {\n "directory": "%s",\n "command": "clang -c %s %s",\n "file": "%s"\n }' \
"$project_dir" "$DEP_COMPILE_FLAGS" "$src_file" "$src_file" >> "$tmpfile"
done
for src_file in $PXL8_SOURCE_FILES; do
src_file="${src_file## }"
src_file="${src_file%% }"
[[ -z "$src_file" ]] && continue
if $first; then first=false; else printf ",\n" >> "$tmpfile"; fi
printf ' {\n "directory": "%s",\n "command": "clang -c %s %s",\n "file": "%s"\n }' \
"$project_dir" "$COMPILE_FLAGS" "$src_file" "$src_file" >> "$tmpfile"
done
printf "\n]\n" >> "$tmpfile"
mv "$tmpfile" "compile_commands.json"
}
compile_shaders() {
local build_mode="$1"
local shader_dir="src/gfx/shaders/cpu"
@ -536,11 +563,13 @@ case "$COMMAND" in
"
if [[ "$HAS_SDL3" -eq 1 ]]; then
PXL8_SOURCE_FILES="$PXL8_SOURCE_FILES src/hal/pxl8_mem_sdl3.c"
PXL8_SOURCE_FILES="$PXL8_SOURCE_FILES src/hal/pxl8_io_sdl3.c src/hal/pxl8_mem_sdl3.c"
else
PXL8_SOURCE_FILES="$PXL8_SOURCE_FILES src/hal/pxl8_mem.c"
fi
generate_compile_commands
LUAJIT_LIB="lib/luajit/src/libluajit.a"
OBJECT_DIR="$BUILDDIR/obj"
mkdir -p "$OBJECT_DIR"

View file

@ -1,5 +1,3 @@
extern crate alloc;
use alloc::boxed::Box;
use alloc::vec::Vec;
@ -146,6 +144,7 @@ pub struct Bsp {
pub vertex_lights: Box<[u32]>,
pub vertices: Box<[Vertex]>,
pub visdata: Box<[u8]>,
pub heightfield: Box<[f32]>,
}
#[derive(Default)]
@ -161,6 +160,16 @@ pub struct BspBuilder {
pub vertex_lights: Vec<u32>,
pub vertices: Vec<Vertex>,
pub visdata: Vec<u8>,
pub heightfield: Vec<f32>,
pub heightfield_w: u16,
pub heightfield_h: u16,
pub heightfield_ox: f32,
pub heightfield_oz: f32,
pub heightfield_cell_size: f32,
pub bounds_min_x: f32,
pub bounds_min_z: f32,
pub bounds_max_x: f32,
pub bounds_max_z: f32,
}
impl BspBuilder {
@ -182,6 +191,7 @@ impl From<BspBuilder> for Bsp {
let vertex_lights = b.vertex_lights.into_boxed_slice();
let vertices = b.vertices.into_boxed_slice();
let visdata = b.visdata.into_boxed_slice();
let heightfield = b.heightfield.into_boxed_slice();
let inner = pxl8_bsp {
cell_portals: if cell_portals.is_empty() { core::ptr::null_mut() } else { cell_portals.as_ptr() as *mut _ },
@ -198,6 +208,7 @@ impl From<BspBuilder> for Bsp {
vertex_lights: if vertex_lights.is_empty() { core::ptr::null_mut() } else { vertex_lights.as_ptr() as *mut _ },
vertices: if vertices.is_empty() { core::ptr::null_mut() } else { vertices.as_ptr() as *mut _ },
visdata: if visdata.is_empty() { core::ptr::null_mut() } else { visdata.as_ptr() as *mut _ },
heightfield: if heightfield.is_empty() { core::ptr::null_mut() } else { heightfield.as_ptr() as *mut _ },
lightdata_size: 0,
num_cell_portals: cell_portals.len() as u32,
num_edges: edges.len() as u32,
@ -211,7 +222,17 @@ impl From<BspBuilder> for Bsp {
num_surfedges: surfedges.len() as u32,
num_vertex_lights: vertex_lights.len() as u32,
num_vertices: vertices.len() as u32,
num_heightfield: heightfield.len() as u32,
heightfield_ox: b.heightfield_ox,
heightfield_oz: b.heightfield_oz,
heightfield_cell_size: b.heightfield_cell_size,
heightfield_w: b.heightfield_w,
heightfield_h: b.heightfield_h,
visdata_size: visdata.len() as u32,
bounds_min_x: b.bounds_min_x,
bounds_min_z: b.bounds_min_z,
bounds_max_x: b.bounds_max_x,
bounds_max_z: b.bounds_max_z,
};
Self {
@ -227,6 +248,7 @@ impl From<BspBuilder> for Bsp {
vertex_lights,
vertices,
visdata,
heightfield,
}
}
}

View file

@ -1,38 +1,23 @@
extern crate alloc;
pub mod stream;
use crate::bsp::Bsp;
use crate::math::Vec3;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum ChunkId {
Bsp(u32),
Bsp { cx: i32, cz: i32 },
}
pub enum Chunk {
Bsp { id: u32, bsp: Bsp, version: u32 },
Bsp { cx: i32, cz: i32, bsp: Bsp, version: u32 },
}
impl Chunk {
pub fn id(&self) -> ChunkId {
match self {
Chunk::Bsp { id, .. } => ChunkId::Bsp(*id),
}
}
pub fn version(&self) -> u32 {
match self {
Chunk::Bsp { version, .. } => *version,
}
}
pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 {
match self {
Chunk::Bsp { bsp, .. } => bsp.trace(from, to, radius),
}
}
pub fn as_bsp(&self) -> Option<&Bsp> {
match self {
Chunk::Bsp { bsp, .. } => Some(bsp),

View file

@ -1,5 +1,3 @@
extern crate alloc;
use alloc::collections::BTreeMap;
use alloc::vec::Vec;
@ -28,11 +26,11 @@ impl ClientChunkState {
}
pub fn next_pending(&mut self) -> Option<ChunkId> {
self.pending.pop()
if self.pending.is_empty() {
None
} else {
Some(self.pending.remove(0))
}
pub fn queue_message(&mut self, msg: ChunkMessage) {
self.pending_messages.push(msg);
}
pub fn queue_messages(&mut self, msgs: Vec<ChunkMessage>) {
@ -50,23 +48,6 @@ impl ClientChunkState {
pub fn mark_sent(&mut self, id: ChunkId, version: u32) {
self.known.insert(id, version);
}
pub fn has_pending(&self) -> bool {
!self.pending.is_empty() || !self.pending_messages.is_empty()
}
pub fn clear_pending(&mut self) {
self.pending.clear();
self.pending_messages.clear();
}
pub fn clear_known_bsp(&mut self) {
self.known.retain(|id, _| !matches!(id, ChunkId::Bsp(_)));
}
pub fn forget(&mut self, id: &ChunkId) {
self.known.remove(id);
}
}
impl Default for ClientChunkState {

View file

@ -31,7 +31,7 @@ pub use bsp::*;
pub use chunk::*;
pub use chunk::stream::*;
pub use math::*;
pub use procgen::{ProcgenParams, generate, generate_rooms};
pub use procgen::generate_chunk;
pub use pxl8::*;
pub use sim::*;
pub use transport::*;

View file

@ -3,9 +3,12 @@
extern crate alloc;
use alloc::vec::Vec;
use pxl8d::*;
use pxl8d::chunk::ChunkId;
use pxl8d::chunk::stream::ClientChunkState;
use pxl8d::pxl8::pxl8_cmd_type::*;
use pxl8d::pxl8::pxl8_msg_type::*;
const TICK_RATE: u64 = 30;
const TICK_NS: u64 = 1_000_000_000 / TICK_RATE;
@ -73,6 +76,9 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
let mut player_id: Option<u64> = None;
let mut last_client_tick: u64 = 0;
let mut client_chunks = ClientChunkState::new();
let mut player_cx: i32 = 0;
let mut player_cz: i32 = 0;
let mut has_sent_initial_enter = false;
let mut sequence: u32 = 0;
let mut last_tick = get_time_ns();
@ -93,52 +99,14 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
let mut latest_input: Option<pxl8d::pxl8_input_msg> = None;
while let Some(msg_type) = transport.recv() {
match msg_type {
x if x == pxl8d::pxl8_msg_type::PXL8_MSG_INPUT as u8 => {
x if x == PXL8_MSG_INPUT as u8 => {
latest_input = Some(transport.get_input());
}
x if x == pxl8d::pxl8_msg_type::PXL8_MSG_COMMAND as u8 => {
x if x == PXL8_MSG_COMMAND as u8 => {
let cmd = transport.get_command();
if cmd.cmd_type == pxl8d::pxl8_cmd_type::PXL8_CMD_SPAWN_ENTITY as u16 {
if cmd.cmd_type == PXL8_CMD_SPAWN_ENTITY as u16 {
let (x, y, z, _yaw, _pitch) = extract_spawn_position(&cmd.payload);
player_id = Some(sim.spawn_player(x, y, z) as u64);
sim.world.generate_bsp(1, None);
sim.world.set_active(ChunkId::Bsp(1));
client_chunks.request(ChunkId::Bsp(1));
transport.send_chunk_enter(1, transport::CHUNK_TYPE_BSP, sequence);
sequence = sequence.wrapping_add(1);
} else if cmd.cmd_type == pxl8d::pxl8_cmd_type::PXL8_CMD_ENTER_SCENE as u16 {
let chunk_id = u32::from_be_bytes([
cmd.payload[0], cmd.payload[1], cmd.payload[2], cmd.payload[3]
]);
let pos_x = f32::from_be_bytes([cmd.payload[8], cmd.payload[9], cmd.payload[10], cmd.payload[11]]);
let pos_y = f32::from_be_bytes([cmd.payload[12], cmd.payload[13], cmd.payload[14], cmd.payload[15]]);
let pos_z = f32::from_be_bytes([cmd.payload[16], cmd.payload[17], cmd.payload[18], cmd.payload[19]]);
sim.world.clear_active();
client_chunks.clear_pending();
client_chunks.clear_known_bsp();
transport.send_chunk_exit(sequence);
sequence = sequence.wrapping_add(1);
let params = match chunk_id {
2 => Some(ProcgenParams {
width: 20,
height: 20,
seed: 12345u32.wrapping_add(2),
min_room_size: 14,
max_room_size: 16,
num_rooms: 1,
}),
_ => None,
};
sim.world.generate_bsp(chunk_id, params.as_ref());
sim.world.set_active(ChunkId::Bsp(chunk_id));
client_chunks.request(ChunkId::Bsp(chunk_id));
transport.send_chunk_enter(chunk_id, transport::CHUNK_TYPE_BSP, sequence);
sequence = sequence.wrapping_add(1);
sim.teleport_player(pos_x, pos_y, pos_z);
}
}
_ => {}
@ -154,7 +122,7 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
}
let mut count = 0;
sim.generate_snapshot(player_id.unwrap_or(u64::MAX), |state| {
sim.generate_snapshot(|state| {
if count < entities_buf.len() {
entities_buf[count] = *state;
count += 1;
@ -173,22 +141,46 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
sequence = sequence.wrapping_add(1);
if let Some(pid) = player_id {
if let Some(_player) = sim.get_player_position(pid) {
if let Some((px, _py, pz)) = sim.get_player_position(pid) {
let new_cx = libm::floorf(px / 1024.0) as i32;
let new_cz = libm::floorf(pz / 1024.0) as i32;
for dz in -2..=2_i32 {
for dx in -2..=2_i32 {
let cx = new_cx + dx;
let cz = new_cz + dz;
sim.world.generate_bsp(cx, cz);
client_chunks.request(ChunkId::Bsp { cx, cz });
}
}
if !has_sent_initial_enter || player_cx != new_cx || player_cz != new_cz {
player_cx = new_cx;
player_cz = new_cz;
sim.world.set_active(ChunkId::Bsp { cx: new_cx, cz: new_cz });
transport.send_chunk_enter(new_cx, new_cz, sequence);
sequence = sequence.wrapping_add(1);
has_sent_initial_enter = true;
}
let mut burst = false;
while let Some(chunk_id) = client_chunks.next_pending() {
match chunk_id {
ChunkId::Bsp(id) => {
ChunkId::Bsp { cx, cz } => {
if let Some(chunk) = sim.world.get(&chunk_id) {
if let Some(bsp) = chunk.as_bsp() {
let msgs = bsp_to_messages(bsp, id, chunk.version());
let msgs = bsp_to_messages(bsp, cx, cz, chunk.version());
client_chunks.queue_messages(msgs);
client_chunks.mark_sent(chunk_id, chunk.version());
burst = true;
}
}
}
}
}
for _ in 0..8 {
let send_limit = if burst { 64 } else { 8 };
for _ in 0..send_limit {
if let Some(msg) = client_chunks.next_message() {
transport.send_chunk(&msg, sequence);
sequence = sequence.wrapping_add(1);
@ -202,30 +194,21 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
}
}
fn bsp_to_messages(bsp: &bsp::Bsp, id: u32, version: u32) -> alloc::vec::Vec<transport::ChunkMessage> {
use alloc::vec::Vec;
fn bsp_to_messages(bsp: &bsp::Bsp, cx: i32, cz: i32, version: u32) -> Vec<transport::ChunkMessage> {
let mut data = Vec::new();
let num_verts = bsp.vertices.len();
let num_edges = bsp.edges.len();
let num_faces = bsp.faces.len();
let num_planes = bsp.planes.len();
let num_nodes = bsp.nodes.len();
let num_leafs = bsp.leafs.len();
let num_surfedges = bsp.surfedges.len();
data.extend_from_slice(&(num_verts as u32).to_be_bytes());
data.extend_from_slice(&(num_edges as u32).to_be_bytes());
data.extend_from_slice(&(num_faces as u32).to_be_bytes());
data.extend_from_slice(&(num_planes as u32).to_be_bytes());
data.extend_from_slice(&(num_nodes as u32).to_be_bytes());
data.extend_from_slice(&(num_leafs as u32).to_be_bytes());
data.extend_from_slice(&(num_surfedges as u32).to_be_bytes());
data.extend_from_slice(&(bsp.vertices.len() as u32).to_be_bytes());
data.extend_from_slice(&(bsp.edges.len() as u32).to_be_bytes());
data.extend_from_slice(&(bsp.faces.len() as u32).to_be_bytes());
data.extend_from_slice(&(bsp.planes.len() as u32).to_be_bytes());
data.extend_from_slice(&(bsp.nodes.len() as u32).to_be_bytes());
data.extend_from_slice(&(bsp.leafs.len() as u32).to_be_bytes());
data.extend_from_slice(&(bsp.surfedges.len() as u32).to_be_bytes());
data.extend_from_slice(&(bsp.marksurfaces.len() as u32).to_be_bytes());
data.extend_from_slice(&(bsp.cell_portals.len() as u32).to_be_bytes());
data.extend_from_slice(&(bsp.visdata.len() as u32).to_be_bytes());
data.extend_from_slice(&(bsp.vertex_lights.len() as u32).to_be_bytes());
data.extend_from_slice(&(bsp.heightfield.len() as u32).to_be_bytes());
for v in &bsp.vertices {
data.extend_from_slice(&v.position.x.to_be_bytes());
@ -318,5 +301,23 @@ fn bsp_to_messages(bsp: &bsp::Bsp, id: u32, version: u32) -> alloc::vec::Vec<tra
data.extend_from_slice(&vl.to_be_bytes());
}
transport::ChunkMessage::from_bsp(data, id, version)
if !bsp.heightfield.is_empty() {
for h in &bsp.heightfield {
data.extend_from_slice(&h.to_be_bytes());
}
let c_bsp = bsp.as_c_bsp();
data.extend_from_slice(&c_bsp.heightfield_w.to_be_bytes());
data.extend_from_slice(&c_bsp.heightfield_h.to_be_bytes());
data.extend_from_slice(&c_bsp.heightfield_ox.to_be_bytes());
data.extend_from_slice(&c_bsp.heightfield_oz.to_be_bytes());
data.extend_from_slice(&c_bsp.heightfield_cell_size.to_be_bytes());
}
let c_bsp = bsp.as_c_bsp();
data.extend_from_slice(&c_bsp.bounds_min_x.to_be_bytes());
data.extend_from_slice(&c_bsp.bounds_min_z.to_be_bytes());
data.extend_from_slice(&c_bsp.bounds_max_x.to_be_bytes());
data.extend_from_slice(&c_bsp.bounds_max_z.to_be_bytes());
transport::ChunkMessage::from_bsp(data, cx, cz, version)
}

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,6 @@
extern crate alloc;
use alloc::vec::Vec;
use crate::chunk::ChunkId;
use crate::math::Vec3;
use crate::pxl8::*;
use crate::world::World;
@ -96,13 +95,6 @@ impl Simulation {
id
}
pub fn teleport_player(&mut self, x: f32, y: f32, z: f32) {
if let Some(id) = self.player {
let ent = &mut self.entities[id as usize];
ent.pos = Vec3::new(x, y, z);
}
}
pub fn step(&mut self, inputs: &[pxl8_input_msg], dt: f32) {
self.tick += 1;
self.time += dt;
@ -114,22 +106,8 @@ impl Simulation {
self.integrate(dt);
}
fn make_sim_world(&self, _pos: Vec3) -> pxl8_sim_world {
if let Some(chunk) = self.world.active() {
if let Some(bsp) = chunk.as_bsp() {
return pxl8_sim_world {
bsp: bsp.as_c_bsp(),
};
}
}
pxl8_sim_world {
bsp: core::ptr::null(),
}
}
fn integrate(&mut self, dt: f32) {
let cfg = pxl8_sim_config {
fn sim_config() -> pxl8_sim_config {
pxl8_sim_config {
move_speed: 180.0,
ground_accel: 10.0,
air_accel: 1.0,
@ -140,7 +118,39 @@ impl Simulation {
player_radius: 16.0,
player_height: 72.0,
max_pitch: 1.5,
}
}
fn make_sim_world(&self, player_pos: Vec3) -> pxl8_sim_world {
let chunk_size: f32 = 16.0 * 64.0;
let center_cx = libm::floorf(player_pos.x / chunk_size) as i32;
let center_cz = libm::floorf(player_pos.z / chunk_size) as i32;
let mut sim = pxl8_sim_world {
chunks: [core::ptr::null(); 9],
center_cx,
center_cz,
chunk_size,
};
for dz in -1..=1i32 {
for dx in -1..=1i32 {
let cx = center_cx + dx;
let cz = center_cz + dz;
let id = ChunkId::Bsp { cx, cz };
if let Some(chunk) = self.world.get(&id) {
if let Some(bsp) = chunk.as_bsp() {
let idx = ((dz + 1) * 3 + (dx + 1)) as usize;
sim.chunks[idx] = bsp.as_c_bsp();
}
}
}
}
sim
}
fn integrate(&mut self, dt: f32) {
let cfg = Self::sim_config();
for i in 0..self.entities.len() {
let ent = &self.entities[i];
if ent.flags & ALIVE == 0 || ent.flags & PLAYER != 0 {
@ -156,18 +166,7 @@ impl Simulation {
}
fn move_player(&mut self, input: &pxl8_input_msg, dt: f32) {
let cfg = pxl8_sim_config {
move_speed: 180.0,
ground_accel: 10.0,
air_accel: 1.0,
stop_speed: 100.0,
friction: 6.0,
gravity: 800.0,
jump_velocity: 200.0,
player_radius: 16.0,
player_height: 72.0,
max_pitch: 1.5,
};
let cfg = Self::sim_config();
let Some(id) = self.player else { return };
let ent = &self.entities[id as usize];
if ent.flags & ALIVE == 0 {
@ -181,7 +180,7 @@ impl Simulation {
}
}
pub fn generate_snapshot<F>(&self, _player_id: u64, mut writer: F)
pub fn generate_snapshot<F>(&self, mut writer: F)
where
F: FnMut(&pxl8_entity_state),
{
@ -211,10 +210,6 @@ impl Simulation {
}
}
pub fn entity_count(&self) -> usize {
self.entities.iter().filter(|e| e.flags & ALIVE != 0).count()
}
pub fn get_player_position(&self, player_id: u64) -> Option<(f32, f32, f32)> {
let idx = player_id as usize;
if idx < self.entities.len() {

View file

@ -1,16 +1,18 @@
extern crate alloc;
use alloc::vec;
use alloc::vec::Vec;
use crate::pxl8::*;
use crate::pxl8::pxl8_msg_type::*;
pub const DEFAULT_PORT: u16 = 7777;
pub const CHUNK_MAX_PAYLOAD: usize = 1400;
pub const CHUNK_FLAG_RLE: u8 = 0x01;
pub const CHUNK_FLAG_FINAL: u8 = 0x04;
pub const CHUNK_TYPE_BSP: u8 = 1;
pub fn chunk_hash(cx: i32, cz: i32) -> u32 {
let h = (cx as u32).wrapping_mul(374761393).wrapping_add((cz as u32).wrapping_mul(668265263));
h ^ (h >> 16)
}
pub struct ChunkMessage {
pub chunk_type: u8,
pub id: u32,
@ -25,16 +27,17 @@ pub struct ChunkMessage {
}
impl ChunkMessage {
pub fn from_bsp(data: Vec<u8>, chunk_id: u32, version: u32) -> Vec<ChunkMessage> {
pub fn from_bsp(data: Vec<u8>, cx: i32, cz: i32, version: u32) -> Vec<ChunkMessage> {
let total_size = data.len();
let id = chunk_hash(cx, cz);
if total_size <= CHUNK_MAX_PAYLOAD {
return vec![ChunkMessage {
chunk_type: CHUNK_TYPE_BSP,
id: chunk_id,
cx: 0,
id,
cx,
cy: 0,
cz: 0,
cz,
version,
flags: CHUNK_FLAG_FINAL,
fragment_idx: 0,
@ -53,10 +56,10 @@ impl ChunkMessage {
messages.push(ChunkMessage {
chunk_type: CHUNK_TYPE_BSP,
id: chunk_id,
cx: 0,
id,
cx,
cy: 0,
cz: 0,
cz,
version,
flags: if is_final { CHUNK_FLAG_FINAL } else { 0 },
fragment_idx: i as u8,
@ -362,7 +365,7 @@ impl Transport {
sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr);
}
pub fn send_chunk_enter(&mut self, chunk_id: u32, chunk_type: u8, sequence: u32) {
pub fn send_chunk_enter(&mut self, cx: i32, cz: i32, sequence: u32) {
if !self.has_client {
return;
}
@ -378,34 +381,13 @@ impl Transport {
offset += self.serialize_header(&msg_header, offset);
let buf = &mut self.send_buf[offset..];
buf[0..4].copy_from_slice(&chunk_id.to_be_bytes());
buf[4] = chunk_type;
buf[5] = 0;
buf[6] = 0;
buf[7] = 0;
buf[0..4].copy_from_slice(&cx.to_be_bytes());
buf[4..8].copy_from_slice(&cz.to_be_bytes());
offset += 8;
sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr);
}
pub fn send_chunk_exit(&mut self, sequence: u32) {
if !self.has_client {
return;
}
let mut offset = 0;
let msg_header = pxl8_msg_header {
sequence,
size: 0,
type_: PXL8_MSG_CHUNK_EXIT as u8,
version: PXL8_PROTOCOL_VERSION as u8,
};
offset += self.serialize_header(&msg_header, offset);
sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr);
}
fn serialize_chunk_msg_header(&mut self, msg: &ChunkMessage, offset: usize) -> usize {
let buf = &mut self.send_buf[offset..];
buf[0] = msg.chunk_type;

View file

@ -1,15 +1,12 @@
extern crate alloc;
use alloc::collections::BTreeMap;
use crate::chunk::{Chunk, ChunkId};
use crate::math::Vec3;
use crate::procgen::{ProcgenParams, generate_rooms};
use crate::procgen::generate_chunk;
pub struct World {
active: Option<ChunkId>,
chunks: BTreeMap<ChunkId, Chunk>,
seed: u64,
pub seed: u64,
}
impl World {
@ -21,67 +18,26 @@ impl World {
}
}
pub fn insert(&mut self, chunk: Chunk) {
let id = chunk.id();
self.chunks.insert(id, chunk);
}
pub fn get(&self, id: &ChunkId) -> Option<&Chunk> {
self.chunks.get(id)
}
pub fn get_mut(&mut self, id: &ChunkId) -> Option<&mut Chunk> {
self.chunks.get_mut(id)
}
pub fn remove(&mut self, id: &ChunkId) -> Option<Chunk> {
self.chunks.remove(id)
}
pub fn active(&self) -> Option<&Chunk> {
self.active.as_ref().and_then(|id| self.chunks.get(id))
}
pub fn active_id(&self) -> Option<ChunkId> {
self.active
}
pub fn set_active(&mut self, id: ChunkId) {
self.active = Some(id);
}
pub fn clear_active(&mut self) {
self.active = None;
}
pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 {
if let Some(chunk) = self.active() {
return chunk.trace(from, to, radius);
}
to
}
pub fn generate_bsp(&mut self, id: u32, params: Option<&ProcgenParams>) -> &Chunk {
let chunk_id = ChunkId::Bsp(id);
pub fn generate_bsp(&mut self, cx: i32, cz: i32) -> &Chunk {
let chunk_id = ChunkId::Bsp { cx, cz };
if !self.chunks.contains_key(&chunk_id) {
let default_params = ProcgenParams {
seed: (self.seed as u32).wrapping_add(id),
..Default::default()
};
let p = params.unwrap_or(&default_params);
let bsp = generate_rooms(p);
self.chunks.insert(chunk_id, Chunk::Bsp { id, bsp, version: 1 });
let bsp = generate_chunk(cx, cz, self.seed);
self.chunks.insert(chunk_id, Chunk::Bsp { cx, cz, bsp, version: 1 });
}
self.chunks.get(&chunk_id).unwrap()
}
pub fn contains(&self, id: &ChunkId) -> bool {
self.chunks.contains_key(id)
}
pub fn iter(&self) -> impl Iterator<Item = (&ChunkId, &Chunk)> {
self.chunks.iter()
}
}
impl Default for World {

View file

@ -1,12 +1,9 @@
#include "pxl8_cart.h"
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "pxl8_io.h"
#include "pxl8_log.h"
#include "pxl8_mem.h"
@ -49,7 +46,6 @@ struct pxl8_cart {
char* title;
pxl8_resolution resolution;
pxl8_size window_size;
pxl8_pixel_mode pixel_mode;
bool is_folder;
bool is_mounted;
};
@ -57,11 +53,6 @@ struct pxl8_cart {
static pxl8_cart* pxl8_current_cart = NULL;
static char* pxl8_original_cwd = NULL;
static bool is_directory(const char* path) {
struct stat st;
return stat(path, &st) == 0 && S_ISDIR(st.st_mode);
}
static bool is_pxc_file(const char* path) {
usize len = strlen(path);
return len > 4 && strcmp(path + len - 4, ".pxc") == 0;
@ -70,41 +61,48 @@ static bool is_pxc_file(const char* path) {
static bool has_main_script(const char* base_path) {
char path[512];
snprintf(path, sizeof(path), "%s/main.fnl", base_path);
if (access(path, F_OK) == 0) return true;
if (pxl8_io_file_exists(path)) return true;
snprintf(path, sizeof(path), "%s/main.lua", base_path);
return access(path, F_OK) == 0;
return pxl8_io_file_exists(path);
}
typedef struct {
const char* dir_path;
const char* prefix;
char*** paths;
u32* count;
u32* capacity;
} collect_ctx;
static bool collect_entry(void* userdata, const char* dir_path, const char* name) {
collect_ctx* ctx = userdata;
char full_path[1024];
char rel_path[1024];
snprintf(full_path, sizeof(full_path), "%s%s", dir_path, name);
snprintf(rel_path, sizeof(rel_path), "%s%s", ctx->prefix, name);
if (pxl8_io_is_directory(full_path)) {
char new_prefix[1025];
snprintf(new_prefix, sizeof(new_prefix), "%s/", rel_path);
collect_ctx sub = { .dir_path = full_path, .prefix = new_prefix,
.paths = ctx->paths, .count = ctx->count, .capacity = ctx->capacity };
pxl8_io_enumerate_directory(full_path, collect_entry, &sub);
} else {
if (*ctx->count >= *ctx->capacity) {
*ctx->capacity = (*ctx->capacity == 0) ? 64 : (*ctx->capacity * 2);
*ctx->paths = pxl8_realloc(*ctx->paths, *ctx->capacity * sizeof(char*));
}
(*ctx->paths)[(*ctx->count)++] = strdup(rel_path);
}
return true;
}
static void collect_files_recursive(const char* dir_path, const char* prefix,
char*** paths, u32* count, u32* capacity) {
DIR* dir = opendir(dir_path);
if (!dir) return;
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;
char full_path[1024];
char rel_path[1024];
snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name);
snprintf(rel_path, sizeof(rel_path), "%s%s", prefix, entry->d_name);
struct stat st;
if (stat(full_path, &st) == 0) {
if (S_ISDIR(st.st_mode)) {
char new_prefix[1025];
snprintf(new_prefix, sizeof(new_prefix), "%s/", rel_path);
collect_files_recursive(full_path, new_prefix, paths, count, capacity);
} else {
if (*count >= *capacity) {
*capacity = (*capacity == 0) ? 64 : (*capacity * 2);
*paths = pxl8_realloc(*paths, *capacity * sizeof(char*));
}
(*paths)[(*count)++] = strdup(rel_path);
}
}
}
closedir(dir);
collect_ctx ctx = { .dir_path = dir_path, .prefix = prefix,
.paths = paths, .count = count, .capacity = capacity };
pxl8_io_enumerate_directory(dir_path, collect_entry, &ctx);
}
static pxl8_cart_file* find_file(const pxl8_cart* cart, const char* path) {
@ -156,7 +154,6 @@ pxl8_cart* pxl8_cart_create(void) {
if (cart) {
cart->resolution = PXL8_RESOLUTION_640x360;
cart->window_size = (pxl8_size){1280, 720};
cart->pixel_mode = PXL8_PIXEL_INDEXED;
}
return cart;
}
@ -175,8 +172,8 @@ pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) {
if (!cart || !path) return PXL8_ERROR_NULL_POINTER;
pxl8_cart_unload(cart);
if (is_directory(path)) {
cart->base_path = realpath(path, NULL);
if (pxl8_io_is_directory(path)) {
cart->base_path = pxl8_io_get_real_path(path);
if (!cart->base_path) {
pxl8_error("Failed to resolve cart path: %s", path);
return PXL8_ERROR_FILE_NOT_FOUND;
@ -300,8 +297,8 @@ pxl8_result pxl8_cart_mount(pxl8_cart* cart) {
}
if (cart->is_folder) {
pxl8_original_cwd = getcwd(NULL, 0);
if (chdir(cart->base_path) != 0) {
pxl8_original_cwd = pxl8_io_get_cwd();
if (!pxl8_io_set_cwd(cart->base_path)) {
pxl8_error("Failed to change to cart directory: %s", cart->base_path);
pxl8_free(pxl8_original_cwd);
pxl8_original_cwd = NULL;
@ -323,7 +320,7 @@ void pxl8_cart_unmount(pxl8_cart* cart) {
if (!cart || !cart->is_mounted) return;
if (pxl8_original_cwd) {
chdir(pxl8_original_cwd);
pxl8_io_set_cwd(pxl8_original_cwd);
pxl8_free(pxl8_original_cwd);
pxl8_original_cwd = NULL;
}
@ -364,14 +361,6 @@ void pxl8_cart_set_window_size(pxl8_cart* cart, pxl8_size size) {
if (cart) cart->window_size = size;
}
pxl8_pixel_mode pxl8_cart_get_pixel_mode(const pxl8_cart* cart) {
return cart ? cart->pixel_mode : PXL8_PIXEL_INDEXED;
}
void pxl8_cart_set_pixel_mode(pxl8_cart* cart, pxl8_pixel_mode mode) {
if (cart) cart->pixel_mode = mode;
}
bool pxl8_cart_is_packed(const pxl8_cart* cart) {
return cart && !cart->is_folder;
}
@ -394,7 +383,7 @@ bool pxl8_cart_file_exists(const pxl8_cart* cart, const char* path) {
if (cart->is_folder) {
char full_path[512];
if (!pxl8_cart_resolve_path(cart, path, full_path, sizeof(full_path))) return false;
return access(full_path, F_OK) == 0;
return pxl8_io_file_exists(full_path);
}
return find_file(cart, path) != NULL;
@ -457,7 +446,7 @@ void pxl8_cart_free_file(u8* data) {
pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path) {
if (!folder_path || !output_path) return PXL8_ERROR_NULL_POINTER;
if (!is_directory(folder_path)) {
if (!pxl8_io_is_directory(folder_path)) {
pxl8_error("Cart folder not found: %s", folder_path);
return PXL8_ERROR_FILE_NOT_FOUND;
}
@ -488,9 +477,9 @@ pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path) {
for (u32 i = 0; i < count; i++) {
char full_path[1024];
snprintf(full_path, sizeof(full_path), "%s/%s", folder_path, paths[i]);
struct stat st;
if (stat(full_path, &st) == 0) {
file_sizes[i] = (u32)st.st_size;
usize fsize = pxl8_io_get_file_size(full_path);
if (fsize > 0) {
file_sizes[i] = (u32)fsize;
total_size += file_sizes[i];
} else {
file_sizes[i] = 0;
@ -563,9 +552,10 @@ pxl8_result pxl8_cart_bundle(const char* input_path, const char* output_path, co
u32 cart_size = 0;
bool free_cart = false;
if (is_directory(input_path)) {
if (pxl8_io_is_directory(input_path)) {
char temp_pxc[256];
snprintf(temp_pxc, sizeof(temp_pxc), "/tmp/pxl8_bundle_%d.pxc", getpid());
static u32 bundle_counter = 0;
snprintf(temp_pxc, sizeof(temp_pxc), "/tmp/pxl8_bundle_%u.pxc", bundle_counter++);
pxl8_result result = pxl8_cart_pack(input_path, temp_pxc);
if (result != PXL8_OK) return result;
@ -577,7 +567,7 @@ pxl8_result pxl8_cart_bundle(const char* input_path, const char* output_path, co
cart_data = pxl8_malloc(cart_size);
fread(cart_data, 1, cart_size, f);
fclose(f);
unlink(temp_pxc);
remove(temp_pxc);
free_cart = true;
} else if (is_pxc_file(input_path)) {
FILE* f = fopen(input_path, "rb");
@ -628,7 +618,7 @@ pxl8_result pxl8_cart_bundle(const char* input_path, const char* output_path, co
fwrite(&trailer, sizeof(trailer), 1, out);
fclose(out);
chmod(output_path, 0755);
pxl8_io_set_executable(output_path);
pxl8_info("Bundle created: %s", output_path);
return PXL8_OK;

View file

@ -25,11 +25,9 @@ const char* pxl8_cart_get_base_path(const pxl8_cart* cart);
const char* pxl8_cart_get_title(const pxl8_cart* cart);
pxl8_resolution pxl8_cart_get_resolution(const pxl8_cart* cart);
pxl8_size pxl8_cart_get_window_size(const pxl8_cart* cart);
pxl8_pixel_mode pxl8_cart_get_pixel_mode(const pxl8_cart* cart);
void pxl8_cart_set_title(pxl8_cart* cart, const char* title);
void pxl8_cart_set_resolution(pxl8_cart* cart, pxl8_resolution resolution);
void pxl8_cart_set_window_size(pxl8_cart* cart, pxl8_size size);
void pxl8_cart_set_pixel_mode(pxl8_cart* cart, pxl8_pixel_mode mode);
bool pxl8_cart_is_packed(const pxl8_cart* cart);
bool pxl8_cart_has_embedded(const char* exe_path);

View file

@ -1,24 +1,16 @@
#include "pxl8_save.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
#include <direct.h>
#include <shlobj.h>
#define PATH_SEP '\\'
#else
#include <unistd.h>
#include <pwd.h>
#define PATH_SEP '/'
#endif
#include "pxl8_io.h"
#include "pxl8_log.h"
#include "pxl8_macros.h"
#include "pxl8_mem.h"
#define PATH_SEP '/'
typedef struct {
u32 magic;
u32 version;
@ -49,23 +41,6 @@ static void pxl8_save_get_slot_path(pxl8_save* save, u8 slot, char* path, usize
}
}
static pxl8_result pxl8_save_ensure_directory(const char* path) {
struct stat st;
if (stat(path, &st) == 0) {
return S_ISDIR(st.st_mode) ? PXL8_OK : PXL8_ERROR_SYSTEM_FAILURE;
}
#ifdef _WIN32
if (_mkdir(path) != 0 && errno != EEXIST) {
#else
if (mkdir(path, 0755) != 0 && errno != EEXIST) {
#endif
return PXL8_ERROR_SYSTEM_FAILURE;
}
return PXL8_OK;
}
pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) {
if (!game_name) return NULL;
@ -75,40 +50,17 @@ pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) {
save->magic = magic;
save->version = version;
char base_dir[PXL8_SAVE_MAX_PATH];
#ifdef _WIN32
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, base_dir))) {
snprintf(save->directory, sizeof(save->directory),
"%s%cpxl8%c%s", base_dir, PATH_SEP, PATH_SEP, game_name);
} else {
pxl8_free(save);
return NULL;
}
#else
const char* home = getenv("HOME");
if (!home) {
struct passwd* pw = getpwuid(getuid());
if (pw) home = pw->pw_dir;
}
if (!home) {
char* pref_path = pxl8_io_get_pref_path("pxl8", game_name);
if (!pref_path) {
pxl8_free(save);
return NULL;
}
pxl8_strncpy(save->directory, pref_path, sizeof(save->directory));
pxl8_free(pref_path);
snprintf(base_dir, sizeof(base_dir), "%s/.local/share", home);
pxl8_save_ensure_directory(base_dir);
snprintf(base_dir, sizeof(base_dir), "%s/.local/share/pxl8", home);
pxl8_save_ensure_directory(base_dir);
snprintf(save->directory, sizeof(save->directory),
"%s/.local/share/pxl8/%s", home, game_name);
#endif
if (pxl8_save_ensure_directory(save->directory) != PXL8_OK) {
pxl8_free(save);
return NULL;
usize dir_len = strlen(save->directory);
if (dir_len > 0 && save->directory[dir_len - 1] == '/') {
save->directory[dir_len - 1] = '\0';
}
pxl8_info("Save system initialized: %s", save->directory);
@ -116,9 +68,7 @@ pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) {
}
void pxl8_save_destroy(pxl8_save* save) {
if (save) {
pxl8_free(save);
}
}
pxl8_result pxl8_save_write(pxl8_save* save, u8 slot, const u8* data, u32 size) {
@ -234,9 +184,7 @@ pxl8_result pxl8_save_read(pxl8_save* save, u8 slot, u8** data_out, u32* size_ou
}
void pxl8_save_free(u8* data) {
if (data) {
pxl8_free(data);
}
}
bool pxl8_save_exists(pxl8_save* save, u8 slot) {
@ -248,8 +196,7 @@ bool pxl8_save_exists(pxl8_save* save, u8 slot) {
char path[PXL8_SAVE_MAX_PATH];
pxl8_save_get_slot_path(save, slot, path, sizeof(path));
struct stat st;
return stat(path, &st) == 0;
return pxl8_io_file_exists(path);
}
pxl8_result pxl8_save_delete(pxl8_save* save, u8 slot) {

View file

@ -342,6 +342,7 @@ void pxl8_bsp_destroy(pxl8_bsp* bsp) {
pxl8_free(bsp->vertex_lights);
pxl8_free(bsp->vertices);
pxl8_free(bsp->visdata);
pxl8_free(bsp->heightfield);
memset(bsp, 0, sizeof(*bsp));
}

View file

@ -110,6 +110,7 @@ typedef struct pxl8_bsp {
u32* vertex_lights;
pxl8_bsp_vertex* vertices;
u8* visdata;
f32* heightfield;
u32 lightdata_size;
u32 num_cell_portals;
@ -124,7 +125,17 @@ typedef struct pxl8_bsp {
u32 num_surfedges;
u32 num_vertex_lights;
u32 num_vertices;
u32 num_heightfield;
f32 heightfield_ox;
f32 heightfield_oz;
f32 heightfield_cell_size;
u16 heightfield_w;
u16 heightfield_h;
u32 visdata_size;
f32 bounds_min_x;
f32 bounds_min_z;
f32 bounds_max_x;
f32 bounds_max_z;
} pxl8_bsp;
#ifdef __cplusplus

View file

@ -7,15 +7,6 @@
#include "pxl8_mem.h"
#include "pxl8_mesh.h"
typedef struct {
f32 x0, y0, x1, y1;
} screen_rect;
typedef struct {
u32 leaf;
screen_rect window;
} portal_queue_entry;
static inline bool pxl8_bsp_get_edge_vertex(const pxl8_bsp* bsp, i32 surfedge_idx, u32* out_vert_idx) {
if (surfedge_idx >= (i32)bsp->num_surfedges) return false;
@ -40,83 +31,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 screen_rect_valid(screen_rect r) {
return r.x0 < r.x1 && r.y0 < r.y1;
}
static inline screen_rect screen_rect_intersect(screen_rect a, screen_rect b) {
return (screen_rect){
.x0 = (a.x0 > b.x0) ? a.x0 : b.x0,
.y0 = (a.y0 > b.y0) ? a.y0 : b.y0,
.x1 = (a.x1 < b.x1) ? a.x1 : b.x1,
.y1 = (a.y1 < b.y1) ? a.y1 : b.y1,
};
}
static inline void expand_rect_with_ndc(screen_rect* r, f32 nx, f32 ny) {
if (nx < r->x0) r->x0 = nx;
if (nx > r->x1) r->x1 = nx;
if (ny < r->y0) r->y0 = ny;
if (ny > r->y1) r->y1 = ny;
}
static screen_rect project_portal_to_screen(const pxl8_bsp_portal* portal, const pxl8_mat4* vp, f32 wall_height) {
pxl8_vec3 world_corners[4] = {
{portal->x0, 0, portal->z0},
{portal->x1, 0, portal->z1},
{portal->x1, wall_height, portal->z1},
{portal->x0, wall_height, portal->z0},
};
pxl8_vec4 clip[4];
bool in_front[4];
i32 front_count = 0;
const f32 NEAR_W = 0.001f;
for (i32 i = 0; i < 4; i++) {
clip[i] = pxl8_mat4_multiply_vec4(*vp, (pxl8_vec4){world_corners[i].x, world_corners[i].y, world_corners[i].z, 1.0f});
in_front[i] = clip[i].w > NEAR_W;
if (in_front[i]) front_count++;
}
if (front_count == 0) return (screen_rect){0, 0, 0, 0};
screen_rect result = {1e30f, 1e30f, -1e30f, -1e30f};
for (i32 i = 0; i < 4; i++) {
i32 j = (i + 1) % 4;
pxl8_vec4 a = clip[i];
pxl8_vec4 b = clip[j];
bool a_in = in_front[i];
bool b_in = in_front[j];
if (a_in) {
f32 inv_w = 1.0f / a.w;
expand_rect_with_ndc(&result, a.x * inv_w, a.y * inv_w);
}
if (a_in != b_in) {
f32 t = (NEAR_W - a.w) / (b.w - a.w);
pxl8_vec4 intersection = {
a.x + t * (b.x - a.x),
a.y + t * (b.y - a.y),
a.z + t * (b.z - a.z),
NEAR_W
};
f32 inv_w = 1.0f / intersection.w;
expand_rect_with_ndc(&result, intersection.x * inv_w, intersection.y * inv_w);
}
}
if (result.x0 < -1.0f) result.x0 = -1.0f;
if (result.y0 < -1.0f) result.y0 = -1.0f;
if (result.x1 > 1.0f) result.x1 = 1.0f;
if (result.y1 > 1.0f) result.y1 = 1.0f;
return result;
}
static void collect_face_to_mesh(const pxl8_bsp* bsp, const pxl8_bsp_render_state* state,
u32 face_id, pxl8_mesh* mesh, u8 ambient) {
const pxl8_bsp_face* face = &bsp->faces[face_id];
@ -228,190 +142,38 @@ void pxl8_bsp_render_state_destroy(pxl8_bsp_render_state* state) {
pxl8_free(state);
}
void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const pxl8_gfx_material* material) {
if (!gfx || !bsp || face_id >= bsp->num_faces || !material) return;
pxl8_mesh* mesh = pxl8_mesh_create(64, 192);
if (!mesh) return;
collect_face_to_mesh(bsp, NULL, face_id, mesh, pxl8_gfx_get_ambient(gfx));
if (mesh->index_count > 0) {
pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_3d_draw_mesh(gfx, mesh, &identity, material);
}
pxl8_mesh_destroy(mesh);
}
void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp,
pxl8_bsp_render_state* state, pxl8_vec3 camera_pos) {
pxl8_bsp_render_state* state,
const pxl8_gfx_draw_opts* opts) {
if (!gfx || !bsp || !state || bsp->num_faces == 0) return;
if (!state->materials || state->num_materials == 0) return;
if (!state->render_face_flags) return;
const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx);
const pxl8_mat4* vp = pxl8_3d_get_view_proj(gfx);
if (!frustum || !vp) return;
if (!frustum) return;
i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
if (camera_leaf < 0 || (u32)camera_leaf >= bsp->num_leafs) return;
if (!bsp->cell_portals || bsp->num_cell_portals == 0) {
pxl8_mesh* mesh = pxl8_mesh_create(8192, 16384);
if (!mesh) return;
u32 current_material = 0xFFFFFFFF;
u8 ambient = pxl8_gfx_get_ambient(gfx);
pxl8_mat4 identity = pxl8_mat4_identity();
for (u32 mat = 0; mat < state->num_materials; mat++) {
for (u32 face_id = 0; face_id < bsp->num_faces; face_id++) {
if (!face_in_frustum(bsp, face_id, frustum)) continue;
if (!state->render_face_flags[face_id]) continue;
const pxl8_bsp_face* face = &bsp->faces[face_id];
u32 material_id = face->material_id;
if (material_id >= state->num_materials) continue;
if (material_id != current_material && mesh->index_count > 0 && current_material < state->num_materials) {
pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_3d_draw_mesh(gfx, mesh, &identity, &state->materials[current_material]);
if (face->material_id != mat) continue;
if (!face_in_frustum(bsp, face_id, frustum)) continue;
collect_face_to_mesh(bsp, state, face_id, mesh, ambient);
}
if (mesh->index_count > 0) {
pxl8_gfx_material mat_copy = state->materials[mat];
if (state->exterior) mat_copy.double_sided = true;
pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat_copy, opts);
pxl8_mesh_clear(mesh);
}
current_material = material_id;
collect_face_to_mesh(bsp, state, face_id, mesh, pxl8_gfx_get_ambient(gfx));
}
if (mesh->index_count > 0 && current_material < state->num_materials) {
pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_3d_draw_mesh(gfx, mesh, &identity, &state->materials[current_material]);
}
pxl8_mesh_destroy(mesh);
return;
}
if (!state->render_face_flags && state->num_faces > 0) {
state->render_face_flags = pxl8_calloc(state->num_faces, 1);
if (!state->render_face_flags) return;
}
memset(state->render_face_flags, 0, state->num_faces);
pxl8_bsp_pvs pvs = pxl8_bsp_decompress_pvs(bsp, camera_leaf);
u32 visited_bytes = (bsp->num_leafs + 7) / 8;
u8* visited = pxl8_calloc(visited_bytes, 1);
screen_rect* cell_windows = pxl8_calloc(bsp->num_leafs, sizeof(screen_rect));
portal_queue_entry* queue = pxl8_malloc(bsp->num_leafs * 4 * sizeof(portal_queue_entry));
if (!visited || !cell_windows || !queue) {
pxl8_free(visited);
pxl8_free(cell_windows);
pxl8_free(queue);
pxl8_bsp_pvs_destroy(&pvs);
return;
}
u32 head = 0, tail = 0;
screen_rect full_screen = {-1.0f, -1.0f, 1.0f, 1.0f};
visited[camera_leaf >> 3] |= (1 << (camera_leaf & 7));
cell_windows[camera_leaf] = full_screen;
queue[tail++] = (portal_queue_entry){camera_leaf, full_screen};
f32 wall_height = 128.0f;
while (head < tail) {
portal_queue_entry entry = queue[head++];
u32 leaf_id = entry.leaf;
screen_rect window = entry.window;
if (leaf_id >= bsp->num_cell_portals) continue;
const pxl8_bsp_cell_portals* cp = &bsp->cell_portals[leaf_id];
for (u8 i = 0; i < cp->num_portals; i++) {
const pxl8_bsp_portal* portal = &cp->portals[i];
u32 target = portal->target_leaf;
if (target >= bsp->num_leafs) continue;
if (bsp->leafs[target].contents == -1) continue;
if (!pxl8_bsp_pvs_is_visible(&pvs, target)) continue;
screen_rect portal_rect = project_portal_to_screen(portal, vp, wall_height);
if (!screen_rect_valid(portal_rect)) continue;
screen_rect new_window = screen_rect_intersect(window, portal_rect);
if (!screen_rect_valid(new_window)) continue;
u32 byte = target >> 3;
u32 bit = target & 7;
if (visited[byte] & (1 << bit)) {
screen_rect existing = cell_windows[target];
bool expanded = false;
if (new_window.x0 < existing.x0) { cell_windows[target].x0 = new_window.x0; expanded = true; }
if (new_window.y0 < existing.y0) { cell_windows[target].y0 = new_window.y0; expanded = true; }
if (new_window.x1 > existing.x1) { cell_windows[target].x1 = new_window.x1; expanded = true; }
if (new_window.y1 > existing.y1) { cell_windows[target].y1 = new_window.y1; expanded = true; }
if (expanded && tail < bsp->num_leafs * 4) {
queue[tail++] = (portal_queue_entry){target, cell_windows[target]};
}
continue;
}
visited[byte] |= (1 << bit);
cell_windows[target] = new_window;
queue[tail++] = (portal_queue_entry){target, new_window};
}
}
pxl8_mesh* mesh = pxl8_mesh_create(8192, 16384);
if (!mesh) {
pxl8_free(visited);
pxl8_free(cell_windows);
pxl8_free(queue);
return;
}
u32 current_material = 0xFFFFFFFF;
for (u32 leaf_id = 0; leaf_id < bsp->num_leafs; leaf_id++) {
if (!(visited[leaf_id >> 3] & (1 << (leaf_id & 7)))) continue;
if (bsp->leafs[leaf_id].contents == -1) continue;
const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id];
for (u32 i = 0; i < leaf->num_marksurfaces; i++) {
u32 surf_idx = leaf->first_marksurface + i;
if (surf_idx >= bsp->num_marksurfaces) continue;
u32 face_id = bsp->marksurfaces[surf_idx];
if (face_id >= bsp->num_faces) continue;
if (state->render_face_flags[face_id]) continue;
state->render_face_flags[face_id] = 1;
if (!face_in_frustum(bsp, face_id, frustum)) continue;
const pxl8_bsp_face* face = &bsp->faces[face_id];
u32 material_id = face->material_id;
if (material_id >= state->num_materials) continue;
if (material_id != current_material && mesh->index_count > 0 && current_material < state->num_materials) {
pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_3d_draw_mesh(gfx, mesh, &identity, &state->materials[current_material]);
pxl8_mesh_clear(mesh);
}
current_material = material_id;
collect_face_to_mesh(bsp, state, face_id, mesh, pxl8_gfx_get_ambient(gfx));
}
}
if (mesh->index_count > 0 && current_material < state->num_materials) {
pxl8_mat4 identity = pxl8_mat4_identity();
pxl8_3d_draw_mesh(gfx, mesh, &identity, &state->materials[current_material]);
}
pxl8_bsp_pvs_destroy(&pvs);
pxl8_free(visited);
pxl8_free(cell_windows);
pxl8_free(queue);
pxl8_mesh_destroy(mesh);
}

View file

@ -12,15 +12,15 @@ typedef struct pxl8_bsp_render_state {
u8* render_face_flags;
u32 num_materials;
u32 num_faces;
bool exterior;
} pxl8_bsp_render_state;
pxl8_bsp_render_state* pxl8_bsp_render_state_create(u32 num_faces);
void pxl8_bsp_render_state_destroy(pxl8_bsp_render_state* state);
void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp,
pxl8_bsp_render_state* state, 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_render_state* state,
const pxl8_gfx_draw_opts* opts);
void pxl8_bsp_set_material(pxl8_bsp_render_state* state, u16 material_id,
const pxl8_gfx_material* material);

View file

@ -3,12 +3,8 @@
#define PXL8_VERSION "0.1.0"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "pxl8_ase.h"
#include "pxl8_game.h"
#include "pxl8_hal.h"
@ -150,20 +146,11 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
}
if (bundle_mode) {
char exe_path[1024];
ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);
if (len == -1) {
pxl8_error("failed to resolve executable path");
return PXL8_ERROR_SYSTEM_FAILURE;
}
exe_path[len] = '\0';
pxl8_result result = pxl8_cart_bundle(pack_input, pack_output, exe_path);
return result;
return pxl8_cart_bundle(pack_input, pack_output, argv[0]);
}
if (pack_mode) {
pxl8_result result = pxl8_cart_pack(pack_input, pack_output);
return result;
return pxl8_cart_pack(pack_input, pack_output);
}
if (remap_palette_mode) {
@ -191,14 +178,14 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
}
const char* cart_path = script_arg;
char* original_cwd = getcwd(NULL, 0);
char* original_cwd = pxl8_io_get_cwd();
bool load_embedded = has_embedded && !run_mode;
bool load_from_path = false;
if (!load_embedded && run_mode) {
if (!cart_path) {
if (access("main.fnl", F_OK) == 0 || access("main.lua", F_OK) == 0) {
if (pxl8_io_file_exists("main.fnl") || pxl8_io_file_exists("main.lua")) {
cart_path = ".";
} else {
pxl8_error("no main.fnl or main.lua found in current directory");
@ -206,9 +193,7 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
return PXL8_ERROR_INITIALIZATION_FAILED;
}
}
struct stat st;
load_from_path = (stat(cart_path, &st) == 0 && S_ISDIR(st.st_mode)) ||
strstr(cart_path, ".pxc");
load_from_path = pxl8_io_is_directory(cart_path) || strstr(cart_path, ".pxc");
}
if (load_embedded || load_from_path) {
@ -239,7 +224,6 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
const char* window_title = pxl8_cart_get_title(sys->cart);
if (!window_title) window_title = "pxl8";
pxl8_resolution resolution = pxl8_cart_get_resolution(sys->cart);
pxl8_pixel_mode pixel_mode = pxl8_cart_get_pixel_mode(sys->cart);
pxl8_size window_size = pxl8_cart_get_window_size(sys->cart);
pxl8_size render_size = pxl8_get_resolution_dimensions(resolution);
@ -249,17 +233,12 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
return PXL8_ERROR_INITIALIZATION_FAILED;
}
game->gfx = pxl8_gfx_create(sys->hal, sys->platform_data, pixel_mode, resolution);
game->gfx = pxl8_gfx_create(sys->hal, sys->platform_data, resolution);
if (!game->gfx) {
pxl8_error("failed to create graphics context");
return PXL8_ERROR_INITIALIZATION_FAILED;
}
if (pxl8_gfx_load_font_atlas(game->gfx) != PXL8_OK) {
pxl8_error("failed to load font atlas");
return PXL8_ERROR_INITIALIZATION_FAILED;
}
game->mixer = pxl8_sfx_mixer_create(sys->hal);
if (!game->mixer) {
pxl8_error("failed to create audio mixer");
@ -375,18 +354,13 @@ pxl8_result pxl8_update(pxl8* sys) {
pxl8_repl_command* cmd = pxl8_repl_pop_command(sys->repl);
if (cmd) {
pxl8_result result = pxl8_script_eval_repl(game->script, pxl8_repl_command_buffer(cmd));
if (result != PXL8_OK) {
if (pxl8_script_is_incomplete_input(game->script)) {
pxl8_repl_signal_complete(sys->repl);
} else {
if (result != PXL8_OK && !pxl8_script_is_incomplete_input(game->script)) {
pxl8_error("%s", pxl8_script_get_last_error(game->script));
pxl8_repl_clear_accumulator(sys->repl);
pxl8_repl_signal_complete(sys->repl);
}
} else {
if (result == PXL8_OK || !pxl8_script_is_incomplete_input(game->script)) {
pxl8_repl_clear_accumulator(sys->repl);
pxl8_repl_signal_complete(sys->repl);
}
pxl8_repl_signal_complete(sys->repl);
}
}

View file

@ -81,46 +81,9 @@ pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, usize si
return pxl8_io_write_file(path, (const char*)data, size);
}
bool pxl8_io_file_exists(const char* path) {
if (!path) return false;
struct stat st;
return stat(path, &st) == 0;
}
f64 pxl8_io_get_file_modified_time(const char* path) {
if (!path) return 0.0;
struct stat st;
if (stat(path, &st) == 0) {
return st.st_mtime;
}
return 0.0;
}
pxl8_result pxl8_io_create_directory(const char* path) {
if (!path) return PXL8_ERROR_NULL_POINTER;
#ifdef _WIN32
if (mkdir(path) != 0) {
#else
if (mkdir(path, 0755) != 0) {
#endif
return PXL8_ERROR_SYSTEM_FAILURE;
}
return PXL8_OK;
}
void pxl8_io_free_file_content(char* content) {
if (content) {
pxl8_free(content);
}
}
void pxl8_io_free_binary_data(u8* data) {
if (data) {
pxl8_free(data);
}
}
static i32 pxl8_key_code(const char* key_name) {

View file

@ -1,7 +1,6 @@
#pragma once
#include <stdio.h>
#include <sys/stat.h>
#include "pxl8_bytes.h"
#include "pxl8_types.h"
@ -10,11 +9,20 @@
extern "C" {
#endif
typedef bool (*pxl8_io_dir_callback)(void* userdata, const char* dir_path, const char* name);
pxl8_result pxl8_io_create_directory(const char* path);
bool pxl8_io_enumerate_directory(const char* path, pxl8_io_dir_callback callback, void* userdata);
bool pxl8_io_file_exists(const char* path);
void pxl8_io_free_binary_data(u8* data);
void pxl8_io_free_file_content(char* content);
char* pxl8_io_get_cwd(void);
f64 pxl8_io_get_file_modified_time(const char* path);
usize pxl8_io_get_file_size(const char* path);
char* pxl8_io_get_pref_path(const char* org, const char* app);
char* pxl8_io_get_real_path(const char* path);
bool pxl8_io_is_directory(const char* path);
bool pxl8_io_set_cwd(const char* path);
void pxl8_io_set_executable(const char* path);
pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, usize* size);
pxl8_result pxl8_io_read_file(const char* path, char** content, usize* size);
pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, usize size);

View file

@ -7,11 +7,11 @@
#include <string.h>
#include <time.h>
#define PXL8_LOG_COLOR_ERROR "\033[38;2;251;73;52m"
#define PXL8_LOG_COLOR_WARN "\033[38;2;250;189;47m"
#define PXL8_LOG_COLOR_INFO "\033[38;2;184;187;38m"
#define PXL8_LOG_COLOR_DEBUG "\033[38;2;131;165;152m"
#define PXL8_LOG_COLOR_TRACE "\033[38;2;211;134;155m"
#define PXL8_LOG_COLOR_ERROR "\033[1;38;2;251;73;52m"
#define PXL8_LOG_COLOR_WARN "\033[1;38;2;250;189;47m"
#define PXL8_LOG_COLOR_INFO "\033[1;38;2;184;187;38m"
#define PXL8_LOG_COLOR_DEBUG "\033[1;38;2;131;165;152m"
#define PXL8_LOG_COLOR_TRACE "\033[1;38;2;211;134;155m"
#define PXL8_LOG_COLOR_RESET "\033[0m"
static pxl8_log* g_log = NULL;

View file

@ -4,7 +4,7 @@
#include "pxl8_types.h"
#define PXL8_QUEUE_CAPACITY 256
#define PXL8_QUEUE_CAPACITY 512
typedef struct pxl8_queue {
void* items[PXL8_QUEUE_CAPACITY];

View file

@ -31,12 +31,6 @@ typedef __int128_t i128;
typedef __uint128_t u128;
#endif
typedef enum pxl8_pixel_mode {
PXL8_PIXEL_INDEXED = 1,
PXL8_PIXEL_HICOLOR = 2,
PXL8_PIXEL_RGBA = 4,
} pxl8_pixel_mode;
typedef enum pxl8_cursor {
PXL8_CURSOR_ARROW,
PXL8_CURSOR_HAND
@ -108,3 +102,4 @@ typedef struct pxl8_viewport {
i32 scaled_width, scaled_height;
f32 scale;
} pxl8_viewport;

View file

@ -1,11 +1,8 @@
#include "pxl8_atlas.h"
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pxl8_color.h"
#include "pxl8_log.h"
#include "pxl8_mem.h"
@ -144,15 +141,14 @@ static void pxl8_skyline_compact(pxl8_skyline* skyline) {
}
}
pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_pixel_mode pixel_mode) {
pxl8_atlas* pxl8_atlas_create(u32 width, u32 height) {
pxl8_atlas* atlas = (pxl8_atlas*)pxl8_calloc(1, sizeof(pxl8_atlas));
if (!atlas) return NULL;
atlas->height = height;
atlas->width = width;
i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode);
atlas->pixels = (u8*)pxl8_calloc(width * height, bytes_per_pixel);
atlas->pixels = (u8*)pxl8_calloc(width * height, 1);
if (!atlas->pixels) {
pxl8_free(atlas);
return NULL;
@ -226,14 +222,13 @@ void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count) {
atlas->dirty = true;
}
bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode) {
bool pxl8_atlas_expand(pxl8_atlas* atlas) {
if (!atlas || atlas->width >= 4096) return false;
i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode);
u32 new_size = atlas->width * 2;
u32 old_width = atlas->width;
u8* new_pixels = (u8*)pxl8_calloc(new_size * new_size, bytes_per_pixel);
u8* new_pixels = (u8*)pxl8_calloc(new_size * new_size, 1);
if (!new_pixels) return false;
pxl8_skyline new_skyline;
@ -268,13 +263,9 @@ bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode) {
for (u32 x = 0; x < (u32)atlas->entries[i].w; x++) {
u32 src_idx = (atlas->entries[i].y + y) * old_width + (atlas->entries[i].x + x);
u32 dst_idx = (fit.pos.y + y) * new_size + (fit.pos.x + x);
if (bytes_per_pixel == 2) {
((u16*)new_pixels)[dst_idx] = ((u16*)atlas->pixels)[src_idx];
} else {
new_pixels[dst_idx] = atlas->pixels[src_idx];
}
}
}
atlas->entries[i].x = fit.pos.x;
atlas->entries[i].y = fit.pos.y;
@ -304,15 +295,14 @@ u32 pxl8_atlas_add_texture(
pxl8_atlas* atlas,
const u8* pixels,
u32 w,
u32 h,
pxl8_pixel_mode pixel_mode
u32 h
) {
if (!atlas || !pixels) return UINT32_MAX;
pxl8_skyline_fit fit =
pxl8_skyline_find_position(&atlas->skyline, atlas->width, atlas->height, w, h);
if (!fit.found) {
if (!pxl8_atlas_expand(atlas, pixel_mode)) {
if (!pxl8_atlas_expand(atlas)) {
return UINT32_MAX;
}
@ -347,19 +337,13 @@ u32 pxl8_atlas_add_texture(
entry->h = h;
entry->log2_w = pxl8_log2(w);
i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode);
for (u32 y = 0; y < h; y++) {
for (u32 x = 0; x < w; x++) {
u32 src_idx = y * w + x;
u32 dst_idx = (fit.pos.y + y) * atlas->width + (fit.pos.x + x);
if (bytes_per_pixel == 2) {
((u16*)atlas->pixels)[dst_idx] = ((const u16*)pixels)[src_idx];
} else {
atlas->pixels[dst_idx] = pixels[src_idx];
}
}
}
u32 tiled_tex_size = w * h;
u32 new_tiled_size = atlas->tiled_size + tiled_tex_size;

View file

@ -30,11 +30,11 @@ static inline u8 pxl8_log2(u32 v) {
extern "C" {
#endif
u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u32 h, pxl8_pixel_mode pixel_mode);
u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u32 h);
void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count);
pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_pixel_mode pixel_mode);
pxl8_atlas* pxl8_atlas_create(u32 width, u32 height);
void pxl8_atlas_destroy(pxl8_atlas* atlas);
bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode);
bool pxl8_atlas_expand(pxl8_atlas* atlas);
const pxl8_atlas_entry* pxl8_atlas_get_entry(const pxl8_atlas* atlas, u32 id);
u32 pxl8_atlas_get_entry_count(const pxl8_atlas* atlas);
u32 pxl8_atlas_get_height(const pxl8_atlas* atlas);

View file

@ -1,33 +1,6 @@
#include "pxl8_blit.h"
void pxl8_blit_hicolor(u16* fb, u32 fb_width, const u16* sprite, u32 atlas_width,
i32 x, i32 y, u32 w, u32 h) {
u16* dest_base = fb + y * fb_width + x;
const u16* src_base = sprite;
for (u32 row = 0; row < h; row++) {
u16* dest_row = dest_base + row * fb_width;
const u16* src_row = src_base + row * atlas_width;
u32 col = 0;
u32 count2 = w / 2;
for (u32 i = 0; i < count2; i++) {
u32 pixels = ((const u32*)src_row)[i];
if (pixels == 0) {
col += 2;
continue;
}
dest_row[col] = pxl8_blend_hicolor((u16)(pixels), dest_row[col]);
dest_row[col + 1] = pxl8_blend_hicolor((u16)(pixels >> 16), dest_row[col + 1]);
col += 2;
}
if (w & 1) {
dest_row[col] = pxl8_blend_hicolor(src_row[col], dest_row[col]);
}
}
}
void pxl8_blit_indexed(u8* fb, u32 fb_width, const u8* sprite, u32 atlas_width,
void pxl8_blit(u8* fb, u32 fb_width, const u8* sprite, u32 atlas_width,
i32 x, i32 y, u32 w, u32 h) {
u8* dest_base = fb + y * fb_width + x;
const u8* src_base = sprite;
@ -44,14 +17,14 @@ void pxl8_blit_indexed(u8* fb, u32 fb_width, const u8* sprite, u32 atlas_width,
col += 4;
continue;
}
dest_row[col] = pxl8_blend_indexed((u8)(pixels), dest_row[col]);
dest_row[col + 1] = pxl8_blend_indexed((u8)(pixels >> 8), dest_row[col + 1]);
dest_row[col + 2] = pxl8_blend_indexed((u8)(pixels >> 16), dest_row[col + 2]);
dest_row[col + 3] = pxl8_blend_indexed((u8)(pixels >> 24), dest_row[col + 3]);
dest_row[col] = pxl8_blit_mask((u8)(pixels), dest_row[col]);
dest_row[col + 1] = pxl8_blit_mask((u8)(pixels >> 8), dest_row[col + 1]);
dest_row[col + 2] = pxl8_blit_mask((u8)(pixels >> 16), dest_row[col + 2]);
dest_row[col + 3] = pxl8_blit_mask((u8)(pixels >> 24), dest_row[col + 3]);
col += 4;
}
for (; col < w; col++) {
dest_row[col] = pxl8_blend_indexed(src_row[col], dest_row[col]);
dest_row[col] = pxl8_blit_mask(src_row[col], dest_row[col]);
}
}
}

View file

@ -6,22 +6,12 @@
extern "C" {
#endif
static inline u8 pxl8_blend_indexed(u8 src, u8 dst) {
static inline u8 pxl8_blit_mask(u8 src, u8 dst) {
u8 m = (u8)(-(src != 0));
return (src & m) | (dst & ~m);
}
static inline u16 pxl8_blend_hicolor(u16 src, u16 dst) {
u16 m = (u16)(-(src != 0));
return (src & m) | (dst & ~m);
}
void pxl8_blit_hicolor(
u16* fb, u32 fb_width,
const u16* sprite, u32 atlas_width,
i32 x, i32 y, u32 w, u32 h
);
void pxl8_blit_indexed(
void pxl8_blit(
u8* fb, u32 fb_width,
const u8* sprite, u32 atlas_width,
i32 x, i32 y, u32 w, u32 h

View file

@ -2,10 +2,6 @@
#include "pxl8_types.h"
static inline i32 pxl8_bytes_per_pixel(pxl8_pixel_mode mode) {
return (i32)mode;
}
static inline u32 pxl8_color_from_rgba(u32 rgba) {
u8 r = (rgba >> 24) & 0xFF;
u8 g = (rgba >> 16) & 0xFF;
@ -76,30 +72,10 @@ static inline void pxl8_rgb332_unpack(u8 c, u8* r, u8* g, u8* b) {
*b = (bi << 6) | (bi << 4) | (bi << 2) | bi;
}
static inline u16 pxl8_rgb565_pack(u8 r, u8 g, u8 b) {
return ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
}
static inline void pxl8_rgb565_unpack(u16 color, u8* r, u8* g, u8* b) {
*r = (color >> 11) << 3;
*g = ((color >> 5) & 0x3F) << 2;
*b = (color & 0x1F) << 3;
}
static inline u32 pxl8_rgb565_to_rgba32(u16 color) {
u8 r, g, b;
pxl8_rgb565_unpack(color, &r, &g, &b);
return r | ((u32)g << 8) | ((u32)b << 16) | 0xFF000000;
}
static inline u32 pxl8_rgba32_pack(u8 r, u8 g, u8 b, u8 a) {
return r | ((u32)g << 8) | ((u32)b << 16) | ((u32)a << 24);
}
static inline u16 pxl8_rgba32_to_rgb565(u32 rgba) {
return pxl8_rgb565_pack(rgba & 0xFF, (rgba >> 8) & 0xFF, (rgba >> 16) & 0xFF);
}
static inline void pxl8_rgba32_unpack(u32 color, u8* r, u8* g, u8* b, u8* a) {
*r = color & 0xFF;
*g = (color >> 8) & 0xFF;

View file

@ -6,7 +6,6 @@
#include "pxl8_ase.h"
#include "pxl8_atlas.h"
#include "pxl8_blit.h"
#include "pxl8_color.h"
#include "pxl8_colormap.h"
#include "pxl8_font.h"
#include "pxl8_glows.h"
@ -72,7 +71,6 @@ struct pxl8_gfx {
pxl8_palette_cube* palette_cube;
pxl8_gfx_pass frame_pass;
pxl8_frame_resources frame_res;
pxl8_pixel_mode pixel_mode;
void* platform_data;
pxl8_sprite_cache_entry* sprite_cache;
u32 sprite_cache_capacity;
@ -100,34 +98,19 @@ pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx) {
return bounds;
}
pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx) {
return gfx ? gfx->pixel_mode : PXL8_PIXEL_INDEXED;
}
u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx) {
if (!gfx || gfx->pixel_mode == PXL8_PIXEL_HICOLOR) return NULL;
u8* pxl8_gfx_framebuffer(pxl8_gfx* gfx) {
if (!gfx) return NULL;
return (u8*)pxl8_texture_get_data(gfx->renderer, gfx->color_target);
}
u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx) {
if (!gfx || gfx->pixel_mode != PXL8_PIXEL_HICOLOR) return NULL;
return (u16*)pxl8_texture_get_data(gfx->renderer, gfx->color_target);
}
i32 pxl8_gfx_get_height(const pxl8_gfx* gfx) {
return gfx ? gfx->framebuffer_height : 0;
}
u32* pxl8_gfx_get_output(pxl8_gfx* gfx) {
if (!gfx) return NULL;
return gfx->output;
}
void pxl8_gfx_resolve(pxl8_gfx* gfx) {
static void pxl8_gfx_resolve(pxl8_gfx* gfx) {
if (!gfx || !gfx->initialized) return;
if (gfx->pixel_mode != PXL8_PIXEL_INDEXED) return;
const u32* pal = gfx->palette ? pxl8_palette_colors(gfx->palette) : NULL;
const u32* pal = pxl8_palette_colors(gfx->palette);
if (!pal) {
pxl8_error("resolve: no palette!");
return;
@ -180,18 +163,15 @@ i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath) {
if (!gfx || !gfx->palette || !filepath) return -1;
pxl8_result result = pxl8_palette_load_ase(gfx->palette, filepath);
if (result != PXL8_OK) return (i32)result;
if (gfx->pixel_mode != PXL8_PIXEL_HICOLOR) {
if (gfx->colormap) {
pxl8_colormap_generate(gfx->colormap, pxl8_palette_colors(gfx->palette));
}
}
return 0;
}
pxl8_gfx* pxl8_gfx_create(
const pxl8_hal* hal,
void* platform_data,
pxl8_pixel_mode mode,
pxl8_resolution resolution
) {
pxl8_shader_registry_init();
@ -205,7 +185,6 @@ pxl8_gfx* pxl8_gfx_create(
gfx->hal = hal;
gfx->platform_data = platform_data;
gfx->pixel_mode = mode;
pxl8_size size = pxl8_get_resolution_dimensions(resolution);
gfx->framebuffer_width = size.w;
gfx->framebuffer_height = size.h;
@ -216,9 +195,7 @@ pxl8_gfx* pxl8_gfx_create(
return NULL;
}
if (mode != PXL8_PIXEL_HICOLOR) {
gfx->palette = pxl8_palette_create();
}
gfx->renderer = pxl8_renderer_create(gfx->framebuffer_width, gfx->framebuffer_height);
if (!gfx->renderer) {
@ -258,9 +235,7 @@ pxl8_gfx* pxl8_gfx_create(
return NULL;
}
if (mode != PXL8_PIXEL_HICOLOR) {
gfx->colormap = pxl8_calloc(1, sizeof(pxl8_colormap));
}
gfx->target_stack[0] = (pxl8_target_entry){
.color = gfx->color_target,
@ -313,7 +288,7 @@ void pxl8_gfx_destroy(pxl8_gfx* gfx) {
static pxl8_result pxl8_gfx_ensure_atlas(pxl8_gfx* gfx) {
if (gfx->atlas) return PXL8_OK;
gfx->atlas = pxl8_atlas_create(PXL8_DEFAULT_ATLAS_SIZE, PXL8_DEFAULT_ATLAS_SIZE, gfx->pixel_mode);
gfx->atlas = pxl8_atlas_create(PXL8_DEFAULT_ATLAS_SIZE, PXL8_DEFAULT_ATLAS_SIZE);
return gfx->atlas ? PXL8_OK : PXL8_ERROR_OUT_OF_MEMORY;
}
@ -335,7 +310,7 @@ pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width,
pxl8_result result = pxl8_gfx_ensure_atlas(gfx);
if (result != PXL8_OK) return result;
u32 texture_id = pxl8_atlas_add_texture(gfx->atlas, pixels, width, height, gfx->pixel_mode);
u32 texture_id = pxl8_atlas_add_texture(gfx->atlas, pixels, width, height);
if (texture_id == UINT32_MAX) {
pxl8_error("Texture doesn't fit in atlas");
return PXL8_ERROR_INVALID_SIZE;
@ -381,8 +356,7 @@ pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) {
gfx->atlas,
ase_file.frames[0].pixels,
ase_file.header.width,
ase_file.header.height,
gfx->pixel_mode
ase_file.header.height
);
pxl8_ase_destroy(&ase_file);
@ -418,37 +392,18 @@ pxl8_atlas* pxl8_gfx_get_atlas(pxl8_gfx* gfx) {
return gfx->atlas;
}
pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx) {
(void)gfx;
return PXL8_OK;
}
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) {
if (!gfx || !gfx->initialized || !gfx->hal) return;
if (gfx->pixel_mode == PXL8_PIXEL_INDEXED) {
pxl8_gfx_resolve(gfx);
gfx->hal->upload_texture(
gfx->platform_data,
gfx->output,
gfx->framebuffer_width,
gfx->framebuffer_height,
PXL8_PIXEL_RGBA,
4,
NULL
);
return;
}
u32* colors = gfx->palette ? pxl8_palette_colors(gfx->palette) : NULL;
u8* framebuffer = (u8*)pxl8_texture_get_data(gfx->renderer, gfx->color_target);
gfx->hal->upload_texture(
gfx->platform_data,
framebuffer,
gfx->framebuffer_width,
gfx->framebuffer_height,
gfx->pixel_mode,
colors
);
}
void pxl8_gfx_present(pxl8_gfx* gfx) {
@ -472,10 +427,6 @@ void pxl8_gfx_set_viewport(pxl8_gfx* gfx, pxl8_viewport vp) {
gfx->viewport = vp;
}
void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom) {
(void)gfx; (void)left; (void)right; (void)top; (void)bottom;
}
static pxl8_gfx_texture gfx_current_color(pxl8_gfx* gfx) {
if (gfx->target_stack_depth == 0) return gfx->color_target;
return gfx->target_stack[gfx->target_stack_depth - 1].color;
@ -639,7 +590,7 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo
if (is_1to1_scale && is_unclipped && !is_flipped) {
const u8* sprite_data = atlas_pixels + entry->y * atlas_width + entry->x;
pxl8_blit_indexed(framebuffer, fb_width, sprite_data, atlas_width, x, y, w, h);
pxl8_blit(framebuffer, fb_width, sprite_data, atlas_width, x, y, w, h);
} else {
for (i32 py = 0; py < draw_height; py++) {
for (i32 px = 0; px < draw_width; px++) {
@ -652,7 +603,7 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo
i32 src_idx = src_y * atlas_width + src_x;
i32 dest_idx = (dest_y + py) * fb_width + (dest_x + px);
framebuffer[dest_idx] = pxl8_blend_indexed(atlas_pixels[src_idx], framebuffer[dest_idx]);
framebuffer[dest_idx] = pxl8_blit_mask(atlas_pixels[src_idx], framebuffer[dest_idx]);
}
}
}
@ -667,7 +618,7 @@ void pxl8_gfx_update(pxl8_gfx* gfx, f32 dt) {
}
}
pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_shader_uniforms* uniforms) {
static pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_shader_uniforms* uniforms) {
pxl8_3d_frame frame = {0};
if (!camera) return frame;
@ -770,6 +721,11 @@ void pxl8_3d_clear_depth(pxl8_gfx* gfx) {
pxl8_cmdbuf_clear_depth(gfx->cmdbuf, gfx_current_depth(gfx));
}
void pxl8_3d_clear_stencil(pxl8_gfx* gfx, u8 value) {
if (!gfx) return;
pxl8_clear_stencil(gfx->renderer, value);
}
void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color) {
if (!gfx) return;
@ -789,7 +745,7 @@ void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color) {
pxl8_draw_line(gfx->renderer, gfx_current_color(gfx), x0, y0, x1, y1, color);
}
void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material) {
void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material, const pxl8_gfx_draw_opts* opts) {
if (!gfx || !mesh || !model || !material) return;
if (!pxl8_gfx_handle_valid(gfx->frame_pass)) return;
@ -801,6 +757,13 @@ void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* mo
shader_name = "unlit";
}
static const pxl8_gfx_draw_opts default_opts = {
.color_write = true,
.depth_compare = PXL8_GFX_COMPARE_LESS,
.depth_write = true,
};
if (!opts) opts = &default_opts;
pxl8_gfx_pipeline_desc pipe_desc = {
.blend = {
.enabled = false,
@ -809,7 +772,9 @@ void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* mo
.alpha_test = false,
.alpha_ref = 0,
},
.depth = { .test = true, .write = true, .compare = PXL8_GFX_COMPARE_LESS },
.color_write = opts->color_write,
.depth = { .test = true, .write = opts->depth_write, .compare = opts->depth_compare },
.stencil = { .test = opts->stencil_test, .write = opts->stencil_write, .compare = opts->stencil_compare, .ref = opts->stencil_ref },
.dither = material->dither,
.double_sided = material->double_sided,
.emissive = material->emissive,
@ -948,11 +913,6 @@ void pxl8_3d_end_frame(pxl8_gfx* gfx) {
res->texture_count = 0;
}
u8* pxl8_3d_get_framebuffer(pxl8_gfx* gfx) {
if (!gfx) return NULL;
return (u8*)pxl8_texture_get_data(gfx->renderer, gfx_current_color(gfx));
}
bool pxl8_gfx_push_target(pxl8_gfx* gfx) {
if (!gfx || gfx->target_stack_depth >= PXL8_MAX_TARGET_STACK) return false;

View file

@ -36,7 +36,7 @@ typedef enum pxl8_gfx_effect {
extern "C" {
#endif
pxl8_gfx* pxl8_gfx_create(const pxl8_hal* hal, void* platform_data, pxl8_pixel_mode mode, pxl8_resolution resolution);
pxl8_gfx* pxl8_gfx_create(const pxl8_hal* hal, void* platform_data, pxl8_resolution resolution);
void pxl8_gfx_destroy(pxl8_gfx* gfx);
void pxl8_gfx_present(pxl8_gfx* gfx);
@ -46,25 +46,21 @@ void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx);
u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color);
pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx);
u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx);
u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx);
u8* pxl8_gfx_framebuffer(pxl8_gfx* gfx);
i32 pxl8_gfx_get_height(const pxl8_gfx* gfx);
const u32* pxl8_gfx_palette_colors(pxl8_gfx* gfx);
u16* pxl8_gfx_get_zbuffer(pxl8_gfx* gfx);
pxl8_palette* pxl8_gfx_palette(pxl8_gfx* gfx);
pxl8_colormap* pxl8_gfx_colormap(pxl8_gfx* gfx);
pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx);
i32 pxl8_gfx_get_width(const pxl8_gfx* gfx);
i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath);
void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom);
void pxl8_gfx_set_palette_colors(pxl8_gfx* gfx, const u32* colors, u16 count);
void pxl8_gfx_set_viewport(pxl8_gfx* gfx, pxl8_viewport vp);
pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height);
void pxl8_gfx_clear_textures(pxl8_gfx* gfx);
pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width, u32 height);
pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx);
pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path);
bool pxl8_gfx_push_target(pxl8_gfx* gfx);

View file

@ -4,6 +4,7 @@
#include "pxl8_lights.h"
#include "pxl8_math.h"
#include "pxl8_mesh.h"
#include "pxl8_render_types.h"
#include "pxl8_shader.h"
#include "pxl8_types.h"
@ -29,16 +30,14 @@ void pxl8_3d_set_bsp(pxl8_gfx* gfx, const pxl8_bsp* bsp);
void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_shader_uniforms* uniforms);
void pxl8_3d_clear(pxl8_gfx* gfx, u8 color);
void pxl8_3d_clear_depth(pxl8_gfx* gfx);
void pxl8_3d_clear_stencil(pxl8_gfx* gfx, u8 value);
void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color);
void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material);
void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material, const pxl8_gfx_draw_opts* opts);
void pxl8_3d_end_frame(pxl8_gfx* gfx);
u8* pxl8_3d_get_framebuffer(pxl8_gfx* gfx);
const pxl8_frustum* pxl8_3d_get_frustum(pxl8_gfx* gfx);
const pxl8_mat4* pxl8_3d_get_view_proj(pxl8_gfx* gfx);
u32 pxl8_3d_project_points(pxl8_gfx* gfx, const pxl8_vec3* in, pxl8_vec3* out, u32 count, const pxl8_mat4* transform);
pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_shader_uniforms* uniforms);
#ifdef __cplusplus
}
#endif

View file

@ -83,7 +83,7 @@ static u8 palette_find_closest(const u32* palette, u8 r, u8 g, u8 b) {
return best_idx;
}
static u8 blend_indexed(
static u8 blend_colors(
const pxl8_gfx_pipeline_desc* pipeline,
u8 src,
u8 dst,
@ -279,6 +279,7 @@ static void rasterize_triangle(
const tri_setup* setup,
u8* fb,
u16* zb,
u8* sb,
u32 fb_width,
pxl8_shader_fn shader,
const pxl8_gfx_pipeline_desc* pipeline,
@ -289,6 +290,7 @@ static void rasterize_triangle(
if (setup->y_start > setup->y_end) return;
bool color_write = !pipeline || pipeline->color_write;
bool depth_test = pipeline && pipeline->depth.test;
bool depth_write = pipeline && pipeline->depth.write;
pxl8_gfx_compare_func depth_compare = pipeline ? pipeline->depth.compare : PXL8_GFX_COMPARE_ALWAYS;
@ -296,6 +298,10 @@ static void rasterize_triangle(
u8 alpha_ref = pipeline ? pipeline->blend.alpha_ref : 0;
bool blend_enabled = pipeline && pipeline->blend.enabled;
const u32* palette = bindings ? bindings->palette : NULL;
bool stencil_test = pipeline && pipeline->stencil.test && sb;
bool stencil_write = pipeline && pipeline->stencil.write && sb;
pxl8_gfx_compare_func stencil_compare = pipeline ? pipeline->stencil.compare : PXL8_GFX_COMPARE_ALWAYS;
u8 stencil_ref = pipeline ? pipeline->stencil.ref : 0;
for (i32 y = setup->y_start; y <= setup->y_end; y++) {
f32 yf = (f32)y + 0.5f;
@ -453,7 +459,7 @@ static void rasterize_triangle(
i32 px = x;
#if defined(PXL8_SIMD_SSE) || defined(PXL8_SIMD_NEON)
if (depth_test && depth_compare == PXL8_GFX_COMPARE_LESS && !blend_enabled) {
if (depth_test && depth_compare == PXL8_GFX_COMPARE_LESS && !blend_enabled && !stencil_test && !stencil_write) {
pxl8_f32_simd dz4_simd = pxl8_f32_simd_set(dz * 4.0f);
pxl8_f32_simd half = pxl8_f32_simd_set(0.5f);
pxl8_f32_simd one = pxl8_f32_simd_set(1.0f);
@ -504,7 +510,7 @@ static void rasterize_triangle(
if (!(mask & (0x8 << (i * 4)))) continue;
u8 color = colors[i];
if (!(alpha_test && color <= alpha_ref) && color != 0) {
prow[px + i] = color;
if (color_write) prow[px + i] = color;
if (depth_write) zrow[px + i] = (u16)z16_arr[i];
}
}
@ -516,11 +522,21 @@ static void rasterize_triangle(
}
}
for (; px <= span_end; px++) {
u32 pixel = row_start + (u32)px;
if (stencil_test && !depth_test_pass(stencil_compare, stencil_ref, sb[pixel])) {
u_a += du; v_a += dv; l_a += dl; c_a += dc;
z_a += dz; wx_a += dwx; wy_a += dwy; wz_a += dwz;
continue;
}
f32 depth_norm = pxl8_clamp((z_a + 1.0f) * 0.5f, 0.0f, 1.0f);
u16 z16 = (u16)(depth_norm * 65535.0f);
bool depth_pass = !depth_test || depth_test_pass(depth_compare, z16, zrow[px]);
if (depth_pass) {
if (stencil_write) sb[pixel] = stencil_ref;
pxl8_shader_ctx frag_ctx = {
.color_count = 1,
.x = pxl8_i32_simd_set(px),
@ -540,10 +556,10 @@ static void rasterize_triangle(
if (color != 0) {
u8 out_color = color;
if (blend_enabled) {
out_color = blend_indexed(pipeline, color, prow[px], palette);
out_color = blend_colors(pipeline, color, prow[px], palette);
}
prow[px] = out_color;
if (color_write) prow[px] = out_color;
if (depth_write) {
zrow[px] = z16;
}
@ -562,11 +578,21 @@ static void rasterize_triangle(
}
#else
for (; px <= span_end; px++) {
u32 pixel = row_start + (u32)px;
if (stencil_test && !depth_test_pass(stencil_compare, stencil_ref, sb[pixel])) {
u_a += du; v_a += dv; l_a += dl; c_a += dc;
z_a += dz; wx_a += dwx; wy_a += dwy; wz_a += dwz;
continue;
}
f32 depth_norm = pxl8_clamp((z_a + 1.0f) * 0.5f, 0.0f, 1.0f);
u16 z16 = (u16)(depth_norm * 65535.0f);
bool depth_pass = !depth_test || depth_test_pass(depth_compare, z16, zrow[px]);
if (depth_pass) {
if (stencil_write) sb[pixel] = stencil_ref;
pxl8_shader_ctx frag_ctx = {
.color_count = 1,
.x = px,
@ -586,10 +612,10 @@ static void rasterize_triangle(
if (color != 0) {
u8 out_color = color;
if (blend_enabled) {
out_color = blend_indexed(pipeline, color, prow[px], palette);
out_color = blend_colors(pipeline, color, prow[px], palette);
}
prow[px] = out_color;
if (color_write) prow[px] = out_color;
if (depth_write) {
zrow[px] = z16;
}
@ -739,6 +765,10 @@ static u32 pipeline_desc_hash(const pxl8_gfx_pipeline_desc* d) {
h = (h ^ (u32)d->dither) * 16777619u;
h = (h ^ (u32)d->double_sided) * 16777619u;
h = (h ^ (u32)d->emissive) * 16777619u;
h = (h ^ (u32)d->stencil.test) * 16777619u;
h = (h ^ (u32)d->stencil.write) * 16777619u;
h = (h ^ (u32)d->stencil.compare) * 16777619u;
h = (h ^ (u32)d->stencil.ref) * 16777619u;
h = (h ^ (u32)d->rasterizer.cull) * 16777619u;
h = (h ^ (u32)d->rasterizer.fill) * 16777619u;
u64 s = (u64)(uintptr_t)d->shader;
@ -767,6 +797,7 @@ typedef struct {
struct pxl8_renderer {
u32 width;
u32 height;
u8* stencil;
texture_slot textures[PXL8_GFX_MAX_TEXTURES];
buffer_slot buffers[PXL8_GFX_MAX_BUFFERS];
@ -801,6 +832,7 @@ pxl8_renderer* pxl8_renderer_create(u32 width, u32 height) {
pxl8_renderer* r = pxl8_calloc(1, sizeof(pxl8_renderer));
r->width = width;
r->height = height;
r->stencil = pxl8_calloc(width * height, 1);
r->viewport_w = width;
r->viewport_h = height;
r->scissor_w = width;
@ -816,21 +848,10 @@ void pxl8_renderer_destroy(pxl8_renderer* r) {
for (u32 i = 0; i < PXL8_GFX_MAX_BUFFERS; i++) {
if (r->buffers[i].data) pxl8_free(r->buffers[i].data);
}
pxl8_free(r->stencil);
pxl8_free(r);
}
u32 pxl8_renderer_get_width(const pxl8_renderer* r) {
return r ? r->width : 0;
}
u32 pxl8_renderer_get_height(const pxl8_renderer* r) {
return r ? r->height : 0;
}
void pxl8_renderer_set_shader(pxl8_renderer* r, pxl8_shader_fn fn) {
if (r) r->shader = fn;
}
void pxl8_renderer_update_stats(pxl8_renderer* r, f32 dt) {
if (!r) return;
@ -1070,16 +1091,6 @@ void pxl8_update_texture(pxl8_renderer* r, pxl8_gfx_texture tex, const pxl8_gfx_
}
}
void* pxl8_buffer_ptr(pxl8_renderer* r, pxl8_gfx_buffer buf) {
if (!VALID_BUF(r, buf)) return NULL;
return r->buffers[SLOT_INDEX(buf.id)].data;
}
u32 pxl8_buffer_size(pxl8_renderer* r, pxl8_gfx_buffer buf) {
if (!VALID_BUF(r, buf)) return 0;
return r->buffers[SLOT_INDEX(buf.id)].size;
}
void* pxl8_texture_get_data(pxl8_renderer* r, pxl8_gfx_texture tex) {
if (!VALID_TEX(r, tex)) return NULL;
return r->textures[SLOT_INDEX(tex.id)].data;
@ -1095,11 +1106,6 @@ u32 pxl8_texture_get_height(pxl8_renderer* r, pxl8_gfx_texture tex) {
return r->textures[SLOT_INDEX(tex.id)].height;
}
pxl8_gfx_texture_format pxl8_texture_get_format(pxl8_renderer* r, pxl8_gfx_texture tex) {
if (!VALID_TEX(r, tex)) return PXL8_GFX_FORMAT_INDEXED8;
return r->textures[SLOT_INDEX(tex.id)].format;
}
pxl8_gfx_cmdbuf* pxl8_cmdbuf_create(u32 capacity) {
pxl8_gfx_cmdbuf* cb = pxl8_malloc(sizeof(pxl8_gfx_cmdbuf));
cb->commands = pxl8_malloc(capacity * sizeof(pxl8_gfx_cmd));
@ -1402,7 +1408,7 @@ static void execute_draw(
}
u64 raster_start = pxl8_get_ticks_ns();
rasterize_triangle(&setup, fb, zb, fb_w, shader, &pip->desc,
rasterize_triangle(&setup, fb, zb, r->stencil, fb_w, shader, &pip->desc,
&shader_bindings, &shader_uniforms);
r->stats.raster_ns += pxl8_get_ticks_ns() - raster_start;
}
@ -1421,6 +1427,7 @@ void pxl8_gfx_submit(pxl8_renderer* r, pxl8_gfx_cmdbuf* cb) {
if (p->desc.color.load == PXL8_GFX_LOAD_CLEAR) {
pxl8_clear(r, p->desc.color.texture, p->desc.color.clear_value);
pxl8_clear_depth(r, p->desc.depth.texture);
if (r->stencil) memset(r->stencil, 0, r->width * r->height);
}
}
break;
@ -1484,6 +1491,12 @@ void pxl8_clear_depth(pxl8_renderer* r, pxl8_gfx_texture target) {
}
}
void pxl8_clear_stencil(pxl8_renderer* r, u8 value) {
if (!r || !r->stencil) return;
memset(r->stencil, value, r->width * r->height);
}
void pxl8_draw_pixel(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y, u8 color) {
if (!VALID_TEX(r, target)) return;
texture_slot* s = &r->textures[SLOT_INDEX(target.id)];

View file

@ -14,10 +14,6 @@ typedef struct pxl8_gfx_cmdbuf pxl8_gfx_cmdbuf;
pxl8_renderer* pxl8_renderer_create(u32 width, u32 height);
void pxl8_renderer_destroy(pxl8_renderer* r);
void pxl8_renderer_set_shader(pxl8_renderer* r, pxl8_shader_fn fn);
u32 pxl8_renderer_get_width(const pxl8_renderer* r);
u32 pxl8_renderer_get_height(const pxl8_renderer* r);
pxl8_gfx_bindings pxl8_create_bindings(pxl8_renderer* r, const pxl8_gfx_bindings_desc* desc);
pxl8_gfx_buffer pxl8_create_buffer(pxl8_renderer* r, const pxl8_gfx_buffer_desc* desc);
@ -35,13 +31,9 @@ void pxl8_update_buffer(pxl8_renderer* r, pxl8_gfx_buffer buf, const pxl8_gfx_ra
i32 pxl8_append_buffer(pxl8_renderer* r, pxl8_gfx_buffer buf, const pxl8_gfx_range* data);
void pxl8_update_texture(pxl8_renderer* r, pxl8_gfx_texture tex, const pxl8_gfx_range* data, u32 x, u32 y, u32 w, u32 h);
void* pxl8_buffer_ptr(pxl8_renderer* r, pxl8_gfx_buffer buf);
u32 pxl8_buffer_size(pxl8_renderer* r, pxl8_gfx_buffer buf);
void* pxl8_texture_get_data(pxl8_renderer* r, pxl8_gfx_texture tex);
u32 pxl8_texture_get_width(pxl8_renderer* r, pxl8_gfx_texture tex);
u32 pxl8_texture_get_height(pxl8_renderer* r, pxl8_gfx_texture tex);
pxl8_gfx_texture_format pxl8_texture_get_format(pxl8_renderer* r, pxl8_gfx_texture tex);
pxl8_gfx_cmdbuf* pxl8_cmdbuf_create(u32 capacity);
void pxl8_cmdbuf_destroy(pxl8_gfx_cmdbuf* cb);
@ -61,6 +53,7 @@ void pxl8_gfx_submit(pxl8_renderer* r, pxl8_gfx_cmdbuf* cb);
void pxl8_clear(pxl8_renderer* r, pxl8_gfx_texture target, u8 color);
void pxl8_clear_depth(pxl8_renderer* r, pxl8_gfx_texture target);
void pxl8_clear_stencil(pxl8_renderer* r, u8 value);
void pxl8_draw_pixel(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y, u8 color);
u8 pxl8_get_pixel(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y);

View file

@ -31,9 +31,6 @@ typedef struct pxl8_gfx_range {
u32 size;
} pxl8_gfx_range;
#define PXL8_GFX_RANGE(x) ((pxl8_gfx_range){ .ptr = &(x), .size = sizeof(x) })
#define PXL8_GFX_RANGE_REF(p, sz) ((pxl8_gfx_range){ .ptr = (p), .size = (sz) })
typedef enum pxl8_gfx_buffer_type {
PXL8_GFX_BUFFER_VERTEX,
PXL8_GFX_BUFFER_INDEX,
@ -87,11 +84,6 @@ typedef enum pxl8_gfx_load_op {
PXL8_GFX_LOAD_DONT_CARE,
} pxl8_gfx_load_op;
typedef enum pxl8_gfx_store_op {
PXL8_GFX_STORE_STORE,
PXL8_GFX_STORE_DONT_CARE,
} pxl8_gfx_store_op;
typedef struct pxl8_gfx_buffer_desc {
pxl8_gfx_range data;
u32 capacity;
@ -125,6 +117,14 @@ typedef struct pxl8_gfx_pipeline_desc {
pxl8_gfx_compare_func compare;
} depth;
struct {
bool test;
bool write;
pxl8_gfx_compare_func compare;
u8 ref;
} stencil;
bool color_write;
bool dither;
bool double_sided;
bool emissive;
@ -144,10 +144,19 @@ typedef struct pxl8_gfx_bindings_desc {
u32 texture_id;
} pxl8_gfx_bindings_desc;
typedef struct pxl8_gfx_draw_opts {
bool color_write;
pxl8_gfx_compare_func depth_compare;
bool depth_write;
bool stencil_test;
bool stencil_write;
u8 stencil_compare;
u8 stencil_ref;
} pxl8_gfx_draw_opts;
typedef struct pxl8_gfx_pass_color_attachment {
pxl8_gfx_texture texture;
pxl8_gfx_load_op load;
pxl8_gfx_store_op store;
u8 clear_value;
} pxl8_gfx_pass_color_attachment;

View file

@ -17,7 +17,6 @@ struct pxl8_tilesheet {
u32 tiles_per_row;
u32 total_tiles;
u32 width;
pxl8_pixel_mode pixel_mode;
u32 ref_count;
pxl8_tile_animation* animations;
u32 animation_count;
@ -547,7 +546,6 @@ pxl8_result pxl8_tilemap_load_ase(pxl8_tilemap* tilemap, const char* filepath, u
tilemap->tilesheet->height = tilesheet_height;
tilemap->tilesheet->tiles_per_row = tiles_per_row;
tilemap->tilesheet->total_tiles = tileset->tile_count;
tilemap->tilesheet->pixel_mode = PXL8_PIXEL_INDEXED;
if (tilemap->tilesheet->tile_valid) pxl8_free(tilemap->tilesheet->tile_valid);
tilemap->tilesheet->tile_valid = pxl8_calloc(tileset->tile_count + 1, sizeof(bool));

View file

@ -4,7 +4,6 @@
#include <string.h>
#include "pxl8_ase.h"
#include "pxl8_color.h"
#include "pxl8_gfx.h"
#include "pxl8_log.h"
#include "pxl8_mem.h"
@ -20,7 +19,6 @@ struct pxl8_tilesheet {
u32 total_tiles;
u32 width;
pxl8_pixel_mode pixel_mode;
u32 ref_count;
pxl8_tile_animation* animations;
@ -106,14 +104,10 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath,
tilesheet->height = height;
tilesheet->tiles_per_row = width / tilesheet->tile_size;
tilesheet->total_tiles = (width / tilesheet->tile_size) * (height / tilesheet->tile_size);
tilesheet->pixel_mode = pxl8_gfx_get_pixel_mode(gfx);
u32 pixel_count = width * height;
u16 ase_depth = ase_file.header.color_depth;
bool gfx_hicolor = (tilesheet->pixel_mode == PXL8_PIXEL_HICOLOR);
usize data_size = pixel_count * pxl8_bytes_per_pixel(tilesheet->pixel_mode);
tilesheet->data = pxl8_malloc(data_size);
tilesheet->data = pxl8_malloc(pixel_count);
if (!tilesheet->data) {
pxl8_ase_destroy(&ase_file);
return PXL8_ERROR_OUT_OF_MEMORY;
@ -122,34 +116,10 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath,
if (ase_file.frame_count > 0 && ase_file.frames[0].pixels) {
const u8* src = ase_file.frames[0].pixels;
if (ase_depth == 8 && !gfx_hicolor) {
memcpy(tilesheet->data, src, pixel_count);
} else if (ase_depth == 32 && gfx_hicolor) {
u16* dst = (u16*)tilesheet->data;
const u32* rgba = (const u32*)src;
for (u32 i = 0; i < pixel_count; i++) {
u32 c = rgba[i];
u8 a = (c >> 24) & 0xFF;
if (a == 0) {
dst[i] = 0;
} else {
dst[i] = pxl8_rgba32_to_rgb565(c);
}
}
} else if (ase_depth == 8 && gfx_hicolor) {
pxl8_warn("Indexed ASE with hicolor gfx - storing as indexed");
tilesheet->pixel_mode = PXL8_PIXEL_INDEXED;
u8* new_data = pxl8_realloc(tilesheet->data, pixel_count);
if (!new_data) {
pxl8_free(tilesheet->data);
tilesheet->data = NULL;
pxl8_ase_destroy(&ase_file);
return PXL8_ERROR_OUT_OF_MEMORY;
}
tilesheet->data = new_data;
if (ase_depth == 8) {
memcpy(tilesheet->data, src, pixel_count);
} else {
pxl8_error("Unsupported ASE color depth %d for gfx mode", ase_depth);
pxl8_error("Unsupported ASE color depth %d (expected indexed 8-bit)", ase_depth);
pxl8_free(tilesheet->data);
tilesheet->data = NULL;
pxl8_ase_destroy(&ase_file);
@ -166,7 +136,6 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath,
}
u32 valid_tiles = 0;
bool is_hicolor = (tilesheet->pixel_mode == PXL8_PIXEL_HICOLOR);
for (u32 tile_id = 1; tile_id <= tilesheet->total_tiles; tile_id++) {
u32 tile_x = ((tile_id - 1) % tilesheet->tiles_per_row) * tilesheet->tile_size;
@ -176,19 +145,12 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath,
for (u32 py = 0; py < tilesheet->tile_size && !has_content; py++) {
for (u32 px = 0; px < tilesheet->tile_size; px++) {
u32 idx = (tile_y + py) * width + (tile_x + px);
if (is_hicolor) {
if (((u16*)tilesheet->data)[idx] != 0) {
has_content = true;
break;
}
} else {
if (tilesheet->data[idx] != 0) {
has_content = true;
break;
}
}
}
}
if (has_content) {
tilesheet->tile_valid[tile_id] = true;
valid_tiles++;
@ -312,7 +274,6 @@ pxl8_result pxl8_tilesheet_set_tile_pixels(pxl8_tilesheet* tilesheet, u16 tile_i
u32 tile_x = (tile_id - 1) % tilesheet->tiles_per_row;
u32 tile_y = (tile_id - 1) / tilesheet->tiles_per_row;
u32 bytes_per_pixel = pxl8_bytes_per_pixel(tilesheet->pixel_mode);
for (u32 py = 0; py < tilesheet->tile_size; py++) {
for (u32 px = 0; px < tilesheet->tile_size; px++) {
@ -320,14 +281,9 @@ pxl8_result pxl8_tilesheet_set_tile_pixels(pxl8_tilesheet* tilesheet, u16 tile_i
u32 dst_x = tile_x * tilesheet->tile_size + px;
u32 dst_y = tile_y * tilesheet->tile_size + py;
u32 dst_idx = dst_y * tilesheet->width + dst_x;
if (bytes_per_pixel == 2) {
((u16*)tilesheet->data)[dst_idx] = ((const u16*)pixels)[src_idx];
} else {
tilesheet->data[dst_idx] = pixels[src_idx];
}
}
}
if (tilesheet->tile_valid) {
tilesheet->tile_valid[tile_id] = true;

View file

@ -170,11 +170,7 @@ void pxl8_transition_render(const pxl8_transition* transition, pxl8_gfx* gfx) {
i32 block_size = (i32)(max_block_size * progress);
if (block_size < 1) block_size = 1;
pxl8_pixel_mode mode = pxl8_gfx_get_pixel_mode(gfx);
bool has_fb = (mode == PXL8_PIXEL_HICOLOR)
? (pxl8_gfx_get_framebuffer_hicolor(gfx) != NULL)
: (pxl8_gfx_get_framebuffer_indexed(gfx) != NULL);
if (!has_fb) break;
if (!pxl8_gfx_framebuffer(gfx)) break;
for (i32 y = 0; y < height; y += block_size) {
for (i32 x = 0; x < width; x += block_size) {

View file

@ -173,6 +173,22 @@ bool pxl8_gui_slider_int(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i3
return changed;
}
i32 pxl8_gui_toolbar(pxl8_gui_state* state, pxl8_gfx* gfx, u32 base_id,
i32 x, i32 y, i32 btn_w, i32 btn_h,
const char** labels, i32 count, i32 selected) {
i32 result = -1;
for (i32 i = 0; i < count; i++) {
i32 bx = x + i * (btn_w + 2);
bool clicked = pxl8_gui_button(state, gfx, base_id + (u32)i, bx, y, btn_w, btn_h, labels[i]);
if (clicked) result = i;
if (i == selected) {
u8 sel_color = pxl8_gui_color(gfx, PXL8_UI_FG0);
pxl8_2d_rect(gfx, bx, y + btn_h - 2, btn_w, 2, sel_color);
}
}
return result;
}
void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title) {
if (!gfx || !title) return;
@ -206,3 +222,63 @@ void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y) {
if (x) *x = state->cursor_x;
if (y) *y = state->cursor_y;
}
i32 pxl8_gui_grid_select(pxl8_gui_state* state, pxl8_gfx* gfx, u32 base_id,
i32 x, i32 y, i32 cell_w, i32 cell_h,
i32 cols, i32 rows, const u8* colors, i32 selected) {
i32 result = -1;
for (i32 r = 0; r < rows; r++) {
for (i32 c = 0; c < cols; c++) {
i32 cx = x + c * (cell_w + 1);
i32 cy = y + r * (cell_h + 1);
i32 idx = r * cols + c;
u32 id = base_id + (u32)idx;
bool over = is_cursor_over(state, cx, cy, cell_w, cell_h);
if (over) state->hot_id = id;
if (over && state->cursor_down && state->active_id == 0)
state->active_id = id;
bool clicked = (state->active_id == id) && state->cursor_clicked && over;
if (clicked) { result = idx; state->active_id = 0; }
pxl8_2d_rect_fill(gfx, cx, cy, cell_w, cell_h, colors[idx]);
if (idx == selected) {
u8 sel = pxl8_gui_color(gfx, PXL8_UI_FG0);
pxl8_2d_rect(gfx, cx - 1, cy - 1, cell_w + 2, cell_h + 2, sel);
} else if (over) {
u8 hov = pxl8_gui_color(gfx, PXL8_UI_BG3);
pxl8_2d_rect(gfx, cx, cy, cell_w, cell_h, hov);
}
}
}
return result;
}
void pxl8_gui_image(pxl8_gfx* gfx, u32 texture_id, i32 sx, i32 sy, i32 sw, i32 sh,
i32 dx, i32 dy, i32 dw, i32 dh) {
pxl8_2d_sprite(gfx, texture_id, dx, dy, dw, dh, false, false);
(void)sx; (void)sy; (void)sw; (void)sh;
}
void pxl8_gui_panel(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h) {
u8 bg = pxl8_gui_color(gfx, PXL8_UI_BG1);
u8 border = pxl8_gui_color(gfx, PXL8_UI_BG3);
pxl8_2d_rect_fill(gfx, x, y, w, h, bg);
pxl8_2d_rect(gfx, x, y, w, h, border);
}
void pxl8_gui_status_bar(pxl8_gfx* gfx, i32 y, i32 screen_w, i32 h, const char* text) {
u8 bg = pxl8_gui_color(gfx, PXL8_UI_BG1);
u8 fg = pxl8_gui_color(gfx, PXL8_UI_FG0);
pxl8_2d_rect_fill(gfx, 0, y, screen_w, h, bg);
pxl8_2d_text(gfx, text, 4, y + (h / 2) - 5, fg);
}
bool pxl8_gui_toggle(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id,
i32 x, i32 y, i32 w, i32 h, const char* label, bool active) {
bool clicked = pxl8_gui_button(state, gfx, id, x, y, w, h, label);
if (active) {
u8 sel = pxl8_gui_color(gfx, PXL8_UI_FG0);
pxl8_2d_rect(gfx, x, y, w, 2, sel);
pxl8_2d_rect(gfx, x, y + h - 2, w, 2, sel);
}
return clicked ? !active : active;
}

View file

@ -34,8 +34,25 @@ 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);
i32 pxl8_gui_toolbar(pxl8_gui_state* state, pxl8_gfx* gfx, u32 base_id,
i32 x, i32 y, i32 btn_w, i32 btn_h,
const char** labels, i32 count, i32 selected);
void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title);
i32 pxl8_gui_grid_select(pxl8_gui_state* state, pxl8_gfx* gfx, u32 base_id,
i32 x, i32 y, i32 cell_w, i32 cell_h,
i32 cols, i32 rows, const u8* colors, i32 selected);
void pxl8_gui_image(pxl8_gfx* gfx, u32 texture_id, i32 sx, i32 sy, i32 sw, i32 sh,
i32 dx, i32 dy, i32 dw, i32 dh);
void pxl8_gui_panel(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h);
void pxl8_gui_status_bar(pxl8_gfx* gfx, i32 y, i32 screen_w, i32 h, const char* text);
bool pxl8_gui_toggle(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id,
i32 x, i32 y, i32 w, i32 h, const char* label, bool active);
#ifdef __cplusplus
}
#endif

View file

@ -125,17 +125,10 @@ static void sdl3_upload_texture(void* platform_data, const void* pixels, u32 w,
ctx->rgba_buffer_size = pixel_count;
}
if (bpp == 2) {
const u16* pixels16 = (const u16*)pixels;
for (u32 i = 0; i < pixel_count; i++) {
ctx->rgba_buffer[i] = pxl8_rgb565_to_rgba32(pixels16[i]);
}
} else {
const u8* pixels8 = (const u8*)pixels;
for (u32 i = 0; i < pixel_count; i++) {
ctx->rgba_buffer[i] = palette[pixels8[i]];
}
}
SDL_UpdateTexture(ctx->framebuffer, NULL, ctx->rgba_buffer, w * 4);
}

125
src/hal/pxl8_io_sdl3.c Normal file
View file

@ -0,0 +1,125 @@
#include "pxl8_io.h"
#include "pxl8_mem.h"
#include <string.h>
#ifndef _WIN32
#include <sys/stat.h>
#include <unistd.h>
#else
#include <direct.h>
#endif
#include <SDL3/SDL.h>
pxl8_result pxl8_io_create_directory(const char* path) {
if (!path) return PXL8_ERROR_NULL_POINTER;
return SDL_CreateDirectory(path) ? PXL8_OK : PXL8_ERROR_SYSTEM_FAILURE;
}
typedef struct {
pxl8_io_dir_callback callback;
void* userdata;
} pxl8_io_enum_ctx;
static SDL_EnumerationResult pxl8_io_enum_adapter(void* userdata, const char* dirname, const char* fname) {
pxl8_io_enum_ctx* ctx = userdata;
if (ctx->callback(ctx->userdata, dirname, fname)) {
return SDL_ENUM_CONTINUE;
}
return SDL_ENUM_SUCCESS;
}
bool pxl8_io_enumerate_directory(const char* path, pxl8_io_dir_callback callback, void* userdata) {
if (!path || !callback) return false;
pxl8_io_enum_ctx ctx = { .callback = callback, .userdata = userdata };
return SDL_EnumerateDirectory(path, pxl8_io_enum_adapter, &ctx);
}
bool pxl8_io_file_exists(const char* path) {
if (!path) return false;
return SDL_GetPathInfo(path, NULL);
}
char* pxl8_io_get_cwd(void) {
char* sdl_cwd = SDL_GetCurrentDirectory();
if (!sdl_cwd) return NULL;
char* cwd = pxl8_malloc(strlen(sdl_cwd) + 1);
if (cwd) strcpy(cwd, sdl_cwd);
SDL_free(sdl_cwd);
return cwd;
}
f64 pxl8_io_get_file_modified_time(const char* path) {
if (!path) return 0.0;
SDL_PathInfo info;
if (SDL_GetPathInfo(path, &info)) {
return (f64)info.modify_time / 1000000000.0;
}
return 0.0;
}
usize pxl8_io_get_file_size(const char* path) {
if (!path) return 0;
SDL_PathInfo info;
if (SDL_GetPathInfo(path, &info)) return (usize)info.size;
return 0;
}
char* pxl8_io_get_pref_path(const char* org, const char* app) {
char* sdl_path = SDL_GetPrefPath(org, app);
if (!sdl_path) return NULL;
char* path = pxl8_malloc(strlen(sdl_path) + 1);
if (path) strcpy(path, sdl_path);
SDL_free(sdl_path);
return path;
}
char* pxl8_io_get_real_path(const char* path) {
if (!path) return NULL;
if (path[0] == '/') {
char* result = pxl8_malloc(strlen(path) + 1);
if (result) strcpy(result, path);
return result;
}
char* cwd = SDL_GetCurrentDirectory();
if (!cwd) return NULL;
usize cwd_len = strlen(cwd);
usize path_len = strlen(path);
char* result = pxl8_malloc(cwd_len + path_len + 2);
if (result) {
strcpy(result, cwd);
if (cwd_len > 0 && cwd[cwd_len - 1] != '/') {
strcat(result, "/");
}
strcat(result, path);
}
SDL_free(cwd);
return result;
}
bool pxl8_io_set_cwd(const char* path) {
if (!path) return false;
#ifdef _WIN32
return _chdir(path) == 0;
#else
return chdir(path) == 0;
#endif
}
void pxl8_io_set_executable(const char* path) {
if (!path) return;
#ifndef _WIN32
chmod(path, 0755);
#endif
}
bool pxl8_io_is_directory(const char* path) {
if (!path) return false;
SDL_PathInfo info;
return SDL_GetPathInfo(path, &info) && info.type == SDL_PATHTYPE_DIRECTORY;
}

View file

@ -94,6 +94,7 @@ pxl8.Mesh = gfx.Mesh
pxl8.begin_frame_3d = gfx.begin_frame_3d
pxl8.clear_3d = gfx.clear_3d
pxl8.clear_depth = gfx.clear_depth
pxl8.clear_stencil = gfx.clear_stencil
pxl8.create_camera_3d = gfx.Camera3D.new
pxl8.create_material = gfx.create_material
pxl8.create_mesh = gfx.Mesh.new
@ -174,8 +175,6 @@ pxl8.create_particles = particles.Particles.new
pxl8.Graph = procgen.Graph
pxl8.create_graph = procgen.create_graph
pxl8.PROCGEN_ROOMS = world.PROCGEN_ROOMS
pxl8.PROCGEN_TERRAIN = world.PROCGEN_TERRAIN
pxl8.SfxContext = sfx.SfxContext
pxl8.SfxNode = sfx.SfxNode
@ -234,9 +233,26 @@ 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.DEPTH_NEVER = 0
pxl8.DEPTH_LESS = 1
pxl8.DEPTH_EQUAL = 2
pxl8.DEPTH_LEQUAL = 3
pxl8.DEPTH_GREATER = 4
pxl8.DEPTH_NOTEQUAL = 5
pxl8.DEPTH_GEQUAL = 6
pxl8.DEPTH_ALWAYS = 7
pxl8.STENCIL_NEVER = 0
pxl8.STENCIL_LESS = 1
pxl8.STENCIL_EQUAL = 2
pxl8.STENCIL_LEQUAL = 3
pxl8.STENCIL_GREATER = 4
pxl8.STENCIL_NOTEQUAL = 5
pxl8.STENCIL_GEQUAL = 6
pxl8.STENCIL_ALWAYS = 7
pxl8.Bsp = world.Bsp
pxl8.Chunk = world.Chunk
pxl8.CHUNK_BSP = world.CHUNK_BSP
pxl8.World = world.World
pxl8.get_world = world.World.get
pxl8.sim_config = world.sim_config

View file

@ -230,6 +230,28 @@ gfx.Camera3D = Camera3D
gfx.Mesh = Mesh
local Material = {}
Material.__index = Material
function Material.new(opts)
opts = opts or {}
local mat = ffi.new("pxl8_gfx_material", {
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,
emissive = opts.emissive or false,
per_pixel = opts.per_pixel or false,
texture_id = opts.texture or 0xFFFFFFFF,
})
return setmetatable({ _ptr = mat }, Material)
end
gfx.Material = Material
gfx.create_material = Material.new
function gfx.draw_mesh(mesh, opts)
if not mesh or not mesh._ptr then
return
@ -249,17 +271,21 @@ function gfx.draw_mesh(mesh, opts)
if opts.y then model.m[13] = opts.y end
if opts.z then model.m[14] = opts.z end
end
local material = ffi.new("pxl8_gfx_material", {
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,
emissive = opts.emissive or false,
per_pixel = opts.per_pixel or false,
texture_id = opts.texture or 0xFFFFFFFF,
local material = Material.new(opts)._ptr
local draw_opts = nil
if opts.color_write ~= nil or opts.depth_compare or opts.depth_write ~= nil
or opts.stencil_test or opts.stencil_write then
draw_opts = ffi.new("pxl8_gfx_draw_opts", {
color_write = opts.color_write ~= false,
depth_compare = opts.depth_compare or 1,
depth_write = opts.depth_write ~= false,
stencil_test = opts.stencil_test or false,
stencil_write = opts.stencil_write or false,
stencil_compare = opts.stencil_compare or 0,
stencil_ref = opts.stencil_ref or 0,
})
C.pxl8_3d_draw_mesh(core.gfx, mesh._ptr, model, material)
end
C.pxl8_3d_draw_mesh(core.gfx, mesh._ptr, model, material, draw_opts)
end
function gfx.begin_frame_3d(camera, lights, uniforms)
@ -301,6 +327,10 @@ function gfx.clear_depth()
C.pxl8_3d_clear_depth(core.gfx)
end
function gfx.clear_stencil(value)
C.pxl8_3d_clear_stencil(core.gfx, value or 0)
end
function gfx.draw_line_3d(p0, p1, color)
local vec0 = ffi.new("pxl8_vec3", {x = p0[1], y = p0[2], z = p0[3]})
local vec1 = ffi.new("pxl8_vec3", {x = p1[1], y = p1[2], z = p1[3]})
@ -327,26 +357,4 @@ function gfx.get_wireframe()
return C.pxl8_gfx_get_wireframe(core.gfx)
end
local Material = {}
Material.__index = Material
function Material.new(opts)
opts = opts or {}
local mat = ffi.new("pxl8_gfx_material", {
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,
emissive = opts.emissive or false,
per_pixel = opts.per_pixel or false,
texture_id = opts.texture or 0xFFFFFFFF,
})
return setmetatable({ _ptr = mat }, Material)
end
gfx.Material = Material
gfx.create_material = Material.new
return gfx

View file

@ -15,18 +15,22 @@ function net.get()
return setmetatable({ _ptr = ptr }, Net)
end
function Net:chunk_id()
return C.pxl8_net_chunk_id(self._ptr)
function Net:chunk_cx()
return C.pxl8_net_chunk_cx(self._ptr)
end
function Net:chunk_cz()
return C.pxl8_net_chunk_cz(self._ptr)
end
function Net:has_chunk()
return C.pxl8_net_has_chunk(self._ptr)
end
function Net:connected()
return C.pxl8_net_connected(self._ptr)
end
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)
local msg = ffi.new("pxl8_input_msg")
msg.buttons = input.buttons or 0

View file

@ -64,6 +64,10 @@ function World:init_local_player(x, y, z)
C.pxl8_world_init_local_player(self._ptr, x, y, z)
end
function World:set_look(yaw, pitch)
C.pxl8_world_set_look(self._ptr, yaw, pitch or 0)
end
function World:local_player()
local ptr = C.pxl8_world_local_player(self._ptr)
if ptr == nil then return nil end

View file

@ -74,6 +74,10 @@ typedef struct pxl8_plane {
pxl8_vec3 normal;
} pxl8_plane;
typedef struct pxl8_rect {
f32 x0, y0, x1, y1;
} pxl8_rect;
typedef struct pxl8_frustum {
pxl8_plane planes[6];
} pxl8_frustum;

View file

@ -46,17 +46,15 @@ struct pxl8_net {
socket_t sock;
pxl8_world_chunk_cache* chunk_cache;
u32 chunk_id;
u8 chunk_type;
i32 chunk_cx;
i32 chunk_cz;
bool has_chunk;
pxl8_world* world;
f32 dt;
u64 highest_tick;
f32 interp_time;
u32 sequence;
pxl8_entity_state entities[PXL8_MAX_SNAPSHOT_ENTITIES];
pxl8_event_msg events[PXL8_MAX_SNAPSHOT_EVENTS];
pxl8_entity_state prev_entities[PXL8_MAX_SNAPSHOT_ENTITIES];
pxl8_snapshot_header prev_snapshot;
pxl8_snapshot_header snapshot;
@ -109,6 +107,9 @@ pxl8_result pxl8_net_connect(pxl8_net* net) {
fcntl(net->sock, F_SETFL, flags | O_NONBLOCK);
#endif
int rcvbuf = 1024 * 1024;
setsockopt(net->sock, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));
memset(&net->server_addr, 0, sizeof(net->server_addr));
#ifdef __APPLE__
net->server_addr.sin_len = sizeof(net->server_addr);
@ -131,9 +132,6 @@ pxl8_net* pxl8_net_create(const pxl8_net_config* config) {
net->port = config->port ? config->port : PXL8_NET_DEFAULT_PORT;
net->sock = INVALID_SOCK;
net->connected = false;
net->sequence = 0;
net->highest_tick = 0;
if (config->address) {
strncpy(net->address, config->address, sizeof(net->address) - 1);
@ -179,11 +177,6 @@ const u8* pxl8_net_entity_userdata(const pxl8_net* net, u64 entity_id) {
return e ? e->userdata : NULL;
}
const pxl8_event_msg* pxl8_net_events(const pxl8_net* net) {
if (!net) return NULL;
return net->events;
}
const pxl8_input_msg* pxl8_net_input_at(const pxl8_net* net, u64 tick) {
if (!net) return NULL;
for (u64 i = 0; i < PXL8_NET_INPUT_HISTORY_SIZE; i++) {
@ -231,22 +224,19 @@ u64 pxl8_net_player_id(const pxl8_net* net) {
return net->snapshot.player_id;
}
bool pxl8_net_poll(pxl8_net* net) {
if (!net || !net->connected) return false;
usize len = pxl8_net_recv(net, net->recv_buf, sizeof(net->recv_buf));
static bool dispatch_message(pxl8_net* net, const u8* data, usize len) {
if (len < sizeof(pxl8_msg_header)) return false;
pxl8_msg_header hdr;
usize offset = pxl8_protocol_deserialize_header(net->recv_buf, len, &hdr);
usize offset = pxl8_protocol_deserialize_header(data, len, &hdr);
if (hdr.type == PXL8_MSG_CHUNK) {
if (!net->chunk_cache) return false;
pxl8_chunk_msg_header chunk_hdr;
offset += pxl8_protocol_deserialize_chunk_msg_header(net->recv_buf + offset, len - offset, &chunk_hdr);
offset += pxl8_protocol_deserialize_chunk_msg_header(data + offset, len - offset, &chunk_hdr);
const u8* payload = net->recv_buf + offset;
const u8* payload = data + offset;
usize payload_len = chunk_hdr.payload_size;
if (payload_len > len - offset) {
payload_len = len - offset;
@ -258,23 +248,23 @@ bool pxl8_net_poll(pxl8_net* net) {
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);
pxl8_protocol_deserialize_chunk_enter(data + offset, len - offset, &chunk_msg);
net->chunk_cx = chunk_msg.cx;
net->chunk_cz = chunk_msg.cz;
net->has_chunk = true;
pxl8_debug("[CLIENT] Received CHUNK_ENTER cx=%d cz=%d", chunk_msg.cx, chunk_msg.cz);
return true;
}
if (hdr.type == PXL8_MSG_CHUNK_EXIT) {
net->chunk_id = 0;
net->chunk_type = 0;
net->has_chunk = false;
return true;
}
if (hdr.type != PXL8_MSG_SNAPSHOT) return false;
pxl8_snapshot_header snap;
offset += pxl8_protocol_deserialize_snapshot_header(net->recv_buf + offset, len - offset, &snap);
offset += pxl8_protocol_deserialize_snapshot_header(data + offset, len - offset, &snap);
if (snap.tick <= net->highest_tick) return false;
@ -290,10 +280,20 @@ bool pxl8_net_poll(pxl8_net* net) {
for (u16 i = 0; i < count; i++) {
offset += pxl8_protocol_deserialize_entity_state(
net->recv_buf + offset, len - offset, &net->entities[i]);
data + offset, len - offset, &net->entities[i]);
}
if (net->world) {
return true;
}
bool pxl8_net_poll(pxl8_net* net) {
if (!net || !net->connected) return false;
usize len = pxl8_net_recv(net, net->recv_buf, sizeof(net->recv_buf));
u64 prev_tick = net->highest_tick;
if (!dispatch_message(net, net->recv_buf, len)) return false;
if (net->highest_tick > prev_tick && net->world) {
pxl8_world_reconcile(net->world, net, 1.0f / 30.0f);
}
@ -377,7 +377,6 @@ u64 pxl8_net_tick(const pxl8_net* net) {
void pxl8_net_update(pxl8_net* net, f32 dt) {
if (!net) return;
net->dt = dt;
net->interp_time += dt;
}
@ -396,14 +395,19 @@ void pxl8_net_set_world(pxl8_net* net, pxl8_world* world) {
net->world = world;
}
u32 pxl8_net_chunk_id(const pxl8_net* net) {
i32 pxl8_net_chunk_cx(const pxl8_net* net) {
if (!net) return 0;
return net->chunk_id;
return net->chunk_cx;
}
u8 pxl8_net_chunk_type(const pxl8_net* net) {
if (!net) return PXL8_CHUNK_TYPE_BSP;
return net->chunk_type;
i32 pxl8_net_chunk_cz(const pxl8_net* net) {
if (!net) return 0;
return net->chunk_cz;
}
bool pxl8_net_has_chunk(const pxl8_net* net) {
if (!net) return false;
return net->has_chunk;
}
pxl8_result pxl8_net_spawn(pxl8_net* net, f32 x, f32 y, f32 z, f32 yaw, f32 pitch) {
@ -422,22 +426,6 @@ 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_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_ENTER_SCENE;
pxl8_pack_u32_be(cmd.payload, 0, chunk_id);
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);
}
#ifdef PXL8_ASYNC_THREADS
static int pxl8_net_recv_thread(void* data) {
@ -504,64 +492,8 @@ void pxl8_net_packet_free(pxl8_packet* pkt) {
}
bool pxl8_net_process_packet(pxl8_net* net, const pxl8_packet* pkt) {
if (!net || !pkt || pkt->len < sizeof(pxl8_msg_header)) return false;
pxl8_msg_header hdr;
usize offset = pxl8_protocol_deserialize_header(pkt->data, pkt->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(pkt->data + offset, pkt->len - offset, &chunk_hdr);
const u8* payload = pkt->data + offset;
usize payload_len = chunk_hdr.payload_size;
if (payload_len > pkt->len - offset) {
payload_len = pkt->len - offset;
}
pxl8_world_chunk_cache_receive(net->chunk_cache, &chunk_hdr, payload, payload_len);
return true;
}
if (hdr.type == PXL8_MSG_CHUNK_ENTER) {
pxl8_chunk_enter_msg chunk_msg;
pxl8_protocol_deserialize_chunk_enter(pkt->data + offset, pkt->len - offset, &chunk_msg);
net->chunk_id = chunk_msg.chunk_id;
net->chunk_type = chunk_msg.chunk_type;
return true;
}
if (hdr.type == PXL8_MSG_CHUNK_EXIT) {
net->chunk_id = 0;
net->chunk_type = 0;
return true;
}
if (hdr.type != PXL8_MSG_SNAPSHOT) return false;
pxl8_snapshot_header snap;
offset += pxl8_protocol_deserialize_snapshot_header(pkt->data + offset, pkt->len - offset, &snap);
if (snap.tick <= net->highest_tick) return false;
memcpy(net->prev_entities, net->entities, sizeof(net->entities));
net->prev_snapshot = net->snapshot;
net->highest_tick = snap.tick;
net->snapshot = snap;
net->interp_time = 0.0f;
u16 count = snap.entity_count;
if (count > PXL8_MAX_SNAPSHOT_ENTITIES) count = PXL8_MAX_SNAPSHOT_ENTITIES;
for (u16 i = 0; i < count; i++) {
offset += pxl8_protocol_deserialize_entity_state(
pkt->data + offset, pkt->len - offset, &net->entities[i]);
}
return true;
if (!net || !pkt) return false;
return dispatch_message(net, pkt->data, pkt->len);
}
#endif

View file

@ -33,7 +33,6 @@ void pxl8_net_disconnect(pxl8_net* net);
const pxl8_entity_state* pxl8_net_entities(const pxl8_net* net);
const u8* pxl8_net_entity_prev_userdata(const pxl8_net* net, u64 entity_id);
const u8* pxl8_net_entity_userdata(const pxl8_net* net, u64 entity_id);
const pxl8_event_msg* pxl8_net_events(const pxl8_net* net);
const pxl8_input_msg* pxl8_net_input_at(const pxl8_net* net, u64 tick);
u64 pxl8_net_input_oldest_tick(const pxl8_net* net);
void pxl8_net_input_push(pxl8_net* net, const pxl8_input_msg* input);
@ -54,11 +53,11 @@ void pxl8_net_set_chunk_cache(pxl8_net* net, pxl8_world_chunk_cache* cache);
pxl8_world_chunk_cache* pxl8_net_chunk_cache(pxl8_net* net);
void pxl8_net_set_world(pxl8_net* net, pxl8_world* world);
u32 pxl8_net_chunk_id(const pxl8_net* net);
u8 pxl8_net_chunk_type(const pxl8_net* net);
i32 pxl8_net_chunk_cx(const pxl8_net* net);
i32 pxl8_net_chunk_cz(const pxl8_net* net);
bool pxl8_net_has_chunk(const pxl8_net* net);
pxl8_result pxl8_net_spawn(pxl8_net* net, f32 x, f32 y, f32 z, f32 yaw, f32 pitch);
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

@ -158,7 +158,7 @@ usize pxl8_protocol_deserialize_chunk_msg_header(const u8* buf, usize len, pxl8_
}
usize pxl8_protocol_deserialize_bsp_wire_header(const u8* buf, usize len, pxl8_bsp_wire_header* hdr) {
if (len < 44) return 0;
if (len < 48) return 0;
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
hdr->num_vertices = pxl8_read_u32_be(&s);
hdr->num_edges = pxl8_read_u32_be(&s);
@ -171,27 +171,27 @@ usize pxl8_protocol_deserialize_bsp_wire_header(const u8* buf, usize len, pxl8_b
hdr->num_cell_portals = pxl8_read_u32_be(&s);
hdr->visdata_size = pxl8_read_u32_be(&s);
hdr->num_vertex_lights = pxl8_read_u32_be(&s);
hdr->num_heightfield = pxl8_read_u32_be(&s);
return s.offset;
}
usize pxl8_protocol_serialize_chunk_enter(const pxl8_chunk_enter_msg* msg, u8* buf, usize len) {
if (len < 8) return 0;
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
pxl8_write_u32_be(&s, 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]);
pxl8_write_u32_be(&s, (u32)msg->cx);
pxl8_write_u32_be(&s, (u32)msg->cz);
return s.offset;
}
usize pxl8_protocol_deserialize_chunk_enter(const u8* buf, usize len, pxl8_chunk_enter_msg* msg) {
if (len < 8) return 0;
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
msg->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);
msg->cx = (i32)pxl8_read_u32_be(&s);
msg->cz = (i32)pxl8_read_u32_be(&s);
return s.offset;
}
u32 pxl8_chunk_hash(i32 cx, i32 cz) {
u32 h = (u32)cx * 374761393u + (u32)cz * 668265263u;
return h ^ (h >> 16);
}

View file

@ -35,7 +35,6 @@ typedef struct pxl8_msg_header {
typedef enum pxl8_cmd_type {
PXL8_CMD_NONE = 0,
PXL8_CMD_SPAWN_ENTITY,
PXL8_CMD_ENTER_SCENE,
} pxl8_cmd_type;
typedef struct pxl8_input_msg {
@ -76,7 +75,6 @@ typedef struct pxl8_snapshot_header {
#define PXL8_CHUNK_TYPE_BSP 1
#define PXL8_CHUNK_FLAG_RLE 0x01
#define PXL8_CHUNK_FLAG_FINAL 0x04
#define PXL8_CHUNK_MAX_PAYLOAD 1400
@ -93,17 +91,18 @@ typedef struct pxl8_chunk_msg_header {
} pxl8_chunk_msg_header;
typedef struct pxl8_bsp_wire_header {
u32 num_cell_portals;
u32 num_vertices;
u32 num_edges;
u32 num_faces;
u32 num_leafs;
u32 num_marksurfaces;
u32 num_nodes;
u32 num_planes;
u32 num_nodes;
u32 num_leafs;
u32 num_surfedges;
u32 num_vertex_lights;
u32 num_vertices;
u32 num_marksurfaces;
u32 num_cell_portals;
u32 visdata_size;
u32 num_vertex_lights;
u32 num_heightfield;
} pxl8_bsp_wire_header;
usize pxl8_protocol_serialize_header(const pxl8_msg_header* msg, u8* buf, usize len);
@ -130,14 +129,15 @@ usize pxl8_protocol_deserialize_chunk_msg_header(const u8* buf, usize len, pxl8_
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];
i32 cx;
i32 cz;
} pxl8_chunk_enter_msg;
usize pxl8_protocol_serialize_chunk_enter(const pxl8_chunk_enter_msg* msg, u8* buf, usize len);
usize pxl8_protocol_deserialize_chunk_enter(const u8* buf, usize len, pxl8_chunk_enter_msg* msg);
u32 pxl8_chunk_hash(i32 cx, i32 cz);
#ifdef __cplusplus
}
#endif

View file

@ -4,16 +4,13 @@
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <lauxlib.h>
#include <lua.h>
#include <lualib.h>
#include "pxl8_cart.h"
#include "pxl8_embed.h"
#include "pxl8_io.h"
#include "pxl8_gui.h"
#include "pxl8_log.h"
#include "pxl8_macros.h"
@ -28,7 +25,7 @@ struct pxl8_script {
char last_error[PXL8_MAX_ERROR_SIZE];
char main_path[PXL8_MAX_PATH];
char watch_dir[PXL8_MAX_PATH];
time_t latest_mod_time;
f64 latest_mod_time;
int repl_env_ref;
bool repl_mode;
};
@ -407,8 +404,12 @@ static pxl8_result pxl8_script_prepare_path(pxl8_script* script, const char* fil
char script_dir[PATH_MAX];
char original_cwd[PATH_MAX];
if (realpath(filename_copy, script_dir) && getcwd(original_cwd, sizeof(original_cwd))) {
chdir(script_dir);
char* resolved = pxl8_io_get_real_path(filename_copy);
char* cwd = pxl8_io_get_cwd();
if (resolved && cwd) {
pxl8_strncpy(script_dir, resolved, sizeof(script_dir));
pxl8_strncpy(original_cwd, cwd, sizeof(original_cwd));
pxl8_io_set_cwd(script_dir);
pxl8_script_set_cart_path(script, script_dir, original_cwd);
strncpy(out_basename, last_slash + 1, basename_size - 1);
out_basename[basename_size - 1] = '\0';
@ -416,6 +417,8 @@ static pxl8_result pxl8_script_prepare_path(pxl8_script* script, const char* fil
strncpy(out_basename, filename, basename_size - 1);
out_basename[basename_size - 1] = '\0';
}
pxl8_free(resolved);
pxl8_free(cwd);
} else {
strncpy(out_basename, filename, basename_size - 1);
out_basename[basename_size - 1] = '\0';
@ -442,51 +445,38 @@ pxl8_result pxl8_script_run_file(pxl8_script* script, const char* filename) {
return result;
}
static time_t get_file_mod_time(const char* path) {
struct stat file_stat;
if (stat(path, &file_stat) == 0) {
return file_stat.st_mtime;
}
return 0;
}
typedef struct {
f64 latest;
} script_mod_ctx;
static time_t get_latest_script_mod_time(const char* dir_path) {
DIR* dir = opendir(dir_path);
if (!dir) return 0;
static f64 get_latest_script_mod_time(const char* dir_path);
time_t latest = 0;
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_name[0] == '.') continue;
static bool check_script_mod_time(void* userdata, const char* dirname, const char* name) {
script_mod_ctx* ctx = userdata;
if (name[0] == '.') return true;
char full_path[512];
snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name);
snprintf(full_path, sizeof(full_path), "%s%s", dirname, name);
struct stat st;
if (stat(full_path, &st) == 0) {
if (S_ISDIR(st.st_mode)) {
time_t subdir_time = get_latest_script_mod_time(full_path);
if (subdir_time > latest) {
latest = subdir_time;
}
if (pxl8_io_is_directory(full_path)) {
f64 subdir_time = get_latest_script_mod_time(full_path);
if (subdir_time > ctx->latest) ctx->latest = subdir_time;
} else {
usize len = strlen(entry->d_name);
bool is_script = (len > 4 && strcmp(entry->d_name + len - 4, ".fnl") == 0) ||
(len > 4 && strcmp(entry->d_name + len - 4, ".lua") == 0);
usize len = strlen(name);
bool is_script = (len > 4 && strcmp(name + len - 4, ".fnl") == 0) ||
(len > 4 && strcmp(name + len - 4, ".lua") == 0);
if (is_script) {
time_t mod_time = get_file_mod_time(full_path);
if (mod_time > latest) {
latest = mod_time;
}
}
}
f64 mod_time = pxl8_io_get_file_modified_time(full_path);
if (mod_time > ctx->latest) ctx->latest = mod_time;
}
}
return true;
}
closedir(dir);
return latest;
static f64 get_latest_script_mod_time(const char* dir_path) {
script_mod_ctx ctx = { .latest = 0.0 };
pxl8_io_enumerate_directory(dir_path, check_script_mod_time, &ctx);
return ctx.latest;
}
void pxl8_script_set_cart_path(pxl8_script* script, const char* cart_path, const char* original_cwd) {
@ -1157,8 +1147,8 @@ bool pxl8_script_check_reload(pxl8_script* script) {
return false;
}
time_t current_mod_time = get_latest_script_mod_time(script->watch_dir);
if (current_mod_time > script->latest_mod_time && current_mod_time != 0) {
f64 current_mod_time = get_latest_script_mod_time(script->watch_dir);
if (current_mod_time > script->latest_mod_time && current_mod_time != 0.0) {
pxl8_info("Script files modified, reloading: %s", script->main_path);
script->latest_mod_time = current_mod_time;
@ -1214,12 +1204,6 @@ static pxl8_resolution parse_resolution(const char* str) {
return PXL8_RESOLUTION_640x360;
}
static pxl8_pixel_mode parse_pixel_mode(const char* str) {
if (strcmp(str, "indexed") == 0) return PXL8_PIXEL_INDEXED;
if (strcmp(str, "hicolor") == 0) return PXL8_PIXEL_HICOLOR;
return PXL8_PIXEL_INDEXED;
}
pxl8_result pxl8_script_load_cart_manifest(pxl8_script* script, pxl8_cart* cart) {
if (!script || !script->L || !cart) return PXL8_ERROR_NULL_POINTER;
@ -1267,12 +1251,6 @@ pxl8_result pxl8_script_load_cart_manifest(pxl8_script* script, pxl8_cart* cart)
}
lua_pop(script->L, 1);
lua_getfield(script->L, -1, "pixel-mode");
if (lua_isstring(script->L, -1)) {
pxl8_cart_set_pixel_mode(cart, parse_pixel_mode(lua_tostring(script->L, -1)));
}
lua_pop(script->L, 1);
lua_getfield(script->L, -1, "window-size");
if (lua_istable(script->L, -1)) {
lua_rawgeti(script->L, -1, 1);

View file

@ -27,9 +27,8 @@ static const char* pxl8_ffi_cdefs =
"f32 pxl8_get_fps(const pxl8* sys);\n"
"\n"
"u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color);\n"
"u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx);\n"
"u8* pxl8_gfx_framebuffer(pxl8_gfx* gfx);\n"
"i32 pxl8_gfx_get_height(pxl8_gfx* ctx);\n"
"u32* pxl8_gfx_get_light_accum(pxl8_gfx* gfx);\n"
"const u32* pxl8_gfx_palette_colors(pxl8_gfx* gfx);\n"
"u16* pxl8_gfx_get_zbuffer(pxl8_gfx* gfx);\n"
"typedef struct pxl8_palette pxl8_palette;\n"
@ -262,6 +261,7 @@ static const char* pxl8_ffi_cdefs =
"void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_3d_uniforms* uniforms);\n"
"void pxl8_3d_clear(pxl8_gfx* gfx, u8 color);\n"
"void pxl8_3d_clear_depth(pxl8_gfx* gfx);\n"
"void pxl8_3d_clear_stencil(pxl8_gfx* gfx, uint8_t value);\n"
"void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u8 color);\n"
"void pxl8_3d_end_frame(pxl8_gfx* gfx);\n"
"u32 pxl8_3d_project_points(pxl8_gfx* gfx, const pxl8_vec3* in, pxl8_vec3* out, u32 count, const pxl8_mat4* transform);\n"
@ -309,9 +309,20 @@ static const char* pxl8_ffi_cdefs =
"void pxl8_mesh_clear(pxl8_mesh* mesh);\n"
"u16 pxl8_mesh_push_vertex(pxl8_mesh* mesh, pxl8_vertex v);\n"
"void pxl8_mesh_push_triangle(pxl8_mesh* mesh, u16 i0, u16 i1, u16 i2);\n"
"void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material);\n"
"typedef struct pxl8_gfx_draw_opts {\n"
" bool color_write;\n"
" int depth_compare;\n"
" bool depth_write;\n"
" bool stencil_test;\n"
" bool stencil_write;\n"
" uint8_t stencil_compare;\n"
" uint8_t stencil_ref;\n"
"} pxl8_gfx_draw_opts;\n"
"\n"
"void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material, const pxl8_gfx_draw_opts* opts);\n"
"\n"
"u32 pxl8_hash32(u32 x);\n"
"u32 pxl8_chunk_hash(i32 cx, i32 cz);\n"
"\n"
"pxl8_mat4 pxl8_mat4_identity(void);\n"
"pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up);\n"
@ -401,6 +412,7 @@ static const char* pxl8_ffi_cdefs =
"\n"
"u32 pxl8_bsp_face_count(const pxl8_bsp* bsp);\n"
"pxl8_vec3 pxl8_bsp_face_normal(const pxl8_bsp* bsp, u32 face_id);\n"
"void pxl8_bsp_face_set_material(pxl8_bsp* bsp, u32 face_id, u16 material_id);\n"
"u8 pxl8_bsp_light_at(const pxl8_bsp* bsp, f32 x, f32 y, f32 z, u8 ambient);\n"
"\n"
"typedef struct pxl8_world_chunk {\n"
@ -437,6 +449,7 @@ static const char* pxl8_ffi_cdefs =
"} pxl8_sim_entity;\n"
"\n"
"void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z);\n"
"void pxl8_world_set_look(pxl8_world* world, f32 yaw, f32 pitch);\n"
"pxl8_sim_entity* pxl8_world_local_player(pxl8_world* world);\n"
"\n"
"typedef struct { i32 cursor_x; i32 cursor_y; bool cursor_down; bool cursor_clicked; u32 hot_id; u32 active_id; } pxl8_gui_state;\n"
@ -450,11 +463,17 @@ static const char* pxl8_ffi_cdefs =
"bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, const char* label);\n"
"bool pxl8_gui_slider(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, float* value, float min_val, float max_val);\n"
"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"
"i32 pxl8_gui_toolbar(pxl8_gui_state* state, pxl8_gfx* gfx, u32 base_id, i32 x, i32 y, i32 btn_w, i32 btn_h, const char** labels, i32 count, i32 selected);\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"
"i32 pxl8_gui_grid_select(pxl8_gui_state* state, pxl8_gfx* gfx, u32 base_id, i32 x, i32 y, i32 cell_w, i32 cell_h, i32 cols, i32 rows, const u8* colors, i32 selected);\n"
"void pxl8_gui_image(pxl8_gfx* gfx, u32 texture_id, i32 sx, i32 sy, i32 sw, i32 sh, i32 dx, i32 dy, i32 dw, i32 dh);\n"
"void pxl8_gui_panel(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h);\n"
"void pxl8_gui_status_bar(pxl8_gfx* gfx, i32 y, i32 screen_w, i32 h, const char* text);\n"
"bool pxl8_gui_toggle(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, const char* label, bool active);\n"
"\n"
"typedef struct pxl8_save pxl8_save;\n"
"pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version);\n"
@ -514,7 +533,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_ENTER_SCENE } pxl8_cmd_type;\n"
"typedef enum pxl8_cmd_type { PXL8_CMD_NONE = 0, PXL8_CMD_SPAWN_ENTITY } pxl8_cmd_type;\n"
"\n"
"typedef struct pxl8_command_msg {\n"
" u16 cmd_type;\n"
@ -548,7 +567,10 @@ static const char* pxl8_ffi_cdefs =
"} pxl8_sim_config;\n"
"\n"
"typedef struct pxl8_sim_world {\n"
" const pxl8_bsp* bsp;\n"
" const pxl8_bsp* chunks[9];\n"
" i32 center_cx;\n"
" i32 center_cz;\n"
" f32 chunk_size;\n"
"} pxl8_sim_world;\n"
"\n"
"void pxl8_sim_move_player(pxl8_sim_entity* ent, const pxl8_input_msg* input, const pxl8_sim_world* world, const pxl8_sim_config* cfg, f32 dt);\n"
@ -587,15 +609,15 @@ static const char* pxl8_ffi_cdefs =
"f32 pxl8_net_lerp_alpha(const pxl8_net* net);\n"
"bool pxl8_net_needs_correction(const pxl8_net* net);\n"
"u64 pxl8_net_player_id(const pxl8_net* net);\n"
"u8 pxl8_net_chunk_type(const pxl8_net* net);\n"
"u32 pxl8_net_chunk_id(const pxl8_net* net);\n"
"i32 pxl8_net_chunk_cx(const pxl8_net* net);\n"
"i32 pxl8_net_chunk_cz(const pxl8_net* net);\n"
"bool pxl8_net_has_chunk(const pxl8_net* net);\n"
"bool pxl8_net_poll(pxl8_net* net);\n"
"u8* pxl8_net_predicted_state(pxl8_net* net);\n"
"void pxl8_net_predicted_tick_set(pxl8_net* net, u64 tick);\n"
"i32 pxl8_net_send_command(pxl8_net* net, const pxl8_command_msg* cmd);\n"
"i32 pxl8_net_send_input(pxl8_net* net, const pxl8_input_msg* input);\n"
"i32 pxl8_net_spawn(pxl8_net* net, f32 x, f32 y, f32 z, f32 yaw, f32 pitch);\n"
"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,6 +2,15 @@
#include <math.h>
#define DIST_EPSILON 0.03125f
typedef struct {
f32 fraction;
pxl8_vec3 normal;
bool all_solid;
bool start_solid;
} trace_result;
static i32 bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos) {
if (!bsp || bsp->num_nodes == 0) return -1;
@ -16,54 +25,299 @@ static i32 bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos) {
return -(node_id + 1);
}
static i32 bsp_contents_from(const pxl8_bsp* bsp, i32 node_id, pxl8_vec3 pos) {
while (node_id >= 0) {
const pxl8_bsp_node* node = &bsp->nodes[node_id];
const pxl8_bsp_plane* plane = &bsp->planes[node->plane_id];
f32 d = pxl8_vec3_dot(pos, plane->normal) - plane->dist;
node_id = node->children[d < 0 ? 1 : 0];
}
i32 leaf_idx = -(node_id + 1);
if (leaf_idx < 0 || (u32)leaf_idx >= bsp->num_leafs) return -1;
return bsp->leafs[leaf_idx].contents;
}
static bool bsp_recursive_trace(const pxl8_bsp* bsp, i32 node_id,
f32 p1f, f32 p2f,
pxl8_vec3 p1, pxl8_vec3 p2,
trace_result* tr) {
if (node_id < 0) {
i32 leaf_idx = -(node_id + 1);
if (leaf_idx >= 0 && (u32)leaf_idx < bsp->num_leafs &&
bsp->leafs[leaf_idx].contents == -1) {
tr->start_solid = true;
} else {
tr->all_solid = false;
}
return true;
}
const pxl8_bsp_node* node = &bsp->nodes[node_id];
const pxl8_bsp_plane* plane = &bsp->planes[node->plane_id];
f32 t1 = pxl8_vec3_dot(p1, plane->normal) - plane->dist;
f32 t2 = pxl8_vec3_dot(p2, plane->normal) - plane->dist;
if (t1 >= 0 && t2 >= 0)
return bsp_recursive_trace(bsp, node->children[0], p1f, p2f, p1, p2, tr);
if (t1 < 0 && t2 < 0)
return bsp_recursive_trace(bsp, node->children[1], p1f, p2f, p1, p2, tr);
i32 side;
f32 frac;
if (t1 < 0) {
frac = (t1 + DIST_EPSILON) / (t1 - t2);
side = 1;
} else {
frac = (t1 - DIST_EPSILON) / (t1 - t2);
side = 0;
}
if (frac < 0) frac = 0;
if (frac > 1) frac = 1;
f32 midf = p1f + (p2f - p1f) * frac;
pxl8_vec3 mid = {
p1.x + frac * (p2.x - p1.x),
p1.y + frac * (p2.y - p1.y),
p1.z + frac * (p2.z - p1.z),
};
if (!bsp_recursive_trace(bsp, node->children[side], p1f, midf, p1, mid, tr))
return false;
if (bsp_contents_from(bsp, node->children[side ^ 1], mid) != -1)
return bsp_recursive_trace(bsp, node->children[side ^ 1], midf, p2f, mid, p2, tr);
if (tr->all_solid)
return false;
if (midf < tr->fraction) {
tr->fraction = midf;
if (side == 0) {
tr->normal = plane->normal;
} else {
tr->normal.x = -plane->normal.x;
tr->normal.y = -plane->normal.y;
tr->normal.z = -plane->normal.z;
}
}
return false;
}
static trace_result bsp_trace_line(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to) {
trace_result tr = { .fraction = 1.0f, .all_solid = true };
if (!bsp || bsp->num_nodes == 0) {
tr.all_solid = false;
return tr;
}
bsp_recursive_trace(bsp, 0, 0.0f, 1.0f, from, to, &tr);
if (tr.all_solid) {
tr.fraction = 0.0f;
tr.start_solid = true;
}
return tr;
}
bool pxl8_bsp_point_solid(const pxl8_bsp* bsp, pxl8_vec3 pos) {
if (!bsp) return false;
if (bsp->bounds_max_x > bsp->bounds_min_x &&
(pos.x < bsp->bounds_min_x || pos.x >= bsp->bounds_max_x ||
pos.z < bsp->bounds_min_z || pos.z >= bsp->bounds_max_z))
return false;
i32 leaf = bsp_find_leaf(bsp, pos);
if (leaf < 0 || (u32)leaf >= bsp->num_leafs) return true;
return bsp->leafs[leaf].contents == -1;
}
static bool bsp_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;
static void trace_offsets(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to,
f32 radius, f32* out_frac, pxl8_vec3* out_normal) {
f32 d = radius * 0.7071f;
pxl8_vec3 offsets[9] = {
{0, 0, 0},
{radius, 0, 0}, {-radius, 0, 0},
{0, 0, radius}, {0, 0, -radius},
{d, 0, d}, {d, 0, -d},
{-d, 0, d}, {-d, 0, -d},
};
*out_frac = 1.0f;
*out_normal = (pxl8_vec3){0, 0, 0};
for (i32 i = 0; i < 9; i++) {
pxl8_vec3 s = { from.x + offsets[i].x, from.y + offsets[i].y, from.z + offsets[i].z };
pxl8_vec3 e = { to.x + offsets[i].x, to.y + offsets[i].y, to.z + offsets[i].z };
trace_result tr = bsp_trace_line(bsp, s, e);
if (tr.fraction < *out_frac && !tr.start_solid) {
*out_frac = tr.fraction;
*out_normal = tr.normal;
}
}
}
pxl8_vec3 pxl8_bsp_trace(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to, f32 radius) {
if (!bsp || bsp->num_nodes == 0) return to;
if (bsp_point_clear(bsp, to.x, to.y, to.z, radius)) {
return to;
f32 frac;
pxl8_vec3 normal;
trace_offsets(bsp, from, to, radius, &frac, &normal);
if (frac >= 1.0f) return to;
pxl8_vec3 delta = { to.x - from.x, to.y - from.y, to.z - from.z };
pxl8_vec3 hit = {
from.x + delta.x * frac,
from.y + delta.y * frac,
from.z + delta.z * frac,
};
f32 remaining = 1.0f - frac;
if (remaining <= 0.0f) return hit;
f32 backoff = pxl8_vec3_dot(delta, normal) * remaining;
pxl8_vec3 slide_target = {
hit.x + delta.x * remaining - normal.x * backoff,
hit.y + delta.y * remaining - normal.y * backoff,
hit.z + delta.z * remaining - normal.z * backoff,
};
f32 slide_frac;
pxl8_vec3 slide_normal;
trace_offsets(bsp, hit, slide_target, radius, &slide_frac, &slide_normal);
if (slide_frac >= 1.0f) return slide_target;
pxl8_vec3 slide_delta = {
slide_target.x - hit.x,
slide_target.y - hit.y,
slide_target.z - hit.z,
};
return (pxl8_vec3){
hit.x + slide_delta.x * slide_frac,
hit.y + slide_delta.y * slide_frac,
hit.z + slide_delta.z * slide_frac,
};
}
static const pxl8_bsp* sim_bsp_at(const pxl8_sim_world* world, f32 x, f32 z) {
i32 cx = (i32)floorf(x / world->chunk_size);
i32 cz = (i32)floorf(z / world->chunk_size);
i32 dx = cx - world->center_cx + 1;
i32 dz = cz - world->center_cz + 1;
if (dx < 0 || dx > 2 || dz < 0 || dz > 2) return NULL;
return world->chunks[dz * 3 + dx];
}
static f32 bsp_terrain_height(const pxl8_bsp* bsp, f32 x, f32 z) {
if (!bsp || !bsp->heightfield || bsp->heightfield_cell_size <= 0) return -1e9f;
f32 lx = (x - bsp->heightfield_ox) / bsp->heightfield_cell_size;
f32 lz = (z - bsp->heightfield_oz) / bsp->heightfield_cell_size;
i32 ix = (i32)floorf(lx);
i32 iz = (i32)floorf(lz);
if (ix < 0 || ix >= bsp->heightfield_w - 1 || iz < 0 || iz >= bsp->heightfield_h - 1) return -1e9f;
f32 fx = lx - ix;
f32 fz = lz - iz;
i32 w = bsp->heightfield_w;
f32 h00 = bsp->heightfield[iz * w + ix];
f32 h10 = bsp->heightfield[iz * w + ix + 1];
f32 h01 = bsp->heightfield[(iz + 1) * w + ix];
f32 h11 = bsp->heightfield[(iz + 1) * w + ix + 1];
f32 h0 = h00 + (h10 - h00) * fx;
f32 h1 = h01 + (h11 - h01) * fx;
return h0 + (h1 - h0) * fz;
}
static f32 sim_terrain_height(const pxl8_sim_world* world, f32 x, f32 z) {
const pxl8_bsp* bsp = sim_bsp_at(world, x, z);
return bsp_terrain_height(bsp, x, z);
}
static void sim_trace_offsets(const pxl8_sim_world* world, pxl8_vec3 from, pxl8_vec3 to,
f32 radius, f32* out_frac, pxl8_vec3* out_normal) {
f32 d = radius * 0.7071f;
pxl8_vec3 offsets[9] = {
{0, 0, 0},
{radius, 0, 0}, {-radius, 0, 0},
{0, 0, radius}, {0, 0, -radius},
{d, 0, d}, {d, 0, -d},
{-d, 0, d}, {-d, 0, -d},
};
*out_frac = 1.0f;
*out_normal = (pxl8_vec3){0, 0, 0};
for (i32 i = 0; i < 9; i++) {
pxl8_vec3 s = { from.x + offsets[i].x, from.y + offsets[i].y, from.z + offsets[i].z };
pxl8_vec3 e = { to.x + offsets[i].x, to.y + offsets[i].y, to.z + offsets[i].z };
const pxl8_bsp* bsp_s = sim_bsp_at(world, s.x, s.z);
const pxl8_bsp* bsp_e = sim_bsp_at(world, e.x, e.z);
if (bsp_s) {
trace_result tr = bsp_trace_line(bsp_s, s, e);
if (tr.fraction < *out_frac && !tr.start_solid) {
*out_frac = tr.fraction;
*out_normal = tr.normal;
}
}
if (bsp_e && bsp_e != bsp_s) {
bool inside_bounds = !(bsp_e->bounds_max_x > bsp_e->bounds_min_x) ||
(s.x >= bsp_e->bounds_min_x && s.x < bsp_e->bounds_max_x &&
s.z >= bsp_e->bounds_min_z && s.z < bsp_e->bounds_max_z);
if (inside_bounds) {
trace_result tr = bsp_trace_line(bsp_e, s, e);
if (tr.fraction < *out_frac && !tr.start_solid) {
*out_frac = tr.fraction;
*out_normal = tr.normal;
}
}
}
}
bool x_ok = bsp_point_clear(bsp, to.x, from.y, from.z, radius);
bool y_ok = bsp_point_clear(bsp, from.x, to.y, from.z, radius);
bool z_ok = bsp_point_clear(bsp, 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, f32 height) {
(void)height;
if (!world) return to;
if (!world || world->chunk_size <= 0) return to;
if (world->bsp) {
return pxl8_bsp_trace(world->bsp, from, to, radius);
}
f32 frac;
pxl8_vec3 normal;
sim_trace_offsets(world, from, to, radius, &frac, &normal);
if (frac >= 1.0f) return to;
return to;
pxl8_vec3 delta = { to.x - from.x, to.y - from.y, to.z - from.z };
pxl8_vec3 hit = {
from.x + delta.x * frac,
from.y + delta.y * frac,
from.z + delta.z * frac,
};
f32 remaining = 1.0f - frac;
if (remaining <= 0.0f) return hit;
f32 backoff = pxl8_vec3_dot(delta, normal) * remaining;
pxl8_vec3 slide_target = {
hit.x + delta.x * remaining - normal.x * backoff,
hit.y + delta.y * remaining - normal.y * backoff,
hit.z + delta.z * remaining - normal.z * backoff,
};
f32 slide_frac;
pxl8_vec3 slide_normal;
sim_trace_offsets(world, hit, slide_target, radius, &slide_frac, &slide_normal);
if (slide_frac >= 1.0f) return slide_target;
pxl8_vec3 slide_delta = {
slide_target.x - hit.x,
slide_target.y - hit.y,
slide_target.z - hit.z,
};
return (pxl8_vec3){
hit.x + slide_delta.x * slide_frac,
hit.y + slide_delta.y * slide_frac,
hit.z + slide_delta.z * slide_frac,
};
}
bool pxl8_sim_check_ground(const pxl8_sim_world* world, pxl8_vec3 pos, f32 radius) {
if (!world) return true;
if (!world->bsp) return true;
if (!world || world->chunk_size <= 0) return true;
f32 th = sim_terrain_height(world, pos.x, pos.z);
if (th > -1e8f && pos.y - th < 2.0f) return true;
pxl8_vec3 down = {pos.x, pos.y - 2.0f, pos.z};
pxl8_vec3 result = pxl8_sim_trace(world, pos, down, radius, 0.0f);
@ -119,22 +373,17 @@ void pxl8_sim_move_player(pxl8_sim_entity* ent, const pxl8_input_msg* input,
ent->pos.z + ent->vel.z * dt
};
if (grounded) {
f32 th_dest = sim_terrain_height(world, target.x, target.z);
if (th_dest > -1e8f) target.y = th_dest;
}
pxl8_vec3 new_pos = pxl8_sim_trace(world, ent->pos, target, cfg->player_radius, cfg->player_height);
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;
f32 th = sim_terrain_height(world, new_pos.x, new_pos.z);
if (th > -1e8f && new_pos.y < th) {
new_pos.y = th;
if (ent->vel.y < 0) ent->vel.y = 0;
}
ent->pos = new_pos;
@ -175,22 +424,17 @@ void pxl8_sim_integrate(pxl8_sim_entity* ent, const pxl8_sim_world* world,
ent->pos.z + ent->vel.z * dt
};
if (grounded) {
f32 th_dest = sim_terrain_height(world, target.x, target.z);
if (th_dest > -1e8f) target.y = th_dest;
}
pxl8_vec3 new_pos = pxl8_sim_trace(world, ent->pos, target, cfg->player_radius, cfg->player_height);
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;
f32 th2 = sim_terrain_height(world, new_pos.x, new_pos.z);
if (th2 > -1e8f && new_pos.y < th2) {
new_pos.y = th2;
if (ent->vel.y < 0.0f) ent->vel.y = 0.0f;
}
ent->pos = new_pos;

View file

@ -37,7 +37,10 @@ typedef struct pxl8_sim_entity {
} pxl8_sim_entity;
typedef struct pxl8_sim_world {
const pxl8_bsp* bsp;
const pxl8_bsp* chunks[9];
i32 center_cx;
i32 center_cz;
f32 chunk_size;
} pxl8_sim_world;
bool pxl8_bsp_point_solid(const pxl8_bsp* bsp, pxl8_vec3 pos);

View file

@ -14,15 +14,31 @@
#include "pxl8_gfx3d.h"
#include "pxl8_log.h"
#include "pxl8_mem.h"
#include "pxl8_protocol.h"
#include "pxl8_sim.h"
#define PXL8_VIS_MAX_NODES (PXL8_WORLD_MAX_LOADED_CHUNKS * 512)
#define PXL8_VIS_MAX_QUEUE (PXL8_VIS_MAX_NODES * 4)
#define PXL8_VIS_BYTES ((PXL8_VIS_MAX_NODES + 7) / 8)
#define PXL8_WORLD_ENTITY_CAPACITY 256
typedef struct {
u16 chunk_idx;
u16 leaf_idx;
pxl8_rect window;
} world_vis_node;
struct pxl8_world {
pxl8_loaded_chunk loaded[PXL8_WORLD_MAX_LOADED_CHUNKS];
u32 loaded_count;
pxl8_world_chunk* active_chunk;
pxl8_bsp_render_state* active_render_state;
pxl8_gfx_material shared_materials[16];
bool shared_material_set[16];
pxl8_world_chunk_cache* chunk_cache;
pxl8_entity_pool* entities;
pxl8_bsp_render_state* bsp_render_state;
pxl8_sim_entity local_player;
u64 client_tick;
@ -30,6 +46,12 @@ struct pxl8_world {
pxl8_vec2 pointer_motion;
pxl8_sim_config sim_config;
u8 vis_bits[PXL8_VIS_BYTES];
u8* vis_ptrs[PXL8_WORLD_MAX_LOADED_CHUNKS];
pxl8_rect vis_windows[PXL8_VIS_MAX_NODES];
pxl8_rect* vis_win_ptrs[PXL8_WORLD_MAX_LOADED_CHUNKS];
world_vis_node vis_queue[PXL8_VIS_MAX_QUEUE];
#ifdef PXL8_ASYNC_THREADS
pxl8_sim_entity render_state[2];
atomic_uint active_buffer;
@ -72,9 +94,11 @@ pxl8_world* pxl8_world_create(void) {
void pxl8_world_destroy(pxl8_world* world) {
if (!world) return;
for (u32 i = 0; i < world->loaded_count; i++) {
pxl8_bsp_render_state_destroy(world->loaded[i].render_state);
}
pxl8_world_chunk_cache_destroy(world->chunk_cache);
pxl8_entity_pool_destroy(world->entities);
pxl8_bsp_render_state_destroy(world->bsp_render_state);
pxl8_free(world);
}
@ -88,27 +112,26 @@ pxl8_world_chunk* pxl8_world_active_chunk(pxl8_world* world) {
return world->active_chunk;
}
void pxl8_world_set_active_chunk(pxl8_world* world, pxl8_world_chunk* chunk) {
if (!world) return;
world->active_chunk = chunk;
}
pxl8_entity_pool* pxl8_world_entities(pxl8_world* world) {
if (!world) return NULL;
return world->entities;
}
pxl8_entity pxl8_world_spawn(pxl8_world* world) {
if (!world || !world->entities) return PXL8_ENTITY_INVALID;
return pxl8_entity_spawn(world->entities);
}
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->bsp) {
sim.bsp = world->active_chunk->bsp;
const f32 chunk_size = 16.0f * 64.0f;
sim.chunk_size = chunk_size;
i32 pcx = (i32)floorf(pos.x / chunk_size);
i32 pcz = (i32)floorf(pos.z / chunk_size);
sim.center_cx = pcx;
sim.center_cz = pcz;
for (u32 i = 0; i < world->loaded_count; i++) {
const pxl8_loaded_chunk* lc = &world->loaded[i];
if (!lc->chunk || !lc->chunk->bsp) continue;
i32 dx = lc->cx - pcx + 1;
i32 dz = lc->cz - pcz + 1;
if (dx >= 0 && dx <= 2 && dz >= 0 && dz <= 2) {
sim.chunks[dz * 3 + dx] = lc->chunk->bsp;
}
}
return sim;
}
@ -244,62 +267,452 @@ void pxl8_world_update(pxl8_world* world, f32 dt) {
pxl8_world_chunk_cache_tick(world->chunk_cache);
}
static inline bool vr_valid(pxl8_rect r) {
return r.x0 < r.x1 && r.y0 < r.y1;
}
static inline pxl8_rect vr_intersect(pxl8_rect a, pxl8_rect b) {
return (pxl8_rect){
.x0 = a.x0 > b.x0 ? a.x0 : b.x0,
.y0 = a.y0 > b.y0 ? a.y0 : b.y0,
.x1 = a.x1 < b.x1 ? a.x1 : b.x1,
.y1 = a.y1 < b.y1 ? a.y1 : b.y1,
};
}
static pxl8_rect project_portal(f32 px0, f32 pz0, f32 px1, f32 pz1,
f32 y_lo, f32 y_hi, const pxl8_mat4* vp) {
pxl8_vec3 corners[4] = {
{px0, y_lo, pz0}, {px1, y_lo, pz1},
{px1, y_hi, pz1}, {px0, y_hi, pz0},
};
const f32 NEAR_W = 0.001f;
pxl8_vec4 clip[4];
bool front[4];
i32 fc = 0;
for (i32 i = 0; i < 4; i++) {
clip[i] = pxl8_mat4_multiply_vec4(*vp, (pxl8_vec4){
corners[i].x, corners[i].y, corners[i].z, 1.0f});
front[i] = clip[i].w > NEAR_W;
if (front[i]) fc++;
}
if (fc == 0) return (pxl8_rect){0, 0, 0, 0};
if (fc < 4) return (pxl8_rect){-1.0f, -1.0f, 1.0f, 1.0f};
pxl8_rect r = {1e30f, 1e30f, -1e30f, -1e30f};
for (i32 i = 0; i < 4; i++) {
f32 iw = 1.0f / clip[i].w;
f32 nx = clip[i].x * iw, ny = clip[i].y * iw;
if (nx < r.x0) r.x0 = nx; if (nx > r.x1) r.x1 = nx;
if (ny < r.y0) r.y0 = ny; if (ny > r.y1) r.y1 = ny;
}
if (r.x0 < -1.0f) r.x0 = -1.0f;
if (r.y0 < -1.0f) r.y0 = -1.0f;
if (r.x1 > 1.0f) r.x1 = 1.0f;
if (r.y1 > 1.0f) r.y1 = 1.0f;
return r;
}
static void compute_edge_leafs(pxl8_loaded_chunk* lc) {
const f32 CHUNK_SIZE = 16.0f * 64.0f;
const pxl8_bsp* bsp = lc->chunk->bsp;
f32 cx0 = lc->cx * CHUNK_SIZE;
f32 cz0 = lc->cz * CHUNK_SIZE;
f32 cx1 = cx0 + CHUNK_SIZE;
f32 cz1 = cz0 + CHUNK_SIZE;
memset(lc->edges, 0, sizeof(lc->edges));
for (u32 i = 0; i < bsp->num_leafs; i++) {
const pxl8_bsp_leaf* leaf = &bsp->leafs[i];
if (bsp->leafs[i].contents == -1) continue;
if ((f32)leaf->mins[2] <= (f32)((i16)cz0) + 1.0f && lc->edges[0].count < 16)
lc->edges[0].leafs[lc->edges[0].count++] = (u16)i;
if ((f32)leaf->maxs[2] >= (f32)((i16)cz1) - 1.0f && lc->edges[1].count < 16)
lc->edges[1].leafs[lc->edges[1].count++] = (u16)i;
if ((f32)leaf->mins[0] <= (f32)((i16)cx0) + 1.0f && lc->edges[2].count < 16)
lc->edges[2].leafs[lc->edges[2].count++] = (u16)i;
if ((f32)leaf->maxs[0] >= (f32)((i16)cx1) - 1.0f && lc->edges[3].count < 16)
lc->edges[3].leafs[lc->edges[3].count++] = (u16)i;
}
}
static i32 world_find_chunk(const pxl8_world* world, i32 cx, i32 cz) {
for (u32 i = 0; i < world->loaded_count; i++) {
if (world->loaded[i].cx == cx && world->loaded[i].cz == cz &&
world->loaded[i].chunk && world->loaded[i].chunk->bsp)
return (i32)i;
}
return -1;
}
static void world_mark_leaf_faces(const pxl8_bsp* bsp, pxl8_bsp_render_state* rs, u32 leaf_idx) {
const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_idx];
for (u32 i = 0; i < leaf->num_marksurfaces; i++) {
u32 si = leaf->first_marksurface + i;
if (si < bsp->num_marksurfaces) {
u32 fi = bsp->marksurfaces[si];
if (fi < bsp->num_faces && rs)
rs->render_face_flags[fi] = 1;
}
}
}
static bool vis_try_enqueue(u8** vis, pxl8_rect** windows, world_vis_node* queue,
u32* tail, u32 max_queue,
u16 ci, u16 li, pxl8_rect nw) {
if (!vis[ci] || !windows[ci]) return false;
u32 byte = li >> 3;
u32 bit = 1 << (li & 7);
if (vis[ci][byte] & bit) {
pxl8_rect* ex = &windows[ci][li];
bool expanded = false;
if (nw.x0 < ex->x0) { ex->x0 = nw.x0; expanded = true; }
if (nw.y0 < ex->y0) { ex->y0 = nw.y0; expanded = true; }
if (nw.x1 > ex->x1) { ex->x1 = nw.x1; expanded = true; }
if (nw.y1 > ex->y1) { ex->y1 = nw.y1; expanded = true; }
if (expanded && *tail < max_queue)
queue[(*tail)++] = (world_vis_node){ci, li, *ex};
return expanded;
}
vis[ci][byte] |= bit;
windows[ci][li] = nw;
if (*tail < max_queue)
queue[(*tail)++] = (world_vis_node){ci, li, nw};
return true;
}
static void world_compute_visibility(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) {
const f32 CHUNK_SIZE = 16.0f * 64.0f;
const f32 PORTAL_Y_HI = 192.0f;
const pxl8_mat4* vp = pxl8_3d_get_view_proj(gfx);
i32 cam_ci = -1, cam_li = -1;
for (u32 i = 0; i < world->loaded_count; i++) {
pxl8_loaded_chunk* lc = &world->loaded[i];
if (!lc->chunk || !lc->chunk->bsp) continue;
const pxl8_bsp* bsp = lc->chunk->bsp;
f32 cx0 = lc->cx * CHUNK_SIZE;
f32 cz0 = lc->cz * CHUNK_SIZE;
if (camera_pos.x < cx0 || camera_pos.x >= cx0 + CHUNK_SIZE ||
camera_pos.z < cz0 || camera_pos.z >= cz0 + CHUNK_SIZE) continue;
if (bsp->bounds_max_x > bsp->bounds_min_x &&
(camera_pos.x < bsp->bounds_min_x || camera_pos.x >= bsp->bounds_max_x ||
camera_pos.z < bsp->bounds_min_z || camera_pos.z >= bsp->bounds_max_z))
continue;
i32 leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
if (leaf >= 0 && (u32)leaf < bsp->num_leafs &&
bsp->leafs[leaf].contents != -1 &&
(!(bsp->bounds_max_x > bsp->bounds_min_x) || bsp->leafs[leaf].contents == -2)) {
cam_ci = (i32)i;
cam_li = leaf;
break;
}
}
if (cam_ci < 0) {
for (u32 i = 0; i < world->loaded_count; i++) {
pxl8_loaded_chunk* lc = &world->loaded[i];
if (!lc->chunk || !lc->chunk->bsp) continue;
const pxl8_bsp* bsp = lc->chunk->bsp;
if (bsp->bounds_max_x > bsp->bounds_min_x) continue;
i32 leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
if (leaf < 0 || (u32)leaf >= bsp->num_leafs) continue;
const pxl8_bsp_leaf* l = &bsp->leafs[leaf];
if (l->contents == -1) continue;
if (camera_pos.x < (f32)l->mins[0] || camera_pos.x > (f32)l->maxs[0] ||
camera_pos.z < (f32)l->mins[2] || camera_pos.z > (f32)l->maxs[2]) continue;
cam_ci = (i32)i;
cam_li = leaf;
break;
}
}
if (cam_ci < 0 || !vp) {
for (u32 i = 0; i < world->loaded_count; i++) {
pxl8_bsp_render_state* rs = world->loaded[i].render_state;
if (!rs) continue;
if (rs->render_face_flags)
memset(rs->render_face_flags, 1, rs->num_faces);
rs->exterior = true;
}
return;
}
memset(world->vis_bits, 0, sizeof(world->vis_bits));
memset(world->vis_ptrs, 0, sizeof(world->vis_ptrs));
memset(world->vis_win_ptrs, 0, sizeof(world->vis_win_ptrs));
u32 voff = 0, woff = 0;
for (u32 i = 0; i < world->loaded_count; i++) {
if (world->loaded[i].chunk && world->loaded[i].chunk->bsp) {
u32 nl = world->loaded[i].chunk->bsp->num_leafs;
u32 vbytes = (nl + 7) / 8;
if (voff + vbytes > PXL8_VIS_BYTES || woff + nl > PXL8_VIS_MAX_NODES)
continue;
world->vis_ptrs[i] = world->vis_bits + voff;
voff += vbytes;
world->vis_win_ptrs[i] = world->vis_windows + woff;
woff += nl;
}
}
if (!world->vis_ptrs[cam_ci] || !world->vis_win_ptrs[cam_ci]) return;
for (u32 i = 0; i < world->loaded_count; i++) {
pxl8_bsp_render_state* rs = world->loaded[i].render_state;
if (!rs) continue;
if (rs->render_face_flags)
memset(rs->render_face_flags, 0, rs->num_faces);
rs->exterior = false;
}
u32 head = 0, tail = 0;
pxl8_rect full_screen = {-1.0f, -1.0f, 1.0f, 1.0f};
const pxl8_bsp* cam_bsp = world->loaded[cam_ci].chunk->bsp;
bool cam_exterior = cam_bsp->leafs[cam_li].contents == 0 &&
!(cam_bsp->bounds_max_x > cam_bsp->bounds_min_x);
world->vis_ptrs[cam_ci][cam_li >> 3] |= (1 << (cam_li & 7));
world->vis_win_ptrs[cam_ci][cam_li] = full_screen;
world->vis_queue[tail++] = (world_vis_node){(u16)cam_ci, (u16)cam_li, full_screen};
while (head < tail) {
world_vis_node cur = world->vis_queue[head++];
pxl8_loaded_chunk* lc = &world->loaded[cur.chunk_idx];
const pxl8_bsp* bsp = lc->chunk->bsp;
world_mark_leaf_faces(bsp, lc->render_state, cur.leaf_idx);
if (bsp->cell_portals && cur.leaf_idx < bsp->num_cell_portals) {
bool is_cam_leaf = (cur.chunk_idx == (u16)cam_ci && cur.leaf_idx == (u16)cam_li);
bool is_cam_chunk = (cur.chunk_idx == (u16)cam_ci);
const pxl8_bsp_cell_portals* cp = &bsp->cell_portals[cur.leaf_idx];
for (u8 pi = 0; pi < cp->num_portals; pi++) {
const pxl8_bsp_portal* p = &cp->portals[pi];
u32 target = p->target_leaf;
if (target >= bsp->num_leafs) continue;
if (bsp->leafs[target].contents == -1) continue;
if (is_cam_chunk && !pxl8_bsp_is_leaf_visible(bsp, cam_li, (i32)target)) continue;
if (is_cam_leaf || cam_exterior) {
vis_try_enqueue(world->vis_ptrs, world->vis_win_ptrs,
world->vis_queue, &tail, PXL8_VIS_MAX_QUEUE,
cur.chunk_idx, (u16)target, full_screen);
continue;
}
pxl8_rect psr = project_portal(p->x0, p->z0, p->x1, p->z1,
0.0f, PORTAL_Y_HI, vp);
if (!vr_valid(psr)) continue;
pxl8_rect nw = vr_intersect(cur.window, psr);
if (!vr_valid(nw)) continue;
vis_try_enqueue(world->vis_ptrs, world->vis_win_ptrs,
world->vis_queue, &tail, PXL8_VIS_MAX_QUEUE,
cur.chunk_idx, (u16)target, nw);
}
}
const pxl8_bsp_leaf* leaf = &bsp->leafs[cur.leaf_idx];
f32 chunk_x0 = lc->cx * CHUNK_SIZE;
f32 chunk_z0 = lc->cz * CHUNK_SIZE;
f32 chunk_x1 = chunk_x0 + CHUNK_SIZE;
f32 chunk_z1 = chunk_z0 + CHUNK_SIZE;
struct { i32 dx, dz; bool at_edge; f32 bnd; } dirs[4] = {
{ 0, -1, (f32)leaf->mins[2] <= (f32)((i16)chunk_z0) + 1.0f, chunk_z0 },
{ 0, 1, (f32)leaf->maxs[2] >= (f32)((i16)chunk_z1) - 1.0f, chunk_z1 },
{-1, 0, (f32)leaf->mins[0] <= (f32)((i16)chunk_x0) + 1.0f, chunk_x0 },
{ 1, 0, (f32)leaf->maxs[0] >= (f32)((i16)chunk_x1) - 1.0f, chunk_x1 },
};
static const u8 opposite_edge[4] = {1, 0, 3, 2};
for (u32 d = 0; d < 4; d++) {
if (!dirs[d].at_edge) continue;
i32 nci = world_find_chunk(world, lc->cx + dirs[d].dx, lc->cz + dirs[d].dz);
if (nci < 0) continue;
const pxl8_bsp* nbsp = world->loaded[nci].chunk->bsp;
const pxl8_edge_leafs* nedge = &world->loaded[nci].edges[opposite_edge[d]];
for (u8 k = 0; k < nedge->count; k++) {
u16 nl = nedge->leafs[k];
const pxl8_bsp_leaf* nleaf = &nbsp->leafs[nl];
bool overlaps = false;
if (dirs[d].dx != 0) {
overlaps = leaf->maxs[2] > nleaf->mins[2] && leaf->mins[2] < nleaf->maxs[2];
} else {
overlaps = leaf->maxs[0] > nleaf->mins[0] && leaf->mins[0] < nleaf->maxs[0];
}
if (!overlaps) continue;
f32 bpx0, bpz0, bpx1, bpz1;
if (dirs[d].dx != 0) {
bpx0 = dirs[d].bnd; bpx1 = dirs[d].bnd;
i16 zlo = leaf->mins[2] > nleaf->mins[2] ? leaf->mins[2] : nleaf->mins[2];
i16 zhi = leaf->maxs[2] < nleaf->maxs[2] ? leaf->maxs[2] : nleaf->maxs[2];
bpz0 = (f32)zlo; bpz1 = (f32)zhi;
} else {
bpz0 = dirs[d].bnd; bpz1 = dirs[d].bnd;
i16 xlo = leaf->mins[0] > nleaf->mins[0] ? leaf->mins[0] : nleaf->mins[0];
i16 xhi = leaf->maxs[0] < nleaf->maxs[0] ? leaf->maxs[0] : nleaf->maxs[0];
bpx0 = (f32)xlo; bpx1 = (f32)xhi;
}
if (cam_exterior) {
vis_try_enqueue(world->vis_ptrs, world->vis_win_ptrs,
world->vis_queue, &tail, PXL8_VIS_MAX_QUEUE,
(u16)nci, (u16)nl, full_screen);
continue;
}
pxl8_rect psr = project_portal(bpx0, bpz0, bpx1, bpz1,
0.0f, PORTAL_Y_HI, vp);
if (!vr_valid(psr)) continue;
pxl8_rect nw = vr_intersect(cur.window, psr);
if (!vr_valid(nw)) continue;
vis_try_enqueue(world->vis_ptrs, world->vis_win_ptrs,
world->vis_queue, &tail, PXL8_VIS_MAX_QUEUE,
(u16)nci, (u16)nl, nw);
}
}
}
}
void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) {
if (!world || !gfx) return;
if (world->active_chunk && world->active_chunk->bsp) {
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 {
else
pxl8_3d_set_bsp(gfx, NULL);
world_compute_visibility(world, gfx, camera_pos);
for (u32 i = 0; i < world->loaded_count; i++) {
pxl8_loaded_chunk* lc = &world->loaded[i];
if (!lc->chunk || !lc->chunk->bsp || !lc->render_state) continue;
pxl8_bsp_render(gfx, lc->chunk->bsp, lc->render_state, NULL);
}
}
static void apply_shared_materials(pxl8_world* world, pxl8_bsp_render_state* rs) {
for (u16 i = 0; i < 16; i++) {
if (world->shared_material_set[i]) {
pxl8_bsp_set_material(rs, i, &world->shared_materials[i]);
}
}
}
void pxl8_world_sync(pxl8_world* world, pxl8_net* net) {
if (!world || !net) return;
u32 chunk_id = pxl8_net_chunk_id(net);
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) {
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);
if (world->bsp_render_state) {
pxl8_bsp_render_state_destroy(world->bsp_render_state);
world->bsp_render_state = NULL;
}
world->bsp_render_state = pxl8_bsp_render_state_create(chunk->bsp->num_faces);
}
}
} else if (world->active_chunk != NULL) {
if (!pxl8_net_has_chunk(net)) {
u32 old_count = world->loaded_count;
world->loaded_count = 0;
world->active_chunk = NULL;
if (world->bsp_render_state) {
pxl8_bsp_render_state_destroy(world->bsp_render_state);
world->bsp_render_state = NULL;
world->active_render_state = NULL;
for (u32 i = 0; i < old_count; i++) {
pxl8_bsp_render_state_destroy(world->loaded[i].render_state);
}
return;
}
i32 center_cx = pxl8_net_chunk_cx(net);
i32 center_cz = pxl8_net_chunk_cz(net);
pxl8_loaded_chunk old_loaded[PXL8_WORLD_MAX_LOADED_CHUNKS];
u32 old_count = world->loaded_count;
memcpy(old_loaded, world->loaded, sizeof(old_loaded));
pxl8_loaded_chunk new_loaded[PXL8_WORLD_MAX_LOADED_CHUNKS];
u32 new_count = 0;
pxl8_world_chunk* new_active_chunk = NULL;
pxl8_bsp_render_state* new_active_rs = NULL;
for (i32 dz = -2; dz <= 2; dz++) {
for (i32 dx = -2; dx <= 2; dx++) {
i32 cx = center_cx + dx;
i32 cz = center_cz + dz;
u32 id = pxl8_chunk_hash(cx, cz);
pxl8_world_chunk* chunk = pxl8_world_chunk_cache_get_bsp(world->chunk_cache, id);
if (!chunk || !chunk->bsp) continue;
pxl8_bsp_render_state* rs = NULL;
for (u32 j = 0; j < old_count; j++) {
if (old_loaded[j].active && old_loaded[j].cx == cx && old_loaded[j].cz == cz &&
old_loaded[j].chunk == chunk) {
rs = old_loaded[j].render_state;
old_loaded[j].active = false;
break;
}
}
}
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->bsp) return;
if (!rs) {
rs = pxl8_bsp_render_state_create(chunk->bsp->num_faces);
if (rs && rs->render_face_flags)
memset(rs->render_face_flags, 1, rs->num_faces);
apply_shared_materials(world, rs);
}
world->bsp_render_state = pxl8_bsp_render_state_create(world->active_chunk->bsp->num_faces);
u32 idx = new_count++;
new_loaded[idx] = (pxl8_loaded_chunk){
.chunk = chunk,
.render_state = rs,
.cx = cx,
.cz = cz,
.active = true,
};
compute_edge_leafs(&new_loaded[idx]);
if (dx == 0 && dz == 0) {
new_active_chunk = chunk;
new_active_rs = rs;
}
}
}
memcpy(world->loaded, new_loaded, sizeof(new_loaded));
world->active_chunk = new_active_chunk;
world->active_render_state = new_active_rs;
world->loaded_count = new_count;
for (u32 j = 0; j < old_count; j++) {
if (old_loaded[j].active) {
pxl8_bsp_render_state_destroy(old_loaded[j].render_state);
}
}
}
void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_gfx_material* material) {
if (!world || !material) return;
if (!world || !material || material_id >= 16) return;
ensure_bsp_render_state(world);
if (!world->bsp_render_state) return;
world->shared_materials[material_id] = *material;
world->shared_material_set[material_id] = true;
pxl8_bsp_set_material(world->bsp_render_state, material_id, material);
for (u32 i = 0; i < world->loaded_count; i++) {
if (world->loaded[i].render_state) {
pxl8_bsp_set_material(world->loaded[i].render_state, material_id, material);
}
}
}
void pxl8_world_set_sim_config(pxl8_world* world, const pxl8_sim_config* config) {
@ -316,6 +729,7 @@ void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z) {
world->local_player.flags = PXL8_SIM_FLAG_ALIVE | PXL8_SIM_FLAG_PLAYER | PXL8_SIM_FLAG_GROUNDED;
world->local_player.kind = 0;
world->client_tick = 0;
world->pointer_motion = (pxl8_vec2){0};
#ifdef PXL8_ASYNC_THREADS
world->render_state[0] = world->local_player;
@ -323,6 +737,20 @@ void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z) {
#endif
}
void pxl8_world_set_look(pxl8_world* world, f32 yaw, f32 pitch) {
if (!world) return;
world->local_player.yaw = yaw;
world->local_player.pitch = pitch;
world->pointer_motion = (pxl8_vec2){.yaw = yaw, .pitch = pitch};
#ifdef PXL8_ASYNC_THREADS
world->render_state[0].yaw = yaw;
world->render_state[0].pitch = pitch;
world->render_state[1].yaw = yaw;
world->render_state[1].pitch = pitch;
#endif
}
pxl8_sim_entity* pxl8_world_local_player(pxl8_world* world) {
if (!world) return NULL;
#ifdef PXL8_ASYNC_THREADS

View file

@ -2,17 +2,35 @@
#include "pxl8_entity.h"
#include "pxl8_gfx.h"
#include "pxl8_gfx3d.h"
#include "pxl8_math.h"
#include "pxl8_net.h"
#include "pxl8_sim.h"
#include "pxl8_types.h"
#include "pxl8_world_chunk.h"
#include "pxl8_world_chunk_cache.h"
#include "pxl8_bsp_render.h"
#ifdef __cplusplus
extern "C" {
#endif
#define PXL8_WORLD_MAX_LOADED_CHUNKS 25
typedef struct {
u16 leafs[16];
u8 count;
} pxl8_edge_leafs;
typedef struct pxl8_loaded_chunk {
pxl8_world_chunk* chunk;
pxl8_bsp_render_state* render_state;
pxl8_edge_leafs edges[4];
i32 cx;
i32 cz;
bool active;
} pxl8_loaded_chunk;
typedef struct pxl8_world pxl8_world;
pxl8_world* pxl8_world_create(void);
@ -20,10 +38,6 @@ void pxl8_world_destroy(pxl8_world* world);
pxl8_world_chunk_cache* pxl8_world_get_chunk_cache(pxl8_world* world);
pxl8_world_chunk* pxl8_world_active_chunk(pxl8_world* world);
void pxl8_world_set_active_chunk(pxl8_world* world, pxl8_world_chunk* chunk);
pxl8_entity_pool* pxl8_world_entities(pxl8_world* world);
pxl8_entity pxl8_world_spawn(pxl8_world* world);
bool pxl8_world_point_solid(const pxl8_world* world, f32 x, f32 y, f32 z);
pxl8_ray pxl8_world_ray(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to);
@ -37,6 +51,7 @@ void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_
void pxl8_world_set_sim_config(pxl8_world* world, const pxl8_sim_config* config);
void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z);
void pxl8_world_set_look(pxl8_world* world, f32 yaw, f32 pitch);
pxl8_sim_entity* pxl8_world_local_player(pxl8_world* world);
pxl8_sim_world pxl8_world_sim_world(const pxl8_world* world, pxl8_vec3 pos);
void pxl8_world_predict(pxl8_world* world, pxl8_net* net, const pxl8_input_msg* input, f32 dt);

View file

@ -179,13 +179,8 @@ static pxl8_bsp* assembly_to_bsp(pxl8_world_chunk_assembly* a) {
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);
pxl8_protocol_deserialize_bsp_wire_header(a->data, 48, &wire_hdr);
s.offset = 48;
if (wire_hdr.num_vertices > 0) {
bsp->vertices = pxl8_calloc(wire_hdr.num_vertices, sizeof(pxl8_bsp_vertex));
@ -273,21 +268,42 @@ static pxl8_bsp* assembly_to_bsp(pxl8_world_chunk_assembly* a) {
}
}
pxl8_debug("Deserialized BSP: %u verts, %u faces, %u nodes, %u leafs",
bsp->num_vertices, bsp->num_faces, bsp->num_nodes, bsp->num_leafs);
if (wire_hdr.num_heightfield > 0) {
bsp->heightfield = pxl8_calloc(wire_hdr.num_heightfield, sizeof(f32));
bsp->num_heightfield = wire_hdr.num_heightfield;
for (u32 i = 0; i < wire_hdr.num_heightfield; i++) {
u32 raw = pxl8_read_u32_be(&s);
memcpy(&bsp->heightfield[i], &raw, sizeof(f32));
}
bsp->heightfield_w = pxl8_read_u16_be(&s);
bsp->heightfield_h = pxl8_read_u16_be(&s);
u32 ox_raw = pxl8_read_u32_be(&s);
memcpy(&bsp->heightfield_ox, &ox_raw, sizeof(f32));
u32 oz_raw = pxl8_read_u32_be(&s);
memcpy(&bsp->heightfield_oz, &oz_raw, sizeof(f32));
u32 cs_raw = pxl8_read_u32_be(&s);
memcpy(&bsp->heightfield_cell_size, &cs_raw, sizeof(f32));
}
u32 raw;
raw = pxl8_read_u32_be(&s);
memcpy(&bsp->bounds_min_x, &raw, sizeof(f32));
raw = pxl8_read_u32_be(&s);
memcpy(&bsp->bounds_min_z, &raw, sizeof(f32));
raw = pxl8_read_u32_be(&s);
memcpy(&bsp->bounds_max_x, &raw, sizeof(f32));
raw = pxl8_read_u32_be(&s);
memcpy(&bsp->bounds_max_z, &raw, sizeof(f32));
return bsp;
}
static pxl8_result assemble_bsp(pxl8_world_chunk_cache* cache, pxl8_world_chunk_assembly* a) {
pxl8_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_world_chunk_cache_entry* entry = find_entry_bsp(cache, a->id);
if (entry) {