stream world data from pxl8d to pxl8
This commit is contained in:
parent
39ee0fefb7
commit
a71a9840b2
55 changed files with 5290 additions and 2131 deletions
|
|
@ -11,7 +11,7 @@
|
|||
(local bob-amount 4.0)
|
||||
(local bob-speed 8.0)
|
||||
(local cam-smoothing 0.25)
|
||||
(local cell-size 64)
|
||||
(local chunk-size 64)
|
||||
(local cursor-sensitivity 0.010)
|
||||
(local gravity -800)
|
||||
(local grid-size 64)
|
||||
|
|
@ -32,25 +32,29 @@
|
|||
(var auto-run-cancel-key nil)
|
||||
(var bob-time 0)
|
||||
(var cam-pitch 0)
|
||||
(var cam-x 1000)
|
||||
(var cam-x 416)
|
||||
(var cam-y 64)
|
||||
(var cam-yaw 0)
|
||||
(var cam-z 1000)
|
||||
(var cam-z 416)
|
||||
(var camera nil)
|
||||
(var grounded? true)
|
||||
(var land-squash 0)
|
||||
(var light-time 0)
|
||||
(var real-time 0)
|
||||
(var network nil)
|
||||
(var smooth-cam-x 1000)
|
||||
(var smooth-cam-z 1000)
|
||||
(var velocity-y 0)
|
||||
(var world nil)
|
||||
(var ceiling-tex nil)
|
||||
(var fireball-mesh nil)
|
||||
(var floor-tex nil)
|
||||
(var fps-avg 0)
|
||||
(var fps-sample-count 0)
|
||||
(var fireball-mesh nil)
|
||||
(var grounded? true)
|
||||
(var land-squash 0)
|
||||
(var last-dt 0.016)
|
||||
(var light-time 0)
|
||||
(var lights nil)
|
||||
(var materials-setup false)
|
||||
(var network nil)
|
||||
(var real-time 0)
|
||||
(var smooth-cam-x 416)
|
||||
(var smooth-cam-z 416)
|
||||
(var velocity-y 0)
|
||||
(var wall-tex nil)
|
||||
(var world nil)
|
||||
|
||||
(local cursor-look? true)
|
||||
(local FIREBALL_COLOR 218)
|
||||
|
|
@ -247,43 +251,54 @@
|
|||
(pxl8.set_palette_rgb (+ FIREBALL_COLOR i) r g b)))
|
||||
(sky.update-gradient 1 2 6 6 10 18)
|
||||
(pxl8.update_palette_deps)
|
||||
(set camera (pxl8.create_camera_3d))
|
||||
(set world (pxl8.create_world))
|
||||
(set lights (pxl8.create_lights))
|
||||
|
||||
(when (not camera)
|
||||
(set camera (pxl8.create_camera_3d)))
|
||||
(when (not lights)
|
||||
(set lights (pxl8.create_lights)))
|
||||
(when (not fireball-mesh)
|
||||
(create-fireball-mesh))
|
||||
|
||||
(sky.generate-stars 12345)
|
||||
(create-fireball-mesh)
|
||||
|
||||
(set network (net.Net.new {:port 7777}))
|
||||
(when (not network)
|
||||
(set network (net.get))
|
||||
(when network
|
||||
(network:connect)
|
||||
(network:spawn cam-x cam-y cam-z cam-yaw cam-pitch))
|
||||
(network:spawn cam-x cam-y cam-z cam-yaw cam-pitch)))
|
||||
|
||||
(let [result (world:generate {
|
||||
:type pxl8.PROCGEN_ROOMS
|
||||
:width 64
|
||||
:height 64
|
||||
:seed 42
|
||||
:min_room_size 5
|
||||
:max_room_size 10
|
||||
:num_rooms 20})]
|
||||
(if (< result 0)
|
||||
(pxl8.error (.. "Failed to generate rooms - result: " result))
|
||||
(let [floor-tex (textures.mossy-cobblestone 44444 STONE_FLOOR_START MOSS_COLOR)
|
||||
wall-tex (textures.ashlar-wall 55555 STONE_WALL_START MOSS_COLOR)
|
||||
sky-tex (pxl8.create_texture [0] 1 1)]
|
||||
(when (not world)
|
||||
(set world (pxl8.get_world)))
|
||||
|
||||
(let [result (world:apply_textures [
|
||||
{:name "floor"
|
||||
:texture_id floor-tex
|
||||
:rule (fn [normal] (> normal.y 0.7))}
|
||||
{:name "ceiling"
|
||||
:texture_id sky-tex
|
||||
:rule (fn [normal] (< normal.y -0.7))}
|
||||
{:name "wall"
|
||||
:texture_id wall-tex
|
||||
:rule (fn [normal] (and (<= normal.y 0.7) (>= normal.y -0.7)))}])]
|
||||
(when (< result 0)
|
||||
(pxl8.error (.. "Failed to apply textures - result: " result))))))))
|
||||
(when (not floor-tex)
|
||||
(set floor-tex (textures.mossy-cobblestone 44444 STONE_FLOOR_START MOSS_COLOR)))
|
||||
(when (not wall-tex)
|
||||
(set wall-tex (textures.ashlar-wall 55555 STONE_WALL_START MOSS_COLOR)))
|
||||
(when (not ceiling-tex)
|
||||
(set ceiling-tex (pxl8.create_texture [0] 1 1)))
|
||||
|
||||
)
|
||||
|
||||
(fn setup-materials []
|
||||
(when (and world (not materials-setup))
|
||||
(let [chunk (world:active_chunk)]
|
||||
(when (and chunk (chunk:ready))
|
||||
(let [bsp (chunk:bsp)]
|
||||
(when bsp
|
||||
(let [floor-mat (pxl8.create_material {:texture floor-tex :lighting true})
|
||||
wall-mat (pxl8.create_material {:texture wall-tex :lighting true})
|
||||
ceiling-mat (pxl8.create_material {:texture ceiling-tex})]
|
||||
|
||||
(bsp:set_material 0 floor-mat)
|
||||
(bsp:set_material 1 wall-mat)
|
||||
(bsp:set_material 2 ceiling-mat)
|
||||
|
||||
(for [i 0 (- (bsp:face_count) 1)]
|
||||
(let [n (bsp:face_normal i)]
|
||||
(bsp:face_set_material i
|
||||
(if (> n.y 0.7) 0
|
||||
(< n.y -0.7) 2
|
||||
1))))
|
||||
(set materials-setup true))))))))
|
||||
|
||||
(fn sample-input []
|
||||
(var move-forward 0)
|
||||
|
|
@ -328,9 +343,10 @@
|
|||
(let [input (get-pending-input t)
|
||||
hist (get-position t)]
|
||||
(when (and input hist)
|
||||
(let [(new-x new-z) (apply-movement cam-x cam-z hist.yaw input)]
|
||||
(set cam-x new-x)
|
||||
(set cam-z new-z)
|
||||
(let [(new-x new-z) (apply-movement cam-x cam-z hist.yaw input)
|
||||
(resolved-x _ resolved-z) (world:resolve_collision cam-x cam-y cam-z new-x cam-y new-z 5)]
|
||||
(set cam-x resolved-x)
|
||||
(set cam-z resolved-z)
|
||||
(store-position t cam-x cam-z hist.yaw))))))))))
|
||||
|
||||
(fn update [dt]
|
||||
|
|
@ -343,9 +359,12 @@
|
|||
(set fps-sample-count 0)
|
||||
(set fps-avg 0)))
|
||||
|
||||
(when (world:is_loaded)
|
||||
(setup-materials)
|
||||
|
||||
(let [chunk (world:active_chunk)]
|
||||
(when (and chunk (chunk:ready))
|
||||
(let [input (sample-input)
|
||||
grid-max (* grid-size cell-size)
|
||||
grid-max (* grid-size chunk-size)
|
||||
movement-yaw cam-yaw]
|
||||
|
||||
(set time-accumulator (+ time-accumulator dt))
|
||||
|
|
@ -388,8 +407,6 @@
|
|||
:look_dy input.look_dy
|
||||
:yaw movement-yaw
|
||||
:tick client-tick})
|
||||
(network:update dt)
|
||||
(when (network:poll)
|
||||
(let [snapshot (network:snapshot)]
|
||||
(when (and snapshot (> snapshot.tick last-processed-tick))
|
||||
(set last-processed-tick snapshot.tick)
|
||||
|
|
@ -399,7 +416,7 @@
|
|||
(when curr
|
||||
(let [srv-x (pxl8.unpack_f32_be curr 0)
|
||||
srv-z (pxl8.unpack_f32_be curr 8)]
|
||||
(reconcile snapshot.tick srv-x srv-z)))))))))))]
|
||||
(reconcile snapshot.tick srv-x srv-z))))))))))]
|
||||
(when (not ok)
|
||||
(pxl8.error (.. "Network error: " err)))))
|
||||
|
||||
|
|
@ -430,7 +447,7 @@
|
|||
(set bob-time (+ (* bob-time 0.8) (* target-phase 0.2))))))
|
||||
|
||||
(set light-time (+ light-time (* dt 0.5)))
|
||||
(set real-time (+ real-time dt)))))
|
||||
(set real-time (+ real-time dt))))))
|
||||
|
||||
(fn frame []
|
||||
(pxl8.clear 1)
|
||||
|
|
@ -441,10 +458,11 @@
|
|||
(when (not world)
|
||||
(pxl8.error "world is nil!"))
|
||||
|
||||
(when (and world (not (world:is_loaded)))
|
||||
(pxl8.text "World not loaded yet..." 5 30 12))
|
||||
(let [chunk (when world (world:active_chunk))]
|
||||
(when (and world (or (not chunk) (not (chunk:ready))))
|
||||
(pxl8.text "Waiting for world data..." 5 30 12))
|
||||
|
||||
(when (and camera world (world:is_loaded))
|
||||
(when (and camera world chunk (chunk:ready))
|
||||
(let [bob-offset (* (math.sin bob-time) bob-amount)
|
||||
eye-y (+ cam-y bob-offset land-squash)
|
||||
forward-x (- (math.sin cam-yaw))
|
||||
|
|
@ -459,8 +477,8 @@
|
|||
[0 1 0])
|
||||
(camera:set_perspective 1.047 aspect 1.0 4096.0)
|
||||
|
||||
(let [light-x (+ 1000 (* 50 (math.cos light-time)))
|
||||
light-z (+ 940 (* 50 (math.sin light-time)))
|
||||
(let [light-x (+ 384 (* 50 (math.cos light-time)))
|
||||
light-z (+ 324 (* 50 (math.sin light-time)))
|
||||
light-y 80
|
||||
phase (+ (* light-x 0.01) 1.7)
|
||||
f1 (* 0.08 (math.sin (+ (* real-time 2.5) phase)))
|
||||
|
|
@ -484,7 +502,10 @@
|
|||
(sky.render smooth-cam-x eye-y smooth-cam-z (menu.is-wireframe))
|
||||
(pxl8.clear_depth)
|
||||
|
||||
(world:set_wireframe (menu.is-wireframe) 15)
|
||||
(when chunk
|
||||
(let [bsp (chunk:bsp)]
|
||||
(when bsp
|
||||
(bsp:set_wireframe (menu.is-wireframe)))))
|
||||
(world:render [smooth-cam-x eye-y smooth-cam-z])
|
||||
|
||||
(when fireball-mesh
|
||||
|
|
@ -509,7 +530,7 @@
|
|||
(pxl8.text (.. "fps: " (string.format "%.0f" fps-avg)) 5 5 text-color)
|
||||
(pxl8.text (.. "pos: " (string.format "%.0f" cam-x) ","
|
||||
(string.format "%.0f" cam-y) ","
|
||||
(string.format "%.0f" cam-z)) 5 15 text-color)))))
|
||||
(string.format "%.0f" cam-z)) 5 15 text-color))))))
|
||||
|
||||
{:init init
|
||||
:update update
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
(local NUM_RANDOM_STARS 300)
|
||||
(local NUM_TINY_STARS 7000)
|
||||
(local STAR_CYCLE_PERIOD 86400)
|
||||
(local STAR_SEED 0xDEADBEEF)
|
||||
(local STAR_CYCLE_PERIOD 7200)
|
||||
|
||||
;; Use existing bright palette colors
|
||||
;; Silver/white: indices 14-15 (brightest grays)
|
||||
|
|
@ -102,15 +102,21 @@
|
|||
(generate-sky-gradient zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b)
|
||||
(set last-gradient-key key))))
|
||||
|
||||
(fn galactic-band-factor [dx dy dz]
|
||||
(let [band-len (math.sqrt (+ (* 0.6 0.6) (* 0.3 0.3) (* 0.742 0.742)))
|
||||
bx (/ 0.6 band-len)
|
||||
by (/ 0.3 band-len)
|
||||
bz (/ 0.742 band-len)
|
||||
dist-from-band (math.abs (+ (* dx bx) (* dy by) (* dz bz)))
|
||||
in-band (- 1 (math.min (* dist-from-band 3) 1))]
|
||||
(fn band-factor [dx dy dz bx by bz width]
|
||||
(let [dist (math.abs (+ (* dx bx) (* dy by) (* dz bz)))
|
||||
in-band (- 1 (math.min (* dist width) 1))]
|
||||
(* in-band in-band)))
|
||||
|
||||
(fn galactic-band-factor [dx dy dz]
|
||||
(let [;; Main galactic band - crosses zenith
|
||||
band-len (math.sqrt (+ (* 0.6 0.6) (* 0.3 0.3) (* 0.742 0.742)))
|
||||
b1 (band-factor dx dy dz (/ 0.6 band-len) (/ 0.3 band-len) (/ 0.742 band-len) 3)
|
||||
;; Secondary band - lower angle, different orientation
|
||||
b2 (band-factor dx dy dz 0.8 0.15 0.58 3.5)
|
||||
;; Tertiary band - opposite side
|
||||
b3 (band-factor dx dy dz -0.7 0.2 0.69 4)]
|
||||
(math.max b1 b2 b3)))
|
||||
|
||||
(fn generate-stars []
|
||||
(set random-stars [])
|
||||
(set tiny-stars [])
|
||||
|
|
@ -205,8 +211,9 @@
|
|||
(let [star (. tiny-stars (+ i 1))
|
||||
int (math.floor (* star.brightness fade-in))]
|
||||
(when (> int 8)
|
||||
(star-glows:add (math.floor screen.x) (math.floor screen.y)
|
||||
1 int star.color pxl8.GLOW_CIRCLE))))))
|
||||
(let [px (math.floor (+ screen.x 0.5))
|
||||
py (math.floor (+ screen.y 0.5))]
|
||||
(star-glows:add px py 1 int star.color pxl8.GLOW_CIRCLE)))))))
|
||||
|
||||
(for [i 0 (- (length random-stars) 1)]
|
||||
(let [screen (. star-projected (+ tiny-count i))]
|
||||
|
|
@ -215,8 +222,8 @@
|
|||
phase (+ (* (+ i 1) 2.137) (* time-factor 3.0))
|
||||
twinkle (+ 0.75 (* 0.25 (math.sin (* phase 6.28))))
|
||||
int (math.floor (* star.brightness fade-in twinkle))
|
||||
sx (math.floor screen.x)
|
||||
sy (math.floor screen.y)]
|
||||
sx (math.floor (+ screen.x 0.5))
|
||||
sy (math.floor (+ screen.y 0.5))]
|
||||
(if (> star.brightness 220)
|
||||
(do
|
||||
(star-glows:add sx sy 3 (math.floor (* int 1.5)) star.color pxl8.GLOW_DIAMOND)
|
||||
|
|
|
|||
85
pxl8.sh
85
pxl8.sh
|
|
@ -58,20 +58,20 @@ build_luajit() {
|
|||
|
||||
build_server() {
|
||||
local mode="$1"
|
||||
if [[ -d "server" ]]; then
|
||||
print_info "Building server ($mode mode)"
|
||||
cd server
|
||||
if [[ -d "pxl8d" ]]; then
|
||||
print_info "Building pxl8d ($mode mode)"
|
||||
cd pxl8d
|
||||
if [[ "$mode" == "release" ]]; then
|
||||
cargo build --release
|
||||
cargo build --release --quiet
|
||||
else
|
||||
cargo build
|
||||
cargo build --quiet
|
||||
fi
|
||||
local status=$?
|
||||
cd - > /dev/null
|
||||
if [[ $status -eq 0 ]]; then
|
||||
print_info "Built server"
|
||||
print_info "Built pxl8d"
|
||||
else
|
||||
print_error "Server build failed"
|
||||
print_error "pxl8d build failed"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
|
@ -80,9 +80,9 @@ start_server() {
|
|||
local mode="$1"
|
||||
local server_bin
|
||||
if [[ "$mode" == "release" ]]; then
|
||||
server_bin="server/target/release/pxl8-server"
|
||||
server_bin="pxl8d/target/release/pxl8d"
|
||||
else
|
||||
server_bin="server/target/debug/pxl8-server"
|
||||
server_bin="pxl8d/target/debug/pxl8d"
|
||||
fi
|
||||
print_info "Server mode: $mode, binary: $server_bin"
|
||||
if [[ -f "$server_bin" ]]; then
|
||||
|
|
@ -92,8 +92,8 @@ start_server() {
|
|||
print_info "Server started with PID $SERVER_PID"
|
||||
sleep 0.5
|
||||
else
|
||||
print_error "Server binary not found: $server_bin"
|
||||
print_error "Build the server first with: cd server && cargo build"
|
||||
print_error "pxl8d binary not found: $server_bin"
|
||||
print_error "Build pxl8d first with: cd pxl8d && cargo build"
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
@ -105,6 +105,15 @@ stop_server() {
|
|||
fi
|
||||
}
|
||||
|
||||
clean_server() {
|
||||
if [[ -d "pxl8d" ]]; then
|
||||
print_info "Cleaning pxl8d"
|
||||
cd pxl8d
|
||||
cargo clean 2>/dev/null || true
|
||||
cd - > /dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
build_sdl() {
|
||||
if [[ ! -f "lib/SDL/build/libSDL3.so" ]] && [[ ! -f "lib/SDL/build/libSDL3.a" ]] && [[ ! -f "lib/SDL/build/libSDL3.dylib" ]]; then
|
||||
print_info "Building SDL3"
|
||||
|
|
@ -160,9 +169,9 @@ print_usage() {
|
|||
echo
|
||||
echo -e "${BOLD}OPTIONS:${NC}"
|
||||
echo " --all Clean both build artifacts and dependencies"
|
||||
echo " --cache Clear ccache (use with clean)"
|
||||
echo " --deps Clean only dependencies"
|
||||
echo " --release Build/run/clean in release mode (default: debug)"
|
||||
echo " --server Start game server before running (for networked games)"
|
||||
}
|
||||
|
||||
setup_sdl3() {
|
||||
|
|
@ -364,6 +373,8 @@ case "$COMMAND" in
|
|||
build_sdl
|
||||
fi
|
||||
|
||||
build_server "$MODE"
|
||||
|
||||
setup_sdl3
|
||||
print_info "Building pxl8 ($MODE mode)"
|
||||
|
||||
|
|
@ -380,16 +391,19 @@ case "$COMMAND" in
|
|||
LIB_SOURCE_FILES="lib/linenoise/linenoise.c lib/miniz/miniz.c"
|
||||
|
||||
PXL8_SOURCE_FILES="
|
||||
src/asset/pxl8_ase.c
|
||||
src/asset/pxl8_cart.c
|
||||
src/asset/pxl8_save.c
|
||||
src/core/pxl8.c
|
||||
src/core/pxl8_bytes.c
|
||||
src/core/pxl8_io.c
|
||||
src/core/pxl8_log.c
|
||||
src/core/pxl8_replay.c
|
||||
src/core/pxl8_rng.c
|
||||
src/math/pxl8_math.c
|
||||
src/gfx/pxl8_3d_camera.c
|
||||
src/gfx/pxl8_anim.c
|
||||
src/gfx/pxl8_atlas.c
|
||||
src/gfx/pxl8_blit.c
|
||||
src/gfx/pxl8_3d_camera.c
|
||||
src/gfx/pxl8_colormap.c
|
||||
src/gfx/pxl8_cpu.c
|
||||
src/gfx/pxl8_dither.c
|
||||
|
|
@ -404,22 +418,23 @@ case "$COMMAND" in
|
|||
src/gfx/pxl8_tilemap.c
|
||||
src/gfx/pxl8_tilesheet.c
|
||||
src/gfx/pxl8_transition.c
|
||||
src/sfx/pxl8_sfx.c
|
||||
src/script/pxl8_repl.c
|
||||
src/script/pxl8_script.c
|
||||
src/gui/pxl8_gui.c
|
||||
src/hal/pxl8_hal_sdl3.c
|
||||
src/hal/pxl8_mem_sdl3.c
|
||||
src/world/pxl8_bsp.c
|
||||
src/world/pxl8_gen.c
|
||||
src/world/pxl8_world.c
|
||||
src/procgen/pxl8_graph.c
|
||||
src/asset/pxl8_ase.c
|
||||
src/asset/pxl8_cart.c
|
||||
src/asset/pxl8_save.c
|
||||
src/gui/pxl8_gui.c
|
||||
src/core/pxl8_replay.c
|
||||
src/math/pxl8_math.c
|
||||
src/net/pxl8_net.c
|
||||
src/net/pxl8_protocol.c
|
||||
src/procgen/pxl8_graph.c
|
||||
src/script/pxl8_repl.c
|
||||
src/script/pxl8_script.c
|
||||
src/sfx/pxl8_sfx.c
|
||||
src/world/pxl8_bsp.c
|
||||
src/world/pxl8_chunk.c
|
||||
src/world/pxl8_chunk_cache.c
|
||||
src/world/pxl8_entity.c
|
||||
src/world/pxl8_gen.c
|
||||
src/world/pxl8_voxel.c
|
||||
src/world/pxl8_world.c
|
||||
"
|
||||
|
||||
LUAJIT_LIB="lib/luajit/src/libluajit.a"
|
||||
|
|
@ -450,7 +465,8 @@ case "$COMMAND" in
|
|||
NEEDS_REBUILD=false
|
||||
if [[ "$src_file" -nt "$obj_file" ]] || \
|
||||
[[ "src/core/pxl8_types.h" -nt "$obj_file" ]] || \
|
||||
[[ "src/core/pxl8_macros.h" -nt "$obj_file" ]]; then
|
||||
[[ "src/core/pxl8_macros.h" -nt "$obj_file" ]] || \
|
||||
[[ "src/net/pxl8_protocol.h" -nt "$obj_file" ]]; then
|
||||
NEEDS_REBUILD=true
|
||||
fi
|
||||
|
||||
|
|
@ -489,24 +505,18 @@ case "$COMMAND" in
|
|||
|
||||
CART=""
|
||||
EXTRA_ARGS=""
|
||||
RUN_SERVER=false
|
||||
for arg in "$@"; do
|
||||
if [[ "$arg" == "--release" ]]; then
|
||||
MODE="release"
|
||||
elif [[ "$arg" == "--repl" ]]; then
|
||||
EXTRA_ARGS="$EXTRA_ARGS --repl"
|
||||
elif [[ "$arg" == "--server" ]]; then
|
||||
RUN_SERVER=true
|
||||
elif [[ -z "$CART" ]]; then
|
||||
CART="$arg"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ "$RUN_SERVER" == true ]]; then
|
||||
build_server "$MODE"
|
||||
start_server "$MODE"
|
||||
trap stop_server EXIT
|
||||
fi
|
||||
|
||||
if [[ -z "$CART" ]]; then
|
||||
"$BINDIR/pxl8" $EXTRA_ARGS
|
||||
|
|
@ -517,17 +527,24 @@ case "$COMMAND" in
|
|||
|
||||
clean)
|
||||
CLEAN_ALL=false
|
||||
CLEAN_CACHE=false
|
||||
CLEAN_DEPS=false
|
||||
CLEAN_RELEASE=false
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--all) CLEAN_ALL=true ;;
|
||||
--cache) CLEAN_CACHE=true ;;
|
||||
--deps) CLEAN_DEPS=true ;;
|
||||
--release) CLEAN_RELEASE=true ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ "$CLEAN_CACHE" == true ]] && command -v ccache >/dev/null 2>&1; then
|
||||
print_info "Clearing ccache"
|
||||
ccache -C >/dev/null
|
||||
fi
|
||||
|
||||
if [[ "$CLEAN_RELEASE" == true ]]; then
|
||||
BUILD_PATH=".build/release"
|
||||
BIN_PATH="bin/release"
|
||||
|
|
@ -541,6 +558,7 @@ case "$COMMAND" in
|
|||
if [[ "$CLEAN_ALL" == true ]]; then
|
||||
print_info "Removing build artifacts and dependencies"
|
||||
rm -rf "$BUILD_PATH" "$BIN_PATH" lib
|
||||
clean_server
|
||||
print_info "Cleaned all"
|
||||
elif [[ "$CLEAN_DEPS" == true ]]; then
|
||||
print_info "Removing dependencies"
|
||||
|
|
@ -549,6 +567,7 @@ case "$COMMAND" in
|
|||
else
|
||||
print_info "Removing build artifacts"
|
||||
rm -rf "$BUILD_PATH" "$BIN_PATH"
|
||||
clean_server
|
||||
print_info "Cleaned"
|
||||
fi
|
||||
;;
|
||||
|
|
|
|||
0
server/.gitignore → pxl8d/.gitignore
vendored
0
server/.gitignore → pxl8d/.gitignore
vendored
263
pxl8d/Cargo.lock
generated
Normal file
263
pxl8d/Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.72.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.180"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pxl8d"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
"libc",
|
||||
"libm",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.114"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
|
@ -1,13 +1,16 @@
|
|||
[package]
|
||||
name = "pxl8-server"
|
||||
name = "pxl8d"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
name = "pxl8d"
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.72"
|
||||
cc = "1.2"
|
||||
|
||||
[dependencies]
|
||||
bevy_ecs = { version = "0.18", default-features = false }
|
||||
libc = { version = "0.2", default-features = false }
|
||||
libm = { version = "0.2", default-features = false }
|
||||
|
||||
|
|
@ -20,15 +23,18 @@ windows-sys = { version = "0.61", default-features = false, features = [
|
|||
] }
|
||||
|
||||
[[bin]]
|
||||
name = "pxl8-server"
|
||||
name = "pxl8d"
|
||||
path = "src/main.rs"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 2
|
||||
panic = "abort"
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
std = ["bevy_ecs/std"]
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
strip = true
|
||||
|
|
@ -5,10 +5,19 @@ fn main() {
|
|||
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
let pxl8_src = PathBuf::from(&manifest_dir).join("../src");
|
||||
|
||||
println!("cargo:rerun-if-changed=../src/net/pxl8_protocol.h");
|
||||
println!("cargo:rerun-if-changed=../src/core/pxl8_log.c");
|
||||
println!("cargo:rerun-if-changed=../src/core/pxl8_log.h");
|
||||
println!("cargo:rerun-if-changed=../src/core/pxl8_types.h");
|
||||
println!("cargo:rerun-if-changed=../src/net/pxl8_protocol.h");
|
||||
|
||||
cc::Build::new()
|
||||
.file(pxl8_src.join("core/pxl8_log.c"))
|
||||
.include(pxl8_src.join("core"))
|
||||
.define("PXL8_SERVER", None)
|
||||
.compile("pxl8_log");
|
||||
|
||||
let bindings = bindgen::Builder::default()
|
||||
.header(pxl8_src.join("core/pxl8_log.h").to_str().unwrap())
|
||||
.header(pxl8_src.join("net/pxl8_protocol.h").to_str().unwrap())
|
||||
.clang_arg(format!("-I{}", pxl8_src.join("core").display()))
|
||||
.use_core()
|
||||
173
pxl8d/src/bsp.rs
Normal file
173
pxl8d/src/bsp.rs
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
extern crate alloc;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::math::Vec3;
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct BspVertex {
|
||||
pub position: Vec3,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct BspEdge {
|
||||
pub vertex: [u16; 2],
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct BspFace {
|
||||
pub first_edge: u32,
|
||||
pub lightmap_offset: u32,
|
||||
pub num_edges: u16,
|
||||
pub plane_id: u16,
|
||||
pub side: u16,
|
||||
pub styles: [u8; 4],
|
||||
pub material_id: u16,
|
||||
pub aabb_min: Vec3,
|
||||
pub aabb_max: Vec3,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct BspPlane {
|
||||
pub normal: Vec3,
|
||||
pub dist: f32,
|
||||
pub plane_type: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct BspNode {
|
||||
pub children: [i32; 2],
|
||||
pub first_face: u16,
|
||||
pub maxs: [i16; 3],
|
||||
pub mins: [i16; 3],
|
||||
pub num_faces: u16,
|
||||
pub plane_id: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct BspLeaf {
|
||||
pub ambient_level: [u8; 4],
|
||||
pub contents: i32,
|
||||
pub first_marksurface: u16,
|
||||
pub maxs: [i16; 3],
|
||||
pub mins: [i16; 3],
|
||||
pub num_marksurfaces: u16,
|
||||
pub visofs: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct BspPortal {
|
||||
pub x0: f32,
|
||||
pub z0: f32,
|
||||
pub x1: f32,
|
||||
pub z1: f32,
|
||||
pub target_leaf: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct BspCellPortals {
|
||||
pub portals: [BspPortal; 4],
|
||||
pub num_portals: u8,
|
||||
}
|
||||
|
||||
pub struct Bsp {
|
||||
pub vertices: Vec<BspVertex>,
|
||||
pub edges: Vec<BspEdge>,
|
||||
pub surfedges: Vec<i32>,
|
||||
pub planes: Vec<BspPlane>,
|
||||
pub faces: Vec<BspFace>,
|
||||
pub nodes: Vec<BspNode>,
|
||||
pub leafs: Vec<BspLeaf>,
|
||||
pub marksurfaces: Vec<u16>,
|
||||
pub cell_portals: Vec<BspCellPortals>,
|
||||
pub visdata: Vec<u8>,
|
||||
pub vertex_lights: Vec<u32>,
|
||||
}
|
||||
|
||||
impl Bsp {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
vertices: Vec::new(),
|
||||
edges: Vec::new(),
|
||||
surfedges: Vec::new(),
|
||||
planes: Vec::new(),
|
||||
faces: Vec::new(),
|
||||
nodes: Vec::new(),
|
||||
leafs: Vec::new(),
|
||||
marksurfaces: Vec::new(),
|
||||
cell_portals: Vec::new(),
|
||||
visdata: Vec::new(),
|
||||
vertex_lights: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_leaf(&self, pos: Vec3) -> i32 {
|
||||
if self.nodes.is_empty() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
let mut node_id: i32 = 0;
|
||||
|
||||
while node_id >= 0 {
|
||||
let node = &self.nodes[node_id as usize];
|
||||
let plane = &self.planes[node.plane_id as usize];
|
||||
|
||||
let dist = pos.dot(plane.normal) - plane.dist;
|
||||
node_id = node.children[if dist < 0.0 { 1 } else { 0 }];
|
||||
}
|
||||
|
||||
-(node_id + 1)
|
||||
}
|
||||
|
||||
pub fn point_solid(&self, pos: Vec3) -> bool {
|
||||
let leaf = self.find_leaf(pos);
|
||||
if leaf < 0 || leaf as usize >= self.leafs.len() {
|
||||
return true;
|
||||
}
|
||||
self.leafs[leaf as usize].contents == -1
|
||||
}
|
||||
|
||||
fn point_clear(&self, x: f32, y: f32, z: f32, radius: f32) -> bool {
|
||||
if self.point_solid(Vec3::new(x, y, z)) { return false; }
|
||||
if self.point_solid(Vec3::new(x + radius, y, z)) { return false; }
|
||||
if self.point_solid(Vec3::new(x - radius, y, z)) { return false; }
|
||||
if self.point_solid(Vec3::new(x, y, z + radius)) { return false; }
|
||||
if self.point_solid(Vec3::new(x, y, z - radius)) { return false; }
|
||||
true
|
||||
}
|
||||
|
||||
pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 {
|
||||
if self.nodes.is_empty() {
|
||||
return to;
|
||||
}
|
||||
|
||||
if self.point_clear(to.x, to.y, to.z, radius) {
|
||||
return to;
|
||||
}
|
||||
|
||||
let x_ok = self.point_clear(to.x, from.y, from.z, radius);
|
||||
let z_ok = self.point_clear(from.x, from.y, to.z, radius);
|
||||
|
||||
if x_ok && z_ok {
|
||||
let dx = to.x - from.x;
|
||||
let dz = to.z - from.z;
|
||||
if dx * dx > dz * dz {
|
||||
Vec3::new(to.x, from.y, from.z)
|
||||
} else {
|
||||
Vec3::new(from.x, from.y, to.z)
|
||||
}
|
||||
} else if x_ok {
|
||||
Vec3::new(to.x, from.y, from.z)
|
||||
} else if z_ok {
|
||||
Vec3::new(from.x, from.y, to.z)
|
||||
} else {
|
||||
from
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Bsp {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
55
pxl8d/src/chunk.rs
Normal file
55
pxl8d/src/chunk.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
extern crate alloc;
|
||||
|
||||
pub mod stream;
|
||||
|
||||
use crate::bsp::Bsp;
|
||||
use crate::math::Vec3;
|
||||
use crate::voxel::VoxelChunk;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
pub enum ChunkId {
|
||||
Bsp(u32),
|
||||
Vxl(i32, i32, i32),
|
||||
}
|
||||
|
||||
pub enum Chunk {
|
||||
Bsp { id: u32, bsp: Bsp, version: u32 },
|
||||
Vxl { cx: i32, cy: i32, cz: i32, data: VoxelChunk, version: u32 },
|
||||
}
|
||||
|
||||
impl Chunk {
|
||||
pub fn id(&self) -> ChunkId {
|
||||
match self {
|
||||
Chunk::Bsp { id, .. } => ChunkId::Bsp(*id),
|
||||
Chunk::Vxl { cx, cy, cz, .. } => ChunkId::Vxl(*cx, *cy, *cz),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn version(&self) -> u32 {
|
||||
match self {
|
||||
Chunk::Bsp { version, .. } => *version,
|
||||
Chunk::Vxl { version, .. } => *version,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 {
|
||||
match self {
|
||||
Chunk::Bsp { bsp, .. } => bsp.trace(from, to, radius),
|
||||
Chunk::Vxl { data, .. } => data.trace(from, to, radius),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_bsp(&self) -> Option<&Bsp> {
|
||||
match self {
|
||||
Chunk::Bsp { bsp, .. } => Some(bsp),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_vxl(&self) -> Option<&VoxelChunk> {
|
||||
match self {
|
||||
Chunk::Vxl { data, .. } => Some(data),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
86
pxl8d/src/chunk/stream.rs
Normal file
86
pxl8d/src/chunk/stream.rs
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
extern crate alloc;
|
||||
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::chunk::ChunkId;
|
||||
use crate::math::Vec3;
|
||||
use crate::transport::ChunkMessage;
|
||||
use crate::voxel::VoxelWorld;
|
||||
|
||||
pub struct ClientChunkState {
|
||||
known: BTreeMap<ChunkId, u32>,
|
||||
pending: Vec<ChunkId>,
|
||||
pending_messages: Vec<ChunkMessage>,
|
||||
}
|
||||
|
||||
impl ClientChunkState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
known: BTreeMap::new(),
|
||||
pending: Vec::new(),
|
||||
pending_messages: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request(&mut self, id: ChunkId) {
|
||||
if !self.known.contains_key(&id) && !self.pending.contains(&id) {
|
||||
self.pending.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_vxl_radius(&mut self, pos: Vec3, radius: i32, world: &VoxelWorld) {
|
||||
let cx = VoxelWorld::world_to_chunk(pos.x);
|
||||
let cy = VoxelWorld::world_to_chunk(pos.y);
|
||||
let cz = VoxelWorld::world_to_chunk(pos.z);
|
||||
|
||||
for dz in -radius..=radius {
|
||||
for dy in -radius..=radius {
|
||||
for dx in -radius..=radius {
|
||||
if world.get_chunk(cx + dx, cy + dy, cz + dz).is_some() {
|
||||
self.request(ChunkId::Vxl(cx + dx, cy + dy, cz + dz));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_pending(&mut self) -> Option<ChunkId> {
|
||||
self.pending.pop()
|
||||
}
|
||||
|
||||
pub fn queue_message(&mut self, msg: ChunkMessage) {
|
||||
self.pending_messages.push(msg);
|
||||
}
|
||||
|
||||
pub fn queue_messages(&mut self, msgs: Vec<ChunkMessage>) {
|
||||
self.pending_messages.extend(msgs);
|
||||
}
|
||||
|
||||
pub fn next_message(&mut self) -> Option<ChunkMessage> {
|
||||
if self.pending_messages.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(self.pending_messages.remove(0))
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ClientChunkState {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
40
pxl8d/src/lib.rs
Normal file
40
pxl8d/src/lib.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#![no_std]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
mod allocator;
|
||||
pub mod bsp;
|
||||
pub mod chunk;
|
||||
pub mod log;
|
||||
pub mod math;
|
||||
pub mod procgen;
|
||||
pub mod sim;
|
||||
pub mod transport;
|
||||
pub mod voxel;
|
||||
pub mod world;
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
#[global_allocator]
|
||||
static ALLOCATOR: allocator::Allocator = allocator::Allocator;
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[allow(dead_code, non_camel_case_types, non_snake_case, non_upper_case_globals)]
|
||||
pub mod protocol {
|
||||
include!(concat!(env!("OUT_DIR"), "/protocol.rs"));
|
||||
}
|
||||
|
||||
pub use bsp::*;
|
||||
pub use chunk::*;
|
||||
pub use chunk::stream::*;
|
||||
pub use math::*;
|
||||
pub use procgen::{ProcgenParams, generate, generate_rooms};
|
||||
pub use protocol::*;
|
||||
pub use sim::*;
|
||||
pub use transport::*;
|
||||
pub use voxel::*;
|
||||
pub use world::*;
|
||||
139
pxl8d/src/log.rs
Normal file
139
pxl8d/src/log.rs
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
use crate::protocol::{pxl8_log, pxl8_log_init};
|
||||
use core::ffi::c_char;
|
||||
|
||||
static mut G_LOG: pxl8_log = pxl8_log {
|
||||
handler: None,
|
||||
level: crate::protocol::pxl8_log_level::PXL8_LOG_LEVEL_DEBUG,
|
||||
};
|
||||
|
||||
pub fn init() {
|
||||
unsafe {
|
||||
pxl8_log_init(&raw mut G_LOG);
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! pxl8_debug {
|
||||
($($arg:tt)*) => {{
|
||||
use ::core::fmt::Write;
|
||||
let mut buf = $crate::log::LogBuffer::new();
|
||||
let _ = ::core::write!(&mut buf, $($arg)*);
|
||||
buf.push(0);
|
||||
unsafe {
|
||||
$crate::protocol::pxl8_log_write_debug(
|
||||
concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char,
|
||||
line!() as ::core::ffi::c_int,
|
||||
c"%s".as_ptr() as *const ::core::ffi::c_char,
|
||||
buf.as_ptr(),
|
||||
);
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! pxl8_error {
|
||||
($($arg:tt)*) => {{
|
||||
use ::core::fmt::Write;
|
||||
let mut buf = $crate::log::LogBuffer::new();
|
||||
let _ = ::core::write!(&mut buf, $($arg)*);
|
||||
buf.push(0);
|
||||
unsafe {
|
||||
$crate::protocol::pxl8_log_write_error(
|
||||
concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char,
|
||||
line!() as ::core::ffi::c_int,
|
||||
c"%s".as_ptr() as *const ::core::ffi::c_char,
|
||||
buf.as_ptr(),
|
||||
);
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! pxl8_info {
|
||||
($($arg:tt)*) => {{
|
||||
use ::core::fmt::Write;
|
||||
let mut buf = $crate::log::LogBuffer::new();
|
||||
let _ = ::core::write!(&mut buf, $($arg)*);
|
||||
buf.push(0);
|
||||
unsafe {
|
||||
$crate::protocol::pxl8_log_write_info(
|
||||
c"%s".as_ptr() as *const ::core::ffi::c_char,
|
||||
buf.as_ptr(),
|
||||
);
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! pxl8_trace {
|
||||
($($arg:tt)*) => {{
|
||||
use ::core::fmt::Write;
|
||||
let mut buf = $crate::log::LogBuffer::new();
|
||||
let _ = ::core::write!(&mut buf, $($arg)*);
|
||||
buf.push(0);
|
||||
unsafe {
|
||||
$crate::protocol::pxl8_log_write_trace(
|
||||
concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char,
|
||||
line!() as ::core::ffi::c_int,
|
||||
c"%s".as_ptr() as *const ::core::ffi::c_char,
|
||||
buf.as_ptr(),
|
||||
);
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! pxl8_warn {
|
||||
($($arg:tt)*) => {{
|
||||
use ::core::fmt::Write;
|
||||
let mut buf = $crate::log::LogBuffer::new();
|
||||
let _ = ::core::write!(&mut buf, $($arg)*);
|
||||
buf.push(0);
|
||||
unsafe {
|
||||
$crate::protocol::pxl8_log_write_warn(
|
||||
concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char,
|
||||
line!() as ::core::ffi::c_int,
|
||||
c"%s".as_ptr() as *const ::core::ffi::c_char,
|
||||
buf.as_ptr(),
|
||||
);
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
pub struct LogBuffer {
|
||||
buf: [u8; 1024],
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl LogBuffer {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
buf: [0; 1024],
|
||||
pos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_ptr(&self) -> *const c_char {
|
||||
self.buf.as_ptr() as *const c_char
|
||||
}
|
||||
|
||||
pub fn push(&mut self, b: u8) {
|
||||
if self.pos < self.buf.len() {
|
||||
self.buf[self.pos] = b;
|
||||
self.pos += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Write for LogBuffer {
|
||||
fn write_str(&mut self, s: &str) -> core::fmt::Result {
|
||||
for b in s.bytes() {
|
||||
if self.pos >= self.buf.len() - 1 {
|
||||
break;
|
||||
}
|
||||
self.buf[self.pos] = b;
|
||||
self.pos += 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
325
pxl8d/src/main.rs
Normal file
325
pxl8d/src/main.rs
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use pxl8d::*;
|
||||
use pxl8d::chunk::ChunkId;
|
||||
use pxl8d::chunk::stream::ClientChunkState;
|
||||
|
||||
const TICK_RATE: u64 = 30;
|
||||
const TICK_NS: u64 = 1_000_000_000 / TICK_RATE;
|
||||
|
||||
#[cfg(unix)]
|
||||
fn get_time_ns() -> u64 {
|
||||
let mut ts = libc::timespec { tv_sec: 0, tv_nsec: 0 };
|
||||
unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts) };
|
||||
(ts.tv_sec as u64) * 1_000_000_000 + (ts.tv_nsec as u64)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn get_time_ns() -> u64 {
|
||||
use windows_sys::Win32::System::Performance::*;
|
||||
static mut FREQ: i64 = 0;
|
||||
unsafe {
|
||||
if FREQ == 0 {
|
||||
QueryPerformanceFrequency(&mut FREQ);
|
||||
}
|
||||
let mut count: i64 = 0;
|
||||
QueryPerformanceCounter(&mut count);
|
||||
((count as u128 * 1_000_000_000) / FREQ as u128) as u64
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn sleep_ms(ms: u64) {
|
||||
let ts = libc::timespec {
|
||||
tv_sec: (ms / 1000) as i64,
|
||||
tv_nsec: ((ms % 1000) * 1_000_000) as i64,
|
||||
};
|
||||
unsafe { libc::nanosleep(&ts, core::ptr::null_mut()) };
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn sleep_ms(ms: u64) {
|
||||
use windows_sys::Win32::System::Threading::Sleep;
|
||||
unsafe { Sleep(ms as u32) };
|
||||
}
|
||||
|
||||
fn extract_spawn_position(payload: &[u8]) -> (f32, f32, f32, f32, f32) {
|
||||
let x = f32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
||||
let y = f32::from_be_bytes([payload[4], payload[5], payload[6], payload[7]]);
|
||||
let z = f32::from_be_bytes([payload[8], payload[9], payload[10], payload[11]]);
|
||||
let yaw = f32::from_be_bytes([payload[12], payload[13], payload[14], payload[15]]);
|
||||
let pitch = f32::from_be_bytes([payload[16], payload[17], payload[18], payload[19]]);
|
||||
(x, y, z, yaw, pitch)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
|
||||
log::init();
|
||||
let mut transport = match transport::Transport::bind(transport::DEFAULT_PORT) {
|
||||
Some(t) => {
|
||||
pxl8_info!("[SERVER] Bound to port 7777");
|
||||
t
|
||||
}
|
||||
None => {
|
||||
pxl8_error!("[SERVER] Failed to bind to port 7777");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
let mut sim = Simulation::new();
|
||||
let mut player_id: Option<u64> = None;
|
||||
let mut last_client_tick: u64 = 0;
|
||||
let mut client_chunks = ClientChunkState::new();
|
||||
|
||||
let mut sequence: u32 = 0;
|
||||
let mut last_tick = get_time_ns();
|
||||
let mut entities_buf = [protocol::pxl8_entity_state {
|
||||
entity_id: 0,
|
||||
userdata: [0u8; 56],
|
||||
}; 64];
|
||||
let mut inputs_buf: [protocol::pxl8_input_msg; 16] = unsafe { core::mem::zeroed() };
|
||||
|
||||
pxl8_debug!("[SERVER] Entering main loop");
|
||||
loop {
|
||||
let now = get_time_ns();
|
||||
let elapsed = now.saturating_sub(last_tick);
|
||||
|
||||
if elapsed >= TICK_NS {
|
||||
last_tick = now;
|
||||
let dt = (elapsed as f32) / 1_000_000_000.0;
|
||||
|
||||
let mut latest_input: Option<protocol::pxl8_input_msg> = None;
|
||||
while let Some(msg_type) = transport.recv() {
|
||||
match msg_type {
|
||||
x if x == protocol::pxl8_msg_type::PXL8_MSG_INPUT as u8 => {
|
||||
latest_input = Some(transport.get_input());
|
||||
}
|
||||
x if x == protocol::pxl8_msg_type::PXL8_MSG_COMMAND as u8 => {
|
||||
let cmd = transport.get_command();
|
||||
if cmd.cmd_type == protocol::pxl8_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);
|
||||
|
||||
pxl8_debug!("[SERVER] Spawn command received, generating BSP...");
|
||||
sim.world.generate_bsp(1, None);
|
||||
sim.world.set_active(ChunkId::Bsp(1));
|
||||
client_chunks.request(ChunkId::Bsp(1));
|
||||
pxl8_debug!("[SERVER] Sending CHUNK_ENTER for BSP id=1");
|
||||
transport.send_chunk_enter(1, transport::CHUNK_TYPE_BSP, sequence);
|
||||
sequence = sequence.wrapping_add(1);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(input) = latest_input {
|
||||
last_client_tick = input.tick;
|
||||
inputs_buf[0] = input;
|
||||
sim.step(&inputs_buf[..1], dt);
|
||||
} else {
|
||||
sim.step(&[], dt);
|
||||
}
|
||||
|
||||
let mut count = 0;
|
||||
sim.generate_snapshot(player_id.unwrap_or(u64::MAX), |state| {
|
||||
if count < entities_buf.len() {
|
||||
entities_buf[count] = *state;
|
||||
count += 1;
|
||||
}
|
||||
});
|
||||
|
||||
let header = protocol::pxl8_snapshot_header {
|
||||
entity_count: count as u16,
|
||||
event_count: 0,
|
||||
player_id: player_id.unwrap_or(0),
|
||||
tick: last_client_tick,
|
||||
time: sim.time,
|
||||
};
|
||||
|
||||
transport.send_snapshot(&header, &entities_buf[..count], sequence);
|
||||
sequence = sequence.wrapping_add(1);
|
||||
|
||||
if let Some(pid) = player_id {
|
||||
if let Some(player) = sim.get_player_position(pid) {
|
||||
let pos = Vec3 { x: player.0, y: player.1, z: player.2 };
|
||||
|
||||
if sim.world.active().is_some() {
|
||||
while let Some(chunk_id) = client_chunks.next_pending() {
|
||||
match chunk_id {
|
||||
ChunkId::Bsp(id) => {
|
||||
pxl8_debug!("[SERVER] Processing pending BSP chunk");
|
||||
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());
|
||||
pxl8_debug!("[SERVER] BSP serialized, queueing messages");
|
||||
client_chunks.queue_messages(msgs);
|
||||
client_chunks.mark_sent(chunk_id, chunk.version());
|
||||
}
|
||||
}
|
||||
}
|
||||
ChunkId::Vxl(cx, cy, cz) => {
|
||||
if let Some(chunk) = sim.voxels.get_chunk(cx, cy, cz) {
|
||||
let msgs = transport::ChunkMessage::from_voxel(chunk, 1);
|
||||
client_chunks.queue_messages(msgs);
|
||||
client_chunks.mark_sent(chunk_id, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _ in 0..4 {
|
||||
if let Some(msg) = client_chunks.next_message() {
|
||||
transport.send_chunk(&msg, sequence);
|
||||
sequence = sequence.wrapping_add(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
client_chunks.request_vxl_radius(pos, 2, &sim.voxels);
|
||||
|
||||
for _ in 0..2 {
|
||||
if let Some(chunk_id) = client_chunks.next_pending() {
|
||||
if let ChunkId::Vxl(cx, cy, cz) = chunk_id {
|
||||
if let Some(chunk) = sim.voxels.get_chunk(cx, cy, cz) {
|
||||
let msgs = transport::ChunkMessage::from_voxel(chunk, 1);
|
||||
for msg in &msgs {
|
||||
transport.send_chunk(msg, sequence);
|
||||
sequence = sequence.wrapping_add(1);
|
||||
}
|
||||
client_chunks.mark_sent(chunk_id, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sleep_ms(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn bsp_to_messages(bsp: &bsp::Bsp, id: u32, version: u32) -> alloc::vec::Vec<transport::ChunkMessage> {
|
||||
use alloc::vec::Vec;
|
||||
|
||||
pxl8_debug!("[SERVER] bsp_to_messages: serializing BSP");
|
||||
|
||||
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.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());
|
||||
|
||||
for v in &bsp.vertices {
|
||||
data.extend_from_slice(&v.position.x.to_be_bytes());
|
||||
data.extend_from_slice(&v.position.y.to_be_bytes());
|
||||
data.extend_from_slice(&v.position.z.to_be_bytes());
|
||||
}
|
||||
|
||||
for e in &bsp.edges {
|
||||
data.extend_from_slice(&e.vertex[0].to_be_bytes());
|
||||
data.extend_from_slice(&e.vertex[1].to_be_bytes());
|
||||
}
|
||||
|
||||
for se in &bsp.surfedges {
|
||||
data.extend_from_slice(&se.to_be_bytes());
|
||||
}
|
||||
|
||||
for p in &bsp.planes {
|
||||
data.extend_from_slice(&p.normal.x.to_be_bytes());
|
||||
data.extend_from_slice(&p.normal.y.to_be_bytes());
|
||||
data.extend_from_slice(&p.normal.z.to_be_bytes());
|
||||
data.extend_from_slice(&p.dist.to_be_bytes());
|
||||
data.extend_from_slice(&p.plane_type.to_be_bytes());
|
||||
}
|
||||
|
||||
for f in &bsp.faces {
|
||||
data.extend_from_slice(&f.first_edge.to_be_bytes());
|
||||
data.extend_from_slice(&f.lightmap_offset.to_be_bytes());
|
||||
data.extend_from_slice(&f.num_edges.to_be_bytes());
|
||||
data.extend_from_slice(&f.plane_id.to_be_bytes());
|
||||
data.extend_from_slice(&f.side.to_be_bytes());
|
||||
data.extend_from_slice(&f.styles);
|
||||
data.extend_from_slice(&f.material_id.to_be_bytes());
|
||||
data.extend_from_slice(&f.aabb_min.x.to_be_bytes());
|
||||
data.extend_from_slice(&f.aabb_min.y.to_be_bytes());
|
||||
data.extend_from_slice(&f.aabb_min.z.to_be_bytes());
|
||||
data.extend_from_slice(&f.aabb_max.x.to_be_bytes());
|
||||
data.extend_from_slice(&f.aabb_max.y.to_be_bytes());
|
||||
data.extend_from_slice(&f.aabb_max.z.to_be_bytes());
|
||||
}
|
||||
|
||||
for n in &bsp.nodes {
|
||||
data.extend_from_slice(&n.children[0].to_be_bytes());
|
||||
data.extend_from_slice(&n.children[1].to_be_bytes());
|
||||
data.extend_from_slice(&n.first_face.to_be_bytes());
|
||||
data.extend_from_slice(&n.maxs[0].to_be_bytes());
|
||||
data.extend_from_slice(&n.maxs[1].to_be_bytes());
|
||||
data.extend_from_slice(&n.maxs[2].to_be_bytes());
|
||||
data.extend_from_slice(&n.mins[0].to_be_bytes());
|
||||
data.extend_from_slice(&n.mins[1].to_be_bytes());
|
||||
data.extend_from_slice(&n.mins[2].to_be_bytes());
|
||||
data.extend_from_slice(&n.num_faces.to_be_bytes());
|
||||
data.extend_from_slice(&n.plane_id.to_be_bytes());
|
||||
}
|
||||
|
||||
for l in &bsp.leafs {
|
||||
data.extend_from_slice(&l.ambient_level);
|
||||
data.extend_from_slice(&l.contents.to_be_bytes());
|
||||
data.extend_from_slice(&l.first_marksurface.to_be_bytes());
|
||||
data.extend_from_slice(&l.maxs[0].to_be_bytes());
|
||||
data.extend_from_slice(&l.maxs[1].to_be_bytes());
|
||||
data.extend_from_slice(&l.maxs[2].to_be_bytes());
|
||||
data.extend_from_slice(&l.mins[0].to_be_bytes());
|
||||
data.extend_from_slice(&l.mins[1].to_be_bytes());
|
||||
data.extend_from_slice(&l.mins[2].to_be_bytes());
|
||||
data.extend_from_slice(&l.num_marksurfaces.to_be_bytes());
|
||||
data.extend_from_slice(&l.visofs.to_be_bytes());
|
||||
}
|
||||
|
||||
for ms in &bsp.marksurfaces {
|
||||
data.extend_from_slice(&ms.to_be_bytes());
|
||||
}
|
||||
|
||||
for cp in &bsp.cell_portals {
|
||||
data.push(cp.num_portals);
|
||||
data.push(0);
|
||||
data.push(0);
|
||||
data.push(0);
|
||||
for i in 0..4 {
|
||||
data.extend_from_slice(&cp.portals[i].x0.to_be_bytes());
|
||||
data.extend_from_slice(&cp.portals[i].z0.to_be_bytes());
|
||||
data.extend_from_slice(&cp.portals[i].x1.to_be_bytes());
|
||||
data.extend_from_slice(&cp.portals[i].z1.to_be_bytes());
|
||||
data.extend_from_slice(&cp.portals[i].target_leaf.to_be_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
data.extend_from_slice(&bsp.visdata);
|
||||
|
||||
for vl in &bsp.vertex_lights {
|
||||
data.extend_from_slice(&vl.to_be_bytes());
|
||||
}
|
||||
|
||||
transport::ChunkMessage::from_bsp(data, id, version)
|
||||
}
|
||||
42
pxl8d/src/math.rs
Normal file
42
pxl8d/src/math.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
use core::ops::{Add, Mul, Sub};
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct Vec3 {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub z: f32,
|
||||
}
|
||||
|
||||
impl Vec3 {
|
||||
pub const ZERO: Self = Self { x: 0.0, y: 0.0, z: 0.0 };
|
||||
pub const Y: Self = Self { x: 0.0, y: 1.0, z: 0.0 };
|
||||
|
||||
pub const fn new(x: f32, y: f32, z: f32) -> Self {
|
||||
Self { x, y, z }
|
||||
}
|
||||
|
||||
pub fn dot(self, rhs: Self) -> f32 {
|
||||
self.x * rhs.x + self.y * rhs.y + self.z * rhs.z
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Vec3 {
|
||||
type Output = Self;
|
||||
fn add(self, rhs: Self) -> Self {
|
||||
Self::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Vec3 {
|
||||
type Output = Self;
|
||||
fn sub(self, rhs: Self) -> Self {
|
||||
Self::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f32> for Vec3 {
|
||||
type Output = Self;
|
||||
fn mul(self, rhs: f32) -> Self {
|
||||
Self::new(self.x * rhs, self.y * rhs, self.z * rhs)
|
||||
}
|
||||
}
|
||||
806
pxl8d/src/procgen.rs
Normal file
806
pxl8d/src/procgen.rs
Normal file
|
|
@ -0,0 +1,806 @@
|
|||
extern crate alloc;
|
||||
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use libm::sqrtf;
|
||||
|
||||
use crate::bsp::{Bsp, BspVertex, BspEdge, BspFace, BspPlane, BspNode, BspLeaf, BspPortal, BspCellPortals};
|
||||
use crate::math::Vec3;
|
||||
|
||||
pub const CELL_SIZE: f32 = 64.0;
|
||||
pub const WALL_HEIGHT: f32 = 128.0;
|
||||
pub const PVS_MAX_DEPTH: u32 = 64;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct LightSource {
|
||||
pub position: Vec3,
|
||||
pub intensity: f32,
|
||||
pub radius: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ProcgenParams {
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
pub seed: u32,
|
||||
pub min_room_size: i32,
|
||||
pub max_room_size: i32,
|
||||
pub num_rooms: i32,
|
||||
}
|
||||
|
||||
impl Default for ProcgenParams {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
width: 16,
|
||||
height: 16,
|
||||
seed: 12345,
|
||||
min_room_size: 3,
|
||||
max_room_size: 6,
|
||||
num_rooms: 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RoomGrid {
|
||||
cells: Vec<u8>,
|
||||
width: i32,
|
||||
height: i32,
|
||||
}
|
||||
|
||||
impl RoomGrid {
|
||||
fn new(width: i32, height: i32) -> Self {
|
||||
Self {
|
||||
cells: vec![0; (width * height) as usize],
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&self, x: i32, y: i32) -> u8 {
|
||||
if x < 0 || x >= self.width || y < 0 || y >= self.height {
|
||||
return 1;
|
||||
}
|
||||
self.cells[(y * self.width + x) as usize]
|
||||
}
|
||||
|
||||
fn set(&mut self, x: i32, y: i32, value: u8) {
|
||||
if x >= 0 && x < self.width && y >= 0 && y < self.height {
|
||||
self.cells[(y * self.width + x) as usize] = value;
|
||||
}
|
||||
}
|
||||
|
||||
fn fill(&mut self, value: u8) {
|
||||
for cell in &mut self.cells {
|
||||
*cell = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Rng {
|
||||
state: u32,
|
||||
}
|
||||
|
||||
impl Rng {
|
||||
fn new(seed: u32) -> Self {
|
||||
Self { state: seed }
|
||||
}
|
||||
|
||||
fn next(&mut self) -> u32 {
|
||||
self.state = self.state.wrapping_mul(1103515245).wrapping_add(12345);
|
||||
(self.state >> 16) & 0x7FFF
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Bounds {
|
||||
x: i32,
|
||||
y: i32,
|
||||
w: i32,
|
||||
h: i32,
|
||||
}
|
||||
|
||||
impl Bounds {
|
||||
fn intersects(&self, other: &Bounds) -> bool {
|
||||
!(self.x + self.w <= other.x || other.x + other.w <= self.x ||
|
||||
self.y + self.h <= other.y || other.y + other.h <= self.y)
|
||||
}
|
||||
}
|
||||
|
||||
struct BspBuildContext<'a> {
|
||||
bsp: &'a mut Bsp,
|
||||
grid: &'a RoomGrid,
|
||||
node_count: u32,
|
||||
plane_offset: u32,
|
||||
}
|
||||
|
||||
fn carve_corridor_h(grid: &mut RoomGrid, x1: i32, x2: i32, y: i32) {
|
||||
let start = x1.min(x2);
|
||||
let end = x1.max(x2);
|
||||
for x in start..=end {
|
||||
grid.set(x, y, 0);
|
||||
grid.set(x, y - 1, 0);
|
||||
grid.set(x, y + 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn carve_corridor_v(grid: &mut RoomGrid, y1: i32, y2: i32, x: i32) {
|
||||
let start = y1.min(y2);
|
||||
let end = y1.max(y2);
|
||||
for y in start..=end {
|
||||
grid.set(x, y, 0);
|
||||
grid.set(x - 1, y, 0);
|
||||
grid.set(x + 1, y, 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_face_aabb(face: &mut BspFace, verts: &[BspVertex], vert_idx: usize) {
|
||||
face.aabb_min = Vec3::new(1e30, 1e30, 1e30);
|
||||
face.aabb_max = Vec3::new(-1e30, -1e30, -1e30);
|
||||
|
||||
for i in 0..4 {
|
||||
let v = verts[vert_idx + i].position;
|
||||
if v.x < face.aabb_min.x { face.aabb_min.x = v.x; }
|
||||
if v.x > face.aabb_max.x { face.aabb_max.x = v.x; }
|
||||
if v.y < face.aabb_min.y { face.aabb_min.y = v.y; }
|
||||
if v.y > face.aabb_max.y { face.aabb_max.y = v.y; }
|
||||
if v.z < face.aabb_min.z { face.aabb_min.z = v.z; }
|
||||
if v.z > face.aabb_max.z { face.aabb_max.z = v.z; }
|
||||
}
|
||||
}
|
||||
|
||||
fn build_bsp_node(ctx: &mut BspBuildContext, x0: i32, y0: i32, x1: i32, y1: i32, depth: i32) -> i32 {
|
||||
if x1 - x0 == 1 && y1 - y0 == 1 {
|
||||
let leaf_idx = y0 * ctx.grid.width + x0;
|
||||
return -(leaf_idx + 1);
|
||||
}
|
||||
|
||||
let node_idx = ctx.node_count;
|
||||
ctx.node_count += 1;
|
||||
|
||||
let plane_idx = ctx.plane_offset;
|
||||
ctx.plane_offset += 1;
|
||||
|
||||
if depth % 2 == 0 {
|
||||
let mid_x = (x0 + x1) / 2;
|
||||
let split_pos = mid_x as f32 * CELL_SIZE;
|
||||
|
||||
ctx.bsp.planes[plane_idx as usize] = BspPlane {
|
||||
normal: Vec3::new(1.0, 0.0, 0.0),
|
||||
dist: split_pos,
|
||||
plane_type: 0,
|
||||
};
|
||||
|
||||
let child0 = build_bsp_node(ctx, mid_x, y0, x1, y1, depth + 1);
|
||||
let child1 = build_bsp_node(ctx, x0, y0, mid_x, y1, depth + 1);
|
||||
|
||||
ctx.bsp.nodes[node_idx as usize] = BspNode {
|
||||
plane_id: plane_idx,
|
||||
children: [child0, child1],
|
||||
..Default::default()
|
||||
};
|
||||
} else {
|
||||
let mid_y = (y0 + y1) / 2;
|
||||
let split_pos = mid_y as f32 * CELL_SIZE;
|
||||
|
||||
ctx.bsp.planes[plane_idx as usize] = BspPlane {
|
||||
normal: Vec3::new(0.0, 0.0, 1.0),
|
||||
dist: split_pos,
|
||||
plane_type: 0,
|
||||
};
|
||||
|
||||
let child0 = build_bsp_node(ctx, x0, mid_y, x1, y1, depth + 1);
|
||||
let child1 = build_bsp_node(ctx, x0, y0, x1, mid_y, depth + 1);
|
||||
|
||||
ctx.bsp.nodes[node_idx as usize] = BspNode {
|
||||
plane_id: plane_idx,
|
||||
children: [child0, child1],
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
|
||||
node_idx as i32
|
||||
}
|
||||
|
||||
fn build_cell_portals(grid: &RoomGrid) -> Vec<BspCellPortals> {
|
||||
let total_cells = (grid.width * grid.height) as usize;
|
||||
let mut portals = vec![BspCellPortals::default(); total_cells];
|
||||
|
||||
for y in 0..grid.height {
|
||||
for x in 0..grid.width {
|
||||
if grid.get(x, y) != 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let c = (y * grid.width + x) as usize;
|
||||
let cx = x as f32 * CELL_SIZE;
|
||||
let cz = y as f32 * CELL_SIZE;
|
||||
|
||||
if x > 0 && grid.get(x - 1, y) == 0 {
|
||||
let p = &mut portals[c];
|
||||
let idx = p.num_portals as usize;
|
||||
p.portals[idx] = BspPortal {
|
||||
x0: cx,
|
||||
z0: cz,
|
||||
x1: cx,
|
||||
z1: cz + CELL_SIZE,
|
||||
target_leaf: (y * grid.width + (x - 1)) as u32,
|
||||
};
|
||||
p.num_portals += 1;
|
||||
}
|
||||
if x < grid.width - 1 && grid.get(x + 1, y) == 0 {
|
||||
let p = &mut portals[c];
|
||||
let idx = p.num_portals as usize;
|
||||
p.portals[idx] = BspPortal {
|
||||
x0: cx + CELL_SIZE,
|
||||
z0: cz + CELL_SIZE,
|
||||
x1: cx + CELL_SIZE,
|
||||
z1: cz,
|
||||
target_leaf: (y * grid.width + (x + 1)) as u32,
|
||||
};
|
||||
p.num_portals += 1;
|
||||
}
|
||||
if y > 0 && grid.get(x, y - 1) == 0 {
|
||||
let p = &mut portals[c];
|
||||
let idx = p.num_portals as usize;
|
||||
p.portals[idx] = BspPortal {
|
||||
x0: cx + CELL_SIZE,
|
||||
z0: cz,
|
||||
x1: cx,
|
||||
z1: cz,
|
||||
target_leaf: ((y - 1) * grid.width + x) as u32,
|
||||
};
|
||||
p.num_portals += 1;
|
||||
}
|
||||
if y < grid.height - 1 && grid.get(x, y + 1) == 0 {
|
||||
let p = &mut portals[c];
|
||||
let idx = p.num_portals as usize;
|
||||
p.portals[idx] = BspPortal {
|
||||
x0: cx,
|
||||
z0: cz + CELL_SIZE,
|
||||
x1: cx + CELL_SIZE,
|
||||
z1: cz + CELL_SIZE,
|
||||
target_leaf: ((y + 1) * grid.width + x) as u32,
|
||||
};
|
||||
p.num_portals += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
portals
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct FloodEntry {
|
||||
leaf: u32,
|
||||
depth: u32,
|
||||
}
|
||||
|
||||
fn portal_flood_bfs(
|
||||
start_leaf: u32,
|
||||
portals: &[BspCellPortals],
|
||||
leafs: &[BspLeaf],
|
||||
pvs: &mut [u8],
|
||||
num_leafs: u32,
|
||||
) {
|
||||
let pvs_bytes = ((num_leafs + 7) / 8) as usize;
|
||||
let mut visited = vec![0u8; pvs_bytes];
|
||||
let mut queue = Vec::with_capacity(num_leafs as usize);
|
||||
|
||||
pvs[(start_leaf >> 3) as usize] |= 1 << (start_leaf & 7);
|
||||
visited[(start_leaf >> 3) as usize] |= 1 << (start_leaf & 7);
|
||||
queue.push(FloodEntry { leaf: start_leaf, depth: 0 });
|
||||
|
||||
let mut head = 0;
|
||||
while head < queue.len() {
|
||||
let e = queue[head];
|
||||
head += 1;
|
||||
|
||||
if e.depth >= PVS_MAX_DEPTH {
|
||||
continue;
|
||||
}
|
||||
if leafs[e.leaf as usize].contents == -1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let cp = &portals[e.leaf as usize];
|
||||
for i in 0..cp.num_portals {
|
||||
let target = cp.portals[i as usize].target_leaf;
|
||||
let byte = (target >> 3) as usize;
|
||||
let bit = target & 7;
|
||||
|
||||
if visited[byte] & (1 << bit) != 0 {
|
||||
continue;
|
||||
}
|
||||
visited[byte] |= 1 << bit;
|
||||
|
||||
if leafs[target as usize].contents == -1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
pvs[byte] |= 1 << bit;
|
||||
queue.push(FloodEntry { leaf: target, depth: e.depth + 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_leaf_pvs(
|
||||
start_leaf: u32,
|
||||
portals: &[BspCellPortals],
|
||||
leafs: &[BspLeaf],
|
||||
num_leafs: u32,
|
||||
) -> Vec<u8> {
|
||||
let pvs_bytes = ((num_leafs + 7) / 8) as usize;
|
||||
let mut pvs = vec![0u8; pvs_bytes];
|
||||
portal_flood_bfs(start_leaf, portals, leafs, &mut pvs, num_leafs);
|
||||
pvs
|
||||
}
|
||||
|
||||
fn rle_compress_pvs(pvs: &[u8]) -> Vec<u8> {
|
||||
let mut out = Vec::new();
|
||||
let mut i = 0;
|
||||
|
||||
while i < pvs.len() {
|
||||
if pvs[i] != 0 {
|
||||
out.push(pvs[i]);
|
||||
i += 1;
|
||||
} else {
|
||||
let mut count = 0u8;
|
||||
while i < pvs.len() && pvs[i] == 0 && count < 255 {
|
||||
count += 1;
|
||||
i += 1;
|
||||
}
|
||||
out.push(0);
|
||||
out.push(count);
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
fn build_pvs_data(bsp: &mut Bsp, portals: &[BspCellPortals]) {
|
||||
let num_leafs = bsp.leafs.len() as u32;
|
||||
let mut visdata = Vec::new();
|
||||
|
||||
for leaf in 0..num_leafs {
|
||||
if bsp.leafs[leaf as usize].contents == -1 {
|
||||
bsp.leafs[leaf as usize].visofs = -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
let pvs = compute_leaf_pvs(leaf, portals, &bsp.leafs, num_leafs);
|
||||
let compressed = rle_compress_pvs(&pvs);
|
||||
|
||||
bsp.leafs[leaf as usize].visofs = visdata.len() as i32;
|
||||
visdata.extend_from_slice(&compressed);
|
||||
}
|
||||
|
||||
bsp.visdata = visdata;
|
||||
}
|
||||
|
||||
fn compute_vertex_light(
|
||||
pos: Vec3,
|
||||
normal: Vec3,
|
||||
lights: &[LightSource],
|
||||
ambient: f32,
|
||||
) -> f32 {
|
||||
let mut total = ambient;
|
||||
|
||||
for light in lights {
|
||||
let to_light = Vec3::new(
|
||||
light.position.x - pos.x,
|
||||
light.position.y - pos.y,
|
||||
light.position.z - pos.z,
|
||||
);
|
||||
|
||||
let dist_sq = to_light.x * to_light.x + to_light.y * to_light.y + to_light.z * to_light.z;
|
||||
let dist = sqrtf(dist_sq).max(1.0);
|
||||
|
||||
if dist > light.radius {
|
||||
continue;
|
||||
}
|
||||
|
||||
let inv_dist = 1.0 / dist;
|
||||
let light_dir = Vec3::new(
|
||||
to_light.x * inv_dist,
|
||||
to_light.y * inv_dist,
|
||||
to_light.z * inv_dist,
|
||||
);
|
||||
|
||||
let ndotl = (normal.x * light_dir.x + normal.y * light_dir.y + normal.z * light_dir.z).max(0.0);
|
||||
|
||||
let attenuation = (1.0 - dist / light.radius).max(0.0);
|
||||
let attenuation = attenuation * attenuation;
|
||||
|
||||
total += light.intensity * ndotl * attenuation;
|
||||
}
|
||||
|
||||
total.min(1.0)
|
||||
}
|
||||
|
||||
fn compute_bsp_vertex_lighting(bsp: &mut Bsp, lights: &[LightSource], ambient: f32) {
|
||||
if bsp.vertices.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
bsp.vertex_lights = vec![0u32; bsp.vertices.len()];
|
||||
|
||||
for f in 0..bsp.faces.len() {
|
||||
let face = &bsp.faces[f];
|
||||
let normal = if (face.plane_id as usize) < bsp.planes.len() {
|
||||
bsp.planes[face.plane_id as usize].normal
|
||||
} else {
|
||||
Vec3::new(0.0, 1.0, 0.0)
|
||||
};
|
||||
|
||||
for e in 0..face.num_edges {
|
||||
let surfedge_idx = (face.first_edge + e as u32) as usize;
|
||||
if surfedge_idx >= bsp.surfedges.len() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let edge_idx = bsp.surfedges[surfedge_idx];
|
||||
let vert_idx = if edge_idx >= 0 {
|
||||
let ei = edge_idx as usize;
|
||||
if ei >= bsp.edges.len() { continue; }
|
||||
bsp.edges[ei].vertex[0] as usize
|
||||
} else {
|
||||
let ei = (-edge_idx) as usize;
|
||||
if ei >= bsp.edges.len() { continue; }
|
||||
bsp.edges[ei].vertex[1] as usize
|
||||
};
|
||||
|
||||
if vert_idx >= bsp.vertices.len() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let pos = bsp.vertices[vert_idx].position;
|
||||
let light = compute_vertex_light(pos, normal, lights, ambient);
|
||||
|
||||
let light_byte = (light * 255.0) as u8;
|
||||
bsp.vertex_lights[vert_idx] = ((light_byte as u32) << 24) | 0x00FFFFFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) {
|
||||
let mut face_count = 0;
|
||||
let mut floor_ceiling_count = 0;
|
||||
|
||||
for y in 0..grid.height {
|
||||
for x in 0..grid.width {
|
||||
if grid.get(x, y) == 0 {
|
||||
if grid.get(x - 1, y) == 1 { face_count += 1; }
|
||||
if grid.get(x + 1, y) == 1 { face_count += 1; }
|
||||
if grid.get(x, y - 1) == 1 { face_count += 1; }
|
||||
if grid.get(x, y + 1) == 1 { face_count += 1; }
|
||||
floor_ceiling_count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
face_count += floor_ceiling_count;
|
||||
let vertex_count = face_count * 4;
|
||||
|
||||
let total_cells = (grid.width * grid.height) as usize;
|
||||
let max_nodes = 2 * total_cells;
|
||||
let total_planes = face_count + max_nodes;
|
||||
|
||||
bsp.vertices = vec![BspVertex::default(); vertex_count];
|
||||
bsp.faces = vec![BspFace::default(); face_count];
|
||||
bsp.planes = vec![BspPlane::default(); total_planes];
|
||||
bsp.edges = vec![BspEdge::default(); vertex_count];
|
||||
bsp.surfedges = vec![0i32; vertex_count];
|
||||
bsp.nodes = vec![BspNode::default(); max_nodes];
|
||||
|
||||
let mut face_cell = vec![0u32; face_count];
|
||||
|
||||
let mut vert_idx = 0usize;
|
||||
let mut face_idx = 0usize;
|
||||
let mut edge_idx = 0usize;
|
||||
|
||||
for y in 0..grid.height {
|
||||
for x in 0..grid.width {
|
||||
if grid.get(x, y) == 0 {
|
||||
let fx = x as f32 * CELL_SIZE;
|
||||
let fy = y as f32 * CELL_SIZE;
|
||||
let cell_idx = (y * grid.width + x) as u32;
|
||||
|
||||
if grid.get(x - 1, y) == 1 {
|
||||
bsp.vertices[vert_idx + 0].position = Vec3::new(fx, 0.0, fy);
|
||||
bsp.vertices[vert_idx + 1].position = Vec3::new(fx, WALL_HEIGHT, fy);
|
||||
bsp.vertices[vert_idx + 2].position = Vec3::new(fx, WALL_HEIGHT, fy + CELL_SIZE);
|
||||
bsp.vertices[vert_idx + 3].position = Vec3::new(fx, 0.0, fy + CELL_SIZE);
|
||||
|
||||
bsp.planes[face_idx].normal = Vec3::new(1.0, 0.0, 0.0);
|
||||
bsp.planes[face_idx].dist = fx;
|
||||
|
||||
bsp.faces[face_idx].plane_id = face_idx as u16;
|
||||
bsp.faces[face_idx].num_edges = 4;
|
||||
bsp.faces[face_idx].first_edge = edge_idx as u32;
|
||||
bsp.faces[face_idx].material_id = 0;
|
||||
|
||||
for i in 0..4 {
|
||||
bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16;
|
||||
bsp.edges[edge_idx + i].vertex[1] = (vert_idx + ((i + 1) % 4)) as u16;
|
||||
bsp.surfedges[edge_idx + i] = (edge_idx + i) as i32;
|
||||
}
|
||||
|
||||
compute_face_aabb(&mut bsp.faces[face_idx], &bsp.vertices, vert_idx);
|
||||
face_cell[face_idx] = cell_idx;
|
||||
|
||||
vert_idx += 4;
|
||||
edge_idx += 4;
|
||||
face_idx += 1;
|
||||
}
|
||||
|
||||
if grid.get(x + 1, y) == 1 {
|
||||
bsp.vertices[vert_idx + 0].position = Vec3::new(fx + CELL_SIZE, 0.0, fy);
|
||||
bsp.vertices[vert_idx + 1].position = Vec3::new(fx + CELL_SIZE, 0.0, fy + CELL_SIZE);
|
||||
bsp.vertices[vert_idx + 2].position = Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fy + CELL_SIZE);
|
||||
bsp.vertices[vert_idx + 3].position = Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fy);
|
||||
|
||||
bsp.planes[face_idx].normal = Vec3::new(-1.0, 0.0, 0.0);
|
||||
bsp.planes[face_idx].dist = -(fx + CELL_SIZE);
|
||||
|
||||
bsp.faces[face_idx].plane_id = face_idx as u16;
|
||||
bsp.faces[face_idx].num_edges = 4;
|
||||
bsp.faces[face_idx].first_edge = edge_idx as u32;
|
||||
bsp.faces[face_idx].material_id = 0;
|
||||
|
||||
for i in 0..4 {
|
||||
bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16;
|
||||
bsp.edges[edge_idx + i].vertex[1] = (vert_idx + ((i + 1) % 4)) as u16;
|
||||
bsp.surfedges[edge_idx + i] = (edge_idx + i) as i32;
|
||||
}
|
||||
|
||||
compute_face_aabb(&mut bsp.faces[face_idx], &bsp.vertices, vert_idx);
|
||||
face_cell[face_idx] = cell_idx;
|
||||
|
||||
vert_idx += 4;
|
||||
edge_idx += 4;
|
||||
face_idx += 1;
|
||||
}
|
||||
|
||||
if grid.get(x, y - 1) == 1 {
|
||||
bsp.vertices[vert_idx + 0].position = Vec3::new(fx, 0.0, fy);
|
||||
bsp.vertices[vert_idx + 1].position = Vec3::new(fx + CELL_SIZE, 0.0, fy);
|
||||
bsp.vertices[vert_idx + 2].position = Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fy);
|
||||
bsp.vertices[vert_idx + 3].position = Vec3::new(fx, WALL_HEIGHT, fy);
|
||||
|
||||
bsp.planes[face_idx].normal = Vec3::new(0.0, 0.0, 1.0);
|
||||
bsp.planes[face_idx].dist = fy;
|
||||
|
||||
bsp.faces[face_idx].plane_id = face_idx as u16;
|
||||
bsp.faces[face_idx].num_edges = 4;
|
||||
bsp.faces[face_idx].first_edge = edge_idx as u32;
|
||||
bsp.faces[face_idx].material_id = 0;
|
||||
|
||||
for i in 0..4 {
|
||||
bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16;
|
||||
bsp.edges[edge_idx + i].vertex[1] = (vert_idx + ((i + 1) % 4)) as u16;
|
||||
bsp.surfedges[edge_idx + i] = (edge_idx + i) as i32;
|
||||
}
|
||||
|
||||
compute_face_aabb(&mut bsp.faces[face_idx], &bsp.vertices, vert_idx);
|
||||
face_cell[face_idx] = cell_idx;
|
||||
|
||||
vert_idx += 4;
|
||||
edge_idx += 4;
|
||||
face_idx += 1;
|
||||
}
|
||||
|
||||
if grid.get(x, y + 1) == 1 {
|
||||
bsp.vertices[vert_idx + 0].position = Vec3::new(fx, 0.0, fy + CELL_SIZE);
|
||||
bsp.vertices[vert_idx + 1].position = Vec3::new(fx, WALL_HEIGHT, fy + CELL_SIZE);
|
||||
bsp.vertices[vert_idx + 2].position = Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fy + CELL_SIZE);
|
||||
bsp.vertices[vert_idx + 3].position = Vec3::new(fx + CELL_SIZE, 0.0, fy + CELL_SIZE);
|
||||
|
||||
bsp.planes[face_idx].normal = Vec3::new(0.0, 0.0, -1.0);
|
||||
bsp.planes[face_idx].dist = -(fy + CELL_SIZE);
|
||||
|
||||
bsp.faces[face_idx].plane_id = face_idx as u16;
|
||||
bsp.faces[face_idx].num_edges = 4;
|
||||
bsp.faces[face_idx].first_edge = edge_idx as u32;
|
||||
bsp.faces[face_idx].material_id = 0;
|
||||
|
||||
for i in 0..4 {
|
||||
bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16;
|
||||
bsp.edges[edge_idx + i].vertex[1] = (vert_idx + ((i + 1) % 4)) as u16;
|
||||
bsp.surfedges[edge_idx + i] = (edge_idx + i) as i32;
|
||||
}
|
||||
|
||||
compute_face_aabb(&mut bsp.faces[face_idx], &bsp.vertices, vert_idx);
|
||||
face_cell[face_idx] = cell_idx;
|
||||
|
||||
vert_idx += 4;
|
||||
edge_idx += 4;
|
||||
face_idx += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for y in 0..grid.height {
|
||||
for x in 0..grid.width {
|
||||
if grid.get(x, y) == 0 {
|
||||
let fx = x as f32 * CELL_SIZE;
|
||||
let fy = y as f32 * CELL_SIZE;
|
||||
let cell_idx = (y * grid.width + x) as u32;
|
||||
|
||||
bsp.vertices[vert_idx + 0].position = Vec3::new(fx, 0.0, fy);
|
||||
bsp.vertices[vert_idx + 1].position = Vec3::new(fx, 0.0, fy + CELL_SIZE);
|
||||
bsp.vertices[vert_idx + 2].position = Vec3::new(fx + CELL_SIZE, 0.0, fy + CELL_SIZE);
|
||||
bsp.vertices[vert_idx + 3].position = Vec3::new(fx + CELL_SIZE, 0.0, fy);
|
||||
|
||||
bsp.planes[face_idx].normal = Vec3::new(0.0, 1.0, 0.0);
|
||||
bsp.planes[face_idx].dist = 0.0;
|
||||
|
||||
bsp.faces[face_idx].plane_id = face_idx as u16;
|
||||
bsp.faces[face_idx].num_edges = 4;
|
||||
bsp.faces[face_idx].first_edge = edge_idx as u32;
|
||||
bsp.faces[face_idx].material_id = 0;
|
||||
|
||||
for i in 0..4 {
|
||||
bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16;
|
||||
bsp.edges[edge_idx + i].vertex[1] = (vert_idx + ((i + 1) % 4)) as u16;
|
||||
bsp.surfedges[edge_idx + i] = (edge_idx + i) as i32;
|
||||
}
|
||||
|
||||
compute_face_aabb(&mut bsp.faces[face_idx], &bsp.vertices, vert_idx);
|
||||
face_cell[face_idx] = cell_idx;
|
||||
|
||||
vert_idx += 4;
|
||||
edge_idx += 4;
|
||||
face_idx += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bsp.vertices.truncate(vert_idx);
|
||||
bsp.faces.truncate(face_idx);
|
||||
bsp.edges.truncate(edge_idx);
|
||||
bsp.surfedges.truncate(edge_idx);
|
||||
|
||||
bsp.leafs = vec![BspLeaf::default(); total_cells];
|
||||
bsp.marksurfaces = vec![0u16; face_idx];
|
||||
|
||||
let mut faces_per_cell = vec![0u32; total_cells];
|
||||
let mut cell_offset = vec![0u32; total_cells];
|
||||
let mut cell_cursor = vec![0u32; total_cells];
|
||||
|
||||
for i in 0..face_idx {
|
||||
faces_per_cell[face_cell[i] as usize] += 1;
|
||||
}
|
||||
|
||||
let mut offset = 0u32;
|
||||
for c in 0..total_cells {
|
||||
cell_offset[c] = offset;
|
||||
offset += faces_per_cell[c];
|
||||
}
|
||||
|
||||
for i in 0..face_idx {
|
||||
let c = face_cell[i] as usize;
|
||||
bsp.marksurfaces[(cell_offset[c] + cell_cursor[c]) as usize] = i as u16;
|
||||
cell_cursor[c] += 1;
|
||||
}
|
||||
|
||||
for y in 0..grid.height {
|
||||
for x in 0..grid.width {
|
||||
let c = (y * grid.width + x) as usize;
|
||||
let leaf = &mut bsp.leafs[c];
|
||||
|
||||
let fx = x as f32 * CELL_SIZE;
|
||||
let fz = y as f32 * CELL_SIZE;
|
||||
|
||||
leaf.mins[0] = fx as i16;
|
||||
leaf.mins[1] = 0;
|
||||
leaf.mins[2] = fz as i16;
|
||||
leaf.maxs[0] = (fx + CELL_SIZE) as i16;
|
||||
leaf.maxs[1] = WALL_HEIGHT as i16;
|
||||
leaf.maxs[2] = (fz + CELL_SIZE) as i16;
|
||||
|
||||
if grid.get(x, y) == 0 {
|
||||
leaf.contents = -2;
|
||||
leaf.first_marksurface = cell_offset[c] as u16;
|
||||
leaf.num_marksurfaces = faces_per_cell[c] as u16;
|
||||
} else {
|
||||
leaf.contents = -1;
|
||||
leaf.first_marksurface = 0;
|
||||
leaf.num_marksurfaces = 0;
|
||||
}
|
||||
leaf.visofs = -1;
|
||||
}
|
||||
}
|
||||
|
||||
let face_count_final = face_idx;
|
||||
let mut ctx = BspBuildContext {
|
||||
bsp,
|
||||
grid,
|
||||
node_count: 0,
|
||||
plane_offset: face_count_final as u32,
|
||||
};
|
||||
|
||||
build_bsp_node(&mut ctx, 0, 0, grid.width, grid.height, 0);
|
||||
|
||||
let node_count = ctx.node_count as usize;
|
||||
let plane_count = ctx.plane_offset as usize;
|
||||
ctx.bsp.nodes.truncate(node_count);
|
||||
ctx.bsp.planes.truncate(plane_count);
|
||||
|
||||
let portals = build_cell_portals(grid);
|
||||
build_pvs_data(ctx.bsp, &portals);
|
||||
ctx.bsp.cell_portals = portals;
|
||||
}
|
||||
|
||||
pub fn generate_rooms(params: &ProcgenParams) -> Bsp {
|
||||
let mut rng = Rng::new(params.seed);
|
||||
|
||||
let mut grid = RoomGrid::new(params.width, params.height);
|
||||
grid.fill(1);
|
||||
|
||||
let mut rooms: Vec<Bounds> = Vec::new();
|
||||
let max_attempts = params.num_rooms * 10;
|
||||
|
||||
for _ in 0..max_attempts {
|
||||
if rooms.len() >= params.num_rooms as usize {
|
||||
break;
|
||||
}
|
||||
|
||||
let w = params.min_room_size + (rng.next() % (params.max_room_size - params.min_room_size + 1) as u32) as i32;
|
||||
let h = params.min_room_size + (rng.next() % (params.max_room_size - params.min_room_size + 1) as u32) as i32;
|
||||
let x = 1 + (rng.next() % (params.width - w - 2) as u32) as i32;
|
||||
let y = 1 + (rng.next() % (params.height - h - 2) as u32) as i32;
|
||||
|
||||
let new_room = Bounds { x, y, w, h };
|
||||
|
||||
let overlaps = rooms.iter().any(|r| new_room.intersects(r));
|
||||
|
||||
if !overlaps {
|
||||
for ry in y..(y + h) {
|
||||
for rx in x..(x + w) {
|
||||
grid.set(rx, ry, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if !rooms.is_empty() {
|
||||
let prev = rooms.last().unwrap();
|
||||
let new_cx = x + w / 2;
|
||||
let new_cy = y + h / 2;
|
||||
let prev_cx = prev.x + prev.w / 2;
|
||||
let prev_cy = prev.y + prev.h / 2;
|
||||
|
||||
if rng.next() % 2 == 0 {
|
||||
carve_corridor_h(&mut grid, prev_cx, new_cx, prev_cy);
|
||||
carve_corridor_v(&mut grid, prev_cy, new_cy, new_cx);
|
||||
} else {
|
||||
carve_corridor_v(&mut grid, prev_cy, new_cy, prev_cx);
|
||||
carve_corridor_h(&mut grid, prev_cx, new_cx, new_cy);
|
||||
}
|
||||
}
|
||||
|
||||
rooms.push(new_room);
|
||||
}
|
||||
}
|
||||
|
||||
let mut bsp = Bsp::new();
|
||||
grid_to_bsp(&mut bsp, &grid);
|
||||
|
||||
let light_height = 80.0;
|
||||
let lights: Vec<LightSource> = rooms.iter().map(|room| {
|
||||
let cx = (room.x as f32 + room.w as f32 / 2.0) * CELL_SIZE;
|
||||
let cy = (room.y as f32 + room.h as f32 / 2.0) * CELL_SIZE;
|
||||
LightSource {
|
||||
position: Vec3::new(cx, light_height, cy),
|
||||
intensity: 0.8,
|
||||
radius: 300.0,
|
||||
}
|
||||
}).collect();
|
||||
|
||||
compute_bsp_vertex_lighting(&mut bsp, &lights, 0.1);
|
||||
|
||||
bsp
|
||||
}
|
||||
|
||||
pub fn generate(params: &ProcgenParams) -> Bsp {
|
||||
generate_rooms(params)
|
||||
}
|
||||
277
pxl8d/src/sim.rs
Normal file
277
pxl8d/src/sim.rs
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
extern crate alloc;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use libm::{cosf, sinf, sqrtf};
|
||||
|
||||
use crate::math::Vec3;
|
||||
use crate::protocol::*;
|
||||
use crate::voxel::VoxelWorld;
|
||||
use crate::world::World;
|
||||
|
||||
const ALIVE: u32 = 1 << 0;
|
||||
const PLAYER: u32 = 1 << 1;
|
||||
const GROUNDED: u32 = 1 << 2;
|
||||
|
||||
const MAX_ENTITIES: usize = 1024;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Entity {
|
||||
pub flags: u32,
|
||||
pub kind: u16,
|
||||
pub pos: Vec3,
|
||||
pub vel: Vec3,
|
||||
pub yaw: f32,
|
||||
pub pitch: f32,
|
||||
}
|
||||
|
||||
impl Default for Entity {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
flags: 0,
|
||||
kind: 0,
|
||||
pos: Vec3::ZERO,
|
||||
vel: Vec3::ZERO,
|
||||
yaw: 0.0,
|
||||
pitch: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Simulation {
|
||||
pub entities: Vec<Entity>,
|
||||
pub free_list: Vec<u32>,
|
||||
pub player: Option<u32>,
|
||||
pub tick: u64,
|
||||
pub time: f32,
|
||||
pub voxels: VoxelWorld,
|
||||
pub world: World,
|
||||
}
|
||||
|
||||
impl Simulation {
|
||||
pub fn new() -> Self {
|
||||
let mut entities = Vec::with_capacity(MAX_ENTITIES);
|
||||
entities.resize(MAX_ENTITIES, Entity::default());
|
||||
|
||||
let mut free_list = Vec::with_capacity(MAX_ENTITIES);
|
||||
for i in (0..MAX_ENTITIES).rev() {
|
||||
free_list.push(i as u32);
|
||||
}
|
||||
|
||||
Self {
|
||||
entities,
|
||||
free_list,
|
||||
player: None,
|
||||
tick: 0,
|
||||
time: 0.0,
|
||||
voxels: VoxelWorld::new(12345),
|
||||
world: World::new(12345),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn(&mut self) -> Option<u32> {
|
||||
let idx = self.free_list.pop()?;
|
||||
self.entities[idx as usize] = Entity {
|
||||
flags: ALIVE,
|
||||
..Default::default()
|
||||
};
|
||||
Some(idx)
|
||||
}
|
||||
|
||||
pub fn despawn(&mut self, id: u32) {
|
||||
if (id as usize) < self.entities.len() {
|
||||
self.entities[id as usize].flags = 0;
|
||||
self.free_list.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn_player(&mut self, x: f32, y: f32, z: f32) -> u32 {
|
||||
let id = self.spawn().unwrap_or(0);
|
||||
let ent = &mut self.entities[id as usize];
|
||||
ent.flags = ALIVE | PLAYER;
|
||||
ent.pos = Vec3::new(x, y, z);
|
||||
self.player = Some(id);
|
||||
|
||||
self.voxels.load_chunks_around(ent.pos, 2);
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
pub fn step(&mut self, inputs: &[pxl8_input_msg], dt: f32) {
|
||||
self.tick += 1;
|
||||
self.time += dt;
|
||||
|
||||
for input in inputs {
|
||||
self.move_player(input, dt);
|
||||
}
|
||||
|
||||
self.integrate(dt);
|
||||
}
|
||||
|
||||
fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 {
|
||||
if self.world.active().is_some() {
|
||||
return self.world.trace(from, to, radius);
|
||||
}
|
||||
self.voxels.trace(from, to, radius)
|
||||
}
|
||||
|
||||
fn integrate(&mut self, dt: f32) {
|
||||
const GRAVITY: f32 = 800.0;
|
||||
const FRICTION: f32 = 6.0;
|
||||
const RADIUS: f32 = 16.0;
|
||||
|
||||
for i in 0..self.entities.len() {
|
||||
let ent = &self.entities[i];
|
||||
if ent.flags & ALIVE == 0 || ent.flags & PLAYER != 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut vel = ent.vel;
|
||||
let pos = ent.pos;
|
||||
let flags = ent.flags;
|
||||
|
||||
vel.y = vel.y - GRAVITY * dt;
|
||||
|
||||
if flags & GROUNDED != 0 {
|
||||
let speed = sqrtf(vel.x * vel.x + vel.z * vel.z);
|
||||
if speed > 0.0 {
|
||||
let drop = speed * FRICTION * dt;
|
||||
let scale = (speed - drop).max(0.0) / speed;
|
||||
vel.x = vel.x * scale;
|
||||
vel.z = vel.z * scale;
|
||||
}
|
||||
}
|
||||
|
||||
let target = pos + vel * dt;
|
||||
let new_pos = self.trace(pos, target, RADIUS);
|
||||
|
||||
let ent = &mut self.entities[i];
|
||||
ent.vel = vel;
|
||||
ent.pos = new_pos;
|
||||
}
|
||||
}
|
||||
|
||||
fn move_player(&mut self, input: &pxl8_input_msg, dt: f32) {
|
||||
let Some(id) = self.player else { return };
|
||||
let ent = &mut self.entities[id as usize];
|
||||
if ent.flags & ALIVE == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
const MOVE_SPEED: f32 = 320.0;
|
||||
const GROUND_ACCEL: f32 = 10.0;
|
||||
const AIR_ACCEL: f32 = 1.0;
|
||||
const STOP_SPEED: f32 = 100.0;
|
||||
const FRICTION: f32 = 6.0;
|
||||
const RADIUS: f32 = 16.0;
|
||||
|
||||
let sin_yaw = sinf(input.yaw);
|
||||
let cos_yaw = cosf(input.yaw);
|
||||
|
||||
let input_len = sqrtf(input.move_x * input.move_x + input.move_y * input.move_y);
|
||||
let (move_dir, target_speed) = if input_len > 0.0 {
|
||||
let nx = input.move_x / input_len;
|
||||
let ny = input.move_y / input_len;
|
||||
let dir = Vec3::new(
|
||||
cos_yaw * nx - sin_yaw * ny,
|
||||
0.0,
|
||||
-sin_yaw * nx - cos_yaw * ny,
|
||||
);
|
||||
(dir, MOVE_SPEED)
|
||||
} else {
|
||||
(Vec3::ZERO, 0.0)
|
||||
};
|
||||
|
||||
let grounded = ent.flags & GROUNDED != 0;
|
||||
|
||||
if grounded {
|
||||
let speed = sqrtf(ent.vel.x * ent.vel.x + ent.vel.z * ent.vel.z);
|
||||
if speed > 0.0 {
|
||||
let control = speed.max(STOP_SPEED);
|
||||
let drop = control * FRICTION * dt;
|
||||
let scale = (speed - drop).max(0.0) / speed;
|
||||
ent.vel.x *= scale;
|
||||
ent.vel.z *= scale;
|
||||
}
|
||||
}
|
||||
|
||||
if target_speed > 0.0 {
|
||||
let accel = if grounded { GROUND_ACCEL } else { AIR_ACCEL };
|
||||
let current = ent.vel.x * move_dir.x + ent.vel.z * move_dir.z;
|
||||
let add = target_speed - current;
|
||||
if add > 0.0 {
|
||||
let amount = (accel * target_speed * dt).min(add);
|
||||
ent.vel.x += move_dir.x * amount;
|
||||
ent.vel.z += move_dir.z * amount;
|
||||
}
|
||||
}
|
||||
|
||||
ent.yaw = input.yaw;
|
||||
ent.pitch = (ent.pitch - input.look_dy * 0.008).clamp(-1.5, 1.5);
|
||||
|
||||
let pos = ent.pos;
|
||||
let vel = ent.vel;
|
||||
let target = pos + vel * dt;
|
||||
let new_pos = self.trace(pos, target, RADIUS);
|
||||
let ground_check = self.trace(new_pos, new_pos - Vec3::Y * 2.0, RADIUS);
|
||||
|
||||
let ent = &mut self.entities[id as usize];
|
||||
ent.pos = new_pos;
|
||||
|
||||
if ground_check.y < new_pos.y - 1.0 {
|
||||
ent.flags &= !GROUNDED;
|
||||
} else {
|
||||
ent.flags |= GROUNDED;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_snapshot<F>(&self, _player_id: u64, mut writer: F)
|
||||
where
|
||||
F: FnMut(&pxl8_entity_state),
|
||||
{
|
||||
for (i, ent) in self.entities.iter().enumerate() {
|
||||
if ent.flags & ALIVE == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut state = pxl8_entity_state {
|
||||
entity_id: i as u64,
|
||||
userdata: [0u8; 56],
|
||||
};
|
||||
|
||||
let bytes = &mut state.userdata;
|
||||
bytes[0..4].copy_from_slice(&ent.pos.x.to_be_bytes());
|
||||
bytes[4..8].copy_from_slice(&ent.pos.y.to_be_bytes());
|
||||
bytes[8..12].copy_from_slice(&ent.pos.z.to_be_bytes());
|
||||
bytes[12..16].copy_from_slice(&ent.yaw.to_be_bytes());
|
||||
bytes[16..20].copy_from_slice(&ent.pitch.to_be_bytes());
|
||||
bytes[20..24].copy_from_slice(&ent.vel.x.to_be_bytes());
|
||||
bytes[24..28].copy_from_slice(&ent.vel.y.to_be_bytes());
|
||||
bytes[28..32].copy_from_slice(&ent.vel.z.to_be_bytes());
|
||||
bytes[32..36].copy_from_slice(&ent.flags.to_be_bytes());
|
||||
bytes[36..38].copy_from_slice(&ent.kind.to_be_bytes());
|
||||
|
||||
writer(&state);
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
let ent = &self.entities[idx];
|
||||
if ent.flags & ALIVE != 0 && ent.flags & PLAYER != 0 {
|
||||
return Some((ent.pos.x, ent.pos.y, ent.pos.z));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Simulation {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,120 @@
|
|||
extern crate alloc;
|
||||
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use crate::protocol::*;
|
||||
use crate::protocol::pxl8_msg_type::*;
|
||||
use crate::voxel::VoxelChunk;
|
||||
|
||||
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_VXL: u8 = 0;
|
||||
pub const CHUNK_TYPE_BSP: u8 = 1;
|
||||
|
||||
pub struct ChunkMessage {
|
||||
pub chunk_type: u8,
|
||||
pub id: u32,
|
||||
pub cx: i32,
|
||||
pub cy: i32,
|
||||
pub cz: i32,
|
||||
pub version: u32,
|
||||
pub flags: u8,
|
||||
pub fragment_idx: u8,
|
||||
pub fragment_count: u8,
|
||||
pub payload: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ChunkMessage {
|
||||
pub fn from_voxel(chunk: &VoxelChunk, version: u32) -> Vec<ChunkMessage> {
|
||||
let rle_data = chunk.rle_encode();
|
||||
let total_size = rle_data.len();
|
||||
|
||||
if total_size <= CHUNK_MAX_PAYLOAD {
|
||||
return vec![ChunkMessage {
|
||||
chunk_type: CHUNK_TYPE_VXL,
|
||||
id: 0,
|
||||
cx: chunk.cx,
|
||||
cy: chunk.cy,
|
||||
cz: chunk.cz,
|
||||
version,
|
||||
flags: CHUNK_FLAG_RLE | CHUNK_FLAG_FINAL,
|
||||
fragment_idx: 0,
|
||||
fragment_count: 1,
|
||||
payload: rle_data,
|
||||
}];
|
||||
}
|
||||
|
||||
let fragment_count = (total_size + CHUNK_MAX_PAYLOAD - 1) / CHUNK_MAX_PAYLOAD;
|
||||
let mut messages = Vec::new();
|
||||
|
||||
for i in 0..fragment_count {
|
||||
let start = i * CHUNK_MAX_PAYLOAD;
|
||||
let end = ((i + 1) * CHUNK_MAX_PAYLOAD).min(total_size);
|
||||
let is_final = i == fragment_count - 1;
|
||||
|
||||
messages.push(ChunkMessage {
|
||||
chunk_type: CHUNK_TYPE_VXL,
|
||||
id: 0,
|
||||
cx: chunk.cx,
|
||||
cy: chunk.cy,
|
||||
cz: chunk.cz,
|
||||
version,
|
||||
flags: CHUNK_FLAG_RLE | if is_final { CHUNK_FLAG_FINAL } else { 0 },
|
||||
fragment_idx: i as u8,
|
||||
fragment_count: fragment_count as u8,
|
||||
payload: rle_data[start..end].to_vec(),
|
||||
});
|
||||
}
|
||||
|
||||
messages
|
||||
}
|
||||
|
||||
pub fn from_bsp(data: Vec<u8>, chunk_id: u32, version: u32) -> Vec<ChunkMessage> {
|
||||
let total_size = data.len();
|
||||
|
||||
if total_size <= CHUNK_MAX_PAYLOAD {
|
||||
return vec![ChunkMessage {
|
||||
chunk_type: CHUNK_TYPE_BSP,
|
||||
id: chunk_id,
|
||||
cx: 0,
|
||||
cy: 0,
|
||||
cz: 0,
|
||||
version,
|
||||
flags: CHUNK_FLAG_FINAL,
|
||||
fragment_idx: 0,
|
||||
fragment_count: 1,
|
||||
payload: data,
|
||||
}];
|
||||
}
|
||||
|
||||
let fragment_count = (total_size + CHUNK_MAX_PAYLOAD - 1) / CHUNK_MAX_PAYLOAD;
|
||||
let mut messages = Vec::new();
|
||||
|
||||
for i in 0..fragment_count {
|
||||
let start = i * CHUNK_MAX_PAYLOAD;
|
||||
let end = ((i + 1) * CHUNK_MAX_PAYLOAD).min(total_size);
|
||||
let is_final = i == fragment_count - 1;
|
||||
|
||||
messages.push(ChunkMessage {
|
||||
chunk_type: CHUNK_TYPE_BSP,
|
||||
id: chunk_id,
|
||||
cx: 0,
|
||||
cy: 0,
|
||||
cz: 0,
|
||||
version,
|
||||
flags: if is_final { CHUNK_FLAG_FINAL } else { 0 },
|
||||
fragment_idx: i as u8,
|
||||
fragment_count: fragment_count as u8,
|
||||
payload: data[start..end].to_vec(),
|
||||
});
|
||||
}
|
||||
|
||||
messages
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
mod sys {
|
||||
|
|
@ -129,8 +243,8 @@ type SockAddr = windows_sys::Win32::Networking::WinSock::SOCKADDR_IN;
|
|||
pub struct Transport {
|
||||
client_addr: SockAddr,
|
||||
has_client: bool,
|
||||
recv_buf: [u8; 4096],
|
||||
send_buf: [u8; 4096],
|
||||
recv_buf: [u8; 512],
|
||||
send_buf: [u8; 2048],
|
||||
socket: sys::RawSocket,
|
||||
}
|
||||
|
||||
|
|
@ -154,8 +268,8 @@ impl Transport {
|
|||
Some(Self {
|
||||
client_addr: unsafe { core::mem::zeroed() },
|
||||
has_client: false,
|
||||
recv_buf: [0u8; 4096],
|
||||
send_buf: [0u8; 4096],
|
||||
recv_buf: [0u8; 512],
|
||||
send_buf: [0u8; 2048],
|
||||
socket: sock,
|
||||
})
|
||||
}
|
||||
|
|
@ -198,7 +312,7 @@ impl Transport {
|
|||
let msg_header = pxl8_msg_header {
|
||||
sequence,
|
||||
size: 0,
|
||||
type_: pxl8_msg_type::PXL8_MSG_SNAPSHOT as u8,
|
||||
type_: PXL8_MSG_SNAPSHOT as u8,
|
||||
version: PXL8_PROTOCOL_VERSION as u8,
|
||||
};
|
||||
offset += self.serialize_header(&msg_header, offset);
|
||||
|
|
@ -211,6 +325,72 @@ impl Transport {
|
|||
sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr);
|
||||
}
|
||||
|
||||
pub fn send_chunk(&mut self, msg: &ChunkMessage, sequence: u32) {
|
||||
if !self.has_client {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut offset = 0;
|
||||
|
||||
let msg_header = pxl8_msg_header {
|
||||
sequence,
|
||||
size: 0,
|
||||
type_: PXL8_MSG_CHUNK as u8,
|
||||
version: PXL8_PROTOCOL_VERSION as u8,
|
||||
};
|
||||
offset += self.serialize_header(&msg_header, offset);
|
||||
offset += self.serialize_chunk_msg_header(msg, offset);
|
||||
|
||||
let payload_len = msg.payload.len().min(self.send_buf.len() - offset);
|
||||
self.send_buf[offset..offset + payload_len].copy_from_slice(&msg.payload[..payload_len]);
|
||||
offset += payload_len;
|
||||
|
||||
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) {
|
||||
if !self.has_client {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut offset = 0;
|
||||
|
||||
let msg_header = pxl8_msg_header {
|
||||
sequence,
|
||||
size: 0,
|
||||
type_: PXL8_MSG_CHUNK_ENTER as u8,
|
||||
version: PXL8_PROTOCOL_VERSION as u8,
|
||||
};
|
||||
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;
|
||||
offset += 8;
|
||||
|
||||
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;
|
||||
buf[1] = msg.flags;
|
||||
buf[2] = msg.fragment_idx;
|
||||
buf[3] = msg.fragment_count;
|
||||
buf[4..8].copy_from_slice(&msg.id.to_be_bytes());
|
||||
buf[8..12].copy_from_slice(&(msg.cx as u32).to_be_bytes());
|
||||
buf[12..16].copy_from_slice(&(msg.cy as u32).to_be_bytes());
|
||||
buf[16..20].copy_from_slice(&(msg.cz as u32).to_be_bytes());
|
||||
buf[20..24].copy_from_slice(&msg.version.to_be_bytes());
|
||||
let payload_size = msg.payload.len() as u16;
|
||||
buf[24..26].copy_from_slice(&payload_size.to_be_bytes());
|
||||
buf[26..28].copy_from_slice(&[0u8; 2]);
|
||||
28
|
||||
}
|
||||
|
||||
fn serialize_header(&mut self, h: &pxl8_msg_header, offset: usize) -> usize {
|
||||
let buf = &mut self.send_buf[offset..];
|
||||
buf[0..4].copy_from_slice(&h.sequence.to_be_bytes());
|
||||
318
pxl8d/src/voxel.rs
Normal file
318
pxl8d/src/voxel.rs
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
extern crate alloc;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use libm::floorf;
|
||||
|
||||
use crate::math::Vec3;
|
||||
|
||||
pub const CHUNK_SIZE: usize = 32;
|
||||
pub const CHUNK_VOLUME: usize = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE;
|
||||
|
||||
pub const AIR: u8 = 0;
|
||||
pub const STONE: u8 = 1;
|
||||
pub const DIRT: u8 = 2;
|
||||
pub const GRASS: u8 = 3;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VoxelChunk {
|
||||
pub blocks: [u8; CHUNK_VOLUME],
|
||||
pub cx: i32,
|
||||
pub cy: i32,
|
||||
pub cz: i32,
|
||||
}
|
||||
|
||||
impl VoxelChunk {
|
||||
pub fn new(cx: i32, cy: i32, cz: i32) -> Self {
|
||||
Self {
|
||||
blocks: [AIR; CHUNK_VOLUME],
|
||||
cx,
|
||||
cy,
|
||||
cz,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 {
|
||||
if !self.is_solid_radius(to.x, to.y, to.z, radius) {
|
||||
return to;
|
||||
}
|
||||
|
||||
let x_ok = !self.is_solid_radius(to.x, from.y, from.z, radius);
|
||||
let y_ok = !self.is_solid_radius(from.x, to.y, from.z, radius);
|
||||
let z_ok = !self.is_solid_radius(from.x, from.y, to.z, radius);
|
||||
|
||||
let mut result = from;
|
||||
if x_ok { result.x = to.x; }
|
||||
if y_ok { result.y = to.y; }
|
||||
if z_ok { result.z = to.z; }
|
||||
result
|
||||
}
|
||||
|
||||
fn world_to_local(x: f32, chunk_coord: i32) -> usize {
|
||||
let chunk_base = chunk_coord as f32 * CHUNK_SIZE as f32;
|
||||
let local = (x - chunk_base) as usize;
|
||||
local.min(CHUNK_SIZE - 1)
|
||||
}
|
||||
|
||||
fn is_solid_at(&self, x: f32, y: f32, z: f32) -> bool {
|
||||
let lx = Self::world_to_local(x, self.cx);
|
||||
let ly = Self::world_to_local(y, self.cy);
|
||||
let lz = Self::world_to_local(z, self.cz);
|
||||
self.get(lx, ly, lz) != AIR
|
||||
}
|
||||
|
||||
fn is_solid_radius(&self, x: f32, y: f32, z: f32, radius: f32) -> bool {
|
||||
self.is_solid_at(x - radius, y, z) ||
|
||||
self.is_solid_at(x + radius, y, z) ||
|
||||
self.is_solid_at(x, y - radius, z) ||
|
||||
self.is_solid_at(x, y + radius, z) ||
|
||||
self.is_solid_at(x, y, z - radius) ||
|
||||
self.is_solid_at(x, y, z + radius)
|
||||
}
|
||||
|
||||
pub fn index(x: usize, y: usize, z: usize) -> usize {
|
||||
x + y * CHUNK_SIZE + z * CHUNK_SIZE * CHUNK_SIZE
|
||||
}
|
||||
|
||||
pub fn get(&self, x: usize, y: usize, z: usize) -> u8 {
|
||||
if x >= CHUNK_SIZE || y >= CHUNK_SIZE || z >= CHUNK_SIZE {
|
||||
return AIR;
|
||||
}
|
||||
self.blocks[Self::index(x, y, z)]
|
||||
}
|
||||
|
||||
pub fn set(&mut self, x: usize, y: usize, z: usize, block: u8) {
|
||||
if x < CHUNK_SIZE && y < CHUNK_SIZE && z < CHUNK_SIZE {
|
||||
self.blocks[Self::index(x, y, z)] = block;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rle_encode(&self) -> Vec<u8> {
|
||||
let mut result = Vec::new();
|
||||
let mut i = 0;
|
||||
|
||||
while i < CHUNK_VOLUME {
|
||||
let block = self.blocks[i];
|
||||
let mut run_len = 1usize;
|
||||
|
||||
while i + run_len < CHUNK_VOLUME
|
||||
&& self.blocks[i + run_len] == block
|
||||
&& run_len < 256
|
||||
{
|
||||
run_len += 1;
|
||||
}
|
||||
|
||||
result.push(block);
|
||||
result.push((run_len - 1) as u8);
|
||||
i += run_len;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VoxelWorld {
|
||||
pub chunks: Vec<VoxelChunk>,
|
||||
pub seed: u64,
|
||||
}
|
||||
|
||||
impl VoxelWorld {
|
||||
pub fn new(seed: u64) -> Self {
|
||||
Self {
|
||||
chunks: Vec::new(),
|
||||
seed,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_chunk(&self, cx: i32, cy: i32, cz: i32) -> Option<&VoxelChunk> {
|
||||
self.chunks.iter().find(|c| c.cx == cx && c.cy == cy && c.cz == cz)
|
||||
}
|
||||
|
||||
pub fn get_chunk_mut(&mut self, cx: i32, cy: i32, cz: i32) -> Option<&mut VoxelChunk> {
|
||||
self.chunks.iter_mut().find(|c| c.cx == cx && c.cy == cy && c.cz == cz)
|
||||
}
|
||||
|
||||
pub fn ensure_chunk(&mut self, cx: i32, cy: i32, cz: i32) -> &mut VoxelChunk {
|
||||
if self.get_chunk(cx, cy, cz).is_none() {
|
||||
let mut chunk = VoxelChunk::new(cx, cy, cz);
|
||||
generate_chunk(&mut chunk, self.seed);
|
||||
self.chunks.push(chunk);
|
||||
}
|
||||
self.get_chunk_mut(cx, cy, cz).unwrap()
|
||||
}
|
||||
|
||||
pub fn world_to_chunk(x: f32) -> i32 {
|
||||
floorf(x / CHUNK_SIZE as f32) as i32
|
||||
}
|
||||
|
||||
pub fn world_to_local(x: f32) -> usize {
|
||||
let chunk = floorf(x / CHUNK_SIZE as f32);
|
||||
let local = (x - chunk * CHUNK_SIZE as f32) as usize;
|
||||
local.min(CHUNK_SIZE - 1)
|
||||
}
|
||||
|
||||
pub fn is_solid(&self, x: f32, y: f32, z: f32) -> bool {
|
||||
let cx = Self::world_to_chunk(x);
|
||||
let cy = Self::world_to_chunk(y);
|
||||
let cz = Self::world_to_chunk(z);
|
||||
|
||||
let lx = Self::world_to_local(x);
|
||||
let ly = Self::world_to_local(y);
|
||||
let lz = Self::world_to_local(z);
|
||||
|
||||
match self.get_chunk(cx, cy, cz) {
|
||||
Some(chunk) => chunk.get(lx, ly, lz) != AIR,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_solid_radius(&self, x: f32, y: f32, z: f32, radius: f32) -> bool {
|
||||
self.is_solid(x - radius, y, z) ||
|
||||
self.is_solid(x + radius, y, z) ||
|
||||
self.is_solid(x, y - radius, z) ||
|
||||
self.is_solid(x, y + radius, z) ||
|
||||
self.is_solid(x, y, z - radius) ||
|
||||
self.is_solid(x, y, z + radius)
|
||||
}
|
||||
|
||||
pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 {
|
||||
if !self.is_solid_radius(to.x, to.y, to.z, radius) {
|
||||
return to;
|
||||
}
|
||||
|
||||
let x_ok = !self.is_solid_radius(to.x, from.y, from.z, radius);
|
||||
let y_ok = !self.is_solid_radius(from.x, to.y, from.z, radius);
|
||||
let z_ok = !self.is_solid_radius(from.x, from.y, to.z, radius);
|
||||
|
||||
let mut result = from;
|
||||
|
||||
if x_ok {
|
||||
result.x = to.x;
|
||||
}
|
||||
if y_ok {
|
||||
result.y = to.y;
|
||||
}
|
||||
if z_ok {
|
||||
result.z = to.z;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn chunks_near(&self, pos: Vec3, radius: i32) -> Vec<(i32, i32, i32)> {
|
||||
let cx = Self::world_to_chunk(pos.x);
|
||||
let cy = Self::world_to_chunk(pos.y);
|
||||
let cz = Self::world_to_chunk(pos.z);
|
||||
|
||||
let mut result = Vec::new();
|
||||
for dz in -radius..=radius {
|
||||
for dy in -radius..=radius {
|
||||
for dx in -radius..=radius {
|
||||
result.push((cx + dx, cy + dy, cz + dz));
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn load_chunks_around(&mut self, pos: Vec3, radius: i32) {
|
||||
let coords = self.chunks_near(pos, radius);
|
||||
for (cx, cy, cz) in coords {
|
||||
self.ensure_chunk(cx, cy, cz);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn hash(mut x: u64) -> u64 {
|
||||
x ^= x >> 33;
|
||||
x = x.wrapping_mul(0xff51afd7ed558ccd);
|
||||
x ^= x >> 33;
|
||||
x = x.wrapping_mul(0xc4ceb9fe1a85ec53);
|
||||
x ^= x >> 33;
|
||||
x
|
||||
}
|
||||
|
||||
fn noise2d(x: i32, z: i32, seed: u64) -> f32 {
|
||||
let h = hash(seed ^ (x as u64) ^ ((z as u64) << 32));
|
||||
(h & 0xFFFF) as f32 / 65535.0
|
||||
}
|
||||
|
||||
fn smoothstep(t: f32) -> f32 {
|
||||
t * t * (3.0 - 2.0 * t)
|
||||
}
|
||||
|
||||
fn lerp(a: f32, b: f32, t: f32) -> f32 {
|
||||
a + (b - a) * t
|
||||
}
|
||||
|
||||
fn value_noise(x: f32, z: f32, seed: u64) -> f32 {
|
||||
let x0 = floorf(x) as i32;
|
||||
let z0 = floorf(z) as i32;
|
||||
let x1 = x0 + 1;
|
||||
let z1 = z0 + 1;
|
||||
|
||||
let tx = smoothstep(x - x0 as f32);
|
||||
let tz = smoothstep(z - z0 as f32);
|
||||
|
||||
let c00 = noise2d(x0, z0, seed);
|
||||
let c10 = noise2d(x1, z0, seed);
|
||||
let c01 = noise2d(x0, z1, seed);
|
||||
let c11 = noise2d(x1, z1, seed);
|
||||
|
||||
let a = lerp(c00, c10, tx);
|
||||
let b = lerp(c01, c11, tx);
|
||||
lerp(a, b, tz)
|
||||
}
|
||||
|
||||
fn fbm(x: f32, z: f32, seed: u64, octaves: u32) -> f32 {
|
||||
let mut value = 0.0;
|
||||
let mut amplitude = 1.0;
|
||||
let mut frequency = 1.0;
|
||||
let mut max_value = 0.0;
|
||||
|
||||
for i in 0..octaves {
|
||||
value += amplitude * value_noise(x * frequency, z * frequency, seed.wrapping_add(i as u64 * 1000));
|
||||
max_value += amplitude;
|
||||
amplitude *= 0.5;
|
||||
frequency *= 2.0;
|
||||
}
|
||||
|
||||
value / max_value
|
||||
}
|
||||
|
||||
fn generate_chunk(chunk: &mut VoxelChunk, seed: u64) {
|
||||
let world_x = chunk.cx * CHUNK_SIZE as i32;
|
||||
let world_y = chunk.cy * CHUNK_SIZE as i32;
|
||||
let world_z = chunk.cz * CHUNK_SIZE as i32;
|
||||
|
||||
for lz in 0..CHUNK_SIZE {
|
||||
for lx in 0..CHUNK_SIZE {
|
||||
let wx = (world_x + lx as i32) as f32;
|
||||
let wz = (world_z + lz as i32) as f32;
|
||||
|
||||
let height = fbm(wx * 0.02, wz * 0.02, seed, 4) * 32.0 + 16.0;
|
||||
let height = height as i32;
|
||||
|
||||
for ly in 0..CHUNK_SIZE {
|
||||
let wy = world_y + ly as i32;
|
||||
|
||||
let block = if wy > height {
|
||||
AIR
|
||||
} else if wy == height {
|
||||
GRASS
|
||||
} else if wy > height - 4 {
|
||||
DIRT
|
||||
} else {
|
||||
STONE
|
||||
};
|
||||
|
||||
chunk.set(lx, ly, lz, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VoxelWorld {
|
||||
fn default() -> Self {
|
||||
Self::new(12345)
|
||||
}
|
||||
}
|
||||
87
pxl8d/src/world.rs
Normal file
87
pxl8d/src/world.rs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
extern crate alloc;
|
||||
|
||||
use alloc::collections::BTreeMap;
|
||||
|
||||
use crate::chunk::{Chunk, ChunkId};
|
||||
use crate::math::Vec3;
|
||||
use crate::procgen::{ProcgenParams, generate_rooms};
|
||||
|
||||
pub struct World {
|
||||
active: Option<ChunkId>,
|
||||
chunks: BTreeMap<ChunkId, Chunk>,
|
||||
seed: u64,
|
||||
}
|
||||
|
||||
impl World {
|
||||
pub fn new(seed: u64) -> Self {
|
||||
Self {
|
||||
chunks: BTreeMap::new(),
|
||||
active: None,
|
||||
seed,
|
||||
}
|
||||
}
|
||||
|
||||
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 set_active(&mut self, id: ChunkId) {
|
||||
self.active = Some(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 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);
|
||||
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 });
|
||||
}
|
||||
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 {
|
||||
fn default() -> Self {
|
||||
Self::new(12345)
|
||||
}
|
||||
}
|
||||
1090
server/Cargo.lock
generated
1090
server/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,45 +0,0 @@
|
|||
use bevy_ecs::prelude::*;
|
||||
|
||||
#[derive(Component, Clone, Copy, Default)]
|
||||
pub struct Position {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub z: f32,
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Copy, Default)]
|
||||
pub struct Rotation {
|
||||
pub pitch: f32,
|
||||
pub yaw: f32,
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Copy, Default)]
|
||||
pub struct Velocity {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub z: f32,
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Copy)]
|
||||
pub struct Health {
|
||||
pub current: f32,
|
||||
pub max: f32,
|
||||
}
|
||||
|
||||
impl Health {
|
||||
pub fn new(max: f32) -> Self {
|
||||
Self { current: max, max }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Health {
|
||||
fn default() -> Self {
|
||||
Self::new(100.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Copy, Default)]
|
||||
pub struct Player;
|
||||
|
||||
#[derive(Component, Clone, Copy)]
|
||||
pub struct TypeId(pub u16);
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
#![no_std]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
pub mod components;
|
||||
mod simulation;
|
||||
pub mod transport;
|
||||
|
||||
#[allow(dead_code, non_camel_case_types, non_snake_case, non_upper_case_globals)]
|
||||
pub mod protocol {
|
||||
include!(concat!(env!("OUT_DIR"), "/protocol.rs"));
|
||||
}
|
||||
|
||||
pub use bevy_ecs::prelude::*;
|
||||
pub use components::*;
|
||||
pub use protocol::*;
|
||||
pub use simulation::*;
|
||||
pub use transport::*;
|
||||
|
|
@ -1,142 +0,0 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
mod allocator;
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
use pxl8_server::*;
|
||||
|
||||
#[global_allocator]
|
||||
static ALLOCATOR: allocator::Allocator = allocator::Allocator;
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
const TICK_RATE: u64 = 30;
|
||||
const TICK_NS: u64 = 1_000_000_000 / TICK_RATE;
|
||||
|
||||
#[cfg(unix)]
|
||||
fn get_time_ns() -> u64 {
|
||||
let mut ts = libc::timespec { tv_sec: 0, tv_nsec: 0 };
|
||||
unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts) };
|
||||
(ts.tv_sec as u64) * 1_000_000_000 + (ts.tv_nsec as u64)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn get_time_ns() -> u64 {
|
||||
use windows_sys::Win32::System::Performance::*;
|
||||
static mut FREQ: i64 = 0;
|
||||
unsafe {
|
||||
if FREQ == 0 {
|
||||
QueryPerformanceFrequency(&mut FREQ);
|
||||
}
|
||||
let mut count: i64 = 0;
|
||||
QueryPerformanceCounter(&mut count);
|
||||
((count as u128 * 1_000_000_000) / FREQ as u128) as u64
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn sleep_ms(ms: u64) {
|
||||
let ts = libc::timespec {
|
||||
tv_sec: (ms / 1000) as i64,
|
||||
tv_nsec: ((ms % 1000) * 1_000_000) as i64,
|
||||
};
|
||||
unsafe { libc::nanosleep(&ts, core::ptr::null_mut()) };
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn sleep_ms(ms: u64) {
|
||||
use windows_sys::Win32::System::Threading::Sleep;
|
||||
unsafe { Sleep(ms as u32) };
|
||||
}
|
||||
|
||||
fn extract_spawn_position(payload: &[u8]) -> (f32, f32, f32, f32, f32) {
|
||||
let x = f32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
||||
let y = f32::from_be_bytes([payload[4], payload[5], payload[6], payload[7]]);
|
||||
let z = f32::from_be_bytes([payload[8], payload[9], payload[10], payload[11]]);
|
||||
let yaw = f32::from_be_bytes([payload[12], payload[13], payload[14], payload[15]]);
|
||||
let pitch = f32::from_be_bytes([payload[16], payload[17], payload[18], payload[19]]);
|
||||
(x, y, z, yaw, pitch)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
|
||||
let mut transport = match transport::Transport::bind(transport::DEFAULT_PORT) {
|
||||
Some(t) => t,
|
||||
None => return 1,
|
||||
};
|
||||
|
||||
let mut sim = Simulation::new();
|
||||
let mut player_id: u64 = 0;
|
||||
let mut last_client_tick: u64 = 0;
|
||||
|
||||
let mut sequence: u32 = 0;
|
||||
let mut last_tick = get_time_ns();
|
||||
let mut entities_buf = [protocol::pxl8_entity_state {
|
||||
entity_id: 0,
|
||||
userdata: [0u8; 56],
|
||||
}; 64];
|
||||
let mut inputs_buf: [protocol::pxl8_input_msg; 16] = unsafe { core::mem::zeroed() };
|
||||
|
||||
loop {
|
||||
let now = get_time_ns();
|
||||
let elapsed = now.saturating_sub(last_tick);
|
||||
|
||||
if elapsed >= TICK_NS {
|
||||
last_tick = now;
|
||||
let dt = (elapsed as f32) / 1_000_000_000.0;
|
||||
|
||||
let mut latest_input: Option<protocol::pxl8_input_msg> = None;
|
||||
while let Some(msg_type) = transport.recv() {
|
||||
match msg_type {
|
||||
x if x == protocol::pxl8_msg_type::PXL8_MSG_INPUT as u8 => {
|
||||
latest_input = Some(transport.get_input());
|
||||
}
|
||||
x if x == protocol::pxl8_msg_type::PXL8_MSG_COMMAND as u8 => {
|
||||
let cmd = transport.get_command();
|
||||
if cmd.cmd_type == protocol::pxl8_cmd_type::PXL8_CMD_SPAWN_ENTITY as u16 {
|
||||
let (x, y, z, _yaw, _pitch) = extract_spawn_position(&cmd.payload);
|
||||
let player = sim.spawn_player(x, y, z);
|
||||
player_id = player.to_bits();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(input) = latest_input {
|
||||
last_client_tick = input.tick;
|
||||
inputs_buf[0] = input;
|
||||
sim.step(&inputs_buf[..1], dt);
|
||||
} else {
|
||||
sim.step(&[], dt);
|
||||
}
|
||||
|
||||
let mut count = 0;
|
||||
sim.generate_snapshot(player_id, |state| {
|
||||
if count < entities_buf.len() {
|
||||
entities_buf[count] = *state;
|
||||
count += 1;
|
||||
}
|
||||
});
|
||||
|
||||
let header = protocol::pxl8_snapshot_header {
|
||||
entity_count: count as u16,
|
||||
event_count: 0,
|
||||
player_id,
|
||||
tick: last_client_tick,
|
||||
time: sim.time,
|
||||
};
|
||||
|
||||
transport.send_snapshot(&header, &entities_buf[..count], sequence);
|
||||
sequence = sequence.wrapping_add(1);
|
||||
}
|
||||
|
||||
sleep_ms(1);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
use bevy_ecs::prelude::*;
|
||||
use libm::{cosf, sinf};
|
||||
|
||||
use crate::components::*;
|
||||
use crate::protocol::*;
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
pub struct SimTime {
|
||||
pub dt: f32,
|
||||
pub time: f32,
|
||||
}
|
||||
|
||||
pub struct Simulation {
|
||||
pub player: Option<Entity>,
|
||||
pub tick: u64,
|
||||
pub time: f32,
|
||||
pub world: World,
|
||||
}
|
||||
|
||||
impl Simulation {
|
||||
pub fn new() -> Self {
|
||||
let mut world = World::new();
|
||||
world.insert_resource(SimTime::default());
|
||||
Self {
|
||||
player: None,
|
||||
tick: 0,
|
||||
time: 0.0,
|
||||
world,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn step(&mut self, inputs: &[pxl8_input_msg], dt: f32) {
|
||||
self.tick += 1;
|
||||
self.time += dt;
|
||||
|
||||
if let Some(mut sim_time) = self.world.get_resource_mut::<SimTime>() {
|
||||
sim_time.dt = dt;
|
||||
sim_time.time = self.time;
|
||||
}
|
||||
|
||||
for input in inputs {
|
||||
self.apply_input(input, dt);
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_input(&mut self, input: &pxl8_input_msg, dt: f32) {
|
||||
let Some(player) = self.player else { return };
|
||||
let speed = 200.0;
|
||||
|
||||
let yaw = input.yaw;
|
||||
let sin_yaw = sinf(yaw);
|
||||
let cos_yaw = cosf(yaw);
|
||||
|
||||
let move_x = input.move_x;
|
||||
let move_y = input.move_y;
|
||||
let len_sq = move_x * move_x + move_y * move_y;
|
||||
|
||||
if len_sq > 0.0 {
|
||||
let len = libm::sqrtf(len_sq);
|
||||
let norm_x = move_x / len;
|
||||
let norm_y = move_y / len;
|
||||
|
||||
let forward_x = -sin_yaw * norm_y * speed * dt;
|
||||
let forward_z = -cos_yaw * norm_y * speed * dt;
|
||||
let strafe_x = cos_yaw * norm_x * speed * dt;
|
||||
let strafe_z = -sin_yaw * norm_x * speed * dt;
|
||||
|
||||
if let Some(mut pos) = self.world.get_mut::<Position>(player) {
|
||||
pos.x += forward_x + strafe_x;
|
||||
pos.z += forward_z + strafe_z;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(mut rot) = self.world.get_mut::<Rotation>(player) {
|
||||
rot.yaw = yaw;
|
||||
rot.pitch = (rot.pitch - input.look_dy * 0.008).clamp(-1.5, 1.5);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn_entity(&mut self) -> Entity {
|
||||
self.world.spawn((
|
||||
Position::default(),
|
||||
Rotation::default(),
|
||||
Velocity::default(),
|
||||
)).id()
|
||||
}
|
||||
|
||||
pub fn spawn_player(&mut self, x: f32, y: f32, z: f32) -> Entity {
|
||||
let entity = self.world.spawn((
|
||||
Player,
|
||||
Position { x, y, z },
|
||||
Rotation::default(),
|
||||
Velocity::default(),
|
||||
Health::default(),
|
||||
)).id();
|
||||
self.player = Some(entity);
|
||||
entity
|
||||
}
|
||||
|
||||
pub fn generate_snapshot<F>(&mut self, player_id: u64, mut writer: F)
|
||||
where
|
||||
F: FnMut(&pxl8_entity_state),
|
||||
{
|
||||
let mut query = self.world.query::<(Entity, &Position, &Rotation)>();
|
||||
for (entity, pos, rot) in query.iter(&self.world) {
|
||||
let mut state = pxl8_entity_state {
|
||||
entity_id: entity.to_bits(),
|
||||
userdata: [0u8; 56],
|
||||
};
|
||||
|
||||
let bytes = &mut state.userdata;
|
||||
bytes[0..4].copy_from_slice(&pos.x.to_be_bytes());
|
||||
bytes[4..8].copy_from_slice(&pos.y.to_be_bytes());
|
||||
bytes[8..12].copy_from_slice(&pos.z.to_be_bytes());
|
||||
bytes[12..16].copy_from_slice(&rot.yaw.to_be_bytes());
|
||||
bytes[16..20].copy_from_slice(&rot.pitch.to_be_bytes());
|
||||
|
||||
writer(&state);
|
||||
}
|
||||
|
||||
let _ = player_id;
|
||||
}
|
||||
|
||||
pub fn entity_count(&self) -> usize {
|
||||
self.world.entities().len() as usize
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Simulation {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
|
@ -55,6 +55,10 @@ static const char embed_pxl8_particles[] = {
|
|||
#embed "src/lua/pxl8/particles.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_procgen[] = {
|
||||
#embed "src/lua/pxl8/procgen.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_sfx[] = {
|
||||
#embed "src/lua/pxl8/sfx.lua"
|
||||
, 0 };
|
||||
|
|
@ -89,6 +93,7 @@ static const pxl8_embed pxl8_embeds[] = {
|
|||
PXL8_EMBED_ENTRY(embed_pxl8_math, "pxl8.math"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_net, "pxl8.net"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_particles, "pxl8.particles"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_procgen, "pxl8.procgen"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_sfx, "pxl8.sfx"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_tilemap, "pxl8.tilemap"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_transition, "pxl8.transition"),
|
||||
|
|
|
|||
|
|
@ -237,6 +237,19 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
|
|||
|
||||
pxl8_rng_seed(&game->rng, (u32)sys->hal->get_ticks());
|
||||
|
||||
game->world = pxl8_world_create();
|
||||
if (!game->world) {
|
||||
pxl8_error("failed to create world");
|
||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
|
||||
pxl8_net_config net_cfg = { .address = "127.0.0.1", .port = 7777 };
|
||||
game->net = pxl8_net_create(&net_cfg);
|
||||
if (game->net) {
|
||||
pxl8_net_set_chunk_cache(game->net, pxl8_world_chunk_cache(game->world));
|
||||
pxl8_net_connect(game->net);
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
game->debug_replay = pxl8_replay_create_buffer(60, 60);
|
||||
pxl8_sfx_mixer_set_event_callback(game->mixer, pxl8_audio_event_callback, game);
|
||||
|
|
@ -338,6 +351,13 @@ pxl8_result pxl8_update(pxl8* sys) {
|
|||
}
|
||||
}
|
||||
|
||||
if (game->net) {
|
||||
while (pxl8_net_poll(game->net)) {}
|
||||
pxl8_net_update(game->net, dt);
|
||||
pxl8_world_sync(game->world, game->net);
|
||||
}
|
||||
|
||||
pxl8_world_update(game->world, dt);
|
||||
pxl8_gfx_update(game->gfx, dt);
|
||||
pxl8_sfx_mixer_process(game->mixer);
|
||||
|
||||
|
|
@ -418,6 +438,9 @@ void pxl8_quit(pxl8* sys) {
|
|||
pxl8_replay_destroy(game->debug_replay);
|
||||
#endif
|
||||
|
||||
if (game->net) pxl8_net_destroy(game->net);
|
||||
if (game->world) pxl8_world_destroy(game->world);
|
||||
|
||||
pxl8_sfx_mixer_destroy(game->mixer);
|
||||
pxl8_gfx_destroy(game->gfx);
|
||||
pxl8_script_destroy(game->script);
|
||||
|
|
@ -437,6 +460,10 @@ void pxl8_set_running(pxl8* sys, bool running) {
|
|||
}
|
||||
}
|
||||
|
||||
pxl8_world* pxl8_get_world(pxl8* sys) {
|
||||
return (sys && sys->game) ? sys->game->world : NULL;
|
||||
}
|
||||
|
||||
f32 pxl8_get_fps(const pxl8* sys) {
|
||||
return (sys && sys->game) ? sys->game->fps : 0.0f;
|
||||
}
|
||||
|
|
@ -449,6 +476,10 @@ pxl8_input_state* pxl8_get_input(const pxl8* sys) {
|
|||
return (sys && sys->game) ? &sys->game->input : NULL;
|
||||
}
|
||||
|
||||
pxl8_net* pxl8_get_net(const pxl8* sys) {
|
||||
return (sys && sys->game) ? sys->game->net : NULL;
|
||||
}
|
||||
|
||||
pxl8_sfx_mixer* pxl8_get_sfx_mixer(const pxl8* sys) {
|
||||
return (sys && sys->game) ? sys->game->mixer : NULL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,37 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_net.h"
|
||||
#include "pxl8_rng.h"
|
||||
#include "pxl8_script.h"
|
||||
#include "pxl8_sfx.h"
|
||||
#include "pxl8_types.h"
|
||||
#include "pxl8_world.h"
|
||||
|
||||
typedef struct pxl8_replay pxl8_replay;
|
||||
|
||||
typedef struct pxl8_game {
|
||||
pxl8_gfx* gfx;
|
||||
pxl8_script* script;
|
||||
pxl8_sfx_mixer* mixer;
|
||||
|
||||
pxl8_rng rng;
|
||||
i32 frame_count;
|
||||
u64 last_time;
|
||||
f32 time;
|
||||
|
||||
f32 fps_accumulator;
|
||||
i32 fps_frame_count;
|
||||
f32 fps;
|
||||
|
||||
pxl8_input_state input;
|
||||
pxl8_input_state prev_input;
|
||||
|
||||
#ifndef NDEBUG
|
||||
pxl8_replay* debug_replay;
|
||||
#endif
|
||||
|
||||
f32 fps;
|
||||
f32 fps_accumulator;
|
||||
i32 fps_frame_count;
|
||||
i32 frame_count;
|
||||
pxl8_gfx* gfx;
|
||||
pxl8_input_state input;
|
||||
u64 last_time;
|
||||
pxl8_sfx_mixer* mixer;
|
||||
pxl8_net* net;
|
||||
pxl8_input_state prev_input;
|
||||
bool repl_mode;
|
||||
bool repl_started;
|
||||
pxl8_rng rng;
|
||||
bool running;
|
||||
pxl8_script* script;
|
||||
bool script_loaded;
|
||||
char script_path[256];
|
||||
f32 time;
|
||||
pxl8_world* world;
|
||||
} pxl8_game;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#include "pxl8_log.h"
|
||||
#include "pxl8_repl.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
|
@ -18,6 +18,7 @@ static pxl8_log* g_log = NULL;
|
|||
|
||||
void pxl8_log_init(pxl8_log* log) {
|
||||
g_log = log;
|
||||
g_log->handler = NULL;
|
||||
g_log->level = PXL8_LOG_LEVEL_DEBUG;
|
||||
|
||||
const char* env_level = getenv("PXL8_LOG_LEVEL");
|
||||
|
|
@ -30,6 +31,10 @@ void pxl8_log_init(pxl8_log* log) {
|
|||
}
|
||||
}
|
||||
|
||||
void pxl8_log_set_handler(pxl8_log_handler handler) {
|
||||
if (g_log) g_log->handler = handler;
|
||||
}
|
||||
|
||||
void pxl8_log_set_level(pxl8_log_level level) {
|
||||
if (g_log) g_log->level = level;
|
||||
}
|
||||
|
|
@ -61,7 +66,7 @@ static void log_output(const char* color, const char* level,
|
|||
|
||||
strncat(buffer, "\n", sizeof(buffer) - strlen(buffer) - 1);
|
||||
|
||||
if (!pxl8_repl_push_log(buffer)) {
|
||||
if (!g_log->handler || !g_log->handler(buffer)) {
|
||||
printf("%s", buffer);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,11 +10,15 @@ typedef enum {
|
|||
PXL8_LOG_LEVEL_ERROR = 4,
|
||||
} pxl8_log_level;
|
||||
|
||||
typedef bool (*pxl8_log_handler)(const char* message);
|
||||
|
||||
typedef struct pxl8_log {
|
||||
pxl8_log_handler handler;
|
||||
pxl8_log_level level;
|
||||
} pxl8_log;
|
||||
|
||||
void pxl8_log_init(pxl8_log* log);
|
||||
void pxl8_log_set_handler(pxl8_log_handler handler);
|
||||
void pxl8_log_set_level(pxl8_log_level level);
|
||||
|
||||
void pxl8_log_write_trace(const char* file, int line, const char* fmt, ...);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_hal.h"
|
||||
#include "pxl8_io.h"
|
||||
#include "pxl8_net.h"
|
||||
#include "pxl8_sfx.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
|
|
@ -23,6 +24,7 @@ void pxl8_quit(pxl8* sys);
|
|||
f32 pxl8_get_fps(const pxl8* sys);
|
||||
pxl8_gfx* pxl8_get_gfx(const pxl8* sys);
|
||||
pxl8_input_state* pxl8_get_input(const pxl8* sys);
|
||||
pxl8_net* pxl8_get_net(const pxl8* sys);
|
||||
pxl8_size pxl8_get_resolution_dimensions(pxl8_resolution resolution);
|
||||
pxl8_sfx_mixer* pxl8_get_sfx_mixer(const pxl8* sys);
|
||||
bool pxl8_is_running(const pxl8* sys);
|
||||
|
|
|
|||
|
|
@ -87,11 +87,13 @@ pxl8.create_anim_from_ase = anim.Anim.from_ase
|
|||
pxl8.bounds = math.bounds
|
||||
|
||||
pxl8.Camera3D = gfx3d.Camera3D
|
||||
pxl8.Material = gfx3d.Material
|
||||
pxl8.Mesh = gfx3d.Mesh
|
||||
pxl8.begin_frame_3d = gfx3d.begin_frame
|
||||
pxl8.clear_3d = gfx3d.clear
|
||||
pxl8.clear_depth = gfx3d.clear_depth
|
||||
pxl8.create_camera_3d = gfx3d.Camera3D.new
|
||||
pxl8.create_material = gfx3d.create_material
|
||||
pxl8.create_mesh = gfx3d.Mesh.new
|
||||
pxl8.create_vec3_array = gfx3d.create_vec3_array
|
||||
pxl8.draw_line_3d = gfx3d.draw_line
|
||||
|
|
@ -132,10 +134,7 @@ pxl8.mat4_rotate_z = math.mat4_rotate_z
|
|||
pxl8.mat4_scale = math.mat4_scale
|
||||
pxl8.mat4_translate = math.mat4_translate
|
||||
|
||||
pxl8.Net = net.Net
|
||||
pxl8.create_net = net.Net.new
|
||||
pxl8.NET_MODE_LOCAL = net.MODE_LOCAL
|
||||
pxl8.NET_MODE_REMOTE = net.MODE_REMOTE
|
||||
pxl8.get_net = net.get
|
||||
|
||||
pxl8.pack_f32_be = bytes.pack_f32_be
|
||||
pxl8.pack_f32_le = bytes.pack_f32_le
|
||||
|
|
@ -221,7 +220,11 @@ pxl8.unpack_u32_le = bytes.unpack_u32_le
|
|||
pxl8.unpack_u64_be = bytes.unpack_u64_be
|
||||
pxl8.unpack_u64_le = bytes.unpack_u64_le
|
||||
|
||||
pxl8.Bsp = world.Bsp
|
||||
pxl8.Chunk = world.Chunk
|
||||
pxl8.CHUNK_BSP = world.CHUNK_BSP
|
||||
pxl8.CHUNK_VXL = world.CHUNK_VXL
|
||||
pxl8.World = world.World
|
||||
pxl8.create_world = world.World.new
|
||||
pxl8.get_world = world.World.get
|
||||
|
||||
return pxl8
|
||||
|
|
|
|||
|
|
@ -205,4 +205,27 @@ function gfx3d.create_vec3_array(count)
|
|||
return ffi.new("pxl8_vec3[?]", count)
|
||||
end
|
||||
|
||||
local Material = {}
|
||||
Material.__index = Material
|
||||
|
||||
function Material.new(opts)
|
||||
opts = opts or {}
|
||||
local mat = ffi.new("pxl8_gfx_material", {
|
||||
texture_id = opts.texture or 0,
|
||||
alpha = opts.alpha or 255,
|
||||
blend_mode = opts.blend_mode or 0,
|
||||
dither = opts.dither ~= false,
|
||||
double_sided = opts.double_sided or false,
|
||||
dynamic_lighting = opts.lighting or false,
|
||||
per_pixel = opts.per_pixel or false,
|
||||
vertex_color_passthrough = opts.passthrough or false,
|
||||
wireframe = opts.wireframe or false,
|
||||
emissive_intensity = opts.emissive or 0.0,
|
||||
})
|
||||
return setmetatable({ _ptr = mat }, Material)
|
||||
end
|
||||
|
||||
gfx3d.Material = Material
|
||||
gfx3d.create_material = Material.new
|
||||
|
||||
return gfx3d
|
||||
|
|
|
|||
|
|
@ -1,47 +1,24 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
local core = require("pxl8.core")
|
||||
|
||||
local net = {}
|
||||
|
||||
local Net = {}
|
||||
Net.__index = Net
|
||||
|
||||
net.MODE_LOCAL = C.PXL8_NET_LOCAL
|
||||
net.MODE_REMOTE = C.PXL8_NET_REMOTE
|
||||
|
||||
function Net.new(config)
|
||||
config = config or {}
|
||||
local cfg = ffi.new("pxl8_net_config")
|
||||
cfg.address = config.address or "127.0.0.1"
|
||||
cfg.mode = config.mode or C.PXL8_NET_REMOTE
|
||||
cfg.port = config.port or 7777
|
||||
|
||||
local n = C.pxl8_net_create(cfg)
|
||||
if n == nil then
|
||||
function net.get()
|
||||
local ptr = C.pxl8_get_net(core.sys)
|
||||
if ptr == nil then
|
||||
return nil
|
||||
end
|
||||
return setmetatable({ _ptr = n }, Net)
|
||||
end
|
||||
|
||||
function Net:connect()
|
||||
return C.pxl8_net_connect(self._ptr) == 0
|
||||
return setmetatable({ _ptr = ptr }, Net)
|
||||
end
|
||||
|
||||
function Net:connected()
|
||||
return C.pxl8_net_connected(self._ptr)
|
||||
end
|
||||
|
||||
function Net:destroy()
|
||||
if self._ptr then
|
||||
C.pxl8_net_destroy(self._ptr)
|
||||
self._ptr = nil
|
||||
end
|
||||
end
|
||||
|
||||
function Net:disconnect()
|
||||
C.pxl8_net_disconnect(self._ptr)
|
||||
end
|
||||
|
||||
function Net:entities()
|
||||
local snap = C.pxl8_net_snapshot(self._ptr)
|
||||
if snap == nil then
|
||||
|
|
@ -113,10 +90,6 @@ function Net:player_id()
|
|||
return tonumber(C.pxl8_net_player_id(self._ptr))
|
||||
end
|
||||
|
||||
function Net:poll()
|
||||
return C.pxl8_net_poll(self._ptr)
|
||||
end
|
||||
|
||||
function Net:predicted_state()
|
||||
return C.pxl8_net_predicted_state(self._ptr)
|
||||
end
|
||||
|
|
@ -173,10 +146,4 @@ function Net:tick()
|
|||
return tonumber(C.pxl8_net_tick(self._ptr))
|
||||
end
|
||||
|
||||
function Net:update(dt)
|
||||
C.pxl8_net_update(self._ptr, dt)
|
||||
end
|
||||
|
||||
net.Net = Net
|
||||
|
||||
return net
|
||||
|
|
|
|||
|
|
@ -4,40 +4,77 @@ local core = require("pxl8.core")
|
|||
|
||||
local world = {}
|
||||
|
||||
world.CHUNK_VXL = 0
|
||||
world.CHUNK_BSP = 1
|
||||
world.PROCGEN_ROOMS = C.PXL8_PROCGEN_ROOMS
|
||||
world.PROCGEN_TERRAIN = C.PXL8_PROCGEN_TERRAIN
|
||||
|
||||
local Bsp = {}
|
||||
Bsp.__index = Bsp
|
||||
|
||||
function Bsp:face_count()
|
||||
return C.pxl8_bsp_face_count(self._ptr)
|
||||
end
|
||||
|
||||
function Bsp:face_normal(face_id)
|
||||
return C.pxl8_bsp_face_normal(self._ptr, face_id)
|
||||
end
|
||||
|
||||
function Bsp:face_set_material(face_id, material_id)
|
||||
C.pxl8_bsp_face_set_material(self._ptr, face_id, material_id)
|
||||
end
|
||||
|
||||
function Bsp:set_material(material_id, material)
|
||||
C.pxl8_bsp_set_material(self._ptr, material_id, material._ptr)
|
||||
end
|
||||
|
||||
function Bsp:set_wireframe(wireframe)
|
||||
C.pxl8_bsp_set_wireframe(self._ptr, wireframe)
|
||||
end
|
||||
|
||||
world.Bsp = Bsp
|
||||
|
||||
local Chunk = {}
|
||||
Chunk.__index = Chunk
|
||||
|
||||
function Chunk:bsp()
|
||||
if self._ptr == nil then return nil end
|
||||
if self._ptr.type ~= world.CHUNK_BSP then return nil end
|
||||
local ptr = self._ptr.bsp
|
||||
if ptr == nil then return nil end
|
||||
return setmetatable({ _ptr = ptr }, Bsp)
|
||||
end
|
||||
|
||||
function Chunk:type()
|
||||
if self._ptr == nil then return nil end
|
||||
return self._ptr.type
|
||||
end
|
||||
|
||||
function Chunk:ready()
|
||||
if self._ptr == nil then return false end
|
||||
if self._ptr.type == world.CHUNK_BSP then
|
||||
return self._ptr.bsp ~= nil
|
||||
elseif self._ptr.type == world.CHUNK_VXL then
|
||||
return self._ptr.voxel ~= nil
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
world.Chunk = Chunk
|
||||
|
||||
local World = {}
|
||||
World.__index = World
|
||||
|
||||
function World.new()
|
||||
local w = C.pxl8_world_create()
|
||||
if w == nil then
|
||||
return nil
|
||||
end
|
||||
function World.get()
|
||||
local w = C.pxl8_get_world(core.sys)
|
||||
if w == nil then return nil end
|
||||
return setmetatable({ _ptr = w }, World)
|
||||
end
|
||||
|
||||
function World:apply_textures(texture_defs)
|
||||
local count = #texture_defs
|
||||
local textures = ffi.new("pxl8_world_texture[?]", count)
|
||||
|
||||
for i, def in ipairs(texture_defs) do
|
||||
local idx = i - 1
|
||||
ffi.copy(textures[idx].name, def.name or "", math.min(#(def.name or ""), 15))
|
||||
textures[idx].texture_id = def.texture_id or 0
|
||||
|
||||
if def.rule then
|
||||
textures[idx].rule = ffi.cast("bool (*)(const pxl8_vec3*, const pxl8_bsp_face*, const pxl8_bsp*)",
|
||||
function(normal, face, bsp)
|
||||
return def.rule(normal[0], face, bsp)
|
||||
end)
|
||||
else
|
||||
textures[idx].rule = nil
|
||||
end
|
||||
end
|
||||
|
||||
return C.pxl8_world_apply_textures(self._ptr, textures, count)
|
||||
function World:active_chunk()
|
||||
local ptr = C.pxl8_world_active_chunk(self._ptr)
|
||||
if ptr == nil then return nil end
|
||||
return setmetatable({ _ptr = ptr }, Chunk)
|
||||
end
|
||||
|
||||
function World:check_collision(x, y, z, radius)
|
||||
|
|
@ -45,34 +82,6 @@ function World:check_collision(x, y, z, radius)
|
|||
return C.pxl8_world_check_collision(self._ptr, pos, radius)
|
||||
end
|
||||
|
||||
function World:destroy()
|
||||
if self._ptr then
|
||||
C.pxl8_world_destroy(self._ptr)
|
||||
self._ptr = nil
|
||||
end
|
||||
end
|
||||
|
||||
function World:generate(params)
|
||||
local c_params = ffi.new("pxl8_procgen_params")
|
||||
c_params.type = params.type or C.PXL8_PROCGEN_ROOMS
|
||||
c_params.width = params.width or 32
|
||||
c_params.height = params.height or 32
|
||||
c_params.depth = params.depth or 0
|
||||
c_params.seed = params.seed or 0
|
||||
c_params.min_room_size = params.min_room_size or 5
|
||||
c_params.max_room_size = params.max_room_size or 10
|
||||
c_params.num_rooms = params.num_rooms or 8
|
||||
return C.pxl8_world_generate(self._ptr, core.gfx, c_params)
|
||||
end
|
||||
|
||||
function World:is_loaded()
|
||||
return C.pxl8_world_is_loaded(self._ptr)
|
||||
end
|
||||
|
||||
function World:load(filepath)
|
||||
return C.pxl8_world_load(self._ptr, filepath)
|
||||
end
|
||||
|
||||
function World:render(camera_pos)
|
||||
local vec = ffi.new("pxl8_vec3", {x = camera_pos[1], y = camera_pos[2], z = camera_pos[3]})
|
||||
C.pxl8_world_render(self._ptr, core.gfx, vec)
|
||||
|
|
@ -85,14 +94,6 @@ function World:resolve_collision(from_x, from_y, from_z, to_x, to_y, to_z, radiu
|
|||
return result.x, result.y, result.z
|
||||
end
|
||||
|
||||
function World:set_wireframe(enabled)
|
||||
C.pxl8_world_set_wireframe(self._ptr, enabled)
|
||||
end
|
||||
|
||||
function World:unload()
|
||||
C.pxl8_world_unload(self._ptr)
|
||||
end
|
||||
|
||||
world.World = World
|
||||
|
||||
return world
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
#include "pxl8_net.h"
|
||||
#include "pxl8_chunk_cache.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_mem.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
|
@ -28,6 +30,9 @@
|
|||
|
||||
struct pxl8_net {
|
||||
char address[256];
|
||||
u32 chunk_id;
|
||||
u8 chunk_type;
|
||||
pxl8_chunk_cache* chunk_cache;
|
||||
bool connected;
|
||||
pxl8_entity_state entities[PXL8_MAX_SNAPSHOT_ENTITIES];
|
||||
pxl8_event_msg events[PXL8_MAX_SNAPSHOT_EVENTS];
|
||||
|
|
@ -36,7 +41,6 @@ struct pxl8_net {
|
|||
u64 input_head;
|
||||
u64 input_oldest_tick;
|
||||
f32 interp_time;
|
||||
pxl8_net_mode mode;
|
||||
u16 port;
|
||||
u8 predicted_state[PXL8_NET_USERDATA_SIZE];
|
||||
u64 predicted_tick;
|
||||
|
|
@ -98,7 +102,6 @@ pxl8_net* pxl8_net_create(const pxl8_net_config* config) {
|
|||
pxl8_net* net = pxl8_calloc(1, sizeof(pxl8_net));
|
||||
if (!net) return NULL;
|
||||
|
||||
net->mode = config->mode;
|
||||
net->port = config->port ? config->port : PXL8_NET_DEFAULT_PORT;
|
||||
net->sock = INVALID_SOCK;
|
||||
net->connected = false;
|
||||
|
|
@ -209,6 +212,32 @@ bool pxl8_net_poll(pxl8_net* net) {
|
|||
|
||||
pxl8_msg_header hdr;
|
||||
usize offset = pxl8_protocol_deserialize_header(net->recv_buf, len, &hdr);
|
||||
|
||||
if (hdr.type == PXL8_MSG_CHUNK) {
|
||||
if (!net->chunk_cache) return false;
|
||||
|
||||
pxl8_chunk_msg_header chunk_hdr;
|
||||
offset += pxl8_protocol_deserialize_chunk_msg_header(net->recv_buf + offset, len - offset, &chunk_hdr);
|
||||
|
||||
const u8* payload = net->recv_buf + offset;
|
||||
usize payload_len = chunk_hdr.payload_size;
|
||||
if (payload_len > len - offset) {
|
||||
payload_len = len - offset;
|
||||
}
|
||||
|
||||
pxl8_chunk_cache_receive(net->chunk_cache, &chunk_hdr, payload, payload_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hdr.type == PXL8_MSG_CHUNK_ENTER) {
|
||||
pxl8_chunk_enter_msg chunk_msg;
|
||||
pxl8_protocol_deserialize_chunk_enter(net->recv_buf + offset, len - offset, &chunk_msg);
|
||||
net->chunk_id = chunk_msg.chunk_id;
|
||||
net->chunk_type = chunk_msg.chunk_type;
|
||||
pxl8_debug("[CLIENT] Received CHUNK_ENTER type=%u id=%u", chunk_msg.chunk_type, chunk_msg.chunk_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hdr.type != PXL8_MSG_SNAPSHOT) return false;
|
||||
|
||||
pxl8_snapshot_header snap;
|
||||
|
|
@ -311,3 +340,23 @@ void pxl8_net_update(pxl8_net* net, f32 dt) {
|
|||
if (!net) return;
|
||||
net->interp_time += dt;
|
||||
}
|
||||
|
||||
void pxl8_net_set_chunk_cache(pxl8_net* net, pxl8_chunk_cache* cache) {
|
||||
if (!net) return;
|
||||
net->chunk_cache = cache;
|
||||
}
|
||||
|
||||
pxl8_chunk_cache* pxl8_net_chunk_cache(pxl8_net* net) {
|
||||
if (!net) return NULL;
|
||||
return net->chunk_cache;
|
||||
}
|
||||
|
||||
u32 pxl8_net_chunk_id(const pxl8_net* net) {
|
||||
if (!net) return 0;
|
||||
return net->chunk_id;
|
||||
}
|
||||
|
||||
u8 pxl8_net_chunk_type(const pxl8_net* net) {
|
||||
if (!net) return PXL8_CHUNK_TYPE_VXL;
|
||||
return net->chunk_type;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,15 +11,10 @@ extern "C" {
|
|||
#define PXL8_NET_USERDATA_SIZE 56
|
||||
|
||||
typedef struct pxl8_net pxl8_net;
|
||||
|
||||
typedef enum pxl8_net_mode {
|
||||
PXL8_NET_LOCAL = 0,
|
||||
PXL8_NET_REMOTE
|
||||
} pxl8_net_mode;
|
||||
typedef struct pxl8_chunk_cache pxl8_chunk_cache;
|
||||
|
||||
typedef struct pxl8_net_config {
|
||||
const char* address;
|
||||
pxl8_net_mode mode;
|
||||
u16 port;
|
||||
} pxl8_net_config;
|
||||
|
||||
|
|
@ -48,6 +43,11 @@ pxl8_result pxl8_net_send_input(pxl8_net* net, const pxl8_input_msg* input);
|
|||
const pxl8_snapshot_header* pxl8_net_snapshot(const pxl8_net* net);
|
||||
u64 pxl8_net_tick(const pxl8_net* net);
|
||||
void pxl8_net_update(pxl8_net* net, f32 dt);
|
||||
void pxl8_net_set_chunk_cache(pxl8_net* net, pxl8_chunk_cache* cache);
|
||||
pxl8_chunk_cache* pxl8_net_chunk_cache(pxl8_net* net);
|
||||
|
||||
u32 pxl8_net_chunk_id(const pxl8_net* net);
|
||||
u8 pxl8_net_chunk_type(const pxl8_net* net);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,3 +122,76 @@ usize pxl8_protocol_deserialize_snapshot_header(const u8* buf, usize len, pxl8_s
|
|||
hdr->time = pxl8_read_f32_be(&s);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
usize pxl8_protocol_serialize_chunk_msg_header(const pxl8_chunk_msg_header* hdr, u8* buf, usize len) {
|
||||
if (len < 24) return 0;
|
||||
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
|
||||
pxl8_write_u8(&s, hdr->chunk_type);
|
||||
pxl8_write_u8(&s, hdr->flags);
|
||||
pxl8_write_u8(&s, hdr->fragment_idx);
|
||||
pxl8_write_u8(&s, hdr->fragment_count);
|
||||
pxl8_write_u32_be(&s, hdr->id);
|
||||
pxl8_write_u32_be(&s, (u32)hdr->cx);
|
||||
pxl8_write_u32_be(&s, (u32)hdr->cy);
|
||||
pxl8_write_u32_be(&s, (u32)hdr->cz);
|
||||
pxl8_write_u32_be(&s, hdr->version);
|
||||
pxl8_write_u16_be(&s, hdr->payload_size);
|
||||
pxl8_write_u16_be(&s, hdr->reserved);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
usize pxl8_protocol_deserialize_chunk_msg_header(const u8* buf, usize len, pxl8_chunk_msg_header* hdr) {
|
||||
if (len < 24) return 0;
|
||||
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
|
||||
hdr->chunk_type = pxl8_read_u8(&s);
|
||||
hdr->flags = pxl8_read_u8(&s);
|
||||
hdr->fragment_idx = pxl8_read_u8(&s);
|
||||
hdr->fragment_count = pxl8_read_u8(&s);
|
||||
hdr->id = pxl8_read_u32_be(&s);
|
||||
hdr->cx = (i32)pxl8_read_u32_be(&s);
|
||||
hdr->cy = (i32)pxl8_read_u32_be(&s);
|
||||
hdr->cz = (i32)pxl8_read_u32_be(&s);
|
||||
hdr->version = pxl8_read_u32_be(&s);
|
||||
hdr->payload_size = pxl8_read_u16_be(&s);
|
||||
hdr->reserved = pxl8_read_u16_be(&s);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
usize pxl8_protocol_deserialize_bsp_wire_header(const u8* buf, usize len, pxl8_bsp_wire_header* hdr) {
|
||||
if (len < 44) return 0;
|
||||
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
|
||||
hdr->num_vertices = pxl8_read_u32_be(&s);
|
||||
hdr->num_edges = pxl8_read_u32_be(&s);
|
||||
hdr->num_faces = pxl8_read_u32_be(&s);
|
||||
hdr->num_planes = pxl8_read_u32_be(&s);
|
||||
hdr->num_nodes = pxl8_read_u32_be(&s);
|
||||
hdr->num_leafs = pxl8_read_u32_be(&s);
|
||||
hdr->num_surfedges = pxl8_read_u32_be(&s);
|
||||
hdr->num_marksurfaces = pxl8_read_u32_be(&s);
|
||||
hdr->num_cell_portals = pxl8_read_u32_be(&s);
|
||||
hdr->visdata_size = pxl8_read_u32_be(&s);
|
||||
hdr->num_vertex_lights = pxl8_read_u32_be(&s);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
usize pxl8_protocol_serialize_chunk_enter(const pxl8_chunk_enter_msg* msg, u8* buf, usize len) {
|
||||
if (len < 8) return 0;
|
||||
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
|
||||
pxl8_write_u32_be(&s, msg->chunk_id);
|
||||
pxl8_write_u8(&s, msg->chunk_type);
|
||||
pxl8_write_u8(&s, msg->reserved[0]);
|
||||
pxl8_write_u8(&s, msg->reserved[1]);
|
||||
pxl8_write_u8(&s, msg->reserved[2]);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
usize pxl8_protocol_deserialize_chunk_enter(const u8* buf, usize len, pxl8_chunk_enter_msg* msg) {
|
||||
if (len < 8) return 0;
|
||||
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
|
||||
msg->chunk_id = pxl8_read_u32_be(&s);
|
||||
msg->chunk_type = pxl8_read_u8(&s);
|
||||
msg->reserved[0] = pxl8_read_u8(&s);
|
||||
msg->reserved[1] = pxl8_read_u8(&s);
|
||||
msg->reserved[2] = pxl8_read_u8(&s);
|
||||
return s.offset;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,12 +14,14 @@ extern "C" {
|
|||
|
||||
typedef enum pxl8_msg_type {
|
||||
PXL8_MSG_NONE = 0,
|
||||
PXL8_MSG_CHUNK,
|
||||
PXL8_MSG_CHUNK_ENTER,
|
||||
PXL8_MSG_COMMAND,
|
||||
PXL8_MSG_CONNECT,
|
||||
PXL8_MSG_DISCONNECT,
|
||||
PXL8_MSG_EVENT,
|
||||
PXL8_MSG_INPUT,
|
||||
PXL8_MSG_COMMAND,
|
||||
PXL8_MSG_SNAPSHOT,
|
||||
PXL8_MSG_EVENT
|
||||
PXL8_MSG_SNAPSHOT
|
||||
} pxl8_msg_type;
|
||||
|
||||
typedef struct pxl8_msg_header {
|
||||
|
|
@ -70,6 +72,39 @@ typedef struct pxl8_snapshot_header {
|
|||
f32 time;
|
||||
} pxl8_snapshot_header;
|
||||
|
||||
#define PXL8_CHUNK_TYPE_VXL 0
|
||||
#define PXL8_CHUNK_TYPE_BSP 1
|
||||
|
||||
#define PXL8_CHUNK_FLAG_RLE 0x01
|
||||
#define PXL8_CHUNK_FLAG_FINAL 0x04
|
||||
#define PXL8_CHUNK_MAX_PAYLOAD 1400
|
||||
|
||||
typedef struct pxl8_chunk_msg_header {
|
||||
u8 chunk_type;
|
||||
u8 flags;
|
||||
u8 fragment_idx;
|
||||
u8 fragment_count;
|
||||
u32 id;
|
||||
i32 cx, cy, cz;
|
||||
u32 version;
|
||||
u16 payload_size;
|
||||
u16 reserved;
|
||||
} pxl8_chunk_msg_header;
|
||||
|
||||
typedef struct pxl8_bsp_wire_header {
|
||||
u32 num_vertices;
|
||||
u32 num_edges;
|
||||
u32 num_faces;
|
||||
u32 num_planes;
|
||||
u32 num_nodes;
|
||||
u32 num_leafs;
|
||||
u32 num_surfedges;
|
||||
u32 num_marksurfaces;
|
||||
u32 num_cell_portals;
|
||||
u32 visdata_size;
|
||||
u32 num_vertex_lights;
|
||||
} pxl8_bsp_wire_header;
|
||||
|
||||
usize pxl8_protocol_serialize_header(const pxl8_msg_header* msg, u8* buf, usize len);
|
||||
usize pxl8_protocol_deserialize_header(const u8* buf, usize len, pxl8_msg_header* msg);
|
||||
|
||||
|
|
@ -88,6 +123,20 @@ usize pxl8_protocol_deserialize_event(const u8* buf, usize len, pxl8_event_msg*
|
|||
usize pxl8_protocol_serialize_snapshot_header(const pxl8_snapshot_header* hdr, u8* buf, usize len);
|
||||
usize pxl8_protocol_deserialize_snapshot_header(const u8* buf, usize len, pxl8_snapshot_header* hdr);
|
||||
|
||||
usize pxl8_protocol_serialize_chunk_msg_header(const pxl8_chunk_msg_header* hdr, u8* buf, usize len);
|
||||
usize pxl8_protocol_deserialize_chunk_msg_header(const u8* buf, usize len, pxl8_chunk_msg_header* hdr);
|
||||
|
||||
usize pxl8_protocol_deserialize_bsp_wire_header(const u8* buf, usize len, pxl8_bsp_wire_header* hdr);
|
||||
|
||||
typedef struct pxl8_chunk_enter_msg {
|
||||
u32 chunk_id;
|
||||
u8 chunk_type;
|
||||
u8 reserved[3];
|
||||
} pxl8_chunk_enter_msg;
|
||||
|
||||
usize pxl8_protocol_serialize_chunk_enter(const pxl8_chunk_enter_msg* msg, u8* buf, usize len);
|
||||
usize pxl8_protocol_deserialize_chunk_enter(const u8* buf, usize len, pxl8_chunk_enter_msg* msg);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
#include "pxl8_repl.h"
|
||||
#include "pxl8_mem.h"
|
||||
|
||||
#include <linenoise.h>
|
||||
#include <poll.h>
|
||||
#include <SDL3/SDL.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <linenoise.h>
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_mem.h"
|
||||
|
||||
#define PXL8_MAX_REPL_COMMAND_SIZE 4096
|
||||
#define PXL8_REPL_QUEUE_SIZE 8
|
||||
|
|
@ -235,6 +236,7 @@ pxl8_repl* pxl8_repl_create(void) {
|
|||
linenoiseHistoryLoad(".pxl8_history");
|
||||
|
||||
g_repl = repl;
|
||||
pxl8_log_set_handler(pxl8_repl_push_log);
|
||||
|
||||
repl->thread = SDL_CreateThread(pxl8_repl_thread, "pxl8-repl", repl);
|
||||
if (!repl->thread) {
|
||||
|
|
@ -260,6 +262,7 @@ void pxl8_repl_destroy(pxl8_repl* repl) {
|
|||
pxl8_repl_flush_logs(repl);
|
||||
|
||||
g_repl = NULL;
|
||||
pxl8_log_set_handler(NULL);
|
||||
|
||||
system("stty sane 2>/dev/null");
|
||||
pxl8_free(repl);
|
||||
|
|
|
|||
|
|
@ -424,27 +424,33 @@ static const char* pxl8_ffi_cdefs =
|
|||
"void pxl8_graph_eval_texture(const pxl8_graph* graph, u8* buffer, i32 width, i32 height);\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_bsp pxl8_bsp;\n"
|
||||
"typedef struct pxl8_bsp_face pxl8_bsp_face;\n"
|
||||
"typedef bool (*pxl8_texture_rule)(const pxl8_vec3* normal, const pxl8_bsp_face* face, const pxl8_bsp* bsp);\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_world_texture {\n"
|
||||
" char name[16];\n"
|
||||
" unsigned int texture_id;\n"
|
||||
" pxl8_texture_rule rule;\n"
|
||||
"} pxl8_world_texture;\n"
|
||||
"u32 pxl8_bsp_face_count(const pxl8_bsp* bsp);\n"
|
||||
"pxl8_vec3 pxl8_bsp_face_normal(const pxl8_bsp* bsp, u32 face_id);\n"
|
||||
"void pxl8_bsp_face_set_material(pxl8_bsp* bsp, u32 face_id, u16 material_id);\n"
|
||||
"void pxl8_bsp_set_material(pxl8_bsp* bsp, u16 material_id, const pxl8_gfx_material* material);\n"
|
||||
"void pxl8_bsp_set_wireframe(pxl8_bsp* bsp, bool wireframe);\n"
|
||||
"\n"
|
||||
"typedef enum { PXL8_CHUNK_VXL = 0, PXL8_CHUNK_BSP = 1 } pxl8_chunk_type;\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_chunk {\n"
|
||||
" pxl8_chunk_type type;\n"
|
||||
" u32 id;\n"
|
||||
" u32 version;\n"
|
||||
" i32 cx, cy, cz;\n"
|
||||
" union {\n"
|
||||
" void* voxel;\n"
|
||||
" pxl8_bsp* bsp;\n"
|
||||
" };\n"
|
||||
"} pxl8_chunk;\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_world pxl8_world;\n"
|
||||
"pxl8_world* pxl8_world_create(void);\n"
|
||||
"void pxl8_world_destroy(pxl8_world* world);\n"
|
||||
"int pxl8_world_generate(pxl8_world* world, pxl8_gfx* gfx, const pxl8_procgen_params* params);\n"
|
||||
"int pxl8_world_load(pxl8_world* world, const char* path);\n"
|
||||
"void pxl8_world_unload(pxl8_world* world);\n"
|
||||
"int pxl8_world_apply_textures(pxl8_world* world, const pxl8_world_texture* textures, unsigned int count);\n"
|
||||
"\n"
|
||||
"pxl8_world* pxl8_get_world(pxl8* sys);\n"
|
||||
"pxl8_chunk* pxl8_world_active_chunk(pxl8_world* world);\n"
|
||||
"bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, float radius);\n"
|
||||
"bool pxl8_world_is_loaded(const pxl8_world* world);\n"
|
||||
"void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);\n"
|
||||
"pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, float radius);\n"
|
||||
"void pxl8_world_set_wireframe(pxl8_world* world, bool enabled);\n"
|
||||
"\n"
|
||||
"typedef struct { i32 cursor_x; i32 cursor_y; bool cursor_down; bool cursor_clicked; u32 hot_id; u32 active_id; } pxl8_gui_state;\n"
|
||||
"pxl8_gui_state* pxl8_gui_state_create(void);\n"
|
||||
|
|
@ -517,8 +523,7 @@ static const char* pxl8_ffi_cdefs =
|
|||
"void pxl8_sfx_stop_voice(pxl8_sfx_context* ctx, u16 voice_id);\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_net pxl8_net;\n"
|
||||
"typedef enum pxl8_net_mode { PXL8_NET_LOCAL = 0, PXL8_NET_REMOTE } pxl8_net_mode;\n"
|
||||
"typedef struct pxl8_net_config { const char* address; pxl8_net_mode mode; u16 port; } pxl8_net_config;\n"
|
||||
"typedef struct pxl8_net_config { const char* address; u16 port; } pxl8_net_config;\n"
|
||||
"typedef enum pxl8_cmd_type { PXL8_CMD_NONE = 0, PXL8_CMD_SPAWN_ENTITY } pxl8_cmd_type;\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_command_msg {\n"
|
||||
|
|
@ -574,6 +579,7 @@ static const char* pxl8_ffi_cdefs =
|
|||
"const pxl8_snapshot_header* pxl8_net_snapshot(const pxl8_net* net);\n"
|
||||
"u64 pxl8_net_tick(const pxl8_net* net);\n"
|
||||
"void pxl8_net_update(pxl8_net* net, f32 dt);\n"
|
||||
"pxl8_net* pxl8_get_net(const pxl8* sys);\n"
|
||||
"\n"
|
||||
"void pxl8_bit_clear(u32* val, u8 bit);\n"
|
||||
"u32 pxl8_bit_count(u32 val);\n"
|
||||
|
|
|
|||
|
|
@ -394,6 +394,48 @@ i32 pxl8_bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos) {
|
|||
return -(node_id + 1);
|
||||
}
|
||||
|
||||
bool pxl8_bsp_point_solid(const pxl8_bsp* bsp, pxl8_vec3 pos) {
|
||||
i32 leaf = pxl8_bsp_find_leaf(bsp, pos);
|
||||
if (leaf < 0 || (u32)leaf >= bsp->num_leafs) return true;
|
||||
return bsp->leafs[leaf].contents == -1;
|
||||
}
|
||||
|
||||
static bool point_clear(const pxl8_bsp* bsp, f32 x, f32 y, f32 z, f32 radius) {
|
||||
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z})) return false;
|
||||
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x + radius, y, z})) return false;
|
||||
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x - radius, y, z})) return false;
|
||||
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z + radius})) return false;
|
||||
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z - radius})) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
pxl8_vec3 pxl8_bsp_trace(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to, f32 radius) {
|
||||
if (!bsp || bsp->num_nodes == 0) return to;
|
||||
|
||||
if (point_clear(bsp, to.x, to.y, to.z, radius)) {
|
||||
return to;
|
||||
}
|
||||
|
||||
bool x_ok = point_clear(bsp, to.x, from.y, from.z, radius);
|
||||
bool z_ok = point_clear(bsp, from.x, from.y, to.z, radius);
|
||||
|
||||
if (x_ok && z_ok) {
|
||||
f32 dx = to.x - from.x;
|
||||
f32 dz = to.z - from.z;
|
||||
if (dx * dx > dz * dz) {
|
||||
return (pxl8_vec3){to.x, from.y, from.z};
|
||||
} else {
|
||||
return (pxl8_vec3){from.x, from.y, to.z};
|
||||
}
|
||||
} else if (x_ok) {
|
||||
return (pxl8_vec3){to.x, from.y, from.z};
|
||||
} else if (z_ok) {
|
||||
return (pxl8_vec3){from.x, from.y, to.z};
|
||||
}
|
||||
|
||||
return from;
|
||||
}
|
||||
|
||||
bool pxl8_bsp_is_leaf_visible(const pxl8_bsp* bsp, i32 leaf_from, i32 leaf_to) {
|
||||
if (!bsp || !bsp->visdata || bsp->visdata_size == 0) return true;
|
||||
if (leaf_from < 0 || leaf_to < 0) return true;
|
||||
|
|
@ -576,12 +618,6 @@ static inline bool face_in_frustum(const pxl8_bsp* bsp, u32 face_id, const pxl8_
|
|||
return pxl8_frustum_test_aabb(frustum, face->aabb_min, face->aabb_max);
|
||||
}
|
||||
|
||||
static inline bool leaf_in_frustum(const pxl8_bsp_leaf* leaf, const pxl8_frustum* frustum) {
|
||||
pxl8_vec3 mins = {(f32)leaf->mins[0], (f32)leaf->mins[1], (f32)leaf->mins[2]};
|
||||
pxl8_vec3 maxs = {(f32)leaf->maxs[0], (f32)leaf->maxs[1], (f32)leaf->maxs[2]};
|
||||
return pxl8_frustum_test_aabb(frustum, mins, maxs);
|
||||
}
|
||||
|
||||
static inline bool screen_rect_valid(screen_rect r) {
|
||||
return r.x0 < r.x1 && r.y0 < r.y1;
|
||||
}
|
||||
|
|
@ -744,16 +780,24 @@ void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const
|
|||
}
|
||||
|
||||
void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos) {
|
||||
if (!gfx || !bsp || bsp->num_faces == 0) return;
|
||||
if (!bsp->cell_portals || bsp->num_cell_portals == 0) return;
|
||||
if (!bsp->materials || bsp->num_materials == 0) return;
|
||||
if (!gfx || !bsp || bsp->num_faces == 0) {
|
||||
return;
|
||||
}
|
||||
if (!bsp->cell_portals || bsp->num_cell_portals == 0) {
|
||||
pxl8_debug("[BSP] render: no cell_portals (ptr=%p count=%u)", (void*)bsp->cell_portals, bsp->num_cell_portals);
|
||||
return;
|
||||
}
|
||||
if (!bsp->materials || bsp->num_materials == 0) {
|
||||
pxl8_debug("[BSP] render: no materials (ptr=%p count=%u)", (void*)bsp->materials, bsp->num_materials);
|
||||
return;
|
||||
}
|
||||
|
||||
const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx);
|
||||
const pxl8_mat4* vp = pxl8_3d_get_view_proj(gfx);
|
||||
if (!frustum || !vp) return;
|
||||
|
||||
i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
|
||||
if (camera_leaf < 0 || (u32)camera_leaf >= bsp->num_cell_portals) return;
|
||||
if (camera_leaf < 0 || (u32)camera_leaf >= bsp->num_leafs) return;
|
||||
|
||||
pxl8_bsp* bsp_mut = (pxl8_bsp*)bsp;
|
||||
if (!bsp_mut->render_face_flags) {
|
||||
|
|
@ -837,15 +881,12 @@ void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos) {
|
|||
}
|
||||
|
||||
u32 current_material = 0xFFFFFFFF;
|
||||
u32 total_faces = 0;
|
||||
|
||||
for (u32 leaf_id = 0; leaf_id < bsp->num_leafs; leaf_id++) {
|
||||
u32 byte = leaf_id >> 3;
|
||||
u32 bit = leaf_id & 7;
|
||||
if (!(visited[byte] & (1 << bit))) continue;
|
||||
if (!(visited[leaf_id >> 3] & (1 << (leaf_id & 7)))) continue;
|
||||
if (bsp->leafs[leaf_id].contents == -1) continue;
|
||||
|
||||
const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id];
|
||||
if (!leaf_in_frustum(leaf, frustum)) continue;
|
||||
|
||||
for (u32 i = 0; i < leaf->num_marksurfaces; i++) {
|
||||
u32 surf_idx = leaf->first_marksurface + i;
|
||||
|
|
@ -862,7 +903,6 @@ void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos) {
|
|||
const pxl8_bsp_face* face = &bsp->faces[face_id];
|
||||
u32 material_id = face->material_id;
|
||||
if (material_id >= bsp->num_materials) continue;
|
||||
total_faces++;
|
||||
|
||||
if (material_id != current_material && mesh->index_count > 0 && current_material < bsp->num_materials) {
|
||||
pxl8_mat4 identity = pxl8_mat4_identity();
|
||||
|
|
@ -880,19 +920,61 @@ void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos) {
|
|||
pxl8_3d_draw_mesh(gfx, mesh, &identity, &bsp->materials[current_material]);
|
||||
}
|
||||
|
||||
static u32 debug_count = 0;
|
||||
if (debug_count++ < 5) {
|
||||
pxl8_debug("BSP render: %u faces, mesh verts=%u indices=%u", total_faces, mesh->vertex_count, mesh->index_count);
|
||||
if (mesh->vertex_count > 0) {
|
||||
pxl8_vertex* v = &mesh->vertices[0];
|
||||
pxl8_debug(" vert[0]: pos=(%.1f,%.1f,%.1f) uv=(%.2f,%.2f)",
|
||||
v->position.x, v->position.y, v->position.z, v->u, v->v);
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_bsp_pvs_destroy(&pvs);
|
||||
pxl8_free(visited);
|
||||
pxl8_free(cell_windows);
|
||||
pxl8_free(queue);
|
||||
pxl8_mesh_destroy(mesh);
|
||||
}
|
||||
|
||||
u32 pxl8_bsp_face_count(const pxl8_bsp* bsp) {
|
||||
if (!bsp) return 0;
|
||||
return bsp->num_faces;
|
||||
}
|
||||
|
||||
pxl8_vec3 pxl8_bsp_face_normal(const pxl8_bsp* bsp, u32 face_id) {
|
||||
pxl8_vec3 up = {0, 1, 0};
|
||||
if (!bsp || face_id >= bsp->num_faces) return up;
|
||||
|
||||
const pxl8_bsp_face* face = &bsp->faces[face_id];
|
||||
if (face->plane_id >= bsp->num_planes) return up;
|
||||
|
||||
pxl8_vec3 normal = bsp->planes[face->plane_id].normal;
|
||||
if (face->side) {
|
||||
normal.x = -normal.x;
|
||||
normal.y = -normal.y;
|
||||
normal.z = -normal.z;
|
||||
}
|
||||
return normal;
|
||||
}
|
||||
|
||||
void pxl8_bsp_face_set_material(pxl8_bsp* bsp, u32 face_id, u16 material_id) {
|
||||
if (!bsp || face_id >= bsp->num_faces) return;
|
||||
bsp->faces[face_id].material_id = material_id;
|
||||
}
|
||||
|
||||
void pxl8_bsp_set_material(pxl8_bsp* bsp, u16 material_id, const pxl8_gfx_material* material) {
|
||||
if (!bsp || !material) return;
|
||||
|
||||
if (material_id >= bsp->num_materials) {
|
||||
u32 new_count = material_id + 1;
|
||||
pxl8_gfx_material* new_materials = pxl8_realloc(bsp->materials, new_count * sizeof(pxl8_gfx_material));
|
||||
if (!new_materials) return;
|
||||
|
||||
for (u32 i = bsp->num_materials; i < new_count; i++) {
|
||||
memset(&new_materials[i], 0, sizeof(pxl8_gfx_material));
|
||||
}
|
||||
|
||||
bsp->materials = new_materials;
|
||||
bsp->num_materials = new_count;
|
||||
}
|
||||
|
||||
bsp->materials[material_id] = *material;
|
||||
}
|
||||
|
||||
void pxl8_bsp_set_wireframe(pxl8_bsp* bsp, bool wireframe) {
|
||||
if (!bsp || !bsp->materials) return;
|
||||
for (u32 i = 0; i < bsp->num_materials; i++) {
|
||||
bsp->materials[i].wireframe = wireframe;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,22 +136,25 @@ typedef struct pxl8_bsp {
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp);
|
||||
pxl8_bsp_pvs pxl8_bsp_decompress_pvs(const pxl8_bsp* bsp, i32 leaf);
|
||||
void pxl8_bsp_destroy(pxl8_bsp* bsp);
|
||||
|
||||
u32 pxl8_bsp_face_count(const pxl8_bsp* bsp);
|
||||
pxl8_vec3 pxl8_bsp_face_normal(const pxl8_bsp* bsp, u32 face_id);
|
||||
void pxl8_bsp_face_set_material(pxl8_bsp* bsp, u32 face_id, u16 material_id);
|
||||
i32 pxl8_bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos);
|
||||
bool pxl8_bsp_is_leaf_visible(const pxl8_bsp* bsp, i32 leaf_from, i32 leaf_to);
|
||||
|
||||
pxl8_bsp_pvs pxl8_bsp_decompress_pvs(const pxl8_bsp* bsp, i32 leaf);
|
||||
bool pxl8_bsp_point_solid(const pxl8_bsp* bsp, pxl8_vec3 pos);
|
||||
pxl8_vec3 pxl8_bsp_trace(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to, f32 radius);
|
||||
pxl8_bsp_lightmap pxl8_bsp_lightmap_mapped(u8 width, u8 height, u32 offset);
|
||||
pxl8_bsp_lightmap pxl8_bsp_lightmap_uniform(u8 r, u8 g, u8 b);
|
||||
pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp);
|
||||
void pxl8_bsp_pvs_destroy(pxl8_bsp_pvs* pvs);
|
||||
bool pxl8_bsp_pvs_is_visible(const pxl8_bsp_pvs* pvs, i32 leaf);
|
||||
|
||||
pxl8_bsp_lightmap pxl8_bsp_lightmap_uniform(u8 r, u8 g, u8 b);
|
||||
pxl8_bsp_lightmap pxl8_bsp_lightmap_mapped(u8 width, u8 height, u32 offset);
|
||||
pxl8_bsp_lightmap_sample pxl8_bsp_sample_lightmap(const pxl8_bsp* bsp, u32 face_idx, f32 u, f32 v);
|
||||
|
||||
void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const pxl8_gfx_material* material);
|
||||
void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos);
|
||||
void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const pxl8_gfx_material* material);
|
||||
pxl8_bsp_lightmap_sample pxl8_bsp_sample_lightmap(const pxl8_bsp* bsp, u32 face_idx, f32 u, f32 v);
|
||||
void pxl8_bsp_set_material(pxl8_bsp* bsp, u16 material_id, const pxl8_gfx_material* material);
|
||||
void pxl8_bsp_set_wireframe(pxl8_bsp* bsp, bool wireframe);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
|||
44
src/world/pxl8_chunk.c
Normal file
44
src/world/pxl8_chunk.c
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#include "pxl8_chunk.h"
|
||||
|
||||
#include "pxl8_mem.h"
|
||||
|
||||
pxl8_chunk* pxl8_chunk_create_vxl(i32 cx, i32 cy, i32 cz) {
|
||||
pxl8_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_chunk));
|
||||
if (!chunk) return NULL;
|
||||
|
||||
chunk->type = PXL8_CHUNK_VXL;
|
||||
chunk->cx = cx;
|
||||
chunk->cy = cy;
|
||||
chunk->cz = cz;
|
||||
chunk->voxel = pxl8_voxel_chunk_create();
|
||||
|
||||
if (!chunk->voxel) {
|
||||
pxl8_free(chunk);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
pxl8_chunk* pxl8_chunk_create_bsp(u32 id) {
|
||||
pxl8_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_chunk));
|
||||
if (!chunk) return NULL;
|
||||
|
||||
chunk->type = PXL8_CHUNK_BSP;
|
||||
chunk->id = id;
|
||||
chunk->bsp = NULL;
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
void pxl8_chunk_destroy(pxl8_chunk* chunk) {
|
||||
if (!chunk) return;
|
||||
|
||||
if (chunk->type == PXL8_CHUNK_VXL && chunk->voxel) {
|
||||
pxl8_voxel_chunk_destroy(chunk->voxel);
|
||||
} else if (chunk->type == PXL8_CHUNK_BSP && chunk->bsp) {
|
||||
pxl8_bsp_destroy(chunk->bsp);
|
||||
}
|
||||
|
||||
pxl8_free(chunk);
|
||||
}
|
||||
33
src/world/pxl8_chunk.h
Normal file
33
src/world/pxl8_chunk.h
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_bsp.h"
|
||||
#include "pxl8_types.h"
|
||||
#include "pxl8_voxel.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum pxl8_chunk_type {
|
||||
PXL8_CHUNK_VXL,
|
||||
PXL8_CHUNK_BSP
|
||||
} pxl8_chunk_type;
|
||||
|
||||
typedef struct pxl8_chunk {
|
||||
pxl8_chunk_type type;
|
||||
u32 id;
|
||||
u32 version;
|
||||
i32 cx, cy, cz;
|
||||
union {
|
||||
pxl8_voxel_chunk* voxel;
|
||||
pxl8_bsp* bsp;
|
||||
};
|
||||
} pxl8_chunk;
|
||||
|
||||
pxl8_chunk* pxl8_chunk_create_vxl(i32 cx, i32 cy, i32 cz);
|
||||
pxl8_chunk* pxl8_chunk_create_bsp(u32 id);
|
||||
void pxl8_chunk_destroy(pxl8_chunk* chunk);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
538
src/world/pxl8_chunk_cache.c
Normal file
538
src/world/pxl8_chunk_cache.c
Normal file
|
|
@ -0,0 +1,538 @@
|
|||
#include "pxl8_chunk_cache.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_bytes.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_mem.h"
|
||||
|
||||
#define PXL8_VOXEL_CHUNK_VOLUME (PXL8_VOXEL_CHUNK_SIZE * PXL8_VOXEL_CHUNK_SIZE * PXL8_VOXEL_CHUNK_SIZE)
|
||||
|
||||
static pxl8_chunk_cache_entry* find_entry_vxl(pxl8_chunk_cache* cache, i32 cx, i32 cy, i32 cz) {
|
||||
for (u32 i = 0; i < cache->entry_count; i++) {
|
||||
pxl8_chunk_cache_entry* e = &cache->entries[i];
|
||||
if (e->valid && e->chunk && e->chunk->type == PXL8_CHUNK_VXL &&
|
||||
e->chunk->cx == cx && e->chunk->cy == cy && e->chunk->cz == cz) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static pxl8_chunk_cache_entry* find_entry_bsp(pxl8_chunk_cache* cache, u32 id) {
|
||||
for (u32 i = 0; i < cache->entry_count; i++) {
|
||||
pxl8_chunk_cache_entry* e = &cache->entries[i];
|
||||
if (e->valid && e->chunk && e->chunk->type == PXL8_CHUNK_BSP &&
|
||||
e->chunk->id == id) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static pxl8_chunk_cache_entry* alloc_entry(pxl8_chunk_cache* cache) {
|
||||
if (cache->entry_count < PXL8_CHUNK_CACHE_SIZE) {
|
||||
pxl8_chunk_cache_entry* e = &cache->entries[cache->entry_count++];
|
||||
memset(e, 0, sizeof(*e));
|
||||
return e;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < PXL8_CHUNK_CACHE_SIZE; i++) {
|
||||
if (!cache->entries[i].valid) {
|
||||
pxl8_chunk_cache_entry* e = &cache->entries[i];
|
||||
memset(e, 0, sizeof(*e));
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
u64 oldest = cache->entries[0].last_used;
|
||||
u32 slot = 0;
|
||||
for (u32 i = 1; i < PXL8_CHUNK_CACHE_SIZE; i++) {
|
||||
if (cache->entries[i].last_used < oldest) {
|
||||
oldest = cache->entries[i].last_used;
|
||||
slot = i;
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_chunk_cache_entry* e = &cache->entries[slot];
|
||||
if (e->chunk) pxl8_chunk_destroy(e->chunk);
|
||||
if (e->mesh) pxl8_mesh_destroy(e->mesh);
|
||||
memset(e, 0, sizeof(*e));
|
||||
return e;
|
||||
}
|
||||
|
||||
static void assembly_reset(pxl8_chunk_assembly* a) {
|
||||
a->type = PXL8_CHUNK_VXL;
|
||||
a->id = 0;
|
||||
a->cx = 0;
|
||||
a->cy = 0;
|
||||
a->cz = 0;
|
||||
a->version = 0;
|
||||
a->fragment_count = 0;
|
||||
a->fragments_received = 0;
|
||||
a->data_size = 0;
|
||||
a->active = false;
|
||||
a->complete = false;
|
||||
}
|
||||
|
||||
static void assembly_init(pxl8_chunk_assembly* a, const pxl8_chunk_msg_header* hdr) {
|
||||
assembly_reset(a);
|
||||
a->type = hdr->chunk_type == PXL8_CHUNK_TYPE_BSP ? PXL8_CHUNK_BSP : PXL8_CHUNK_VXL;
|
||||
a->id = hdr->id;
|
||||
a->cx = hdr->cx;
|
||||
a->cy = hdr->cy;
|
||||
a->cz = hdr->cz;
|
||||
a->version = hdr->version;
|
||||
a->fragment_count = hdr->fragment_count;
|
||||
a->active = true;
|
||||
|
||||
u32 needed = PXL8_CHUNK_MAX_PAYLOAD * hdr->fragment_count;
|
||||
if (a->data_capacity < needed) {
|
||||
a->data_capacity = needed;
|
||||
a->data = pxl8_realloc(a->data, a->data_capacity);
|
||||
}
|
||||
}
|
||||
|
||||
static bool rle_decode_voxel(const u8* src, usize src_len, pxl8_voxel_chunk* chunk) {
|
||||
usize src_pos = 0;
|
||||
usize dst_pos = 0;
|
||||
|
||||
while (src_pos + 1 < src_len && dst_pos < PXL8_VOXEL_CHUNK_VOLUME) {
|
||||
u8 block = src[src_pos++];
|
||||
u8 run_minus_one = src[src_pos++];
|
||||
usize run = (usize)run_minus_one + 1;
|
||||
|
||||
for (usize i = 0; i < run && dst_pos < PXL8_VOXEL_CHUNK_VOLUME; i++) {
|
||||
i32 x = (i32)(dst_pos % PXL8_VOXEL_CHUNK_SIZE);
|
||||
i32 y = (i32)((dst_pos / PXL8_VOXEL_CHUNK_SIZE) % PXL8_VOXEL_CHUNK_SIZE);
|
||||
i32 z = (i32)(dst_pos / (PXL8_VOXEL_CHUNK_SIZE * PXL8_VOXEL_CHUNK_SIZE));
|
||||
pxl8_voxel_set(chunk, x, y, z, block);
|
||||
dst_pos++;
|
||||
}
|
||||
}
|
||||
|
||||
return dst_pos == PXL8_VOXEL_CHUNK_VOLUME;
|
||||
}
|
||||
|
||||
static pxl8_result deserialize_vertex(pxl8_stream* s, pxl8_bsp_vertex* v) {
|
||||
v->position.x = pxl8_read_f32_be(s);
|
||||
v->position.y = pxl8_read_f32_be(s);
|
||||
v->position.z = pxl8_read_f32_be(s);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result deserialize_edge(pxl8_stream* s, pxl8_bsp_edge* e) {
|
||||
e->vertex[0] = pxl8_read_u16_be(s);
|
||||
e->vertex[1] = pxl8_read_u16_be(s);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result deserialize_plane(pxl8_stream* s, pxl8_bsp_plane* p) {
|
||||
p->normal.x = pxl8_read_f32_be(s);
|
||||
p->normal.y = pxl8_read_f32_be(s);
|
||||
p->normal.z = pxl8_read_f32_be(s);
|
||||
p->dist = pxl8_read_f32_be(s);
|
||||
p->type = (i32)pxl8_read_u32_be(s);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result deserialize_face(pxl8_stream* s, pxl8_bsp_face* f) {
|
||||
f->first_edge = pxl8_read_u32_be(s);
|
||||
f->lightmap_offset = pxl8_read_u32_be(s);
|
||||
f->num_edges = pxl8_read_u16_be(s);
|
||||
f->plane_id = pxl8_read_u16_be(s);
|
||||
f->side = pxl8_read_u16_be(s);
|
||||
pxl8_read_bytes(s, f->styles, 4);
|
||||
f->material_id = pxl8_read_u16_be(s);
|
||||
f->aabb_min.x = pxl8_read_f32_be(s);
|
||||
f->aabb_min.y = pxl8_read_f32_be(s);
|
||||
f->aabb_min.z = pxl8_read_f32_be(s);
|
||||
f->aabb_max.x = pxl8_read_f32_be(s);
|
||||
f->aabb_max.y = pxl8_read_f32_be(s);
|
||||
f->aabb_max.z = pxl8_read_f32_be(s);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result deserialize_node(pxl8_stream* s, pxl8_bsp_node* n) {
|
||||
n->children[0] = (i32)pxl8_read_u32_be(s);
|
||||
n->children[1] = (i32)pxl8_read_u32_be(s);
|
||||
n->first_face = pxl8_read_u16_be(s);
|
||||
n->maxs[0] = (i16)pxl8_read_u16_be(s);
|
||||
n->maxs[1] = (i16)pxl8_read_u16_be(s);
|
||||
n->maxs[2] = (i16)pxl8_read_u16_be(s);
|
||||
n->mins[0] = (i16)pxl8_read_u16_be(s);
|
||||
n->mins[1] = (i16)pxl8_read_u16_be(s);
|
||||
n->mins[2] = (i16)pxl8_read_u16_be(s);
|
||||
n->num_faces = pxl8_read_u16_be(s);
|
||||
n->plane_id = pxl8_read_u32_be(s);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result deserialize_leaf(pxl8_stream* s, pxl8_bsp_leaf* l) {
|
||||
pxl8_read_bytes(s, l->ambient_level, 4);
|
||||
l->contents = (i32)pxl8_read_u32_be(s);
|
||||
l->first_marksurface = pxl8_read_u16_be(s);
|
||||
l->maxs[0] = (i16)pxl8_read_u16_be(s);
|
||||
l->maxs[1] = (i16)pxl8_read_u16_be(s);
|
||||
l->maxs[2] = (i16)pxl8_read_u16_be(s);
|
||||
l->mins[0] = (i16)pxl8_read_u16_be(s);
|
||||
l->mins[1] = (i16)pxl8_read_u16_be(s);
|
||||
l->mins[2] = (i16)pxl8_read_u16_be(s);
|
||||
l->num_marksurfaces = pxl8_read_u16_be(s);
|
||||
l->visofs = (i32)pxl8_read_u32_be(s);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result deserialize_portal(pxl8_stream* s, pxl8_bsp_portal* p) {
|
||||
p->x0 = pxl8_read_f32_be(s);
|
||||
p->z0 = pxl8_read_f32_be(s);
|
||||
p->x1 = pxl8_read_f32_be(s);
|
||||
p->z1 = pxl8_read_f32_be(s);
|
||||
p->target_leaf = pxl8_read_u32_be(s);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result deserialize_cell_portals(pxl8_stream* s, pxl8_bsp_cell_portals* cp) {
|
||||
cp->num_portals = pxl8_read_u8(s);
|
||||
pxl8_read_u8(s);
|
||||
pxl8_read_u8(s);
|
||||
pxl8_read_u8(s);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
deserialize_portal(s, &cp->portals[i]);
|
||||
}
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_bsp* assembly_to_bsp(pxl8_chunk_assembly* a) {
|
||||
if (!a->complete || a->data_size < 44) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pxl8_bsp* bsp = pxl8_calloc(1, sizeof(pxl8_bsp));
|
||||
if (!bsp) return NULL;
|
||||
|
||||
pxl8_stream s = pxl8_stream_create(a->data, (u32)a->data_size);
|
||||
|
||||
pxl8_bsp_wire_header wire_hdr;
|
||||
pxl8_protocol_deserialize_bsp_wire_header(a->data, 44, &wire_hdr);
|
||||
s.offset = 44;
|
||||
|
||||
pxl8_debug("[CLIENT] Wire header: verts=%u edges=%u faces=%u planes=%u nodes=%u leafs=%u surfedges=%u visdata=%u",
|
||||
wire_hdr.num_vertices, wire_hdr.num_edges, wire_hdr.num_faces,
|
||||
wire_hdr.num_planes, wire_hdr.num_nodes, wire_hdr.num_leafs,
|
||||
wire_hdr.num_surfedges, wire_hdr.visdata_size);
|
||||
|
||||
if (wire_hdr.num_vertices > 0) {
|
||||
bsp->vertices = pxl8_calloc(wire_hdr.num_vertices, sizeof(pxl8_bsp_vertex));
|
||||
bsp->num_vertices = wire_hdr.num_vertices;
|
||||
for (u32 i = 0; i < wire_hdr.num_vertices; i++) {
|
||||
deserialize_vertex(&s, &bsp->vertices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (wire_hdr.num_edges > 0) {
|
||||
bsp->edges = pxl8_calloc(wire_hdr.num_edges, sizeof(pxl8_bsp_edge));
|
||||
bsp->num_edges = wire_hdr.num_edges;
|
||||
for (u32 i = 0; i < wire_hdr.num_edges; i++) {
|
||||
deserialize_edge(&s, &bsp->edges[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (wire_hdr.num_surfedges > 0) {
|
||||
bsp->surfedges = pxl8_calloc(wire_hdr.num_surfedges, sizeof(i32));
|
||||
bsp->num_surfedges = wire_hdr.num_surfedges;
|
||||
for (u32 i = 0; i < wire_hdr.num_surfedges; i++) {
|
||||
bsp->surfedges[i] = (i32)pxl8_read_u32_be(&s);
|
||||
}
|
||||
}
|
||||
|
||||
if (wire_hdr.num_planes > 0) {
|
||||
bsp->planes = pxl8_calloc(wire_hdr.num_planes, sizeof(pxl8_bsp_plane));
|
||||
bsp->num_planes = wire_hdr.num_planes;
|
||||
for (u32 i = 0; i < wire_hdr.num_planes; i++) {
|
||||
deserialize_plane(&s, &bsp->planes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (wire_hdr.num_faces > 0) {
|
||||
bsp->faces = pxl8_calloc(wire_hdr.num_faces, sizeof(pxl8_bsp_face));
|
||||
bsp->num_faces = wire_hdr.num_faces;
|
||||
for (u32 i = 0; i < wire_hdr.num_faces; i++) {
|
||||
deserialize_face(&s, &bsp->faces[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (wire_hdr.num_nodes > 0) {
|
||||
bsp->nodes = pxl8_calloc(wire_hdr.num_nodes, sizeof(pxl8_bsp_node));
|
||||
bsp->num_nodes = wire_hdr.num_nodes;
|
||||
for (u32 i = 0; i < wire_hdr.num_nodes; i++) {
|
||||
deserialize_node(&s, &bsp->nodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (wire_hdr.num_leafs > 0) {
|
||||
bsp->leafs = pxl8_calloc(wire_hdr.num_leafs, sizeof(pxl8_bsp_leaf));
|
||||
bsp->num_leafs = wire_hdr.num_leafs;
|
||||
for (u32 i = 0; i < wire_hdr.num_leafs; i++) {
|
||||
deserialize_leaf(&s, &bsp->leafs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (wire_hdr.num_marksurfaces > 0) {
|
||||
bsp->marksurfaces = pxl8_calloc(wire_hdr.num_marksurfaces, sizeof(u16));
|
||||
bsp->num_marksurfaces = wire_hdr.num_marksurfaces;
|
||||
for (u32 i = 0; i < wire_hdr.num_marksurfaces; i++) {
|
||||
bsp->marksurfaces[i] = pxl8_read_u16_be(&s);
|
||||
}
|
||||
}
|
||||
|
||||
if (wire_hdr.num_cell_portals > 0) {
|
||||
bsp->cell_portals = pxl8_calloc(wire_hdr.num_cell_portals, sizeof(pxl8_bsp_cell_portals));
|
||||
bsp->num_cell_portals = wire_hdr.num_cell_portals;
|
||||
for (u32 i = 0; i < wire_hdr.num_cell_portals; i++) {
|
||||
deserialize_cell_portals(&s, &bsp->cell_portals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (wire_hdr.visdata_size > 0) {
|
||||
bsp->visdata = pxl8_malloc(wire_hdr.visdata_size);
|
||||
bsp->visdata_size = wire_hdr.visdata_size;
|
||||
pxl8_read_bytes(&s, bsp->visdata, wire_hdr.visdata_size);
|
||||
}
|
||||
|
||||
if (wire_hdr.num_vertex_lights > 0) {
|
||||
bsp->vertex_lights = pxl8_calloc(wire_hdr.num_vertex_lights, sizeof(u32));
|
||||
bsp->num_vertex_lights = wire_hdr.num_vertex_lights;
|
||||
for (u32 i = 0; i < wire_hdr.num_vertex_lights; i++) {
|
||||
bsp->vertex_lights[i] = pxl8_read_u32_be(&s);
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_debug("Deserialized BSP: %u verts, %u faces, %u nodes, %u leafs",
|
||||
bsp->num_vertices, bsp->num_faces, bsp->num_nodes, bsp->num_leafs);
|
||||
|
||||
return bsp;
|
||||
}
|
||||
|
||||
static pxl8_result assemble_vxl(pxl8_chunk_cache* cache, pxl8_chunk_assembly* a) {
|
||||
pxl8_chunk_cache_entry* entry = find_entry_vxl(cache, a->cx, a->cy, a->cz);
|
||||
if (!entry) {
|
||||
entry = alloc_entry(cache);
|
||||
entry->chunk = pxl8_chunk_create_vxl(a->cx, a->cy, a->cz);
|
||||
entry->valid = true;
|
||||
}
|
||||
|
||||
entry->chunk->version = a->version;
|
||||
entry->mesh_dirty = true;
|
||||
entry->last_used = cache->frame_counter;
|
||||
|
||||
if (entry->mesh) {
|
||||
pxl8_mesh_destroy(entry->mesh);
|
||||
entry->mesh = NULL;
|
||||
}
|
||||
|
||||
pxl8_voxel_chunk_clear(entry->chunk->voxel);
|
||||
|
||||
if (!rle_decode_voxel(a->data, a->data_size, entry->chunk->voxel)) {
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
assembly_reset(a);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result assemble_bsp(pxl8_chunk_cache* cache, pxl8_chunk_assembly* a) {
|
||||
pxl8_debug("[CLIENT] assemble_bsp: id=%u data_size=%zu", a->id, a->data_size);
|
||||
pxl8_bsp* bsp = assembly_to_bsp(a);
|
||||
if (!bsp) {
|
||||
pxl8_debug("[CLIENT] assemble_bsp: assembly_to_bsp returned NULL!");
|
||||
assembly_reset(a);
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
pxl8_debug("[CLIENT] assemble_bsp: BSP created with %u verts %u faces", bsp->num_vertices, bsp->num_faces);
|
||||
|
||||
pxl8_chunk_cache_entry* entry = find_entry_bsp(cache, a->id);
|
||||
if (entry) {
|
||||
if (entry->chunk && entry->chunk->bsp) {
|
||||
pxl8_bsp_destroy(entry->chunk->bsp);
|
||||
}
|
||||
} else {
|
||||
entry = alloc_entry(cache);
|
||||
entry->chunk = pxl8_chunk_create_bsp(a->id);
|
||||
entry->valid = true;
|
||||
}
|
||||
|
||||
entry->chunk->bsp = bsp;
|
||||
entry->chunk->version = a->version;
|
||||
entry->last_used = cache->frame_counter;
|
||||
|
||||
assembly_reset(a);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_chunk_cache* pxl8_chunk_cache_create(void) {
|
||||
pxl8_chunk_cache* cache = pxl8_calloc(1, sizeof(pxl8_chunk_cache));
|
||||
if (!cache) return NULL;
|
||||
assembly_reset(&cache->assembly);
|
||||
return cache;
|
||||
}
|
||||
|
||||
void pxl8_chunk_cache_destroy(pxl8_chunk_cache* cache) {
|
||||
if (!cache) return;
|
||||
|
||||
for (u32 i = 0; i < cache->entry_count; i++) {
|
||||
pxl8_chunk_cache_entry* e = &cache->entries[i];
|
||||
if (e->chunk) pxl8_chunk_destroy(e->chunk);
|
||||
if (e->mesh) pxl8_mesh_destroy(e->mesh);
|
||||
}
|
||||
|
||||
pxl8_free(cache->assembly.data);
|
||||
pxl8_free(cache);
|
||||
}
|
||||
|
||||
pxl8_result pxl8_chunk_cache_receive(pxl8_chunk_cache* cache,
|
||||
const pxl8_chunk_msg_header* hdr,
|
||||
const u8* payload, usize len) {
|
||||
if (!cache || !hdr || !payload) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
pxl8_chunk_assembly* a = &cache->assembly;
|
||||
|
||||
bool new_assembly = !a->active ||
|
||||
(hdr->chunk_type == PXL8_CHUNK_TYPE_BSP && a->id != hdr->id) ||
|
||||
(hdr->chunk_type == PXL8_CHUNK_TYPE_VXL &&
|
||||
(a->cx != hdr->cx || a->cy != hdr->cy || a->cz != hdr->cz)) ||
|
||||
a->version != hdr->version ||
|
||||
hdr->fragment_idx == 0;
|
||||
|
||||
if (new_assembly) {
|
||||
assembly_init(a, hdr);
|
||||
}
|
||||
|
||||
if (hdr->fragment_idx >= PXL8_CHUNK_MAX_FRAGMENTS) {
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
u32 offset = (u32)hdr->fragment_idx * PXL8_CHUNK_MAX_PAYLOAD;
|
||||
u32 required = offset + (u32)len;
|
||||
|
||||
if (required > a->data_capacity) {
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
memcpy(a->data + offset, payload, len);
|
||||
|
||||
if (required > a->data_size) {
|
||||
a->data_size = required;
|
||||
}
|
||||
|
||||
a->fragments_received++;
|
||||
|
||||
if (hdr->flags & PXL8_CHUNK_FLAG_FINAL) {
|
||||
a->complete = true;
|
||||
pxl8_debug("[CLIENT] Final fragment received, assembling type=%d data_size=%zu", a->type, a->data_size);
|
||||
|
||||
if (a->type == PXL8_CHUNK_BSP) {
|
||||
return assemble_bsp(cache, a);
|
||||
} else {
|
||||
return assemble_vxl(cache, a);
|
||||
}
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_chunk* pxl8_chunk_cache_get_vxl(pxl8_chunk_cache* cache, i32 cx, i32 cy, i32 cz) {
|
||||
if (!cache) return NULL;
|
||||
pxl8_chunk_cache_entry* e = find_entry_vxl(cache, cx, cy, cz);
|
||||
if (e) {
|
||||
e->last_used = cache->frame_counter;
|
||||
return e->chunk;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pxl8_chunk* pxl8_chunk_cache_get_bsp(pxl8_chunk_cache* cache, u32 id) {
|
||||
if (!cache) return NULL;
|
||||
pxl8_chunk_cache_entry* e = find_entry_bsp(cache, id);
|
||||
if (e) {
|
||||
e->last_used = cache->frame_counter;
|
||||
return e->chunk;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pxl8_mesh* pxl8_chunk_cache_get_mesh(pxl8_chunk_cache* cache,
|
||||
i32 cx, i32 cy, i32 cz,
|
||||
const pxl8_block_registry* registry,
|
||||
const pxl8_voxel_mesh_config* config) {
|
||||
if (!cache || !registry) return NULL;
|
||||
|
||||
pxl8_chunk_cache_entry* entry = find_entry_vxl(cache, cx, cy, cz);
|
||||
if (!entry || !entry->chunk || !entry->chunk->voxel) return NULL;
|
||||
|
||||
if (entry->mesh && !entry->mesh_dirty) {
|
||||
return entry->mesh;
|
||||
}
|
||||
|
||||
if (entry->mesh) {
|
||||
pxl8_mesh_destroy(entry->mesh);
|
||||
entry->mesh = NULL;
|
||||
}
|
||||
|
||||
pxl8_chunk* nx = pxl8_chunk_cache_get_vxl(cache, cx - 1, cy, cz);
|
||||
pxl8_chunk* px = pxl8_chunk_cache_get_vxl(cache, cx + 1, cy, cz);
|
||||
pxl8_chunk* ny = pxl8_chunk_cache_get_vxl(cache, cx, cy - 1, cz);
|
||||
pxl8_chunk* py = pxl8_chunk_cache_get_vxl(cache, cx, cy + 1, cz);
|
||||
pxl8_chunk* nz = pxl8_chunk_cache_get_vxl(cache, cx, cy, cz - 1);
|
||||
pxl8_chunk* pz = pxl8_chunk_cache_get_vxl(cache, cx, cy, cz + 1);
|
||||
|
||||
const pxl8_voxel_chunk* neighbors[6] = {
|
||||
nx ? nx->voxel : NULL,
|
||||
px ? px->voxel : NULL,
|
||||
ny ? ny->voxel : NULL,
|
||||
py ? py->voxel : NULL,
|
||||
nz ? nz->voxel : NULL,
|
||||
pz ? pz->voxel : NULL
|
||||
};
|
||||
|
||||
entry->mesh = pxl8_voxel_build_mesh(entry->chunk->voxel, neighbors, registry, config);
|
||||
entry->mesh_dirty = false;
|
||||
|
||||
return entry->mesh;
|
||||
}
|
||||
|
||||
void pxl8_chunk_cache_tick(pxl8_chunk_cache* cache) {
|
||||
if (!cache) return;
|
||||
cache->frame_counter++;
|
||||
}
|
||||
|
||||
void pxl8_chunk_cache_evict_distant(pxl8_chunk_cache* cache,
|
||||
i32 cx, i32 cy, i32 cz, i32 radius) {
|
||||
if (!cache) return;
|
||||
|
||||
for (u32 i = 0; i < cache->entry_count; i++) {
|
||||
pxl8_chunk_cache_entry* e = &cache->entries[i];
|
||||
if (!e->valid || !e->chunk || e->chunk->type != PXL8_CHUNK_VXL) continue;
|
||||
|
||||
i32 dx = e->chunk->cx - cx;
|
||||
i32 dy = e->chunk->cy - cy;
|
||||
i32 dz = e->chunk->cz - cz;
|
||||
|
||||
if (abs(dx) > radius || abs(dy) > radius || abs(dz) > radius) {
|
||||
pxl8_chunk_destroy(e->chunk);
|
||||
if (e->mesh) pxl8_mesh_destroy(e->mesh);
|
||||
e->chunk = NULL;
|
||||
e->mesh = NULL;
|
||||
e->valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_chunk_cache_invalidate_meshes(pxl8_chunk_cache* cache) {
|
||||
if (!cache) return;
|
||||
|
||||
for (u32 i = 0; i < cache->entry_count; i++) {
|
||||
cache->entries[i].mesh_dirty = true;
|
||||
}
|
||||
}
|
||||
67
src/world/pxl8_chunk_cache.h
Normal file
67
src/world/pxl8_chunk_cache.h
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_chunk.h"
|
||||
#include "pxl8_mesh.h"
|
||||
#include "pxl8_protocol.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define PXL8_CHUNK_CACHE_SIZE 64
|
||||
#define PXL8_CHUNK_MAX_FRAGMENTS 64
|
||||
#define PXL8_CHUNK_MAX_DATA_SIZE 131072
|
||||
|
||||
typedef struct pxl8_chunk_cache_entry {
|
||||
pxl8_chunk* chunk;
|
||||
pxl8_mesh* mesh;
|
||||
u64 last_used;
|
||||
bool mesh_dirty;
|
||||
bool valid;
|
||||
} pxl8_chunk_cache_entry;
|
||||
|
||||
typedef struct pxl8_chunk_assembly {
|
||||
pxl8_chunk_type type;
|
||||
u32 id;
|
||||
i32 cx, cy, cz;
|
||||
u32 version;
|
||||
u8 fragment_count;
|
||||
u8 fragments_received;
|
||||
u8* data;
|
||||
u32 data_size;
|
||||
u32 data_capacity;
|
||||
bool active;
|
||||
bool complete;
|
||||
} pxl8_chunk_assembly;
|
||||
|
||||
typedef struct pxl8_chunk_cache {
|
||||
pxl8_chunk_cache_entry entries[PXL8_CHUNK_CACHE_SIZE];
|
||||
pxl8_chunk_assembly assembly;
|
||||
u32 entry_count;
|
||||
u64 frame_counter;
|
||||
} pxl8_chunk_cache;
|
||||
|
||||
pxl8_chunk_cache* pxl8_chunk_cache_create(void);
|
||||
void pxl8_chunk_cache_destroy(pxl8_chunk_cache* cache);
|
||||
|
||||
pxl8_result pxl8_chunk_cache_receive(pxl8_chunk_cache* cache,
|
||||
const pxl8_chunk_msg_header* hdr,
|
||||
const u8* payload, usize len);
|
||||
|
||||
pxl8_chunk* pxl8_chunk_cache_get_vxl(pxl8_chunk_cache* cache, i32 cx, i32 cy, i32 cz);
|
||||
pxl8_chunk* pxl8_chunk_cache_get_bsp(pxl8_chunk_cache* cache, u32 id);
|
||||
|
||||
pxl8_mesh* pxl8_chunk_cache_get_mesh(pxl8_chunk_cache* cache,
|
||||
i32 cx, i32 cy, i32 cz,
|
||||
const pxl8_block_registry* registry,
|
||||
const pxl8_voxel_mesh_config* config);
|
||||
|
||||
void pxl8_chunk_cache_tick(pxl8_chunk_cache* cache);
|
||||
void pxl8_chunk_cache_evict_distant(pxl8_chunk_cache* cache,
|
||||
i32 cx, i32 cy, i32 cz, i32 radius);
|
||||
void pxl8_chunk_cache_invalidate_meshes(pxl8_chunk_cache* cache);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
496
src/world/pxl8_entity.c
Normal file
496
src/world/pxl8_entity.c
Normal file
|
|
@ -0,0 +1,496 @@
|
|||
#include "pxl8_entity.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_mem.h"
|
||||
|
||||
#define PXL8_ENTITY_COMPONENT_NAME_MAX 32
|
||||
#define PXL8_ENTITY_RELATIONSHIP_NAME_MAX 32
|
||||
|
||||
typedef struct pxl8_component_type {
|
||||
char name[PXL8_ENTITY_COMPONENT_NAME_MAX];
|
||||
u32 size;
|
||||
} pxl8_component_type;
|
||||
|
||||
typedef struct pxl8_component_storage {
|
||||
u32* sparse;
|
||||
void* dense_data;
|
||||
pxl8_entity* dense_entities;
|
||||
u32 count;
|
||||
} pxl8_component_storage;
|
||||
|
||||
typedef struct pxl8_relationship_type {
|
||||
char name[PXL8_ENTITY_RELATIONSHIP_NAME_MAX];
|
||||
} pxl8_relationship_type;
|
||||
|
||||
typedef struct pxl8_relationship_entry {
|
||||
pxl8_entity subject;
|
||||
pxl8_entity object;
|
||||
pxl8_entity_relationship rel;
|
||||
u32 next_by_subject;
|
||||
u32 next_by_object;
|
||||
} pxl8_relationship_entry;
|
||||
|
||||
struct pxl8_entity_pool {
|
||||
u32* generations;
|
||||
u32* free_list;
|
||||
u32 free_count;
|
||||
u32 capacity;
|
||||
u32 alive_count;
|
||||
|
||||
pxl8_component_type* component_types;
|
||||
pxl8_component_storage* component_storage;
|
||||
u32 component_type_count;
|
||||
u32 component_type_capacity;
|
||||
|
||||
pxl8_relationship_type* relationship_types;
|
||||
u32 relationship_type_count;
|
||||
u32 relationship_type_capacity;
|
||||
|
||||
pxl8_relationship_entry* relationships;
|
||||
u32* rel_by_subject;
|
||||
u32* rel_by_object;
|
||||
u32 relationship_count;
|
||||
u32 relationship_capacity;
|
||||
};
|
||||
|
||||
pxl8_entity_pool* pxl8_entity_pool_create(u32 capacity) {
|
||||
pxl8_entity_pool* pool = pxl8_calloc(1, sizeof(pxl8_entity_pool));
|
||||
if (!pool) return NULL;
|
||||
|
||||
pool->capacity = capacity;
|
||||
pool->generations = pxl8_calloc(capacity, sizeof(u32));
|
||||
pool->free_list = pxl8_malloc(capacity * sizeof(u32));
|
||||
|
||||
if (!pool->generations || !pool->free_list) {
|
||||
pxl8_entity_pool_destroy(pool);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < capacity; i++) {
|
||||
pool->free_list[i] = capacity - 1 - i;
|
||||
}
|
||||
pool->free_count = capacity;
|
||||
|
||||
pool->component_type_capacity = 16;
|
||||
pool->component_types = pxl8_calloc(pool->component_type_capacity, sizeof(pxl8_component_type));
|
||||
pool->component_storage = pxl8_calloc(pool->component_type_capacity, sizeof(pxl8_component_storage));
|
||||
|
||||
pool->relationship_type_capacity = 16;
|
||||
pool->relationship_types = pxl8_calloc(pool->relationship_type_capacity, sizeof(pxl8_relationship_type));
|
||||
|
||||
pool->relationship_capacity = 256;
|
||||
pool->relationships = pxl8_malloc(pool->relationship_capacity * sizeof(pxl8_relationship_entry));
|
||||
pool->rel_by_subject = pxl8_malloc(capacity * sizeof(u32));
|
||||
pool->rel_by_object = pxl8_malloc(capacity * sizeof(u32));
|
||||
|
||||
for (u32 i = 0; i < capacity; i++) {
|
||||
pool->rel_by_subject[i] = UINT32_MAX;
|
||||
pool->rel_by_object[i] = UINT32_MAX;
|
||||
}
|
||||
|
||||
return pool;
|
||||
}
|
||||
|
||||
void pxl8_entity_pool_clear(pxl8_entity_pool* pool) {
|
||||
if (!pool) return;
|
||||
|
||||
for (u32 i = 0; i < pool->capacity; i++) {
|
||||
pool->generations[i] = 0;
|
||||
pool->free_list[i] = pool->capacity - 1 - i;
|
||||
pool->rel_by_subject[i] = UINT32_MAX;
|
||||
pool->rel_by_object[i] = UINT32_MAX;
|
||||
}
|
||||
pool->free_count = pool->capacity;
|
||||
pool->alive_count = 0;
|
||||
|
||||
for (u32 i = 0; i < pool->component_type_count; i++) {
|
||||
pool->component_storage[i].count = 0;
|
||||
}
|
||||
|
||||
pool->relationship_count = 0;
|
||||
}
|
||||
|
||||
void pxl8_entity_pool_destroy(pxl8_entity_pool* pool) {
|
||||
if (!pool) return;
|
||||
|
||||
for (u32 i = 0; i < pool->component_type_count; i++) {
|
||||
pxl8_free(pool->component_storage[i].sparse);
|
||||
pxl8_free(pool->component_storage[i].dense_data);
|
||||
pxl8_free(pool->component_storage[i].dense_entities);
|
||||
}
|
||||
|
||||
pxl8_free(pool->component_types);
|
||||
pxl8_free(pool->component_storage);
|
||||
pxl8_free(pool->relationship_types);
|
||||
pxl8_free(pool->relationships);
|
||||
pxl8_free(pool->rel_by_subject);
|
||||
pxl8_free(pool->rel_by_object);
|
||||
pxl8_free(pool->generations);
|
||||
pxl8_free(pool->free_list);
|
||||
pxl8_free(pool);
|
||||
}
|
||||
|
||||
pxl8_entity pxl8_entity_spawn(pxl8_entity_pool* pool) {
|
||||
if (!pool || pool->free_count == 0) return PXL8_ENTITY_INVALID;
|
||||
|
||||
u32 idx = pool->free_list[--pool->free_count];
|
||||
pool->generations[idx]++;
|
||||
pool->alive_count++;
|
||||
|
||||
return (pxl8_entity){ .idx = idx, .gen = pool->generations[idx] };
|
||||
}
|
||||
|
||||
void pxl8_entity_despawn(pxl8_entity_pool* pool, pxl8_entity e) {
|
||||
if (!pool || !pxl8_entity_alive(pool, e)) return;
|
||||
|
||||
for (u32 i = 0; i < pool->component_type_count; i++) {
|
||||
pxl8_entity_component_remove(pool, e, i + 1);
|
||||
}
|
||||
|
||||
pool->free_list[pool->free_count++] = e.idx;
|
||||
pool->generations[e.idx]++;
|
||||
pool->alive_count--;
|
||||
}
|
||||
|
||||
bool pxl8_entity_alive(const pxl8_entity_pool* pool, pxl8_entity e) {
|
||||
if (!pool || e.idx >= pool->capacity) return false;
|
||||
return pool->generations[e.idx] == e.gen && e.gen != 0;
|
||||
}
|
||||
|
||||
u32 pxl8_entity_count(const pxl8_entity_pool* pool) {
|
||||
return pool ? pool->alive_count : 0;
|
||||
}
|
||||
|
||||
pxl8_entity_component pxl8_entity_component_register(pxl8_entity_pool* pool, const char* name, u32 size) {
|
||||
if (!pool || !name || size == 0) return PXL8_ENTITY_COMPONENT_INVALID;
|
||||
|
||||
pxl8_entity_component existing = pxl8_entity_component_find(pool, name);
|
||||
if (existing != PXL8_ENTITY_COMPONENT_INVALID) return existing;
|
||||
|
||||
if (pool->component_type_count >= pool->component_type_capacity) {
|
||||
u32 new_capacity = pool->component_type_capacity * 2;
|
||||
pxl8_component_type* new_types = pxl8_realloc(pool->component_types, new_capacity * sizeof(pxl8_component_type));
|
||||
pxl8_component_storage* new_storage = pxl8_realloc(pool->component_storage, new_capacity * sizeof(pxl8_component_storage));
|
||||
if (!new_types || !new_storage) return PXL8_ENTITY_COMPONENT_INVALID;
|
||||
|
||||
pool->component_types = new_types;
|
||||
pool->component_storage = new_storage;
|
||||
memset(&pool->component_types[pool->component_type_capacity], 0, (new_capacity - pool->component_type_capacity) * sizeof(pxl8_component_type));
|
||||
memset(&pool->component_storage[pool->component_type_capacity], 0, (new_capacity - pool->component_type_capacity) * sizeof(pxl8_component_storage));
|
||||
pool->component_type_capacity = new_capacity;
|
||||
}
|
||||
|
||||
u32 type_idx = pool->component_type_count++;
|
||||
strncpy(pool->component_types[type_idx].name, name, PXL8_ENTITY_COMPONENT_NAME_MAX - 1);
|
||||
pool->component_types[type_idx].size = size;
|
||||
|
||||
pxl8_component_storage* storage = &pool->component_storage[type_idx];
|
||||
storage->sparse = pxl8_malloc(pool->capacity * sizeof(u32));
|
||||
storage->dense_data = pxl8_malloc(pool->capacity * size);
|
||||
storage->dense_entities = pxl8_malloc(pool->capacity * sizeof(pxl8_entity));
|
||||
storage->count = 0;
|
||||
|
||||
for (u32 i = 0; i < pool->capacity; i++) {
|
||||
storage->sparse[i] = UINT32_MAX;
|
||||
}
|
||||
|
||||
return type_idx + 1;
|
||||
}
|
||||
|
||||
pxl8_entity_component pxl8_entity_component_find(const pxl8_entity_pool* pool, const char* name) {
|
||||
if (!pool || !name) return PXL8_ENTITY_COMPONENT_INVALID;
|
||||
|
||||
for (u32 i = 0; i < pool->component_type_count; i++) {
|
||||
if (strcmp(pool->component_types[i].name, name) == 0) {
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
return PXL8_ENTITY_COMPONENT_INVALID;
|
||||
}
|
||||
|
||||
const char* pxl8_entity_component_name(const pxl8_entity_pool* pool, pxl8_entity_component comp) {
|
||||
if (!pool || comp == 0 || comp > pool->component_type_count) return NULL;
|
||||
return pool->component_types[comp - 1].name;
|
||||
}
|
||||
|
||||
void* pxl8_entity_component_add(pxl8_entity_pool* pool, pxl8_entity e, pxl8_entity_component comp) {
|
||||
if (!pool || !pxl8_entity_alive(pool, e)) return NULL;
|
||||
if (comp == 0 || comp > pool->component_type_count) return NULL;
|
||||
|
||||
u32 type_idx = comp - 1;
|
||||
pxl8_component_storage* storage = &pool->component_storage[type_idx];
|
||||
u32 size = pool->component_types[type_idx].size;
|
||||
|
||||
if (storage->sparse[e.idx] != UINT32_MAX) {
|
||||
return (u8*)storage->dense_data + storage->sparse[e.idx] * size;
|
||||
}
|
||||
|
||||
u32 dense_idx = storage->count++;
|
||||
storage->sparse[e.idx] = dense_idx;
|
||||
storage->dense_entities[dense_idx] = e;
|
||||
|
||||
void* data = (u8*)storage->dense_data + dense_idx * size;
|
||||
memset(data, 0, size);
|
||||
return data;
|
||||
}
|
||||
|
||||
void* pxl8_entity_component_get(const pxl8_entity_pool* pool, pxl8_entity e, pxl8_entity_component comp) {
|
||||
if (!pool || !pxl8_entity_alive(pool, e)) return NULL;
|
||||
if (comp == 0 || comp > pool->component_type_count) return NULL;
|
||||
|
||||
u32 type_idx = comp - 1;
|
||||
const pxl8_component_storage* storage = &pool->component_storage[type_idx];
|
||||
|
||||
if (storage->sparse[e.idx] == UINT32_MAX) return NULL;
|
||||
|
||||
u32 size = pool->component_types[type_idx].size;
|
||||
return (u8*)storage->dense_data + storage->sparse[e.idx] * size;
|
||||
}
|
||||
|
||||
void pxl8_entity_component_remove(pxl8_entity_pool* pool, pxl8_entity e, pxl8_entity_component comp) {
|
||||
if (!pool || !pxl8_entity_alive(pool, e)) return;
|
||||
if (comp == 0 || comp > pool->component_type_count) return;
|
||||
|
||||
u32 type_idx = comp - 1;
|
||||
pxl8_component_storage* storage = &pool->component_storage[type_idx];
|
||||
u32 size = pool->component_types[type_idx].size;
|
||||
|
||||
u32 dense_idx = storage->sparse[e.idx];
|
||||
if (dense_idx == UINT32_MAX) return;
|
||||
|
||||
u32 last_idx = storage->count - 1;
|
||||
if (dense_idx != last_idx) {
|
||||
pxl8_entity last_entity = storage->dense_entities[last_idx];
|
||||
memcpy((u8*)storage->dense_data + dense_idx * size,
|
||||
(u8*)storage->dense_data + last_idx * size, size);
|
||||
storage->dense_entities[dense_idx] = last_entity;
|
||||
storage->sparse[last_entity.idx] = dense_idx;
|
||||
}
|
||||
|
||||
storage->sparse[e.idx] = UINT32_MAX;
|
||||
storage->count--;
|
||||
}
|
||||
|
||||
bool pxl8_entity_component_has(const pxl8_entity_pool* pool, pxl8_entity e, pxl8_entity_component comp) {
|
||||
if (!pool || !pxl8_entity_alive(pool, e)) return false;
|
||||
if (comp == 0 || comp > pool->component_type_count) return false;
|
||||
|
||||
return pool->component_storage[comp - 1].sparse[e.idx] != UINT32_MAX;
|
||||
}
|
||||
|
||||
pxl8_entity_relationship pxl8_entity_relationship_register(pxl8_entity_pool* pool, const char* name) {
|
||||
if (!pool || !name) return PXL8_ENTITY_RELATIONSHIP_INVALID;
|
||||
|
||||
pxl8_entity_relationship existing = pxl8_entity_relationship_find(pool, name);
|
||||
if (existing != PXL8_ENTITY_RELATIONSHIP_INVALID) return existing;
|
||||
|
||||
if (pool->relationship_type_count >= pool->relationship_type_capacity) {
|
||||
u32 new_capacity = pool->relationship_type_capacity * 2;
|
||||
pxl8_relationship_type* new_types = pxl8_realloc(pool->relationship_types, new_capacity * sizeof(pxl8_relationship_type));
|
||||
if (!new_types) return PXL8_ENTITY_RELATIONSHIP_INVALID;
|
||||
|
||||
pool->relationship_types = new_types;
|
||||
memset(&pool->relationship_types[pool->relationship_type_capacity], 0, (new_capacity - pool->relationship_type_capacity) * sizeof(pxl8_relationship_type));
|
||||
pool->relationship_type_capacity = new_capacity;
|
||||
}
|
||||
|
||||
u32 type_idx = pool->relationship_type_count++;
|
||||
strncpy(pool->relationship_types[type_idx].name, name, PXL8_ENTITY_RELATIONSHIP_NAME_MAX - 1);
|
||||
|
||||
return type_idx + 1;
|
||||
}
|
||||
|
||||
pxl8_entity_relationship pxl8_entity_relationship_find(const pxl8_entity_pool* pool, const char* name) {
|
||||
if (!pool || !name) return PXL8_ENTITY_RELATIONSHIP_INVALID;
|
||||
|
||||
for (u32 i = 0; i < pool->relationship_type_count; i++) {
|
||||
if (strcmp(pool->relationship_types[i].name, name) == 0) {
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
return PXL8_ENTITY_RELATIONSHIP_INVALID;
|
||||
}
|
||||
|
||||
const char* pxl8_entity_relationship_name(const pxl8_entity_pool* pool, pxl8_entity_relationship rel) {
|
||||
if (!pool || rel == 0 || rel > pool->relationship_type_count) return NULL;
|
||||
return pool->relationship_types[rel - 1].name;
|
||||
}
|
||||
|
||||
void pxl8_entity_relationship_add(pxl8_entity_pool* pool, pxl8_entity subject, pxl8_entity_relationship rel, pxl8_entity object) {
|
||||
if (!pool) return;
|
||||
if (!pxl8_entity_alive(pool, subject) || !pxl8_entity_alive(pool, object)) return;
|
||||
if (rel == 0 || rel > pool->relationship_type_count) return;
|
||||
if (pxl8_entity_relationship_has(pool, subject, rel, object)) return;
|
||||
|
||||
if (pool->relationship_count >= pool->relationship_capacity) {
|
||||
u32 new_capacity = pool->relationship_capacity * 2;
|
||||
pxl8_relationship_entry* new_rels = pxl8_realloc(pool->relationships, new_capacity * sizeof(pxl8_relationship_entry));
|
||||
if (!new_rels) return;
|
||||
pool->relationships = new_rels;
|
||||
pool->relationship_capacity = new_capacity;
|
||||
}
|
||||
|
||||
u32 entry_idx = pool->relationship_count++;
|
||||
pxl8_relationship_entry* entry = &pool->relationships[entry_idx];
|
||||
entry->subject = subject;
|
||||
entry->object = object;
|
||||
entry->rel = rel;
|
||||
|
||||
entry->next_by_subject = pool->rel_by_subject[subject.idx];
|
||||
pool->rel_by_subject[subject.idx] = entry_idx;
|
||||
|
||||
entry->next_by_object = pool->rel_by_object[object.idx];
|
||||
pool->rel_by_object[object.idx] = entry_idx;
|
||||
}
|
||||
|
||||
void pxl8_entity_relationship_remove(pxl8_entity_pool* pool, pxl8_entity subject, pxl8_entity_relationship rel, pxl8_entity object) {
|
||||
if (!pool) return;
|
||||
if (rel == 0 || rel > pool->relationship_type_count) return;
|
||||
|
||||
u32* prev_ptr = &pool->rel_by_subject[subject.idx];
|
||||
u32 idx = *prev_ptr;
|
||||
|
||||
while (idx != UINT32_MAX) {
|
||||
pxl8_relationship_entry* entry = &pool->relationships[idx];
|
||||
if (pxl8_entity_eq(entry->subject, subject) &&
|
||||
pxl8_entity_eq(entry->object, object) &&
|
||||
entry->rel == rel) {
|
||||
|
||||
*prev_ptr = entry->next_by_subject;
|
||||
|
||||
u32* obj_prev = &pool->rel_by_object[object.idx];
|
||||
while (*obj_prev != UINT32_MAX) {
|
||||
if (*obj_prev == idx) {
|
||||
*obj_prev = entry->next_by_object;
|
||||
break;
|
||||
}
|
||||
obj_prev = &pool->relationships[*obj_prev].next_by_object;
|
||||
}
|
||||
|
||||
if (idx != pool->relationship_count - 1) {
|
||||
u32 last_idx = pool->relationship_count - 1;
|
||||
pxl8_relationship_entry* last = &pool->relationships[last_idx];
|
||||
|
||||
u32* last_subj_prev = &pool->rel_by_subject[last->subject.idx];
|
||||
while (*last_subj_prev != UINT32_MAX) {
|
||||
if (*last_subj_prev == last_idx) {
|
||||
*last_subj_prev = idx;
|
||||
break;
|
||||
}
|
||||
last_subj_prev = &pool->relationships[*last_subj_prev].next_by_subject;
|
||||
}
|
||||
|
||||
u32* last_obj_prev = &pool->rel_by_object[last->object.idx];
|
||||
while (*last_obj_prev != UINT32_MAX) {
|
||||
if (*last_obj_prev == last_idx) {
|
||||
*last_obj_prev = idx;
|
||||
break;
|
||||
}
|
||||
last_obj_prev = &pool->relationships[*last_obj_prev].next_by_object;
|
||||
}
|
||||
|
||||
*entry = *last;
|
||||
}
|
||||
pool->relationship_count--;
|
||||
return;
|
||||
}
|
||||
prev_ptr = &entry->next_by_subject;
|
||||
idx = *prev_ptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool pxl8_entity_relationship_has(const pxl8_entity_pool* pool, pxl8_entity subject, pxl8_entity_relationship rel, pxl8_entity object) {
|
||||
if (!pool) return false;
|
||||
if (rel == 0 || rel > pool->relationship_type_count) return false;
|
||||
|
||||
u32 idx = pool->rel_by_subject[subject.idx];
|
||||
while (idx != UINT32_MAX) {
|
||||
const pxl8_relationship_entry* entry = &pool->relationships[idx];
|
||||
if (pxl8_entity_eq(entry->subject, subject) &&
|
||||
pxl8_entity_eq(entry->object, object) &&
|
||||
entry->rel == rel) {
|
||||
return true;
|
||||
}
|
||||
idx = entry->next_by_subject;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 pxl8_entity_relationship_subjects(const pxl8_entity_pool* pool, pxl8_entity object, pxl8_entity_relationship rel, pxl8_entity* out, u32 max) {
|
||||
if (!pool || !out || max == 0) return 0;
|
||||
if (rel == 0 || rel > pool->relationship_type_count) return 0;
|
||||
|
||||
u32 count = 0;
|
||||
u32 idx = pool->rel_by_object[object.idx];
|
||||
while (idx != UINT32_MAX && count < max) {
|
||||
const pxl8_relationship_entry* entry = &pool->relationships[idx];
|
||||
if (pxl8_entity_eq(entry->object, object) && entry->rel == rel) {
|
||||
out[count++] = entry->subject;
|
||||
}
|
||||
idx = entry->next_by_object;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
u32 pxl8_entity_relationship_objects(const pxl8_entity_pool* pool, pxl8_entity subject, pxl8_entity_relationship rel, pxl8_entity* out, u32 max) {
|
||||
if (!pool || !out || max == 0) return 0;
|
||||
if (rel == 0 || rel > pool->relationship_type_count) return 0;
|
||||
|
||||
u32 count = 0;
|
||||
u32 idx = pool->rel_by_subject[subject.idx];
|
||||
while (idx != UINT32_MAX && count < max) {
|
||||
const pxl8_relationship_entry* entry = &pool->relationships[idx];
|
||||
if (pxl8_entity_eq(entry->subject, subject) && entry->rel == rel) {
|
||||
out[count++] = entry->object;
|
||||
}
|
||||
idx = entry->next_by_subject;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void pxl8_entity_each(pxl8_entity_pool* pool, pxl8_entity_component comp, pxl8_entity_each_fn fn, void* ctx) {
|
||||
if (!pool || !fn) return;
|
||||
if (comp == 0 || comp > pool->component_type_count) return;
|
||||
|
||||
pxl8_component_storage* storage = &pool->component_storage[comp - 1];
|
||||
for (u32 i = 0; i < storage->count; i++) {
|
||||
pxl8_entity e = storage->dense_entities[i];
|
||||
if (pxl8_entity_alive(pool, e)) {
|
||||
fn(pool, e, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_entity_each_with(pxl8_entity_pool* pool, const pxl8_entity_component* comps, u32 count, pxl8_entity_each_fn fn, void* ctx) {
|
||||
if (!pool || !comps || !fn || count == 0) return;
|
||||
|
||||
u32 smallest_idx = 0;
|
||||
u32 smallest_count = UINT32_MAX;
|
||||
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
if (comps[i] == 0 || comps[i] > pool->component_type_count) return;
|
||||
u32 storage_count = pool->component_storage[comps[i] - 1].count;
|
||||
if (storage_count < smallest_count) {
|
||||
smallest_count = storage_count;
|
||||
smallest_idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_component_storage* storage = &pool->component_storage[comps[smallest_idx] - 1];
|
||||
for (u32 i = 0; i < storage->count; i++) {
|
||||
pxl8_entity e = storage->dense_entities[i];
|
||||
if (!pxl8_entity_alive(pool, e)) continue;
|
||||
|
||||
bool has_all = true;
|
||||
for (u32 j = 0; j < count && has_all; j++) {
|
||||
if (j != smallest_idx) {
|
||||
has_all = pxl8_entity_component_has(pool, e, comps[j]);
|
||||
}
|
||||
}
|
||||
|
||||
if (has_all) {
|
||||
fn(pool, e, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
67
src/world/pxl8_entity.h
Normal file
67
src/world/pxl8_entity.h
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct pxl8_entity_pool pxl8_entity_pool;
|
||||
|
||||
typedef struct pxl8_entity {
|
||||
u32 idx;
|
||||
u32 gen;
|
||||
} pxl8_entity;
|
||||
|
||||
#define PXL8_ENTITY_INVALID ((pxl8_entity){0, 0})
|
||||
|
||||
typedef u32 pxl8_entity_component;
|
||||
typedef u32 pxl8_entity_relationship;
|
||||
|
||||
#define PXL8_ENTITY_COMPONENT_INVALID 0
|
||||
#define PXL8_ENTITY_RELATIONSHIP_INVALID 0
|
||||
|
||||
pxl8_entity_pool* pxl8_entity_pool_create(u32 capacity);
|
||||
void pxl8_entity_pool_clear(pxl8_entity_pool* pool);
|
||||
void pxl8_entity_pool_destroy(pxl8_entity_pool* pool);
|
||||
|
||||
pxl8_entity pxl8_entity_spawn(pxl8_entity_pool* pool);
|
||||
void pxl8_entity_despawn(pxl8_entity_pool* pool, pxl8_entity e);
|
||||
bool pxl8_entity_alive(const pxl8_entity_pool* pool, pxl8_entity e);
|
||||
u32 pxl8_entity_count(const pxl8_entity_pool* pool);
|
||||
|
||||
pxl8_entity_component pxl8_entity_component_register(pxl8_entity_pool* pool, const char* name, u32 size);
|
||||
pxl8_entity_component pxl8_entity_component_find(const pxl8_entity_pool* pool, const char* name);
|
||||
const char* pxl8_entity_component_name(const pxl8_entity_pool* pool, pxl8_entity_component comp);
|
||||
|
||||
void* pxl8_entity_component_add(pxl8_entity_pool* pool, pxl8_entity e, pxl8_entity_component comp);
|
||||
void* pxl8_entity_component_get(const pxl8_entity_pool* pool, pxl8_entity e, pxl8_entity_component comp);
|
||||
void pxl8_entity_component_remove(pxl8_entity_pool* pool, pxl8_entity e, pxl8_entity_component comp);
|
||||
bool pxl8_entity_component_has(const pxl8_entity_pool* pool, pxl8_entity e, pxl8_entity_component comp);
|
||||
|
||||
pxl8_entity_relationship pxl8_entity_relationship_register(pxl8_entity_pool* pool, const char* name);
|
||||
pxl8_entity_relationship pxl8_entity_relationship_find(const pxl8_entity_pool* pool, const char* name);
|
||||
const char* pxl8_entity_relationship_name(const pxl8_entity_pool* pool, pxl8_entity_relationship rel);
|
||||
|
||||
void pxl8_entity_relationship_add(pxl8_entity_pool* pool, pxl8_entity subject, pxl8_entity_relationship rel, pxl8_entity object);
|
||||
void pxl8_entity_relationship_remove(pxl8_entity_pool* pool, pxl8_entity subject, pxl8_entity_relationship rel, pxl8_entity object);
|
||||
bool pxl8_entity_relationship_has(const pxl8_entity_pool* pool, pxl8_entity subject, pxl8_entity_relationship rel, pxl8_entity object);
|
||||
|
||||
u32 pxl8_entity_relationship_subjects(const pxl8_entity_pool* pool, pxl8_entity object, pxl8_entity_relationship rel, pxl8_entity* out, u32 max);
|
||||
u32 pxl8_entity_relationship_objects(const pxl8_entity_pool* pool, pxl8_entity subject, pxl8_entity_relationship rel, pxl8_entity* out, u32 max);
|
||||
|
||||
typedef void (*pxl8_entity_each_fn)(pxl8_entity_pool* pool, pxl8_entity e, void* ctx);
|
||||
void pxl8_entity_each(pxl8_entity_pool* pool, pxl8_entity_component comp, pxl8_entity_each_fn fn, void* ctx);
|
||||
void pxl8_entity_each_with(pxl8_entity_pool* pool, const pxl8_entity_component* comps, u32 count, pxl8_entity_each_fn fn, void* ctx);
|
||||
|
||||
static inline bool pxl8_entity_valid(pxl8_entity e) {
|
||||
return e.idx != 0 || e.gen != 0;
|
||||
}
|
||||
|
||||
static inline bool pxl8_entity_eq(pxl8_entity a, pxl8_entity b) {
|
||||
return a.idx == b.idx && a.gen == b.gen;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
406
src/world/pxl8_voxel.c
Normal file
406
src/world/pxl8_voxel.c
Normal file
|
|
@ -0,0 +1,406 @@
|
|||
#include "pxl8_voxel.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_mem.h"
|
||||
|
||||
#define PXL8_VOXEL_CHUNK_VOLUME (PXL8_VOXEL_CHUNK_SIZE * PXL8_VOXEL_CHUNK_SIZE * PXL8_VOXEL_CHUNK_SIZE)
|
||||
|
||||
typedef struct pxl8_block_def {
|
||||
char name[32];
|
||||
u8 texture_id;
|
||||
pxl8_voxel_geometry geometry;
|
||||
bool registered;
|
||||
} pxl8_block_def;
|
||||
|
||||
struct pxl8_block_registry {
|
||||
pxl8_block_def blocks[PXL8_BLOCK_COUNT];
|
||||
};
|
||||
|
||||
struct pxl8_voxel_chunk {
|
||||
pxl8_block blocks[PXL8_VOXEL_CHUNK_VOLUME];
|
||||
};
|
||||
|
||||
static inline u32 voxel_index(i32 x, i32 y, i32 z) {
|
||||
return (u32)(x + y * PXL8_VOXEL_CHUNK_SIZE + z * PXL8_VOXEL_CHUNK_SIZE * PXL8_VOXEL_CHUNK_SIZE);
|
||||
}
|
||||
|
||||
static inline bool voxel_in_bounds(i32 x, i32 y, i32 z) {
|
||||
return x >= 0 && x < PXL8_VOXEL_CHUNK_SIZE &&
|
||||
y >= 0 && y < PXL8_VOXEL_CHUNK_SIZE &&
|
||||
z >= 0 && z < PXL8_VOXEL_CHUNK_SIZE;
|
||||
}
|
||||
|
||||
pxl8_voxel_chunk* pxl8_voxel_chunk_create(void) {
|
||||
pxl8_voxel_chunk* chunk = pxl8_calloc(1, sizeof(pxl8_voxel_chunk));
|
||||
return chunk;
|
||||
}
|
||||
|
||||
void pxl8_voxel_chunk_clear(pxl8_voxel_chunk* chunk) {
|
||||
if (!chunk) return;
|
||||
memset(chunk->blocks, 0, sizeof(chunk->blocks));
|
||||
}
|
||||
|
||||
void pxl8_voxel_chunk_destroy(pxl8_voxel_chunk* chunk) {
|
||||
pxl8_free(chunk);
|
||||
}
|
||||
|
||||
pxl8_block pxl8_voxel_get(const pxl8_voxel_chunk* chunk, i32 x, i32 y, i32 z) {
|
||||
if (!chunk || !voxel_in_bounds(x, y, z)) return PXL8_BLOCK_AIR;
|
||||
return chunk->blocks[voxel_index(x, y, z)];
|
||||
}
|
||||
|
||||
void pxl8_voxel_set(pxl8_voxel_chunk* chunk, i32 x, i32 y, i32 z, pxl8_block block) {
|
||||
if (!chunk || !voxel_in_bounds(x, y, z)) return;
|
||||
chunk->blocks[voxel_index(x, y, z)] = block;
|
||||
}
|
||||
|
||||
void pxl8_voxel_fill(pxl8_voxel_chunk* chunk, pxl8_block block) {
|
||||
if (!chunk) return;
|
||||
memset(chunk->blocks, block, sizeof(chunk->blocks));
|
||||
}
|
||||
|
||||
pxl8_block_registry* pxl8_block_registry_create(void) {
|
||||
pxl8_block_registry* registry = pxl8_calloc(1, sizeof(pxl8_block_registry));
|
||||
return registry;
|
||||
}
|
||||
|
||||
void pxl8_block_registry_destroy(pxl8_block_registry* registry) {
|
||||
pxl8_free(registry);
|
||||
}
|
||||
|
||||
void pxl8_block_registry_register(pxl8_block_registry* registry, pxl8_block id, const char* name, u8 texture_id, pxl8_voxel_geometry geo) {
|
||||
if (!registry || id == PXL8_BLOCK_AIR) return;
|
||||
|
||||
pxl8_block_def* def = ®istry->blocks[id];
|
||||
strncpy(def->name, name ? name : "", sizeof(def->name) - 1);
|
||||
def->texture_id = texture_id;
|
||||
def->geometry = geo;
|
||||
def->registered = true;
|
||||
}
|
||||
|
||||
const char* pxl8_block_registry_name(const pxl8_block_registry* registry, pxl8_block id) {
|
||||
if (!registry || !registry->blocks[id].registered) return NULL;
|
||||
return registry->blocks[id].name;
|
||||
}
|
||||
|
||||
pxl8_voxel_geometry pxl8_block_registry_geometry(const pxl8_block_registry* registry, pxl8_block id) {
|
||||
if (!registry || !registry->blocks[id].registered) return PXL8_VOXEL_GEOMETRY_CUBE;
|
||||
return registry->blocks[id].geometry;
|
||||
}
|
||||
|
||||
u8 pxl8_block_registry_texture(const pxl8_block_registry* registry, pxl8_block id) {
|
||||
if (!registry || !registry->blocks[id].registered) return 0;
|
||||
return registry->blocks[id].texture_id;
|
||||
}
|
||||
|
||||
static bool block_is_opaque(pxl8_block block) {
|
||||
return block != PXL8_BLOCK_AIR;
|
||||
}
|
||||
|
||||
static pxl8_block get_block_or_neighbor(const pxl8_voxel_chunk* chunk, const pxl8_voxel_chunk** neighbors, i32 x, i32 y, i32 z) {
|
||||
if (voxel_in_bounds(x, y, z)) {
|
||||
return chunk->blocks[voxel_index(x, y, z)];
|
||||
}
|
||||
|
||||
i32 nx = x, ny = y, nz = z;
|
||||
i32 neighbor_idx = -1;
|
||||
|
||||
if (x < 0) { neighbor_idx = 0; nx = x + PXL8_VOXEL_CHUNK_SIZE; }
|
||||
else if (x >= PXL8_VOXEL_CHUNK_SIZE) { neighbor_idx = 1; nx = x - PXL8_VOXEL_CHUNK_SIZE; }
|
||||
else if (y < 0) { neighbor_idx = 2; ny = y + PXL8_VOXEL_CHUNK_SIZE; }
|
||||
else if (y >= PXL8_VOXEL_CHUNK_SIZE) { neighbor_idx = 3; ny = y - PXL8_VOXEL_CHUNK_SIZE; }
|
||||
else if (z < 0) { neighbor_idx = 4; nz = z + PXL8_VOXEL_CHUNK_SIZE; }
|
||||
else if (z >= PXL8_VOXEL_CHUNK_SIZE) { neighbor_idx = 5; nz = z - PXL8_VOXEL_CHUNK_SIZE; }
|
||||
|
||||
if (neighbor_idx >= 0 && neighbors && neighbors[neighbor_idx]) {
|
||||
return pxl8_voxel_get(neighbors[neighbor_idx], nx, ny, nz);
|
||||
}
|
||||
|
||||
return PXL8_BLOCK_AIR;
|
||||
}
|
||||
|
||||
static f32 compute_ao(const pxl8_voxel_chunk* chunk, const pxl8_voxel_chunk** neighbors,
|
||||
i32 x, i32 y, i32 z, i32 dx1, i32 dy1, i32 dz1, i32 dx2, i32 dy2, i32 dz2) {
|
||||
bool side1 = block_is_opaque(get_block_or_neighbor(chunk, neighbors, x + dx1, y + dy1, z + dz1));
|
||||
bool side2 = block_is_opaque(get_block_or_neighbor(chunk, neighbors, x + dx2, y + dy2, z + dz2));
|
||||
bool corner = block_is_opaque(get_block_or_neighbor(chunk, neighbors, x + dx1 + dx2, y + dy1 + dy2, z + dz1 + dz2));
|
||||
|
||||
if (side1 && side2) return 0.0f;
|
||||
return (3.0f - (f32)side1 - (f32)side2 - (f32)corner) / 3.0f;
|
||||
}
|
||||
|
||||
static void add_face_vertices(pxl8_mesh* mesh, const pxl8_voxel_mesh_config* config,
|
||||
pxl8_vec3 pos, i32 face, u8 texture_id,
|
||||
f32 ao0, f32 ao1, f32 ao2, f32 ao3) {
|
||||
static const pxl8_vec3 face_normals[6] = {
|
||||
{-1, 0, 0}, { 1, 0, 0},
|
||||
{ 0, -1, 0}, { 0, 1, 0},
|
||||
{ 0, 0, -1}, { 0, 0, 1}
|
||||
};
|
||||
|
||||
static const i32 face_vertices[6][4][3] = {
|
||||
{{0,0,1}, {0,0,0}, {0,1,0}, {0,1,1}},
|
||||
{{1,0,0}, {1,0,1}, {1,1,1}, {1,1,0}},
|
||||
{{0,0,0}, {1,0,0}, {1,0,1}, {0,0,1}},
|
||||
{{0,1,1}, {1,1,1}, {1,1,0}, {0,1,0}},
|
||||
{{0,0,0}, {0,1,0}, {1,1,0}, {1,0,0}},
|
||||
{{1,0,1}, {1,1,1}, {0,1,1}, {0,0,1}}
|
||||
};
|
||||
|
||||
static const f32 face_uvs[4][2] = {
|
||||
{0, 1}, {1, 1}, {1, 0}, {0, 0}
|
||||
};
|
||||
|
||||
f32 ao_values[4] = {ao0, ao1, ao2, ao3};
|
||||
f32 ao_strength = config->ambient_occlusion ? config->ao_strength : 0.0f;
|
||||
f32 tex_scale = config->texture_scale;
|
||||
|
||||
pxl8_vec3 normal = face_normals[face];
|
||||
u16 indices[4];
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
pxl8_vertex v = {0};
|
||||
v.position.x = pos.x + (f32)face_vertices[face][i][0];
|
||||
v.position.y = pos.y + (f32)face_vertices[face][i][1];
|
||||
v.position.z = pos.z + (f32)face_vertices[face][i][2];
|
||||
v.normal = normal;
|
||||
v.u = face_uvs[i][0] * tex_scale;
|
||||
v.v = face_uvs[i][1] * tex_scale;
|
||||
v.color = texture_id;
|
||||
v.light = (u8)(255.0f * (1.0f - ao_strength * (1.0f - ao_values[i])));
|
||||
|
||||
indices[i] = pxl8_mesh_push_vertex(mesh, v);
|
||||
}
|
||||
|
||||
if (ao0 + ao2 > ao1 + ao3) {
|
||||
pxl8_mesh_push_quad(mesh, indices[0], indices[1], indices[2], indices[3]);
|
||||
} else {
|
||||
pxl8_mesh_push_triangle(mesh, indices[0], indices[1], indices[2]);
|
||||
pxl8_mesh_push_triangle(mesh, indices[0], indices[2], indices[3]);
|
||||
}
|
||||
}
|
||||
|
||||
static void add_cube_faces(pxl8_mesh* mesh, const pxl8_voxel_chunk* chunk,
|
||||
const pxl8_voxel_chunk** neighbors, const pxl8_block_registry* registry,
|
||||
const pxl8_voxel_mesh_config* config,
|
||||
i32 x, i32 y, i32 z, pxl8_block block) {
|
||||
u8 texture_id = pxl8_block_registry_texture(registry, block);
|
||||
pxl8_vec3 pos = {(f32)x, (f32)y, (f32)z};
|
||||
|
||||
static const i32 face_dirs[6][3] = {
|
||||
{-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1}
|
||||
};
|
||||
|
||||
for (i32 face = 0; face < 6; face++) {
|
||||
i32 nx = x + face_dirs[face][0];
|
||||
i32 ny = y + face_dirs[face][1];
|
||||
i32 nz = z + face_dirs[face][2];
|
||||
|
||||
pxl8_block neighbor = get_block_or_neighbor(chunk, neighbors, nx, ny, nz);
|
||||
if (block_is_opaque(neighbor)) continue;
|
||||
|
||||
f32 ao[4] = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
|
||||
if (config->ambient_occlusion) {
|
||||
switch (face) {
|
||||
case 0:
|
||||
ao[0] = compute_ao(chunk, neighbors, x-1, y, z, 0, -1, 0, 0, 0, 1);
|
||||
ao[1] = compute_ao(chunk, neighbors, x-1, y, z, 0, -1, 0, 0, 0, -1);
|
||||
ao[2] = compute_ao(chunk, neighbors, x-1, y, z, 0, 1, 0, 0, 0, -1);
|
||||
ao[3] = compute_ao(chunk, neighbors, x-1, y, z, 0, 1, 0, 0, 0, 1);
|
||||
break;
|
||||
case 1:
|
||||
ao[0] = compute_ao(chunk, neighbors, x+1, y, z, 0, -1, 0, 0, 0, -1);
|
||||
ao[1] = compute_ao(chunk, neighbors, x+1, y, z, 0, -1, 0, 0, 0, 1);
|
||||
ao[2] = compute_ao(chunk, neighbors, x+1, y, z, 0, 1, 0, 0, 0, 1);
|
||||
ao[3] = compute_ao(chunk, neighbors, x+1, y, z, 0, 1, 0, 0, 0, -1);
|
||||
break;
|
||||
case 2:
|
||||
ao[0] = compute_ao(chunk, neighbors, x, y-1, z, -1, 0, 0, 0, 0, -1);
|
||||
ao[1] = compute_ao(chunk, neighbors, x, y-1, z, 1, 0, 0, 0, 0, -1);
|
||||
ao[2] = compute_ao(chunk, neighbors, x, y-1, z, 1, 0, 0, 0, 0, 1);
|
||||
ao[3] = compute_ao(chunk, neighbors, x, y-1, z, -1, 0, 0, 0, 0, 1);
|
||||
break;
|
||||
case 3:
|
||||
ao[0] = compute_ao(chunk, neighbors, x, y+1, z, -1, 0, 0, 0, 0, 1);
|
||||
ao[1] = compute_ao(chunk, neighbors, x, y+1, z, 1, 0, 0, 0, 0, 1);
|
||||
ao[2] = compute_ao(chunk, neighbors, x, y+1, z, 1, 0, 0, 0, 0, -1);
|
||||
ao[3] = compute_ao(chunk, neighbors, x, y+1, z, -1, 0, 0, 0, 0, -1);
|
||||
break;
|
||||
case 4:
|
||||
ao[0] = compute_ao(chunk, neighbors, x, y, z-1, -1, 0, 0, 0, 1, 0);
|
||||
ao[1] = compute_ao(chunk, neighbors, x, y, z-1, -1, 0, 0, 0, -1, 0);
|
||||
ao[2] = compute_ao(chunk, neighbors, x, y, z-1, 1, 0, 0, 0, -1, 0);
|
||||
ao[3] = compute_ao(chunk, neighbors, x, y, z-1, 1, 0, 0, 0, 1, 0);
|
||||
break;
|
||||
case 5:
|
||||
ao[0] = compute_ao(chunk, neighbors, x, y, z+1, 1, 0, 0, 0, -1, 0);
|
||||
ao[1] = compute_ao(chunk, neighbors, x, y, z+1, 1, 0, 0, 0, 1, 0);
|
||||
ao[2] = compute_ao(chunk, neighbors, x, y, z+1, -1, 0, 0, 0, 1, 0);
|
||||
ao[3] = compute_ao(chunk, neighbors, x, y, z+1, -1, 0, 0, 0, -1, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
add_face_vertices(mesh, config, pos, face, texture_id, ao[0], ao[1], ao[2], ao[3]);
|
||||
}
|
||||
}
|
||||
|
||||
static void add_slab_faces(pxl8_mesh* mesh, const pxl8_voxel_chunk* chunk,
|
||||
const pxl8_voxel_chunk** neighbors, const pxl8_block_registry* registry,
|
||||
const pxl8_voxel_mesh_config* config,
|
||||
i32 x, i32 y, i32 z, pxl8_block block, bool top) {
|
||||
u8 texture_id = pxl8_block_registry_texture(registry, block);
|
||||
f32 y_offset = top ? 0.5f : 0.0f;
|
||||
pxl8_vec3 pos = {(f32)x, (f32)y + y_offset, (f32)z};
|
||||
|
||||
static const i32 horiz_faces[4] = {0, 1, 4, 5};
|
||||
static const i32 face_dirs[6][3] = {
|
||||
{-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1}
|
||||
};
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
i32 face = horiz_faces[i];
|
||||
i32 nx = x + face_dirs[face][0];
|
||||
i32 nz = z + face_dirs[face][2];
|
||||
|
||||
pxl8_block neighbor = get_block_or_neighbor(chunk, neighbors, nx, y, nz);
|
||||
if (block_is_opaque(neighbor)) continue;
|
||||
|
||||
add_face_vertices(mesh, config, pos, face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
i32 top_face = 3;
|
||||
i32 bot_face = 2;
|
||||
|
||||
if (top) {
|
||||
pxl8_block above = get_block_or_neighbor(chunk, neighbors, x, y + 1, z);
|
||||
if (!block_is_opaque(above)) {
|
||||
add_face_vertices(mesh, config, pos, top_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
add_face_vertices(mesh, config, (pxl8_vec3){(f32)x, (f32)y + 0.5f, (f32)z}, bot_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
|
||||
} else {
|
||||
add_face_vertices(mesh, config, pos, top_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
|
||||
pxl8_block below = get_block_or_neighbor(chunk, neighbors, x, y - 1, z);
|
||||
if (!block_is_opaque(below)) {
|
||||
add_face_vertices(mesh, config, pos, bot_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void add_slope_faces(pxl8_mesh* mesh, const pxl8_voxel_chunk* chunk,
|
||||
const pxl8_voxel_chunk** neighbors, const pxl8_block_registry* registry,
|
||||
const pxl8_voxel_mesh_config* config,
|
||||
i32 x, i32 y, i32 z, pxl8_block block, i32 direction) {
|
||||
u8 texture_id = pxl8_block_registry_texture(registry, block);
|
||||
pxl8_vec3 pos = {(f32)x, (f32)y, (f32)z};
|
||||
|
||||
pxl8_block below = get_block_or_neighbor(chunk, neighbors, x, y - 1, z);
|
||||
if (!block_is_opaque(below)) {
|
||||
add_face_vertices(mesh, config, pos, 2, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
static const i32 dir_to_back_face[4] = {5, 4, 1, 0};
|
||||
i32 back_face = dir_to_back_face[direction];
|
||||
|
||||
static const i32 face_dirs[6][3] = {
|
||||
{-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1}
|
||||
};
|
||||
|
||||
i32 bx = x + face_dirs[back_face][0];
|
||||
i32 bz = z + face_dirs[back_face][2];
|
||||
pxl8_block back_neighbor = get_block_or_neighbor(chunk, neighbors, bx, y, bz);
|
||||
if (!block_is_opaque(back_neighbor)) {
|
||||
add_face_vertices(mesh, config, pos, back_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
pxl8_vertex verts[4];
|
||||
memset(verts, 0, sizeof(verts));
|
||||
|
||||
static const f32 slope_verts[4][4][3] = {
|
||||
{{0,0,0}, {1,0,0}, {1,1,1}, {0,1,1}},
|
||||
{{0,0,1}, {1,0,1}, {1,1,0}, {0,1,0}},
|
||||
{{1,0,0}, {1,0,1}, {0,1,1}, {0,1,0}},
|
||||
{{0,0,0}, {0,0,1}, {1,1,1}, {1,1,0}}
|
||||
};
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
verts[i].position.x = pos.x + slope_verts[direction][i][0];
|
||||
verts[i].position.y = pos.y + slope_verts[direction][i][1];
|
||||
verts[i].position.z = pos.z + slope_verts[direction][i][2];
|
||||
verts[i].color = texture_id;
|
||||
verts[i].light = 255;
|
||||
}
|
||||
|
||||
static const pxl8_vec3 slope_normals[4] = {
|
||||
{0, 0.707f, -0.707f}, {0, 0.707f, 0.707f},
|
||||
{-0.707f, 0.707f, 0}, {0.707f, 0.707f, 0}
|
||||
};
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
verts[i].normal = slope_normals[direction];
|
||||
}
|
||||
|
||||
u16 i0 = pxl8_mesh_push_vertex(mesh, verts[0]);
|
||||
u16 i1 = pxl8_mesh_push_vertex(mesh, verts[1]);
|
||||
u16 i2 = pxl8_mesh_push_vertex(mesh, verts[2]);
|
||||
u16 i3 = pxl8_mesh_push_vertex(mesh, verts[3]);
|
||||
pxl8_mesh_push_quad(mesh, i0, i1, i2, i3);
|
||||
}
|
||||
|
||||
pxl8_mesh* pxl8_voxel_build_mesh(const pxl8_voxel_chunk* chunk,
|
||||
const pxl8_voxel_chunk** neighbors,
|
||||
const pxl8_block_registry* registry,
|
||||
const pxl8_voxel_mesh_config* config) {
|
||||
if (!chunk || !registry) return NULL;
|
||||
|
||||
pxl8_voxel_mesh_config cfg = config ? *config : PXL8_VOXEL_MESH_CONFIG_DEFAULT;
|
||||
|
||||
u32 max_faces = PXL8_VOXEL_CHUNK_VOLUME * 6;
|
||||
pxl8_mesh* mesh = pxl8_mesh_create(max_faces * 4, max_faces * 6);
|
||||
if (!mesh) return NULL;
|
||||
|
||||
for (i32 z = 0; z < PXL8_VOXEL_CHUNK_SIZE; z++) {
|
||||
for (i32 y = 0; y < PXL8_VOXEL_CHUNK_SIZE; y++) {
|
||||
for (i32 x = 0; x < PXL8_VOXEL_CHUNK_SIZE; x++) {
|
||||
pxl8_block block = chunk->blocks[voxel_index(x, y, z)];
|
||||
if (block == PXL8_BLOCK_AIR) continue;
|
||||
|
||||
pxl8_voxel_geometry geo = pxl8_block_registry_geometry(registry, block);
|
||||
|
||||
switch (geo) {
|
||||
case PXL8_VOXEL_GEOMETRY_CUBE:
|
||||
add_cube_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block);
|
||||
break;
|
||||
case PXL8_VOXEL_GEOMETRY_SLAB_BOTTOM:
|
||||
add_slab_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, false);
|
||||
break;
|
||||
case PXL8_VOXEL_GEOMETRY_SLAB_TOP:
|
||||
add_slab_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, true);
|
||||
break;
|
||||
case PXL8_VOXEL_GEOMETRY_SLOPE_NORTH:
|
||||
add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 0);
|
||||
break;
|
||||
case PXL8_VOXEL_GEOMETRY_SLOPE_SOUTH:
|
||||
add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 1);
|
||||
break;
|
||||
case PXL8_VOXEL_GEOMETRY_SLOPE_EAST:
|
||||
add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 2);
|
||||
break;
|
||||
case PXL8_VOXEL_GEOMETRY_SLOPE_WEST:
|
||||
add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 3);
|
||||
break;
|
||||
case PXL8_VOXEL_GEOMETRY_STAIRS_NORTH:
|
||||
case PXL8_VOXEL_GEOMETRY_STAIRS_SOUTH:
|
||||
case PXL8_VOXEL_GEOMETRY_STAIRS_EAST:
|
||||
case PXL8_VOXEL_GEOMETRY_STAIRS_WEST:
|
||||
add_cube_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
67
src/world/pxl8_voxel.h
Normal file
67
src/world/pxl8_voxel.h
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_mesh.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define PXL8_VOXEL_CHUNK_SIZE 32
|
||||
#define PXL8_BLOCK_COUNT 256
|
||||
|
||||
typedef struct pxl8_voxel_chunk pxl8_voxel_chunk;
|
||||
typedef struct pxl8_block_registry pxl8_block_registry;
|
||||
typedef u8 pxl8_block;
|
||||
|
||||
#define PXL8_BLOCK_AIR 0
|
||||
|
||||
typedef enum pxl8_voxel_geometry {
|
||||
PXL8_VOXEL_GEOMETRY_CUBE,
|
||||
PXL8_VOXEL_GEOMETRY_SLAB_BOTTOM,
|
||||
PXL8_VOXEL_GEOMETRY_SLAB_TOP,
|
||||
PXL8_VOXEL_GEOMETRY_SLOPE_NORTH,
|
||||
PXL8_VOXEL_GEOMETRY_SLOPE_SOUTH,
|
||||
PXL8_VOXEL_GEOMETRY_SLOPE_EAST,
|
||||
PXL8_VOXEL_GEOMETRY_SLOPE_WEST,
|
||||
PXL8_VOXEL_GEOMETRY_STAIRS_NORTH,
|
||||
PXL8_VOXEL_GEOMETRY_STAIRS_SOUTH,
|
||||
PXL8_VOXEL_GEOMETRY_STAIRS_EAST,
|
||||
PXL8_VOXEL_GEOMETRY_STAIRS_WEST
|
||||
} pxl8_voxel_geometry;
|
||||
|
||||
typedef struct pxl8_voxel_mesh_config {
|
||||
bool ambient_occlusion;
|
||||
f32 ao_strength;
|
||||
f32 texture_scale;
|
||||
} pxl8_voxel_mesh_config;
|
||||
|
||||
#define PXL8_VOXEL_MESH_CONFIG_DEFAULT ((pxl8_voxel_mesh_config){ \
|
||||
.ambient_occlusion = true, \
|
||||
.ao_strength = 0.2f, \
|
||||
.texture_scale = 1.0f \
|
||||
})
|
||||
|
||||
pxl8_voxel_chunk* pxl8_voxel_chunk_create(void);
|
||||
void pxl8_voxel_chunk_clear(pxl8_voxel_chunk* chunk);
|
||||
void pxl8_voxel_chunk_destroy(pxl8_voxel_chunk* chunk);
|
||||
|
||||
pxl8_block pxl8_voxel_get(const pxl8_voxel_chunk* chunk, i32 x, i32 y, i32 z);
|
||||
void pxl8_voxel_set(pxl8_voxel_chunk* chunk, i32 x, i32 y, i32 z, pxl8_block block);
|
||||
void pxl8_voxel_fill(pxl8_voxel_chunk* chunk, pxl8_block block);
|
||||
|
||||
pxl8_block_registry* pxl8_block_registry_create(void);
|
||||
void pxl8_block_registry_destroy(pxl8_block_registry* registry);
|
||||
void pxl8_block_registry_register(pxl8_block_registry* registry, pxl8_block id, const char* name, u8 texture_id, pxl8_voxel_geometry geo);
|
||||
const char* pxl8_block_registry_name(const pxl8_block_registry* registry, pxl8_block id);
|
||||
pxl8_voxel_geometry pxl8_block_registry_geometry(const pxl8_block_registry* registry, pxl8_block id);
|
||||
u8 pxl8_block_registry_texture(const pxl8_block_registry* registry, pxl8_block id);
|
||||
|
||||
pxl8_mesh* pxl8_voxel_build_mesh(const pxl8_voxel_chunk* chunk,
|
||||
const pxl8_voxel_chunk** neighbors,
|
||||
const pxl8_block_registry* registry,
|
||||
const pxl8_voxel_mesh_config* config);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
@ -1,413 +1,125 @@
|
|||
#include "pxl8_world.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "pxl8_bsp.h"
|
||||
#include "pxl8_gen.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_math.h"
|
||||
#include "pxl8_mem.h"
|
||||
|
||||
#define PXL8_WORLD_ENTITY_CAPACITY 256
|
||||
|
||||
struct pxl8_world {
|
||||
pxl8_bsp bsp;
|
||||
bool loaded;
|
||||
pxl8_chunk* active_chunk;
|
||||
pxl8_block_registry* block_registry;
|
||||
pxl8_chunk_cache* chunk_cache;
|
||||
pxl8_entity_pool* entities;
|
||||
};
|
||||
|
||||
pxl8_world* pxl8_world_create(void) {
|
||||
pxl8_world* world = (pxl8_world*)pxl8_calloc(1, sizeof(pxl8_world));
|
||||
if (!world) {
|
||||
pxl8_error("Failed to allocate world");
|
||||
pxl8_world* world = pxl8_calloc(1, sizeof(pxl8_world));
|
||||
if (!world) return NULL;
|
||||
|
||||
world->block_registry = pxl8_block_registry_create();
|
||||
world->chunk_cache = pxl8_chunk_cache_create();
|
||||
world->entities = pxl8_entity_pool_create(PXL8_WORLD_ENTITY_CAPACITY);
|
||||
|
||||
if (!world->block_registry || !world->chunk_cache || !world->entities) {
|
||||
pxl8_world_destroy(world);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
world->loaded = false;
|
||||
|
||||
return world;
|
||||
}
|
||||
|
||||
void pxl8_world_destroy(pxl8_world* world) {
|
||||
if (!world) return;
|
||||
|
||||
if (world->loaded) {
|
||||
pxl8_bsp_destroy(&world->bsp);
|
||||
}
|
||||
|
||||
pxl8_block_registry_destroy(world->block_registry);
|
||||
pxl8_chunk_cache_destroy(world->chunk_cache);
|
||||
pxl8_entity_pool_destroy(world->entities);
|
||||
pxl8_free(world);
|
||||
}
|
||||
|
||||
pxl8_result pxl8_world_generate(pxl8_world* world, pxl8_gfx* gfx, const pxl8_procgen_params* params) {
|
||||
if (!world || !gfx || !params) {
|
||||
pxl8_error("Invalid arguments to pxl8_world_generate");
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
pxl8_chunk_cache* pxl8_world_chunk_cache(pxl8_world* world) {
|
||||
if (!world) return NULL;
|
||||
return world->chunk_cache;
|
||||
}
|
||||
|
||||
if (world->loaded) {
|
||||
pxl8_bsp_destroy(&world->bsp);
|
||||
world->loaded = false;
|
||||
pxl8_chunk* pxl8_world_active_chunk(pxl8_world* world) {
|
||||
if (!world) return NULL;
|
||||
return world->active_chunk;
|
||||
}
|
||||
|
||||
memset(&world->bsp, 0, sizeof(pxl8_bsp));
|
||||
|
||||
pxl8_result result = pxl8_procgen(&world->bsp, params);
|
||||
if (result != PXL8_OK) {
|
||||
pxl8_error("Failed to generate world: %d", result);
|
||||
pxl8_bsp_destroy(&world->bsp);
|
||||
return result;
|
||||
void pxl8_world_set_active_chunk(pxl8_world* world, pxl8_chunk* chunk) {
|
||||
if (!world) return;
|
||||
world->active_chunk = chunk;
|
||||
}
|
||||
|
||||
world->loaded = true;
|
||||
return PXL8_OK;
|
||||
pxl8_block_registry* pxl8_world_block_registry(pxl8_world* world) {
|
||||
if (!world) return NULL;
|
||||
return world->block_registry;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_world_load(pxl8_world* world, const char* path) {
|
||||
if (!world || !path) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
if (world->loaded) {
|
||||
pxl8_bsp_destroy(&world->bsp);
|
||||
world->loaded = false;
|
||||
pxl8_entity_pool* pxl8_world_entities(pxl8_world* world) {
|
||||
if (!world) return NULL;
|
||||
return world->entities;
|
||||
}
|
||||
|
||||
memset(&world->bsp, 0, sizeof(pxl8_bsp));
|
||||
|
||||
pxl8_result result = pxl8_bsp_load(path, &world->bsp);
|
||||
if (result != PXL8_OK) {
|
||||
pxl8_error("Failed to load world: %s", path);
|
||||
return result;
|
||||
}
|
||||
|
||||
world->loaded = true;
|
||||
pxl8_info("Loaded world: %s", path);
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
void pxl8_world_unload(pxl8_world* world) {
|
||||
if (!world || !world->loaded) return;
|
||||
|
||||
pxl8_bsp_destroy(&world->bsp);
|
||||
world->loaded = false;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_world_apply_textures(pxl8_world* world, const pxl8_world_texture* textures, u32 count) {
|
||||
if (!world || !world->loaded || !textures || count == 0) {
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
pxl8_bsp* bsp = &world->bsp;
|
||||
|
||||
u32 max_materials = count * 6;
|
||||
bsp->materials = pxl8_calloc(max_materials, sizeof(pxl8_gfx_material));
|
||||
if (!bsp->materials) {
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
bsp->num_materials = 0;
|
||||
|
||||
for (u32 face_idx = 0; face_idx < bsp->num_faces; face_idx++) {
|
||||
pxl8_bsp_face* face = &bsp->faces[face_idx];
|
||||
pxl8_vec3 normal = bsp->planes[face->plane_id].normal;
|
||||
|
||||
u32 matched_texture_idx = count;
|
||||
for (u32 tex_idx = 0; tex_idx < count; tex_idx++) {
|
||||
if (textures[tex_idx].rule && textures[tex_idx].rule(&normal, face, bsp)) {
|
||||
matched_texture_idx = tex_idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matched_texture_idx >= count) {
|
||||
pxl8_warn("No texture rule matched for face %u", face_idx);
|
||||
continue;
|
||||
}
|
||||
|
||||
const pxl8_world_texture* matched = &textures[matched_texture_idx];
|
||||
|
||||
pxl8_vec3 u_axis, v_axis;
|
||||
if (fabsf(normal.y) > 0.9f) {
|
||||
u_axis = (pxl8_vec3){1.0f, 0.0f, 0.0f};
|
||||
v_axis = (pxl8_vec3){0.0f, 0.0f, 1.0f};
|
||||
} else if (fabsf(normal.x) > 0.7f) {
|
||||
u_axis = (pxl8_vec3){0.0f, 1.0f, 0.0f};
|
||||
v_axis = (pxl8_vec3){0.0f, 0.0f, 1.0f};
|
||||
} else {
|
||||
u_axis = (pxl8_vec3){1.0f, 0.0f, 0.0f};
|
||||
v_axis = (pxl8_vec3){0.0f, 1.0f, 0.0f};
|
||||
}
|
||||
|
||||
u32 material_idx = bsp->num_materials;
|
||||
bool found_existing = false;
|
||||
for (u32 i = 0; i < bsp->num_materials; i++) {
|
||||
if (strcmp(bsp->materials[i].name, matched->name) == 0 &&
|
||||
bsp->materials[i].texture_id == matched->texture_id &&
|
||||
bsp->materials[i].u_axis.x == u_axis.x &&
|
||||
bsp->materials[i].u_axis.y == u_axis.y &&
|
||||
bsp->materials[i].u_axis.z == u_axis.z &&
|
||||
bsp->materials[i].v_axis.x == v_axis.x &&
|
||||
bsp->materials[i].v_axis.y == v_axis.y &&
|
||||
bsp->materials[i].v_axis.z == v_axis.z) {
|
||||
material_idx = i;
|
||||
found_existing = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_existing) {
|
||||
if (bsp->num_materials >= max_materials) {
|
||||
pxl8_error("Too many unique material entries");
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
pxl8_gfx_material* mat = &bsp->materials[material_idx];
|
||||
memcpy(mat->name, matched->name, sizeof(mat->name));
|
||||
mat->name[sizeof(mat->name) - 1] = '\0';
|
||||
mat->texture_id = matched->texture_id;
|
||||
mat->u_offset = 0.0f;
|
||||
mat->v_offset = 0.0f;
|
||||
mat->u_axis = u_axis;
|
||||
mat->v_axis = v_axis;
|
||||
mat->alpha = 255;
|
||||
mat->dither = true;
|
||||
mat->double_sided = true;
|
||||
mat->dynamic_lighting = true;
|
||||
|
||||
bsp->num_materials++;
|
||||
}
|
||||
|
||||
face->material_id = material_idx;
|
||||
}
|
||||
|
||||
pxl8_info("Applied %u textures to %u faces, created %u materials",
|
||||
count, bsp->num_faces, bsp->num_materials);
|
||||
|
||||
return PXL8_OK;
|
||||
pxl8_entity pxl8_world_spawn(pxl8_world* world) {
|
||||
if (!world || !world->entities) return PXL8_ENTITY_INVALID;
|
||||
return pxl8_entity_spawn(world->entities);
|
||||
}
|
||||
|
||||
bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, f32 radius) {
|
||||
if (!world || !world->loaded) return false;
|
||||
(void)radius;
|
||||
|
||||
const pxl8_bsp* bsp = &world->bsp;
|
||||
if (!world || !world->active_chunk) return false;
|
||||
|
||||
for (u32 i = 0; i < bsp->num_faces; i++) {
|
||||
const pxl8_bsp_face* face = &bsp->faces[i];
|
||||
const pxl8_bsp_plane* plane = &bsp->planes[face->plane_id];
|
||||
|
||||
if (fabsf(plane->normal.y) > 0.7f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
f32 dist = plane->normal.x * pos.x +
|
||||
plane->normal.y * pos.y +
|
||||
plane->normal.z * pos.z - plane->dist;
|
||||
|
||||
if (fabsf(dist) > radius) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pxl8_vec3 closest_point = {
|
||||
pos.x - plane->normal.x * dist,
|
||||
pos.y - plane->normal.y * dist,
|
||||
pos.z - plane->normal.z * dist
|
||||
};
|
||||
|
||||
if (closest_point.x < face->aabb_min.x - radius || closest_point.x > face->aabb_max.x + radius ||
|
||||
closest_point.y < face->aabb_min.y - radius || closest_point.y > face->aabb_max.y + radius ||
|
||||
closest_point.z < face->aabb_min.z - radius || closest_point.z > face->aabb_max.z + radius) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return true;
|
||||
if (world->active_chunk->type == PXL8_CHUNK_BSP && world->active_chunk->bsp) {
|
||||
return pxl8_bsp_point_solid(world->active_chunk->bsp, pos);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool pxl8_world_is_loaded(const pxl8_world* world) {
|
||||
return world && world->loaded;
|
||||
}
|
||||
|
||||
pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius) {
|
||||
if (!world || !world->loaded) return to;
|
||||
if (!world || !world->active_chunk) return to;
|
||||
|
||||
const pxl8_bsp* bsp = &world->bsp;
|
||||
pxl8_vec3 pos = to;
|
||||
pxl8_vec3 original_velocity = {to.x - from.x, to.y - from.y, to.z - from.z};
|
||||
|
||||
pxl8_vec3 clip_planes[5];
|
||||
u32 num_planes = 0;
|
||||
|
||||
const f32 edge_epsilon = 1.2f;
|
||||
const f32 radius_min = -radius + edge_epsilon;
|
||||
const f32 radius_max = radius - edge_epsilon;
|
||||
|
||||
for (i32 iteration = 0; iteration < 4; iteration++) {
|
||||
bool collided = false;
|
||||
|
||||
for (u32 i = 0; i < bsp->num_faces; i++) {
|
||||
const pxl8_bsp_face* face = &bsp->faces[i];
|
||||
const pxl8_bsp_plane* plane = &bsp->planes[face->plane_id];
|
||||
|
||||
if (fabsf(plane->normal.y) > 0.7f) continue;
|
||||
|
||||
f32 dist = plane->normal.x * pos.x +
|
||||
plane->normal.y * pos.y +
|
||||
plane->normal.z * pos.z - plane->dist;
|
||||
|
||||
f32 abs_dist = fabsf(dist);
|
||||
if (abs_dist > radius) continue;
|
||||
|
||||
pxl8_vec3 closest_point = {
|
||||
pos.x - plane->normal.x * dist,
|
||||
pos.y - plane->normal.y * dist,
|
||||
pos.z - plane->normal.z * dist
|
||||
};
|
||||
|
||||
if (closest_point.x < face->aabb_min.x + radius_min || closest_point.x > face->aabb_max.x + radius_max ||
|
||||
closest_point.y < face->aabb_min.y + radius_min || closest_point.y > face->aabb_max.y + radius_max ||
|
||||
closest_point.z < face->aabb_min.z + radius_min || closest_point.z > face->aabb_max.z + radius_max) {
|
||||
continue;
|
||||
if (world->active_chunk->type == PXL8_CHUNK_BSP && world->active_chunk->bsp) {
|
||||
return pxl8_bsp_trace(world->active_chunk->bsp, from, to, radius);
|
||||
}
|
||||
|
||||
f32 penetration = radius - abs_dist;
|
||||
if (penetration > 0.01f) {
|
||||
pxl8_vec3 push_dir;
|
||||
if (dist < 0) {
|
||||
push_dir.x = -plane->normal.x;
|
||||
push_dir.y = -plane->normal.y;
|
||||
push_dir.z = -plane->normal.z;
|
||||
} else {
|
||||
push_dir.x = plane->normal.x;
|
||||
push_dir.y = plane->normal.y;
|
||||
push_dir.z = plane->normal.z;
|
||||
return to;
|
||||
}
|
||||
|
||||
bool is_new_plane = true;
|
||||
for (u32 p = 0; p < num_planes; p++) {
|
||||
if (pxl8_vec3_dot(push_dir, clip_planes[p]) > 0.99f) {
|
||||
is_new_plane = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
void pxl8_world_update(pxl8_world* world, f32 dt) {
|
||||
(void)dt;
|
||||
if (!world) return;
|
||||
|
||||
if (is_new_plane && num_planes < 5) {
|
||||
clip_planes[num_planes++] = push_dir;
|
||||
}
|
||||
|
||||
pos.x += push_dir.x * penetration;
|
||||
pos.y += push_dir.y * penetration;
|
||||
pos.z += push_dir.z * penetration;
|
||||
|
||||
collided = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!collided) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (num_planes >= 3) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (num_planes == 2) {
|
||||
f32 orig_vel_len_sq = original_velocity.x * original_velocity.x +
|
||||
original_velocity.y * original_velocity.y +
|
||||
original_velocity.z * original_velocity.z;
|
||||
|
||||
if (orig_vel_len_sq > 0.000001f) {
|
||||
f32 vdot0 = pxl8_vec3_dot(original_velocity, clip_planes[0]);
|
||||
f32 vdot1 = pxl8_vec3_dot(original_velocity, clip_planes[1]);
|
||||
f32 dot0 = fabsf(vdot0);
|
||||
f32 dot1 = fabsf(vdot1);
|
||||
|
||||
pxl8_vec3 slide_vel;
|
||||
if (dot0 < dot1) {
|
||||
slide_vel.x = original_velocity.x - clip_planes[0].x * vdot0;
|
||||
slide_vel.y = original_velocity.y - clip_planes[0].y * vdot0;
|
||||
slide_vel.z = original_velocity.z - clip_planes[0].z * vdot0;
|
||||
} else {
|
||||
slide_vel.x = original_velocity.x - clip_planes[1].x * vdot1;
|
||||
slide_vel.y = original_velocity.y - clip_planes[1].y * vdot1;
|
||||
slide_vel.z = original_velocity.z - clip_planes[1].z * vdot1;
|
||||
}
|
||||
|
||||
f32 slide_len = sqrtf(slide_vel.x * slide_vel.x + slide_vel.y * slide_vel.y + slide_vel.z * slide_vel.z);
|
||||
|
||||
if (slide_len > 0.01f) {
|
||||
pos.x += slide_vel.x;
|
||||
pos.y += slide_vel.y;
|
||||
pos.z += slide_vel.z;
|
||||
|
||||
pxl8_vec3 crease_dir = pxl8_vec3_cross(clip_planes[0], clip_planes[1]);
|
||||
f32 crease_len = pxl8_vec3_length(crease_dir);
|
||||
if (crease_len > 0.01f && fabsf(crease_dir.y / crease_len) > 0.9f) {
|
||||
f32 bias0 = pxl8_vec3_dot(original_velocity, clip_planes[0]);
|
||||
f32 bias1 = pxl8_vec3_dot(original_velocity, clip_planes[1]);
|
||||
|
||||
if (bias0 < 0 && bias1 < 0) {
|
||||
const f32 corner_push = 0.1f;
|
||||
pxl8_vec3 push_away = {
|
||||
clip_planes[0].x + clip_planes[1].x,
|
||||
clip_planes[0].y + clip_planes[1].y,
|
||||
clip_planes[0].z + clip_planes[1].z
|
||||
};
|
||||
f32 push_len_sq = push_away.x * push_away.x + push_away.y * push_away.y + push_away.z * push_away.z;
|
||||
if (push_len_sq > 0.000001f) {
|
||||
f32 inv_push_len = corner_push / sqrtf(push_len_sq);
|
||||
pos.x += push_away.x * inv_push_len;
|
||||
pos.y += push_away.y * inv_push_len;
|
||||
pos.z += push_away.z * inv_push_len;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f32 horizontal_movement_sq = (pos.x - from.x) * (pos.x - from.x) +
|
||||
(pos.z - from.z) * (pos.z - from.z);
|
||||
f32 desired_horizontal_sq = (to.x - from.x) * (to.x - from.x) +
|
||||
(to.z - from.z) * (to.z - from.z);
|
||||
|
||||
if (desired_horizontal_sq > 0.01f && horizontal_movement_sq < desired_horizontal_sq * 0.25f) {
|
||||
const f32 max_step_height = 0.4f;
|
||||
|
||||
pxl8_vec3 step_up = pos;
|
||||
step_up.y += max_step_height;
|
||||
|
||||
if (!pxl8_world_check_collision(world, step_up, radius)) {
|
||||
pxl8_vec3 step_forward = {
|
||||
step_up.x + (to.x - pos.x),
|
||||
step_up.y,
|
||||
step_up.z + (to.z - pos.z)
|
||||
};
|
||||
|
||||
if (!pxl8_world_check_collision(world, step_forward, radius)) {
|
||||
pos = step_forward;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pos;
|
||||
pxl8_chunk_cache_tick(world->chunk_cache);
|
||||
}
|
||||
|
||||
void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) {
|
||||
if (!world || !gfx || !world->loaded) {
|
||||
static int count = 0;
|
||||
if (count++ < 10) {
|
||||
pxl8_debug("world_render: early return - world=%p, gfx=%p, loaded=%d",
|
||||
(void*)world, (void*)gfx, world ? world->loaded : -1);
|
||||
if (!world || !gfx || !world->active_chunk) return;
|
||||
|
||||
if (world->active_chunk->type == PXL8_CHUNK_BSP && world->active_chunk->bsp) {
|
||||
pxl8_bsp_render(gfx, world->active_chunk->bsp, camera_pos);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
pxl8_bsp_render(gfx, &world->bsp, camera_pos);
|
||||
}
|
||||
void pxl8_world_sync(pxl8_world* world, pxl8_net* net) {
|
||||
if (!world || !net) return;
|
||||
|
||||
void pxl8_world_set_wireframe(pxl8_world* world, bool enabled) {
|
||||
if (!world || !world->loaded) return;
|
||||
u8 chunk_type = pxl8_net_chunk_type(net);
|
||||
u32 chunk_id = pxl8_net_chunk_id(net);
|
||||
|
||||
for (u32 i = 0; i < world->bsp.num_materials; i++) {
|
||||
world->bsp.materials[i].wireframe = enabled;
|
||||
if (chunk_type == PXL8_CHUNK_TYPE_BSP && chunk_id != 0) {
|
||||
pxl8_chunk* chunk = pxl8_chunk_cache_get_bsp(world->chunk_cache, chunk_id);
|
||||
if (chunk && chunk->bsp) {
|
||||
if (world->active_chunk != chunk) {
|
||||
world->active_chunk = chunk;
|
||||
pxl8_debug("[CLIENT] Synced BSP chunk id=%u as active (verts=%u faces=%u)",
|
||||
chunk_id, chunk->bsp->num_vertices, chunk->bsp->num_faces);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +1,36 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_bsp.h"
|
||||
#include "pxl8_gen.h"
|
||||
#include "pxl8_chunk.h"
|
||||
#include "pxl8_chunk_cache.h"
|
||||
#include "pxl8_entity.h"
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_math.h"
|
||||
#include "pxl8_net.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_world pxl8_world;
|
||||
|
||||
typedef bool (*pxl8_texture_rule)(const pxl8_vec3* normal, const pxl8_bsp_face* face, const pxl8_bsp* bsp);
|
||||
|
||||
typedef struct pxl8_world_texture {
|
||||
char name[16];
|
||||
u32 texture_id;
|
||||
pxl8_texture_rule rule;
|
||||
} pxl8_world_texture;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct pxl8_world pxl8_world;
|
||||
|
||||
pxl8_world* pxl8_world_create(void);
|
||||
void pxl8_world_destroy(pxl8_world* world);
|
||||
|
||||
pxl8_result pxl8_world_generate(pxl8_world* world, pxl8_gfx* gfx, const pxl8_procgen_params* params);
|
||||
pxl8_result pxl8_world_load(pxl8_world* world, const char* path);
|
||||
void pxl8_world_unload(pxl8_world* world);
|
||||
pxl8_chunk_cache* pxl8_world_chunk_cache(pxl8_world* world);
|
||||
pxl8_chunk* pxl8_world_active_chunk(pxl8_world* world);
|
||||
void pxl8_world_set_active_chunk(pxl8_world* world, pxl8_chunk* chunk);
|
||||
|
||||
pxl8_block_registry* pxl8_world_block_registry(pxl8_world* world);
|
||||
pxl8_entity_pool* pxl8_world_entities(pxl8_world* world);
|
||||
pxl8_entity pxl8_world_spawn(pxl8_world* world);
|
||||
|
||||
pxl8_result pxl8_world_apply_textures(pxl8_world* world, const pxl8_world_texture* textures, u32 count);
|
||||
bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, f32 radius);
|
||||
bool pxl8_world_is_loaded(const pxl8_world* world);
|
||||
void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);
|
||||
pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius);
|
||||
void pxl8_world_set_wireframe(pxl8_world* world, bool enabled);
|
||||
|
||||
void pxl8_world_update(pxl8_world* world, f32 dt);
|
||||
void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);
|
||||
void pxl8_world_sync(pxl8_world* world, pxl8_net* net);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue