feat(gui): add toolbar widget
feat(gui): add grid_select, toggle, panel, status_bar, image widgets fix(bsp): fill in exterior cells
This commit is contained in:
parent
5a565844dd
commit
8d491612ab
63 changed files with 3150 additions and 1686 deletions
41
.clang-format
Normal file
41
.clang-format
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
BasedOnStyle: LLVM
|
||||||
|
|
||||||
|
Language: C
|
||||||
|
Standard: Latest
|
||||||
|
|
||||||
|
IndentWidth: 4
|
||||||
|
TabWidth: 4
|
||||||
|
UseTab: Never
|
||||||
|
ColumnLimit: 120
|
||||||
|
|
||||||
|
PointerAlignment: Left
|
||||||
|
ReferenceAlignment: Left
|
||||||
|
SpaceBeforeParens: ControlStatements
|
||||||
|
|
||||||
|
BreakBeforeBraces: Attach
|
||||||
|
AllowShortFunctionsOnASingleLine: Empty
|
||||||
|
AllowShortIfStatementsOnASingleLine: WithoutElse
|
||||||
|
AllowShortLoopsOnASingleLine: false
|
||||||
|
AllowShortBlocksOnASingleLine: Empty
|
||||||
|
|
||||||
|
BinPackArguments: true
|
||||||
|
BinPackParameters: true
|
||||||
|
AlignAfterOpenBracket: Align
|
||||||
|
AlignConsecutiveMacros: Consecutive
|
||||||
|
AlignEscapedNewlines: Left
|
||||||
|
AlignOperands: Align
|
||||||
|
|
||||||
|
IncludeBlocks: Preserve
|
||||||
|
SortIncludes: Never
|
||||||
|
|
||||||
|
SpaceAfterCStyleCast: false
|
||||||
|
SpacesInParens: Never
|
||||||
|
|
||||||
|
IndentCaseLabels: false
|
||||||
|
IndentPPDirectives: None
|
||||||
|
|
||||||
|
MaxEmptyLinesToKeep: 1
|
||||||
|
KeepEmptyLines:
|
||||||
|
AtEndOfFile: false
|
||||||
|
AtStartOfBlock: false
|
||||||
|
AtStartOfFile: false
|
||||||
30
.clangd
Normal file
30
.clangd
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
CompileFlags:
|
||||||
|
CompilationDatabase: .
|
||||||
|
|
||||||
|
Diagnostics:
|
||||||
|
UnusedIncludes: None
|
||||||
|
MissingIncludes: None
|
||||||
|
ClangTidy:
|
||||||
|
Add:
|
||||||
|
- bugprone-sizeof-expression
|
||||||
|
- bugprone-suspicious-memset-usage
|
||||||
|
- bugprone-integer-division
|
||||||
|
- bugprone-macro-parentheses
|
||||||
|
- bugprone-bool-pointer-implicit-conversion
|
||||||
|
- bugprone-multiple-statement-macro
|
||||||
|
- performance-type-promotion-in-math-fn
|
||||||
|
Remove:
|
||||||
|
- bugprone-easily-swappable-parameters
|
||||||
|
|
||||||
|
InlayHints:
|
||||||
|
Enabled: Yes
|
||||||
|
ParameterNames: Yes
|
||||||
|
DeducedTypes: Yes
|
||||||
|
Designators: Yes
|
||||||
|
BlockEnd: No
|
||||||
|
|
||||||
|
Hover:
|
||||||
|
ShowAKA: Yes
|
||||||
|
|
||||||
|
Completion:
|
||||||
|
AllScopes: Yes
|
||||||
9
.gitignore
vendored
9
.gitignore
vendored
|
|
@ -1,6 +1,5 @@
|
||||||
.ccls-cache/
|
|
||||||
.ccls
|
|
||||||
**.DS_Store
|
|
||||||
|
|
||||||
.pxl8_history
|
|
||||||
*.aseprite-extension
|
*.aseprite-extension
|
||||||
|
.cache
|
||||||
|
compile_commands.json
|
||||||
|
**.DS_Store
|
||||||
|
.pxl8_history
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
{:title "pxl8 demo"
|
{:title "pxl8 demo"
|
||||||
:pixel-mode "indexed"
|
|
||||||
:resolution "640x360"
|
:resolution "640x360"
|
||||||
:window-size [1280 720]}
|
:window-size [1280 720]}
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,9 @@
|
||||||
(local pxl8 (require :pxl8))
|
(local pxl8 (require :pxl8))
|
||||||
|
|
||||||
(local DOOR_HEIGHT 96)
|
|
||||||
(local DOOR_WIDTH 48)
|
|
||||||
(local DOOR_X 892)
|
|
||||||
(local DOOR_Z 416)
|
|
||||||
(local FIREBALL_COLOR 218)
|
(local FIREBALL_COLOR 218)
|
||||||
|
|
||||||
(var door-mesh nil)
|
|
||||||
(var door-tex nil)
|
|
||||||
(var fireball-mesh nil)
|
(var fireball-mesh nil)
|
||||||
|
|
||||||
(fn create-door-mesh [bsp ambient]
|
|
||||||
(let [verts []
|
|
||||||
indices []
|
|
||||||
hw (/ DOOR_WIDTH 2)
|
|
||||||
y0 0
|
|
||||||
y1 DOOR_HEIGHT
|
|
||||||
x DOOR_X
|
|
||||||
sample (fn [vx vy vz]
|
|
||||||
(if bsp (bsp:light_at vx vy vz (or ambient 0)) 200))]
|
|
||||||
(table.insert verts {:x x :y y0 :z (- DOOR_Z hw) :u 0 :v 1 :nx -1 :ny 0 :nz 0 :color 80 :light (sample x y0 (- DOOR_Z hw))})
|
|
||||||
(table.insert verts {:x x :y y0 :z (+ DOOR_Z hw) :u 1 :v 1 :nx -1 :ny 0 :nz 0 :color 80 :light (sample x y0 (+ DOOR_Z hw))})
|
|
||||||
(table.insert verts {:x x :y y1 :z (+ DOOR_Z hw) :u 1 :v 0 :nx -1 :ny 0 :nz 0 :color 80 :light (sample x y1 (+ DOOR_Z hw))})
|
|
||||||
(table.insert verts {:x x :y y1 :z (- DOOR_Z hw) :u 0 :v 0 :nx -1 :ny 0 :nz 0 :color 80 :light (sample x y1 (- DOOR_Z hw))})
|
|
||||||
(table.insert indices 0)
|
|
||||||
(table.insert indices 1)
|
|
||||||
(table.insert indices 2)
|
|
||||||
(table.insert indices 0)
|
|
||||||
(table.insert indices 2)
|
|
||||||
(table.insert indices 3)
|
|
||||||
(set door-mesh (pxl8.create_mesh verts indices))))
|
|
||||||
|
|
||||||
(fn create-fireball-mesh []
|
(fn create-fireball-mesh []
|
||||||
(let [verts []
|
(let [verts []
|
||||||
indices []
|
indices []
|
||||||
|
|
@ -145,40 +118,15 @@
|
||||||
|
|
||||||
(set fireball-mesh (pxl8.create_mesh verts indices))))
|
(set fireball-mesh (pxl8.create_mesh verts indices))))
|
||||||
|
|
||||||
(fn get-door-position []
|
|
||||||
(values DOOR_X DOOR_Z))
|
|
||||||
|
|
||||||
(fn get-door-radius []
|
|
||||||
20)
|
|
||||||
|
|
||||||
(fn init [textures]
|
(fn init [textures]
|
||||||
(when (not fireball-mesh)
|
(when (not fireball-mesh)
|
||||||
(create-fireball-mesh))
|
(create-fireball-mesh)))
|
||||||
(when (and (not door-tex) textures)
|
|
||||||
(set door-tex (textures.door))))
|
|
||||||
|
|
||||||
(fn setup-lighting [bsp ambient]
|
(fn render-fireball [x y z]
|
||||||
(when (and (not door-mesh) bsp)
|
|
||||||
(create-door-mesh bsp ambient)))
|
|
||||||
|
|
||||||
(fn render-door [wireframe floor-y]
|
|
||||||
(when (and door-mesh door-tex)
|
|
||||||
(pxl8.draw_mesh door-mesh {:x 0 :y (or floor-y 0) :z 0
|
|
||||||
:texture door-tex
|
|
||||||
:lighting true
|
|
||||||
:double_sided true
|
|
||||||
:wireframe wireframe})))
|
|
||||||
|
|
||||||
(fn render-fireball [x y z wireframe]
|
|
||||||
(when fireball-mesh
|
(when fireball-mesh
|
||||||
(pxl8.draw_mesh fireball-mesh {:x x :y y :z z
|
(pxl8.draw_mesh fireball-mesh {:x x :y y :z z
|
||||||
:emissive true
|
:emissive true})))
|
||||||
:wireframe wireframe})))
|
|
||||||
|
|
||||||
{:FIREBALL_COLOR FIREBALL_COLOR
|
{:FIREBALL_COLOR FIREBALL_COLOR
|
||||||
:get-door-position get-door-position
|
|
||||||
:get-door-radius get-door-radius
|
|
||||||
:init init
|
:init init
|
||||||
:render-door render-door
|
:render-fireball render-fireball}
|
||||||
:render-fireball render-fireball
|
|
||||||
:setup-lighting setup-lighting}
|
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@
|
||||||
(local sky (require :mod.sky))
|
(local sky (require :mod.sky))
|
||||||
(local textures (require :mod.textures))
|
(local textures (require :mod.textures))
|
||||||
|
|
||||||
(local bob-amount 4.0)
|
(local bob-amount 3.0)
|
||||||
(local bob-speed 8.0)
|
(local bob-speed 6.0)
|
||||||
(local cam-smoothing 0.25)
|
(local cam-smoothing 0.25)
|
||||||
(local land-recovery-speed 20)
|
(local land-recovery-speed 20)
|
||||||
(local land-squash-amount -4)
|
(local land-squash-amount -4)
|
||||||
|
|
@ -31,7 +31,6 @@
|
||||||
|
|
||||||
(var auto-run-cancel-key nil)
|
(var auto-run-cancel-key nil)
|
||||||
(var auto-run? false)
|
(var auto-run? false)
|
||||||
(var was-auto-running false)
|
|
||||||
(var bob-time 0)
|
(var bob-time 0)
|
||||||
(var cam-pitch 0)
|
(var cam-pitch 0)
|
||||||
(var day-time 0)
|
(var day-time 0)
|
||||||
|
|
@ -40,29 +39,29 @@
|
||||||
(var cam-yaw 0)
|
(var cam-yaw 0)
|
||||||
(var cam-z 416)
|
(var cam-z 416)
|
||||||
(var camera nil)
|
(var camera nil)
|
||||||
(var ceiling-tex nil)
|
|
||||||
(var floor-tex nil)
|
|
||||||
(var land-squash 0)
|
(var land-squash 0)
|
||||||
(var last-dt 0.016)
|
(var last-dt 0.016)
|
||||||
(var light-time 0)
|
(var light-time 0)
|
||||||
(var glows nil)
|
(var glows nil)
|
||||||
(var lights nil)
|
(var lights nil)
|
||||||
(var bsp-materials-setup false)
|
(var materials-set? false)
|
||||||
(var network nil)
|
(var network nil)
|
||||||
(var portal-cooldown 0)
|
|
||||||
(var real-time 0)
|
(var real-time 0)
|
||||||
(var smooth-cam-x 416)
|
(var smooth-cam-x 416)
|
||||||
(var smooth-cam-z 416)
|
(var smooth-cam-z 416)
|
||||||
(var was-grounded true)
|
(var was-grounded true)
|
||||||
(var trim-tex nil)
|
|
||||||
(var wall-tex nil)
|
|
||||||
(var world nil)
|
(var world nil)
|
||||||
|
|
||||||
(local MOSS_COLOR 200)
|
(local MOSS_COLOR 200)
|
||||||
(local PLASTER_COLOR 16)
|
(local GRASS_COLOR 200)
|
||||||
(local STONE_WALL_START 2)
|
(local STONE_WALL_START 2)
|
||||||
(local WOOD_COLOR 88)
|
(local WOOD_COLOR 88)
|
||||||
|
|
||||||
|
(local ASHLAR_COLOR 64)
|
||||||
|
(local ASHLAR_MOSS 68)
|
||||||
|
(local STONE_FLOOR_COLOR 72)
|
||||||
|
(local STONE_TRIM_COLOR 72)
|
||||||
|
|
||||||
(fn preload []
|
(fn preload []
|
||||||
(when (not network)
|
(when (not network)
|
||||||
(set network (net.get))
|
(set network (net.get))
|
||||||
|
|
@ -71,8 +70,7 @@
|
||||||
(when (not world)
|
(when (not world)
|
||||||
(set world (pxl8.get_world))
|
(set world (pxl8.get_world))
|
||||||
(when world
|
(when world
|
||||||
(world:set_sim_config sim-cfg)
|
(world:set_sim_config sim-cfg))))
|
||||||
(world:init_local_player cam-x cam-y cam-z))))
|
|
||||||
|
|
||||||
(fn is-connected []
|
(fn is-connected []
|
||||||
(and network (network:connected)))
|
(and network (network:connected)))
|
||||||
|
|
@ -111,29 +109,38 @@
|
||||||
(when world
|
(when world
|
||||||
(world:init_local_player cam-x cam-y cam-z)
|
(world:init_local_player cam-x cam-y cam-z)
|
||||||
(set smooth-cam-x cam-x)
|
(set smooth-cam-x cam-x)
|
||||||
(set smooth-cam-z cam-z))
|
(set smooth-cam-z cam-z)))
|
||||||
|
|
||||||
(when (not ceiling-tex)
|
|
||||||
(set ceiling-tex (textures.plaster-wall 66666 PLASTER_COLOR)))
|
|
||||||
(when (not floor-tex)
|
|
||||||
(set floor-tex (textures.wood-planks 44444 WOOD_COLOR)))
|
|
||||||
(when (not trim-tex)
|
|
||||||
(set trim-tex (textures.wood-trim 77777 WOOD_COLOR)))
|
|
||||||
(when (not wall-tex)
|
|
||||||
(set wall-tex (textures.cobble-timber 55555 STONE_WALL_START MOSS_COLOR WOOD_COLOR))))
|
|
||||||
|
|
||||||
(fn setup-materials []
|
(fn setup-materials []
|
||||||
(when (and world (not bsp-materials-setup) floor-tex trim-tex wall-tex)
|
(when (not world) (lua "return"))
|
||||||
(let [chunk (world:active_chunk)]
|
(when (not network) (lua "return"))
|
||||||
(when (and chunk (chunk:ready))
|
(when materials-set? (lua "return"))
|
||||||
(let [floor-mat (pxl8.create_material {:texture floor-tex :lighting true :double_sided true})
|
(when (not (network:has_chunk)) (lua "return"))
|
||||||
trim-mat (pxl8.create_material {:texture trim-tex :lighting true :double_sided true})
|
(let [chunk (world:active_chunk)]
|
||||||
wall-mat (pxl8.create_material {:texture wall-tex :lighting true :double_sided true})]
|
(when (not chunk) (lua "return"))
|
||||||
(world:set_bsp_material 0 floor-mat)
|
(when (not (chunk:ready)) (lua "return"))
|
||||||
(world:set_bsp_material 1 wall-mat)
|
(let [dungeon-floor-tex (textures.wood-planks 44444 WOOD_COLOR)
|
||||||
(world:set_bsp_material 3 trim-mat)
|
dungeon-wall-tex (textures.cobble-timber 55555 STONE_WALL_START MOSS_COLOR WOOD_COLOR)
|
||||||
(entities.setup-lighting (chunk:bsp) 2)
|
dungeon-trim-tex (textures.wood-trim 77777 WOOD_COLOR)
|
||||||
(set bsp-materials-setup true))))))
|
court-floor-tex (textures.rough-stone 44442 STONE_FLOOR_COLOR)
|
||||||
|
court-wall-tex (textures.ashlar-wall 55552 ASHLAR_COLOR ASHLAR_MOSS)
|
||||||
|
court-trim-tex (textures.rough-stone 77772 STONE_TRIM_COLOR)
|
||||||
|
grass-tex (textures.grass-top 44447 GRASS_COLOR)
|
||||||
|
dungeon-floor-mat (pxl8.create_material {:texture dungeon-floor-tex :lighting true :double_sided true})
|
||||||
|
dungeon-wall-mat (pxl8.create_material {:texture dungeon-wall-tex :lighting true :double_sided true})
|
||||||
|
dungeon-trim-mat (pxl8.create_material {:texture dungeon-trim-tex :lighting true :double_sided true})
|
||||||
|
court-floor-mat (pxl8.create_material {:texture court-floor-tex :lighting true :double_sided true})
|
||||||
|
court-wall-mat (pxl8.create_material {:texture court-wall-tex :lighting true :double_sided true})
|
||||||
|
court-trim-mat (pxl8.create_material {:texture court-trim-tex :lighting true :double_sided true})
|
||||||
|
grass-mat (pxl8.create_material {:texture grass-tex :lighting true :double_sided true})]
|
||||||
|
(world:set_bsp_material 0 dungeon-floor-mat)
|
||||||
|
(world:set_bsp_material 1 dungeon-wall-mat)
|
||||||
|
(world:set_bsp_material 3 dungeon-trim-mat)
|
||||||
|
(world:set_bsp_material 4 court-floor-mat)
|
||||||
|
(world:set_bsp_material 5 court-wall-mat)
|
||||||
|
(world:set_bsp_material 6 court-trim-mat)
|
||||||
|
(world:set_bsp_material 7 grass-mat)
|
||||||
|
(set materials-set? true))))
|
||||||
|
|
||||||
(fn sample-input []
|
(fn sample-input []
|
||||||
(when (pxl8.key_pressed "`")
|
(when (pxl8.key_pressed "`")
|
||||||
|
|
@ -147,8 +154,6 @@
|
||||||
(when (and auto-run-cancel-key (not (pxl8.key_down auto-run-cancel-key)))
|
(when (and auto-run-cancel-key (not (pxl8.key_down auto-run-cancel-key)))
|
||||||
(set auto-run-cancel-key nil))
|
(set auto-run-cancel-key nil))
|
||||||
|
|
||||||
(set was-auto-running auto-run?)
|
|
||||||
|
|
||||||
(pxl8.make_input_msg
|
(pxl8.make_input_msg
|
||||||
{:move_x (+ (if (pxl8.key_down "d") 1 0) (if (pxl8.key_down "a") -1 0))
|
{:move_x (+ (if (pxl8.key_down "d") 1 0) (if (pxl8.key_down "a") -1 0))
|
||||||
:move_y (+ (if (or (pxl8.key_down "w") auto-run?) 1 0)
|
:move_y (+ (if (or (pxl8.key_down "w") auto-run?) 1 0)
|
||||||
|
|
@ -161,41 +166,6 @@
|
||||||
(set last-dt dt)
|
(set last-dt dt)
|
||||||
(setup-materials)
|
(setup-materials)
|
||||||
|
|
||||||
(when (> portal-cooldown 0)
|
|
||||||
(set portal-cooldown (- portal-cooldown dt)))
|
|
||||||
|
|
||||||
(when (and world network (<= portal-cooldown 0))
|
|
||||||
(let [(door-x door-z) (entities.get-door-position)
|
|
||||||
door-radius (entities.get-door-radius)
|
|
||||||
dist-to-door (math.sqrt (+ (* (- cam-x door-x) (- cam-x door-x))
|
|
||||||
(* (- cam-z door-z) (- cam-z door-z))))]
|
|
||||||
(when (< dist-to-door door-radius)
|
|
||||||
(let [current-id (network:chunk_id)]
|
|
||||||
(if (= current-id 1)
|
|
||||||
(do
|
|
||||||
(pxl8.info "Door: BSP 1 -> BSP 2")
|
|
||||||
(network:enter_scene 1 2 416 0 416)
|
|
||||||
(world:init_local_player 416 0 416)
|
|
||||||
(set cam-x 416)
|
|
||||||
(set cam-y 0)
|
|
||||||
(set cam-z 416)
|
|
||||||
(set smooth-cam-x 416)
|
|
||||||
(set smooth-cam-z 416)
|
|
||||||
(set bsp-materials-setup false)
|
|
||||||
(set portal-cooldown 2.0))
|
|
||||||
(= current-id 2)
|
|
||||||
(do
|
|
||||||
(pxl8.info "Door: BSP 2 -> BSP 1")
|
|
||||||
(network:enter_scene 1 1 416 0 416)
|
|
||||||
(world:init_local_player 416 0 416)
|
|
||||||
(set cam-x 416)
|
|
||||||
(set cam-y 0)
|
|
||||||
(set cam-z 416)
|
|
||||||
(set smooth-cam-x 416)
|
|
||||||
(set smooth-cam-z 416)
|
|
||||||
(set bsp-materials-setup false)
|
|
||||||
(set portal-cooldown 2.0)))))))
|
|
||||||
|
|
||||||
(when world
|
(when world
|
||||||
(let [input-msg (sample-input)
|
(let [input-msg (sample-input)
|
||||||
player (world:local_player)]
|
player (world:local_player)]
|
||||||
|
|
@ -286,9 +256,7 @@
|
||||||
(world:render [smooth-cam-x eye-y smooth-cam-z])
|
(world:render [smooth-cam-x eye-y smooth-cam-z])
|
||||||
|
|
||||||
(when chunk
|
(when chunk
|
||||||
(entities.render-fireball light-x light-y light-z (menu.is-wireframe)))
|
(entities.render-fireball light-x light-y light-z))
|
||||||
|
|
||||||
(entities.render-door (menu.is-wireframe) 0)
|
|
||||||
|
|
||||||
(pxl8.end_frame_3d)
|
(pxl8.end_frame_3d)
|
||||||
|
|
||||||
|
|
|
||||||
39
pxl8.sh
39
pxl8.sh
|
|
@ -43,10 +43,6 @@ NC='\033[0m'
|
||||||
RED='\033[38;2;251;73;52m'
|
RED='\033[38;2;251;73;52m'
|
||||||
YELLOW='\033[38;2;250;189;47m'
|
YELLOW='\033[38;2;250;189;47m'
|
||||||
|
|
||||||
if [[ "$(uname)" == "Linux" ]]; then
|
|
||||||
CFLAGS="$CFLAGS -D_GNU_SOURCE"
|
|
||||||
fi
|
|
||||||
|
|
||||||
build_luajit() {
|
build_luajit() {
|
||||||
if [[ ! -f "lib/luajit/src/libluajit.a" ]]; then
|
if [[ ! -f "lib/luajit/src/libluajit.a" ]]; then
|
||||||
print_info "Building LuaJIT"
|
print_info "Building LuaJIT"
|
||||||
|
|
@ -167,6 +163,37 @@ compile_source_file() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generate_compile_commands() {
|
||||||
|
local project_dir
|
||||||
|
project_dir="$(pwd)"
|
||||||
|
local tmpfile
|
||||||
|
tmpfile=$(mktemp)
|
||||||
|
local first=true
|
||||||
|
|
||||||
|
echo "[" > "$tmpfile"
|
||||||
|
|
||||||
|
for src_file in $LIB_SOURCE_FILES; do
|
||||||
|
src_file="${src_file## }"
|
||||||
|
src_file="${src_file%% }"
|
||||||
|
[[ -z "$src_file" ]] && continue
|
||||||
|
if $first; then first=false; else printf ",\n" >> "$tmpfile"; fi
|
||||||
|
printf ' {\n "directory": "%s",\n "command": "clang -c %s %s",\n "file": "%s"\n }' \
|
||||||
|
"$project_dir" "$DEP_COMPILE_FLAGS" "$src_file" "$src_file" >> "$tmpfile"
|
||||||
|
done
|
||||||
|
|
||||||
|
for src_file in $PXL8_SOURCE_FILES; do
|
||||||
|
src_file="${src_file## }"
|
||||||
|
src_file="${src_file%% }"
|
||||||
|
[[ -z "$src_file" ]] && continue
|
||||||
|
if $first; then first=false; else printf ",\n" >> "$tmpfile"; fi
|
||||||
|
printf ' {\n "directory": "%s",\n "command": "clang -c %s %s",\n "file": "%s"\n }' \
|
||||||
|
"$project_dir" "$COMPILE_FLAGS" "$src_file" "$src_file" >> "$tmpfile"
|
||||||
|
done
|
||||||
|
|
||||||
|
printf "\n]\n" >> "$tmpfile"
|
||||||
|
mv "$tmpfile" "compile_commands.json"
|
||||||
|
}
|
||||||
|
|
||||||
compile_shaders() {
|
compile_shaders() {
|
||||||
local build_mode="$1"
|
local build_mode="$1"
|
||||||
local shader_dir="src/gfx/shaders/cpu"
|
local shader_dir="src/gfx/shaders/cpu"
|
||||||
|
|
@ -536,11 +563,13 @@ case "$COMMAND" in
|
||||||
"
|
"
|
||||||
|
|
||||||
if [[ "$HAS_SDL3" -eq 1 ]]; then
|
if [[ "$HAS_SDL3" -eq 1 ]]; then
|
||||||
PXL8_SOURCE_FILES="$PXL8_SOURCE_FILES src/hal/pxl8_mem_sdl3.c"
|
PXL8_SOURCE_FILES="$PXL8_SOURCE_FILES src/hal/pxl8_io_sdl3.c src/hal/pxl8_mem_sdl3.c"
|
||||||
else
|
else
|
||||||
PXL8_SOURCE_FILES="$PXL8_SOURCE_FILES src/hal/pxl8_mem.c"
|
PXL8_SOURCE_FILES="$PXL8_SOURCE_FILES src/hal/pxl8_mem.c"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
generate_compile_commands
|
||||||
|
|
||||||
LUAJIT_LIB="lib/luajit/src/libluajit.a"
|
LUAJIT_LIB="lib/luajit/src/libluajit.a"
|
||||||
OBJECT_DIR="$BUILDDIR/obj"
|
OBJECT_DIR="$BUILDDIR/obj"
|
||||||
mkdir -p "$OBJECT_DIR"
|
mkdir -p "$OBJECT_DIR"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
extern crate alloc;
|
|
||||||
|
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
|
@ -146,6 +144,7 @@ pub struct Bsp {
|
||||||
pub vertex_lights: Box<[u32]>,
|
pub vertex_lights: Box<[u32]>,
|
||||||
pub vertices: Box<[Vertex]>,
|
pub vertices: Box<[Vertex]>,
|
||||||
pub visdata: Box<[u8]>,
|
pub visdata: Box<[u8]>,
|
||||||
|
pub heightfield: Box<[f32]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -161,6 +160,16 @@ pub struct BspBuilder {
|
||||||
pub vertex_lights: Vec<u32>,
|
pub vertex_lights: Vec<u32>,
|
||||||
pub vertices: Vec<Vertex>,
|
pub vertices: Vec<Vertex>,
|
||||||
pub visdata: Vec<u8>,
|
pub visdata: Vec<u8>,
|
||||||
|
pub heightfield: Vec<f32>,
|
||||||
|
pub heightfield_w: u16,
|
||||||
|
pub heightfield_h: u16,
|
||||||
|
pub heightfield_ox: f32,
|
||||||
|
pub heightfield_oz: f32,
|
||||||
|
pub heightfield_cell_size: f32,
|
||||||
|
pub bounds_min_x: f32,
|
||||||
|
pub bounds_min_z: f32,
|
||||||
|
pub bounds_max_x: f32,
|
||||||
|
pub bounds_max_z: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BspBuilder {
|
impl BspBuilder {
|
||||||
|
|
@ -182,6 +191,7 @@ impl From<BspBuilder> for Bsp {
|
||||||
let vertex_lights = b.vertex_lights.into_boxed_slice();
|
let vertex_lights = b.vertex_lights.into_boxed_slice();
|
||||||
let vertices = b.vertices.into_boxed_slice();
|
let vertices = b.vertices.into_boxed_slice();
|
||||||
let visdata = b.visdata.into_boxed_slice();
|
let visdata = b.visdata.into_boxed_slice();
|
||||||
|
let heightfield = b.heightfield.into_boxed_slice();
|
||||||
|
|
||||||
let inner = pxl8_bsp {
|
let inner = pxl8_bsp {
|
||||||
cell_portals: if cell_portals.is_empty() { core::ptr::null_mut() } else { cell_portals.as_ptr() as *mut _ },
|
cell_portals: if cell_portals.is_empty() { core::ptr::null_mut() } else { cell_portals.as_ptr() as *mut _ },
|
||||||
|
|
@ -198,6 +208,7 @@ impl From<BspBuilder> for Bsp {
|
||||||
vertex_lights: if vertex_lights.is_empty() { core::ptr::null_mut() } else { vertex_lights.as_ptr() as *mut _ },
|
vertex_lights: if vertex_lights.is_empty() { core::ptr::null_mut() } else { vertex_lights.as_ptr() as *mut _ },
|
||||||
vertices: if vertices.is_empty() { core::ptr::null_mut() } else { vertices.as_ptr() as *mut _ },
|
vertices: if vertices.is_empty() { core::ptr::null_mut() } else { vertices.as_ptr() as *mut _ },
|
||||||
visdata: if visdata.is_empty() { core::ptr::null_mut() } else { visdata.as_ptr() as *mut _ },
|
visdata: if visdata.is_empty() { core::ptr::null_mut() } else { visdata.as_ptr() as *mut _ },
|
||||||
|
heightfield: if heightfield.is_empty() { core::ptr::null_mut() } else { heightfield.as_ptr() as *mut _ },
|
||||||
lightdata_size: 0,
|
lightdata_size: 0,
|
||||||
num_cell_portals: cell_portals.len() as u32,
|
num_cell_portals: cell_portals.len() as u32,
|
||||||
num_edges: edges.len() as u32,
|
num_edges: edges.len() as u32,
|
||||||
|
|
@ -211,7 +222,17 @@ impl From<BspBuilder> for Bsp {
|
||||||
num_surfedges: surfedges.len() as u32,
|
num_surfedges: surfedges.len() as u32,
|
||||||
num_vertex_lights: vertex_lights.len() as u32,
|
num_vertex_lights: vertex_lights.len() as u32,
|
||||||
num_vertices: vertices.len() as u32,
|
num_vertices: vertices.len() as u32,
|
||||||
|
num_heightfield: heightfield.len() as u32,
|
||||||
|
heightfield_ox: b.heightfield_ox,
|
||||||
|
heightfield_oz: b.heightfield_oz,
|
||||||
|
heightfield_cell_size: b.heightfield_cell_size,
|
||||||
|
heightfield_w: b.heightfield_w,
|
||||||
|
heightfield_h: b.heightfield_h,
|
||||||
visdata_size: visdata.len() as u32,
|
visdata_size: visdata.len() as u32,
|
||||||
|
bounds_min_x: b.bounds_min_x,
|
||||||
|
bounds_min_z: b.bounds_min_z,
|
||||||
|
bounds_max_x: b.bounds_max_x,
|
||||||
|
bounds_max_z: b.bounds_max_z,
|
||||||
};
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -227,6 +248,7 @@ impl From<BspBuilder> for Bsp {
|
||||||
vertex_lights,
|
vertex_lights,
|
||||||
vertices,
|
vertices,
|
||||||
visdata,
|
visdata,
|
||||||
|
heightfield,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,23 @@
|
||||||
extern crate alloc;
|
|
||||||
|
|
||||||
pub mod stream;
|
pub mod stream;
|
||||||
|
|
||||||
use crate::bsp::Bsp;
|
use crate::bsp::Bsp;
|
||||||
use crate::math::Vec3;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||||
pub enum ChunkId {
|
pub enum ChunkId {
|
||||||
Bsp(u32),
|
Bsp { cx: i32, cz: i32 },
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Chunk {
|
pub enum Chunk {
|
||||||
Bsp { id: u32, bsp: Bsp, version: u32 },
|
Bsp { cx: i32, cz: i32, bsp: Bsp, version: u32 },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Chunk {
|
impl Chunk {
|
||||||
pub fn id(&self) -> ChunkId {
|
|
||||||
match self {
|
|
||||||
Chunk::Bsp { id, .. } => ChunkId::Bsp(*id),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn version(&self) -> u32 {
|
pub fn version(&self) -> u32 {
|
||||||
match self {
|
match self {
|
||||||
Chunk::Bsp { version, .. } => *version,
|
Chunk::Bsp { version, .. } => *version,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 {
|
|
||||||
match self {
|
|
||||||
Chunk::Bsp { bsp, .. } => bsp.trace(from, to, radius),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_bsp(&self) -> Option<&Bsp> {
|
pub fn as_bsp(&self) -> Option<&Bsp> {
|
||||||
match self {
|
match self {
|
||||||
Chunk::Bsp { bsp, .. } => Some(bsp),
|
Chunk::Bsp { bsp, .. } => Some(bsp),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
extern crate alloc;
|
|
||||||
|
|
||||||
use alloc::collections::BTreeMap;
|
use alloc::collections::BTreeMap;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
|
@ -28,11 +26,11 @@ impl ClientChunkState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next_pending(&mut self) -> Option<ChunkId> {
|
pub fn next_pending(&mut self) -> Option<ChunkId> {
|
||||||
self.pending.pop()
|
if self.pending.is_empty() {
|
||||||
}
|
None
|
||||||
|
} else {
|
||||||
pub fn queue_message(&mut self, msg: ChunkMessage) {
|
Some(self.pending.remove(0))
|
||||||
self.pending_messages.push(msg);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn queue_messages(&mut self, msgs: Vec<ChunkMessage>) {
|
pub fn queue_messages(&mut self, msgs: Vec<ChunkMessage>) {
|
||||||
|
|
@ -50,23 +48,6 @@ impl ClientChunkState {
|
||||||
pub fn mark_sent(&mut self, id: ChunkId, version: u32) {
|
pub fn mark_sent(&mut self, id: ChunkId, version: u32) {
|
||||||
self.known.insert(id, version);
|
self.known.insert(id, version);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_pending(&self) -> bool {
|
|
||||||
!self.pending.is_empty() || !self.pending_messages.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear_pending(&mut self) {
|
|
||||||
self.pending.clear();
|
|
||||||
self.pending_messages.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear_known_bsp(&mut self) {
|
|
||||||
self.known.retain(|id, _| !matches!(id, ChunkId::Bsp(_)));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn forget(&mut self, id: &ChunkId) {
|
|
||||||
self.known.remove(id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ClientChunkState {
|
impl Default for ClientChunkState {
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ pub use bsp::*;
|
||||||
pub use chunk::*;
|
pub use chunk::*;
|
||||||
pub use chunk::stream::*;
|
pub use chunk::stream::*;
|
||||||
pub use math::*;
|
pub use math::*;
|
||||||
pub use procgen::{ProcgenParams, generate, generate_rooms};
|
pub use procgen::generate_chunk;
|
||||||
pub use pxl8::*;
|
pub use pxl8::*;
|
||||||
pub use sim::*;
|
pub use sim::*;
|
||||||
pub use transport::*;
|
pub use transport::*;
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,12 @@
|
||||||
|
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
|
||||||
|
use alloc::vec::Vec;
|
||||||
use pxl8d::*;
|
use pxl8d::*;
|
||||||
use pxl8d::chunk::ChunkId;
|
use pxl8d::chunk::ChunkId;
|
||||||
use pxl8d::chunk::stream::ClientChunkState;
|
use pxl8d::chunk::stream::ClientChunkState;
|
||||||
|
use pxl8d::pxl8::pxl8_cmd_type::*;
|
||||||
|
use pxl8d::pxl8::pxl8_msg_type::*;
|
||||||
|
|
||||||
const TICK_RATE: u64 = 30;
|
const TICK_RATE: u64 = 30;
|
||||||
const TICK_NS: u64 = 1_000_000_000 / TICK_RATE;
|
const TICK_NS: u64 = 1_000_000_000 / TICK_RATE;
|
||||||
|
|
@ -73,6 +76,9 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
|
||||||
let mut player_id: Option<u64> = None;
|
let mut player_id: Option<u64> = None;
|
||||||
let mut last_client_tick: u64 = 0;
|
let mut last_client_tick: u64 = 0;
|
||||||
let mut client_chunks = ClientChunkState::new();
|
let mut client_chunks = ClientChunkState::new();
|
||||||
|
let mut player_cx: i32 = 0;
|
||||||
|
let mut player_cz: i32 = 0;
|
||||||
|
let mut has_sent_initial_enter = false;
|
||||||
|
|
||||||
let mut sequence: u32 = 0;
|
let mut sequence: u32 = 0;
|
||||||
let mut last_tick = get_time_ns();
|
let mut last_tick = get_time_ns();
|
||||||
|
|
@ -93,52 +99,14 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
|
||||||
let mut latest_input: Option<pxl8d::pxl8_input_msg> = None;
|
let mut latest_input: Option<pxl8d::pxl8_input_msg> = None;
|
||||||
while let Some(msg_type) = transport.recv() {
|
while let Some(msg_type) = transport.recv() {
|
||||||
match msg_type {
|
match msg_type {
|
||||||
x if x == pxl8d::pxl8_msg_type::PXL8_MSG_INPUT as u8 => {
|
x if x == PXL8_MSG_INPUT as u8 => {
|
||||||
latest_input = Some(transport.get_input());
|
latest_input = Some(transport.get_input());
|
||||||
}
|
}
|
||||||
x if x == pxl8d::pxl8_msg_type::PXL8_MSG_COMMAND as u8 => {
|
x if x == PXL8_MSG_COMMAND as u8 => {
|
||||||
let cmd = transport.get_command();
|
let cmd = transport.get_command();
|
||||||
if cmd.cmd_type == pxl8d::pxl8_cmd_type::PXL8_CMD_SPAWN_ENTITY as u16 {
|
if cmd.cmd_type == PXL8_CMD_SPAWN_ENTITY as u16 {
|
||||||
let (x, y, z, _yaw, _pitch) = extract_spawn_position(&cmd.payload);
|
let (x, y, z, _yaw, _pitch) = extract_spawn_position(&cmd.payload);
|
||||||
player_id = Some(sim.spawn_player(x, y, z) as u64);
|
player_id = Some(sim.spawn_player(x, y, z) as u64);
|
||||||
|
|
||||||
sim.world.generate_bsp(1, None);
|
|
||||||
sim.world.set_active(ChunkId::Bsp(1));
|
|
||||||
client_chunks.request(ChunkId::Bsp(1));
|
|
||||||
transport.send_chunk_enter(1, transport::CHUNK_TYPE_BSP, sequence);
|
|
||||||
sequence = sequence.wrapping_add(1);
|
|
||||||
} else if cmd.cmd_type == pxl8d::pxl8_cmd_type::PXL8_CMD_ENTER_SCENE as u16 {
|
|
||||||
let chunk_id = u32::from_be_bytes([
|
|
||||||
cmd.payload[0], cmd.payload[1], cmd.payload[2], cmd.payload[3]
|
|
||||||
]);
|
|
||||||
let pos_x = f32::from_be_bytes([cmd.payload[8], cmd.payload[9], cmd.payload[10], cmd.payload[11]]);
|
|
||||||
let pos_y = f32::from_be_bytes([cmd.payload[12], cmd.payload[13], cmd.payload[14], cmd.payload[15]]);
|
|
||||||
let pos_z = f32::from_be_bytes([cmd.payload[16], cmd.payload[17], cmd.payload[18], cmd.payload[19]]);
|
|
||||||
|
|
||||||
sim.world.clear_active();
|
|
||||||
client_chunks.clear_pending();
|
|
||||||
client_chunks.clear_known_bsp();
|
|
||||||
transport.send_chunk_exit(sequence);
|
|
||||||
sequence = sequence.wrapping_add(1);
|
|
||||||
|
|
||||||
let params = match chunk_id {
|
|
||||||
2 => Some(ProcgenParams {
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
seed: 12345u32.wrapping_add(2),
|
|
||||||
min_room_size: 14,
|
|
||||||
max_room_size: 16,
|
|
||||||
num_rooms: 1,
|
|
||||||
}),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
sim.world.generate_bsp(chunk_id, params.as_ref());
|
|
||||||
sim.world.set_active(ChunkId::Bsp(chunk_id));
|
|
||||||
client_chunks.request(ChunkId::Bsp(chunk_id));
|
|
||||||
transport.send_chunk_enter(chunk_id, transport::CHUNK_TYPE_BSP, sequence);
|
|
||||||
sequence = sequence.wrapping_add(1);
|
|
||||||
|
|
||||||
sim.teleport_player(pos_x, pos_y, pos_z);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
@ -154,7 +122,7 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
sim.generate_snapshot(player_id.unwrap_or(u64::MAX), |state| {
|
sim.generate_snapshot(|state| {
|
||||||
if count < entities_buf.len() {
|
if count < entities_buf.len() {
|
||||||
entities_buf[count] = *state;
|
entities_buf[count] = *state;
|
||||||
count += 1;
|
count += 1;
|
||||||
|
|
@ -173,22 +141,46 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
|
||||||
sequence = sequence.wrapping_add(1);
|
sequence = sequence.wrapping_add(1);
|
||||||
|
|
||||||
if let Some(pid) = player_id {
|
if let Some(pid) = player_id {
|
||||||
if let Some(_player) = sim.get_player_position(pid) {
|
if let Some((px, _py, pz)) = sim.get_player_position(pid) {
|
||||||
|
let new_cx = libm::floorf(px / 1024.0) as i32;
|
||||||
|
let new_cz = libm::floorf(pz / 1024.0) as i32;
|
||||||
|
|
||||||
|
for dz in -2..=2_i32 {
|
||||||
|
for dx in -2..=2_i32 {
|
||||||
|
let cx = new_cx + dx;
|
||||||
|
let cz = new_cz + dz;
|
||||||
|
sim.world.generate_bsp(cx, cz);
|
||||||
|
client_chunks.request(ChunkId::Bsp { cx, cz });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !has_sent_initial_enter || player_cx != new_cx || player_cz != new_cz {
|
||||||
|
player_cx = new_cx;
|
||||||
|
player_cz = new_cz;
|
||||||
|
sim.world.set_active(ChunkId::Bsp { cx: new_cx, cz: new_cz });
|
||||||
|
transport.send_chunk_enter(new_cx, new_cz, sequence);
|
||||||
|
sequence = sequence.wrapping_add(1);
|
||||||
|
has_sent_initial_enter = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut burst = false;
|
||||||
while let Some(chunk_id) = client_chunks.next_pending() {
|
while let Some(chunk_id) = client_chunks.next_pending() {
|
||||||
match chunk_id {
|
match chunk_id {
|
||||||
ChunkId::Bsp(id) => {
|
ChunkId::Bsp { cx, cz } => {
|
||||||
if let Some(chunk) = sim.world.get(&chunk_id) {
|
if let Some(chunk) = sim.world.get(&chunk_id) {
|
||||||
if let Some(bsp) = chunk.as_bsp() {
|
if let Some(bsp) = chunk.as_bsp() {
|
||||||
let msgs = bsp_to_messages(bsp, id, chunk.version());
|
let msgs = bsp_to_messages(bsp, cx, cz, chunk.version());
|
||||||
client_chunks.queue_messages(msgs);
|
client_chunks.queue_messages(msgs);
|
||||||
client_chunks.mark_sent(chunk_id, chunk.version());
|
client_chunks.mark_sent(chunk_id, chunk.version());
|
||||||
|
burst = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _ in 0..8 {
|
let send_limit = if burst { 64 } else { 8 };
|
||||||
|
for _ in 0..send_limit {
|
||||||
if let Some(msg) = client_chunks.next_message() {
|
if let Some(msg) = client_chunks.next_message() {
|
||||||
transport.send_chunk(&msg, sequence);
|
transport.send_chunk(&msg, sequence);
|
||||||
sequence = sequence.wrapping_add(1);
|
sequence = sequence.wrapping_add(1);
|
||||||
|
|
@ -202,30 +194,21 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bsp_to_messages(bsp: &bsp::Bsp, id: u32, version: u32) -> alloc::vec::Vec<transport::ChunkMessage> {
|
fn bsp_to_messages(bsp: &bsp::Bsp, cx: i32, cz: i32, version: u32) -> Vec<transport::ChunkMessage> {
|
||||||
use alloc::vec::Vec;
|
|
||||||
|
|
||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
|
|
||||||
let num_verts = bsp.vertices.len();
|
data.extend_from_slice(&(bsp.vertices.len() as u32).to_be_bytes());
|
||||||
let num_edges = bsp.edges.len();
|
data.extend_from_slice(&(bsp.edges.len() as u32).to_be_bytes());
|
||||||
let num_faces = bsp.faces.len();
|
data.extend_from_slice(&(bsp.faces.len() as u32).to_be_bytes());
|
||||||
let num_planes = bsp.planes.len();
|
data.extend_from_slice(&(bsp.planes.len() as u32).to_be_bytes());
|
||||||
let num_nodes = bsp.nodes.len();
|
data.extend_from_slice(&(bsp.nodes.len() as u32).to_be_bytes());
|
||||||
let num_leafs = bsp.leafs.len();
|
data.extend_from_slice(&(bsp.leafs.len() as u32).to_be_bytes());
|
||||||
let num_surfedges = bsp.surfedges.len();
|
data.extend_from_slice(&(bsp.surfedges.len() as u32).to_be_bytes());
|
||||||
|
|
||||||
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.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.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.visdata.len() as u32).to_be_bytes());
|
||||||
data.extend_from_slice(&(bsp.vertex_lights.len() as u32).to_be_bytes());
|
data.extend_from_slice(&(bsp.vertex_lights.len() as u32).to_be_bytes());
|
||||||
|
data.extend_from_slice(&(bsp.heightfield.len() as u32).to_be_bytes());
|
||||||
|
|
||||||
for v in &bsp.vertices {
|
for v in &bsp.vertices {
|
||||||
data.extend_from_slice(&v.position.x.to_be_bytes());
|
data.extend_from_slice(&v.position.x.to_be_bytes());
|
||||||
|
|
@ -318,5 +301,23 @@ fn bsp_to_messages(bsp: &bsp::Bsp, id: u32, version: u32) -> alloc::vec::Vec<tra
|
||||||
data.extend_from_slice(&vl.to_be_bytes());
|
data.extend_from_slice(&vl.to_be_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
transport::ChunkMessage::from_bsp(data, id, version)
|
if !bsp.heightfield.is_empty() {
|
||||||
|
for h in &bsp.heightfield {
|
||||||
|
data.extend_from_slice(&h.to_be_bytes());
|
||||||
|
}
|
||||||
|
let c_bsp = bsp.as_c_bsp();
|
||||||
|
data.extend_from_slice(&c_bsp.heightfield_w.to_be_bytes());
|
||||||
|
data.extend_from_slice(&c_bsp.heightfield_h.to_be_bytes());
|
||||||
|
data.extend_from_slice(&c_bsp.heightfield_ox.to_be_bytes());
|
||||||
|
data.extend_from_slice(&c_bsp.heightfield_oz.to_be_bytes());
|
||||||
|
data.extend_from_slice(&c_bsp.heightfield_cell_size.to_be_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
let c_bsp = bsp.as_c_bsp();
|
||||||
|
data.extend_from_slice(&c_bsp.bounds_min_x.to_be_bytes());
|
||||||
|
data.extend_from_slice(&c_bsp.bounds_min_z.to_be_bytes());
|
||||||
|
data.extend_from_slice(&c_bsp.bounds_max_x.to_be_bytes());
|
||||||
|
data.extend_from_slice(&c_bsp.bounds_max_z.to_be_bytes());
|
||||||
|
|
||||||
|
transport::ChunkMessage::from_bsp(data, cx, cz, version)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1466
pxl8d/src/procgen.rs
1466
pxl8d/src/procgen.rs
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,6 @@
|
||||||
extern crate alloc;
|
|
||||||
|
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
|
||||||
|
use crate::chunk::ChunkId;
|
||||||
use crate::math::Vec3;
|
use crate::math::Vec3;
|
||||||
use crate::pxl8::*;
|
use crate::pxl8::*;
|
||||||
use crate::world::World;
|
use crate::world::World;
|
||||||
|
|
@ -96,13 +95,6 @@ impl Simulation {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn teleport_player(&mut self, x: f32, y: f32, z: f32) {
|
|
||||||
if let Some(id) = self.player {
|
|
||||||
let ent = &mut self.entities[id as usize];
|
|
||||||
ent.pos = Vec3::new(x, y, z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn step(&mut self, inputs: &[pxl8_input_msg], dt: f32) {
|
pub fn step(&mut self, inputs: &[pxl8_input_msg], dt: f32) {
|
||||||
self.tick += 1;
|
self.tick += 1;
|
||||||
self.time += dt;
|
self.time += dt;
|
||||||
|
|
@ -114,22 +106,8 @@ impl Simulation {
|
||||||
self.integrate(dt);
|
self.integrate(dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_sim_world(&self, _pos: Vec3) -> pxl8_sim_world {
|
fn sim_config() -> pxl8_sim_config {
|
||||||
if let Some(chunk) = self.world.active() {
|
pxl8_sim_config {
|
||||||
if let Some(bsp) = chunk.as_bsp() {
|
|
||||||
return pxl8_sim_world {
|
|
||||||
bsp: bsp.as_c_bsp(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pxl8_sim_world {
|
|
||||||
bsp: core::ptr::null(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn integrate(&mut self, dt: f32) {
|
|
||||||
let cfg = pxl8_sim_config {
|
|
||||||
move_speed: 180.0,
|
move_speed: 180.0,
|
||||||
ground_accel: 10.0,
|
ground_accel: 10.0,
|
||||||
air_accel: 1.0,
|
air_accel: 1.0,
|
||||||
|
|
@ -140,7 +118,39 @@ impl Simulation {
|
||||||
player_radius: 16.0,
|
player_radius: 16.0,
|
||||||
player_height: 72.0,
|
player_height: 72.0,
|
||||||
max_pitch: 1.5,
|
max_pitch: 1.5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_sim_world(&self, player_pos: Vec3) -> pxl8_sim_world {
|
||||||
|
let chunk_size: f32 = 16.0 * 64.0;
|
||||||
|
let center_cx = libm::floorf(player_pos.x / chunk_size) as i32;
|
||||||
|
let center_cz = libm::floorf(player_pos.z / chunk_size) as i32;
|
||||||
|
|
||||||
|
let mut sim = pxl8_sim_world {
|
||||||
|
chunks: [core::ptr::null(); 9],
|
||||||
|
center_cx,
|
||||||
|
center_cz,
|
||||||
|
chunk_size,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
for dz in -1..=1i32 {
|
||||||
|
for dx in -1..=1i32 {
|
||||||
|
let cx = center_cx + dx;
|
||||||
|
let cz = center_cz + dz;
|
||||||
|
let id = ChunkId::Bsp { cx, cz };
|
||||||
|
if let Some(chunk) = self.world.get(&id) {
|
||||||
|
if let Some(bsp) = chunk.as_bsp() {
|
||||||
|
let idx = ((dz + 1) * 3 + (dx + 1)) as usize;
|
||||||
|
sim.chunks[idx] = bsp.as_c_bsp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sim
|
||||||
|
}
|
||||||
|
|
||||||
|
fn integrate(&mut self, dt: f32) {
|
||||||
|
let cfg = Self::sim_config();
|
||||||
for i in 0..self.entities.len() {
|
for i in 0..self.entities.len() {
|
||||||
let ent = &self.entities[i];
|
let ent = &self.entities[i];
|
||||||
if ent.flags & ALIVE == 0 || ent.flags & PLAYER != 0 {
|
if ent.flags & ALIVE == 0 || ent.flags & PLAYER != 0 {
|
||||||
|
|
@ -156,18 +166,7 @@ impl Simulation {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_player(&mut self, input: &pxl8_input_msg, dt: f32) {
|
fn move_player(&mut self, input: &pxl8_input_msg, dt: f32) {
|
||||||
let cfg = pxl8_sim_config {
|
let cfg = Self::sim_config();
|
||||||
move_speed: 180.0,
|
|
||||||
ground_accel: 10.0,
|
|
||||||
air_accel: 1.0,
|
|
||||||
stop_speed: 100.0,
|
|
||||||
friction: 6.0,
|
|
||||||
gravity: 800.0,
|
|
||||||
jump_velocity: 200.0,
|
|
||||||
player_radius: 16.0,
|
|
||||||
player_height: 72.0,
|
|
||||||
max_pitch: 1.5,
|
|
||||||
};
|
|
||||||
let Some(id) = self.player else { return };
|
let Some(id) = self.player else { return };
|
||||||
let ent = &self.entities[id as usize];
|
let ent = &self.entities[id as usize];
|
||||||
if ent.flags & ALIVE == 0 {
|
if ent.flags & ALIVE == 0 {
|
||||||
|
|
@ -181,7 +180,7 @@ impl Simulation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_snapshot<F>(&self, _player_id: u64, mut writer: F)
|
pub fn generate_snapshot<F>(&self, mut writer: F)
|
||||||
where
|
where
|
||||||
F: FnMut(&pxl8_entity_state),
|
F: FnMut(&pxl8_entity_state),
|
||||||
{
|
{
|
||||||
|
|
@ -211,10 +210,6 @@ impl Simulation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn entity_count(&self) -> usize {
|
|
||||||
self.entities.iter().filter(|e| e.flags & ALIVE != 0).count()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_player_position(&self, player_id: u64) -> Option<(f32, f32, f32)> {
|
pub fn get_player_position(&self, player_id: u64) -> Option<(f32, f32, f32)> {
|
||||||
let idx = player_id as usize;
|
let idx = player_id as usize;
|
||||||
if idx < self.entities.len() {
|
if idx < self.entities.len() {
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,18 @@
|
||||||
extern crate alloc;
|
|
||||||
|
|
||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use crate::pxl8::*;
|
use crate::pxl8::*;
|
||||||
use crate::pxl8::pxl8_msg_type::*;
|
use crate::pxl8::pxl8_msg_type::*;
|
||||||
pub const DEFAULT_PORT: u16 = 7777;
|
pub const DEFAULT_PORT: u16 = 7777;
|
||||||
pub const CHUNK_MAX_PAYLOAD: usize = 1400;
|
pub const CHUNK_MAX_PAYLOAD: usize = 1400;
|
||||||
pub const CHUNK_FLAG_RLE: u8 = 0x01;
|
|
||||||
pub const CHUNK_FLAG_FINAL: u8 = 0x04;
|
pub const CHUNK_FLAG_FINAL: u8 = 0x04;
|
||||||
|
|
||||||
pub const CHUNK_TYPE_BSP: u8 = 1;
|
pub const CHUNK_TYPE_BSP: u8 = 1;
|
||||||
|
|
||||||
|
pub fn chunk_hash(cx: i32, cz: i32) -> u32 {
|
||||||
|
let h = (cx as u32).wrapping_mul(374761393).wrapping_add((cz as u32).wrapping_mul(668265263));
|
||||||
|
h ^ (h >> 16)
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ChunkMessage {
|
pub struct ChunkMessage {
|
||||||
pub chunk_type: u8,
|
pub chunk_type: u8,
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
|
|
@ -25,16 +27,17 @@ pub struct ChunkMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChunkMessage {
|
impl ChunkMessage {
|
||||||
pub fn from_bsp(data: Vec<u8>, chunk_id: u32, version: u32) -> Vec<ChunkMessage> {
|
pub fn from_bsp(data: Vec<u8>, cx: i32, cz: i32, version: u32) -> Vec<ChunkMessage> {
|
||||||
let total_size = data.len();
|
let total_size = data.len();
|
||||||
|
let id = chunk_hash(cx, cz);
|
||||||
|
|
||||||
if total_size <= CHUNK_MAX_PAYLOAD {
|
if total_size <= CHUNK_MAX_PAYLOAD {
|
||||||
return vec![ChunkMessage {
|
return vec![ChunkMessage {
|
||||||
chunk_type: CHUNK_TYPE_BSP,
|
chunk_type: CHUNK_TYPE_BSP,
|
||||||
id: chunk_id,
|
id,
|
||||||
cx: 0,
|
cx,
|
||||||
cy: 0,
|
cy: 0,
|
||||||
cz: 0,
|
cz,
|
||||||
version,
|
version,
|
||||||
flags: CHUNK_FLAG_FINAL,
|
flags: CHUNK_FLAG_FINAL,
|
||||||
fragment_idx: 0,
|
fragment_idx: 0,
|
||||||
|
|
@ -53,10 +56,10 @@ impl ChunkMessage {
|
||||||
|
|
||||||
messages.push(ChunkMessage {
|
messages.push(ChunkMessage {
|
||||||
chunk_type: CHUNK_TYPE_BSP,
|
chunk_type: CHUNK_TYPE_BSP,
|
||||||
id: chunk_id,
|
id,
|
||||||
cx: 0,
|
cx,
|
||||||
cy: 0,
|
cy: 0,
|
||||||
cz: 0,
|
cz,
|
||||||
version,
|
version,
|
||||||
flags: if is_final { CHUNK_FLAG_FINAL } else { 0 },
|
flags: if is_final { CHUNK_FLAG_FINAL } else { 0 },
|
||||||
fragment_idx: i as u8,
|
fragment_idx: i as u8,
|
||||||
|
|
@ -362,7 +365,7 @@ impl Transport {
|
||||||
sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr);
|
sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_chunk_enter(&mut self, chunk_id: u32, chunk_type: u8, sequence: u32) {
|
pub fn send_chunk_enter(&mut self, cx: i32, cz: i32, sequence: u32) {
|
||||||
if !self.has_client {
|
if !self.has_client {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -378,34 +381,13 @@ impl Transport {
|
||||||
offset += self.serialize_header(&msg_header, offset);
|
offset += self.serialize_header(&msg_header, offset);
|
||||||
|
|
||||||
let buf = &mut self.send_buf[offset..];
|
let buf = &mut self.send_buf[offset..];
|
||||||
buf[0..4].copy_from_slice(&chunk_id.to_be_bytes());
|
buf[0..4].copy_from_slice(&cx.to_be_bytes());
|
||||||
buf[4] = chunk_type;
|
buf[4..8].copy_from_slice(&cz.to_be_bytes());
|
||||||
buf[5] = 0;
|
|
||||||
buf[6] = 0;
|
|
||||||
buf[7] = 0;
|
|
||||||
offset += 8;
|
offset += 8;
|
||||||
|
|
||||||
sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr);
|
sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_chunk_exit(&mut self, sequence: u32) {
|
|
||||||
if !self.has_client {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut offset = 0;
|
|
||||||
|
|
||||||
let msg_header = pxl8_msg_header {
|
|
||||||
sequence,
|
|
||||||
size: 0,
|
|
||||||
type_: PXL8_MSG_CHUNK_EXIT as u8,
|
|
||||||
version: PXL8_PROTOCOL_VERSION as u8,
|
|
||||||
};
|
|
||||||
offset += self.serialize_header(&msg_header, offset);
|
|
||||||
|
|
||||||
sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn serialize_chunk_msg_header(&mut self, msg: &ChunkMessage, offset: usize) -> usize {
|
fn serialize_chunk_msg_header(&mut self, msg: &ChunkMessage, offset: usize) -> usize {
|
||||||
let buf = &mut self.send_buf[offset..];
|
let buf = &mut self.send_buf[offset..];
|
||||||
buf[0] = msg.chunk_type;
|
buf[0] = msg.chunk_type;
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,12 @@
|
||||||
extern crate alloc;
|
|
||||||
|
|
||||||
use alloc::collections::BTreeMap;
|
use alloc::collections::BTreeMap;
|
||||||
|
|
||||||
use crate::chunk::{Chunk, ChunkId};
|
use crate::chunk::{Chunk, ChunkId};
|
||||||
use crate::math::Vec3;
|
use crate::procgen::generate_chunk;
|
||||||
use crate::procgen::{ProcgenParams, generate_rooms};
|
|
||||||
|
|
||||||
pub struct World {
|
pub struct World {
|
||||||
active: Option<ChunkId>,
|
active: Option<ChunkId>,
|
||||||
chunks: BTreeMap<ChunkId, Chunk>,
|
chunks: BTreeMap<ChunkId, Chunk>,
|
||||||
seed: u64,
|
pub seed: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl World {
|
impl World {
|
||||||
|
|
@ -21,67 +18,26 @@ impl World {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, chunk: Chunk) {
|
|
||||||
let id = chunk.id();
|
|
||||||
self.chunks.insert(id, chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(&self, id: &ChunkId) -> Option<&Chunk> {
|
pub fn get(&self, id: &ChunkId) -> Option<&Chunk> {
|
||||||
self.chunks.get(id)
|
self.chunks.get(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mut(&mut self, id: &ChunkId) -> Option<&mut Chunk> {
|
|
||||||
self.chunks.get_mut(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove(&mut self, id: &ChunkId) -> Option<Chunk> {
|
|
||||||
self.chunks.remove(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn active(&self) -> Option<&Chunk> {
|
pub fn active(&self) -> Option<&Chunk> {
|
||||||
self.active.as_ref().and_then(|id| self.chunks.get(id))
|
self.active.as_ref().and_then(|id| self.chunks.get(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn active_id(&self) -> Option<ChunkId> {
|
|
||||||
self.active
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_active(&mut self, id: ChunkId) {
|
pub fn set_active(&mut self, id: ChunkId) {
|
||||||
self.active = Some(id);
|
self.active = Some(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_active(&mut self) {
|
pub fn generate_bsp(&mut self, cx: i32, cz: i32) -> &Chunk {
|
||||||
self.active = None;
|
let chunk_id = ChunkId::Bsp { cx, cz };
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
if !self.chunks.contains_key(&chunk_id) {
|
||||||
let default_params = ProcgenParams {
|
let bsp = generate_chunk(cx, cz, self.seed);
|
||||||
seed: (self.seed as u32).wrapping_add(id),
|
self.chunks.insert(chunk_id, Chunk::Bsp { cx, cz, bsp, version: 1 });
|
||||||
..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()
|
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 {
|
impl Default for World {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
#include "pxl8_cart.h"
|
#include "pxl8_cart.h"
|
||||||
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
|
#include "pxl8_io.h"
|
||||||
#include "pxl8_log.h"
|
#include "pxl8_log.h"
|
||||||
#include "pxl8_mem.h"
|
#include "pxl8_mem.h"
|
||||||
|
|
||||||
|
|
@ -49,7 +46,6 @@ struct pxl8_cart {
|
||||||
char* title;
|
char* title;
|
||||||
pxl8_resolution resolution;
|
pxl8_resolution resolution;
|
||||||
pxl8_size window_size;
|
pxl8_size window_size;
|
||||||
pxl8_pixel_mode pixel_mode;
|
|
||||||
bool is_folder;
|
bool is_folder;
|
||||||
bool is_mounted;
|
bool is_mounted;
|
||||||
};
|
};
|
||||||
|
|
@ -57,11 +53,6 @@ struct pxl8_cart {
|
||||||
static pxl8_cart* pxl8_current_cart = NULL;
|
static pxl8_cart* pxl8_current_cart = NULL;
|
||||||
static char* pxl8_original_cwd = NULL;
|
static char* pxl8_original_cwd = NULL;
|
||||||
|
|
||||||
static bool is_directory(const char* path) {
|
|
||||||
struct stat st;
|
|
||||||
return stat(path, &st) == 0 && S_ISDIR(st.st_mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool is_pxc_file(const char* path) {
|
static bool is_pxc_file(const char* path) {
|
||||||
usize len = strlen(path);
|
usize len = strlen(path);
|
||||||
return len > 4 && strcmp(path + len - 4, ".pxc") == 0;
|
return len > 4 && strcmp(path + len - 4, ".pxc") == 0;
|
||||||
|
|
@ -70,41 +61,48 @@ static bool is_pxc_file(const char* path) {
|
||||||
static bool has_main_script(const char* base_path) {
|
static bool has_main_script(const char* base_path) {
|
||||||
char path[512];
|
char path[512];
|
||||||
snprintf(path, sizeof(path), "%s/main.fnl", base_path);
|
snprintf(path, sizeof(path), "%s/main.fnl", base_path);
|
||||||
if (access(path, F_OK) == 0) return true;
|
if (pxl8_io_file_exists(path)) return true;
|
||||||
snprintf(path, sizeof(path), "%s/main.lua", base_path);
|
snprintf(path, sizeof(path), "%s/main.lua", base_path);
|
||||||
return access(path, F_OK) == 0;
|
return pxl8_io_file_exists(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char* dir_path;
|
||||||
|
const char* prefix;
|
||||||
|
char*** paths;
|
||||||
|
u32* count;
|
||||||
|
u32* capacity;
|
||||||
|
} collect_ctx;
|
||||||
|
|
||||||
|
static bool collect_entry(void* userdata, const char* dir_path, const char* name) {
|
||||||
|
collect_ctx* ctx = userdata;
|
||||||
|
|
||||||
|
char full_path[1024];
|
||||||
|
char rel_path[1024];
|
||||||
|
snprintf(full_path, sizeof(full_path), "%s%s", dir_path, name);
|
||||||
|
snprintf(rel_path, sizeof(rel_path), "%s%s", ctx->prefix, name);
|
||||||
|
|
||||||
|
if (pxl8_io_is_directory(full_path)) {
|
||||||
|
char new_prefix[1025];
|
||||||
|
snprintf(new_prefix, sizeof(new_prefix), "%s/", rel_path);
|
||||||
|
collect_ctx sub = { .dir_path = full_path, .prefix = new_prefix,
|
||||||
|
.paths = ctx->paths, .count = ctx->count, .capacity = ctx->capacity };
|
||||||
|
pxl8_io_enumerate_directory(full_path, collect_entry, &sub);
|
||||||
|
} else {
|
||||||
|
if (*ctx->count >= *ctx->capacity) {
|
||||||
|
*ctx->capacity = (*ctx->capacity == 0) ? 64 : (*ctx->capacity * 2);
|
||||||
|
*ctx->paths = pxl8_realloc(*ctx->paths, *ctx->capacity * sizeof(char*));
|
||||||
|
}
|
||||||
|
(*ctx->paths)[(*ctx->count)++] = strdup(rel_path);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void collect_files_recursive(const char* dir_path, const char* prefix,
|
static void collect_files_recursive(const char* dir_path, const char* prefix,
|
||||||
char*** paths, u32* count, u32* capacity) {
|
char*** paths, u32* count, u32* capacity) {
|
||||||
DIR* dir = opendir(dir_path);
|
collect_ctx ctx = { .dir_path = dir_path, .prefix = prefix,
|
||||||
if (!dir) return;
|
.paths = paths, .count = count, .capacity = capacity };
|
||||||
|
pxl8_io_enumerate_directory(dir_path, collect_entry, &ctx);
|
||||||
struct dirent* entry;
|
|
||||||
while ((entry = readdir(dir)) != NULL) {
|
|
||||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;
|
|
||||||
|
|
||||||
char full_path[1024];
|
|
||||||
char rel_path[1024];
|
|
||||||
snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name);
|
|
||||||
snprintf(rel_path, sizeof(rel_path), "%s%s", prefix, entry->d_name);
|
|
||||||
|
|
||||||
struct stat st;
|
|
||||||
if (stat(full_path, &st) == 0) {
|
|
||||||
if (S_ISDIR(st.st_mode)) {
|
|
||||||
char new_prefix[1025];
|
|
||||||
snprintf(new_prefix, sizeof(new_prefix), "%s/", rel_path);
|
|
||||||
collect_files_recursive(full_path, new_prefix, paths, count, capacity);
|
|
||||||
} else {
|
|
||||||
if (*count >= *capacity) {
|
|
||||||
*capacity = (*capacity == 0) ? 64 : (*capacity * 2);
|
|
||||||
*paths = pxl8_realloc(*paths, *capacity * sizeof(char*));
|
|
||||||
}
|
|
||||||
(*paths)[(*count)++] = strdup(rel_path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
closedir(dir);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static pxl8_cart_file* find_file(const pxl8_cart* cart, const char* path) {
|
static pxl8_cart_file* find_file(const pxl8_cart* cart, const char* path) {
|
||||||
|
|
@ -156,7 +154,6 @@ pxl8_cart* pxl8_cart_create(void) {
|
||||||
if (cart) {
|
if (cart) {
|
||||||
cart->resolution = PXL8_RESOLUTION_640x360;
|
cart->resolution = PXL8_RESOLUTION_640x360;
|
||||||
cart->window_size = (pxl8_size){1280, 720};
|
cart->window_size = (pxl8_size){1280, 720};
|
||||||
cart->pixel_mode = PXL8_PIXEL_INDEXED;
|
|
||||||
}
|
}
|
||||||
return cart;
|
return cart;
|
||||||
}
|
}
|
||||||
|
|
@ -175,8 +172,8 @@ pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) {
|
||||||
if (!cart || !path) return PXL8_ERROR_NULL_POINTER;
|
if (!cart || !path) return PXL8_ERROR_NULL_POINTER;
|
||||||
pxl8_cart_unload(cart);
|
pxl8_cart_unload(cart);
|
||||||
|
|
||||||
if (is_directory(path)) {
|
if (pxl8_io_is_directory(path)) {
|
||||||
cart->base_path = realpath(path, NULL);
|
cart->base_path = pxl8_io_get_real_path(path);
|
||||||
if (!cart->base_path) {
|
if (!cart->base_path) {
|
||||||
pxl8_error("Failed to resolve cart path: %s", path);
|
pxl8_error("Failed to resolve cart path: %s", path);
|
||||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||||
|
|
@ -300,8 +297,8 @@ pxl8_result pxl8_cart_mount(pxl8_cart* cart) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cart->is_folder) {
|
if (cart->is_folder) {
|
||||||
pxl8_original_cwd = getcwd(NULL, 0);
|
pxl8_original_cwd = pxl8_io_get_cwd();
|
||||||
if (chdir(cart->base_path) != 0) {
|
if (!pxl8_io_set_cwd(cart->base_path)) {
|
||||||
pxl8_error("Failed to change to cart directory: %s", cart->base_path);
|
pxl8_error("Failed to change to cart directory: %s", cart->base_path);
|
||||||
pxl8_free(pxl8_original_cwd);
|
pxl8_free(pxl8_original_cwd);
|
||||||
pxl8_original_cwd = NULL;
|
pxl8_original_cwd = NULL;
|
||||||
|
|
@ -323,7 +320,7 @@ void pxl8_cart_unmount(pxl8_cart* cart) {
|
||||||
if (!cart || !cart->is_mounted) return;
|
if (!cart || !cart->is_mounted) return;
|
||||||
|
|
||||||
if (pxl8_original_cwd) {
|
if (pxl8_original_cwd) {
|
||||||
chdir(pxl8_original_cwd);
|
pxl8_io_set_cwd(pxl8_original_cwd);
|
||||||
pxl8_free(pxl8_original_cwd);
|
pxl8_free(pxl8_original_cwd);
|
||||||
pxl8_original_cwd = NULL;
|
pxl8_original_cwd = NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -364,14 +361,6 @@ void pxl8_cart_set_window_size(pxl8_cart* cart, pxl8_size size) {
|
||||||
if (cart) cart->window_size = size;
|
if (cart) cart->window_size = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_pixel_mode pxl8_cart_get_pixel_mode(const pxl8_cart* cart) {
|
|
||||||
return cart ? cart->pixel_mode : PXL8_PIXEL_INDEXED;
|
|
||||||
}
|
|
||||||
|
|
||||||
void pxl8_cart_set_pixel_mode(pxl8_cart* cart, pxl8_pixel_mode mode) {
|
|
||||||
if (cart) cart->pixel_mode = mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool pxl8_cart_is_packed(const pxl8_cart* cart) {
|
bool pxl8_cart_is_packed(const pxl8_cart* cart) {
|
||||||
return cart && !cart->is_folder;
|
return cart && !cart->is_folder;
|
||||||
}
|
}
|
||||||
|
|
@ -394,7 +383,7 @@ bool pxl8_cart_file_exists(const pxl8_cart* cart, const char* path) {
|
||||||
if (cart->is_folder) {
|
if (cart->is_folder) {
|
||||||
char full_path[512];
|
char full_path[512];
|
||||||
if (!pxl8_cart_resolve_path(cart, path, full_path, sizeof(full_path))) return false;
|
if (!pxl8_cart_resolve_path(cart, path, full_path, sizeof(full_path))) return false;
|
||||||
return access(full_path, F_OK) == 0;
|
return pxl8_io_file_exists(full_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
return find_file(cart, path) != NULL;
|
return find_file(cart, path) != NULL;
|
||||||
|
|
@ -457,7 +446,7 @@ void pxl8_cart_free_file(u8* data) {
|
||||||
pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path) {
|
pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path) {
|
||||||
if (!folder_path || !output_path) return PXL8_ERROR_NULL_POINTER;
|
if (!folder_path || !output_path) return PXL8_ERROR_NULL_POINTER;
|
||||||
|
|
||||||
if (!is_directory(folder_path)) {
|
if (!pxl8_io_is_directory(folder_path)) {
|
||||||
pxl8_error("Cart folder not found: %s", folder_path);
|
pxl8_error("Cart folder not found: %s", folder_path);
|
||||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
@ -488,9 +477,9 @@ pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path) {
|
||||||
for (u32 i = 0; i < count; i++) {
|
for (u32 i = 0; i < count; i++) {
|
||||||
char full_path[1024];
|
char full_path[1024];
|
||||||
snprintf(full_path, sizeof(full_path), "%s/%s", folder_path, paths[i]);
|
snprintf(full_path, sizeof(full_path), "%s/%s", folder_path, paths[i]);
|
||||||
struct stat st;
|
usize fsize = pxl8_io_get_file_size(full_path);
|
||||||
if (stat(full_path, &st) == 0) {
|
if (fsize > 0) {
|
||||||
file_sizes[i] = (u32)st.st_size;
|
file_sizes[i] = (u32)fsize;
|
||||||
total_size += file_sizes[i];
|
total_size += file_sizes[i];
|
||||||
} else {
|
} else {
|
||||||
file_sizes[i] = 0;
|
file_sizes[i] = 0;
|
||||||
|
|
@ -563,9 +552,10 @@ pxl8_result pxl8_cart_bundle(const char* input_path, const char* output_path, co
|
||||||
u32 cart_size = 0;
|
u32 cart_size = 0;
|
||||||
bool free_cart = false;
|
bool free_cart = false;
|
||||||
|
|
||||||
if (is_directory(input_path)) {
|
if (pxl8_io_is_directory(input_path)) {
|
||||||
char temp_pxc[256];
|
char temp_pxc[256];
|
||||||
snprintf(temp_pxc, sizeof(temp_pxc), "/tmp/pxl8_bundle_%d.pxc", getpid());
|
static u32 bundle_counter = 0;
|
||||||
|
snprintf(temp_pxc, sizeof(temp_pxc), "/tmp/pxl8_bundle_%u.pxc", bundle_counter++);
|
||||||
pxl8_result result = pxl8_cart_pack(input_path, temp_pxc);
|
pxl8_result result = pxl8_cart_pack(input_path, temp_pxc);
|
||||||
if (result != PXL8_OK) return result;
|
if (result != PXL8_OK) return result;
|
||||||
|
|
||||||
|
|
@ -577,7 +567,7 @@ pxl8_result pxl8_cart_bundle(const char* input_path, const char* output_path, co
|
||||||
cart_data = pxl8_malloc(cart_size);
|
cart_data = pxl8_malloc(cart_size);
|
||||||
fread(cart_data, 1, cart_size, f);
|
fread(cart_data, 1, cart_size, f);
|
||||||
fclose(f);
|
fclose(f);
|
||||||
unlink(temp_pxc);
|
remove(temp_pxc);
|
||||||
free_cart = true;
|
free_cart = true;
|
||||||
} else if (is_pxc_file(input_path)) {
|
} else if (is_pxc_file(input_path)) {
|
||||||
FILE* f = fopen(input_path, "rb");
|
FILE* f = fopen(input_path, "rb");
|
||||||
|
|
@ -628,7 +618,7 @@ pxl8_result pxl8_cart_bundle(const char* input_path, const char* output_path, co
|
||||||
fwrite(&trailer, sizeof(trailer), 1, out);
|
fwrite(&trailer, sizeof(trailer), 1, out);
|
||||||
fclose(out);
|
fclose(out);
|
||||||
|
|
||||||
chmod(output_path, 0755);
|
pxl8_io_set_executable(output_path);
|
||||||
|
|
||||||
pxl8_info("Bundle created: %s", output_path);
|
pxl8_info("Bundle created: %s", output_path);
|
||||||
return PXL8_OK;
|
return PXL8_OK;
|
||||||
|
|
|
||||||
|
|
@ -25,11 +25,9 @@ const char* pxl8_cart_get_base_path(const pxl8_cart* cart);
|
||||||
const char* pxl8_cart_get_title(const pxl8_cart* cart);
|
const char* pxl8_cart_get_title(const pxl8_cart* cart);
|
||||||
pxl8_resolution pxl8_cart_get_resolution(const pxl8_cart* cart);
|
pxl8_resolution pxl8_cart_get_resolution(const pxl8_cart* cart);
|
||||||
pxl8_size pxl8_cart_get_window_size(const pxl8_cart* cart);
|
pxl8_size pxl8_cart_get_window_size(const pxl8_cart* cart);
|
||||||
pxl8_pixel_mode pxl8_cart_get_pixel_mode(const pxl8_cart* cart);
|
|
||||||
void pxl8_cart_set_title(pxl8_cart* cart, const char* title);
|
void pxl8_cart_set_title(pxl8_cart* cart, const char* title);
|
||||||
void pxl8_cart_set_resolution(pxl8_cart* cart, pxl8_resolution resolution);
|
void pxl8_cart_set_resolution(pxl8_cart* cart, pxl8_resolution resolution);
|
||||||
void pxl8_cart_set_window_size(pxl8_cart* cart, pxl8_size size);
|
void pxl8_cart_set_window_size(pxl8_cart* cart, pxl8_size size);
|
||||||
void pxl8_cart_set_pixel_mode(pxl8_cart* cart, pxl8_pixel_mode mode);
|
|
||||||
bool pxl8_cart_is_packed(const pxl8_cart* cart);
|
bool pxl8_cart_is_packed(const pxl8_cart* cart);
|
||||||
bool pxl8_cart_has_embedded(const char* exe_path);
|
bool pxl8_cart_has_embedded(const char* exe_path);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,16 @@
|
||||||
#include "pxl8_save.h"
|
#include "pxl8_save.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#include "pxl8_io.h"
|
||||||
#include <direct.h>
|
|
||||||
#include <shlobj.h>
|
|
||||||
#define PATH_SEP '\\'
|
|
||||||
#else
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <pwd.h>
|
|
||||||
#define PATH_SEP '/'
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "pxl8_log.h"
|
#include "pxl8_log.h"
|
||||||
|
#include "pxl8_macros.h"
|
||||||
#include "pxl8_mem.h"
|
#include "pxl8_mem.h"
|
||||||
|
|
||||||
|
#define PATH_SEP '/'
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
u32 magic;
|
u32 magic;
|
||||||
u32 version;
|
u32 version;
|
||||||
|
|
@ -49,23 +41,6 @@ static void pxl8_save_get_slot_path(pxl8_save* save, u8 slot, char* path, usize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static pxl8_result pxl8_save_ensure_directory(const char* path) {
|
|
||||||
struct stat st;
|
|
||||||
if (stat(path, &st) == 0) {
|
|
||||||
return S_ISDIR(st.st_mode) ? PXL8_OK : PXL8_ERROR_SYSTEM_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
if (_mkdir(path) != 0 && errno != EEXIST) {
|
|
||||||
#else
|
|
||||||
if (mkdir(path, 0755) != 0 && errno != EEXIST) {
|
|
||||||
#endif
|
|
||||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return PXL8_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) {
|
pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) {
|
||||||
if (!game_name) return NULL;
|
if (!game_name) return NULL;
|
||||||
|
|
||||||
|
|
@ -75,40 +50,17 @@ pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) {
|
||||||
save->magic = magic;
|
save->magic = magic;
|
||||||
save->version = version;
|
save->version = version;
|
||||||
|
|
||||||
char base_dir[PXL8_SAVE_MAX_PATH];
|
char* pref_path = pxl8_io_get_pref_path("pxl8", game_name);
|
||||||
|
if (!pref_path) {
|
||||||
#ifdef _WIN32
|
|
||||||
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, base_dir))) {
|
|
||||||
snprintf(save->directory, sizeof(save->directory),
|
|
||||||
"%s%cpxl8%c%s", base_dir, PATH_SEP, PATH_SEP, game_name);
|
|
||||||
} else {
|
|
||||||
pxl8_free(save);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
const char* home = getenv("HOME");
|
|
||||||
if (!home) {
|
|
||||||
struct passwd* pw = getpwuid(getuid());
|
|
||||||
if (pw) home = pw->pw_dir;
|
|
||||||
}
|
|
||||||
if (!home) {
|
|
||||||
pxl8_free(save);
|
pxl8_free(save);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
pxl8_strncpy(save->directory, pref_path, sizeof(save->directory));
|
||||||
|
pxl8_free(pref_path);
|
||||||
|
|
||||||
snprintf(base_dir, sizeof(base_dir), "%s/.local/share", home);
|
usize dir_len = strlen(save->directory);
|
||||||
pxl8_save_ensure_directory(base_dir);
|
if (dir_len > 0 && save->directory[dir_len - 1] == '/') {
|
||||||
|
save->directory[dir_len - 1] = '\0';
|
||||||
snprintf(base_dir, sizeof(base_dir), "%s/.local/share/pxl8", home);
|
|
||||||
pxl8_save_ensure_directory(base_dir);
|
|
||||||
|
|
||||||
snprintf(save->directory, sizeof(save->directory),
|
|
||||||
"%s/.local/share/pxl8/%s", home, game_name);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (pxl8_save_ensure_directory(save->directory) != PXL8_OK) {
|
|
||||||
pxl8_free(save);
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_info("Save system initialized: %s", save->directory);
|
pxl8_info("Save system initialized: %s", save->directory);
|
||||||
|
|
@ -116,9 +68,7 @@ pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void pxl8_save_destroy(pxl8_save* save) {
|
void pxl8_save_destroy(pxl8_save* save) {
|
||||||
if (save) {
|
pxl8_free(save);
|
||||||
pxl8_free(save);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_result pxl8_save_write(pxl8_save* save, u8 slot, const u8* data, u32 size) {
|
pxl8_result pxl8_save_write(pxl8_save* save, u8 slot, const u8* data, u32 size) {
|
||||||
|
|
@ -234,9 +184,7 @@ pxl8_result pxl8_save_read(pxl8_save* save, u8 slot, u8** data_out, u32* size_ou
|
||||||
}
|
}
|
||||||
|
|
||||||
void pxl8_save_free(u8* data) {
|
void pxl8_save_free(u8* data) {
|
||||||
if (data) {
|
pxl8_free(data);
|
||||||
pxl8_free(data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool pxl8_save_exists(pxl8_save* save, u8 slot) {
|
bool pxl8_save_exists(pxl8_save* save, u8 slot) {
|
||||||
|
|
@ -248,8 +196,7 @@ bool pxl8_save_exists(pxl8_save* save, u8 slot) {
|
||||||
char path[PXL8_SAVE_MAX_PATH];
|
char path[PXL8_SAVE_MAX_PATH];
|
||||||
pxl8_save_get_slot_path(save, slot, path, sizeof(path));
|
pxl8_save_get_slot_path(save, slot, path, sizeof(path));
|
||||||
|
|
||||||
struct stat st;
|
return pxl8_io_file_exists(path);
|
||||||
return stat(path, &st) == 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_result pxl8_save_delete(pxl8_save* save, u8 slot) {
|
pxl8_result pxl8_save_delete(pxl8_save* save, u8 slot) {
|
||||||
|
|
|
||||||
|
|
@ -342,6 +342,7 @@ void pxl8_bsp_destroy(pxl8_bsp* bsp) {
|
||||||
pxl8_free(bsp->vertex_lights);
|
pxl8_free(bsp->vertex_lights);
|
||||||
pxl8_free(bsp->vertices);
|
pxl8_free(bsp->vertices);
|
||||||
pxl8_free(bsp->visdata);
|
pxl8_free(bsp->visdata);
|
||||||
|
pxl8_free(bsp->heightfield);
|
||||||
|
|
||||||
memset(bsp, 0, sizeof(*bsp));
|
memset(bsp, 0, sizeof(*bsp));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -110,6 +110,7 @@ typedef struct pxl8_bsp {
|
||||||
u32* vertex_lights;
|
u32* vertex_lights;
|
||||||
pxl8_bsp_vertex* vertices;
|
pxl8_bsp_vertex* vertices;
|
||||||
u8* visdata;
|
u8* visdata;
|
||||||
|
f32* heightfield;
|
||||||
|
|
||||||
u32 lightdata_size;
|
u32 lightdata_size;
|
||||||
u32 num_cell_portals;
|
u32 num_cell_portals;
|
||||||
|
|
@ -124,7 +125,17 @@ typedef struct pxl8_bsp {
|
||||||
u32 num_surfedges;
|
u32 num_surfedges;
|
||||||
u32 num_vertex_lights;
|
u32 num_vertex_lights;
|
||||||
u32 num_vertices;
|
u32 num_vertices;
|
||||||
|
u32 num_heightfield;
|
||||||
|
f32 heightfield_ox;
|
||||||
|
f32 heightfield_oz;
|
||||||
|
f32 heightfield_cell_size;
|
||||||
|
u16 heightfield_w;
|
||||||
|
u16 heightfield_h;
|
||||||
u32 visdata_size;
|
u32 visdata_size;
|
||||||
|
f32 bounds_min_x;
|
||||||
|
f32 bounds_min_z;
|
||||||
|
f32 bounds_max_x;
|
||||||
|
f32 bounds_max_z;
|
||||||
} pxl8_bsp;
|
} pxl8_bsp;
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,6 @@
|
||||||
#include "pxl8_mem.h"
|
#include "pxl8_mem.h"
|
||||||
#include "pxl8_mesh.h"
|
#include "pxl8_mesh.h"
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
f32 x0, y0, x1, y1;
|
|
||||||
} screen_rect;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
u32 leaf;
|
|
||||||
screen_rect window;
|
|
||||||
} portal_queue_entry;
|
|
||||||
|
|
||||||
static inline bool pxl8_bsp_get_edge_vertex(const pxl8_bsp* bsp, i32 surfedge_idx, u32* out_vert_idx) {
|
static inline bool pxl8_bsp_get_edge_vertex(const pxl8_bsp* bsp, i32 surfedge_idx, u32* out_vert_idx) {
|
||||||
if (surfedge_idx >= (i32)bsp->num_surfedges) return false;
|
if (surfedge_idx >= (i32)bsp->num_surfedges) return false;
|
||||||
|
|
||||||
|
|
@ -40,83 +31,6 @@ static inline bool face_in_frustum(const pxl8_bsp* bsp, u32 face_id, const pxl8_
|
||||||
return pxl8_frustum_test_aabb(frustum, face->aabb_min, face->aabb_max);
|
return pxl8_frustum_test_aabb(frustum, face->aabb_min, face->aabb_max);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool screen_rect_valid(screen_rect r) {
|
|
||||||
return r.x0 < r.x1 && r.y0 < r.y1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline screen_rect screen_rect_intersect(screen_rect a, screen_rect b) {
|
|
||||||
return (screen_rect){
|
|
||||||
.x0 = (a.x0 > b.x0) ? a.x0 : b.x0,
|
|
||||||
.y0 = (a.y0 > b.y0) ? a.y0 : b.y0,
|
|
||||||
.x1 = (a.x1 < b.x1) ? a.x1 : b.x1,
|
|
||||||
.y1 = (a.y1 < b.y1) ? a.y1 : b.y1,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void expand_rect_with_ndc(screen_rect* r, f32 nx, f32 ny) {
|
|
||||||
if (nx < r->x0) r->x0 = nx;
|
|
||||||
if (nx > r->x1) r->x1 = nx;
|
|
||||||
if (ny < r->y0) r->y0 = ny;
|
|
||||||
if (ny > r->y1) r->y1 = ny;
|
|
||||||
}
|
|
||||||
|
|
||||||
static screen_rect project_portal_to_screen(const pxl8_bsp_portal* portal, const pxl8_mat4* vp, f32 wall_height) {
|
|
||||||
pxl8_vec3 world_corners[4] = {
|
|
||||||
{portal->x0, 0, portal->z0},
|
|
||||||
{portal->x1, 0, portal->z1},
|
|
||||||
{portal->x1, wall_height, portal->z1},
|
|
||||||
{portal->x0, wall_height, portal->z0},
|
|
||||||
};
|
|
||||||
|
|
||||||
pxl8_vec4 clip[4];
|
|
||||||
bool in_front[4];
|
|
||||||
i32 front_count = 0;
|
|
||||||
|
|
||||||
const f32 NEAR_W = 0.001f;
|
|
||||||
|
|
||||||
for (i32 i = 0; i < 4; i++) {
|
|
||||||
clip[i] = pxl8_mat4_multiply_vec4(*vp, (pxl8_vec4){world_corners[i].x, world_corners[i].y, world_corners[i].z, 1.0f});
|
|
||||||
in_front[i] = clip[i].w > NEAR_W;
|
|
||||||
if (in_front[i]) front_count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (front_count == 0) return (screen_rect){0, 0, 0, 0};
|
|
||||||
|
|
||||||
screen_rect result = {1e30f, 1e30f, -1e30f, -1e30f};
|
|
||||||
|
|
||||||
for (i32 i = 0; i < 4; i++) {
|
|
||||||
i32 j = (i + 1) % 4;
|
|
||||||
pxl8_vec4 a = clip[i];
|
|
||||||
pxl8_vec4 b = clip[j];
|
|
||||||
bool a_in = in_front[i];
|
|
||||||
bool b_in = in_front[j];
|
|
||||||
|
|
||||||
if (a_in) {
|
|
||||||
f32 inv_w = 1.0f / a.w;
|
|
||||||
expand_rect_with_ndc(&result, a.x * inv_w, a.y * inv_w);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a_in != b_in) {
|
|
||||||
f32 t = (NEAR_W - a.w) / (b.w - a.w);
|
|
||||||
pxl8_vec4 intersection = {
|
|
||||||
a.x + t * (b.x - a.x),
|
|
||||||
a.y + t * (b.y - a.y),
|
|
||||||
a.z + t * (b.z - a.z),
|
|
||||||
NEAR_W
|
|
||||||
};
|
|
||||||
f32 inv_w = 1.0f / intersection.w;
|
|
||||||
expand_rect_with_ndc(&result, intersection.x * inv_w, intersection.y * inv_w);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.x0 < -1.0f) result.x0 = -1.0f;
|
|
||||||
if (result.y0 < -1.0f) result.y0 = -1.0f;
|
|
||||||
if (result.x1 > 1.0f) result.x1 = 1.0f;
|
|
||||||
if (result.y1 > 1.0f) result.y1 = 1.0f;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void collect_face_to_mesh(const pxl8_bsp* bsp, const pxl8_bsp_render_state* state,
|
static void collect_face_to_mesh(const pxl8_bsp* bsp, const pxl8_bsp_render_state* state,
|
||||||
u32 face_id, pxl8_mesh* mesh, u8 ambient) {
|
u32 face_id, pxl8_mesh* mesh, u8 ambient) {
|
||||||
const pxl8_bsp_face* face = &bsp->faces[face_id];
|
const pxl8_bsp_face* face = &bsp->faces[face_id];
|
||||||
|
|
@ -228,190 +142,38 @@ void pxl8_bsp_render_state_destroy(pxl8_bsp_render_state* state) {
|
||||||
pxl8_free(state);
|
pxl8_free(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, const pxl8_gfx_material* material) {
|
|
||||||
if (!gfx || !bsp || face_id >= bsp->num_faces || !material) return;
|
|
||||||
|
|
||||||
pxl8_mesh* mesh = pxl8_mesh_create(64, 192);
|
|
||||||
if (!mesh) return;
|
|
||||||
|
|
||||||
collect_face_to_mesh(bsp, NULL, face_id, mesh, pxl8_gfx_get_ambient(gfx));
|
|
||||||
|
|
||||||
if (mesh->index_count > 0) {
|
|
||||||
pxl8_mat4 identity = pxl8_mat4_identity();
|
|
||||||
pxl8_3d_draw_mesh(gfx, mesh, &identity, material);
|
|
||||||
}
|
|
||||||
|
|
||||||
pxl8_mesh_destroy(mesh);
|
|
||||||
}
|
|
||||||
|
|
||||||
void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp,
|
void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp,
|
||||||
pxl8_bsp_render_state* state, pxl8_vec3 camera_pos) {
|
pxl8_bsp_render_state* state,
|
||||||
|
const pxl8_gfx_draw_opts* opts) {
|
||||||
if (!gfx || !bsp || !state || bsp->num_faces == 0) return;
|
if (!gfx || !bsp || !state || bsp->num_faces == 0) return;
|
||||||
if (!state->materials || state->num_materials == 0) return;
|
if (!state->materials || state->num_materials == 0) return;
|
||||||
|
if (!state->render_face_flags) return;
|
||||||
|
|
||||||
const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx);
|
const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx);
|
||||||
const pxl8_mat4* vp = pxl8_3d_get_view_proj(gfx);
|
if (!frustum) return;
|
||||||
if (!frustum || !vp) return;
|
|
||||||
|
|
||||||
i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
|
|
||||||
if (camera_leaf < 0 || (u32)camera_leaf >= bsp->num_leafs) return;
|
|
||||||
|
|
||||||
if (!bsp->cell_portals || bsp->num_cell_portals == 0) {
|
|
||||||
pxl8_mesh* mesh = pxl8_mesh_create(8192, 16384);
|
|
||||||
if (!mesh) return;
|
|
||||||
|
|
||||||
u32 current_material = 0xFFFFFFFF;
|
|
||||||
|
|
||||||
for (u32 face_id = 0; face_id < bsp->num_faces; face_id++) {
|
|
||||||
if (!face_in_frustum(bsp, face_id, frustum)) continue;
|
|
||||||
|
|
||||||
const pxl8_bsp_face* face = &bsp->faces[face_id];
|
|
||||||
u32 material_id = face->material_id;
|
|
||||||
if (material_id >= state->num_materials) continue;
|
|
||||||
|
|
||||||
if (material_id != current_material && mesh->index_count > 0 && current_material < state->num_materials) {
|
|
||||||
pxl8_mat4 identity = pxl8_mat4_identity();
|
|
||||||
pxl8_3d_draw_mesh(gfx, mesh, &identity, &state->materials[current_material]);
|
|
||||||
pxl8_mesh_clear(mesh);
|
|
||||||
}
|
|
||||||
|
|
||||||
current_material = material_id;
|
|
||||||
collect_face_to_mesh(bsp, state, face_id, mesh, pxl8_gfx_get_ambient(gfx));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mesh->index_count > 0 && current_material < state->num_materials) {
|
|
||||||
pxl8_mat4 identity = pxl8_mat4_identity();
|
|
||||||
pxl8_3d_draw_mesh(gfx, mesh, &identity, &state->materials[current_material]);
|
|
||||||
}
|
|
||||||
|
|
||||||
pxl8_mesh_destroy(mesh);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!state->render_face_flags && state->num_faces > 0) {
|
|
||||||
state->render_face_flags = pxl8_calloc(state->num_faces, 1);
|
|
||||||
if (!state->render_face_flags) return;
|
|
||||||
}
|
|
||||||
memset(state->render_face_flags, 0, state->num_faces);
|
|
||||||
|
|
||||||
pxl8_bsp_pvs pvs = pxl8_bsp_decompress_pvs(bsp, camera_leaf);
|
|
||||||
|
|
||||||
u32 visited_bytes = (bsp->num_leafs + 7) / 8;
|
|
||||||
u8* visited = pxl8_calloc(visited_bytes, 1);
|
|
||||||
screen_rect* cell_windows = pxl8_calloc(bsp->num_leafs, sizeof(screen_rect));
|
|
||||||
portal_queue_entry* queue = pxl8_malloc(bsp->num_leafs * 4 * sizeof(portal_queue_entry));
|
|
||||||
if (!visited || !cell_windows || !queue) {
|
|
||||||
pxl8_free(visited);
|
|
||||||
pxl8_free(cell_windows);
|
|
||||||
pxl8_free(queue);
|
|
||||||
pxl8_bsp_pvs_destroy(&pvs);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 head = 0, tail = 0;
|
|
||||||
screen_rect full_screen = {-1.0f, -1.0f, 1.0f, 1.0f};
|
|
||||||
|
|
||||||
visited[camera_leaf >> 3] |= (1 << (camera_leaf & 7));
|
|
||||||
cell_windows[camera_leaf] = full_screen;
|
|
||||||
queue[tail++] = (portal_queue_entry){camera_leaf, full_screen};
|
|
||||||
|
|
||||||
f32 wall_height = 128.0f;
|
|
||||||
|
|
||||||
while (head < tail) {
|
|
||||||
portal_queue_entry entry = queue[head++];
|
|
||||||
u32 leaf_id = entry.leaf;
|
|
||||||
screen_rect window = entry.window;
|
|
||||||
|
|
||||||
if (leaf_id >= bsp->num_cell_portals) continue;
|
|
||||||
const pxl8_bsp_cell_portals* cp = &bsp->cell_portals[leaf_id];
|
|
||||||
|
|
||||||
for (u8 i = 0; i < cp->num_portals; i++) {
|
|
||||||
const pxl8_bsp_portal* portal = &cp->portals[i];
|
|
||||||
u32 target = portal->target_leaf;
|
|
||||||
|
|
||||||
if (target >= bsp->num_leafs) continue;
|
|
||||||
if (bsp->leafs[target].contents == -1) continue;
|
|
||||||
if (!pxl8_bsp_pvs_is_visible(&pvs, target)) continue;
|
|
||||||
|
|
||||||
screen_rect portal_rect = project_portal_to_screen(portal, vp, wall_height);
|
|
||||||
if (!screen_rect_valid(portal_rect)) continue;
|
|
||||||
|
|
||||||
screen_rect new_window = screen_rect_intersect(window, portal_rect);
|
|
||||||
if (!screen_rect_valid(new_window)) continue;
|
|
||||||
|
|
||||||
u32 byte = target >> 3;
|
|
||||||
u32 bit = target & 7;
|
|
||||||
if (visited[byte] & (1 << bit)) {
|
|
||||||
screen_rect existing = cell_windows[target];
|
|
||||||
bool expanded = false;
|
|
||||||
if (new_window.x0 < existing.x0) { cell_windows[target].x0 = new_window.x0; expanded = true; }
|
|
||||||
if (new_window.y0 < existing.y0) { cell_windows[target].y0 = new_window.y0; expanded = true; }
|
|
||||||
if (new_window.x1 > existing.x1) { cell_windows[target].x1 = new_window.x1; expanded = true; }
|
|
||||||
if (new_window.y1 > existing.y1) { cell_windows[target].y1 = new_window.y1; expanded = true; }
|
|
||||||
if (expanded && tail < bsp->num_leafs * 4) {
|
|
||||||
queue[tail++] = (portal_queue_entry){target, cell_windows[target]};
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
visited[byte] |= (1 << bit);
|
|
||||||
cell_windows[target] = new_window;
|
|
||||||
queue[tail++] = (portal_queue_entry){target, new_window};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pxl8_mesh* mesh = pxl8_mesh_create(8192, 16384);
|
pxl8_mesh* mesh = pxl8_mesh_create(8192, 16384);
|
||||||
if (!mesh) {
|
if (!mesh) return;
|
||||||
pxl8_free(visited);
|
|
||||||
pxl8_free(cell_windows);
|
|
||||||
pxl8_free(queue);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 current_material = 0xFFFFFFFF;
|
u8 ambient = pxl8_gfx_get_ambient(gfx);
|
||||||
|
pxl8_mat4 identity = pxl8_mat4_identity();
|
||||||
for (u32 leaf_id = 0; leaf_id < bsp->num_leafs; leaf_id++) {
|
|
||||||
if (!(visited[leaf_id >> 3] & (1 << (leaf_id & 7)))) continue;
|
|
||||||
if (bsp->leafs[leaf_id].contents == -1) continue;
|
|
||||||
|
|
||||||
const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id];
|
|
||||||
|
|
||||||
for (u32 i = 0; i < leaf->num_marksurfaces; i++) {
|
|
||||||
u32 surf_idx = leaf->first_marksurface + i;
|
|
||||||
if (surf_idx >= bsp->num_marksurfaces) continue;
|
|
||||||
|
|
||||||
u32 face_id = bsp->marksurfaces[surf_idx];
|
|
||||||
if (face_id >= bsp->num_faces) continue;
|
|
||||||
|
|
||||||
if (state->render_face_flags[face_id]) continue;
|
|
||||||
state->render_face_flags[face_id] = 1;
|
|
||||||
|
|
||||||
if (!face_in_frustum(bsp, face_id, frustum)) continue;
|
|
||||||
|
|
||||||
|
for (u32 mat = 0; mat < state->num_materials; mat++) {
|
||||||
|
for (u32 face_id = 0; face_id < bsp->num_faces; face_id++) {
|
||||||
|
if (!state->render_face_flags[face_id]) continue;
|
||||||
const pxl8_bsp_face* face = &bsp->faces[face_id];
|
const pxl8_bsp_face* face = &bsp->faces[face_id];
|
||||||
u32 material_id = face->material_id;
|
if (face->material_id != mat) continue;
|
||||||
if (material_id >= state->num_materials) continue;
|
if (!face_in_frustum(bsp, face_id, frustum)) continue;
|
||||||
|
collect_face_to_mesh(bsp, state, face_id, mesh, ambient);
|
||||||
if (material_id != current_material && mesh->index_count > 0 && current_material < state->num_materials) {
|
}
|
||||||
pxl8_mat4 identity = pxl8_mat4_identity();
|
if (mesh->index_count > 0) {
|
||||||
pxl8_3d_draw_mesh(gfx, mesh, &identity, &state->materials[current_material]);
|
pxl8_gfx_material mat_copy = state->materials[mat];
|
||||||
pxl8_mesh_clear(mesh);
|
if (state->exterior) mat_copy.double_sided = true;
|
||||||
}
|
pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat_copy, opts);
|
||||||
|
pxl8_mesh_clear(mesh);
|
||||||
current_material = material_id;
|
|
||||||
collect_face_to_mesh(bsp, state, face_id, mesh, pxl8_gfx_get_ambient(gfx));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mesh->index_count > 0 && current_material < state->num_materials) {
|
|
||||||
pxl8_mat4 identity = pxl8_mat4_identity();
|
|
||||||
pxl8_3d_draw_mesh(gfx, mesh, &identity, &state->materials[current_material]);
|
|
||||||
}
|
|
||||||
|
|
||||||
pxl8_bsp_pvs_destroy(&pvs);
|
|
||||||
pxl8_free(visited);
|
|
||||||
pxl8_free(cell_windows);
|
|
||||||
pxl8_free(queue);
|
|
||||||
pxl8_mesh_destroy(mesh);
|
pxl8_mesh_destroy(mesh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,15 @@ typedef struct pxl8_bsp_render_state {
|
||||||
u8* render_face_flags;
|
u8* render_face_flags;
|
||||||
u32 num_materials;
|
u32 num_materials;
|
||||||
u32 num_faces;
|
u32 num_faces;
|
||||||
|
bool exterior;
|
||||||
} pxl8_bsp_render_state;
|
} pxl8_bsp_render_state;
|
||||||
|
|
||||||
pxl8_bsp_render_state* pxl8_bsp_render_state_create(u32 num_faces);
|
pxl8_bsp_render_state* pxl8_bsp_render_state_create(u32 num_faces);
|
||||||
void pxl8_bsp_render_state_destroy(pxl8_bsp_render_state* state);
|
void pxl8_bsp_render_state_destroy(pxl8_bsp_render_state* state);
|
||||||
|
|
||||||
void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp,
|
void pxl8_bsp_render(pxl8_gfx* gfx, const pxl8_bsp* bsp,
|
||||||
pxl8_bsp_render_state* state, pxl8_vec3 camera_pos);
|
pxl8_bsp_render_state* state,
|
||||||
void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id,
|
const pxl8_gfx_draw_opts* opts);
|
||||||
const pxl8_gfx_material* material);
|
|
||||||
void pxl8_bsp_set_material(pxl8_bsp_render_state* state, u16 material_id,
|
void pxl8_bsp_set_material(pxl8_bsp_render_state* state, u16 material_id,
|
||||||
const pxl8_gfx_material* material);
|
const pxl8_gfx_material* material);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,8 @@
|
||||||
#define PXL8_VERSION "0.1.0"
|
#define PXL8_VERSION "0.1.0"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "pxl8_ase.h"
|
#include "pxl8_ase.h"
|
||||||
#include "pxl8_game.h"
|
#include "pxl8_game.h"
|
||||||
#include "pxl8_hal.h"
|
#include "pxl8_hal.h"
|
||||||
|
|
@ -150,20 +146,11 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bundle_mode) {
|
if (bundle_mode) {
|
||||||
char exe_path[1024];
|
return pxl8_cart_bundle(pack_input, pack_output, argv[0]);
|
||||||
ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);
|
|
||||||
if (len == -1) {
|
|
||||||
pxl8_error("failed to resolve executable path");
|
|
||||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
|
||||||
}
|
|
||||||
exe_path[len] = '\0';
|
|
||||||
pxl8_result result = pxl8_cart_bundle(pack_input, pack_output, exe_path);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pack_mode) {
|
if (pack_mode) {
|
||||||
pxl8_result result = pxl8_cart_pack(pack_input, pack_output);
|
return pxl8_cart_pack(pack_input, pack_output);
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (remap_palette_mode) {
|
if (remap_palette_mode) {
|
||||||
|
|
@ -191,14 +178,14 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* cart_path = script_arg;
|
const char* cart_path = script_arg;
|
||||||
char* original_cwd = getcwd(NULL, 0);
|
char* original_cwd = pxl8_io_get_cwd();
|
||||||
|
|
||||||
bool load_embedded = has_embedded && !run_mode;
|
bool load_embedded = has_embedded && !run_mode;
|
||||||
bool load_from_path = false;
|
bool load_from_path = false;
|
||||||
|
|
||||||
if (!load_embedded && run_mode) {
|
if (!load_embedded && run_mode) {
|
||||||
if (!cart_path) {
|
if (!cart_path) {
|
||||||
if (access("main.fnl", F_OK) == 0 || access("main.lua", F_OK) == 0) {
|
if (pxl8_io_file_exists("main.fnl") || pxl8_io_file_exists("main.lua")) {
|
||||||
cart_path = ".";
|
cart_path = ".";
|
||||||
} else {
|
} else {
|
||||||
pxl8_error("no main.fnl or main.lua found in current directory");
|
pxl8_error("no main.fnl or main.lua found in current directory");
|
||||||
|
|
@ -206,9 +193,7 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
|
||||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
struct stat st;
|
load_from_path = pxl8_io_is_directory(cart_path) || strstr(cart_path, ".pxc");
|
||||||
load_from_path = (stat(cart_path, &st) == 0 && S_ISDIR(st.st_mode)) ||
|
|
||||||
strstr(cart_path, ".pxc");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (load_embedded || load_from_path) {
|
if (load_embedded || load_from_path) {
|
||||||
|
|
@ -239,7 +224,6 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
|
||||||
const char* window_title = pxl8_cart_get_title(sys->cart);
|
const char* window_title = pxl8_cart_get_title(sys->cart);
|
||||||
if (!window_title) window_title = "pxl8";
|
if (!window_title) window_title = "pxl8";
|
||||||
pxl8_resolution resolution = pxl8_cart_get_resolution(sys->cart);
|
pxl8_resolution resolution = pxl8_cart_get_resolution(sys->cart);
|
||||||
pxl8_pixel_mode pixel_mode = pxl8_cart_get_pixel_mode(sys->cart);
|
|
||||||
pxl8_size window_size = pxl8_cart_get_window_size(sys->cart);
|
pxl8_size window_size = pxl8_cart_get_window_size(sys->cart);
|
||||||
pxl8_size render_size = pxl8_get_resolution_dimensions(resolution);
|
pxl8_size render_size = pxl8_get_resolution_dimensions(resolution);
|
||||||
|
|
||||||
|
|
@ -249,17 +233,12 @@ pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
|
||||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
game->gfx = pxl8_gfx_create(sys->hal, sys->platform_data, pixel_mode, resolution);
|
game->gfx = pxl8_gfx_create(sys->hal, sys->platform_data, resolution);
|
||||||
if (!game->gfx) {
|
if (!game->gfx) {
|
||||||
pxl8_error("failed to create graphics context");
|
pxl8_error("failed to create graphics context");
|
||||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pxl8_gfx_load_font_atlas(game->gfx) != PXL8_OK) {
|
|
||||||
pxl8_error("failed to load font atlas");
|
|
||||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
|
||||||
}
|
|
||||||
|
|
||||||
game->mixer = pxl8_sfx_mixer_create(sys->hal);
|
game->mixer = pxl8_sfx_mixer_create(sys->hal);
|
||||||
if (!game->mixer) {
|
if (!game->mixer) {
|
||||||
pxl8_error("failed to create audio mixer");
|
pxl8_error("failed to create audio mixer");
|
||||||
|
|
@ -375,18 +354,13 @@ pxl8_result pxl8_update(pxl8* sys) {
|
||||||
pxl8_repl_command* cmd = pxl8_repl_pop_command(sys->repl);
|
pxl8_repl_command* cmd = pxl8_repl_pop_command(sys->repl);
|
||||||
if (cmd) {
|
if (cmd) {
|
||||||
pxl8_result result = pxl8_script_eval_repl(game->script, pxl8_repl_command_buffer(cmd));
|
pxl8_result result = pxl8_script_eval_repl(game->script, pxl8_repl_command_buffer(cmd));
|
||||||
if (result != PXL8_OK) {
|
if (result != PXL8_OK && !pxl8_script_is_incomplete_input(game->script)) {
|
||||||
if (pxl8_script_is_incomplete_input(game->script)) {
|
pxl8_error("%s", pxl8_script_get_last_error(game->script));
|
||||||
pxl8_repl_signal_complete(sys->repl);
|
|
||||||
} else {
|
|
||||||
pxl8_error("%s", pxl8_script_get_last_error(game->script));
|
|
||||||
pxl8_repl_clear_accumulator(sys->repl);
|
|
||||||
pxl8_repl_signal_complete(sys->repl);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pxl8_repl_clear_accumulator(sys->repl);
|
|
||||||
pxl8_repl_signal_complete(sys->repl);
|
|
||||||
}
|
}
|
||||||
|
if (result == PXL8_OK || !pxl8_script_is_incomplete_input(game->script)) {
|
||||||
|
pxl8_repl_clear_accumulator(sys->repl);
|
||||||
|
}
|
||||||
|
pxl8_repl_signal_complete(sys->repl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -81,46 +81,9 @@ pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, usize si
|
||||||
return pxl8_io_write_file(path, (const char*)data, size);
|
return pxl8_io_write_file(path, (const char*)data, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool pxl8_io_file_exists(const char* path) {
|
|
||||||
if (!path) return false;
|
|
||||||
|
|
||||||
struct stat st;
|
|
||||||
return stat(path, &st) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
f64 pxl8_io_get_file_modified_time(const char* path) {
|
|
||||||
if (!path) return 0.0;
|
|
||||||
|
|
||||||
struct stat st;
|
|
||||||
if (stat(path, &st) == 0) {
|
|
||||||
return st.st_mtime;
|
|
||||||
}
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pxl8_result pxl8_io_create_directory(const char* path) {
|
|
||||||
if (!path) return PXL8_ERROR_NULL_POINTER;
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
if (mkdir(path) != 0) {
|
|
||||||
#else
|
|
||||||
if (mkdir(path, 0755) != 0) {
|
|
||||||
#endif
|
|
||||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
|
||||||
}
|
|
||||||
return PXL8_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
void pxl8_io_free_file_content(char* content) {
|
|
||||||
if (content) {
|
|
||||||
pxl8_free(content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void pxl8_io_free_binary_data(u8* data) {
|
void pxl8_io_free_binary_data(u8* data) {
|
||||||
if (data) {
|
pxl8_free(data);
|
||||||
pxl8_free(data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static i32 pxl8_key_code(const char* key_name) {
|
static i32 pxl8_key_code(const char* key_name) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <sys/stat.h>
|
|
||||||
|
|
||||||
#include "pxl8_bytes.h"
|
#include "pxl8_bytes.h"
|
||||||
#include "pxl8_types.h"
|
#include "pxl8_types.h"
|
||||||
|
|
@ -10,11 +9,20 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
typedef bool (*pxl8_io_dir_callback)(void* userdata, const char* dir_path, const char* name);
|
||||||
|
|
||||||
pxl8_result pxl8_io_create_directory(const char* path);
|
pxl8_result pxl8_io_create_directory(const char* path);
|
||||||
|
bool pxl8_io_enumerate_directory(const char* path, pxl8_io_dir_callback callback, void* userdata);
|
||||||
bool pxl8_io_file_exists(const char* path);
|
bool pxl8_io_file_exists(const char* path);
|
||||||
void pxl8_io_free_binary_data(u8* data);
|
void pxl8_io_free_binary_data(u8* data);
|
||||||
void pxl8_io_free_file_content(char* content);
|
char* pxl8_io_get_cwd(void);
|
||||||
f64 pxl8_io_get_file_modified_time(const char* path);
|
f64 pxl8_io_get_file_modified_time(const char* path);
|
||||||
|
usize pxl8_io_get_file_size(const char* path);
|
||||||
|
char* pxl8_io_get_pref_path(const char* org, const char* app);
|
||||||
|
char* pxl8_io_get_real_path(const char* path);
|
||||||
|
bool pxl8_io_is_directory(const char* path);
|
||||||
|
bool pxl8_io_set_cwd(const char* path);
|
||||||
|
void pxl8_io_set_executable(const char* path);
|
||||||
pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, usize* size);
|
pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, usize* size);
|
||||||
pxl8_result pxl8_io_read_file(const char* path, char** content, usize* size);
|
pxl8_result pxl8_io_read_file(const char* path, char** content, usize* size);
|
||||||
pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, usize size);
|
pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, usize size);
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,11 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
#define PXL8_LOG_COLOR_ERROR "\033[38;2;251;73;52m"
|
#define PXL8_LOG_COLOR_ERROR "\033[1;38;2;251;73;52m"
|
||||||
#define PXL8_LOG_COLOR_WARN "\033[38;2;250;189;47m"
|
#define PXL8_LOG_COLOR_WARN "\033[1;38;2;250;189;47m"
|
||||||
#define PXL8_LOG_COLOR_INFO "\033[38;2;184;187;38m"
|
#define PXL8_LOG_COLOR_INFO "\033[1;38;2;184;187;38m"
|
||||||
#define PXL8_LOG_COLOR_DEBUG "\033[38;2;131;165;152m"
|
#define PXL8_LOG_COLOR_DEBUG "\033[1;38;2;131;165;152m"
|
||||||
#define PXL8_LOG_COLOR_TRACE "\033[38;2;211;134;155m"
|
#define PXL8_LOG_COLOR_TRACE "\033[1;38;2;211;134;155m"
|
||||||
#define PXL8_LOG_COLOR_RESET "\033[0m"
|
#define PXL8_LOG_COLOR_RESET "\033[0m"
|
||||||
|
|
||||||
static pxl8_log* g_log = NULL;
|
static pxl8_log* g_log = NULL;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
#include "pxl8_types.h"
|
#include "pxl8_types.h"
|
||||||
|
|
||||||
#define PXL8_QUEUE_CAPACITY 256
|
#define PXL8_QUEUE_CAPACITY 512
|
||||||
|
|
||||||
typedef struct pxl8_queue {
|
typedef struct pxl8_queue {
|
||||||
void* items[PXL8_QUEUE_CAPACITY];
|
void* items[PXL8_QUEUE_CAPACITY];
|
||||||
|
|
|
||||||
|
|
@ -31,12 +31,6 @@ typedef __int128_t i128;
|
||||||
typedef __uint128_t u128;
|
typedef __uint128_t u128;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef enum pxl8_pixel_mode {
|
|
||||||
PXL8_PIXEL_INDEXED = 1,
|
|
||||||
PXL8_PIXEL_HICOLOR = 2,
|
|
||||||
PXL8_PIXEL_RGBA = 4,
|
|
||||||
} pxl8_pixel_mode;
|
|
||||||
|
|
||||||
typedef enum pxl8_cursor {
|
typedef enum pxl8_cursor {
|
||||||
PXL8_CURSOR_ARROW,
|
PXL8_CURSOR_ARROW,
|
||||||
PXL8_CURSOR_HAND
|
PXL8_CURSOR_HAND
|
||||||
|
|
@ -108,3 +102,4 @@ typedef struct pxl8_viewport {
|
||||||
i32 scaled_width, scaled_height;
|
i32 scaled_width, scaled_height;
|
||||||
f32 scale;
|
f32 scale;
|
||||||
} pxl8_viewport;
|
} pxl8_viewport;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,8 @@
|
||||||
#include "pxl8_atlas.h"
|
#include "pxl8_atlas.h"
|
||||||
|
|
||||||
#include <limits.h>
|
#include <limits.h>
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "pxl8_color.h"
|
|
||||||
#include "pxl8_log.h"
|
#include "pxl8_log.h"
|
||||||
#include "pxl8_mem.h"
|
#include "pxl8_mem.h"
|
||||||
|
|
||||||
|
|
@ -144,15 +141,14 @@ static void pxl8_skyline_compact(pxl8_skyline* skyline) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_pixel_mode pixel_mode) {
|
pxl8_atlas* pxl8_atlas_create(u32 width, u32 height) {
|
||||||
pxl8_atlas* atlas = (pxl8_atlas*)pxl8_calloc(1, sizeof(pxl8_atlas));
|
pxl8_atlas* atlas = (pxl8_atlas*)pxl8_calloc(1, sizeof(pxl8_atlas));
|
||||||
if (!atlas) return NULL;
|
if (!atlas) return NULL;
|
||||||
|
|
||||||
atlas->height = height;
|
atlas->height = height;
|
||||||
atlas->width = width;
|
atlas->width = width;
|
||||||
|
|
||||||
i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode);
|
atlas->pixels = (u8*)pxl8_calloc(width * height, 1);
|
||||||
atlas->pixels = (u8*)pxl8_calloc(width * height, bytes_per_pixel);
|
|
||||||
if (!atlas->pixels) {
|
if (!atlas->pixels) {
|
||||||
pxl8_free(atlas);
|
pxl8_free(atlas);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
@ -226,14 +222,13 @@ void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count) {
|
||||||
atlas->dirty = true;
|
atlas->dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode) {
|
bool pxl8_atlas_expand(pxl8_atlas* atlas) {
|
||||||
if (!atlas || atlas->width >= 4096) return false;
|
if (!atlas || atlas->width >= 4096) return false;
|
||||||
|
|
||||||
i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode);
|
|
||||||
u32 new_size = atlas->width * 2;
|
u32 new_size = atlas->width * 2;
|
||||||
u32 old_width = atlas->width;
|
u32 old_width = atlas->width;
|
||||||
|
|
||||||
u8* new_pixels = (u8*)pxl8_calloc(new_size * new_size, bytes_per_pixel);
|
u8* new_pixels = (u8*)pxl8_calloc(new_size * new_size, 1);
|
||||||
if (!new_pixels) return false;
|
if (!new_pixels) return false;
|
||||||
|
|
||||||
pxl8_skyline new_skyline;
|
pxl8_skyline new_skyline;
|
||||||
|
|
@ -268,11 +263,7 @@ bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode) {
|
||||||
for (u32 x = 0; x < (u32)atlas->entries[i].w; x++) {
|
for (u32 x = 0; x < (u32)atlas->entries[i].w; x++) {
|
||||||
u32 src_idx = (atlas->entries[i].y + y) * old_width + (atlas->entries[i].x + x);
|
u32 src_idx = (atlas->entries[i].y + y) * old_width + (atlas->entries[i].x + x);
|
||||||
u32 dst_idx = (fit.pos.y + y) * new_size + (fit.pos.x + x);
|
u32 dst_idx = (fit.pos.y + y) * new_size + (fit.pos.x + x);
|
||||||
if (bytes_per_pixel == 2) {
|
new_pixels[dst_idx] = atlas->pixels[src_idx];
|
||||||
((u16*)new_pixels)[dst_idx] = ((u16*)atlas->pixels)[src_idx];
|
|
||||||
} else {
|
|
||||||
new_pixels[dst_idx] = atlas->pixels[src_idx];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -304,15 +295,14 @@ u32 pxl8_atlas_add_texture(
|
||||||
pxl8_atlas* atlas,
|
pxl8_atlas* atlas,
|
||||||
const u8* pixels,
|
const u8* pixels,
|
||||||
u32 w,
|
u32 w,
|
||||||
u32 h,
|
u32 h
|
||||||
pxl8_pixel_mode pixel_mode
|
|
||||||
) {
|
) {
|
||||||
if (!atlas || !pixels) return UINT32_MAX;
|
if (!atlas || !pixels) return UINT32_MAX;
|
||||||
|
|
||||||
pxl8_skyline_fit fit =
|
pxl8_skyline_fit fit =
|
||||||
pxl8_skyline_find_position(&atlas->skyline, atlas->width, atlas->height, w, h);
|
pxl8_skyline_find_position(&atlas->skyline, atlas->width, atlas->height, w, h);
|
||||||
if (!fit.found) {
|
if (!fit.found) {
|
||||||
if (!pxl8_atlas_expand(atlas, pixel_mode)) {
|
if (!pxl8_atlas_expand(atlas)) {
|
||||||
return UINT32_MAX;
|
return UINT32_MAX;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -347,17 +337,11 @@ u32 pxl8_atlas_add_texture(
|
||||||
entry->h = h;
|
entry->h = h;
|
||||||
entry->log2_w = pxl8_log2(w);
|
entry->log2_w = pxl8_log2(w);
|
||||||
|
|
||||||
i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode);
|
|
||||||
for (u32 y = 0; y < h; y++) {
|
for (u32 y = 0; y < h; y++) {
|
||||||
for (u32 x = 0; x < w; x++) {
|
for (u32 x = 0; x < w; x++) {
|
||||||
u32 src_idx = y * w + x;
|
u32 src_idx = y * w + x;
|
||||||
u32 dst_idx = (fit.pos.y + y) * atlas->width + (fit.pos.x + x);
|
u32 dst_idx = (fit.pos.y + y) * atlas->width + (fit.pos.x + x);
|
||||||
|
atlas->pixels[dst_idx] = pixels[src_idx];
|
||||||
if (bytes_per_pixel == 2) {
|
|
||||||
((u16*)atlas->pixels)[dst_idx] = ((const u16*)pixels)[src_idx];
|
|
||||||
} else {
|
|
||||||
atlas->pixels[dst_idx] = pixels[src_idx];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,11 @@ static inline u8 pxl8_log2(u32 v) {
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u32 h, pxl8_pixel_mode pixel_mode);
|
u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u32 h);
|
||||||
void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count);
|
void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count);
|
||||||
pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_pixel_mode pixel_mode);
|
pxl8_atlas* pxl8_atlas_create(u32 width, u32 height);
|
||||||
void pxl8_atlas_destroy(pxl8_atlas* atlas);
|
void pxl8_atlas_destroy(pxl8_atlas* atlas);
|
||||||
bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode);
|
bool pxl8_atlas_expand(pxl8_atlas* atlas);
|
||||||
const pxl8_atlas_entry* pxl8_atlas_get_entry(const pxl8_atlas* atlas, u32 id);
|
const pxl8_atlas_entry* pxl8_atlas_get_entry(const pxl8_atlas* atlas, u32 id);
|
||||||
u32 pxl8_atlas_get_entry_count(const pxl8_atlas* atlas);
|
u32 pxl8_atlas_get_entry_count(const pxl8_atlas* atlas);
|
||||||
u32 pxl8_atlas_get_height(const pxl8_atlas* atlas);
|
u32 pxl8_atlas_get_height(const pxl8_atlas* atlas);
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,7 @@
|
||||||
#include "pxl8_blit.h"
|
#include "pxl8_blit.h"
|
||||||
|
|
||||||
void pxl8_blit_hicolor(u16* fb, u32 fb_width, const u16* sprite, u32 atlas_width,
|
void pxl8_blit(u8* fb, u32 fb_width, const u8* sprite, u32 atlas_width,
|
||||||
i32 x, i32 y, u32 w, u32 h) {
|
i32 x, i32 y, u32 w, u32 h) {
|
||||||
u16* dest_base = fb + y * fb_width + x;
|
|
||||||
const u16* src_base = sprite;
|
|
||||||
|
|
||||||
for (u32 row = 0; row < h; row++) {
|
|
||||||
u16* dest_row = dest_base + row * fb_width;
|
|
||||||
const u16* src_row = src_base + row * atlas_width;
|
|
||||||
|
|
||||||
u32 col = 0;
|
|
||||||
u32 count2 = w / 2;
|
|
||||||
for (u32 i = 0; i < count2; i++) {
|
|
||||||
u32 pixels = ((const u32*)src_row)[i];
|
|
||||||
if (pixels == 0) {
|
|
||||||
col += 2;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
dest_row[col] = pxl8_blend_hicolor((u16)(pixels), dest_row[col]);
|
|
||||||
dest_row[col + 1] = pxl8_blend_hicolor((u16)(pixels >> 16), dest_row[col + 1]);
|
|
||||||
col += 2;
|
|
||||||
}
|
|
||||||
if (w & 1) {
|
|
||||||
dest_row[col] = pxl8_blend_hicolor(src_row[col], dest_row[col]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void pxl8_blit_indexed(u8* fb, u32 fb_width, const u8* sprite, u32 atlas_width,
|
|
||||||
i32 x, i32 y, u32 w, u32 h) {
|
|
||||||
u8* dest_base = fb + y * fb_width + x;
|
u8* dest_base = fb + y * fb_width + x;
|
||||||
const u8* src_base = sprite;
|
const u8* src_base = sprite;
|
||||||
|
|
||||||
|
|
@ -44,14 +17,14 @@ void pxl8_blit_indexed(u8* fb, u32 fb_width, const u8* sprite, u32 atlas_width,
|
||||||
col += 4;
|
col += 4;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
dest_row[col] = pxl8_blend_indexed((u8)(pixels), dest_row[col]);
|
dest_row[col] = pxl8_blit_mask((u8)(pixels), dest_row[col]);
|
||||||
dest_row[col + 1] = pxl8_blend_indexed((u8)(pixels >> 8), dest_row[col + 1]);
|
dest_row[col + 1] = pxl8_blit_mask((u8)(pixels >> 8), dest_row[col + 1]);
|
||||||
dest_row[col + 2] = pxl8_blend_indexed((u8)(pixels >> 16), dest_row[col + 2]);
|
dest_row[col + 2] = pxl8_blit_mask((u8)(pixels >> 16), dest_row[col + 2]);
|
||||||
dest_row[col + 3] = pxl8_blend_indexed((u8)(pixels >> 24), dest_row[col + 3]);
|
dest_row[col + 3] = pxl8_blit_mask((u8)(pixels >> 24), dest_row[col + 3]);
|
||||||
col += 4;
|
col += 4;
|
||||||
}
|
}
|
||||||
for (; col < w; col++) {
|
for (; col < w; col++) {
|
||||||
dest_row[col] = pxl8_blend_indexed(src_row[col], dest_row[col]);
|
dest_row[col] = pxl8_blit_mask(src_row[col], dest_row[col]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,22 +6,12 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static inline u8 pxl8_blend_indexed(u8 src, u8 dst) {
|
static inline u8 pxl8_blit_mask(u8 src, u8 dst) {
|
||||||
u8 m = (u8)(-(src != 0));
|
u8 m = (u8)(-(src != 0));
|
||||||
return (src & m) | (dst & ~m);
|
return (src & m) | (dst & ~m);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline u16 pxl8_blend_hicolor(u16 src, u16 dst) {
|
void pxl8_blit(
|
||||||
u16 m = (u16)(-(src != 0));
|
|
||||||
return (src & m) | (dst & ~m);
|
|
||||||
}
|
|
||||||
|
|
||||||
void pxl8_blit_hicolor(
|
|
||||||
u16* fb, u32 fb_width,
|
|
||||||
const u16* sprite, u32 atlas_width,
|
|
||||||
i32 x, i32 y, u32 w, u32 h
|
|
||||||
);
|
|
||||||
void pxl8_blit_indexed(
|
|
||||||
u8* fb, u32 fb_width,
|
u8* fb, u32 fb_width,
|
||||||
const u8* sprite, u32 atlas_width,
|
const u8* sprite, u32 atlas_width,
|
||||||
i32 x, i32 y, u32 w, u32 h
|
i32 x, i32 y, u32 w, u32 h
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,6 @@
|
||||||
|
|
||||||
#include "pxl8_types.h"
|
#include "pxl8_types.h"
|
||||||
|
|
||||||
static inline i32 pxl8_bytes_per_pixel(pxl8_pixel_mode mode) {
|
|
||||||
return (i32)mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline u32 pxl8_color_from_rgba(u32 rgba) {
|
static inline u32 pxl8_color_from_rgba(u32 rgba) {
|
||||||
u8 r = (rgba >> 24) & 0xFF;
|
u8 r = (rgba >> 24) & 0xFF;
|
||||||
u8 g = (rgba >> 16) & 0xFF;
|
u8 g = (rgba >> 16) & 0xFF;
|
||||||
|
|
@ -76,30 +72,10 @@ static inline void pxl8_rgb332_unpack(u8 c, u8* r, u8* g, u8* b) {
|
||||||
*b = (bi << 6) | (bi << 4) | (bi << 2) | bi;
|
*b = (bi << 6) | (bi << 4) | (bi << 2) | bi;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline u16 pxl8_rgb565_pack(u8 r, u8 g, u8 b) {
|
|
||||||
return ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void pxl8_rgb565_unpack(u16 color, u8* r, u8* g, u8* b) {
|
|
||||||
*r = (color >> 11) << 3;
|
|
||||||
*g = ((color >> 5) & 0x3F) << 2;
|
|
||||||
*b = (color & 0x1F) << 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline u32 pxl8_rgb565_to_rgba32(u16 color) {
|
|
||||||
u8 r, g, b;
|
|
||||||
pxl8_rgb565_unpack(color, &r, &g, &b);
|
|
||||||
return r | ((u32)g << 8) | ((u32)b << 16) | 0xFF000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline u32 pxl8_rgba32_pack(u8 r, u8 g, u8 b, u8 a) {
|
static inline u32 pxl8_rgba32_pack(u8 r, u8 g, u8 b, u8 a) {
|
||||||
return r | ((u32)g << 8) | ((u32)b << 16) | ((u32)a << 24);
|
return r | ((u32)g << 8) | ((u32)b << 16) | ((u32)a << 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline u16 pxl8_rgba32_to_rgb565(u32 rgba) {
|
|
||||||
return pxl8_rgb565_pack(rgba & 0xFF, (rgba >> 8) & 0xFF, (rgba >> 16) & 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void pxl8_rgba32_unpack(u32 color, u8* r, u8* g, u8* b, u8* a) {
|
static inline void pxl8_rgba32_unpack(u32 color, u8* r, u8* g, u8* b, u8* a) {
|
||||||
*r = color & 0xFF;
|
*r = color & 0xFF;
|
||||||
*g = (color >> 8) & 0xFF;
|
*g = (color >> 8) & 0xFF;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
#include "pxl8_ase.h"
|
#include "pxl8_ase.h"
|
||||||
#include "pxl8_atlas.h"
|
#include "pxl8_atlas.h"
|
||||||
#include "pxl8_blit.h"
|
#include "pxl8_blit.h"
|
||||||
#include "pxl8_color.h"
|
|
||||||
#include "pxl8_colormap.h"
|
#include "pxl8_colormap.h"
|
||||||
#include "pxl8_font.h"
|
#include "pxl8_font.h"
|
||||||
#include "pxl8_glows.h"
|
#include "pxl8_glows.h"
|
||||||
|
|
@ -72,7 +71,6 @@ struct pxl8_gfx {
|
||||||
pxl8_palette_cube* palette_cube;
|
pxl8_palette_cube* palette_cube;
|
||||||
pxl8_gfx_pass frame_pass;
|
pxl8_gfx_pass frame_pass;
|
||||||
pxl8_frame_resources frame_res;
|
pxl8_frame_resources frame_res;
|
||||||
pxl8_pixel_mode pixel_mode;
|
|
||||||
void* platform_data;
|
void* platform_data;
|
||||||
pxl8_sprite_cache_entry* sprite_cache;
|
pxl8_sprite_cache_entry* sprite_cache;
|
||||||
u32 sprite_cache_capacity;
|
u32 sprite_cache_capacity;
|
||||||
|
|
@ -100,34 +98,19 @@ pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx) {
|
||||||
return bounds;
|
return bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx) {
|
u8* pxl8_gfx_framebuffer(pxl8_gfx* gfx) {
|
||||||
return gfx ? gfx->pixel_mode : PXL8_PIXEL_INDEXED;
|
if (!gfx) return NULL;
|
||||||
}
|
|
||||||
|
|
||||||
u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx) {
|
|
||||||
if (!gfx || gfx->pixel_mode == PXL8_PIXEL_HICOLOR) return NULL;
|
|
||||||
return (u8*)pxl8_texture_get_data(gfx->renderer, gfx->color_target);
|
return (u8*)pxl8_texture_get_data(gfx->renderer, gfx->color_target);
|
||||||
}
|
}
|
||||||
|
|
||||||
u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx) {
|
|
||||||
if (!gfx || gfx->pixel_mode != PXL8_PIXEL_HICOLOR) return NULL;
|
|
||||||
return (u16*)pxl8_texture_get_data(gfx->renderer, gfx->color_target);
|
|
||||||
}
|
|
||||||
|
|
||||||
i32 pxl8_gfx_get_height(const pxl8_gfx* gfx) {
|
i32 pxl8_gfx_get_height(const pxl8_gfx* gfx) {
|
||||||
return gfx ? gfx->framebuffer_height : 0;
|
return gfx ? gfx->framebuffer_height : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32* pxl8_gfx_get_output(pxl8_gfx* gfx) {
|
static void pxl8_gfx_resolve(pxl8_gfx* gfx) {
|
||||||
if (!gfx) return NULL;
|
|
||||||
return gfx->output;
|
|
||||||
}
|
|
||||||
|
|
||||||
void pxl8_gfx_resolve(pxl8_gfx* gfx) {
|
|
||||||
if (!gfx || !gfx->initialized) return;
|
if (!gfx || !gfx->initialized) return;
|
||||||
if (gfx->pixel_mode != PXL8_PIXEL_INDEXED) return;
|
|
||||||
|
|
||||||
const u32* pal = gfx->palette ? pxl8_palette_colors(gfx->palette) : NULL;
|
const u32* pal = pxl8_palette_colors(gfx->palette);
|
||||||
if (!pal) {
|
if (!pal) {
|
||||||
pxl8_error("resolve: no palette!");
|
pxl8_error("resolve: no palette!");
|
||||||
return;
|
return;
|
||||||
|
|
@ -180,10 +163,8 @@ i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath) {
|
||||||
if (!gfx || !gfx->palette || !filepath) return -1;
|
if (!gfx || !gfx->palette || !filepath) return -1;
|
||||||
pxl8_result result = pxl8_palette_load_ase(gfx->palette, filepath);
|
pxl8_result result = pxl8_palette_load_ase(gfx->palette, filepath);
|
||||||
if (result != PXL8_OK) return (i32)result;
|
if (result != PXL8_OK) return (i32)result;
|
||||||
if (gfx->pixel_mode != PXL8_PIXEL_HICOLOR) {
|
if (gfx->colormap) {
|
||||||
if (gfx->colormap) {
|
pxl8_colormap_generate(gfx->colormap, pxl8_palette_colors(gfx->palette));
|
||||||
pxl8_colormap_generate(gfx->colormap, pxl8_palette_colors(gfx->palette));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -191,7 +172,6 @@ i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath) {
|
||||||
pxl8_gfx* pxl8_gfx_create(
|
pxl8_gfx* pxl8_gfx_create(
|
||||||
const pxl8_hal* hal,
|
const pxl8_hal* hal,
|
||||||
void* platform_data,
|
void* platform_data,
|
||||||
pxl8_pixel_mode mode,
|
|
||||||
pxl8_resolution resolution
|
pxl8_resolution resolution
|
||||||
) {
|
) {
|
||||||
pxl8_shader_registry_init();
|
pxl8_shader_registry_init();
|
||||||
|
|
@ -205,7 +185,6 @@ pxl8_gfx* pxl8_gfx_create(
|
||||||
gfx->hal = hal;
|
gfx->hal = hal;
|
||||||
gfx->platform_data = platform_data;
|
gfx->platform_data = platform_data;
|
||||||
|
|
||||||
gfx->pixel_mode = mode;
|
|
||||||
pxl8_size size = pxl8_get_resolution_dimensions(resolution);
|
pxl8_size size = pxl8_get_resolution_dimensions(resolution);
|
||||||
gfx->framebuffer_width = size.w;
|
gfx->framebuffer_width = size.w;
|
||||||
gfx->framebuffer_height = size.h;
|
gfx->framebuffer_height = size.h;
|
||||||
|
|
@ -216,9 +195,7 @@ pxl8_gfx* pxl8_gfx_create(
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode != PXL8_PIXEL_HICOLOR) {
|
gfx->palette = pxl8_palette_create();
|
||||||
gfx->palette = pxl8_palette_create();
|
|
||||||
}
|
|
||||||
|
|
||||||
gfx->renderer = pxl8_renderer_create(gfx->framebuffer_width, gfx->framebuffer_height);
|
gfx->renderer = pxl8_renderer_create(gfx->framebuffer_width, gfx->framebuffer_height);
|
||||||
if (!gfx->renderer) {
|
if (!gfx->renderer) {
|
||||||
|
|
@ -258,9 +235,7 @@ pxl8_gfx* pxl8_gfx_create(
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode != PXL8_PIXEL_HICOLOR) {
|
gfx->colormap = pxl8_calloc(1, sizeof(pxl8_colormap));
|
||||||
gfx->colormap = pxl8_calloc(1, sizeof(pxl8_colormap));
|
|
||||||
}
|
|
||||||
|
|
||||||
gfx->target_stack[0] = (pxl8_target_entry){
|
gfx->target_stack[0] = (pxl8_target_entry){
|
||||||
.color = gfx->color_target,
|
.color = gfx->color_target,
|
||||||
|
|
@ -313,7 +288,7 @@ void pxl8_gfx_destroy(pxl8_gfx* gfx) {
|
||||||
|
|
||||||
static pxl8_result pxl8_gfx_ensure_atlas(pxl8_gfx* gfx) {
|
static pxl8_result pxl8_gfx_ensure_atlas(pxl8_gfx* gfx) {
|
||||||
if (gfx->atlas) return PXL8_OK;
|
if (gfx->atlas) return PXL8_OK;
|
||||||
gfx->atlas = pxl8_atlas_create(PXL8_DEFAULT_ATLAS_SIZE, PXL8_DEFAULT_ATLAS_SIZE, gfx->pixel_mode);
|
gfx->atlas = pxl8_atlas_create(PXL8_DEFAULT_ATLAS_SIZE, PXL8_DEFAULT_ATLAS_SIZE);
|
||||||
return gfx->atlas ? PXL8_OK : PXL8_ERROR_OUT_OF_MEMORY;
|
return gfx->atlas ? PXL8_OK : PXL8_ERROR_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -335,7 +310,7 @@ pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width,
|
||||||
pxl8_result result = pxl8_gfx_ensure_atlas(gfx);
|
pxl8_result result = pxl8_gfx_ensure_atlas(gfx);
|
||||||
if (result != PXL8_OK) return result;
|
if (result != PXL8_OK) return result;
|
||||||
|
|
||||||
u32 texture_id = pxl8_atlas_add_texture(gfx->atlas, pixels, width, height, gfx->pixel_mode);
|
u32 texture_id = pxl8_atlas_add_texture(gfx->atlas, pixels, width, height);
|
||||||
if (texture_id == UINT32_MAX) {
|
if (texture_id == UINT32_MAX) {
|
||||||
pxl8_error("Texture doesn't fit in atlas");
|
pxl8_error("Texture doesn't fit in atlas");
|
||||||
return PXL8_ERROR_INVALID_SIZE;
|
return PXL8_ERROR_INVALID_SIZE;
|
||||||
|
|
@ -381,8 +356,7 @@ pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) {
|
||||||
gfx->atlas,
|
gfx->atlas,
|
||||||
ase_file.frames[0].pixels,
|
ase_file.frames[0].pixels,
|
||||||
ase_file.header.width,
|
ase_file.header.width,
|
||||||
ase_file.header.height,
|
ase_file.header.height
|
||||||
gfx->pixel_mode
|
|
||||||
);
|
);
|
||||||
|
|
||||||
pxl8_ase_destroy(&ase_file);
|
pxl8_ase_destroy(&ase_file);
|
||||||
|
|
@ -418,36 +392,17 @@ pxl8_atlas* pxl8_gfx_get_atlas(pxl8_gfx* gfx) {
|
||||||
return gfx->atlas;
|
return gfx->atlas;
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx) {
|
|
||||||
(void)gfx;
|
|
||||||
return PXL8_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) {
|
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) {
|
||||||
if (!gfx || !gfx->initialized || !gfx->hal) return;
|
if (!gfx || !gfx->initialized || !gfx->hal) return;
|
||||||
|
|
||||||
if (gfx->pixel_mode == PXL8_PIXEL_INDEXED) {
|
pxl8_gfx_resolve(gfx);
|
||||||
pxl8_gfx_resolve(gfx);
|
|
||||||
gfx->hal->upload_texture(
|
|
||||||
gfx->platform_data,
|
|
||||||
gfx->output,
|
|
||||||
gfx->framebuffer_width,
|
|
||||||
gfx->framebuffer_height,
|
|
||||||
PXL8_PIXEL_RGBA,
|
|
||||||
NULL
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
u32* colors = gfx->palette ? pxl8_palette_colors(gfx->palette) : NULL;
|
|
||||||
u8* framebuffer = (u8*)pxl8_texture_get_data(gfx->renderer, gfx->color_target);
|
|
||||||
gfx->hal->upload_texture(
|
gfx->hal->upload_texture(
|
||||||
gfx->platform_data,
|
gfx->platform_data,
|
||||||
framebuffer,
|
gfx->output,
|
||||||
gfx->framebuffer_width,
|
gfx->framebuffer_width,
|
||||||
gfx->framebuffer_height,
|
gfx->framebuffer_height,
|
||||||
gfx->pixel_mode,
|
4,
|
||||||
colors
|
NULL
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -472,10 +427,6 @@ void pxl8_gfx_set_viewport(pxl8_gfx* gfx, pxl8_viewport vp) {
|
||||||
gfx->viewport = vp;
|
gfx->viewport = vp;
|
||||||
}
|
}
|
||||||
|
|
||||||
void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom) {
|
|
||||||
(void)gfx; (void)left; (void)right; (void)top; (void)bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
static pxl8_gfx_texture gfx_current_color(pxl8_gfx* gfx) {
|
static pxl8_gfx_texture gfx_current_color(pxl8_gfx* gfx) {
|
||||||
if (gfx->target_stack_depth == 0) return gfx->color_target;
|
if (gfx->target_stack_depth == 0) return gfx->color_target;
|
||||||
return gfx->target_stack[gfx->target_stack_depth - 1].color;
|
return gfx->target_stack[gfx->target_stack_depth - 1].color;
|
||||||
|
|
@ -639,7 +590,7 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo
|
||||||
|
|
||||||
if (is_1to1_scale && is_unclipped && !is_flipped) {
|
if (is_1to1_scale && is_unclipped && !is_flipped) {
|
||||||
const u8* sprite_data = atlas_pixels + entry->y * atlas_width + entry->x;
|
const u8* sprite_data = atlas_pixels + entry->y * atlas_width + entry->x;
|
||||||
pxl8_blit_indexed(framebuffer, fb_width, sprite_data, atlas_width, x, y, w, h);
|
pxl8_blit(framebuffer, fb_width, sprite_data, atlas_width, x, y, w, h);
|
||||||
} else {
|
} else {
|
||||||
for (i32 py = 0; py < draw_height; py++) {
|
for (i32 py = 0; py < draw_height; py++) {
|
||||||
for (i32 px = 0; px < draw_width; px++) {
|
for (i32 px = 0; px < draw_width; px++) {
|
||||||
|
|
@ -652,7 +603,7 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo
|
||||||
i32 src_idx = src_y * atlas_width + src_x;
|
i32 src_idx = src_y * atlas_width + src_x;
|
||||||
i32 dest_idx = (dest_y + py) * fb_width + (dest_x + px);
|
i32 dest_idx = (dest_y + py) * fb_width + (dest_x + px);
|
||||||
|
|
||||||
framebuffer[dest_idx] = pxl8_blend_indexed(atlas_pixels[src_idx], framebuffer[dest_idx]);
|
framebuffer[dest_idx] = pxl8_blit_mask(atlas_pixels[src_idx], framebuffer[dest_idx]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -667,7 +618,7 @@ void pxl8_gfx_update(pxl8_gfx* gfx, f32 dt) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_shader_uniforms* uniforms) {
|
static pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_shader_uniforms* uniforms) {
|
||||||
pxl8_3d_frame frame = {0};
|
pxl8_3d_frame frame = {0};
|
||||||
if (!camera) return frame;
|
if (!camera) return frame;
|
||||||
|
|
||||||
|
|
@ -770,6 +721,11 @@ void pxl8_3d_clear_depth(pxl8_gfx* gfx) {
|
||||||
pxl8_cmdbuf_clear_depth(gfx->cmdbuf, gfx_current_depth(gfx));
|
pxl8_cmdbuf_clear_depth(gfx->cmdbuf, gfx_current_depth(gfx));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void pxl8_3d_clear_stencil(pxl8_gfx* gfx, u8 value) {
|
||||||
|
if (!gfx) return;
|
||||||
|
pxl8_clear_stencil(gfx->renderer, value);
|
||||||
|
}
|
||||||
|
|
||||||
void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color) {
|
void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color) {
|
||||||
if (!gfx) return;
|
if (!gfx) return;
|
||||||
|
|
||||||
|
|
@ -789,7 +745,7 @@ void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color) {
|
||||||
pxl8_draw_line(gfx->renderer, gfx_current_color(gfx), x0, y0, x1, y1, color);
|
pxl8_draw_line(gfx->renderer, gfx_current_color(gfx), x0, y0, x1, y1, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material) {
|
void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material, const pxl8_gfx_draw_opts* opts) {
|
||||||
if (!gfx || !mesh || !model || !material) return;
|
if (!gfx || !mesh || !model || !material) return;
|
||||||
if (!pxl8_gfx_handle_valid(gfx->frame_pass)) return;
|
if (!pxl8_gfx_handle_valid(gfx->frame_pass)) return;
|
||||||
|
|
||||||
|
|
@ -801,6 +757,13 @@ void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* mo
|
||||||
shader_name = "unlit";
|
shader_name = "unlit";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const pxl8_gfx_draw_opts default_opts = {
|
||||||
|
.color_write = true,
|
||||||
|
.depth_compare = PXL8_GFX_COMPARE_LESS,
|
||||||
|
.depth_write = true,
|
||||||
|
};
|
||||||
|
if (!opts) opts = &default_opts;
|
||||||
|
|
||||||
pxl8_gfx_pipeline_desc pipe_desc = {
|
pxl8_gfx_pipeline_desc pipe_desc = {
|
||||||
.blend = {
|
.blend = {
|
||||||
.enabled = false,
|
.enabled = false,
|
||||||
|
|
@ -809,7 +772,9 @@ void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* mo
|
||||||
.alpha_test = false,
|
.alpha_test = false,
|
||||||
.alpha_ref = 0,
|
.alpha_ref = 0,
|
||||||
},
|
},
|
||||||
.depth = { .test = true, .write = true, .compare = PXL8_GFX_COMPARE_LESS },
|
.color_write = opts->color_write,
|
||||||
|
.depth = { .test = true, .write = opts->depth_write, .compare = opts->depth_compare },
|
||||||
|
.stencil = { .test = opts->stencil_test, .write = opts->stencil_write, .compare = opts->stencil_compare, .ref = opts->stencil_ref },
|
||||||
.dither = material->dither,
|
.dither = material->dither,
|
||||||
.double_sided = material->double_sided,
|
.double_sided = material->double_sided,
|
||||||
.emissive = material->emissive,
|
.emissive = material->emissive,
|
||||||
|
|
@ -948,11 +913,6 @@ void pxl8_3d_end_frame(pxl8_gfx* gfx) {
|
||||||
res->texture_count = 0;
|
res->texture_count = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
u8* pxl8_3d_get_framebuffer(pxl8_gfx* gfx) {
|
|
||||||
if (!gfx) return NULL;
|
|
||||||
return (u8*)pxl8_texture_get_data(gfx->renderer, gfx_current_color(gfx));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool pxl8_gfx_push_target(pxl8_gfx* gfx) {
|
bool pxl8_gfx_push_target(pxl8_gfx* gfx) {
|
||||||
if (!gfx || gfx->target_stack_depth >= PXL8_MAX_TARGET_STACK) return false;
|
if (!gfx || gfx->target_stack_depth >= PXL8_MAX_TARGET_STACK) return false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ typedef enum pxl8_gfx_effect {
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
pxl8_gfx* pxl8_gfx_create(const pxl8_hal* hal, void* platform_data, pxl8_pixel_mode mode, pxl8_resolution resolution);
|
pxl8_gfx* pxl8_gfx_create(const pxl8_hal* hal, void* platform_data, pxl8_resolution resolution);
|
||||||
void pxl8_gfx_destroy(pxl8_gfx* gfx);
|
void pxl8_gfx_destroy(pxl8_gfx* gfx);
|
||||||
|
|
||||||
void pxl8_gfx_present(pxl8_gfx* gfx);
|
void pxl8_gfx_present(pxl8_gfx* gfx);
|
||||||
|
|
@ -46,25 +46,21 @@ void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx);
|
||||||
u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color);
|
u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color);
|
||||||
|
|
||||||
pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx);
|
pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx);
|
||||||
u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx);
|
u8* pxl8_gfx_framebuffer(pxl8_gfx* gfx);
|
||||||
u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx);
|
|
||||||
i32 pxl8_gfx_get_height(const pxl8_gfx* gfx);
|
i32 pxl8_gfx_get_height(const pxl8_gfx* gfx);
|
||||||
const u32* pxl8_gfx_palette_colors(pxl8_gfx* gfx);
|
const u32* pxl8_gfx_palette_colors(pxl8_gfx* gfx);
|
||||||
u16* pxl8_gfx_get_zbuffer(pxl8_gfx* gfx);
|
u16* pxl8_gfx_get_zbuffer(pxl8_gfx* gfx);
|
||||||
pxl8_palette* pxl8_gfx_palette(pxl8_gfx* gfx);
|
pxl8_palette* pxl8_gfx_palette(pxl8_gfx* gfx);
|
||||||
pxl8_colormap* pxl8_gfx_colormap(pxl8_gfx* gfx);
|
pxl8_colormap* pxl8_gfx_colormap(pxl8_gfx* gfx);
|
||||||
pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx);
|
|
||||||
i32 pxl8_gfx_get_width(const pxl8_gfx* gfx);
|
i32 pxl8_gfx_get_width(const pxl8_gfx* gfx);
|
||||||
|
|
||||||
i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath);
|
i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath);
|
||||||
void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom);
|
|
||||||
void pxl8_gfx_set_palette_colors(pxl8_gfx* gfx, const u32* colors, u16 count);
|
void pxl8_gfx_set_palette_colors(pxl8_gfx* gfx, const u32* colors, u16 count);
|
||||||
void pxl8_gfx_set_viewport(pxl8_gfx* gfx, pxl8_viewport vp);
|
void pxl8_gfx_set_viewport(pxl8_gfx* gfx, pxl8_viewport vp);
|
||||||
pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height);
|
pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height);
|
||||||
|
|
||||||
void pxl8_gfx_clear_textures(pxl8_gfx* gfx);
|
void pxl8_gfx_clear_textures(pxl8_gfx* gfx);
|
||||||
pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width, u32 height);
|
pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width, u32 height);
|
||||||
pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx);
|
|
||||||
pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path);
|
pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path);
|
||||||
|
|
||||||
bool pxl8_gfx_push_target(pxl8_gfx* gfx);
|
bool pxl8_gfx_push_target(pxl8_gfx* gfx);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
#include "pxl8_lights.h"
|
#include "pxl8_lights.h"
|
||||||
#include "pxl8_math.h"
|
#include "pxl8_math.h"
|
||||||
#include "pxl8_mesh.h"
|
#include "pxl8_mesh.h"
|
||||||
|
#include "pxl8_render_types.h"
|
||||||
#include "pxl8_shader.h"
|
#include "pxl8_shader.h"
|
||||||
#include "pxl8_types.h"
|
#include "pxl8_types.h"
|
||||||
|
|
||||||
|
|
@ -29,16 +30,14 @@ void pxl8_3d_set_bsp(pxl8_gfx* gfx, const pxl8_bsp* bsp);
|
||||||
void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_shader_uniforms* uniforms);
|
void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_shader_uniforms* uniforms);
|
||||||
void pxl8_3d_clear(pxl8_gfx* gfx, u8 color);
|
void pxl8_3d_clear(pxl8_gfx* gfx, u8 color);
|
||||||
void pxl8_3d_clear_depth(pxl8_gfx* gfx);
|
void pxl8_3d_clear_depth(pxl8_gfx* gfx);
|
||||||
|
void pxl8_3d_clear_stencil(pxl8_gfx* gfx, u8 value);
|
||||||
void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color);
|
void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color);
|
||||||
void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material);
|
void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material, const pxl8_gfx_draw_opts* opts);
|
||||||
void pxl8_3d_end_frame(pxl8_gfx* gfx);
|
void pxl8_3d_end_frame(pxl8_gfx* gfx);
|
||||||
u8* pxl8_3d_get_framebuffer(pxl8_gfx* gfx);
|
|
||||||
const pxl8_frustum* pxl8_3d_get_frustum(pxl8_gfx* gfx);
|
const pxl8_frustum* pxl8_3d_get_frustum(pxl8_gfx* gfx);
|
||||||
const pxl8_mat4* pxl8_3d_get_view_proj(pxl8_gfx* gfx);
|
const pxl8_mat4* pxl8_3d_get_view_proj(pxl8_gfx* gfx);
|
||||||
u32 pxl8_3d_project_points(pxl8_gfx* gfx, const pxl8_vec3* in, pxl8_vec3* out, u32 count, const pxl8_mat4* transform);
|
u32 pxl8_3d_project_points(pxl8_gfx* gfx, const pxl8_vec3* in, pxl8_vec3* out, u32 count, const pxl8_mat4* transform);
|
||||||
|
|
||||||
pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_shader_uniforms* uniforms);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ static u8 palette_find_closest(const u32* palette, u8 r, u8 g, u8 b) {
|
||||||
return best_idx;
|
return best_idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
static u8 blend_indexed(
|
static u8 blend_colors(
|
||||||
const pxl8_gfx_pipeline_desc* pipeline,
|
const pxl8_gfx_pipeline_desc* pipeline,
|
||||||
u8 src,
|
u8 src,
|
||||||
u8 dst,
|
u8 dst,
|
||||||
|
|
@ -279,6 +279,7 @@ static void rasterize_triangle(
|
||||||
const tri_setup* setup,
|
const tri_setup* setup,
|
||||||
u8* fb,
|
u8* fb,
|
||||||
u16* zb,
|
u16* zb,
|
||||||
|
u8* sb,
|
||||||
u32 fb_width,
|
u32 fb_width,
|
||||||
pxl8_shader_fn shader,
|
pxl8_shader_fn shader,
|
||||||
const pxl8_gfx_pipeline_desc* pipeline,
|
const pxl8_gfx_pipeline_desc* pipeline,
|
||||||
|
|
@ -289,6 +290,7 @@ static void rasterize_triangle(
|
||||||
|
|
||||||
if (setup->y_start > setup->y_end) return;
|
if (setup->y_start > setup->y_end) return;
|
||||||
|
|
||||||
|
bool color_write = !pipeline || pipeline->color_write;
|
||||||
bool depth_test = pipeline && pipeline->depth.test;
|
bool depth_test = pipeline && pipeline->depth.test;
|
||||||
bool depth_write = pipeline && pipeline->depth.write;
|
bool depth_write = pipeline && pipeline->depth.write;
|
||||||
pxl8_gfx_compare_func depth_compare = pipeline ? pipeline->depth.compare : PXL8_GFX_COMPARE_ALWAYS;
|
pxl8_gfx_compare_func depth_compare = pipeline ? pipeline->depth.compare : PXL8_GFX_COMPARE_ALWAYS;
|
||||||
|
|
@ -296,6 +298,10 @@ static void rasterize_triangle(
|
||||||
u8 alpha_ref = pipeline ? pipeline->blend.alpha_ref : 0;
|
u8 alpha_ref = pipeline ? pipeline->blend.alpha_ref : 0;
|
||||||
bool blend_enabled = pipeline && pipeline->blend.enabled;
|
bool blend_enabled = pipeline && pipeline->blend.enabled;
|
||||||
const u32* palette = bindings ? bindings->palette : NULL;
|
const u32* palette = bindings ? bindings->palette : NULL;
|
||||||
|
bool stencil_test = pipeline && pipeline->stencil.test && sb;
|
||||||
|
bool stencil_write = pipeline && pipeline->stencil.write && sb;
|
||||||
|
pxl8_gfx_compare_func stencil_compare = pipeline ? pipeline->stencil.compare : PXL8_GFX_COMPARE_ALWAYS;
|
||||||
|
u8 stencil_ref = pipeline ? pipeline->stencil.ref : 0;
|
||||||
|
|
||||||
for (i32 y = setup->y_start; y <= setup->y_end; y++) {
|
for (i32 y = setup->y_start; y <= setup->y_end; y++) {
|
||||||
f32 yf = (f32)y + 0.5f;
|
f32 yf = (f32)y + 0.5f;
|
||||||
|
|
@ -453,7 +459,7 @@ static void rasterize_triangle(
|
||||||
i32 px = x;
|
i32 px = x;
|
||||||
|
|
||||||
#if defined(PXL8_SIMD_SSE) || defined(PXL8_SIMD_NEON)
|
#if defined(PXL8_SIMD_SSE) || defined(PXL8_SIMD_NEON)
|
||||||
if (depth_test && depth_compare == PXL8_GFX_COMPARE_LESS && !blend_enabled) {
|
if (depth_test && depth_compare == PXL8_GFX_COMPARE_LESS && !blend_enabled && !stencil_test && !stencil_write) {
|
||||||
pxl8_f32_simd dz4_simd = pxl8_f32_simd_set(dz * 4.0f);
|
pxl8_f32_simd dz4_simd = pxl8_f32_simd_set(dz * 4.0f);
|
||||||
pxl8_f32_simd half = pxl8_f32_simd_set(0.5f);
|
pxl8_f32_simd half = pxl8_f32_simd_set(0.5f);
|
||||||
pxl8_f32_simd one = pxl8_f32_simd_set(1.0f);
|
pxl8_f32_simd one = pxl8_f32_simd_set(1.0f);
|
||||||
|
|
@ -504,7 +510,7 @@ static void rasterize_triangle(
|
||||||
if (!(mask & (0x8 << (i * 4)))) continue;
|
if (!(mask & (0x8 << (i * 4)))) continue;
|
||||||
u8 color = colors[i];
|
u8 color = colors[i];
|
||||||
if (!(alpha_test && color <= alpha_ref) && color != 0) {
|
if (!(alpha_test && color <= alpha_ref) && color != 0) {
|
||||||
prow[px + i] = color;
|
if (color_write) prow[px + i] = color;
|
||||||
if (depth_write) zrow[px + i] = (u16)z16_arr[i];
|
if (depth_write) zrow[px + i] = (u16)z16_arr[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -516,11 +522,21 @@ static void rasterize_triangle(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (; px <= span_end; px++) {
|
for (; px <= span_end; px++) {
|
||||||
|
u32 pixel = row_start + (u32)px;
|
||||||
|
|
||||||
|
if (stencil_test && !depth_test_pass(stencil_compare, stencil_ref, sb[pixel])) {
|
||||||
|
u_a += du; v_a += dv; l_a += dl; c_a += dc;
|
||||||
|
z_a += dz; wx_a += dwx; wy_a += dwy; wz_a += dwz;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
f32 depth_norm = pxl8_clamp((z_a + 1.0f) * 0.5f, 0.0f, 1.0f);
|
f32 depth_norm = pxl8_clamp((z_a + 1.0f) * 0.5f, 0.0f, 1.0f);
|
||||||
u16 z16 = (u16)(depth_norm * 65535.0f);
|
u16 z16 = (u16)(depth_norm * 65535.0f);
|
||||||
|
|
||||||
bool depth_pass = !depth_test || depth_test_pass(depth_compare, z16, zrow[px]);
|
bool depth_pass = !depth_test || depth_test_pass(depth_compare, z16, zrow[px]);
|
||||||
if (depth_pass) {
|
if (depth_pass) {
|
||||||
|
if (stencil_write) sb[pixel] = stencil_ref;
|
||||||
|
|
||||||
pxl8_shader_ctx frag_ctx = {
|
pxl8_shader_ctx frag_ctx = {
|
||||||
.color_count = 1,
|
.color_count = 1,
|
||||||
.x = pxl8_i32_simd_set(px),
|
.x = pxl8_i32_simd_set(px),
|
||||||
|
|
@ -540,10 +556,10 @@ static void rasterize_triangle(
|
||||||
if (color != 0) {
|
if (color != 0) {
|
||||||
u8 out_color = color;
|
u8 out_color = color;
|
||||||
if (blend_enabled) {
|
if (blend_enabled) {
|
||||||
out_color = blend_indexed(pipeline, color, prow[px], palette);
|
out_color = blend_colors(pipeline, color, prow[px], palette);
|
||||||
}
|
}
|
||||||
|
|
||||||
prow[px] = out_color;
|
if (color_write) prow[px] = out_color;
|
||||||
if (depth_write) {
|
if (depth_write) {
|
||||||
zrow[px] = z16;
|
zrow[px] = z16;
|
||||||
}
|
}
|
||||||
|
|
@ -562,11 +578,21 @@ static void rasterize_triangle(
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
for (; px <= span_end; px++) {
|
for (; px <= span_end; px++) {
|
||||||
|
u32 pixel = row_start + (u32)px;
|
||||||
|
|
||||||
|
if (stencil_test && !depth_test_pass(stencil_compare, stencil_ref, sb[pixel])) {
|
||||||
|
u_a += du; v_a += dv; l_a += dl; c_a += dc;
|
||||||
|
z_a += dz; wx_a += dwx; wy_a += dwy; wz_a += dwz;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
f32 depth_norm = pxl8_clamp((z_a + 1.0f) * 0.5f, 0.0f, 1.0f);
|
f32 depth_norm = pxl8_clamp((z_a + 1.0f) * 0.5f, 0.0f, 1.0f);
|
||||||
u16 z16 = (u16)(depth_norm * 65535.0f);
|
u16 z16 = (u16)(depth_norm * 65535.0f);
|
||||||
|
|
||||||
bool depth_pass = !depth_test || depth_test_pass(depth_compare, z16, zrow[px]);
|
bool depth_pass = !depth_test || depth_test_pass(depth_compare, z16, zrow[px]);
|
||||||
if (depth_pass) {
|
if (depth_pass) {
|
||||||
|
if (stencil_write) sb[pixel] = stencil_ref;
|
||||||
|
|
||||||
pxl8_shader_ctx frag_ctx = {
|
pxl8_shader_ctx frag_ctx = {
|
||||||
.color_count = 1,
|
.color_count = 1,
|
||||||
.x = px,
|
.x = px,
|
||||||
|
|
@ -586,10 +612,10 @@ static void rasterize_triangle(
|
||||||
if (color != 0) {
|
if (color != 0) {
|
||||||
u8 out_color = color;
|
u8 out_color = color;
|
||||||
if (blend_enabled) {
|
if (blend_enabled) {
|
||||||
out_color = blend_indexed(pipeline, color, prow[px], palette);
|
out_color = blend_colors(pipeline, color, prow[px], palette);
|
||||||
}
|
}
|
||||||
|
|
||||||
prow[px] = out_color;
|
if (color_write) prow[px] = out_color;
|
||||||
if (depth_write) {
|
if (depth_write) {
|
||||||
zrow[px] = z16;
|
zrow[px] = z16;
|
||||||
}
|
}
|
||||||
|
|
@ -739,6 +765,10 @@ static u32 pipeline_desc_hash(const pxl8_gfx_pipeline_desc* d) {
|
||||||
h = (h ^ (u32)d->dither) * 16777619u;
|
h = (h ^ (u32)d->dither) * 16777619u;
|
||||||
h = (h ^ (u32)d->double_sided) * 16777619u;
|
h = (h ^ (u32)d->double_sided) * 16777619u;
|
||||||
h = (h ^ (u32)d->emissive) * 16777619u;
|
h = (h ^ (u32)d->emissive) * 16777619u;
|
||||||
|
h = (h ^ (u32)d->stencil.test) * 16777619u;
|
||||||
|
h = (h ^ (u32)d->stencil.write) * 16777619u;
|
||||||
|
h = (h ^ (u32)d->stencil.compare) * 16777619u;
|
||||||
|
h = (h ^ (u32)d->stencil.ref) * 16777619u;
|
||||||
h = (h ^ (u32)d->rasterizer.cull) * 16777619u;
|
h = (h ^ (u32)d->rasterizer.cull) * 16777619u;
|
||||||
h = (h ^ (u32)d->rasterizer.fill) * 16777619u;
|
h = (h ^ (u32)d->rasterizer.fill) * 16777619u;
|
||||||
u64 s = (u64)(uintptr_t)d->shader;
|
u64 s = (u64)(uintptr_t)d->shader;
|
||||||
|
|
@ -767,6 +797,7 @@ typedef struct {
|
||||||
struct pxl8_renderer {
|
struct pxl8_renderer {
|
||||||
u32 width;
|
u32 width;
|
||||||
u32 height;
|
u32 height;
|
||||||
|
u8* stencil;
|
||||||
|
|
||||||
texture_slot textures[PXL8_GFX_MAX_TEXTURES];
|
texture_slot textures[PXL8_GFX_MAX_TEXTURES];
|
||||||
buffer_slot buffers[PXL8_GFX_MAX_BUFFERS];
|
buffer_slot buffers[PXL8_GFX_MAX_BUFFERS];
|
||||||
|
|
@ -801,6 +832,7 @@ pxl8_renderer* pxl8_renderer_create(u32 width, u32 height) {
|
||||||
pxl8_renderer* r = pxl8_calloc(1, sizeof(pxl8_renderer));
|
pxl8_renderer* r = pxl8_calloc(1, sizeof(pxl8_renderer));
|
||||||
r->width = width;
|
r->width = width;
|
||||||
r->height = height;
|
r->height = height;
|
||||||
|
r->stencil = pxl8_calloc(width * height, 1);
|
||||||
r->viewport_w = width;
|
r->viewport_w = width;
|
||||||
r->viewport_h = height;
|
r->viewport_h = height;
|
||||||
r->scissor_w = width;
|
r->scissor_w = width;
|
||||||
|
|
@ -816,21 +848,10 @@ void pxl8_renderer_destroy(pxl8_renderer* r) {
|
||||||
for (u32 i = 0; i < PXL8_GFX_MAX_BUFFERS; i++) {
|
for (u32 i = 0; i < PXL8_GFX_MAX_BUFFERS; i++) {
|
||||||
if (r->buffers[i].data) pxl8_free(r->buffers[i].data);
|
if (r->buffers[i].data) pxl8_free(r->buffers[i].data);
|
||||||
}
|
}
|
||||||
|
pxl8_free(r->stencil);
|
||||||
pxl8_free(r);
|
pxl8_free(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 pxl8_renderer_get_width(const pxl8_renderer* r) {
|
|
||||||
return r ? r->width : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 pxl8_renderer_get_height(const pxl8_renderer* r) {
|
|
||||||
return r ? r->height : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void pxl8_renderer_set_shader(pxl8_renderer* r, pxl8_shader_fn fn) {
|
|
||||||
if (r) r->shader = fn;
|
|
||||||
}
|
|
||||||
|
|
||||||
void pxl8_renderer_update_stats(pxl8_renderer* r, f32 dt) {
|
void pxl8_renderer_update_stats(pxl8_renderer* r, f32 dt) {
|
||||||
if (!r) return;
|
if (!r) return;
|
||||||
|
|
||||||
|
|
@ -1070,16 +1091,6 @@ void pxl8_update_texture(pxl8_renderer* r, pxl8_gfx_texture tex, const pxl8_gfx_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void* pxl8_buffer_ptr(pxl8_renderer* r, pxl8_gfx_buffer buf) {
|
|
||||||
if (!VALID_BUF(r, buf)) return NULL;
|
|
||||||
return r->buffers[SLOT_INDEX(buf.id)].data;
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 pxl8_buffer_size(pxl8_renderer* r, pxl8_gfx_buffer buf) {
|
|
||||||
if (!VALID_BUF(r, buf)) return 0;
|
|
||||||
return r->buffers[SLOT_INDEX(buf.id)].size;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* pxl8_texture_get_data(pxl8_renderer* r, pxl8_gfx_texture tex) {
|
void* pxl8_texture_get_data(pxl8_renderer* r, pxl8_gfx_texture tex) {
|
||||||
if (!VALID_TEX(r, tex)) return NULL;
|
if (!VALID_TEX(r, tex)) return NULL;
|
||||||
return r->textures[SLOT_INDEX(tex.id)].data;
|
return r->textures[SLOT_INDEX(tex.id)].data;
|
||||||
|
|
@ -1095,11 +1106,6 @@ u32 pxl8_texture_get_height(pxl8_renderer* r, pxl8_gfx_texture tex) {
|
||||||
return r->textures[SLOT_INDEX(tex.id)].height;
|
return r->textures[SLOT_INDEX(tex.id)].height;
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_gfx_texture_format pxl8_texture_get_format(pxl8_renderer* r, pxl8_gfx_texture tex) {
|
|
||||||
if (!VALID_TEX(r, tex)) return PXL8_GFX_FORMAT_INDEXED8;
|
|
||||||
return r->textures[SLOT_INDEX(tex.id)].format;
|
|
||||||
}
|
|
||||||
|
|
||||||
pxl8_gfx_cmdbuf* pxl8_cmdbuf_create(u32 capacity) {
|
pxl8_gfx_cmdbuf* pxl8_cmdbuf_create(u32 capacity) {
|
||||||
pxl8_gfx_cmdbuf* cb = pxl8_malloc(sizeof(pxl8_gfx_cmdbuf));
|
pxl8_gfx_cmdbuf* cb = pxl8_malloc(sizeof(pxl8_gfx_cmdbuf));
|
||||||
cb->commands = pxl8_malloc(capacity * sizeof(pxl8_gfx_cmd));
|
cb->commands = pxl8_malloc(capacity * sizeof(pxl8_gfx_cmd));
|
||||||
|
|
@ -1402,7 +1408,7 @@ static void execute_draw(
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 raster_start = pxl8_get_ticks_ns();
|
u64 raster_start = pxl8_get_ticks_ns();
|
||||||
rasterize_triangle(&setup, fb, zb, fb_w, shader, &pip->desc,
|
rasterize_triangle(&setup, fb, zb, r->stencil, fb_w, shader, &pip->desc,
|
||||||
&shader_bindings, &shader_uniforms);
|
&shader_bindings, &shader_uniforms);
|
||||||
r->stats.raster_ns += pxl8_get_ticks_ns() - raster_start;
|
r->stats.raster_ns += pxl8_get_ticks_ns() - raster_start;
|
||||||
}
|
}
|
||||||
|
|
@ -1421,6 +1427,7 @@ void pxl8_gfx_submit(pxl8_renderer* r, pxl8_gfx_cmdbuf* cb) {
|
||||||
if (p->desc.color.load == PXL8_GFX_LOAD_CLEAR) {
|
if (p->desc.color.load == PXL8_GFX_LOAD_CLEAR) {
|
||||||
pxl8_clear(r, p->desc.color.texture, p->desc.color.clear_value);
|
pxl8_clear(r, p->desc.color.texture, p->desc.color.clear_value);
|
||||||
pxl8_clear_depth(r, p->desc.depth.texture);
|
pxl8_clear_depth(r, p->desc.depth.texture);
|
||||||
|
if (r->stencil) memset(r->stencil, 0, r->width * r->height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -1484,6 +1491,12 @@ void pxl8_clear_depth(pxl8_renderer* r, pxl8_gfx_texture target) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void pxl8_clear_stencil(pxl8_renderer* r, u8 value) {
|
||||||
|
if (!r || !r->stencil) return;
|
||||||
|
memset(r->stencil, value, r->width * r->height);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void pxl8_draw_pixel(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y, u8 color) {
|
void pxl8_draw_pixel(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y, u8 color) {
|
||||||
if (!VALID_TEX(r, target)) return;
|
if (!VALID_TEX(r, target)) return;
|
||||||
texture_slot* s = &r->textures[SLOT_INDEX(target.id)];
|
texture_slot* s = &r->textures[SLOT_INDEX(target.id)];
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,6 @@ typedef struct pxl8_gfx_cmdbuf pxl8_gfx_cmdbuf;
|
||||||
|
|
||||||
pxl8_renderer* pxl8_renderer_create(u32 width, u32 height);
|
pxl8_renderer* pxl8_renderer_create(u32 width, u32 height);
|
||||||
void pxl8_renderer_destroy(pxl8_renderer* r);
|
void pxl8_renderer_destroy(pxl8_renderer* r);
|
||||||
void pxl8_renderer_set_shader(pxl8_renderer* r, pxl8_shader_fn fn);
|
|
||||||
|
|
||||||
u32 pxl8_renderer_get_width(const pxl8_renderer* r);
|
|
||||||
u32 pxl8_renderer_get_height(const pxl8_renderer* r);
|
|
||||||
|
|
||||||
pxl8_gfx_bindings pxl8_create_bindings(pxl8_renderer* r, const pxl8_gfx_bindings_desc* desc);
|
pxl8_gfx_bindings pxl8_create_bindings(pxl8_renderer* r, const pxl8_gfx_bindings_desc* desc);
|
||||||
pxl8_gfx_buffer pxl8_create_buffer(pxl8_renderer* r, const pxl8_gfx_buffer_desc* desc);
|
pxl8_gfx_buffer pxl8_create_buffer(pxl8_renderer* r, const pxl8_gfx_buffer_desc* desc);
|
||||||
|
|
@ -35,13 +31,9 @@ void pxl8_update_buffer(pxl8_renderer* r, pxl8_gfx_buffer buf, const pxl8_gfx_ra
|
||||||
i32 pxl8_append_buffer(pxl8_renderer* r, pxl8_gfx_buffer buf, const pxl8_gfx_range* data);
|
i32 pxl8_append_buffer(pxl8_renderer* r, pxl8_gfx_buffer buf, const pxl8_gfx_range* data);
|
||||||
void pxl8_update_texture(pxl8_renderer* r, pxl8_gfx_texture tex, const pxl8_gfx_range* data, u32 x, u32 y, u32 w, u32 h);
|
void pxl8_update_texture(pxl8_renderer* r, pxl8_gfx_texture tex, const pxl8_gfx_range* data, u32 x, u32 y, u32 w, u32 h);
|
||||||
|
|
||||||
void* pxl8_buffer_ptr(pxl8_renderer* r, pxl8_gfx_buffer buf);
|
|
||||||
u32 pxl8_buffer_size(pxl8_renderer* r, pxl8_gfx_buffer buf);
|
|
||||||
|
|
||||||
void* pxl8_texture_get_data(pxl8_renderer* r, pxl8_gfx_texture tex);
|
void* pxl8_texture_get_data(pxl8_renderer* r, pxl8_gfx_texture tex);
|
||||||
u32 pxl8_texture_get_width(pxl8_renderer* r, pxl8_gfx_texture tex);
|
u32 pxl8_texture_get_width(pxl8_renderer* r, pxl8_gfx_texture tex);
|
||||||
u32 pxl8_texture_get_height(pxl8_renderer* r, pxl8_gfx_texture tex);
|
u32 pxl8_texture_get_height(pxl8_renderer* r, pxl8_gfx_texture tex);
|
||||||
pxl8_gfx_texture_format pxl8_texture_get_format(pxl8_renderer* r, pxl8_gfx_texture tex);
|
|
||||||
|
|
||||||
pxl8_gfx_cmdbuf* pxl8_cmdbuf_create(u32 capacity);
|
pxl8_gfx_cmdbuf* pxl8_cmdbuf_create(u32 capacity);
|
||||||
void pxl8_cmdbuf_destroy(pxl8_gfx_cmdbuf* cb);
|
void pxl8_cmdbuf_destroy(pxl8_gfx_cmdbuf* cb);
|
||||||
|
|
@ -61,6 +53,7 @@ void pxl8_gfx_submit(pxl8_renderer* r, pxl8_gfx_cmdbuf* cb);
|
||||||
|
|
||||||
void pxl8_clear(pxl8_renderer* r, pxl8_gfx_texture target, u8 color);
|
void pxl8_clear(pxl8_renderer* r, pxl8_gfx_texture target, u8 color);
|
||||||
void pxl8_clear_depth(pxl8_renderer* r, pxl8_gfx_texture target);
|
void pxl8_clear_depth(pxl8_renderer* r, pxl8_gfx_texture target);
|
||||||
|
void pxl8_clear_stencil(pxl8_renderer* r, u8 value);
|
||||||
|
|
||||||
void pxl8_draw_pixel(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y, u8 color);
|
void pxl8_draw_pixel(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y, u8 color);
|
||||||
u8 pxl8_get_pixel(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y);
|
u8 pxl8_get_pixel(pxl8_renderer* r, pxl8_gfx_texture target, i32 x, i32 y);
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,6 @@ typedef struct pxl8_gfx_range {
|
||||||
u32 size;
|
u32 size;
|
||||||
} pxl8_gfx_range;
|
} pxl8_gfx_range;
|
||||||
|
|
||||||
#define PXL8_GFX_RANGE(x) ((pxl8_gfx_range){ .ptr = &(x), .size = sizeof(x) })
|
|
||||||
#define PXL8_GFX_RANGE_REF(p, sz) ((pxl8_gfx_range){ .ptr = (p), .size = (sz) })
|
|
||||||
|
|
||||||
typedef enum pxl8_gfx_buffer_type {
|
typedef enum pxl8_gfx_buffer_type {
|
||||||
PXL8_GFX_BUFFER_VERTEX,
|
PXL8_GFX_BUFFER_VERTEX,
|
||||||
PXL8_GFX_BUFFER_INDEX,
|
PXL8_GFX_BUFFER_INDEX,
|
||||||
|
|
@ -87,11 +84,6 @@ typedef enum pxl8_gfx_load_op {
|
||||||
PXL8_GFX_LOAD_DONT_CARE,
|
PXL8_GFX_LOAD_DONT_CARE,
|
||||||
} pxl8_gfx_load_op;
|
} pxl8_gfx_load_op;
|
||||||
|
|
||||||
typedef enum pxl8_gfx_store_op {
|
|
||||||
PXL8_GFX_STORE_STORE,
|
|
||||||
PXL8_GFX_STORE_DONT_CARE,
|
|
||||||
} pxl8_gfx_store_op;
|
|
||||||
|
|
||||||
typedef struct pxl8_gfx_buffer_desc {
|
typedef struct pxl8_gfx_buffer_desc {
|
||||||
pxl8_gfx_range data;
|
pxl8_gfx_range data;
|
||||||
u32 capacity;
|
u32 capacity;
|
||||||
|
|
@ -125,6 +117,14 @@ typedef struct pxl8_gfx_pipeline_desc {
|
||||||
pxl8_gfx_compare_func compare;
|
pxl8_gfx_compare_func compare;
|
||||||
} depth;
|
} depth;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
bool test;
|
||||||
|
bool write;
|
||||||
|
pxl8_gfx_compare_func compare;
|
||||||
|
u8 ref;
|
||||||
|
} stencil;
|
||||||
|
|
||||||
|
bool color_write;
|
||||||
bool dither;
|
bool dither;
|
||||||
bool double_sided;
|
bool double_sided;
|
||||||
bool emissive;
|
bool emissive;
|
||||||
|
|
@ -144,10 +144,19 @@ typedef struct pxl8_gfx_bindings_desc {
|
||||||
u32 texture_id;
|
u32 texture_id;
|
||||||
} pxl8_gfx_bindings_desc;
|
} pxl8_gfx_bindings_desc;
|
||||||
|
|
||||||
|
typedef struct pxl8_gfx_draw_opts {
|
||||||
|
bool color_write;
|
||||||
|
pxl8_gfx_compare_func depth_compare;
|
||||||
|
bool depth_write;
|
||||||
|
bool stencil_test;
|
||||||
|
bool stencil_write;
|
||||||
|
u8 stencil_compare;
|
||||||
|
u8 stencil_ref;
|
||||||
|
} pxl8_gfx_draw_opts;
|
||||||
|
|
||||||
typedef struct pxl8_gfx_pass_color_attachment {
|
typedef struct pxl8_gfx_pass_color_attachment {
|
||||||
pxl8_gfx_texture texture;
|
pxl8_gfx_texture texture;
|
||||||
pxl8_gfx_load_op load;
|
pxl8_gfx_load_op load;
|
||||||
pxl8_gfx_store_op store;
|
|
||||||
u8 clear_value;
|
u8 clear_value;
|
||||||
} pxl8_gfx_pass_color_attachment;
|
} pxl8_gfx_pass_color_attachment;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ struct pxl8_tilesheet {
|
||||||
u32 tiles_per_row;
|
u32 tiles_per_row;
|
||||||
u32 total_tiles;
|
u32 total_tiles;
|
||||||
u32 width;
|
u32 width;
|
||||||
pxl8_pixel_mode pixel_mode;
|
|
||||||
u32 ref_count;
|
u32 ref_count;
|
||||||
pxl8_tile_animation* animations;
|
pxl8_tile_animation* animations;
|
||||||
u32 animation_count;
|
u32 animation_count;
|
||||||
|
|
@ -547,7 +546,6 @@ pxl8_result pxl8_tilemap_load_ase(pxl8_tilemap* tilemap, const char* filepath, u
|
||||||
tilemap->tilesheet->height = tilesheet_height;
|
tilemap->tilesheet->height = tilesheet_height;
|
||||||
tilemap->tilesheet->tiles_per_row = tiles_per_row;
|
tilemap->tilesheet->tiles_per_row = tiles_per_row;
|
||||||
tilemap->tilesheet->total_tiles = tileset->tile_count;
|
tilemap->tilesheet->total_tiles = tileset->tile_count;
|
||||||
tilemap->tilesheet->pixel_mode = PXL8_PIXEL_INDEXED;
|
|
||||||
|
|
||||||
if (tilemap->tilesheet->tile_valid) pxl8_free(tilemap->tilesheet->tile_valid);
|
if (tilemap->tilesheet->tile_valid) pxl8_free(tilemap->tilesheet->tile_valid);
|
||||||
tilemap->tilesheet->tile_valid = pxl8_calloc(tileset->tile_count + 1, sizeof(bool));
|
tilemap->tilesheet->tile_valid = pxl8_calloc(tileset->tile_count + 1, sizeof(bool));
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "pxl8_ase.h"
|
#include "pxl8_ase.h"
|
||||||
#include "pxl8_color.h"
|
|
||||||
#include "pxl8_gfx.h"
|
#include "pxl8_gfx.h"
|
||||||
#include "pxl8_log.h"
|
#include "pxl8_log.h"
|
||||||
#include "pxl8_mem.h"
|
#include "pxl8_mem.h"
|
||||||
|
|
@ -20,7 +19,6 @@ struct pxl8_tilesheet {
|
||||||
u32 total_tiles;
|
u32 total_tiles;
|
||||||
u32 width;
|
u32 width;
|
||||||
|
|
||||||
pxl8_pixel_mode pixel_mode;
|
|
||||||
u32 ref_count;
|
u32 ref_count;
|
||||||
|
|
||||||
pxl8_tile_animation* animations;
|
pxl8_tile_animation* animations;
|
||||||
|
|
@ -106,14 +104,10 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath,
|
||||||
tilesheet->height = height;
|
tilesheet->height = height;
|
||||||
tilesheet->tiles_per_row = width / tilesheet->tile_size;
|
tilesheet->tiles_per_row = width / tilesheet->tile_size;
|
||||||
tilesheet->total_tiles = (width / tilesheet->tile_size) * (height / tilesheet->tile_size);
|
tilesheet->total_tiles = (width / tilesheet->tile_size) * (height / tilesheet->tile_size);
|
||||||
tilesheet->pixel_mode = pxl8_gfx_get_pixel_mode(gfx);
|
|
||||||
|
|
||||||
u32 pixel_count = width * height;
|
u32 pixel_count = width * height;
|
||||||
u16 ase_depth = ase_file.header.color_depth;
|
u16 ase_depth = ase_file.header.color_depth;
|
||||||
bool gfx_hicolor = (tilesheet->pixel_mode == PXL8_PIXEL_HICOLOR);
|
|
||||||
|
|
||||||
usize data_size = pixel_count * pxl8_bytes_per_pixel(tilesheet->pixel_mode);
|
tilesheet->data = pxl8_malloc(pixel_count);
|
||||||
tilesheet->data = pxl8_malloc(data_size);
|
|
||||||
if (!tilesheet->data) {
|
if (!tilesheet->data) {
|
||||||
pxl8_ase_destroy(&ase_file);
|
pxl8_ase_destroy(&ase_file);
|
||||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||||
|
|
@ -122,34 +116,10 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath,
|
||||||
if (ase_file.frame_count > 0 && ase_file.frames[0].pixels) {
|
if (ase_file.frame_count > 0 && ase_file.frames[0].pixels) {
|
||||||
const u8* src = ase_file.frames[0].pixels;
|
const u8* src = ase_file.frames[0].pixels;
|
||||||
|
|
||||||
if (ase_depth == 8 && !gfx_hicolor) {
|
if (ase_depth == 8) {
|
||||||
memcpy(tilesheet->data, src, pixel_count);
|
|
||||||
} else if (ase_depth == 32 && gfx_hicolor) {
|
|
||||||
u16* dst = (u16*)tilesheet->data;
|
|
||||||
const u32* rgba = (const u32*)src;
|
|
||||||
for (u32 i = 0; i < pixel_count; i++) {
|
|
||||||
u32 c = rgba[i];
|
|
||||||
u8 a = (c >> 24) & 0xFF;
|
|
||||||
if (a == 0) {
|
|
||||||
dst[i] = 0;
|
|
||||||
} else {
|
|
||||||
dst[i] = pxl8_rgba32_to_rgb565(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (ase_depth == 8 && gfx_hicolor) {
|
|
||||||
pxl8_warn("Indexed ASE with hicolor gfx - storing as indexed");
|
|
||||||
tilesheet->pixel_mode = PXL8_PIXEL_INDEXED;
|
|
||||||
u8* new_data = pxl8_realloc(tilesheet->data, pixel_count);
|
|
||||||
if (!new_data) {
|
|
||||||
pxl8_free(tilesheet->data);
|
|
||||||
tilesheet->data = NULL;
|
|
||||||
pxl8_ase_destroy(&ase_file);
|
|
||||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
|
||||||
}
|
|
||||||
tilesheet->data = new_data;
|
|
||||||
memcpy(tilesheet->data, src, pixel_count);
|
memcpy(tilesheet->data, src, pixel_count);
|
||||||
} else {
|
} else {
|
||||||
pxl8_error("Unsupported ASE color depth %d for gfx mode", ase_depth);
|
pxl8_error("Unsupported ASE color depth %d (expected indexed 8-bit)", ase_depth);
|
||||||
pxl8_free(tilesheet->data);
|
pxl8_free(tilesheet->data);
|
||||||
tilesheet->data = NULL;
|
tilesheet->data = NULL;
|
||||||
pxl8_ase_destroy(&ase_file);
|
pxl8_ase_destroy(&ase_file);
|
||||||
|
|
@ -166,7 +136,6 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath,
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 valid_tiles = 0;
|
u32 valid_tiles = 0;
|
||||||
bool is_hicolor = (tilesheet->pixel_mode == PXL8_PIXEL_HICOLOR);
|
|
||||||
|
|
||||||
for (u32 tile_id = 1; tile_id <= tilesheet->total_tiles; tile_id++) {
|
for (u32 tile_id = 1; tile_id <= tilesheet->total_tiles; tile_id++) {
|
||||||
u32 tile_x = ((tile_id - 1) % tilesheet->tiles_per_row) * tilesheet->tile_size;
|
u32 tile_x = ((tile_id - 1) % tilesheet->tiles_per_row) * tilesheet->tile_size;
|
||||||
|
|
@ -176,16 +145,9 @@ pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath,
|
||||||
for (u32 py = 0; py < tilesheet->tile_size && !has_content; py++) {
|
for (u32 py = 0; py < tilesheet->tile_size && !has_content; py++) {
|
||||||
for (u32 px = 0; px < tilesheet->tile_size; px++) {
|
for (u32 px = 0; px < tilesheet->tile_size; px++) {
|
||||||
u32 idx = (tile_y + py) * width + (tile_x + px);
|
u32 idx = (tile_y + py) * width + (tile_x + px);
|
||||||
if (is_hicolor) {
|
if (tilesheet->data[idx] != 0) {
|
||||||
if (((u16*)tilesheet->data)[idx] != 0) {
|
has_content = true;
|
||||||
has_content = true;
|
break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (tilesheet->data[idx] != 0) {
|
|
||||||
has_content = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -312,7 +274,6 @@ pxl8_result pxl8_tilesheet_set_tile_pixels(pxl8_tilesheet* tilesheet, u16 tile_i
|
||||||
|
|
||||||
u32 tile_x = (tile_id - 1) % tilesheet->tiles_per_row;
|
u32 tile_x = (tile_id - 1) % tilesheet->tiles_per_row;
|
||||||
u32 tile_y = (tile_id - 1) / tilesheet->tiles_per_row;
|
u32 tile_y = (tile_id - 1) / tilesheet->tiles_per_row;
|
||||||
u32 bytes_per_pixel = pxl8_bytes_per_pixel(tilesheet->pixel_mode);
|
|
||||||
|
|
||||||
for (u32 py = 0; py < tilesheet->tile_size; py++) {
|
for (u32 py = 0; py < tilesheet->tile_size; py++) {
|
||||||
for (u32 px = 0; px < tilesheet->tile_size; px++) {
|
for (u32 px = 0; px < tilesheet->tile_size; px++) {
|
||||||
|
|
@ -320,12 +281,7 @@ pxl8_result pxl8_tilesheet_set_tile_pixels(pxl8_tilesheet* tilesheet, u16 tile_i
|
||||||
u32 dst_x = tile_x * tilesheet->tile_size + px;
|
u32 dst_x = tile_x * tilesheet->tile_size + px;
|
||||||
u32 dst_y = tile_y * tilesheet->tile_size + py;
|
u32 dst_y = tile_y * tilesheet->tile_size + py;
|
||||||
u32 dst_idx = dst_y * tilesheet->width + dst_x;
|
u32 dst_idx = dst_y * tilesheet->width + dst_x;
|
||||||
|
tilesheet->data[dst_idx] = pixels[src_idx];
|
||||||
if (bytes_per_pixel == 2) {
|
|
||||||
((u16*)tilesheet->data)[dst_idx] = ((const u16*)pixels)[src_idx];
|
|
||||||
} else {
|
|
||||||
tilesheet->data[dst_idx] = pixels[src_idx];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -170,11 +170,7 @@ void pxl8_transition_render(const pxl8_transition* transition, pxl8_gfx* gfx) {
|
||||||
i32 block_size = (i32)(max_block_size * progress);
|
i32 block_size = (i32)(max_block_size * progress);
|
||||||
if (block_size < 1) block_size = 1;
|
if (block_size < 1) block_size = 1;
|
||||||
|
|
||||||
pxl8_pixel_mode mode = pxl8_gfx_get_pixel_mode(gfx);
|
if (!pxl8_gfx_framebuffer(gfx)) break;
|
||||||
bool has_fb = (mode == PXL8_PIXEL_HICOLOR)
|
|
||||||
? (pxl8_gfx_get_framebuffer_hicolor(gfx) != NULL)
|
|
||||||
: (pxl8_gfx_get_framebuffer_indexed(gfx) != NULL);
|
|
||||||
if (!has_fb) break;
|
|
||||||
|
|
||||||
for (i32 y = 0; y < height; y += block_size) {
|
for (i32 y = 0; y < height; y += block_size) {
|
||||||
for (i32 x = 0; x < width; x += block_size) {
|
for (i32 x = 0; x < width; x += block_size) {
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,22 @@ bool pxl8_gui_slider_int(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i3
|
||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i32 pxl8_gui_toolbar(pxl8_gui_state* state, pxl8_gfx* gfx, u32 base_id,
|
||||||
|
i32 x, i32 y, i32 btn_w, i32 btn_h,
|
||||||
|
const char** labels, i32 count, i32 selected) {
|
||||||
|
i32 result = -1;
|
||||||
|
for (i32 i = 0; i < count; i++) {
|
||||||
|
i32 bx = x + i * (btn_w + 2);
|
||||||
|
bool clicked = pxl8_gui_button(state, gfx, base_id + (u32)i, bx, y, btn_w, btn_h, labels[i]);
|
||||||
|
if (clicked) result = i;
|
||||||
|
if (i == selected) {
|
||||||
|
u8 sel_color = pxl8_gui_color(gfx, PXL8_UI_FG0);
|
||||||
|
pxl8_2d_rect(gfx, bx, y + btn_h - 2, btn_w, 2, sel_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title) {
|
void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title) {
|
||||||
if (!gfx || !title) return;
|
if (!gfx || !title) return;
|
||||||
|
|
||||||
|
|
@ -206,3 +222,63 @@ void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y) {
|
||||||
if (x) *x = state->cursor_x;
|
if (x) *x = state->cursor_x;
|
||||||
if (y) *y = state->cursor_y;
|
if (y) *y = state->cursor_y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i32 pxl8_gui_grid_select(pxl8_gui_state* state, pxl8_gfx* gfx, u32 base_id,
|
||||||
|
i32 x, i32 y, i32 cell_w, i32 cell_h,
|
||||||
|
i32 cols, i32 rows, const u8* colors, i32 selected) {
|
||||||
|
i32 result = -1;
|
||||||
|
for (i32 r = 0; r < rows; r++) {
|
||||||
|
for (i32 c = 0; c < cols; c++) {
|
||||||
|
i32 cx = x + c * (cell_w + 1);
|
||||||
|
i32 cy = y + r * (cell_h + 1);
|
||||||
|
i32 idx = r * cols + c;
|
||||||
|
u32 id = base_id + (u32)idx;
|
||||||
|
bool over = is_cursor_over(state, cx, cy, cell_w, cell_h);
|
||||||
|
if (over) state->hot_id = id;
|
||||||
|
if (over && state->cursor_down && state->active_id == 0)
|
||||||
|
state->active_id = id;
|
||||||
|
bool clicked = (state->active_id == id) && state->cursor_clicked && over;
|
||||||
|
if (clicked) { result = idx; state->active_id = 0; }
|
||||||
|
pxl8_2d_rect_fill(gfx, cx, cy, cell_w, cell_h, colors[idx]);
|
||||||
|
if (idx == selected) {
|
||||||
|
u8 sel = pxl8_gui_color(gfx, PXL8_UI_FG0);
|
||||||
|
pxl8_2d_rect(gfx, cx - 1, cy - 1, cell_w + 2, cell_h + 2, sel);
|
||||||
|
} else if (over) {
|
||||||
|
u8 hov = pxl8_gui_color(gfx, PXL8_UI_BG3);
|
||||||
|
pxl8_2d_rect(gfx, cx, cy, cell_w, cell_h, hov);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pxl8_gui_image(pxl8_gfx* gfx, u32 texture_id, i32 sx, i32 sy, i32 sw, i32 sh,
|
||||||
|
i32 dx, i32 dy, i32 dw, i32 dh) {
|
||||||
|
pxl8_2d_sprite(gfx, texture_id, dx, dy, dw, dh, false, false);
|
||||||
|
(void)sx; (void)sy; (void)sw; (void)sh;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pxl8_gui_panel(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h) {
|
||||||
|
u8 bg = pxl8_gui_color(gfx, PXL8_UI_BG1);
|
||||||
|
u8 border = pxl8_gui_color(gfx, PXL8_UI_BG3);
|
||||||
|
pxl8_2d_rect_fill(gfx, x, y, w, h, bg);
|
||||||
|
pxl8_2d_rect(gfx, x, y, w, h, border);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pxl8_gui_status_bar(pxl8_gfx* gfx, i32 y, i32 screen_w, i32 h, const char* text) {
|
||||||
|
u8 bg = pxl8_gui_color(gfx, PXL8_UI_BG1);
|
||||||
|
u8 fg = pxl8_gui_color(gfx, PXL8_UI_FG0);
|
||||||
|
pxl8_2d_rect_fill(gfx, 0, y, screen_w, h, bg);
|
||||||
|
pxl8_2d_text(gfx, text, 4, y + (h / 2) - 5, fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pxl8_gui_toggle(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id,
|
||||||
|
i32 x, i32 y, i32 w, i32 h, const char* label, bool active) {
|
||||||
|
bool clicked = pxl8_gui_button(state, gfx, id, x, y, w, h, label);
|
||||||
|
if (active) {
|
||||||
|
u8 sel = pxl8_gui_color(gfx, PXL8_UI_FG0);
|
||||||
|
pxl8_2d_rect(gfx, x, y, w, 2, sel);
|
||||||
|
pxl8_2d_rect(gfx, x, y + h - 2, w, 2, sel);
|
||||||
|
}
|
||||||
|
return clicked ? !active : active;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,25 @@ u8 pxl8_gui_color(pxl8_gfx* gfx, u8 index);
|
||||||
void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color);
|
void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color);
|
||||||
bool pxl8_gui_slider(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, f32* value, f32 min_val, f32 max_val);
|
bool pxl8_gui_slider(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, f32* value, f32 min_val, f32 max_val);
|
||||||
bool pxl8_gui_slider_int(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, i32* value, i32 min_val, i32 max_val);
|
bool pxl8_gui_slider_int(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, i32* value, i32 min_val, i32 max_val);
|
||||||
|
i32 pxl8_gui_toolbar(pxl8_gui_state* state, pxl8_gfx* gfx, u32 base_id,
|
||||||
|
i32 x, i32 y, i32 btn_w, i32 btn_h,
|
||||||
|
const char** labels, i32 count, i32 selected);
|
||||||
void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title);
|
void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title);
|
||||||
|
|
||||||
|
i32 pxl8_gui_grid_select(pxl8_gui_state* state, pxl8_gfx* gfx, u32 base_id,
|
||||||
|
i32 x, i32 y, i32 cell_w, i32 cell_h,
|
||||||
|
i32 cols, i32 rows, const u8* colors, i32 selected);
|
||||||
|
|
||||||
|
void pxl8_gui_image(pxl8_gfx* gfx, u32 texture_id, i32 sx, i32 sy, i32 sw, i32 sh,
|
||||||
|
i32 dx, i32 dy, i32 dw, i32 dh);
|
||||||
|
|
||||||
|
void pxl8_gui_panel(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h);
|
||||||
|
|
||||||
|
void pxl8_gui_status_bar(pxl8_gfx* gfx, i32 y, i32 screen_w, i32 h, const char* text);
|
||||||
|
|
||||||
|
bool pxl8_gui_toggle(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id,
|
||||||
|
i32 x, i32 y, i32 w, i32 h, const char* label, bool active);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -125,16 +125,9 @@ static void sdl3_upload_texture(void* platform_data, const void* pixels, u32 w,
|
||||||
ctx->rgba_buffer_size = pixel_count;
|
ctx->rgba_buffer_size = pixel_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bpp == 2) {
|
const u8* pixels8 = (const u8*)pixels;
|
||||||
const u16* pixels16 = (const u16*)pixels;
|
for (u32 i = 0; i < pixel_count; i++) {
|
||||||
for (u32 i = 0; i < pixel_count; i++) {
|
ctx->rgba_buffer[i] = palette[pixels8[i]];
|
||||||
ctx->rgba_buffer[i] = pxl8_rgb565_to_rgba32(pixels16[i]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const u8* pixels8 = (const u8*)pixels;
|
|
||||||
for (u32 i = 0; i < pixel_count; i++) {
|
|
||||||
ctx->rgba_buffer[i] = palette[pixels8[i]];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_UpdateTexture(ctx->framebuffer, NULL, ctx->rgba_buffer, w * 4);
|
SDL_UpdateTexture(ctx->framebuffer, NULL, ctx->rgba_buffer, w * 4);
|
||||||
|
|
|
||||||
125
src/hal/pxl8_io_sdl3.c
Normal file
125
src/hal/pxl8_io_sdl3.c
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
#include "pxl8_io.h"
|
||||||
|
#include "pxl8_mem.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#else
|
||||||
|
#include <direct.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
pxl8_result pxl8_io_create_directory(const char* path) {
|
||||||
|
if (!path) return PXL8_ERROR_NULL_POINTER;
|
||||||
|
return SDL_CreateDirectory(path) ? PXL8_OK : PXL8_ERROR_SYSTEM_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
pxl8_io_dir_callback callback;
|
||||||
|
void* userdata;
|
||||||
|
} pxl8_io_enum_ctx;
|
||||||
|
|
||||||
|
static SDL_EnumerationResult pxl8_io_enum_adapter(void* userdata, const char* dirname, const char* fname) {
|
||||||
|
pxl8_io_enum_ctx* ctx = userdata;
|
||||||
|
if (ctx->callback(ctx->userdata, dirname, fname)) {
|
||||||
|
return SDL_ENUM_CONTINUE;
|
||||||
|
}
|
||||||
|
return SDL_ENUM_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pxl8_io_enumerate_directory(const char* path, pxl8_io_dir_callback callback, void* userdata) {
|
||||||
|
if (!path || !callback) return false;
|
||||||
|
pxl8_io_enum_ctx ctx = { .callback = callback, .userdata = userdata };
|
||||||
|
return SDL_EnumerateDirectory(path, pxl8_io_enum_adapter, &ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pxl8_io_file_exists(const char* path) {
|
||||||
|
if (!path) return false;
|
||||||
|
return SDL_GetPathInfo(path, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
char* pxl8_io_get_cwd(void) {
|
||||||
|
char* sdl_cwd = SDL_GetCurrentDirectory();
|
||||||
|
if (!sdl_cwd) return NULL;
|
||||||
|
char* cwd = pxl8_malloc(strlen(sdl_cwd) + 1);
|
||||||
|
if (cwd) strcpy(cwd, sdl_cwd);
|
||||||
|
SDL_free(sdl_cwd);
|
||||||
|
return cwd;
|
||||||
|
}
|
||||||
|
|
||||||
|
f64 pxl8_io_get_file_modified_time(const char* path) {
|
||||||
|
if (!path) return 0.0;
|
||||||
|
SDL_PathInfo info;
|
||||||
|
if (SDL_GetPathInfo(path, &info)) {
|
||||||
|
return (f64)info.modify_time / 1000000000.0;
|
||||||
|
}
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
usize pxl8_io_get_file_size(const char* path) {
|
||||||
|
if (!path) return 0;
|
||||||
|
SDL_PathInfo info;
|
||||||
|
if (SDL_GetPathInfo(path, &info)) return (usize)info.size;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* pxl8_io_get_pref_path(const char* org, const char* app) {
|
||||||
|
char* sdl_path = SDL_GetPrefPath(org, app);
|
||||||
|
if (!sdl_path) return NULL;
|
||||||
|
char* path = pxl8_malloc(strlen(sdl_path) + 1);
|
||||||
|
if (path) strcpy(path, sdl_path);
|
||||||
|
SDL_free(sdl_path);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* pxl8_io_get_real_path(const char* path) {
|
||||||
|
if (!path) return NULL;
|
||||||
|
|
||||||
|
if (path[0] == '/') {
|
||||||
|
char* result = pxl8_malloc(strlen(path) + 1);
|
||||||
|
if (result) strcpy(result, path);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* cwd = SDL_GetCurrentDirectory();
|
||||||
|
if (!cwd) return NULL;
|
||||||
|
|
||||||
|
usize cwd_len = strlen(cwd);
|
||||||
|
usize path_len = strlen(path);
|
||||||
|
char* result = pxl8_malloc(cwd_len + path_len + 2);
|
||||||
|
if (result) {
|
||||||
|
strcpy(result, cwd);
|
||||||
|
if (cwd_len > 0 && cwd[cwd_len - 1] != '/') {
|
||||||
|
strcat(result, "/");
|
||||||
|
}
|
||||||
|
strcat(result, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_free(cwd);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pxl8_io_set_cwd(const char* path) {
|
||||||
|
if (!path) return false;
|
||||||
|
#ifdef _WIN32
|
||||||
|
return _chdir(path) == 0;
|
||||||
|
#else
|
||||||
|
return chdir(path) == 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void pxl8_io_set_executable(const char* path) {
|
||||||
|
if (!path) return;
|
||||||
|
#ifndef _WIN32
|
||||||
|
chmod(path, 0755);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pxl8_io_is_directory(const char* path) {
|
||||||
|
if (!path) return false;
|
||||||
|
SDL_PathInfo info;
|
||||||
|
return SDL_GetPathInfo(path, &info) && info.type == SDL_PATHTYPE_DIRECTORY;
|
||||||
|
}
|
||||||
|
|
@ -94,6 +94,7 @@ pxl8.Mesh = gfx.Mesh
|
||||||
pxl8.begin_frame_3d = gfx.begin_frame_3d
|
pxl8.begin_frame_3d = gfx.begin_frame_3d
|
||||||
pxl8.clear_3d = gfx.clear_3d
|
pxl8.clear_3d = gfx.clear_3d
|
||||||
pxl8.clear_depth = gfx.clear_depth
|
pxl8.clear_depth = gfx.clear_depth
|
||||||
|
pxl8.clear_stencil = gfx.clear_stencil
|
||||||
pxl8.create_camera_3d = gfx.Camera3D.new
|
pxl8.create_camera_3d = gfx.Camera3D.new
|
||||||
pxl8.create_material = gfx.create_material
|
pxl8.create_material = gfx.create_material
|
||||||
pxl8.create_mesh = gfx.Mesh.new
|
pxl8.create_mesh = gfx.Mesh.new
|
||||||
|
|
@ -174,8 +175,6 @@ pxl8.create_particles = particles.Particles.new
|
||||||
|
|
||||||
pxl8.Graph = procgen.Graph
|
pxl8.Graph = procgen.Graph
|
||||||
pxl8.create_graph = procgen.create_graph
|
pxl8.create_graph = procgen.create_graph
|
||||||
pxl8.PROCGEN_ROOMS = world.PROCGEN_ROOMS
|
|
||||||
pxl8.PROCGEN_TERRAIN = world.PROCGEN_TERRAIN
|
|
||||||
|
|
||||||
pxl8.SfxContext = sfx.SfxContext
|
pxl8.SfxContext = sfx.SfxContext
|
||||||
pxl8.SfxNode = sfx.SfxNode
|
pxl8.SfxNode = sfx.SfxNode
|
||||||
|
|
@ -234,9 +233,26 @@ pxl8.unpack_u32_le = bytes.unpack_u32_le
|
||||||
pxl8.unpack_u64_be = bytes.unpack_u64_be
|
pxl8.unpack_u64_be = bytes.unpack_u64_be
|
||||||
pxl8.unpack_u64_le = bytes.unpack_u64_le
|
pxl8.unpack_u64_le = bytes.unpack_u64_le
|
||||||
|
|
||||||
|
pxl8.DEPTH_NEVER = 0
|
||||||
|
pxl8.DEPTH_LESS = 1
|
||||||
|
pxl8.DEPTH_EQUAL = 2
|
||||||
|
pxl8.DEPTH_LEQUAL = 3
|
||||||
|
pxl8.DEPTH_GREATER = 4
|
||||||
|
pxl8.DEPTH_NOTEQUAL = 5
|
||||||
|
pxl8.DEPTH_GEQUAL = 6
|
||||||
|
pxl8.DEPTH_ALWAYS = 7
|
||||||
|
|
||||||
|
pxl8.STENCIL_NEVER = 0
|
||||||
|
pxl8.STENCIL_LESS = 1
|
||||||
|
pxl8.STENCIL_EQUAL = 2
|
||||||
|
pxl8.STENCIL_LEQUAL = 3
|
||||||
|
pxl8.STENCIL_GREATER = 4
|
||||||
|
pxl8.STENCIL_NOTEQUAL = 5
|
||||||
|
pxl8.STENCIL_GEQUAL = 6
|
||||||
|
pxl8.STENCIL_ALWAYS = 7
|
||||||
|
|
||||||
pxl8.Bsp = world.Bsp
|
pxl8.Bsp = world.Bsp
|
||||||
pxl8.Chunk = world.Chunk
|
pxl8.Chunk = world.Chunk
|
||||||
pxl8.CHUNK_BSP = world.CHUNK_BSP
|
|
||||||
pxl8.World = world.World
|
pxl8.World = world.World
|
||||||
pxl8.get_world = world.World.get
|
pxl8.get_world = world.World.get
|
||||||
pxl8.sim_config = world.sim_config
|
pxl8.sim_config = world.sim_config
|
||||||
|
|
|
||||||
|
|
@ -230,6 +230,28 @@ gfx.Camera3D = Camera3D
|
||||||
|
|
||||||
gfx.Mesh = Mesh
|
gfx.Mesh = Mesh
|
||||||
|
|
||||||
|
local Material = {}
|
||||||
|
Material.__index = Material
|
||||||
|
|
||||||
|
function Material.new(opts)
|
||||||
|
opts = opts or {}
|
||||||
|
local mat = ffi.new("pxl8_gfx_material", {
|
||||||
|
alpha = opts.alpha or 255,
|
||||||
|
blend_mode = opts.blend_mode or 0,
|
||||||
|
dither = opts.dither ~= false,
|
||||||
|
double_sided = opts.double_sided or false,
|
||||||
|
dynamic_lighting = opts.lighting or false,
|
||||||
|
emissive = opts.emissive or false,
|
||||||
|
per_pixel = opts.per_pixel or false,
|
||||||
|
texture_id = opts.texture or 0xFFFFFFFF,
|
||||||
|
})
|
||||||
|
return setmetatable({ _ptr = mat }, Material)
|
||||||
|
end
|
||||||
|
|
||||||
|
gfx.Material = Material
|
||||||
|
|
||||||
|
gfx.create_material = Material.new
|
||||||
|
|
||||||
function gfx.draw_mesh(mesh, opts)
|
function gfx.draw_mesh(mesh, opts)
|
||||||
if not mesh or not mesh._ptr then
|
if not mesh or not mesh._ptr then
|
||||||
return
|
return
|
||||||
|
|
@ -249,17 +271,21 @@ function gfx.draw_mesh(mesh, opts)
|
||||||
if opts.y then model.m[13] = opts.y end
|
if opts.y then model.m[13] = opts.y end
|
||||||
if opts.z then model.m[14] = opts.z end
|
if opts.z then model.m[14] = opts.z end
|
||||||
end
|
end
|
||||||
local material = ffi.new("pxl8_gfx_material", {
|
local material = Material.new(opts)._ptr
|
||||||
alpha = opts.alpha or 255,
|
local draw_opts = nil
|
||||||
blend_mode = opts.blend_mode or 0,
|
if opts.color_write ~= nil or opts.depth_compare or opts.depth_write ~= nil
|
||||||
dither = opts.dither ~= false,
|
or opts.stencil_test or opts.stencil_write then
|
||||||
double_sided = opts.double_sided or false,
|
draw_opts = ffi.new("pxl8_gfx_draw_opts", {
|
||||||
dynamic_lighting = opts.lighting or false,
|
color_write = opts.color_write ~= false,
|
||||||
emissive = opts.emissive or false,
|
depth_compare = opts.depth_compare or 1,
|
||||||
per_pixel = opts.per_pixel or false,
|
depth_write = opts.depth_write ~= false,
|
||||||
texture_id = opts.texture or 0xFFFFFFFF,
|
stencil_test = opts.stencil_test or false,
|
||||||
})
|
stencil_write = opts.stencil_write or false,
|
||||||
C.pxl8_3d_draw_mesh(core.gfx, mesh._ptr, model, material)
|
stencil_compare = opts.stencil_compare or 0,
|
||||||
|
stencil_ref = opts.stencil_ref or 0,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
C.pxl8_3d_draw_mesh(core.gfx, mesh._ptr, model, material, draw_opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
function gfx.begin_frame_3d(camera, lights, uniforms)
|
function gfx.begin_frame_3d(camera, lights, uniforms)
|
||||||
|
|
@ -301,6 +327,10 @@ function gfx.clear_depth()
|
||||||
C.pxl8_3d_clear_depth(core.gfx)
|
C.pxl8_3d_clear_depth(core.gfx)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function gfx.clear_stencil(value)
|
||||||
|
C.pxl8_3d_clear_stencil(core.gfx, value or 0)
|
||||||
|
end
|
||||||
|
|
||||||
function gfx.draw_line_3d(p0, p1, color)
|
function gfx.draw_line_3d(p0, p1, color)
|
||||||
local vec0 = ffi.new("pxl8_vec3", {x = p0[1], y = p0[2], z = p0[3]})
|
local vec0 = ffi.new("pxl8_vec3", {x = p0[1], y = p0[2], z = p0[3]})
|
||||||
local vec1 = ffi.new("pxl8_vec3", {x = p1[1], y = p1[2], z = p1[3]})
|
local vec1 = ffi.new("pxl8_vec3", {x = p1[1], y = p1[2], z = p1[3]})
|
||||||
|
|
@ -327,26 +357,4 @@ function gfx.get_wireframe()
|
||||||
return C.pxl8_gfx_get_wireframe(core.gfx)
|
return C.pxl8_gfx_get_wireframe(core.gfx)
|
||||||
end
|
end
|
||||||
|
|
||||||
local Material = {}
|
|
||||||
Material.__index = Material
|
|
||||||
|
|
||||||
function Material.new(opts)
|
|
||||||
opts = opts or {}
|
|
||||||
local mat = ffi.new("pxl8_gfx_material", {
|
|
||||||
alpha = opts.alpha or 255,
|
|
||||||
blend_mode = opts.blend_mode or 0,
|
|
||||||
dither = opts.dither ~= false,
|
|
||||||
double_sided = opts.double_sided or false,
|
|
||||||
dynamic_lighting = opts.lighting or false,
|
|
||||||
emissive = opts.emissive or false,
|
|
||||||
per_pixel = opts.per_pixel or false,
|
|
||||||
texture_id = opts.texture or 0xFFFFFFFF,
|
|
||||||
})
|
|
||||||
return setmetatable({ _ptr = mat }, Material)
|
|
||||||
end
|
|
||||||
|
|
||||||
gfx.Material = Material
|
|
||||||
|
|
||||||
gfx.create_material = Material.new
|
|
||||||
|
|
||||||
return gfx
|
return gfx
|
||||||
|
|
|
||||||
|
|
@ -15,18 +15,22 @@ function net.get()
|
||||||
return setmetatable({ _ptr = ptr }, Net)
|
return setmetatable({ _ptr = ptr }, Net)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Net:chunk_id()
|
function Net:chunk_cx()
|
||||||
return C.pxl8_net_chunk_id(self._ptr)
|
return C.pxl8_net_chunk_cx(self._ptr)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Net:chunk_cz()
|
||||||
|
return C.pxl8_net_chunk_cz(self._ptr)
|
||||||
|
end
|
||||||
|
|
||||||
|
function Net:has_chunk()
|
||||||
|
return C.pxl8_net_has_chunk(self._ptr)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Net:connected()
|
function Net:connected()
|
||||||
return C.pxl8_net_connected(self._ptr)
|
return C.pxl8_net_connected(self._ptr)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Net:enter_scene(chunk_type, chunk_id, x, y, z)
|
|
||||||
return C.pxl8_net_enter_scene(self._ptr, chunk_type or 0, chunk_id or 0, x or 0, y or 0, z or 0) == 0
|
|
||||||
end
|
|
||||||
|
|
||||||
function Net:send_input(input)
|
function Net:send_input(input)
|
||||||
local msg = ffi.new("pxl8_input_msg")
|
local msg = ffi.new("pxl8_input_msg")
|
||||||
msg.buttons = input.buttons or 0
|
msg.buttons = input.buttons or 0
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,10 @@ function World:init_local_player(x, y, z)
|
||||||
C.pxl8_world_init_local_player(self._ptr, x, y, z)
|
C.pxl8_world_init_local_player(self._ptr, x, y, z)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function World:set_look(yaw, pitch)
|
||||||
|
C.pxl8_world_set_look(self._ptr, yaw, pitch or 0)
|
||||||
|
end
|
||||||
|
|
||||||
function World:local_player()
|
function World:local_player()
|
||||||
local ptr = C.pxl8_world_local_player(self._ptr)
|
local ptr = C.pxl8_world_local_player(self._ptr)
|
||||||
if ptr == nil then return nil end
|
if ptr == nil then return nil end
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,10 @@ typedef struct pxl8_plane {
|
||||||
pxl8_vec3 normal;
|
pxl8_vec3 normal;
|
||||||
} pxl8_plane;
|
} pxl8_plane;
|
||||||
|
|
||||||
|
typedef struct pxl8_rect {
|
||||||
|
f32 x0, y0, x1, y1;
|
||||||
|
} pxl8_rect;
|
||||||
|
|
||||||
typedef struct pxl8_frustum {
|
typedef struct pxl8_frustum {
|
||||||
pxl8_plane planes[6];
|
pxl8_plane planes[6];
|
||||||
} pxl8_frustum;
|
} pxl8_frustum;
|
||||||
|
|
|
||||||
|
|
@ -46,17 +46,15 @@ struct pxl8_net {
|
||||||
socket_t sock;
|
socket_t sock;
|
||||||
|
|
||||||
pxl8_world_chunk_cache* chunk_cache;
|
pxl8_world_chunk_cache* chunk_cache;
|
||||||
u32 chunk_id;
|
i32 chunk_cx;
|
||||||
u8 chunk_type;
|
i32 chunk_cz;
|
||||||
|
bool has_chunk;
|
||||||
pxl8_world* world;
|
pxl8_world* world;
|
||||||
|
|
||||||
f32 dt;
|
|
||||||
u64 highest_tick;
|
u64 highest_tick;
|
||||||
f32 interp_time;
|
f32 interp_time;
|
||||||
u32 sequence;
|
|
||||||
|
|
||||||
pxl8_entity_state entities[PXL8_MAX_SNAPSHOT_ENTITIES];
|
pxl8_entity_state entities[PXL8_MAX_SNAPSHOT_ENTITIES];
|
||||||
pxl8_event_msg events[PXL8_MAX_SNAPSHOT_EVENTS];
|
|
||||||
pxl8_entity_state prev_entities[PXL8_MAX_SNAPSHOT_ENTITIES];
|
pxl8_entity_state prev_entities[PXL8_MAX_SNAPSHOT_ENTITIES];
|
||||||
pxl8_snapshot_header prev_snapshot;
|
pxl8_snapshot_header prev_snapshot;
|
||||||
pxl8_snapshot_header snapshot;
|
pxl8_snapshot_header snapshot;
|
||||||
|
|
@ -109,6 +107,9 @@ pxl8_result pxl8_net_connect(pxl8_net* net) {
|
||||||
fcntl(net->sock, F_SETFL, flags | O_NONBLOCK);
|
fcntl(net->sock, F_SETFL, flags | O_NONBLOCK);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
int rcvbuf = 1024 * 1024;
|
||||||
|
setsockopt(net->sock, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf));
|
||||||
|
|
||||||
memset(&net->server_addr, 0, sizeof(net->server_addr));
|
memset(&net->server_addr, 0, sizeof(net->server_addr));
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
net->server_addr.sin_len = sizeof(net->server_addr);
|
net->server_addr.sin_len = sizeof(net->server_addr);
|
||||||
|
|
@ -131,9 +132,6 @@ pxl8_net* pxl8_net_create(const pxl8_net_config* config) {
|
||||||
|
|
||||||
net->port = config->port ? config->port : PXL8_NET_DEFAULT_PORT;
|
net->port = config->port ? config->port : PXL8_NET_DEFAULT_PORT;
|
||||||
net->sock = INVALID_SOCK;
|
net->sock = INVALID_SOCK;
|
||||||
net->connected = false;
|
|
||||||
net->sequence = 0;
|
|
||||||
net->highest_tick = 0;
|
|
||||||
|
|
||||||
if (config->address) {
|
if (config->address) {
|
||||||
strncpy(net->address, config->address, sizeof(net->address) - 1);
|
strncpy(net->address, config->address, sizeof(net->address) - 1);
|
||||||
|
|
@ -179,11 +177,6 @@ const u8* pxl8_net_entity_userdata(const pxl8_net* net, u64 entity_id) {
|
||||||
return e ? e->userdata : NULL;
|
return e ? e->userdata : NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pxl8_event_msg* pxl8_net_events(const pxl8_net* net) {
|
|
||||||
if (!net) return NULL;
|
|
||||||
return net->events;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pxl8_input_msg* pxl8_net_input_at(const pxl8_net* net, u64 tick) {
|
const pxl8_input_msg* pxl8_net_input_at(const pxl8_net* net, u64 tick) {
|
||||||
if (!net) return NULL;
|
if (!net) return NULL;
|
||||||
for (u64 i = 0; i < PXL8_NET_INPUT_HISTORY_SIZE; i++) {
|
for (u64 i = 0; i < PXL8_NET_INPUT_HISTORY_SIZE; i++) {
|
||||||
|
|
@ -231,22 +224,19 @@ u64 pxl8_net_player_id(const pxl8_net* net) {
|
||||||
return net->snapshot.player_id;
|
return net->snapshot.player_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool pxl8_net_poll(pxl8_net* net) {
|
static bool dispatch_message(pxl8_net* net, const u8* data, usize len) {
|
||||||
if (!net || !net->connected) return false;
|
|
||||||
|
|
||||||
usize len = pxl8_net_recv(net, net->recv_buf, sizeof(net->recv_buf));
|
|
||||||
if (len < sizeof(pxl8_msg_header)) return false;
|
if (len < sizeof(pxl8_msg_header)) return false;
|
||||||
|
|
||||||
pxl8_msg_header hdr;
|
pxl8_msg_header hdr;
|
||||||
usize offset = pxl8_protocol_deserialize_header(net->recv_buf, len, &hdr);
|
usize offset = pxl8_protocol_deserialize_header(data, len, &hdr);
|
||||||
|
|
||||||
if (hdr.type == PXL8_MSG_CHUNK) {
|
if (hdr.type == PXL8_MSG_CHUNK) {
|
||||||
if (!net->chunk_cache) return false;
|
if (!net->chunk_cache) return false;
|
||||||
|
|
||||||
pxl8_chunk_msg_header chunk_hdr;
|
pxl8_chunk_msg_header chunk_hdr;
|
||||||
offset += pxl8_protocol_deserialize_chunk_msg_header(net->recv_buf + offset, len - offset, &chunk_hdr);
|
offset += pxl8_protocol_deserialize_chunk_msg_header(data + offset, len - offset, &chunk_hdr);
|
||||||
|
|
||||||
const u8* payload = net->recv_buf + offset;
|
const u8* payload = data + offset;
|
||||||
usize payload_len = chunk_hdr.payload_size;
|
usize payload_len = chunk_hdr.payload_size;
|
||||||
if (payload_len > len - offset) {
|
if (payload_len > len - offset) {
|
||||||
payload_len = len - offset;
|
payload_len = len - offset;
|
||||||
|
|
@ -258,23 +248,23 @@ bool pxl8_net_poll(pxl8_net* net) {
|
||||||
|
|
||||||
if (hdr.type == PXL8_MSG_CHUNK_ENTER) {
|
if (hdr.type == PXL8_MSG_CHUNK_ENTER) {
|
||||||
pxl8_chunk_enter_msg chunk_msg;
|
pxl8_chunk_enter_msg chunk_msg;
|
||||||
pxl8_protocol_deserialize_chunk_enter(net->recv_buf + offset, len - offset, &chunk_msg);
|
pxl8_protocol_deserialize_chunk_enter(data + offset, len - offset, &chunk_msg);
|
||||||
net->chunk_id = chunk_msg.chunk_id;
|
net->chunk_cx = chunk_msg.cx;
|
||||||
net->chunk_type = chunk_msg.chunk_type;
|
net->chunk_cz = chunk_msg.cz;
|
||||||
pxl8_debug("[CLIENT] Received CHUNK_ENTER type=%u id=%u", chunk_msg.chunk_type, chunk_msg.chunk_id);
|
net->has_chunk = true;
|
||||||
|
pxl8_debug("[CLIENT] Received CHUNK_ENTER cx=%d cz=%d", chunk_msg.cx, chunk_msg.cz);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hdr.type == PXL8_MSG_CHUNK_EXIT) {
|
if (hdr.type == PXL8_MSG_CHUNK_EXIT) {
|
||||||
net->chunk_id = 0;
|
net->has_chunk = false;
|
||||||
net->chunk_type = 0;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hdr.type != PXL8_MSG_SNAPSHOT) return false;
|
if (hdr.type != PXL8_MSG_SNAPSHOT) return false;
|
||||||
|
|
||||||
pxl8_snapshot_header snap;
|
pxl8_snapshot_header snap;
|
||||||
offset += pxl8_protocol_deserialize_snapshot_header(net->recv_buf + offset, len - offset, &snap);
|
offset += pxl8_protocol_deserialize_snapshot_header(data + offset, len - offset, &snap);
|
||||||
|
|
||||||
if (snap.tick <= net->highest_tick) return false;
|
if (snap.tick <= net->highest_tick) return false;
|
||||||
|
|
||||||
|
|
@ -290,10 +280,20 @@ bool pxl8_net_poll(pxl8_net* net) {
|
||||||
|
|
||||||
for (u16 i = 0; i < count; i++) {
|
for (u16 i = 0; i < count; i++) {
|
||||||
offset += pxl8_protocol_deserialize_entity_state(
|
offset += pxl8_protocol_deserialize_entity_state(
|
||||||
net->recv_buf + offset, len - offset, &net->entities[i]);
|
data + offset, len - offset, &net->entities[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (net->world) {
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pxl8_net_poll(pxl8_net* net) {
|
||||||
|
if (!net || !net->connected) return false;
|
||||||
|
|
||||||
|
usize len = pxl8_net_recv(net, net->recv_buf, sizeof(net->recv_buf));
|
||||||
|
u64 prev_tick = net->highest_tick;
|
||||||
|
if (!dispatch_message(net, net->recv_buf, len)) return false;
|
||||||
|
|
||||||
|
if (net->highest_tick > prev_tick && net->world) {
|
||||||
pxl8_world_reconcile(net->world, net, 1.0f / 30.0f);
|
pxl8_world_reconcile(net->world, net, 1.0f / 30.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -377,7 +377,6 @@ u64 pxl8_net_tick(const pxl8_net* net) {
|
||||||
|
|
||||||
void pxl8_net_update(pxl8_net* net, f32 dt) {
|
void pxl8_net_update(pxl8_net* net, f32 dt) {
|
||||||
if (!net) return;
|
if (!net) return;
|
||||||
net->dt = dt;
|
|
||||||
net->interp_time += dt;
|
net->interp_time += dt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -396,14 +395,19 @@ void pxl8_net_set_world(pxl8_net* net, pxl8_world* world) {
|
||||||
net->world = world;
|
net->world = world;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 pxl8_net_chunk_id(const pxl8_net* net) {
|
i32 pxl8_net_chunk_cx(const pxl8_net* net) {
|
||||||
if (!net) return 0;
|
if (!net) return 0;
|
||||||
return net->chunk_id;
|
return net->chunk_cx;
|
||||||
}
|
}
|
||||||
|
|
||||||
u8 pxl8_net_chunk_type(const pxl8_net* net) {
|
i32 pxl8_net_chunk_cz(const pxl8_net* net) {
|
||||||
if (!net) return PXL8_CHUNK_TYPE_BSP;
|
if (!net) return 0;
|
||||||
return net->chunk_type;
|
return net->chunk_cz;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pxl8_net_has_chunk(const pxl8_net* net) {
|
||||||
|
if (!net) return false;
|
||||||
|
return net->has_chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_result pxl8_net_spawn(pxl8_net* net, f32 x, f32 y, f32 z, f32 yaw, f32 pitch) {
|
pxl8_result pxl8_net_spawn(pxl8_net* net, f32 x, f32 y, f32 z, f32 yaw, f32 pitch) {
|
||||||
|
|
@ -422,22 +426,6 @@ pxl8_result pxl8_net_spawn(pxl8_net* net, f32 x, f32 y, f32 z, f32 yaw, f32 pitc
|
||||||
return pxl8_net_send_command(net, &cmd);
|
return pxl8_net_send_command(net, &cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_result pxl8_net_enter_scene(pxl8_net* net, u8 chunk_type, u32 chunk_id, f32 x, f32 y, f32 z) {
|
|
||||||
if (!net) return PXL8_ERROR_NULL_POINTER;
|
|
||||||
if (!net->connected) return PXL8_ERROR_NOT_CONNECTED;
|
|
||||||
|
|
||||||
pxl8_command_msg cmd = {0};
|
|
||||||
cmd.cmd_type = PXL8_CMD_ENTER_SCENE;
|
|
||||||
pxl8_pack_u32_be(cmd.payload, 0, chunk_id);
|
|
||||||
cmd.payload[4] = chunk_type;
|
|
||||||
pxl8_pack_f32_be(cmd.payload, 8, x);
|
|
||||||
pxl8_pack_f32_be(cmd.payload, 12, y);
|
|
||||||
pxl8_pack_f32_be(cmd.payload, 16, z);
|
|
||||||
cmd.payload_size = 20;
|
|
||||||
|
|
||||||
return pxl8_net_send_command(net, &cmd);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef PXL8_ASYNC_THREADS
|
#ifdef PXL8_ASYNC_THREADS
|
||||||
|
|
||||||
static int pxl8_net_recv_thread(void* data) {
|
static int pxl8_net_recv_thread(void* data) {
|
||||||
|
|
@ -504,64 +492,8 @@ void pxl8_net_packet_free(pxl8_packet* pkt) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool pxl8_net_process_packet(pxl8_net* net, const pxl8_packet* pkt) {
|
bool pxl8_net_process_packet(pxl8_net* net, const pxl8_packet* pkt) {
|
||||||
if (!net || !pkt || pkt->len < sizeof(pxl8_msg_header)) return false;
|
if (!net || !pkt) return false;
|
||||||
|
return dispatch_message(net, pkt->data, pkt->len);
|
||||||
pxl8_msg_header hdr;
|
|
||||||
usize offset = pxl8_protocol_deserialize_header(pkt->data, pkt->len, &hdr);
|
|
||||||
|
|
||||||
if (hdr.type == PXL8_MSG_CHUNK) {
|
|
||||||
if (!net->chunk_cache) return false;
|
|
||||||
|
|
||||||
pxl8_chunk_msg_header chunk_hdr;
|
|
||||||
offset += pxl8_protocol_deserialize_chunk_msg_header(pkt->data + offset, pkt->len - offset, &chunk_hdr);
|
|
||||||
|
|
||||||
const u8* payload = pkt->data + offset;
|
|
||||||
usize payload_len = chunk_hdr.payload_size;
|
|
||||||
if (payload_len > pkt->len - offset) {
|
|
||||||
payload_len = pkt->len - offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
pxl8_world_chunk_cache_receive(net->chunk_cache, &chunk_hdr, payload, payload_len);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hdr.type == PXL8_MSG_CHUNK_ENTER) {
|
|
||||||
pxl8_chunk_enter_msg chunk_msg;
|
|
||||||
pxl8_protocol_deserialize_chunk_enter(pkt->data + offset, pkt->len - offset, &chunk_msg);
|
|
||||||
net->chunk_id = chunk_msg.chunk_id;
|
|
||||||
net->chunk_type = chunk_msg.chunk_type;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hdr.type == PXL8_MSG_CHUNK_EXIT) {
|
|
||||||
net->chunk_id = 0;
|
|
||||||
net->chunk_type = 0;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hdr.type != PXL8_MSG_SNAPSHOT) return false;
|
|
||||||
|
|
||||||
pxl8_snapshot_header snap;
|
|
||||||
offset += pxl8_protocol_deserialize_snapshot_header(pkt->data + offset, pkt->len - offset, &snap);
|
|
||||||
|
|
||||||
if (snap.tick <= net->highest_tick) return false;
|
|
||||||
|
|
||||||
memcpy(net->prev_entities, net->entities, sizeof(net->entities));
|
|
||||||
net->prev_snapshot = net->snapshot;
|
|
||||||
|
|
||||||
net->highest_tick = snap.tick;
|
|
||||||
net->snapshot = snap;
|
|
||||||
net->interp_time = 0.0f;
|
|
||||||
|
|
||||||
u16 count = snap.entity_count;
|
|
||||||
if (count > PXL8_MAX_SNAPSHOT_ENTITIES) count = PXL8_MAX_SNAPSHOT_ENTITIES;
|
|
||||||
|
|
||||||
for (u16 i = 0; i < count; i++) {
|
|
||||||
offset += pxl8_protocol_deserialize_entity_state(
|
|
||||||
pkt->data + offset, pkt->len - offset, &net->entities[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ void pxl8_net_disconnect(pxl8_net* net);
|
||||||
const pxl8_entity_state* pxl8_net_entities(const pxl8_net* net);
|
const pxl8_entity_state* pxl8_net_entities(const pxl8_net* net);
|
||||||
const u8* pxl8_net_entity_prev_userdata(const pxl8_net* net, u64 entity_id);
|
const u8* pxl8_net_entity_prev_userdata(const pxl8_net* net, u64 entity_id);
|
||||||
const u8* pxl8_net_entity_userdata(const pxl8_net* net, u64 entity_id);
|
const u8* pxl8_net_entity_userdata(const pxl8_net* net, u64 entity_id);
|
||||||
const pxl8_event_msg* pxl8_net_events(const pxl8_net* net);
|
|
||||||
const pxl8_input_msg* pxl8_net_input_at(const pxl8_net* net, u64 tick);
|
const pxl8_input_msg* pxl8_net_input_at(const pxl8_net* net, u64 tick);
|
||||||
u64 pxl8_net_input_oldest_tick(const pxl8_net* net);
|
u64 pxl8_net_input_oldest_tick(const pxl8_net* net);
|
||||||
void pxl8_net_input_push(pxl8_net* net, const pxl8_input_msg* input);
|
void pxl8_net_input_push(pxl8_net* net, const pxl8_input_msg* input);
|
||||||
|
|
@ -54,11 +53,11 @@ void pxl8_net_set_chunk_cache(pxl8_net* net, pxl8_world_chunk_cache* cache);
|
||||||
pxl8_world_chunk_cache* pxl8_net_chunk_cache(pxl8_net* net);
|
pxl8_world_chunk_cache* pxl8_net_chunk_cache(pxl8_net* net);
|
||||||
void pxl8_net_set_world(pxl8_net* net, pxl8_world* world);
|
void pxl8_net_set_world(pxl8_net* net, pxl8_world* world);
|
||||||
|
|
||||||
u32 pxl8_net_chunk_id(const pxl8_net* net);
|
i32 pxl8_net_chunk_cx(const pxl8_net* net);
|
||||||
u8 pxl8_net_chunk_type(const pxl8_net* net);
|
i32 pxl8_net_chunk_cz(const pxl8_net* net);
|
||||||
|
bool pxl8_net_has_chunk(const pxl8_net* net);
|
||||||
|
|
||||||
pxl8_result pxl8_net_spawn(pxl8_net* net, f32 x, f32 y, f32 z, f32 yaw, f32 pitch);
|
pxl8_result pxl8_net_spawn(pxl8_net* net, f32 x, f32 y, f32 z, f32 yaw, f32 pitch);
|
||||||
pxl8_result pxl8_net_enter_scene(pxl8_net* net, u8 chunk_type, u32 chunk_id, f32 x, f32 y, f32 z);
|
|
||||||
|
|
||||||
#ifdef PXL8_ASYNC_THREADS
|
#ifdef PXL8_ASYNC_THREADS
|
||||||
void pxl8_net_start_thread(pxl8_net* net);
|
void pxl8_net_start_thread(pxl8_net* net);
|
||||||
|
|
|
||||||
|
|
@ -158,7 +158,7 @@ usize pxl8_protocol_deserialize_chunk_msg_header(const u8* buf, usize len, pxl8_
|
||||||
}
|
}
|
||||||
|
|
||||||
usize pxl8_protocol_deserialize_bsp_wire_header(const u8* buf, usize len, pxl8_bsp_wire_header* hdr) {
|
usize pxl8_protocol_deserialize_bsp_wire_header(const u8* buf, usize len, pxl8_bsp_wire_header* hdr) {
|
||||||
if (len < 44) return 0;
|
if (len < 48) return 0;
|
||||||
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
|
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
|
||||||
hdr->num_vertices = pxl8_read_u32_be(&s);
|
hdr->num_vertices = pxl8_read_u32_be(&s);
|
||||||
hdr->num_edges = pxl8_read_u32_be(&s);
|
hdr->num_edges = pxl8_read_u32_be(&s);
|
||||||
|
|
@ -171,27 +171,27 @@ usize pxl8_protocol_deserialize_bsp_wire_header(const u8* buf, usize len, pxl8_b
|
||||||
hdr->num_cell_portals = pxl8_read_u32_be(&s);
|
hdr->num_cell_portals = pxl8_read_u32_be(&s);
|
||||||
hdr->visdata_size = pxl8_read_u32_be(&s);
|
hdr->visdata_size = pxl8_read_u32_be(&s);
|
||||||
hdr->num_vertex_lights = pxl8_read_u32_be(&s);
|
hdr->num_vertex_lights = pxl8_read_u32_be(&s);
|
||||||
|
hdr->num_heightfield = pxl8_read_u32_be(&s);
|
||||||
return s.offset;
|
return s.offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
usize pxl8_protocol_serialize_chunk_enter(const pxl8_chunk_enter_msg* msg, u8* buf, usize len) {
|
usize pxl8_protocol_serialize_chunk_enter(const pxl8_chunk_enter_msg* msg, u8* buf, usize len) {
|
||||||
if (len < 8) return 0;
|
if (len < 8) return 0;
|
||||||
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
|
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
|
||||||
pxl8_write_u32_be(&s, msg->chunk_id);
|
pxl8_write_u32_be(&s, (u32)msg->cx);
|
||||||
pxl8_write_u8(&s, msg->chunk_type);
|
pxl8_write_u32_be(&s, (u32)msg->cz);
|
||||||
pxl8_write_u8(&s, msg->reserved[0]);
|
|
||||||
pxl8_write_u8(&s, msg->reserved[1]);
|
|
||||||
pxl8_write_u8(&s, msg->reserved[2]);
|
|
||||||
return s.offset;
|
return s.offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
usize pxl8_protocol_deserialize_chunk_enter(const u8* buf, usize len, pxl8_chunk_enter_msg* msg) {
|
usize pxl8_protocol_deserialize_chunk_enter(const u8* buf, usize len, pxl8_chunk_enter_msg* msg) {
|
||||||
if (len < 8) return 0;
|
if (len < 8) return 0;
|
||||||
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
|
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
|
||||||
msg->chunk_id = pxl8_read_u32_be(&s);
|
msg->cx = (i32)pxl8_read_u32_be(&s);
|
||||||
msg->chunk_type = pxl8_read_u8(&s);
|
msg->cz = (i32)pxl8_read_u32_be(&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;
|
return s.offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u32 pxl8_chunk_hash(i32 cx, i32 cz) {
|
||||||
|
u32 h = (u32)cx * 374761393u + (u32)cz * 668265263u;
|
||||||
|
return h ^ (h >> 16);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,6 @@ typedef struct pxl8_msg_header {
|
||||||
typedef enum pxl8_cmd_type {
|
typedef enum pxl8_cmd_type {
|
||||||
PXL8_CMD_NONE = 0,
|
PXL8_CMD_NONE = 0,
|
||||||
PXL8_CMD_SPAWN_ENTITY,
|
PXL8_CMD_SPAWN_ENTITY,
|
||||||
PXL8_CMD_ENTER_SCENE,
|
|
||||||
} pxl8_cmd_type;
|
} pxl8_cmd_type;
|
||||||
|
|
||||||
typedef struct pxl8_input_msg {
|
typedef struct pxl8_input_msg {
|
||||||
|
|
@ -76,7 +75,6 @@ typedef struct pxl8_snapshot_header {
|
||||||
|
|
||||||
#define PXL8_CHUNK_TYPE_BSP 1
|
#define PXL8_CHUNK_TYPE_BSP 1
|
||||||
|
|
||||||
#define PXL8_CHUNK_FLAG_RLE 0x01
|
|
||||||
#define PXL8_CHUNK_FLAG_FINAL 0x04
|
#define PXL8_CHUNK_FLAG_FINAL 0x04
|
||||||
#define PXL8_CHUNK_MAX_PAYLOAD 1400
|
#define PXL8_CHUNK_MAX_PAYLOAD 1400
|
||||||
|
|
||||||
|
|
@ -93,17 +91,18 @@ typedef struct pxl8_chunk_msg_header {
|
||||||
} pxl8_chunk_msg_header;
|
} pxl8_chunk_msg_header;
|
||||||
|
|
||||||
typedef struct pxl8_bsp_wire_header {
|
typedef struct pxl8_bsp_wire_header {
|
||||||
u32 num_cell_portals;
|
u32 num_vertices;
|
||||||
u32 num_edges;
|
u32 num_edges;
|
||||||
u32 num_faces;
|
u32 num_faces;
|
||||||
u32 num_leafs;
|
|
||||||
u32 num_marksurfaces;
|
|
||||||
u32 num_nodes;
|
|
||||||
u32 num_planes;
|
u32 num_planes;
|
||||||
|
u32 num_nodes;
|
||||||
|
u32 num_leafs;
|
||||||
u32 num_surfedges;
|
u32 num_surfedges;
|
||||||
u32 num_vertex_lights;
|
u32 num_marksurfaces;
|
||||||
u32 num_vertices;
|
u32 num_cell_portals;
|
||||||
u32 visdata_size;
|
u32 visdata_size;
|
||||||
|
u32 num_vertex_lights;
|
||||||
|
u32 num_heightfield;
|
||||||
} pxl8_bsp_wire_header;
|
} pxl8_bsp_wire_header;
|
||||||
|
|
||||||
usize pxl8_protocol_serialize_header(const pxl8_msg_header* msg, u8* buf, usize len);
|
usize pxl8_protocol_serialize_header(const pxl8_msg_header* msg, u8* buf, usize len);
|
||||||
|
|
@ -130,14 +129,15 @@ usize pxl8_protocol_deserialize_chunk_msg_header(const u8* buf, usize len, pxl8_
|
||||||
usize pxl8_protocol_deserialize_bsp_wire_header(const u8* buf, usize len, pxl8_bsp_wire_header* hdr);
|
usize pxl8_protocol_deserialize_bsp_wire_header(const u8* buf, usize len, pxl8_bsp_wire_header* hdr);
|
||||||
|
|
||||||
typedef struct pxl8_chunk_enter_msg {
|
typedef struct pxl8_chunk_enter_msg {
|
||||||
u32 chunk_id;
|
i32 cx;
|
||||||
u8 chunk_type;
|
i32 cz;
|
||||||
u8 reserved[3];
|
|
||||||
} pxl8_chunk_enter_msg;
|
} pxl8_chunk_enter_msg;
|
||||||
|
|
||||||
usize pxl8_protocol_serialize_chunk_enter(const pxl8_chunk_enter_msg* msg, u8* buf, usize len);
|
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);
|
usize pxl8_protocol_deserialize_chunk_enter(const u8* buf, usize len, pxl8_chunk_enter_msg* msg);
|
||||||
|
|
||||||
|
u32 pxl8_chunk_hash(i32 cx, i32 cz);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,13 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include <lauxlib.h>
|
#include <lauxlib.h>
|
||||||
#include <lua.h>
|
#include <lua.h>
|
||||||
#include <lualib.h>
|
#include <lualib.h>
|
||||||
|
|
||||||
#include "pxl8_cart.h"
|
#include "pxl8_cart.h"
|
||||||
#include "pxl8_embed.h"
|
#include "pxl8_embed.h"
|
||||||
|
#include "pxl8_io.h"
|
||||||
#include "pxl8_gui.h"
|
#include "pxl8_gui.h"
|
||||||
#include "pxl8_log.h"
|
#include "pxl8_log.h"
|
||||||
#include "pxl8_macros.h"
|
#include "pxl8_macros.h"
|
||||||
|
|
@ -28,7 +25,7 @@ struct pxl8_script {
|
||||||
char last_error[PXL8_MAX_ERROR_SIZE];
|
char last_error[PXL8_MAX_ERROR_SIZE];
|
||||||
char main_path[PXL8_MAX_PATH];
|
char main_path[PXL8_MAX_PATH];
|
||||||
char watch_dir[PXL8_MAX_PATH];
|
char watch_dir[PXL8_MAX_PATH];
|
||||||
time_t latest_mod_time;
|
f64 latest_mod_time;
|
||||||
int repl_env_ref;
|
int repl_env_ref;
|
||||||
bool repl_mode;
|
bool repl_mode;
|
||||||
};
|
};
|
||||||
|
|
@ -407,8 +404,12 @@ static pxl8_result pxl8_script_prepare_path(pxl8_script* script, const char* fil
|
||||||
char script_dir[PATH_MAX];
|
char script_dir[PATH_MAX];
|
||||||
char original_cwd[PATH_MAX];
|
char original_cwd[PATH_MAX];
|
||||||
|
|
||||||
if (realpath(filename_copy, script_dir) && getcwd(original_cwd, sizeof(original_cwd))) {
|
char* resolved = pxl8_io_get_real_path(filename_copy);
|
||||||
chdir(script_dir);
|
char* cwd = pxl8_io_get_cwd();
|
||||||
|
if (resolved && cwd) {
|
||||||
|
pxl8_strncpy(script_dir, resolved, sizeof(script_dir));
|
||||||
|
pxl8_strncpy(original_cwd, cwd, sizeof(original_cwd));
|
||||||
|
pxl8_io_set_cwd(script_dir);
|
||||||
pxl8_script_set_cart_path(script, script_dir, original_cwd);
|
pxl8_script_set_cart_path(script, script_dir, original_cwd);
|
||||||
strncpy(out_basename, last_slash + 1, basename_size - 1);
|
strncpy(out_basename, last_slash + 1, basename_size - 1);
|
||||||
out_basename[basename_size - 1] = '\0';
|
out_basename[basename_size - 1] = '\0';
|
||||||
|
|
@ -416,6 +417,8 @@ static pxl8_result pxl8_script_prepare_path(pxl8_script* script, const char* fil
|
||||||
strncpy(out_basename, filename, basename_size - 1);
|
strncpy(out_basename, filename, basename_size - 1);
|
||||||
out_basename[basename_size - 1] = '\0';
|
out_basename[basename_size - 1] = '\0';
|
||||||
}
|
}
|
||||||
|
pxl8_free(resolved);
|
||||||
|
pxl8_free(cwd);
|
||||||
} else {
|
} else {
|
||||||
strncpy(out_basename, filename, basename_size - 1);
|
strncpy(out_basename, filename, basename_size - 1);
|
||||||
out_basename[basename_size - 1] = '\0';
|
out_basename[basename_size - 1] = '\0';
|
||||||
|
|
@ -442,51 +445,38 @@ pxl8_result pxl8_script_run_file(pxl8_script* script, const char* filename) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static time_t get_file_mod_time(const char* path) {
|
typedef struct {
|
||||||
struct stat file_stat;
|
f64 latest;
|
||||||
if (stat(path, &file_stat) == 0) {
|
} script_mod_ctx;
|
||||||
return file_stat.st_mtime;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static time_t get_latest_script_mod_time(const char* dir_path) {
|
static f64 get_latest_script_mod_time(const char* dir_path);
|
||||||
DIR* dir = opendir(dir_path);
|
|
||||||
if (!dir) return 0;
|
|
||||||
|
|
||||||
time_t latest = 0;
|
static bool check_script_mod_time(void* userdata, const char* dirname, const char* name) {
|
||||||
struct dirent* entry;
|
script_mod_ctx* ctx = userdata;
|
||||||
|
if (name[0] == '.') return true;
|
||||||
|
|
||||||
while ((entry = readdir(dir)) != NULL) {
|
char full_path[512];
|
||||||
if (entry->d_name[0] == '.') continue;
|
snprintf(full_path, sizeof(full_path), "%s%s", dirname, name);
|
||||||
|
|
||||||
char full_path[512];
|
if (pxl8_io_is_directory(full_path)) {
|
||||||
snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name);
|
f64 subdir_time = get_latest_script_mod_time(full_path);
|
||||||
|
if (subdir_time > ctx->latest) ctx->latest = subdir_time;
|
||||||
struct stat st;
|
} else {
|
||||||
if (stat(full_path, &st) == 0) {
|
usize len = strlen(name);
|
||||||
if (S_ISDIR(st.st_mode)) {
|
bool is_script = (len > 4 && strcmp(name + len - 4, ".fnl") == 0) ||
|
||||||
time_t subdir_time = get_latest_script_mod_time(full_path);
|
(len > 4 && strcmp(name + len - 4, ".lua") == 0);
|
||||||
if (subdir_time > latest) {
|
if (is_script) {
|
||||||
latest = subdir_time;
|
f64 mod_time = pxl8_io_get_file_modified_time(full_path);
|
||||||
}
|
if (mod_time > ctx->latest) ctx->latest = mod_time;
|
||||||
} else {
|
|
||||||
usize len = strlen(entry->d_name);
|
|
||||||
bool is_script = (len > 4 && strcmp(entry->d_name + len - 4, ".fnl") == 0) ||
|
|
||||||
(len > 4 && strcmp(entry->d_name + len - 4, ".lua") == 0);
|
|
||||||
|
|
||||||
if (is_script) {
|
|
||||||
time_t mod_time = get_file_mod_time(full_path);
|
|
||||||
if (mod_time > latest) {
|
|
||||||
latest = mod_time;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
closedir(dir);
|
static f64 get_latest_script_mod_time(const char* dir_path) {
|
||||||
return latest;
|
script_mod_ctx ctx = { .latest = 0.0 };
|
||||||
|
pxl8_io_enumerate_directory(dir_path, check_script_mod_time, &ctx);
|
||||||
|
return ctx.latest;
|
||||||
}
|
}
|
||||||
|
|
||||||
void pxl8_script_set_cart_path(pxl8_script* script, const char* cart_path, const char* original_cwd) {
|
void pxl8_script_set_cart_path(pxl8_script* script, const char* cart_path, const char* original_cwd) {
|
||||||
|
|
@ -1157,8 +1147,8 @@ bool pxl8_script_check_reload(pxl8_script* script) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
time_t current_mod_time = get_latest_script_mod_time(script->watch_dir);
|
f64 current_mod_time = get_latest_script_mod_time(script->watch_dir);
|
||||||
if (current_mod_time > script->latest_mod_time && current_mod_time != 0) {
|
if (current_mod_time > script->latest_mod_time && current_mod_time != 0.0) {
|
||||||
pxl8_info("Script files modified, reloading: %s", script->main_path);
|
pxl8_info("Script files modified, reloading: %s", script->main_path);
|
||||||
script->latest_mod_time = current_mod_time;
|
script->latest_mod_time = current_mod_time;
|
||||||
|
|
||||||
|
|
@ -1214,12 +1204,6 @@ static pxl8_resolution parse_resolution(const char* str) {
|
||||||
return PXL8_RESOLUTION_640x360;
|
return PXL8_RESOLUTION_640x360;
|
||||||
}
|
}
|
||||||
|
|
||||||
static pxl8_pixel_mode parse_pixel_mode(const char* str) {
|
|
||||||
if (strcmp(str, "indexed") == 0) return PXL8_PIXEL_INDEXED;
|
|
||||||
if (strcmp(str, "hicolor") == 0) return PXL8_PIXEL_HICOLOR;
|
|
||||||
return PXL8_PIXEL_INDEXED;
|
|
||||||
}
|
|
||||||
|
|
||||||
pxl8_result pxl8_script_load_cart_manifest(pxl8_script* script, pxl8_cart* cart) {
|
pxl8_result pxl8_script_load_cart_manifest(pxl8_script* script, pxl8_cart* cart) {
|
||||||
if (!script || !script->L || !cart) return PXL8_ERROR_NULL_POINTER;
|
if (!script || !script->L || !cart) return PXL8_ERROR_NULL_POINTER;
|
||||||
|
|
||||||
|
|
@ -1267,12 +1251,6 @@ pxl8_result pxl8_script_load_cart_manifest(pxl8_script* script, pxl8_cart* cart)
|
||||||
}
|
}
|
||||||
lua_pop(script->L, 1);
|
lua_pop(script->L, 1);
|
||||||
|
|
||||||
lua_getfield(script->L, -1, "pixel-mode");
|
|
||||||
if (lua_isstring(script->L, -1)) {
|
|
||||||
pxl8_cart_set_pixel_mode(cart, parse_pixel_mode(lua_tostring(script->L, -1)));
|
|
||||||
}
|
|
||||||
lua_pop(script->L, 1);
|
|
||||||
|
|
||||||
lua_getfield(script->L, -1, "window-size");
|
lua_getfield(script->L, -1, "window-size");
|
||||||
if (lua_istable(script->L, -1)) {
|
if (lua_istable(script->L, -1)) {
|
||||||
lua_rawgeti(script->L, -1, 1);
|
lua_rawgeti(script->L, -1, 1);
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,8 @@ static const char* pxl8_ffi_cdefs =
|
||||||
"f32 pxl8_get_fps(const pxl8* sys);\n"
|
"f32 pxl8_get_fps(const pxl8* sys);\n"
|
||||||
"\n"
|
"\n"
|
||||||
"u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color);\n"
|
"u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color);\n"
|
||||||
"u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx);\n"
|
"u8* pxl8_gfx_framebuffer(pxl8_gfx* gfx);\n"
|
||||||
"i32 pxl8_gfx_get_height(pxl8_gfx* ctx);\n"
|
"i32 pxl8_gfx_get_height(pxl8_gfx* ctx);\n"
|
||||||
"u32* pxl8_gfx_get_light_accum(pxl8_gfx* gfx);\n"
|
|
||||||
"const u32* pxl8_gfx_palette_colors(pxl8_gfx* gfx);\n"
|
"const u32* pxl8_gfx_palette_colors(pxl8_gfx* gfx);\n"
|
||||||
"u16* pxl8_gfx_get_zbuffer(pxl8_gfx* gfx);\n"
|
"u16* pxl8_gfx_get_zbuffer(pxl8_gfx* gfx);\n"
|
||||||
"typedef struct pxl8_palette pxl8_palette;\n"
|
"typedef struct pxl8_palette pxl8_palette;\n"
|
||||||
|
|
@ -262,6 +261,7 @@ static const char* pxl8_ffi_cdefs =
|
||||||
"void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_3d_uniforms* uniforms);\n"
|
"void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_lights* lights, const pxl8_3d_uniforms* uniforms);\n"
|
||||||
"void pxl8_3d_clear(pxl8_gfx* gfx, u8 color);\n"
|
"void pxl8_3d_clear(pxl8_gfx* gfx, u8 color);\n"
|
||||||
"void pxl8_3d_clear_depth(pxl8_gfx* gfx);\n"
|
"void pxl8_3d_clear_depth(pxl8_gfx* gfx);\n"
|
||||||
|
"void pxl8_3d_clear_stencil(pxl8_gfx* gfx, uint8_t value);\n"
|
||||||
"void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u8 color);\n"
|
"void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u8 color);\n"
|
||||||
"void pxl8_3d_end_frame(pxl8_gfx* gfx);\n"
|
"void pxl8_3d_end_frame(pxl8_gfx* gfx);\n"
|
||||||
"u32 pxl8_3d_project_points(pxl8_gfx* gfx, const pxl8_vec3* in, pxl8_vec3* out, u32 count, const pxl8_mat4* transform);\n"
|
"u32 pxl8_3d_project_points(pxl8_gfx* gfx, const pxl8_vec3* in, pxl8_vec3* out, u32 count, const pxl8_mat4* transform);\n"
|
||||||
|
|
@ -309,9 +309,20 @@ static const char* pxl8_ffi_cdefs =
|
||||||
"void pxl8_mesh_clear(pxl8_mesh* mesh);\n"
|
"void pxl8_mesh_clear(pxl8_mesh* mesh);\n"
|
||||||
"u16 pxl8_mesh_push_vertex(pxl8_mesh* mesh, pxl8_vertex v);\n"
|
"u16 pxl8_mesh_push_vertex(pxl8_mesh* mesh, pxl8_vertex v);\n"
|
||||||
"void pxl8_mesh_push_triangle(pxl8_mesh* mesh, u16 i0, u16 i1, u16 i2);\n"
|
"void pxl8_mesh_push_triangle(pxl8_mesh* mesh, u16 i0, u16 i1, u16 i2);\n"
|
||||||
"void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material);\n"
|
"typedef struct pxl8_gfx_draw_opts {\n"
|
||||||
|
" bool color_write;\n"
|
||||||
|
" int depth_compare;\n"
|
||||||
|
" bool depth_write;\n"
|
||||||
|
" bool stencil_test;\n"
|
||||||
|
" bool stencil_write;\n"
|
||||||
|
" uint8_t stencil_compare;\n"
|
||||||
|
" uint8_t stencil_ref;\n"
|
||||||
|
"} pxl8_gfx_draw_opts;\n"
|
||||||
|
"\n"
|
||||||
|
"void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_gfx_material* material, const pxl8_gfx_draw_opts* opts);\n"
|
||||||
"\n"
|
"\n"
|
||||||
"u32 pxl8_hash32(u32 x);\n"
|
"u32 pxl8_hash32(u32 x);\n"
|
||||||
|
"u32 pxl8_chunk_hash(i32 cx, i32 cz);\n"
|
||||||
"\n"
|
"\n"
|
||||||
"pxl8_mat4 pxl8_mat4_identity(void);\n"
|
"pxl8_mat4 pxl8_mat4_identity(void);\n"
|
||||||
"pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up);\n"
|
"pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up);\n"
|
||||||
|
|
@ -401,6 +412,7 @@ static const char* pxl8_ffi_cdefs =
|
||||||
"\n"
|
"\n"
|
||||||
"u32 pxl8_bsp_face_count(const pxl8_bsp* bsp);\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"
|
"pxl8_vec3 pxl8_bsp_face_normal(const pxl8_bsp* bsp, u32 face_id);\n"
|
||||||
|
"void pxl8_bsp_face_set_material(pxl8_bsp* bsp, u32 face_id, u16 material_id);\n"
|
||||||
"u8 pxl8_bsp_light_at(const pxl8_bsp* bsp, f32 x, f32 y, f32 z, u8 ambient);\n"
|
"u8 pxl8_bsp_light_at(const pxl8_bsp* bsp, f32 x, f32 y, f32 z, u8 ambient);\n"
|
||||||
"\n"
|
"\n"
|
||||||
"typedef struct pxl8_world_chunk {\n"
|
"typedef struct pxl8_world_chunk {\n"
|
||||||
|
|
@ -437,6 +449,7 @@ static const char* pxl8_ffi_cdefs =
|
||||||
"} pxl8_sim_entity;\n"
|
"} pxl8_sim_entity;\n"
|
||||||
"\n"
|
"\n"
|
||||||
"void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z);\n"
|
"void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z);\n"
|
||||||
|
"void pxl8_world_set_look(pxl8_world* world, f32 yaw, f32 pitch);\n"
|
||||||
"pxl8_sim_entity* pxl8_world_local_player(pxl8_world* world);\n"
|
"pxl8_sim_entity* pxl8_world_local_player(pxl8_world* world);\n"
|
||||||
"\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"
|
"typedef struct { i32 cursor_x; i32 cursor_y; bool cursor_down; bool cursor_clicked; u32 hot_id; u32 active_id; } pxl8_gui_state;\n"
|
||||||
|
|
@ -450,11 +463,17 @@ static const char* pxl8_ffi_cdefs =
|
||||||
"bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, const char* label);\n"
|
"bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, const char* label);\n"
|
||||||
"bool pxl8_gui_slider(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, float* value, float min_val, float max_val);\n"
|
"bool pxl8_gui_slider(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, float* value, float min_val, float max_val);\n"
|
||||||
"bool pxl8_gui_slider_int(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, i32* value, i32 min_val, i32 max_val);\n"
|
"bool pxl8_gui_slider_int(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, i32* value, i32 min_val, i32 max_val);\n"
|
||||||
|
"i32 pxl8_gui_toolbar(pxl8_gui_state* state, pxl8_gfx* gfx, u32 base_id, i32 x, i32 y, i32 btn_w, i32 btn_h, const char** labels, i32 count, i32 selected);\n"
|
||||||
"void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title);\n"
|
"void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title);\n"
|
||||||
"void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color);\n"
|
"void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color);\n"
|
||||||
"u8 pxl8_gui_color(pxl8_gfx* gfx, u8 index);\n"
|
"u8 pxl8_gui_color(pxl8_gfx* gfx, u8 index);\n"
|
||||||
"bool pxl8_gui_is_hovering(const pxl8_gui_state* state);\n"
|
"bool pxl8_gui_is_hovering(const pxl8_gui_state* state);\n"
|
||||||
"void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y);\n"
|
"void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y);\n"
|
||||||
|
"i32 pxl8_gui_grid_select(pxl8_gui_state* state, pxl8_gfx* gfx, u32 base_id, i32 x, i32 y, i32 cell_w, i32 cell_h, i32 cols, i32 rows, const u8* colors, i32 selected);\n"
|
||||||
|
"void pxl8_gui_image(pxl8_gfx* gfx, u32 texture_id, i32 sx, i32 sy, i32 sw, i32 sh, i32 dx, i32 dy, i32 dw, i32 dh);\n"
|
||||||
|
"void pxl8_gui_panel(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h);\n"
|
||||||
|
"void pxl8_gui_status_bar(pxl8_gfx* gfx, i32 y, i32 screen_w, i32 h, const char* text);\n"
|
||||||
|
"bool pxl8_gui_toggle(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, const char* label, bool active);\n"
|
||||||
"\n"
|
"\n"
|
||||||
"typedef struct pxl8_save pxl8_save;\n"
|
"typedef struct pxl8_save pxl8_save;\n"
|
||||||
"pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version);\n"
|
"pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version);\n"
|
||||||
|
|
@ -514,7 +533,7 @@ static const char* pxl8_ffi_cdefs =
|
||||||
"\n"
|
"\n"
|
||||||
"typedef struct pxl8_net pxl8_net;\n"
|
"typedef struct pxl8_net pxl8_net;\n"
|
||||||
"typedef struct pxl8_net_config { const char* address; 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_ENTER_SCENE } pxl8_cmd_type;\n"
|
"typedef enum pxl8_cmd_type { PXL8_CMD_NONE = 0, PXL8_CMD_SPAWN_ENTITY } pxl8_cmd_type;\n"
|
||||||
"\n"
|
"\n"
|
||||||
"typedef struct pxl8_command_msg {\n"
|
"typedef struct pxl8_command_msg {\n"
|
||||||
" u16 cmd_type;\n"
|
" u16 cmd_type;\n"
|
||||||
|
|
@ -548,7 +567,10 @@ static const char* pxl8_ffi_cdefs =
|
||||||
"} pxl8_sim_config;\n"
|
"} pxl8_sim_config;\n"
|
||||||
"\n"
|
"\n"
|
||||||
"typedef struct pxl8_sim_world {\n"
|
"typedef struct pxl8_sim_world {\n"
|
||||||
" const pxl8_bsp* bsp;\n"
|
" const pxl8_bsp* chunks[9];\n"
|
||||||
|
" i32 center_cx;\n"
|
||||||
|
" i32 center_cz;\n"
|
||||||
|
" f32 chunk_size;\n"
|
||||||
"} pxl8_sim_world;\n"
|
"} pxl8_sim_world;\n"
|
||||||
"\n"
|
"\n"
|
||||||
"void pxl8_sim_move_player(pxl8_sim_entity* ent, const pxl8_input_msg* input, const pxl8_sim_world* world, const pxl8_sim_config* cfg, f32 dt);\n"
|
"void pxl8_sim_move_player(pxl8_sim_entity* ent, const pxl8_input_msg* input, const pxl8_sim_world* world, const pxl8_sim_config* cfg, f32 dt);\n"
|
||||||
|
|
@ -587,15 +609,15 @@ static const char* pxl8_ffi_cdefs =
|
||||||
"f32 pxl8_net_lerp_alpha(const pxl8_net* net);\n"
|
"f32 pxl8_net_lerp_alpha(const pxl8_net* net);\n"
|
||||||
"bool pxl8_net_needs_correction(const pxl8_net* net);\n"
|
"bool pxl8_net_needs_correction(const pxl8_net* net);\n"
|
||||||
"u64 pxl8_net_player_id(const pxl8_net* net);\n"
|
"u64 pxl8_net_player_id(const pxl8_net* net);\n"
|
||||||
"u8 pxl8_net_chunk_type(const pxl8_net* net);\n"
|
"i32 pxl8_net_chunk_cx(const pxl8_net* net);\n"
|
||||||
"u32 pxl8_net_chunk_id(const pxl8_net* net);\n"
|
"i32 pxl8_net_chunk_cz(const pxl8_net* net);\n"
|
||||||
|
"bool pxl8_net_has_chunk(const pxl8_net* net);\n"
|
||||||
"bool pxl8_net_poll(pxl8_net* net);\n"
|
"bool pxl8_net_poll(pxl8_net* net);\n"
|
||||||
"u8* pxl8_net_predicted_state(pxl8_net* net);\n"
|
"u8* pxl8_net_predicted_state(pxl8_net* net);\n"
|
||||||
"void pxl8_net_predicted_tick_set(pxl8_net* net, u64 tick);\n"
|
"void pxl8_net_predicted_tick_set(pxl8_net* net, u64 tick);\n"
|
||||||
"i32 pxl8_net_send_command(pxl8_net* net, const pxl8_command_msg* cmd);\n"
|
"i32 pxl8_net_send_command(pxl8_net* net, const pxl8_command_msg* cmd);\n"
|
||||||
"i32 pxl8_net_send_input(pxl8_net* net, const pxl8_input_msg* input);\n"
|
"i32 pxl8_net_send_input(pxl8_net* net, const pxl8_input_msg* input);\n"
|
||||||
"i32 pxl8_net_spawn(pxl8_net* net, f32 x, f32 y, f32 z, f32 yaw, f32 pitch);\n"
|
"i32 pxl8_net_spawn(pxl8_net* net, f32 x, f32 y, f32 z, f32 yaw, f32 pitch);\n"
|
||||||
"i32 pxl8_net_enter_scene(pxl8_net* net, u8 chunk_type, u32 chunk_id, f32 x, f32 y, f32 z);\n"
|
|
||||||
"const pxl8_snapshot_header* pxl8_net_snapshot(const pxl8_net* net);\n"
|
"const pxl8_snapshot_header* pxl8_net_snapshot(const pxl8_net* net);\n"
|
||||||
"u64 pxl8_net_tick(const pxl8_net* net);\n"
|
"u64 pxl8_net_tick(const pxl8_net* net);\n"
|
||||||
"void pxl8_net_update(pxl8_net* net, f32 dt);\n"
|
"void pxl8_net_update(pxl8_net* net, f32 dt);\n"
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,15 @@
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
|
#define DIST_EPSILON 0.03125f
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
f32 fraction;
|
||||||
|
pxl8_vec3 normal;
|
||||||
|
bool all_solid;
|
||||||
|
bool start_solid;
|
||||||
|
} trace_result;
|
||||||
|
|
||||||
static i32 bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos) {
|
static i32 bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos) {
|
||||||
if (!bsp || bsp->num_nodes == 0) return -1;
|
if (!bsp || bsp->num_nodes == 0) return -1;
|
||||||
|
|
||||||
|
|
@ -16,54 +25,299 @@ static i32 bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos) {
|
||||||
return -(node_id + 1);
|
return -(node_id + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static i32 bsp_contents_from(const pxl8_bsp* bsp, i32 node_id, pxl8_vec3 pos) {
|
||||||
|
while (node_id >= 0) {
|
||||||
|
const pxl8_bsp_node* node = &bsp->nodes[node_id];
|
||||||
|
const pxl8_bsp_plane* plane = &bsp->planes[node->plane_id];
|
||||||
|
f32 d = pxl8_vec3_dot(pos, plane->normal) - plane->dist;
|
||||||
|
node_id = node->children[d < 0 ? 1 : 0];
|
||||||
|
}
|
||||||
|
i32 leaf_idx = -(node_id + 1);
|
||||||
|
if (leaf_idx < 0 || (u32)leaf_idx >= bsp->num_leafs) return -1;
|
||||||
|
return bsp->leafs[leaf_idx].contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool bsp_recursive_trace(const pxl8_bsp* bsp, i32 node_id,
|
||||||
|
f32 p1f, f32 p2f,
|
||||||
|
pxl8_vec3 p1, pxl8_vec3 p2,
|
||||||
|
trace_result* tr) {
|
||||||
|
if (node_id < 0) {
|
||||||
|
i32 leaf_idx = -(node_id + 1);
|
||||||
|
if (leaf_idx >= 0 && (u32)leaf_idx < bsp->num_leafs &&
|
||||||
|
bsp->leafs[leaf_idx].contents == -1) {
|
||||||
|
tr->start_solid = true;
|
||||||
|
} else {
|
||||||
|
tr->all_solid = false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pxl8_bsp_node* node = &bsp->nodes[node_id];
|
||||||
|
const pxl8_bsp_plane* plane = &bsp->planes[node->plane_id];
|
||||||
|
|
||||||
|
f32 t1 = pxl8_vec3_dot(p1, plane->normal) - plane->dist;
|
||||||
|
f32 t2 = pxl8_vec3_dot(p2, plane->normal) - plane->dist;
|
||||||
|
|
||||||
|
if (t1 >= 0 && t2 >= 0)
|
||||||
|
return bsp_recursive_trace(bsp, node->children[0], p1f, p2f, p1, p2, tr);
|
||||||
|
if (t1 < 0 && t2 < 0)
|
||||||
|
return bsp_recursive_trace(bsp, node->children[1], p1f, p2f, p1, p2, tr);
|
||||||
|
|
||||||
|
i32 side;
|
||||||
|
f32 frac;
|
||||||
|
if (t1 < 0) {
|
||||||
|
frac = (t1 + DIST_EPSILON) / (t1 - t2);
|
||||||
|
side = 1;
|
||||||
|
} else {
|
||||||
|
frac = (t1 - DIST_EPSILON) / (t1 - t2);
|
||||||
|
side = 0;
|
||||||
|
}
|
||||||
|
if (frac < 0) frac = 0;
|
||||||
|
if (frac > 1) frac = 1;
|
||||||
|
|
||||||
|
f32 midf = p1f + (p2f - p1f) * frac;
|
||||||
|
pxl8_vec3 mid = {
|
||||||
|
p1.x + frac * (p2.x - p1.x),
|
||||||
|
p1.y + frac * (p2.y - p1.y),
|
||||||
|
p1.z + frac * (p2.z - p1.z),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!bsp_recursive_trace(bsp, node->children[side], p1f, midf, p1, mid, tr))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (bsp_contents_from(bsp, node->children[side ^ 1], mid) != -1)
|
||||||
|
return bsp_recursive_trace(bsp, node->children[side ^ 1], midf, p2f, mid, p2, tr);
|
||||||
|
|
||||||
|
if (tr->all_solid)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (midf < tr->fraction) {
|
||||||
|
tr->fraction = midf;
|
||||||
|
if (side == 0) {
|
||||||
|
tr->normal = plane->normal;
|
||||||
|
} else {
|
||||||
|
tr->normal.x = -plane->normal.x;
|
||||||
|
tr->normal.y = -plane->normal.y;
|
||||||
|
tr->normal.z = -plane->normal.z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static trace_result bsp_trace_line(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to) {
|
||||||
|
trace_result tr = { .fraction = 1.0f, .all_solid = true };
|
||||||
|
if (!bsp || bsp->num_nodes == 0) {
|
||||||
|
tr.all_solid = false;
|
||||||
|
return tr;
|
||||||
|
}
|
||||||
|
bsp_recursive_trace(bsp, 0, 0.0f, 1.0f, from, to, &tr);
|
||||||
|
if (tr.all_solid) {
|
||||||
|
tr.fraction = 0.0f;
|
||||||
|
tr.start_solid = true;
|
||||||
|
}
|
||||||
|
return tr;
|
||||||
|
}
|
||||||
|
|
||||||
bool pxl8_bsp_point_solid(const pxl8_bsp* bsp, pxl8_vec3 pos) {
|
bool pxl8_bsp_point_solid(const pxl8_bsp* bsp, pxl8_vec3 pos) {
|
||||||
|
if (!bsp) return false;
|
||||||
|
if (bsp->bounds_max_x > bsp->bounds_min_x &&
|
||||||
|
(pos.x < bsp->bounds_min_x || pos.x >= bsp->bounds_max_x ||
|
||||||
|
pos.z < bsp->bounds_min_z || pos.z >= bsp->bounds_max_z))
|
||||||
|
return false;
|
||||||
i32 leaf = bsp_find_leaf(bsp, pos);
|
i32 leaf = bsp_find_leaf(bsp, pos);
|
||||||
if (leaf < 0 || (u32)leaf >= bsp->num_leafs) return true;
|
if (leaf < 0 || (u32)leaf >= bsp->num_leafs) return true;
|
||||||
return bsp->leafs[leaf].contents == -1;
|
return bsp->leafs[leaf].contents == -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool bsp_point_clear(const pxl8_bsp* bsp, f32 x, f32 y, f32 z, f32 radius) {
|
static void trace_offsets(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to,
|
||||||
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z})) return false;
|
f32 radius, f32* out_frac, pxl8_vec3* out_normal) {
|
||||||
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x + radius, y, z})) return false;
|
f32 d = radius * 0.7071f;
|
||||||
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x - radius, y, z})) return false;
|
pxl8_vec3 offsets[9] = {
|
||||||
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z + radius})) return false;
|
{0, 0, 0},
|
||||||
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z - radius})) return false;
|
{radius, 0, 0}, {-radius, 0, 0},
|
||||||
return true;
|
{0, 0, radius}, {0, 0, -radius},
|
||||||
|
{d, 0, d}, {d, 0, -d},
|
||||||
|
{-d, 0, d}, {-d, 0, -d},
|
||||||
|
};
|
||||||
|
|
||||||
|
*out_frac = 1.0f;
|
||||||
|
*out_normal = (pxl8_vec3){0, 0, 0};
|
||||||
|
|
||||||
|
for (i32 i = 0; i < 9; i++) {
|
||||||
|
pxl8_vec3 s = { from.x + offsets[i].x, from.y + offsets[i].y, from.z + offsets[i].z };
|
||||||
|
pxl8_vec3 e = { to.x + offsets[i].x, to.y + offsets[i].y, to.z + offsets[i].z };
|
||||||
|
trace_result tr = bsp_trace_line(bsp, s, e);
|
||||||
|
if (tr.fraction < *out_frac && !tr.start_solid) {
|
||||||
|
*out_frac = tr.fraction;
|
||||||
|
*out_normal = tr.normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_vec3 pxl8_bsp_trace(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to, f32 radius) {
|
pxl8_vec3 pxl8_bsp_trace(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to, f32 radius) {
|
||||||
if (!bsp || bsp->num_nodes == 0) return to;
|
if (!bsp || bsp->num_nodes == 0) return to;
|
||||||
|
|
||||||
if (bsp_point_clear(bsp, to.x, to.y, to.z, radius)) {
|
f32 frac;
|
||||||
return to;
|
pxl8_vec3 normal;
|
||||||
|
trace_offsets(bsp, from, to, radius, &frac, &normal);
|
||||||
|
if (frac >= 1.0f) return to;
|
||||||
|
|
||||||
|
pxl8_vec3 delta = { to.x - from.x, to.y - from.y, to.z - from.z };
|
||||||
|
pxl8_vec3 hit = {
|
||||||
|
from.x + delta.x * frac,
|
||||||
|
from.y + delta.y * frac,
|
||||||
|
from.z + delta.z * frac,
|
||||||
|
};
|
||||||
|
|
||||||
|
f32 remaining = 1.0f - frac;
|
||||||
|
if (remaining <= 0.0f) return hit;
|
||||||
|
|
||||||
|
f32 backoff = pxl8_vec3_dot(delta, normal) * remaining;
|
||||||
|
pxl8_vec3 slide_target = {
|
||||||
|
hit.x + delta.x * remaining - normal.x * backoff,
|
||||||
|
hit.y + delta.y * remaining - normal.y * backoff,
|
||||||
|
hit.z + delta.z * remaining - normal.z * backoff,
|
||||||
|
};
|
||||||
|
|
||||||
|
f32 slide_frac;
|
||||||
|
pxl8_vec3 slide_normal;
|
||||||
|
trace_offsets(bsp, hit, slide_target, radius, &slide_frac, &slide_normal);
|
||||||
|
if (slide_frac >= 1.0f) return slide_target;
|
||||||
|
|
||||||
|
pxl8_vec3 slide_delta = {
|
||||||
|
slide_target.x - hit.x,
|
||||||
|
slide_target.y - hit.y,
|
||||||
|
slide_target.z - hit.z,
|
||||||
|
};
|
||||||
|
return (pxl8_vec3){
|
||||||
|
hit.x + slide_delta.x * slide_frac,
|
||||||
|
hit.y + slide_delta.y * slide_frac,
|
||||||
|
hit.z + slide_delta.z * slide_frac,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static const pxl8_bsp* sim_bsp_at(const pxl8_sim_world* world, f32 x, f32 z) {
|
||||||
|
i32 cx = (i32)floorf(x / world->chunk_size);
|
||||||
|
i32 cz = (i32)floorf(z / world->chunk_size);
|
||||||
|
i32 dx = cx - world->center_cx + 1;
|
||||||
|
i32 dz = cz - world->center_cz + 1;
|
||||||
|
if (dx < 0 || dx > 2 || dz < 0 || dz > 2) return NULL;
|
||||||
|
return world->chunks[dz * 3 + dx];
|
||||||
|
}
|
||||||
|
|
||||||
|
static f32 bsp_terrain_height(const pxl8_bsp* bsp, f32 x, f32 z) {
|
||||||
|
if (!bsp || !bsp->heightfield || bsp->heightfield_cell_size <= 0) return -1e9f;
|
||||||
|
f32 lx = (x - bsp->heightfield_ox) / bsp->heightfield_cell_size;
|
||||||
|
f32 lz = (z - bsp->heightfield_oz) / bsp->heightfield_cell_size;
|
||||||
|
i32 ix = (i32)floorf(lx);
|
||||||
|
i32 iz = (i32)floorf(lz);
|
||||||
|
if (ix < 0 || ix >= bsp->heightfield_w - 1 || iz < 0 || iz >= bsp->heightfield_h - 1) return -1e9f;
|
||||||
|
f32 fx = lx - ix;
|
||||||
|
f32 fz = lz - iz;
|
||||||
|
i32 w = bsp->heightfield_w;
|
||||||
|
f32 h00 = bsp->heightfield[iz * w + ix];
|
||||||
|
f32 h10 = bsp->heightfield[iz * w + ix + 1];
|
||||||
|
f32 h01 = bsp->heightfield[(iz + 1) * w + ix];
|
||||||
|
f32 h11 = bsp->heightfield[(iz + 1) * w + ix + 1];
|
||||||
|
f32 h0 = h00 + (h10 - h00) * fx;
|
||||||
|
f32 h1 = h01 + (h11 - h01) * fx;
|
||||||
|
return h0 + (h1 - h0) * fz;
|
||||||
|
}
|
||||||
|
|
||||||
|
static f32 sim_terrain_height(const pxl8_sim_world* world, f32 x, f32 z) {
|
||||||
|
const pxl8_bsp* bsp = sim_bsp_at(world, x, z);
|
||||||
|
return bsp_terrain_height(bsp, x, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sim_trace_offsets(const pxl8_sim_world* world, pxl8_vec3 from, pxl8_vec3 to,
|
||||||
|
f32 radius, f32* out_frac, pxl8_vec3* out_normal) {
|
||||||
|
f32 d = radius * 0.7071f;
|
||||||
|
pxl8_vec3 offsets[9] = {
|
||||||
|
{0, 0, 0},
|
||||||
|
{radius, 0, 0}, {-radius, 0, 0},
|
||||||
|
{0, 0, radius}, {0, 0, -radius},
|
||||||
|
{d, 0, d}, {d, 0, -d},
|
||||||
|
{-d, 0, d}, {-d, 0, -d},
|
||||||
|
};
|
||||||
|
|
||||||
|
*out_frac = 1.0f;
|
||||||
|
*out_normal = (pxl8_vec3){0, 0, 0};
|
||||||
|
|
||||||
|
for (i32 i = 0; i < 9; i++) {
|
||||||
|
pxl8_vec3 s = { from.x + offsets[i].x, from.y + offsets[i].y, from.z + offsets[i].z };
|
||||||
|
pxl8_vec3 e = { to.x + offsets[i].x, to.y + offsets[i].y, to.z + offsets[i].z };
|
||||||
|
const pxl8_bsp* bsp_s = sim_bsp_at(world, s.x, s.z);
|
||||||
|
const pxl8_bsp* bsp_e = sim_bsp_at(world, e.x, e.z);
|
||||||
|
if (bsp_s) {
|
||||||
|
trace_result tr = bsp_trace_line(bsp_s, s, e);
|
||||||
|
if (tr.fraction < *out_frac && !tr.start_solid) {
|
||||||
|
*out_frac = tr.fraction;
|
||||||
|
*out_normal = tr.normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bsp_e && bsp_e != bsp_s) {
|
||||||
|
bool inside_bounds = !(bsp_e->bounds_max_x > bsp_e->bounds_min_x) ||
|
||||||
|
(s.x >= bsp_e->bounds_min_x && s.x < bsp_e->bounds_max_x &&
|
||||||
|
s.z >= bsp_e->bounds_min_z && s.z < bsp_e->bounds_max_z);
|
||||||
|
if (inside_bounds) {
|
||||||
|
trace_result tr = bsp_trace_line(bsp_e, s, e);
|
||||||
|
if (tr.fraction < *out_frac && !tr.start_solid) {
|
||||||
|
*out_frac = tr.fraction;
|
||||||
|
*out_normal = tr.normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool x_ok = bsp_point_clear(bsp, to.x, from.y, from.z, radius);
|
|
||||||
bool y_ok = bsp_point_clear(bsp, from.x, to.y, from.z, radius);
|
|
||||||
bool z_ok = bsp_point_clear(bsp, from.x, from.y, to.z, radius);
|
|
||||||
|
|
||||||
pxl8_vec3 result = from;
|
|
||||||
if (x_ok) result.x = to.x;
|
|
||||||
if (y_ok) result.y = to.y;
|
|
||||||
if (z_ok) result.z = to.z;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_vec3 pxl8_sim_trace(const pxl8_sim_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius, f32 height) {
|
pxl8_vec3 pxl8_sim_trace(const pxl8_sim_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius, f32 height) {
|
||||||
(void)height;
|
(void)height;
|
||||||
if (!world) return to;
|
if (!world || world->chunk_size <= 0) return to;
|
||||||
|
|
||||||
if (world->bsp) {
|
f32 frac;
|
||||||
return pxl8_bsp_trace(world->bsp, from, to, radius);
|
pxl8_vec3 normal;
|
||||||
}
|
sim_trace_offsets(world, from, to, radius, &frac, &normal);
|
||||||
|
if (frac >= 1.0f) return to;
|
||||||
|
|
||||||
return to;
|
pxl8_vec3 delta = { to.x - from.x, to.y - from.y, to.z - from.z };
|
||||||
|
pxl8_vec3 hit = {
|
||||||
|
from.x + delta.x * frac,
|
||||||
|
from.y + delta.y * frac,
|
||||||
|
from.z + delta.z * frac,
|
||||||
|
};
|
||||||
|
|
||||||
|
f32 remaining = 1.0f - frac;
|
||||||
|
if (remaining <= 0.0f) return hit;
|
||||||
|
|
||||||
|
f32 backoff = pxl8_vec3_dot(delta, normal) * remaining;
|
||||||
|
pxl8_vec3 slide_target = {
|
||||||
|
hit.x + delta.x * remaining - normal.x * backoff,
|
||||||
|
hit.y + delta.y * remaining - normal.y * backoff,
|
||||||
|
hit.z + delta.z * remaining - normal.z * backoff,
|
||||||
|
};
|
||||||
|
|
||||||
|
f32 slide_frac;
|
||||||
|
pxl8_vec3 slide_normal;
|
||||||
|
sim_trace_offsets(world, hit, slide_target, radius, &slide_frac, &slide_normal);
|
||||||
|
if (slide_frac >= 1.0f) return slide_target;
|
||||||
|
|
||||||
|
pxl8_vec3 slide_delta = {
|
||||||
|
slide_target.x - hit.x,
|
||||||
|
slide_target.y - hit.y,
|
||||||
|
slide_target.z - hit.z,
|
||||||
|
};
|
||||||
|
return (pxl8_vec3){
|
||||||
|
hit.x + slide_delta.x * slide_frac,
|
||||||
|
hit.y + slide_delta.y * slide_frac,
|
||||||
|
hit.z + slide_delta.z * slide_frac,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool pxl8_sim_check_ground(const pxl8_sim_world* world, pxl8_vec3 pos, f32 radius) {
|
bool pxl8_sim_check_ground(const pxl8_sim_world* world, pxl8_vec3 pos, f32 radius) {
|
||||||
if (!world) return true;
|
if (!world || world->chunk_size <= 0) return true;
|
||||||
if (!world->bsp) return true;
|
|
||||||
|
f32 th = sim_terrain_height(world, pos.x, pos.z);
|
||||||
|
if (th > -1e8f && pos.y - th < 2.0f) return true;
|
||||||
|
|
||||||
pxl8_vec3 down = {pos.x, pos.y - 2.0f, pos.z};
|
pxl8_vec3 down = {pos.x, pos.y - 2.0f, pos.z};
|
||||||
pxl8_vec3 result = pxl8_sim_trace(world, pos, down, radius, 0.0f);
|
pxl8_vec3 result = pxl8_sim_trace(world, pos, down, radius, 0.0f);
|
||||||
|
|
@ -119,22 +373,17 @@ void pxl8_sim_move_player(pxl8_sim_entity* ent, const pxl8_input_msg* input,
|
||||||
ent->pos.z + ent->vel.z * dt
|
ent->pos.z + ent->vel.z * dt
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (grounded) {
|
||||||
|
f32 th_dest = sim_terrain_height(world, target.x, target.z);
|
||||||
|
if (th_dest > -1e8f) target.y = th_dest;
|
||||||
|
}
|
||||||
|
|
||||||
pxl8_vec3 new_pos = pxl8_sim_trace(world, ent->pos, target, cfg->player_radius, cfg->player_height);
|
pxl8_vec3 new_pos = pxl8_sim_trace(world, ent->pos, target, cfg->player_radius, cfg->player_height);
|
||||||
|
|
||||||
if (ent->vel.y < 0 && new_pos.y > target.y + 0.01f) {
|
f32 th = sim_terrain_height(world, new_pos.x, new_pos.z);
|
||||||
f32 hi = new_pos.y;
|
if (th > -1e8f && new_pos.y < th) {
|
||||||
f32 lo = target.y;
|
new_pos.y = th;
|
||||||
for (i32 i = 0; i < 8; i++) {
|
if (ent->vel.y < 0) ent->vel.y = 0;
|
||||||
f32 mid = (hi + lo) * 0.5f;
|
|
||||||
pxl8_vec3 test = {new_pos.x, mid, new_pos.z};
|
|
||||||
pxl8_vec3 result = pxl8_sim_trace(world, new_pos, test, cfg->player_radius, cfg->player_height);
|
|
||||||
if (result.y > mid + 0.01f) {
|
|
||||||
lo = mid;
|
|
||||||
} else {
|
|
||||||
hi = mid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
new_pos.y = hi;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ent->pos = new_pos;
|
ent->pos = new_pos;
|
||||||
|
|
@ -175,22 +424,17 @@ void pxl8_sim_integrate(pxl8_sim_entity* ent, const pxl8_sim_world* world,
|
||||||
ent->pos.z + ent->vel.z * dt
|
ent->pos.z + ent->vel.z * dt
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (grounded) {
|
||||||
|
f32 th_dest = sim_terrain_height(world, target.x, target.z);
|
||||||
|
if (th_dest > -1e8f) target.y = th_dest;
|
||||||
|
}
|
||||||
|
|
||||||
pxl8_vec3 new_pos = pxl8_sim_trace(world, ent->pos, target, cfg->player_radius, cfg->player_height);
|
pxl8_vec3 new_pos = pxl8_sim_trace(world, ent->pos, target, cfg->player_radius, cfg->player_height);
|
||||||
|
|
||||||
if (ent->vel.y < 0 && new_pos.y > target.y + 0.01f) {
|
f32 th2 = sim_terrain_height(world, new_pos.x, new_pos.z);
|
||||||
f32 hi = new_pos.y;
|
if (th2 > -1e8f && new_pos.y < th2) {
|
||||||
f32 lo = target.y;
|
new_pos.y = th2;
|
||||||
for (i32 i = 0; i < 8; i++) {
|
if (ent->vel.y < 0.0f) ent->vel.y = 0.0f;
|
||||||
f32 mid = (hi + lo) * 0.5f;
|
|
||||||
pxl8_vec3 test = {new_pos.x, mid, new_pos.z};
|
|
||||||
pxl8_vec3 result = pxl8_sim_trace(world, new_pos, test, cfg->player_radius, cfg->player_height);
|
|
||||||
if (result.y > mid + 0.01f) {
|
|
||||||
lo = mid;
|
|
||||||
} else {
|
|
||||||
hi = mid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
new_pos.y = hi;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ent->pos = new_pos;
|
ent->pos = new_pos;
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,10 @@ typedef struct pxl8_sim_entity {
|
||||||
} pxl8_sim_entity;
|
} pxl8_sim_entity;
|
||||||
|
|
||||||
typedef struct pxl8_sim_world {
|
typedef struct pxl8_sim_world {
|
||||||
const pxl8_bsp* bsp;
|
const pxl8_bsp* chunks[9];
|
||||||
|
i32 center_cx;
|
||||||
|
i32 center_cz;
|
||||||
|
f32 chunk_size;
|
||||||
} pxl8_sim_world;
|
} pxl8_sim_world;
|
||||||
|
|
||||||
bool pxl8_bsp_point_solid(const pxl8_bsp* bsp, pxl8_vec3 pos);
|
bool pxl8_bsp_point_solid(const pxl8_bsp* bsp, pxl8_vec3 pos);
|
||||||
|
|
|
||||||
|
|
@ -14,15 +14,31 @@
|
||||||
#include "pxl8_gfx3d.h"
|
#include "pxl8_gfx3d.h"
|
||||||
#include "pxl8_log.h"
|
#include "pxl8_log.h"
|
||||||
#include "pxl8_mem.h"
|
#include "pxl8_mem.h"
|
||||||
|
#include "pxl8_protocol.h"
|
||||||
#include "pxl8_sim.h"
|
#include "pxl8_sim.h"
|
||||||
|
|
||||||
|
#define PXL8_VIS_MAX_NODES (PXL8_WORLD_MAX_LOADED_CHUNKS * 512)
|
||||||
|
#define PXL8_VIS_MAX_QUEUE (PXL8_VIS_MAX_NODES * 4)
|
||||||
|
#define PXL8_VIS_BYTES ((PXL8_VIS_MAX_NODES + 7) / 8)
|
||||||
#define PXL8_WORLD_ENTITY_CAPACITY 256
|
#define PXL8_WORLD_ENTITY_CAPACITY 256
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u16 chunk_idx;
|
||||||
|
u16 leaf_idx;
|
||||||
|
pxl8_rect window;
|
||||||
|
} world_vis_node;
|
||||||
|
|
||||||
struct pxl8_world {
|
struct pxl8_world {
|
||||||
|
pxl8_loaded_chunk loaded[PXL8_WORLD_MAX_LOADED_CHUNKS];
|
||||||
|
u32 loaded_count;
|
||||||
pxl8_world_chunk* active_chunk;
|
pxl8_world_chunk* active_chunk;
|
||||||
|
pxl8_bsp_render_state* active_render_state;
|
||||||
|
|
||||||
|
pxl8_gfx_material shared_materials[16];
|
||||||
|
bool shared_material_set[16];
|
||||||
|
|
||||||
pxl8_world_chunk_cache* chunk_cache;
|
pxl8_world_chunk_cache* chunk_cache;
|
||||||
pxl8_entity_pool* entities;
|
pxl8_entity_pool* entities;
|
||||||
pxl8_bsp_render_state* bsp_render_state;
|
|
||||||
|
|
||||||
pxl8_sim_entity local_player;
|
pxl8_sim_entity local_player;
|
||||||
u64 client_tick;
|
u64 client_tick;
|
||||||
|
|
@ -30,6 +46,12 @@ struct pxl8_world {
|
||||||
pxl8_vec2 pointer_motion;
|
pxl8_vec2 pointer_motion;
|
||||||
pxl8_sim_config sim_config;
|
pxl8_sim_config sim_config;
|
||||||
|
|
||||||
|
u8 vis_bits[PXL8_VIS_BYTES];
|
||||||
|
u8* vis_ptrs[PXL8_WORLD_MAX_LOADED_CHUNKS];
|
||||||
|
pxl8_rect vis_windows[PXL8_VIS_MAX_NODES];
|
||||||
|
pxl8_rect* vis_win_ptrs[PXL8_WORLD_MAX_LOADED_CHUNKS];
|
||||||
|
world_vis_node vis_queue[PXL8_VIS_MAX_QUEUE];
|
||||||
|
|
||||||
#ifdef PXL8_ASYNC_THREADS
|
#ifdef PXL8_ASYNC_THREADS
|
||||||
pxl8_sim_entity render_state[2];
|
pxl8_sim_entity render_state[2];
|
||||||
atomic_uint active_buffer;
|
atomic_uint active_buffer;
|
||||||
|
|
@ -72,9 +94,11 @@ pxl8_world* pxl8_world_create(void) {
|
||||||
void pxl8_world_destroy(pxl8_world* world) {
|
void pxl8_world_destroy(pxl8_world* world) {
|
||||||
if (!world) return;
|
if (!world) return;
|
||||||
|
|
||||||
|
for (u32 i = 0; i < world->loaded_count; i++) {
|
||||||
|
pxl8_bsp_render_state_destroy(world->loaded[i].render_state);
|
||||||
|
}
|
||||||
pxl8_world_chunk_cache_destroy(world->chunk_cache);
|
pxl8_world_chunk_cache_destroy(world->chunk_cache);
|
||||||
pxl8_entity_pool_destroy(world->entities);
|
pxl8_entity_pool_destroy(world->entities);
|
||||||
pxl8_bsp_render_state_destroy(world->bsp_render_state);
|
|
||||||
pxl8_free(world);
|
pxl8_free(world);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,27 +112,26 @@ pxl8_world_chunk* pxl8_world_active_chunk(pxl8_world* world) {
|
||||||
return world->active_chunk;
|
return world->active_chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
void pxl8_world_set_active_chunk(pxl8_world* world, pxl8_world_chunk* chunk) {
|
|
||||||
if (!world) return;
|
|
||||||
world->active_chunk = chunk;
|
|
||||||
}
|
|
||||||
|
|
||||||
pxl8_entity_pool* pxl8_world_entities(pxl8_world* world) {
|
|
||||||
if (!world) return NULL;
|
|
||||||
return world->entities;
|
|
||||||
}
|
|
||||||
|
|
||||||
pxl8_entity pxl8_world_spawn(pxl8_world* world) {
|
|
||||||
if (!world || !world->entities) return PXL8_ENTITY_INVALID;
|
|
||||||
return pxl8_entity_spawn(world->entities);
|
|
||||||
}
|
|
||||||
|
|
||||||
pxl8_sim_world pxl8_world_sim_world(const pxl8_world* world, pxl8_vec3 pos) {
|
pxl8_sim_world pxl8_world_sim_world(const pxl8_world* world, pxl8_vec3 pos) {
|
||||||
(void)pos;
|
|
||||||
pxl8_sim_world sim = {0};
|
pxl8_sim_world sim = {0};
|
||||||
if (world->active_chunk && world->active_chunk->bsp) {
|
const f32 chunk_size = 16.0f * 64.0f;
|
||||||
sim.bsp = world->active_chunk->bsp;
|
sim.chunk_size = chunk_size;
|
||||||
|
|
||||||
|
i32 pcx = (i32)floorf(pos.x / chunk_size);
|
||||||
|
i32 pcz = (i32)floorf(pos.z / chunk_size);
|
||||||
|
sim.center_cx = pcx;
|
||||||
|
sim.center_cz = pcz;
|
||||||
|
|
||||||
|
for (u32 i = 0; i < world->loaded_count; i++) {
|
||||||
|
const pxl8_loaded_chunk* lc = &world->loaded[i];
|
||||||
|
if (!lc->chunk || !lc->chunk->bsp) continue;
|
||||||
|
i32 dx = lc->cx - pcx + 1;
|
||||||
|
i32 dz = lc->cz - pcz + 1;
|
||||||
|
if (dx >= 0 && dx <= 2 && dz >= 0 && dz <= 2) {
|
||||||
|
sim.chunks[dz * 3 + dx] = lc->chunk->bsp;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sim;
|
return sim;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -244,62 +267,452 @@ void pxl8_world_update(pxl8_world* world, f32 dt) {
|
||||||
pxl8_world_chunk_cache_tick(world->chunk_cache);
|
pxl8_world_chunk_cache_tick(world->chunk_cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool vr_valid(pxl8_rect r) {
|
||||||
|
return r.x0 < r.x1 && r.y0 < r.y1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline pxl8_rect vr_intersect(pxl8_rect a, pxl8_rect b) {
|
||||||
|
return (pxl8_rect){
|
||||||
|
.x0 = a.x0 > b.x0 ? a.x0 : b.x0,
|
||||||
|
.y0 = a.y0 > b.y0 ? a.y0 : b.y0,
|
||||||
|
.x1 = a.x1 < b.x1 ? a.x1 : b.x1,
|
||||||
|
.y1 = a.y1 < b.y1 ? a.y1 : b.y1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static pxl8_rect project_portal(f32 px0, f32 pz0, f32 px1, f32 pz1,
|
||||||
|
f32 y_lo, f32 y_hi, const pxl8_mat4* vp) {
|
||||||
|
pxl8_vec3 corners[4] = {
|
||||||
|
{px0, y_lo, pz0}, {px1, y_lo, pz1},
|
||||||
|
{px1, y_hi, pz1}, {px0, y_hi, pz0},
|
||||||
|
};
|
||||||
|
|
||||||
|
const f32 NEAR_W = 0.001f;
|
||||||
|
pxl8_vec4 clip[4];
|
||||||
|
bool front[4];
|
||||||
|
i32 fc = 0;
|
||||||
|
|
||||||
|
for (i32 i = 0; i < 4; i++) {
|
||||||
|
clip[i] = pxl8_mat4_multiply_vec4(*vp, (pxl8_vec4){
|
||||||
|
corners[i].x, corners[i].y, corners[i].z, 1.0f});
|
||||||
|
front[i] = clip[i].w > NEAR_W;
|
||||||
|
if (front[i]) fc++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fc == 0) return (pxl8_rect){0, 0, 0, 0};
|
||||||
|
if (fc < 4) return (pxl8_rect){-1.0f, -1.0f, 1.0f, 1.0f};
|
||||||
|
|
||||||
|
pxl8_rect r = {1e30f, 1e30f, -1e30f, -1e30f};
|
||||||
|
|
||||||
|
for (i32 i = 0; i < 4; i++) {
|
||||||
|
f32 iw = 1.0f / clip[i].w;
|
||||||
|
f32 nx = clip[i].x * iw, ny = clip[i].y * iw;
|
||||||
|
if (nx < r.x0) r.x0 = nx; if (nx > r.x1) r.x1 = nx;
|
||||||
|
if (ny < r.y0) r.y0 = ny; if (ny > r.y1) r.y1 = ny;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.x0 < -1.0f) r.x0 = -1.0f;
|
||||||
|
if (r.y0 < -1.0f) r.y0 = -1.0f;
|
||||||
|
if (r.x1 > 1.0f) r.x1 = 1.0f;
|
||||||
|
if (r.y1 > 1.0f) r.y1 = 1.0f;
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void compute_edge_leafs(pxl8_loaded_chunk* lc) {
|
||||||
|
const f32 CHUNK_SIZE = 16.0f * 64.0f;
|
||||||
|
const pxl8_bsp* bsp = lc->chunk->bsp;
|
||||||
|
f32 cx0 = lc->cx * CHUNK_SIZE;
|
||||||
|
f32 cz0 = lc->cz * CHUNK_SIZE;
|
||||||
|
f32 cx1 = cx0 + CHUNK_SIZE;
|
||||||
|
f32 cz1 = cz0 + CHUNK_SIZE;
|
||||||
|
|
||||||
|
memset(lc->edges, 0, sizeof(lc->edges));
|
||||||
|
|
||||||
|
for (u32 i = 0; i < bsp->num_leafs; i++) {
|
||||||
|
const pxl8_bsp_leaf* leaf = &bsp->leafs[i];
|
||||||
|
if (bsp->leafs[i].contents == -1) continue;
|
||||||
|
|
||||||
|
if ((f32)leaf->mins[2] <= (f32)((i16)cz0) + 1.0f && lc->edges[0].count < 16)
|
||||||
|
lc->edges[0].leafs[lc->edges[0].count++] = (u16)i;
|
||||||
|
if ((f32)leaf->maxs[2] >= (f32)((i16)cz1) - 1.0f && lc->edges[1].count < 16)
|
||||||
|
lc->edges[1].leafs[lc->edges[1].count++] = (u16)i;
|
||||||
|
if ((f32)leaf->mins[0] <= (f32)((i16)cx0) + 1.0f && lc->edges[2].count < 16)
|
||||||
|
lc->edges[2].leafs[lc->edges[2].count++] = (u16)i;
|
||||||
|
if ((f32)leaf->maxs[0] >= (f32)((i16)cx1) - 1.0f && lc->edges[3].count < 16)
|
||||||
|
lc->edges[3].leafs[lc->edges[3].count++] = (u16)i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static i32 world_find_chunk(const pxl8_world* world, i32 cx, i32 cz) {
|
||||||
|
for (u32 i = 0; i < world->loaded_count; i++) {
|
||||||
|
if (world->loaded[i].cx == cx && world->loaded[i].cz == cz &&
|
||||||
|
world->loaded[i].chunk && world->loaded[i].chunk->bsp)
|
||||||
|
return (i32)i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void world_mark_leaf_faces(const pxl8_bsp* bsp, pxl8_bsp_render_state* rs, u32 leaf_idx) {
|
||||||
|
const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_idx];
|
||||||
|
for (u32 i = 0; i < leaf->num_marksurfaces; i++) {
|
||||||
|
u32 si = leaf->first_marksurface + i;
|
||||||
|
if (si < bsp->num_marksurfaces) {
|
||||||
|
u32 fi = bsp->marksurfaces[si];
|
||||||
|
if (fi < bsp->num_faces && rs)
|
||||||
|
rs->render_face_flags[fi] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool vis_try_enqueue(u8** vis, pxl8_rect** windows, world_vis_node* queue,
|
||||||
|
u32* tail, u32 max_queue,
|
||||||
|
u16 ci, u16 li, pxl8_rect nw) {
|
||||||
|
if (!vis[ci] || !windows[ci]) return false;
|
||||||
|
|
||||||
|
u32 byte = li >> 3;
|
||||||
|
u32 bit = 1 << (li & 7);
|
||||||
|
|
||||||
|
if (vis[ci][byte] & bit) {
|
||||||
|
pxl8_rect* ex = &windows[ci][li];
|
||||||
|
bool expanded = false;
|
||||||
|
if (nw.x0 < ex->x0) { ex->x0 = nw.x0; expanded = true; }
|
||||||
|
if (nw.y0 < ex->y0) { ex->y0 = nw.y0; expanded = true; }
|
||||||
|
if (nw.x1 > ex->x1) { ex->x1 = nw.x1; expanded = true; }
|
||||||
|
if (nw.y1 > ex->y1) { ex->y1 = nw.y1; expanded = true; }
|
||||||
|
if (expanded && *tail < max_queue)
|
||||||
|
queue[(*tail)++] = (world_vis_node){ci, li, *ex};
|
||||||
|
return expanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
vis[ci][byte] |= bit;
|
||||||
|
windows[ci][li] = nw;
|
||||||
|
if (*tail < max_queue)
|
||||||
|
queue[(*tail)++] = (world_vis_node){ci, li, nw};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void world_compute_visibility(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) {
|
||||||
|
const f32 CHUNK_SIZE = 16.0f * 64.0f;
|
||||||
|
const f32 PORTAL_Y_HI = 192.0f;
|
||||||
|
|
||||||
|
const pxl8_mat4* vp = pxl8_3d_get_view_proj(gfx);
|
||||||
|
|
||||||
|
i32 cam_ci = -1, cam_li = -1;
|
||||||
|
for (u32 i = 0; i < world->loaded_count; i++) {
|
||||||
|
pxl8_loaded_chunk* lc = &world->loaded[i];
|
||||||
|
if (!lc->chunk || !lc->chunk->bsp) continue;
|
||||||
|
const pxl8_bsp* bsp = lc->chunk->bsp;
|
||||||
|
f32 cx0 = lc->cx * CHUNK_SIZE;
|
||||||
|
f32 cz0 = lc->cz * CHUNK_SIZE;
|
||||||
|
if (camera_pos.x < cx0 || camera_pos.x >= cx0 + CHUNK_SIZE ||
|
||||||
|
camera_pos.z < cz0 || camera_pos.z >= cz0 + CHUNK_SIZE) continue;
|
||||||
|
if (bsp->bounds_max_x > bsp->bounds_min_x &&
|
||||||
|
(camera_pos.x < bsp->bounds_min_x || camera_pos.x >= bsp->bounds_max_x ||
|
||||||
|
camera_pos.z < bsp->bounds_min_z || camera_pos.z >= bsp->bounds_max_z))
|
||||||
|
continue;
|
||||||
|
i32 leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
|
||||||
|
if (leaf >= 0 && (u32)leaf < bsp->num_leafs &&
|
||||||
|
bsp->leafs[leaf].contents != -1 &&
|
||||||
|
(!(bsp->bounds_max_x > bsp->bounds_min_x) || bsp->leafs[leaf].contents == -2)) {
|
||||||
|
cam_ci = (i32)i;
|
||||||
|
cam_li = leaf;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cam_ci < 0) {
|
||||||
|
for (u32 i = 0; i < world->loaded_count; i++) {
|
||||||
|
pxl8_loaded_chunk* lc = &world->loaded[i];
|
||||||
|
if (!lc->chunk || !lc->chunk->bsp) continue;
|
||||||
|
const pxl8_bsp* bsp = lc->chunk->bsp;
|
||||||
|
if (bsp->bounds_max_x > bsp->bounds_min_x) continue;
|
||||||
|
i32 leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
|
||||||
|
if (leaf < 0 || (u32)leaf >= bsp->num_leafs) continue;
|
||||||
|
const pxl8_bsp_leaf* l = &bsp->leafs[leaf];
|
||||||
|
if (l->contents == -1) continue;
|
||||||
|
if (camera_pos.x < (f32)l->mins[0] || camera_pos.x > (f32)l->maxs[0] ||
|
||||||
|
camera_pos.z < (f32)l->mins[2] || camera_pos.z > (f32)l->maxs[2]) continue;
|
||||||
|
cam_ci = (i32)i;
|
||||||
|
cam_li = leaf;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cam_ci < 0 || !vp) {
|
||||||
|
for (u32 i = 0; i < world->loaded_count; i++) {
|
||||||
|
pxl8_bsp_render_state* rs = world->loaded[i].render_state;
|
||||||
|
if (!rs) continue;
|
||||||
|
if (rs->render_face_flags)
|
||||||
|
memset(rs->render_face_flags, 1, rs->num_faces);
|
||||||
|
rs->exterior = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(world->vis_bits, 0, sizeof(world->vis_bits));
|
||||||
|
memset(world->vis_ptrs, 0, sizeof(world->vis_ptrs));
|
||||||
|
memset(world->vis_win_ptrs, 0, sizeof(world->vis_win_ptrs));
|
||||||
|
|
||||||
|
u32 voff = 0, woff = 0;
|
||||||
|
for (u32 i = 0; i < world->loaded_count; i++) {
|
||||||
|
if (world->loaded[i].chunk && world->loaded[i].chunk->bsp) {
|
||||||
|
u32 nl = world->loaded[i].chunk->bsp->num_leafs;
|
||||||
|
u32 vbytes = (nl + 7) / 8;
|
||||||
|
if (voff + vbytes > PXL8_VIS_BYTES || woff + nl > PXL8_VIS_MAX_NODES)
|
||||||
|
continue;
|
||||||
|
world->vis_ptrs[i] = world->vis_bits + voff;
|
||||||
|
voff += vbytes;
|
||||||
|
world->vis_win_ptrs[i] = world->vis_windows + woff;
|
||||||
|
woff += nl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!world->vis_ptrs[cam_ci] || !world->vis_win_ptrs[cam_ci]) return;
|
||||||
|
|
||||||
|
for (u32 i = 0; i < world->loaded_count; i++) {
|
||||||
|
pxl8_bsp_render_state* rs = world->loaded[i].render_state;
|
||||||
|
if (!rs) continue;
|
||||||
|
if (rs->render_face_flags)
|
||||||
|
memset(rs->render_face_flags, 0, rs->num_faces);
|
||||||
|
rs->exterior = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 head = 0, tail = 0;
|
||||||
|
|
||||||
|
pxl8_rect full_screen = {-1.0f, -1.0f, 1.0f, 1.0f};
|
||||||
|
const pxl8_bsp* cam_bsp = world->loaded[cam_ci].chunk->bsp;
|
||||||
|
bool cam_exterior = cam_bsp->leafs[cam_li].contents == 0 &&
|
||||||
|
!(cam_bsp->bounds_max_x > cam_bsp->bounds_min_x);
|
||||||
|
|
||||||
|
world->vis_ptrs[cam_ci][cam_li >> 3] |= (1 << (cam_li & 7));
|
||||||
|
world->vis_win_ptrs[cam_ci][cam_li] = full_screen;
|
||||||
|
world->vis_queue[tail++] = (world_vis_node){(u16)cam_ci, (u16)cam_li, full_screen};
|
||||||
|
|
||||||
|
while (head < tail) {
|
||||||
|
world_vis_node cur = world->vis_queue[head++];
|
||||||
|
pxl8_loaded_chunk* lc = &world->loaded[cur.chunk_idx];
|
||||||
|
const pxl8_bsp* bsp = lc->chunk->bsp;
|
||||||
|
|
||||||
|
world_mark_leaf_faces(bsp, lc->render_state, cur.leaf_idx);
|
||||||
|
|
||||||
|
if (bsp->cell_portals && cur.leaf_idx < bsp->num_cell_portals) {
|
||||||
|
bool is_cam_leaf = (cur.chunk_idx == (u16)cam_ci && cur.leaf_idx == (u16)cam_li);
|
||||||
|
bool is_cam_chunk = (cur.chunk_idx == (u16)cam_ci);
|
||||||
|
const pxl8_bsp_cell_portals* cp = &bsp->cell_portals[cur.leaf_idx];
|
||||||
|
for (u8 pi = 0; pi < cp->num_portals; pi++) {
|
||||||
|
const pxl8_bsp_portal* p = &cp->portals[pi];
|
||||||
|
u32 target = p->target_leaf;
|
||||||
|
if (target >= bsp->num_leafs) continue;
|
||||||
|
if (bsp->leafs[target].contents == -1) continue;
|
||||||
|
if (is_cam_chunk && !pxl8_bsp_is_leaf_visible(bsp, cam_li, (i32)target)) continue;
|
||||||
|
|
||||||
|
if (is_cam_leaf || cam_exterior) {
|
||||||
|
vis_try_enqueue(world->vis_ptrs, world->vis_win_ptrs,
|
||||||
|
world->vis_queue, &tail, PXL8_VIS_MAX_QUEUE,
|
||||||
|
cur.chunk_idx, (u16)target, full_screen);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_rect psr = project_portal(p->x0, p->z0, p->x1, p->z1,
|
||||||
|
0.0f, PORTAL_Y_HI, vp);
|
||||||
|
if (!vr_valid(psr)) continue;
|
||||||
|
pxl8_rect nw = vr_intersect(cur.window, psr);
|
||||||
|
if (!vr_valid(nw)) continue;
|
||||||
|
|
||||||
|
vis_try_enqueue(world->vis_ptrs, world->vis_win_ptrs,
|
||||||
|
world->vis_queue, &tail, PXL8_VIS_MAX_QUEUE,
|
||||||
|
cur.chunk_idx, (u16)target, nw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pxl8_bsp_leaf* leaf = &bsp->leafs[cur.leaf_idx];
|
||||||
|
f32 chunk_x0 = lc->cx * CHUNK_SIZE;
|
||||||
|
f32 chunk_z0 = lc->cz * CHUNK_SIZE;
|
||||||
|
f32 chunk_x1 = chunk_x0 + CHUNK_SIZE;
|
||||||
|
f32 chunk_z1 = chunk_z0 + CHUNK_SIZE;
|
||||||
|
|
||||||
|
struct { i32 dx, dz; bool at_edge; f32 bnd; } dirs[4] = {
|
||||||
|
{ 0, -1, (f32)leaf->mins[2] <= (f32)((i16)chunk_z0) + 1.0f, chunk_z0 },
|
||||||
|
{ 0, 1, (f32)leaf->maxs[2] >= (f32)((i16)chunk_z1) - 1.0f, chunk_z1 },
|
||||||
|
{-1, 0, (f32)leaf->mins[0] <= (f32)((i16)chunk_x0) + 1.0f, chunk_x0 },
|
||||||
|
{ 1, 0, (f32)leaf->maxs[0] >= (f32)((i16)chunk_x1) - 1.0f, chunk_x1 },
|
||||||
|
};
|
||||||
|
|
||||||
|
static const u8 opposite_edge[4] = {1, 0, 3, 2};
|
||||||
|
|
||||||
|
for (u32 d = 0; d < 4; d++) {
|
||||||
|
if (!dirs[d].at_edge) continue;
|
||||||
|
i32 nci = world_find_chunk(world, lc->cx + dirs[d].dx, lc->cz + dirs[d].dz);
|
||||||
|
if (nci < 0) continue;
|
||||||
|
|
||||||
|
const pxl8_bsp* nbsp = world->loaded[nci].chunk->bsp;
|
||||||
|
const pxl8_edge_leafs* nedge = &world->loaded[nci].edges[opposite_edge[d]];
|
||||||
|
|
||||||
|
for (u8 k = 0; k < nedge->count; k++) {
|
||||||
|
u16 nl = nedge->leafs[k];
|
||||||
|
const pxl8_bsp_leaf* nleaf =  ->leafs[nl];
|
||||||
|
|
||||||
|
bool overlaps = false;
|
||||||
|
if (dirs[d].dx != 0) {
|
||||||
|
overlaps = leaf->maxs[2] > nleaf->mins[2] && leaf->mins[2] < nleaf->maxs[2];
|
||||||
|
} else {
|
||||||
|
overlaps = leaf->maxs[0] > nleaf->mins[0] && leaf->mins[0] < nleaf->maxs[0];
|
||||||
|
}
|
||||||
|
if (!overlaps) continue;
|
||||||
|
|
||||||
|
f32 bpx0, bpz0, bpx1, bpz1;
|
||||||
|
if (dirs[d].dx != 0) {
|
||||||
|
bpx0 = dirs[d].bnd; bpx1 = dirs[d].bnd;
|
||||||
|
i16 zlo = leaf->mins[2] > nleaf->mins[2] ? leaf->mins[2] : nleaf->mins[2];
|
||||||
|
i16 zhi = leaf->maxs[2] < nleaf->maxs[2] ? leaf->maxs[2] : nleaf->maxs[2];
|
||||||
|
bpz0 = (f32)zlo; bpz1 = (f32)zhi;
|
||||||
|
} else {
|
||||||
|
bpz0 = dirs[d].bnd; bpz1 = dirs[d].bnd;
|
||||||
|
i16 xlo = leaf->mins[0] > nleaf->mins[0] ? leaf->mins[0] : nleaf->mins[0];
|
||||||
|
i16 xhi = leaf->maxs[0] < nleaf->maxs[0] ? leaf->maxs[0] : nleaf->maxs[0];
|
||||||
|
bpx0 = (f32)xlo; bpx1 = (f32)xhi;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cam_exterior) {
|
||||||
|
vis_try_enqueue(world->vis_ptrs, world->vis_win_ptrs,
|
||||||
|
world->vis_queue, &tail, PXL8_VIS_MAX_QUEUE,
|
||||||
|
(u16)nci, (u16)nl, full_screen);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_rect psr = project_portal(bpx0, bpz0, bpx1, bpz1,
|
||||||
|
0.0f, PORTAL_Y_HI, vp);
|
||||||
|
if (!vr_valid(psr)) continue;
|
||||||
|
pxl8_rect nw = vr_intersect(cur.window, psr);
|
||||||
|
if (!vr_valid(nw)) continue;
|
||||||
|
|
||||||
|
vis_try_enqueue(world->vis_ptrs, world->vis_win_ptrs,
|
||||||
|
world->vis_queue, &tail, PXL8_VIS_MAX_QUEUE,
|
||||||
|
(u16)nci, (u16)nl, nw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) {
|
void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) {
|
||||||
if (!world || !gfx) return;
|
if (!world || !gfx) return;
|
||||||
|
|
||||||
if (world->active_chunk && world->active_chunk->bsp) {
|
if (world->active_chunk && world->active_chunk->bsp)
|
||||||
pxl8_3d_set_bsp(gfx, world->active_chunk->bsp);
|
pxl8_3d_set_bsp(gfx, world->active_chunk->bsp);
|
||||||
pxl8_bsp_render(gfx, world->active_chunk->bsp,
|
else
|
||||||
world->bsp_render_state, camera_pos);
|
|
||||||
} else {
|
|
||||||
pxl8_3d_set_bsp(gfx, NULL);
|
pxl8_3d_set_bsp(gfx, NULL);
|
||||||
|
|
||||||
|
world_compute_visibility(world, gfx, camera_pos);
|
||||||
|
|
||||||
|
for (u32 i = 0; i < world->loaded_count; i++) {
|
||||||
|
pxl8_loaded_chunk* lc = &world->loaded[i];
|
||||||
|
if (!lc->chunk || !lc->chunk->bsp || !lc->render_state) continue;
|
||||||
|
pxl8_bsp_render(gfx, lc->chunk->bsp, lc->render_state, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void apply_shared_materials(pxl8_world* world, pxl8_bsp_render_state* rs) {
|
||||||
|
for (u16 i = 0; i < 16; i++) {
|
||||||
|
if (world->shared_material_set[i]) {
|
||||||
|
pxl8_bsp_set_material(rs, i, &world->shared_materials[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void pxl8_world_sync(pxl8_world* world, pxl8_net* net) {
|
void pxl8_world_sync(pxl8_world* world, pxl8_net* net) {
|
||||||
if (!world || !net) return;
|
if (!world || !net) return;
|
||||||
|
|
||||||
u32 chunk_id = pxl8_net_chunk_id(net);
|
if (!pxl8_net_has_chunk(net)) {
|
||||||
|
u32 old_count = world->loaded_count;
|
||||||
|
world->loaded_count = 0;
|
||||||
|
world->active_chunk = NULL;
|
||||||
|
world->active_render_state = NULL;
|
||||||
|
for (u32 i = 0; i < old_count; i++) {
|
||||||
|
pxl8_bsp_render_state_destroy(world->loaded[i].render_state);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (chunk_id != 0) {
|
i32 center_cx = pxl8_net_chunk_cx(net);
|
||||||
pxl8_world_chunk* chunk = pxl8_world_chunk_cache_get_bsp(world->chunk_cache, chunk_id);
|
i32 center_cz = pxl8_net_chunk_cz(net);
|
||||||
if (chunk && chunk->bsp) {
|
|
||||||
if (world->active_chunk != chunk) {
|
|
||||||
world->active_chunk = chunk;
|
|
||||||
pxl8_debug("[CLIENT] Synced BSP chunk id=%u as active (verts=%u faces=%u)",
|
|
||||||
chunk_id, chunk->bsp->num_vertices, chunk->bsp->num_faces);
|
|
||||||
|
|
||||||
if (world->bsp_render_state) {
|
pxl8_loaded_chunk old_loaded[PXL8_WORLD_MAX_LOADED_CHUNKS];
|
||||||
pxl8_bsp_render_state_destroy(world->bsp_render_state);
|
u32 old_count = world->loaded_count;
|
||||||
world->bsp_render_state = NULL;
|
memcpy(old_loaded, world->loaded, sizeof(old_loaded));
|
||||||
|
|
||||||
|
pxl8_loaded_chunk new_loaded[PXL8_WORLD_MAX_LOADED_CHUNKS];
|
||||||
|
u32 new_count = 0;
|
||||||
|
pxl8_world_chunk* new_active_chunk = NULL;
|
||||||
|
pxl8_bsp_render_state* new_active_rs = NULL;
|
||||||
|
|
||||||
|
for (i32 dz = -2; dz <= 2; dz++) {
|
||||||
|
for (i32 dx = -2; dx <= 2; dx++) {
|
||||||
|
i32 cx = center_cx + dx;
|
||||||
|
i32 cz = center_cz + dz;
|
||||||
|
u32 id = pxl8_chunk_hash(cx, cz);
|
||||||
|
|
||||||
|
pxl8_world_chunk* chunk = pxl8_world_chunk_cache_get_bsp(world->chunk_cache, id);
|
||||||
|
if (!chunk || !chunk->bsp) continue;
|
||||||
|
|
||||||
|
pxl8_bsp_render_state* rs = NULL;
|
||||||
|
for (u32 j = 0; j < old_count; j++) {
|
||||||
|
if (old_loaded[j].active && old_loaded[j].cx == cx && old_loaded[j].cz == cz &&
|
||||||
|
old_loaded[j].chunk == chunk) {
|
||||||
|
rs = old_loaded[j].render_state;
|
||||||
|
old_loaded[j].active = false;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
world->bsp_render_state = pxl8_bsp_render_state_create(chunk->bsp->num_faces);
|
}
|
||||||
|
|
||||||
|
if (!rs) {
|
||||||
|
rs = pxl8_bsp_render_state_create(chunk->bsp->num_faces);
|
||||||
|
if (rs && rs->render_face_flags)
|
||||||
|
memset(rs->render_face_flags, 1, rs->num_faces);
|
||||||
|
apply_shared_materials(world, rs);
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 idx = new_count++;
|
||||||
|
new_loaded[idx] = (pxl8_loaded_chunk){
|
||||||
|
.chunk = chunk,
|
||||||
|
.render_state = rs,
|
||||||
|
.cx = cx,
|
||||||
|
.cz = cz,
|
||||||
|
.active = true,
|
||||||
|
};
|
||||||
|
compute_edge_leafs(&new_loaded[idx]);
|
||||||
|
|
||||||
|
if (dx == 0 && dz == 0) {
|
||||||
|
new_active_chunk = chunk;
|
||||||
|
new_active_rs = rs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (world->active_chunk != NULL) {
|
}
|
||||||
world->active_chunk = NULL;
|
|
||||||
if (world->bsp_render_state) {
|
memcpy(world->loaded, new_loaded, sizeof(new_loaded));
|
||||||
pxl8_bsp_render_state_destroy(world->bsp_render_state);
|
world->active_chunk = new_active_chunk;
|
||||||
world->bsp_render_state = NULL;
|
world->active_render_state = new_active_rs;
|
||||||
|
world->loaded_count = new_count;
|
||||||
|
|
||||||
|
for (u32 j = 0; j < old_count; j++) {
|
||||||
|
if (old_loaded[j].active) {
|
||||||
|
pxl8_bsp_render_state_destroy(old_loaded[j].render_state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ensure_bsp_render_state(pxl8_world* world) {
|
|
||||||
if (!world || world->bsp_render_state) return;
|
|
||||||
if (!world->active_chunk) return;
|
|
||||||
if (!world->active_chunk->bsp) return;
|
|
||||||
|
|
||||||
world->bsp_render_state = pxl8_bsp_render_state_create(world->active_chunk->bsp->num_faces);
|
|
||||||
}
|
|
||||||
|
|
||||||
void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_gfx_material* material) {
|
void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_gfx_material* material) {
|
||||||
if (!world || !material) return;
|
if (!world || !material || material_id >= 16) return;
|
||||||
|
|
||||||
ensure_bsp_render_state(world);
|
world->shared_materials[material_id] = *material;
|
||||||
if (!world->bsp_render_state) return;
|
world->shared_material_set[material_id] = true;
|
||||||
|
|
||||||
pxl8_bsp_set_material(world->bsp_render_state, material_id, material);
|
for (u32 i = 0; i < world->loaded_count; i++) {
|
||||||
|
if (world->loaded[i].render_state) {
|
||||||
|
pxl8_bsp_set_material(world->loaded[i].render_state, material_id, material);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void pxl8_world_set_sim_config(pxl8_world* world, const pxl8_sim_config* config) {
|
void pxl8_world_set_sim_config(pxl8_world* world, const pxl8_sim_config* config) {
|
||||||
|
|
@ -316,6 +729,7 @@ void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z) {
|
||||||
world->local_player.flags = PXL8_SIM_FLAG_ALIVE | PXL8_SIM_FLAG_PLAYER | PXL8_SIM_FLAG_GROUNDED;
|
world->local_player.flags = PXL8_SIM_FLAG_ALIVE | PXL8_SIM_FLAG_PLAYER | PXL8_SIM_FLAG_GROUNDED;
|
||||||
world->local_player.kind = 0;
|
world->local_player.kind = 0;
|
||||||
world->client_tick = 0;
|
world->client_tick = 0;
|
||||||
|
world->pointer_motion = (pxl8_vec2){0};
|
||||||
|
|
||||||
#ifdef PXL8_ASYNC_THREADS
|
#ifdef PXL8_ASYNC_THREADS
|
||||||
world->render_state[0] = world->local_player;
|
world->render_state[0] = world->local_player;
|
||||||
|
|
@ -323,6 +737,20 @@ void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void pxl8_world_set_look(pxl8_world* world, f32 yaw, f32 pitch) {
|
||||||
|
if (!world) return;
|
||||||
|
world->local_player.yaw = yaw;
|
||||||
|
world->local_player.pitch = pitch;
|
||||||
|
world->pointer_motion = (pxl8_vec2){.yaw = yaw, .pitch = pitch};
|
||||||
|
|
||||||
|
#ifdef PXL8_ASYNC_THREADS
|
||||||
|
world->render_state[0].yaw = yaw;
|
||||||
|
world->render_state[0].pitch = pitch;
|
||||||
|
world->render_state[1].yaw = yaw;
|
||||||
|
world->render_state[1].pitch = pitch;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
pxl8_sim_entity* pxl8_world_local_player(pxl8_world* world) {
|
pxl8_sim_entity* pxl8_world_local_player(pxl8_world* world) {
|
||||||
if (!world) return NULL;
|
if (!world) return NULL;
|
||||||
#ifdef PXL8_ASYNC_THREADS
|
#ifdef PXL8_ASYNC_THREADS
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,35 @@
|
||||||
|
|
||||||
#include "pxl8_entity.h"
|
#include "pxl8_entity.h"
|
||||||
#include "pxl8_gfx.h"
|
#include "pxl8_gfx.h"
|
||||||
|
#include "pxl8_gfx3d.h"
|
||||||
#include "pxl8_math.h"
|
#include "pxl8_math.h"
|
||||||
#include "pxl8_net.h"
|
#include "pxl8_net.h"
|
||||||
#include "pxl8_sim.h"
|
#include "pxl8_sim.h"
|
||||||
#include "pxl8_types.h"
|
#include "pxl8_types.h"
|
||||||
#include "pxl8_world_chunk.h"
|
#include "pxl8_world_chunk.h"
|
||||||
#include "pxl8_world_chunk_cache.h"
|
#include "pxl8_world_chunk_cache.h"
|
||||||
|
#include "pxl8_bsp_render.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define PXL8_WORLD_MAX_LOADED_CHUNKS 25
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u16 leafs[16];
|
||||||
|
u8 count;
|
||||||
|
} pxl8_edge_leafs;
|
||||||
|
|
||||||
|
typedef struct pxl8_loaded_chunk {
|
||||||
|
pxl8_world_chunk* chunk;
|
||||||
|
pxl8_bsp_render_state* render_state;
|
||||||
|
pxl8_edge_leafs edges[4];
|
||||||
|
i32 cx;
|
||||||
|
i32 cz;
|
||||||
|
bool active;
|
||||||
|
} pxl8_loaded_chunk;
|
||||||
|
|
||||||
typedef struct pxl8_world pxl8_world;
|
typedef struct pxl8_world pxl8_world;
|
||||||
|
|
||||||
pxl8_world* pxl8_world_create(void);
|
pxl8_world* pxl8_world_create(void);
|
||||||
|
|
@ -20,10 +38,6 @@ void pxl8_world_destroy(pxl8_world* world);
|
||||||
|
|
||||||
pxl8_world_chunk_cache* pxl8_world_get_chunk_cache(pxl8_world* world);
|
pxl8_world_chunk_cache* pxl8_world_get_chunk_cache(pxl8_world* world);
|
||||||
pxl8_world_chunk* pxl8_world_active_chunk(pxl8_world* world);
|
pxl8_world_chunk* pxl8_world_active_chunk(pxl8_world* world);
|
||||||
void pxl8_world_set_active_chunk(pxl8_world* world, pxl8_world_chunk* chunk);
|
|
||||||
|
|
||||||
pxl8_entity_pool* pxl8_world_entities(pxl8_world* world);
|
|
||||||
pxl8_entity pxl8_world_spawn(pxl8_world* world);
|
|
||||||
|
|
||||||
bool pxl8_world_point_solid(const pxl8_world* world, f32 x, f32 y, f32 z);
|
bool pxl8_world_point_solid(const pxl8_world* world, f32 x, f32 y, f32 z);
|
||||||
pxl8_ray pxl8_world_ray(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to);
|
pxl8_ray pxl8_world_ray(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to);
|
||||||
|
|
@ -37,6 +51,7 @@ void pxl8_world_set_bsp_material(pxl8_world* world, u16 material_id, const pxl8_
|
||||||
|
|
||||||
void pxl8_world_set_sim_config(pxl8_world* world, const pxl8_sim_config* config);
|
void pxl8_world_set_sim_config(pxl8_world* world, const pxl8_sim_config* config);
|
||||||
void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z);
|
void pxl8_world_init_local_player(pxl8_world* world, f32 x, f32 y, f32 z);
|
||||||
|
void pxl8_world_set_look(pxl8_world* world, f32 yaw, f32 pitch);
|
||||||
pxl8_sim_entity* pxl8_world_local_player(pxl8_world* world);
|
pxl8_sim_entity* pxl8_world_local_player(pxl8_world* world);
|
||||||
pxl8_sim_world pxl8_world_sim_world(const pxl8_world* world, pxl8_vec3 pos);
|
pxl8_sim_world pxl8_world_sim_world(const pxl8_world* world, pxl8_vec3 pos);
|
||||||
void pxl8_world_predict(pxl8_world* world, pxl8_net* net, const pxl8_input_msg* input, f32 dt);
|
void pxl8_world_predict(pxl8_world* world, pxl8_net* net, const pxl8_input_msg* input, f32 dt);
|
||||||
|
|
|
||||||
|
|
@ -179,13 +179,8 @@ static pxl8_bsp* assembly_to_bsp(pxl8_world_chunk_assembly* a) {
|
||||||
pxl8_stream s = pxl8_stream_create(a->data, (u32)a->data_size);
|
pxl8_stream s = pxl8_stream_create(a->data, (u32)a->data_size);
|
||||||
|
|
||||||
pxl8_bsp_wire_header wire_hdr;
|
pxl8_bsp_wire_header wire_hdr;
|
||||||
pxl8_protocol_deserialize_bsp_wire_header(a->data, 44, &wire_hdr);
|
pxl8_protocol_deserialize_bsp_wire_header(a->data, 48, &wire_hdr);
|
||||||
s.offset = 44;
|
s.offset = 48;
|
||||||
|
|
||||||
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) {
|
if (wire_hdr.num_vertices > 0) {
|
||||||
bsp->vertices = pxl8_calloc(wire_hdr.num_vertices, sizeof(pxl8_bsp_vertex));
|
bsp->vertices = pxl8_calloc(wire_hdr.num_vertices, sizeof(pxl8_bsp_vertex));
|
||||||
|
|
@ -273,21 +268,42 @@ static pxl8_bsp* assembly_to_bsp(pxl8_world_chunk_assembly* a) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_debug("Deserialized BSP: %u verts, %u faces, %u nodes, %u leafs",
|
if (wire_hdr.num_heightfield > 0) {
|
||||||
bsp->num_vertices, bsp->num_faces, bsp->num_nodes, bsp->num_leafs);
|
bsp->heightfield = pxl8_calloc(wire_hdr.num_heightfield, sizeof(f32));
|
||||||
|
bsp->num_heightfield = wire_hdr.num_heightfield;
|
||||||
|
for (u32 i = 0; i < wire_hdr.num_heightfield; i++) {
|
||||||
|
u32 raw = pxl8_read_u32_be(&s);
|
||||||
|
memcpy(&bsp->heightfield[i], &raw, sizeof(f32));
|
||||||
|
}
|
||||||
|
bsp->heightfield_w = pxl8_read_u16_be(&s);
|
||||||
|
bsp->heightfield_h = pxl8_read_u16_be(&s);
|
||||||
|
u32 ox_raw = pxl8_read_u32_be(&s);
|
||||||
|
memcpy(&bsp->heightfield_ox, &ox_raw, sizeof(f32));
|
||||||
|
u32 oz_raw = pxl8_read_u32_be(&s);
|
||||||
|
memcpy(&bsp->heightfield_oz, &oz_raw, sizeof(f32));
|
||||||
|
u32 cs_raw = pxl8_read_u32_be(&s);
|
||||||
|
memcpy(&bsp->heightfield_cell_size, &cs_raw, sizeof(f32));
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 raw;
|
||||||
|
raw = pxl8_read_u32_be(&s);
|
||||||
|
memcpy(&bsp->bounds_min_x, &raw, sizeof(f32));
|
||||||
|
raw = pxl8_read_u32_be(&s);
|
||||||
|
memcpy(&bsp->bounds_min_z, &raw, sizeof(f32));
|
||||||
|
raw = pxl8_read_u32_be(&s);
|
||||||
|
memcpy(&bsp->bounds_max_x, &raw, sizeof(f32));
|
||||||
|
raw = pxl8_read_u32_be(&s);
|
||||||
|
memcpy(&bsp->bounds_max_z, &raw, sizeof(f32));
|
||||||
|
|
||||||
return bsp;
|
return bsp;
|
||||||
}
|
}
|
||||||
|
|
||||||
static pxl8_result assemble_bsp(pxl8_world_chunk_cache* cache, pxl8_world_chunk_assembly* a) {
|
static pxl8_result assemble_bsp(pxl8_world_chunk_cache* cache, pxl8_world_chunk_assembly* a) {
|
||||||
pxl8_debug("[CLIENT] assemble_bsp: id=%u data_size=%zu", a->id, a->data_size);
|
|
||||||
pxl8_bsp* bsp = assembly_to_bsp(a);
|
pxl8_bsp* bsp = assembly_to_bsp(a);
|
||||||
if (!bsp) {
|
if (!bsp) {
|
||||||
pxl8_debug("[CLIENT] assemble_bsp: assembly_to_bsp returned NULL!");
|
|
||||||
assembly_reset(a);
|
assembly_reset(a);
|
||||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
pxl8_debug("[CLIENT] assemble_bsp: BSP created with %u verts %u faces", bsp->num_vertices, bsp->num_faces);
|
|
||||||
|
|
||||||
pxl8_world_chunk_cache_entry* entry = find_entry_bsp(cache, a->id);
|
pxl8_world_chunk_cache_entry* entry = find_entry_bsp(cache, a->id);
|
||||||
if (entry) {
|
if (entry) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue