add networking, 3d improvements, reorganize src structure

This commit is contained in:
asrael 2026-01-17 22:52:36 -06:00
parent 39b604b333
commit 415d424057
122 changed files with 5358 additions and 721 deletions

View file

@ -1,126 +0,0 @@
#pragma once
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include "pxl8_types.h"
typedef struct {
const u8* bytes;
u32 offset;
u32 size;
bool overflow;
} pxl8_stream;
static inline pxl8_stream pxl8_stream_create(const u8* bytes, u32 size) {
return (pxl8_stream){
.bytes = bytes,
.offset = 0,
.size = size,
.overflow = false
};
}
static inline bool pxl8_stream_can_read(const pxl8_stream* stream, u32 count) {
return !stream->overflow && stream->offset + count <= stream->size;
}
static inline bool pxl8_stream_has_overflow(const pxl8_stream* stream) {
return stream->overflow;
}
static inline void pxl8_stream_seek(pxl8_stream* stream, u32 offset) {
stream->offset = offset;
}
static inline u32 pxl8_stream_position(const pxl8_stream* stream) {
return stream->offset;
}
static inline u8 pxl8_read_u8(pxl8_stream* stream) {
if (stream->offset + 1 > stream->size) { stream->overflow = true; return 0; }
return stream->bytes[stream->offset++];
}
static inline u16 pxl8_read_u16(pxl8_stream* stream) {
if (stream->offset + 2 > stream->size) { stream->overflow = true; return 0; }
u16 val = (u16)stream->bytes[stream->offset] | ((u16)stream->bytes[stream->offset + 1] << 8);
stream->offset += 2;
return val;
}
static inline u32 pxl8_read_u32(pxl8_stream* stream) {
if (stream->offset + 4 > stream->size) { stream->overflow = true; return 0; }
u32 val = (u32)stream->bytes[stream->offset] |
((u32)stream->bytes[stream->offset + 1] << 8) |
((u32)stream->bytes[stream->offset + 2] << 16) |
((u32)stream->bytes[stream->offset + 3] << 24);
stream->offset += 4;
return val;
}
static inline i16 pxl8_read_i16(pxl8_stream* stream) {
return (i16)pxl8_read_u16(stream);
}
static inline i32 pxl8_read_i32(pxl8_stream* stream) {
return (i32)pxl8_read_u32(stream);
}
static inline f32 pxl8_read_f32(pxl8_stream* stream) {
u32 val = pxl8_read_u32(stream);
f32 result;
memcpy(&result, &val, sizeof(f32));
return result;
}
static inline void pxl8_read_bytes(pxl8_stream* stream, void* dest, u32 count) {
if (stream->offset + count > stream->size) { stream->overflow = true; return; }
for (u32 i = 0; i < count; i++) {
((u8*)dest)[i] = stream->bytes[stream->offset++];
}
}
static inline void pxl8_skip_bytes(pxl8_stream* stream, u32 count) {
if (stream->offset + count > stream->size) { stream->overflow = true; return; }
stream->offset += count;
}
static inline const u8* pxl8_read_ptr(pxl8_stream* stream, u32 count) {
if (stream->offset + count > stream->size) { stream->overflow = true; return NULL; }
const u8* ptr = &stream->bytes[stream->offset];
stream->offset += count;
return ptr;
}
#ifdef __cplusplus
extern "C" {
#endif
pxl8_result pxl8_io_create_directory(const char* path);
bool pxl8_io_file_exists(const char* path);
void pxl8_io_free_binary_data(u8* data);
void pxl8_io_free_file_content(char* content);
f64 pxl8_io_get_file_modified_time(const char* path);
pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, size_t* size);
pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size);
pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, size_t size);
pxl8_result pxl8_io_write_file(const char* path, const char* content, size_t size);
bool pxl8_key_down(const pxl8_input_state* input, const char* key_name);
bool pxl8_key_pressed(const pxl8_input_state* input, const char* key_name);
bool pxl8_key_released(const pxl8_input_state* input, const char* key_name);
i32 pxl8_mouse_dx(const pxl8_input_state* input);
i32 pxl8_mouse_dy(const pxl8_input_state* input);
bool pxl8_mouse_pressed(const pxl8_input_state* input, i32 button);
bool pxl8_mouse_released(const pxl8_input_state* input, i32 button);
i32 pxl8_mouse_wheel_x(const pxl8_input_state* input);
i32 pxl8_mouse_wheel_y(const pxl8_input_state* input);
i32 pxl8_mouse_x(const pxl8_input_state* input);
i32 pxl8_mouse_y(const pxl8_input_state* input);
#ifdef __cplusplus
}
#endif

View file

@ -1,33 +0,0 @@
#pragma once
#include "pxl8_3d_camera.h"
#include "pxl8_math.h"
#include "pxl8_mesh.h"
#include "pxl8_types.h"
typedef struct pxl8_gfx pxl8_gfx;
typedef struct pxl8_3d_uniforms {
u8 ambient;
u8 fog_color;
f32 fog_density;
f32 time;
} pxl8_3d_uniforms;
#ifdef __cplusplus
extern "C" {
#endif
void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms);
void pxl8_3d_clear(pxl8_gfx* gfx, u8 color);
void pxl8_3d_clear_depth(pxl8_gfx* gfx);
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, pxl8_mat4 model, pxl8_material material);
void pxl8_3d_draw_mesh_wireframe(pxl8_gfx* gfx, const pxl8_mesh* mesh, pxl8_mat4 model, u8 color);
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);
#ifdef __cplusplus
}
#endif

View file

@ -1,65 +0,0 @@
local ffi = require("ffi")
local C = ffi.C
local core = require("pxl8.core")
local vfx = {}
function vfx.raster_bars(bars, time)
local c_bars = ffi.new("pxl8_raster_bar[?]", #bars)
for i, bar in ipairs(bars) do
c_bars[i-1].base_y = bar.base_y or 0
c_bars[i-1].amplitude = bar.amplitude or 10
c_bars[i-1].height = bar.height or 5
c_bars[i-1].speed = bar.speed or 1.0
c_bars[i-1].phase = bar.phase or 0
c_bars[i-1].color = bar.color or 15
c_bars[i-1].fade_color = bar.fade_color or bar.color or 15
end
C.pxl8_vfx_raster_bars(core.gfx, c_bars, #bars, time)
end
function vfx.plasma(time, scale1, scale2, palette_offset)
C.pxl8_vfx_plasma(core.gfx, time, scale1 or 0.05, scale2 or 0.03, palette_offset or 0)
end
function vfx.rotozoom(angle, zoom, cx, cy)
local width = C.pxl8_gfx_get_width(core.gfx)
local height = C.pxl8_gfx_get_height(core.gfx)
C.pxl8_vfx_rotozoom(core.gfx, angle, zoom, cx or width/2, cy or height/2)
end
function vfx.tunnel(time, speed, twist)
C.pxl8_vfx_tunnel(core.gfx, time, speed or 2.0, twist or 0.5)
end
function vfx.explosion(ps, x, y, color, force)
C.pxl8_vfx_explosion(ps, x, y, color or 15, force or 200.0)
end
function vfx.fire(ps, x, y, width, palette_start)
C.pxl8_vfx_fire(ps, x, y, width or 50, palette_start or 64)
end
function vfx.rain(ps, width, wind)
local w = width or C.pxl8_gfx_get_width(core.gfx)
C.pxl8_vfx_rain(ps, w, wind or 0.0)
end
function vfx.smoke(ps, x, y, color)
C.pxl8_vfx_smoke(ps, x, y, color or 8)
end
function vfx.snow(ps, width, wind)
local w = width or C.pxl8_gfx_get_width(core.gfx)
C.pxl8_vfx_snow(ps, w, wind or 10.0)
end
function vfx.sparks(ps, x, y, color)
C.pxl8_vfx_sparks(ps, x, y, color or 15)
end
function vfx.starfield(ps, speed, spread)
C.pxl8_vfx_starfield(ps, speed or 5.0, spread or 500.0)
end
return vfx

View file

@ -1,7 +1,7 @@
(local pxl8 (require :pxl8))
(local menu (require :mod.menu))
(local music (require :mod.music))
(local worldgen (require :mod.worldgen))
(local first_person3d (require :mod.first_person3d))
(var time 0)
(var active-demo :logo)
@ -34,13 +34,12 @@
(set particles (pxl8.create_particles 1000))
(set particles2 (pxl8.create_particles 500))
(music.init)
(music.start)
(worldgen.init)))
(first_person3d.init)))
(global update (fn [dt]
(when (pxl8.key_pressed "escape")
(menu.toggle)
(when (= active-demo :worldgen)
(when (= active-demo :first_person3d)
(pxl8.set_relative_mouse_mode (not (menu.is-paused)))))
(when (not (menu.is-paused))
@ -50,9 +49,9 @@
(transition:update dt)
(when (transition:is_complete)
(when transition-pending
(when (and (= active-demo :worldgen) (not= transition-pending :worldgen))
(when (and (= active-demo :first_person3d) (not= transition-pending :first_person3d))
(pxl8.set_relative_mouse_mode false))
(when (and (not= active-demo :worldgen) (= transition-pending :worldgen))
(when (and (not= active-demo :first_person3d) (= transition-pending :first_person3d))
(pxl8.set_relative_mouse_mode true))
(set active-demo transition-pending)
(set transition-pending nil)
@ -69,12 +68,14 @@
(when (pxl8.key_pressed "5") (switch-demo :fire))
(when (pxl8.key_pressed "6") (switch-demo :rain))
(when (pxl8.key_pressed "7") (switch-demo :snow))
(when (pxl8.key_pressed "8") (switch-demo :worldgen))
(when (pxl8.key_pressed "8") (switch-demo :first_person3d))
(when (pxl8.key_pressed "=")
(set use-famicube-palette? (not use-famicube-palette?))
(local palette-path (if use-famicube-palette? "res/palettes/famicube.ase" "res/sprites/pxl8_logo.ase"))
(pxl8.load_palette palette-path))
(music.update dt)
(case active-demo
:logo (do
(set logo-x (+ logo-x (* logo-dx dt)))
@ -91,9 +92,7 @@
(when (> logo-y 296)
(set logo-y 296)
(set logo-dy (- (math.abs logo-dy)))))
:worldgen (worldgen.update dt))
(music.update dt)
:first_person3d (first_person3d.update dt))
(when particles
(particles:update dt))
@ -173,7 +172,7 @@
(set snow-init? true))
(particles:render)))
:worldgen (worldgen.frame)
:first_person3d (first_person3d.frame)
_ (pxl8.clear 0))

393
demo/mod/first_person3d.fnl Normal file
View file

@ -0,0 +1,393 @@
(local pxl8 (require :pxl8))
(local effects (require :pxl8.effects))
(local net (require :pxl8.net))
(local sky (require :mod.sky))
(local bob-amount 4.0)
(local bob-speed 8.0)
(local cam-smoothing 0.25)
(local cell-size 64)
(local cursor-sensitivity 0.008)
(local gravity -800)
(local grid-size 64)
(local ground-y 64)
(local jump-force 175)
(local land-recovery-speed 20)
(local land-squash-amount -4)
(local max-pitch 1.5)
(local move-speed 200)
(local turn-speed 2.0)
(local sim-tick-rate 60)
(local sim-dt (/ 1.0 sim-tick-rate))
(local history-size 128)
(local correction-threshold 1.0)
(var auto-run? false)
(var auto-run-cancel-key nil)
(var bob-time 0)
(var cam-pitch 0)
(var cam-x 1000)
(var cam-y 64)
(var cam-yaw 0)
(var cam-z 1000)
(var camera nil)
(var cursor-look? true)
(var grounded? true)
(var land-squash 0)
(var light-time 0)
(var network nil)
(var smooth-cam-x 1000)
(var smooth-cam-z 1000)
(var velocity-y 0)
(var world nil)
(var fps-avg 0)
(var fps-sample-count 0)
(local FIREBALL_COLOR 184)
(fn init-fireball-palette []
(for [i 0 7]
(let [t (/ i 7)
r (math.floor (+ 0xFF (* t 0)))
g (math.floor (+ 0x60 (* t (- 0xE0 0x60))))
b (math.floor (+ 0x10 (* t (- 0x80 0x10))))]
(pxl8.set_palette_rgb (+ FIREBALL_COLOR i) r g b))))
(var client-tick 0)
(var last-processed-tick 0)
(var time-accumulator 0)
(var position-history {})
(var pending-inputs {})
(fn history-idx [tick]
(+ 1 (% tick history-size)))
(fn store-position [tick x z yaw]
(tset position-history (history-idx tick) {:tick tick :x x :z z :yaw yaw}))
(fn get-position [tick]
(let [entry (. position-history (history-idx tick))]
(when (and entry (= entry.tick tick))
entry)))
(fn store-pending-input [tick input]
(tset pending-inputs (history-idx tick) {:tick tick :input input}))
(fn get-pending-input [tick]
(let [entry (. pending-inputs (history-idx tick))]
(when (and entry (= entry.tick tick))
entry.input)))
(fn apply-movement [x z yaw input]
(var new-x x)
(var new-z z)
(let [move-forward (or input.move_y 0)
move-right (or input.move_x 0)]
(when (or (not= move-forward 0) (not= move-right 0))
(let [forward-x (- (math.sin yaw))
forward-z (- (math.cos yaw))
right-x (math.cos yaw)
right-z (- (math.sin yaw))
len (math.sqrt (+ (* move-forward move-forward) (* move-right move-right)))
norm-forward (/ move-forward len)
norm-right (/ move-right len)
move-delta (* move-speed sim-dt)]
(set new-x (+ new-x (* move-delta (+ (* forward-x norm-forward) (* right-x norm-right)))))
(set new-z (+ new-z (* move-delta (+ (* forward-z norm-forward) (* right-z norm-right))))))))
(values new-x new-z))
(fn init []
(set camera (pxl8.create_camera_3d))
(set world (pxl8.create_world))
(sky.generate-stars 12345)
(init-fireball-palette)
(set network (net.Net.new {:port 7777}))
(when network
(network:connect)
(network:spawn cam-x cam-y cam-z cam-yaw cam-pitch))
(let [result (world:generate {
:type pxl8.PROCGEN_ROOMS
:width 64
:height 64
:seed 42
:min_room_size 5
:max_room_size 10
:num_rooms 20})]
(if (< result 0)
(pxl8.error (.. "Failed to generate rooms - result: " result))
(let [floor-tex (pxl8.procgen_tex {:name "floor"
:seed 11111
:width 64
:height 64
:base_color 19})
wall-tex (pxl8.procgen_tex {:name "wall"
:seed 12345
:width 64
:height 64
:base_color 4})
sky-tex (pxl8.create_texture [0] 1 1)]
(let [result (world:apply_textures [
{:name "floor"
:texture_id floor-tex
:rule (fn [normal] (> normal.y 0.7))}
{:name "ceiling"
:texture_id sky-tex
:rule (fn [normal] (< normal.y -0.7))}
{:name "wall"
:texture_id wall-tex
:rule (fn [normal] (and (<= normal.y 0.7) (>= normal.y -0.7)))}])]
(when (< result 0)
(pxl8.error (.. "Failed to apply textures - result: " result))))))))
(fn sample-input []
(var move-forward 0)
(var move-right 0)
(when (pxl8.key_pressed "`")
(set auto-run? (not auto-run?))
(when (and auto-run? (pxl8.key_down "w"))
(set auto-run-cancel-key "w")))
(when (and auto-run? (not auto-run-cancel-key) (or (pxl8.key_down "w") (pxl8.key_down "s")))
(set auto-run? false)
(when (pxl8.key_down "s")
(set auto-run-cancel-key "s")))
(when (and auto-run-cancel-key (not (pxl8.key_down auto-run-cancel-key)))
(set auto-run-cancel-key nil))
(when (or (pxl8.key_down "w") auto-run?)
(set move-forward (+ move-forward 1)))
(when (and (pxl8.key_down "s") (not= auto-run-cancel-key "s"))
(set move-forward (- move-forward 1)))
(when (pxl8.key_down "a")
(set move-right (- move-right 1)))
(when (pxl8.key_down "d")
(set move-right (+ move-right 1)))
{:move_x move-right
:move_y move-forward
:look_dx (pxl8.mouse_dx)
:look_dy (pxl8.mouse_dy)})
(fn reconcile [server-tick server-x server-z]
(let [predicted (get-position server-tick)]
(when predicted
(let [dx (- predicted.x server-x)
dz (- predicted.z server-z)
error (math.sqrt (+ (* dx dx) (* dz dz)))]
(when (> error correction-threshold)
(set cam-x server-x)
(set cam-z server-z)
(for [t (+ server-tick 1) client-tick]
(let [input (get-pending-input t)
hist (get-position t)]
(when (and input hist)
(let [(new-x new-z) (apply-movement cam-x cam-z hist.yaw input)]
(set cam-x new-x)
(set cam-z new-z)
(store-position t cam-x cam-z hist.yaw))))))))))
(fn update [dt]
(let [fps (pxl8.get_fps)]
(set fps-sample-count (+ fps-sample-count 1))
(set fps-avg (+ (* fps-avg (/ (- fps-sample-count 1) fps-sample-count))
(/ fps fps-sample-count)))
(when (>= fps-sample-count 120)
(set fps-sample-count 0)
(set fps-avg 0)))
(when (world:is_loaded)
(let [input (sample-input)
grid-max (* grid-size cell-size)
movement-yaw cam-yaw]
(set time-accumulator (+ time-accumulator dt))
(while (>= time-accumulator sim-dt)
(set time-accumulator (- time-accumulator sim-dt))
(set client-tick (+ client-tick 1))
(store-pending-input client-tick input)
(let [(new-x new-z) (apply-movement cam-x cam-z movement-yaw input)]
(when (and (>= new-x 0) (<= new-x grid-max)
(>= new-z 0) (<= new-z grid-max))
(let [(resolved-x _ resolved-z) (world:resolve_collision cam-x cam-y cam-z new-x cam-y new-z 5)]
(set cam-x resolved-x)
(set cam-z resolved-z)))
(store-position client-tick cam-x cam-z movement-yaw)))
(when cursor-look?
(set cam-yaw (- cam-yaw (* input.look_dx cursor-sensitivity)))
(set cam-pitch (math.max (- max-pitch)
(math.min max-pitch
(- cam-pitch (* input.look_dy cursor-sensitivity))))))
(when (and (not cursor-look?) (pxl8.key_down "up"))
(set cam-pitch (math.min max-pitch (+ cam-pitch (* turn-speed dt)))))
(when (and (not cursor-look?) (pxl8.key_down "down"))
(set cam-pitch (math.max (- max-pitch) (- cam-pitch (* turn-speed dt)))))
(when (and (not cursor-look?) (pxl8.key_down "left"))
(set cam-yaw (+ cam-yaw (* turn-speed dt))))
(when (and (not cursor-look?) (pxl8.key_down "right"))
(set cam-yaw (- cam-yaw (* turn-speed dt))))
(when network
(let [(ok err) (pcall (fn []
(network:send_input {:move_x input.move_x
:move_y input.move_y
:look_dx input.look_dx
:look_dy input.look_dy
:yaw movement-yaw
:tick client-tick})
(network:update dt)
(when (network:poll)
(let [snapshot (network:snapshot)]
(when (and snapshot (> snapshot.tick last-processed-tick))
(set last-processed-tick snapshot.tick)
(let [player-id (network:player_id)]
(when (> player-id 0)
(let [curr (network:entity_userdata player-id)]
(when curr
(let [srv-x (pxl8.unpack_f32_be curr 0)
srv-z (pxl8.unpack_f32_be curr 8)]
(reconcile snapshot.tick srv-x srv-z)))))))))))]
(when (not ok)
(pxl8.error (.. "Network error: " err)))))
(set smooth-cam-x (+ (* smooth-cam-x (- 1 cam-smoothing)) (* cam-x cam-smoothing)))
(set smooth-cam-z (+ (* smooth-cam-z (- 1 cam-smoothing)) (* cam-z cam-smoothing)))
(when (and (pxl8.key_pressed "space") grounded?)
(set velocity-y jump-force)
(set grounded? false))
(set velocity-y (+ velocity-y (* gravity dt)))
(set cam-y (+ cam-y (* velocity-y dt)))
(when (<= cam-y ground-y)
(when (not grounded?)
(set land-squash land-squash-amount))
(set cam-y ground-y)
(set velocity-y 0)
(set grounded? true))
(when (< land-squash 0)
(set land-squash (math.min 0 (+ land-squash (* land-recovery-speed dt)))))
(let [moving (or (not= input.move_x 0) (not= input.move_y 0))]
(if (and moving grounded?)
(set bob-time (+ bob-time (* dt bob-speed)))
(let [target-phase (* (math.floor (/ bob-time math.pi)) math.pi)]
(set bob-time (+ (* bob-time 0.8) (* target-phase 0.2))))))
(set light-time (+ light-time (* dt 0.15))))))
(fn frame []
(pxl8.clear 1)
(when (not camera)
(pxl8.error "camera is nil!"))
(when (not world)
(pxl8.error "world is nil!"))
(when (and world (not (world:is_loaded)))
(pxl8.text "World not loaded yet..." 5 30 12))
(when (and camera world (world:is_loaded))
(let [bob-offset (* (math.sin bob-time) bob-amount)
eye-y (+ cam-y bob-offset land-squash)
forward-x (- (math.sin cam-yaw))
forward-z (- (math.cos cam-yaw))
target-x (+ smooth-cam-x forward-x)
target-y (+ eye-y (math.sin cam-pitch))
target-z (+ smooth-cam-z forward-z)
aspect (/ (pxl8.get_width) (pxl8.get_height))]
(camera:lookat [smooth-cam-x eye-y smooth-cam-z]
[target-x target-y target-z]
[0 1 0])
(camera:set_perspective 1.047 aspect 1.0 4096.0)
(let [light-pulse (+ 0.7 (* 0.3 (math.sin (* light-time 2))))
forward-x (- (math.sin cam-yaw))
forward-z (- (math.cos cam-yaw))
light-x (+ smooth-cam-x (* 150 forward-x) (* 50 (math.cos light-time)))
light-z (+ smooth-cam-z (* 150 forward-z) (* 50 (math.sin light-time)))
light-y (+ eye-y 30)]
(pxl8.begin_frame_3d camera {
:ambient 80
:celestial_dir [0.5 -0.8 0.3]
:celestial_intensity 0.5
:lights [{:x light-x :y light-y :z light-z
:r 255 :g 200 :b 150
:intensity (* 255 light-pulse)
:radius 400}]})
(pxl8.clear_depth)
(sky.update-gradient 1 2 6 6 10 18)
(sky.render smooth-cam-x eye-y smooth-cam-z)
(pxl8.clear_depth)
(world:render [smooth-cam-x eye-y smooth-cam-z])
(pxl8.end_frame_3d)
(let [dx (- light-x smooth-cam-x)
dy (- light-y eye-y)
dz (- light-z smooth-cam-z)
dist (math.sqrt (+ (* dx dx) (* dy dy) (* dz dz)))]
(when (> dist 1)
(let [inv-dist (/ 1 dist)
dir-x (* dx inv-dist)
dir-y (* dy inv-dist)
dir-z (* dz inv-dist)
cos-yaw (math.cos cam-yaw)
sin-yaw (math.sin cam-yaw)
cos-pitch (math.cos cam-pitch)
sin-pitch (math.sin cam-pitch)
rx (+ (* dir-x cos-yaw) (* dir-z sin-yaw))
rz (+ (* (- dir-x) sin-yaw) (* dir-z cos-yaw))
ry (- (* dir-y cos-pitch) (* rz sin-pitch))
fz (+ (* dir-y sin-pitch) (* rz cos-pitch))]
(when (> fz 0.01)
(let [width (pxl8.get_width)
height (pxl8.get_height)
fov 1.047
half-fov-tan (math.tan (* fov 0.5))
ndc-x (/ rx (* fz half-fov-tan aspect))
ndc-y (/ ry (* fz half-fov-tan))
sx (math.floor (* (+ 1 ndc-x) 0.5 width))
sy (math.floor (* (- 1 ndc-y) 0.5 height))
screen-size (/ 400 dist)
base-radius (math.max 2 (math.min 12 (math.floor screen-size)))
pulse-int (math.floor (* 180 light-pulse))]
(when (and (>= sx 0) (< sx width) (>= sy 0) (< sy height))
(effects.glows [
{:x sx :y sy :radius (+ base-radius 2) :intensity (/ pulse-int 5) :color (+ FIREBALL_COLOR 1) :shape effects.GLOW_CIRCLE}
{:x sx :y sy :radius base-radius :intensity pulse-int :color (+ FIREBALL_COLOR 5) :shape effects.GLOW_DIAMOND}]))))))))
(sky.render-stars cam-yaw cam-pitch 1.0)
(let [cx (/ (pxl8.get_width) 2)
cy (/ (pxl8.get_height) 2)
crosshair-size 4
red-color 18]
(pxl8.line (- cx crosshair-size) cy (+ cx crosshair-size) cy red-color)
(pxl8.line cx (- cy crosshair-size) cx (+ cy crosshair-size) red-color))
(pxl8.text (.. "fps: " (string.format "%.0f" fps-avg)) 5 5 12)
(pxl8.text (.. "pos: " (string.format "%.0f" cam-x) ","
(string.format "%.0f" cam-y) ","
(string.format "%.0f" cam-z)) 5 15 12))))
{:init init
:update update
:frame frame}

View file

@ -1,4 +1,5 @@
(local pxl8 (require :pxl8))
(local music (require :mod.music))
(var paused false)
(var gui nil)
@ -36,12 +37,18 @@
(when gui
(gui:begin_frame)
(pxl8.gui_window 200 100 240 140 "pxl8 demo")
(pxl8.gui_window 200 100 240 180 "pxl8 demo")
(when (gui:button 1 215 145 210 32 "Resume")
(hide))
(when (gui:button 2 215 185 210 32 "Quit")
(let [music-label (if (music.is-playing) "Music: On" "Music: Off")]
(when (gui:button 3 215 185 210 32 music-label)
(if (music.is-playing)
(music.stop)
(music.start))))
(when (gui:button 2 215 225 210 32 "Quit")
(pxl8.quit))
(if (gui:is_hovering)

View file

@ -101,7 +101,7 @@
(fn update [dt]
(when playing
(set time (+ time dt))
(when (>= time step-duration)
(while (>= time step-duration)
(set time (- time step-duration))
(local melody-idx (+ 1 (% step (length melody))))
@ -125,4 +125,5 @@
:start start
:stop stop
:update update
:is-playing (fn [] playing)}
:is-playing (fn [] playing)
:get-step (fn [] step)}

250
demo/mod/sky.fnl Normal file
View file

@ -0,0 +1,250 @@
(local ffi (require :ffi))
(local pxl8 (require :pxl8))
(local effects (require :pxl8.effects))
(local SKY_GRADIENT_START 144)
(local SKY_GRADIENT_COUNT 16)
(local sky-radius 900)
(local sky-segments 16)
(local sky-rings 16)
(local max-theta (* math.pi 0.55))
(local STAR_COUNT 200)
(local TINY_STAR_COUNT 5000)
(local STAR_SILVER_START 160)
(local STAR_BLUE_START 168)
(local STAR_RED_START 176)
(var sky-mesh nil)
(var last-gradient-key nil)
(var stars [])
(var tiny-stars [])
(fn generate-sky-gradient [zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b]
(for [i 0 (- SKY_GRADIENT_COUNT 1)]
(let [t (/ i (- SKY_GRADIENT_COUNT 1))
r (math.floor (+ zenith-r (* t (- horizon-r zenith-r))))
g (math.floor (+ zenith-g (* t (- horizon-g zenith-g))))
b (math.floor (+ zenith-b (* t (- horizon-b zenith-b))))]
(pxl8.set_palette_rgb (+ SKY_GRADIENT_START i) r g b))))
(fn create-sky-dome []
(let [verts []
indices []]
(for [i 0 (- sky-rings 1)]
(let [theta0 (* (/ i sky-rings) max-theta)
theta1 (* (/ (+ i 1) sky-rings) max-theta)
sin-t0 (math.sin theta0)
cos-t0 (math.cos theta0)
sin-t1 (math.sin theta1)
cos-t1 (math.cos theta1)
y0 (* sky-radius cos-t0)
y1 (* sky-radius cos-t1)
r0 (* sky-radius sin-t0)
r1 (* sky-radius sin-t1)
t0 (/ i sky-rings)
t1 (/ (+ i 1) sky-rings)
c0 (math.floor (+ SKY_GRADIENT_START (* t0 (- SKY_GRADIENT_COUNT 1)) 0.5))
c1 (math.floor (+ SKY_GRADIENT_START (* t1 (- SKY_GRADIENT_COUNT 1)) 0.5))]
(for [j 0 (- sky-segments 1)]
(let [phi0 (* (/ j sky-segments) math.pi 2)
phi1 (* (/ (+ j 1) sky-segments) math.pi 2)
cos-p0 (math.cos phi0)
sin-p0 (math.sin phi0)
cos-p1 (math.cos phi1)
sin-p1 (math.sin phi1)
x00 (* r0 cos-p0) z00 (* r0 sin-p0)
x01 (* r0 cos-p1) z01 (* r0 sin-p1)
x10 (* r1 cos-p0) z10 (* r1 sin-p0)
x11 (* r1 cos-p1) z11 (* r1 sin-p1)
nx00 (- (* sin-t0 cos-p0)) ny00 (- cos-t0) nz00 (- (* sin-t0 sin-p0))
nx01 (- (* sin-t0 cos-p1)) ny01 (- cos-t0) nz01 (- (* sin-t0 sin-p1))
nx10 (- (* sin-t1 cos-p0)) ny10 (- cos-t1) nz10 (- (* sin-t1 sin-p0))
nx11 (- (* sin-t1 cos-p1)) ny11 (- cos-t1) nz11 (- (* sin-t1 sin-p1))
base-idx (# verts)]
(if (= i 0)
(do
;; First ring is degenerate - just a triangle from pole
;; Vertices: v00 (pole, c0), v11 (bottom-right, c1), v10 (bottom-left, c1)
(table.insert verts {:x x00 :y y0 :z z00 :nx nx00 :ny ny00 :nz nz00 :color c0 :light 255})
(table.insert verts {:x x11 :y y1 :z z11 :nx nx11 :ny ny11 :nz nz11 :color c1 :light 255})
(table.insert verts {:x x10 :y y1 :z z10 :nx nx10 :ny ny10 :nz nz10 :color c1 :light 255})
;; Triangle: base, base+2, base+1
(table.insert indices base-idx)
(table.insert indices (+ base-idx 2))
(table.insert indices (+ base-idx 1)))
(do
;; Regular quad: v00 (top-left), v01 (top-right), v11 (bottom-right), v10 (bottom-left)
(table.insert verts {:x x00 :y y0 :z z00 :nx nx00 :ny ny00 :nz nz00 :color c0 :light 255})
(table.insert verts {:x x01 :y y0 :z z01 :nx nx01 :ny ny01 :nz nz01 :color c0 :light 255})
(table.insert verts {:x x11 :y y1 :z z11 :nx nx11 :ny ny11 :nz nz11 :color c1 :light 255})
(table.insert verts {:x x10 :y y1 :z z10 :nx nx10 :ny ny10 :nz nz10 :color c1 :light 255})
;; push_quad(base, base+3, base+2, base+1) = triangles (base,base+3,base+2) and (base,base+2,base+1)
(table.insert indices base-idx)
(table.insert indices (+ base-idx 3))
(table.insert indices (+ base-idx 2))
(table.insert indices base-idx)
(table.insert indices (+ base-idx 2))
(table.insert indices (+ base-idx 1))))))))
(set sky-mesh (pxl8.create_mesh verts indices))))
(fn update-gradient [zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b]
(let [key (.. zenith-r "," zenith-g "," zenith-b "," horizon-r "," horizon-g "," horizon-b)]
(when (not= key last-gradient-key)
(generate-sky-gradient zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b)
(set last-gradient-key key))))
(fn palette-ramp [start c0 c1]
(let [r0 (bit.rshift (bit.band c0 0xFF0000) 16)
g0 (bit.rshift (bit.band c0 0x00FF00) 8)
b0 (bit.band c0 0x0000FF)
r1 (bit.rshift (bit.band c1 0xFF0000) 16)
g1 (bit.rshift (bit.band c1 0x00FF00) 8)
b1 (bit.band c1 0x0000FF)]
(for [i 0 7]
(let [t (/ i 7)
r (math.floor (+ r0 (* t (- r1 r0))))
g (math.floor (+ g0 (* t (- g1 g0))))
b (math.floor (+ b0 (* t (- b1 b0))))]
(pxl8.set_palette_rgb (+ start i) r g b)))))
(fn init-star-palette []
(palette-ramp STAR_SILVER_START 0x707888 0xFFFFFF) ;; silver
(palette-ramp STAR_BLUE_START 0x5070B0 0xD0E8FF) ;; blue
(palette-ramp STAR_RED_START 0x802020 0xFF9090)) ;; red
(fn generate-stars [seed]
(set stars [])
(set tiny-stars [])
(init-star-palette)
(pxl8.rng_seed seed)
(for [i 1 STAR_COUNT]
(let [theta (math.acos (- 1 (* (pxl8.rng_f32) 0.85)))
phi (* (pxl8.rng_f32) math.pi 2)
brightness (pxl8.rng_range 1 4)
color-type (pxl8.rng_range 0 100)
shade (pxl8.rng_range 0 5)
color (if (< color-type 3) (+ STAR_RED_START shade)
(< color-type 15) (+ STAR_BLUE_START shade)
(+ STAR_SILVER_START shade))
sin-t (math.sin theta)
cos-t (math.cos theta)]
(table.insert stars {:dx (* sin-t (math.cos phi))
:dy cos-t
:dz (* sin-t (math.sin phi))
:brightness brightness
:color color})))
(pxl8.rng_seed (+ seed 0xCAFEBABE))
(for [i 1 TINY_STAR_COUNT]
(let [theta (math.acos (- 1 (* (pxl8.rng_f32) 0.95)))
phi (* (pxl8.rng_f32) math.pi 2)
brightness (+ 25 (pxl8.rng_range 0 40))
shade (pxl8.rng_range 0 3)
color-type (pxl8.rng_range 0 100)
color (if (< color-type 15) (+ STAR_BLUE_START shade)
(+ STAR_SILVER_START shade))
sin-t (math.sin theta)
cos-t (math.cos theta)]
(table.insert tiny-stars {:dx (* sin-t (math.cos phi))
:dy cos-t
:dz (* sin-t (math.sin phi))
:brightness brightness
:color color}))))
(fn project-direction [dir-x dir-y dir-z yaw pitch width height]
(let [cos-yaw (math.cos yaw)
sin-yaw (math.sin yaw)
cos-pitch (math.cos pitch)
sin-pitch (math.sin pitch)
rotated-x (+ (* dir-x cos-yaw) (* dir-z sin-yaw))
rotated-z (+ (* (- dir-x) sin-yaw) (* dir-z cos-yaw))
rotated-y (- (* dir-y cos-pitch) (* rotated-z sin-pitch))
final-z (+ (* dir-y sin-pitch) (* rotated-z cos-pitch))]
(when (> final-z 0.01)
(let [fov 1.047
aspect (/ width height)
half-fov-tan (math.tan (* fov 0.5))
ndc-x (/ rotated-x (* final-z half-fov-tan aspect))
ndc-y (/ rotated-y (* final-z half-fov-tan))]
(when (and (>= ndc-x -1) (<= ndc-x 1) (>= ndc-y -1) (<= ndc-y 1))
{:x (math.floor (* (+ 1 ndc-x) 0.5 width))
:y (math.floor (* (- 1 ndc-y) 0.5 height))})))))
(fn render-stars [yaw pitch intensity]
(when (> intensity 0)
(let [width (pxl8.get_width)
height (pxl8.get_height)
glows []
fade-sq (* intensity intensity)]
(each [_ star (ipairs tiny-stars)]
(let [screen (project-direction star.dx star.dy star.dz yaw pitch width height)]
(when screen
(let [int (math.floor (* star.brightness fade-sq))]
(when (> int 8)
(table.insert glows {:x screen.x :y screen.y
:radius 1
:intensity int
:color star.color
:shape effects.GLOW_CIRCLE}))))))
(each [_ star (ipairs stars)]
(let [screen (project-direction star.dx star.dy star.dz yaw pitch width height)]
(when screen
(let [base-int (math.floor (* star.brightness 50 fade-sq 1.5))]
(if (>= star.brightness 4)
(do
(table.insert glows {:x screen.x :y screen.y
:radius 4
:intensity (math.floor (/ base-int 4))
:color star.color
:shape effects.GLOW_CIRCLE})
(table.insert glows {:x screen.x :y screen.y
:radius 2
:intensity base-int
:color star.color
:shape effects.GLOW_DIAMOND}))
(>= star.brightness 3)
(do
(table.insert glows {:x screen.x :y screen.y
:radius 3
:intensity (math.floor (/ base-int 4))
:color star.color
:shape effects.GLOW_CIRCLE})
(table.insert glows {:x screen.x :y screen.y
:radius 2
:intensity base-int
:color star.color
:shape effects.GLOW_DIAMOND}))
(>= star.brightness 2)
(table.insert glows {:x screen.x :y screen.y
:radius 2
:intensity base-int
:color star.color
:shape effects.GLOW_DIAMOND})
(table.insert glows {:x screen.x :y screen.y
:radius 1
:intensity (math.floor (* base-int 0.7))
:color star.color
:shape effects.GLOW_CIRCLE}))))))
(when (> (length glows) 0)
(effects.glows glows)))))
(fn render [cam-x cam-y cam-z]
(when (not sky-mesh) (create-sky-dome))
(when sky-mesh
(pxl8.draw_mesh sky-mesh {:x cam-x :y cam-y :z cam-z :passthrough true})))
{:render render
:render-stars render-stars
:generate-stars generate-stars
:update-gradient update-gradient
:SKY_GRADIENT_START SKY_GRADIENT_START
:SKY_GRADIENT_COUNT SKY_GRADIENT_COUNT}

75
demo/mod/vfx.fnl Normal file
View file

@ -0,0 +1,75 @@
(local vfx {})
(fn vfx.explosion [ps x y ?opts]
(let [opts (or ?opts {})
color (or opts.color 208)
force (or opts.force 200)]
(ps:set_position x y)
(ps:set_colors color (+ color 15))
(ps:set_velocity (- force) force (- force) force)
(ps:set_gravity 0 100)
(ps:set_life 0.3 0.8)
(ps:set_size 1 3)
(ps:set_drag 0.98)
(ps:set_spawn_rate 0)
(ps:emit (or opts.count 50))))
(fn vfx.fire [ps x y ?opts]
(let [opts (or ?opts {})
width (or opts.width 50)
color (or opts.color 208)]
(ps:set_position x y)
(ps:set_spread width 5)
(ps:set_colors color (+ color 15))
(ps:set_velocity -20 20 -80 -40)
(ps:set_gravity 0 -30)
(ps:set_life 0.5 1.5)
(ps:set_size 1 2)
(ps:set_turbulence 30)
(ps:set_drag 0.95)
(ps:set_spawn_rate (or opts.rate 50))))
(fn vfx.rain [ps width ?opts]
(let [opts (or ?opts {})
wind (or opts.wind 0)
color (or opts.color 153)]
(ps:set_position (/ width 2) -10)
(ps:set_spread width 0)
(ps:set_colors color (+ color 3))
(ps:set_velocity (- wind 10) (+ wind 10) 300 400)
(ps:set_gravity 0 200)
(ps:set_life 1 2)
(ps:set_size 1 1)
(ps:set_drag 1)
(ps:set_spawn_rate (or opts.rate 100))))
(fn vfx.smoke [ps x y ?opts]
(let [opts (or ?opts {})
color (or opts.color 248)]
(ps:set_position x y)
(ps:set_spread 10 5)
(ps:set_colors color (+ color 7))
(ps:set_velocity -15 15 -30 -10)
(ps:set_gravity 0 -20)
(ps:set_life 1 3)
(ps:set_size 2 4)
(ps:set_turbulence 20)
(ps:set_drag 0.98)
(ps:set_spawn_rate (or opts.rate 20))))
(fn vfx.snow [ps width ?opts]
(let [opts (or ?opts {})
wind (or opts.wind 10)
color (or opts.color 15)]
(ps:set_position (/ width 2) -10)
(ps:set_spread width 0)
(ps:set_colors color color)
(ps:set_velocity (- wind 20) (+ wind 20) 30 60)
(ps:set_gravity 0 10)
(ps:set_life 3 6)
(ps:set_size 1 2)
(ps:set_turbulence 15)
(ps:set_drag 0.99)
(ps:set_spawn_rate (or opts.rate 30))))
vfx

View file

@ -1,224 +0,0 @@
(local pxl8 (require :pxl8))
(local bob-amount 4.0)
(local bob-speed 8.0)
(local cam-smoothing 0.25)
(local cell-size 64)
(local cursor-sensitivity 0.008)
(local gravity -800)
(local grid-size 64)
(local ground-y 64)
(local jump-force 175)
(local land-recovery-speed 20)
(local land-squash-amount -4)
(local max-pitch 1.5)
(local move-speed 200)
(local turn-speed 2.0)
(var auto-run? false)
(var auto-run-cancel-key nil)
(var bob-time 0)
(var cam-pitch 0)
(var cam-x 1000)
(var cam-y 64)
(var cam-yaw 0)
(var cam-z 1000)
(var camera nil)
(var cursor-look? true)
(var grounded? true)
(var land-squash 0)
(var smooth-cam-x 1000)
(var smooth-cam-z 1000)
(var velocity-y 0)
(var world nil)
(fn init []
(set camera (pxl8.create_camera_3d))
(set world (pxl8.create_world))
(let [result (world:generate {
:type pxl8.PROCGEN_ROOMS
:width 64
:height 64
:seed 42
:min_room_size 5
:max_room_size 10
:num_rooms 20})]
(if (< result 0)
(pxl8.error (.. "Failed to generate rooms - result: " result))
(let [floor-tex (pxl8.procgen_tex {:name "floor"
:seed 11111
:width 64
:height 64
:base_color 19})
ceiling-tex (pxl8.procgen_tex {:name "ceiling"
:seed 22222
:width 64
:height 64
:base_color 1})
wall-tex (pxl8.procgen_tex {:name "wall"
:seed 12345
:width 64
:height 64
:base_color 4})]
(let [result (world:apply_textures [
{:name "floor"
:texture_id floor-tex
:rule (fn [normal] (> normal.y 0.7))}
{:name "ceiling"
:texture_id ceiling-tex
:rule (fn [normal] (< normal.y -0.7))}
{:name "wall"
:texture_id wall-tex
:rule (fn [normal] (and (<= normal.y 0.7) (>= normal.y -0.7)))}])]
(when (< result 0)
(pxl8.error (.. "Failed to apply textures - result: " result))))))))
(fn update [dt]
(when (pxl8.key_pressed "`")
(set auto-run? (not auto-run?))
(when (and auto-run? (pxl8.key_down "w"))
(set auto-run-cancel-key "w")))
(when (and auto-run? (not auto-run-cancel-key) (or (pxl8.key_down "w") (pxl8.key_down "s")))
(set auto-run? false)
(when (pxl8.key_down "s")
(set auto-run-cancel-key "s")))
(when (and auto-run-cancel-key (not (pxl8.key_down auto-run-cancel-key)))
(set auto-run-cancel-key nil))
(when (world:is_loaded)
(let [forward-x (- (math.sin cam-yaw))
forward-z (- (math.cos cam-yaw))
right-x (math.cos cam-yaw)
right-z (- (math.sin cam-yaw))
grid-max (* grid-size cell-size)
move-delta (* move-speed dt)]
(var moving false)
(var move-forward 0)
(var move-right 0)
(when (or (pxl8.key_down "w") auto-run?)
(set move-forward (+ move-forward 1)))
(when (and (pxl8.key_down "s") (not= auto-run-cancel-key "s"))
(set move-forward (- move-forward 1)))
(when (pxl8.key_down "a")
(set move-right (- move-right 1)))
(when (pxl8.key_down "d")
(set move-right (+ move-right 1)))
(set moving (or (not= move-forward 0) (not= move-right 0)))
(var new-x cam-x)
(var new-z cam-z)
(when moving
(let [len (math.sqrt (+ (* move-forward move-forward) (* move-right move-right)))
norm-forward (/ move-forward len)
norm-right (/ move-right len)]
(set new-x (+ new-x (* move-delta (+ (* forward-x norm-forward) (* right-x norm-right)))))
(set new-z (+ new-z (* move-delta (+ (* forward-z norm-forward) (* right-z norm-right)))))))
(when (and (>= new-x 0) (<= new-x grid-max)
(>= new-z 0) (<= new-z grid-max))
(let [(resolved-x resolved-y resolved-z) (world:resolve_collision cam-x cam-y cam-z new-x cam-y new-z 5)]
(set cam-x resolved-x)
(set cam-z resolved-z)))
(set smooth-cam-x (+ (* smooth-cam-x (- 1 cam-smoothing)) (* cam-x cam-smoothing)))
(set smooth-cam-z (+ (* smooth-cam-z (- 1 cam-smoothing)) (* cam-z cam-smoothing)))
(when cursor-look?
(let [dx (pxl8.mouse_dx)
dy (pxl8.mouse_dy)]
(set cam-yaw (- cam-yaw (* dx cursor-sensitivity)))
(set cam-pitch (math.max (- max-pitch)
(math.min max-pitch
(- cam-pitch (* dy cursor-sensitivity)))))))
(when (and (not cursor-look?) (pxl8.key_down "left"))
(set cam-yaw (+ cam-yaw (* turn-speed dt))))
(when (and (not cursor-look?) (pxl8.key_down "right"))
(set cam-yaw (- cam-yaw (* turn-speed dt))))
(when (and (not cursor-look?) (pxl8.key_down "up"))
(set cam-pitch (math.min max-pitch (+ cam-pitch (* turn-speed dt)))))
(when (and (not cursor-look?) (pxl8.key_down "down"))
(set cam-pitch (math.max (- max-pitch) (- cam-pitch (* turn-speed dt)))))
(when (and (pxl8.key_pressed "space") grounded?)
(set velocity-y jump-force)
(set grounded? false))
(set velocity-y (+ velocity-y (* gravity dt)))
(set cam-y (+ cam-y (* velocity-y dt)))
(when (<= cam-y ground-y)
(when (not grounded?)
(set land-squash land-squash-amount))
(set cam-y ground-y)
(set velocity-y 0)
(set grounded? true))
(when (< land-squash 0)
(set land-squash (math.min 0 (+ land-squash (* land-recovery-speed dt)))))
(if (and moving grounded?)
(set bob-time (+ bob-time (* dt bob-speed)))
(let [target-phase (* (math.floor (/ bob-time math.pi)) math.pi)]
(set bob-time (+ (* bob-time 0.8) (* target-phase 0.2))))))))
(fn frame []
(pxl8.clear 0)
(when (not camera)
(pxl8.error "camera is nil!"))
(when (not world)
(pxl8.error "world is nil!"))
(when (and world (not (world:is_loaded)))
(pxl8.text "World not loaded yet..." 5 30 12))
(when (and camera world (world:is_loaded))
(let [bob-offset (* (math.sin bob-time) bob-amount)
eye-y (+ cam-y bob-offset land-squash)
forward-x (- (math.sin cam-yaw))
forward-z (- (math.cos cam-yaw))
target-x (+ smooth-cam-x forward-x)
target-y (+ eye-y (math.sin cam-pitch))
target-z (+ smooth-cam-z forward-z)
aspect (/ (pxl8.get_width) (pxl8.get_height))]
(camera:lookat [smooth-cam-x eye-y smooth-cam-z]
[target-x target-y target-z]
[0 1 0])
(camera:set_perspective 1.047 aspect 1.0 4096.0)
(pxl8.begin_frame_3d camera)
(pxl8.clear_depth)
(world:render [smooth-cam-x eye-y smooth-cam-z])
(pxl8.end_frame_3d)
(let [cx (/ (pxl8.get_width) 2)
cy (/ (pxl8.get_height) 2)
crosshair-size 4
red-color 18]
(pxl8.line (- cx crosshair-size) cy (+ cx crosshair-size) cy red-color)
(pxl8.line cx (- cy crosshair-size) cx (+ cy crosshair-size) red-color))
(pxl8.text (.. "fps: " (string.format "%.1f" (pxl8.get_fps))) 5 5 12)
(pxl8.text (.. "pos: " (string.format "%.0f" cam-x) ","
(string.format "%.0f" cam-y) ","
(string.format "%.0f" cam-z)) 5 15 12))))
{:init init
:update update
:frame frame}

127
pxl8.sh
View file

@ -56,6 +56,45 @@ build_luajit() {
fi
}
build_server() {
local mode="$1"
if [[ -d "server" ]]; then
print_info "Building server ($mode mode)"
cd server
if [[ "$mode" == "release" ]]; then
cargo build --release > /dev/null 2>&1
else
cargo build > /dev/null 2>&1
fi
cd - > /dev/null
print_info "Built server"
fi
}
start_server() {
local mode="$1"
local server_bin
if [[ "$mode" == "release" ]]; then
server_bin="server/target/release/pxl8-server"
else
server_bin="server/target/debug/pxl8-server"
fi
if [[ -f "$server_bin" ]]; then
print_info "Starting server"
./$server_bin &
SERVER_PID=$!
sleep 0.2
fi
}
stop_server() {
if [[ -n "$SERVER_PID" ]]; then
print_info "Stopping server"
kill $SERVER_PID 2>/dev/null || true
wait $SERVER_PID 2>/dev/null || true
fi
}
build_sdl() {
if [[ ! -f "lib/SDL/build/libSDL3.so" ]] && [[ ! -f "lib/SDL/build/libSDL3.a" ]] && [[ ! -f "lib/SDL/build/libSDL3.dylib" ]]; then
print_info "Building SDL3"
@ -113,6 +152,7 @@ print_usage() {
echo " --all Clean both build artifacts and dependencies"
echo " --deps Clean only dependencies"
echo " --release Build/run/clean in release mode (default: debug)"
echo " --server Start game server before running (for networked games)"
}
setup_sdl3() {
@ -320,7 +360,7 @@ case "$COMMAND" in
print_info "Compiler cache: ccache enabled"
fi
INCLUDES="-Iclient/src/core -Iclient/src/math -Iclient/src/gfx -Iclient/src/sfx -Iclient/src/script -Iclient/src/hal -Iclient/src/world -Iclient/src/asset -Iclient/src/game -Ilib -Ilib/luajit/src -Ilib/linenoise -Ilib/miniz"
INCLUDES="-Isrc/core -Isrc/math -Isrc/gfx -Isrc/sfx -Isrc/script -Isrc/hal -Isrc/world -Isrc/asset -Isrc/game -Isrc/net -Ilib -Ilib/luajit/src -Ilib/linenoise -Ilib/miniz"
COMPILE_FLAGS="$CFLAGS $INCLUDES"
DEP_COMPILE_FLAGS="$DEP_CFLAGS $INCLUDES"
@ -329,38 +369,42 @@ case "$COMMAND" in
LIB_SOURCE_FILES="lib/linenoise/linenoise.c lib/miniz/miniz.c"
PXL8_SOURCE_FILES="
client/src/core/pxl8.c
client/src/core/pxl8_io.c
client/src/core/pxl8_log.c
client/src/core/pxl8_rng.c
client/src/math/pxl8_math.c
client/src/gfx/pxl8_anim.c
client/src/gfx/pxl8_atlas.c
client/src/gfx/pxl8_blit.c
client/src/gfx/pxl8_3d_camera.c
client/src/gfx/pxl8_colormap.c
client/src/gfx/pxl8_cpu.c
client/src/gfx/pxl8_dither.c
client/src/gfx/pxl8_font.c
client/src/gfx/pxl8_gfx.c
client/src/gfx/pxl8_mesh.c
client/src/gfx/pxl8_palette.c
client/src/gfx/pxl8_particles.c
client/src/gfx/pxl8_tilemap.c
client/src/gfx/pxl8_tilesheet.c
client/src/gfx/pxl8_transition.c
client/src/sfx/pxl8_sfx.c
client/src/script/pxl8_repl.c
client/src/script/pxl8_script.c
client/src/hal/pxl8_sdl3.c
client/src/world/pxl8_bsp.c
client/src/world/pxl8_gen.c
client/src/world/pxl8_world.c
client/src/asset/pxl8_ase.c
client/src/asset/pxl8_cart.c
client/src/asset/pxl8_save.c
client/src/game/pxl8_gui.c
client/src/game/pxl8_replay.c
src/core/pxl8.c
src/core/pxl8_bytes.c
src/core/pxl8_io.c
src/core/pxl8_log.c
src/core/pxl8_rng.c
src/math/pxl8_math.c
src/gfx/pxl8_anim.c
src/gfx/pxl8_atlas.c
src/gfx/pxl8_blend.c
src/gfx/pxl8_blit.c
src/gfx/pxl8_3d_camera.c
src/gfx/pxl8_colormap.c
src/gfx/pxl8_cpu.c
src/gfx/pxl8_dither.c
src/gfx/pxl8_font.c
src/gfx/pxl8_gfx.c
src/gfx/pxl8_mesh.c
src/gfx/pxl8_palette.c
src/gfx/pxl8_particles.c
src/gfx/pxl8_tilemap.c
src/gfx/pxl8_tilesheet.c
src/gfx/pxl8_transition.c
src/sfx/pxl8_sfx.c
src/script/pxl8_repl.c
src/script/pxl8_script.c
src/hal/pxl8_sdl3.c
src/world/pxl8_bsp.c
src/world/pxl8_gen.c
src/world/pxl8_world.c
src/asset/pxl8_ase.c
src/asset/pxl8_cart.c
src/asset/pxl8_save.c
src/game/pxl8_gui.c
src/game/pxl8_replay.c
src/net/pxl8_net.c
src/net/pxl8_protocol.c
"
LUAJIT_LIB="lib/luajit/src/libluajit.a"
@ -390,13 +434,13 @@ case "$COMMAND" in
NEEDS_REBUILD=false
if [[ "$src_file" -nt "$obj_file" ]] || \
[[ "client/src/core/pxl8_types.h" -nt "$obj_file" ]] || \
[[ "client/src/core/pxl8_macros.h" -nt "$obj_file" ]]; then
[[ "src/core/pxl8_types.h" -nt "$obj_file" ]] || \
[[ "src/core/pxl8_macros.h" -nt "$obj_file" ]]; then
NEEDS_REBUILD=true
fi
if [[ "$src_file" == "client/src/script/pxl8_script.c" ]]; then
for lua_file in client/src/lua/*.lua client/src/lua/pxl8/*.lua lib/fennel/fennel.lua; do
if [[ "$src_file" == "src/script/pxl8_script.c" ]]; then
for lua_file in src/lua/*.lua src/lua/pxl8/*.lua lib/fennel/fennel.lua; do
if [[ -f "$lua_file" ]] && [[ "$lua_file" -nt "$obj_file" ]]; then
NEEDS_REBUILD=true
break
@ -430,16 +474,25 @@ case "$COMMAND" in
CART=""
EXTRA_ARGS=""
RUN_SERVER=false
for arg in "$@"; do
if [[ "$arg" == "--release" ]]; then
continue
elif [[ "$arg" == "--repl" ]]; then
EXTRA_ARGS="$EXTRA_ARGS --repl"
elif [[ "$arg" == "--server" ]]; then
RUN_SERVER=true
elif [[ -z "$CART" ]]; then
CART="$arg"
fi
done
if [[ "$RUN_SERVER" == true ]]; then
build_server "$MODE"
start_server "$MODE"
trap stop_server EXIT
fi
if [[ -z "$CART" ]]; then
"$BINDIR/pxl8" $EXTRA_ARGS
else

View file

@ -0,0 +1,2 @@
[unstable]
build-std = ["core", "alloc"]

1
server/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
target/

1090
server/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

34
server/Cargo.toml Normal file
View file

@ -0,0 +1,34 @@
[package]
name = "pxl8-server"
version = "0.1.0"
edition = "2024"
[build-dependencies]
bindgen = "0.72"
[dependencies]
bevy_ecs = { version = "0.18", default-features = false }
libc = { version = "0.2", default-features = false }
libm = { version = "0.2", default-features = false }
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.61", default-features = false, features = [
"Win32_System_Memory",
"Win32_System_Performance",
"Win32_System_Threading",
"Win32_Networking_WinSock",
] }
[[bin]]
name = "pxl8-server"
path = "src/main.rs"
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
[features]
default = []
std = ["bevy_ecs/std"]

23
server/build.rs Normal file
View file

@ -0,0 +1,23 @@
use std::env;
use std::path::PathBuf;
fn main() {
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let client_src = PathBuf::from(&manifest_dir).join("../client/src");
println!("cargo:rerun-if-changed=../client/src/net/pxl8_protocol.h");
println!("cargo:rerun-if-changed=../client/src/core/pxl8_types.h");
let bindings = bindgen::Builder::default()
.header(client_src.join("net/pxl8_protocol.h").to_str().unwrap())
.clang_arg(format!("-I{}", client_src.join("core").display()))
.use_core()
.rustified_enum(".*")
.generate()
.expect("Unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("protocol.rs"))
.expect("Couldn't write bindings");
}

View file

@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"

36
server/src/allocator.rs Normal file
View file

@ -0,0 +1,36 @@
use core::alloc::{GlobalAlloc, Layout};
pub struct Allocator;
#[cfg(unix)]
unsafe impl GlobalAlloc for Allocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
unsafe { libc::memalign(layout.align(), layout.size()) as *mut u8 }
}
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
unsafe { libc::free(ptr as *mut libc::c_void) }
}
unsafe fn realloc(&self, ptr: *mut u8, _layout: Layout, new_size: usize) -> *mut u8 {
unsafe { libc::realloc(ptr as *mut libc::c_void, new_size) as *mut u8 }
}
}
#[cfg(windows)]
unsafe impl GlobalAlloc for Allocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
use windows_sys::Win32::System::Memory::*;
unsafe { HeapAlloc(GetProcessHeap(), 0, layout.size()) as *mut u8 }
}
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
use windows_sys::Win32::System::Memory::*;
unsafe { HeapFree(GetProcessHeap(), 0, ptr as *mut core::ffi::c_void) };
}
unsafe fn realloc(&self, ptr: *mut u8, _layout: Layout, new_size: usize) -> *mut u8 {
use windows_sys::Win32::System::Memory::*;
unsafe { HeapReAlloc(GetProcessHeap(), 0, ptr as *mut core::ffi::c_void, new_size) as *mut u8 }
}
}

45
server/src/components.rs Normal file
View file

@ -0,0 +1,45 @@
use bevy_ecs::prelude::*;
#[derive(Component, Clone, Copy, Default)]
pub struct Position {
pub x: f32,
pub y: f32,
pub z: f32,
}
#[derive(Component, Clone, Copy, Default)]
pub struct Rotation {
pub pitch: f32,
pub yaw: f32,
}
#[derive(Component, Clone, Copy, Default)]
pub struct Velocity {
pub x: f32,
pub y: f32,
pub z: f32,
}
#[derive(Component, Clone, Copy)]
pub struct Health {
pub current: f32,
pub max: f32,
}
impl Health {
pub fn new(max: f32) -> Self {
Self { current: max, max }
}
}
impl Default for Health {
fn default() -> Self {
Self::new(100.0)
}
}
#[derive(Component, Clone, Copy, Default)]
pub struct Player;
#[derive(Component, Clone, Copy)]
pub struct TypeId(pub u16);

18
server/src/lib.rs Normal file
View file

@ -0,0 +1,18 @@
#![no_std]
extern crate alloc;
pub mod components;
mod simulation;
pub mod transport;
#[allow(dead_code, non_camel_case_types, non_snake_case, non_upper_case_globals)]
pub mod protocol {
include!(concat!(env!("OUT_DIR"), "/protocol.rs"));
}
pub use bevy_ecs::prelude::*;
pub use components::*;
pub use protocol::*;
pub use simulation::*;
pub use transport::*;

142
server/src/main.rs Normal file
View file

@ -0,0 +1,142 @@
#![no_std]
#![no_main]
extern crate alloc;
mod allocator;
use core::panic::PanicInfo;
use pxl8_server::*;
#[global_allocator]
static ALLOCATOR: allocator::Allocator = allocator::Allocator;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
const TICK_RATE: u64 = 30;
const TICK_NS: u64 = 1_000_000_000 / TICK_RATE;
#[cfg(unix)]
fn get_time_ns() -> u64 {
let mut ts = libc::timespec { tv_sec: 0, tv_nsec: 0 };
unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts) };
(ts.tv_sec as u64) * 1_000_000_000 + (ts.tv_nsec as u64)
}
#[cfg(windows)]
fn get_time_ns() -> u64 {
use windows_sys::Win32::System::Performance::*;
static mut FREQ: i64 = 0;
unsafe {
if FREQ == 0 {
QueryPerformanceFrequency(&mut FREQ);
}
let mut count: i64 = 0;
QueryPerformanceCounter(&mut count);
((count as u128 * 1_000_000_000) / FREQ as u128) as u64
}
}
#[cfg(unix)]
fn sleep_ms(ms: u64) {
let ts = libc::timespec {
tv_sec: (ms / 1000) as i64,
tv_nsec: ((ms % 1000) * 1_000_000) as i64,
};
unsafe { libc::nanosleep(&ts, core::ptr::null_mut()) };
}
#[cfg(windows)]
fn sleep_ms(ms: u64) {
use windows_sys::Win32::System::Threading::Sleep;
unsafe { Sleep(ms as u32) };
}
fn extract_spawn_position(payload: &[u8]) -> (f32, f32, f32, f32, f32) {
let x = f32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]);
let y = f32::from_be_bytes([payload[4], payload[5], payload[6], payload[7]]);
let z = f32::from_be_bytes([payload[8], payload[9], payload[10], payload[11]]);
let yaw = f32::from_be_bytes([payload[12], payload[13], payload[14], payload[15]]);
let pitch = f32::from_be_bytes([payload[16], payload[17], payload[18], payload[19]]);
(x, y, z, yaw, pitch)
}
#[unsafe(no_mangle)]
pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
let mut transport = match transport::Transport::bind(transport::DEFAULT_PORT) {
Some(t) => t,
None => return 1,
};
let mut sim = Simulation::new();
let mut player_id: u64 = 0;
let mut last_client_tick: u64 = 0;
let mut sequence: u32 = 0;
let mut last_tick = get_time_ns();
let mut entities_buf = [protocol::pxl8_entity_state {
entity_id: 0,
userdata: [0u8; 56],
}; 64];
let mut inputs_buf: [protocol::pxl8_input_msg; 16] = unsafe { core::mem::zeroed() };
loop {
let now = get_time_ns();
let elapsed = now.saturating_sub(last_tick);
if elapsed >= TICK_NS {
last_tick = now;
let dt = (elapsed as f32) / 1_000_000_000.0;
let mut latest_input: Option<protocol::pxl8_input_msg> = None;
while let Some(msg_type) = transport.recv() {
match msg_type {
x if x == protocol::pxl8_msg_type::PXL8_MSG_INPUT as u8 => {
latest_input = Some(transport.get_input());
}
x if x == protocol::pxl8_msg_type::PXL8_MSG_COMMAND as u8 => {
let cmd = transport.get_command();
if cmd.cmd_type == protocol::pxl8_cmd_type::PXL8_CMD_SPAWN_ENTITY as u16 {
let (x, y, z, _yaw, _pitch) = extract_spawn_position(&cmd.payload);
let player = sim.spawn_player(x, y, z);
player_id = player.to_bits();
}
}
_ => {}
}
}
if let Some(input) = latest_input {
last_client_tick = input.tick;
inputs_buf[0] = input;
sim.step(&inputs_buf[..1], dt);
} else {
sim.step(&[], dt);
}
let mut count = 0;
sim.generate_snapshot(player_id, |state| {
if count < entities_buf.len() {
entities_buf[count] = *state;
count += 1;
}
});
let header = protocol::pxl8_snapshot_header {
entity_count: count as u16,
event_count: 0,
player_id,
tick: last_client_tick,
time: sim.time,
};
transport.send_snapshot(&header, &entities_buf[..count], sequence);
sequence = sequence.wrapping_add(1);
}
sleep_ms(1);
}
}

133
server/src/simulation.rs Normal file
View file

@ -0,0 +1,133 @@
use bevy_ecs::prelude::*;
use libm::{cosf, sinf};
use crate::components::*;
use crate::protocol::*;
#[derive(Resource, Default)]
pub struct SimTime {
pub dt: f32,
pub time: f32,
}
pub struct Simulation {
pub player: Option<Entity>,
pub tick: u64,
pub time: f32,
pub world: World,
}
impl Simulation {
pub fn new() -> Self {
let mut world = World::new();
world.insert_resource(SimTime::default());
Self {
player: None,
tick: 0,
time: 0.0,
world,
}
}
pub fn step(&mut self, inputs: &[pxl8_input_msg], dt: f32) {
self.tick += 1;
self.time += dt;
if let Some(mut sim_time) = self.world.get_resource_mut::<SimTime>() {
sim_time.dt = dt;
sim_time.time = self.time;
}
for input in inputs {
self.apply_input(input, dt);
}
}
fn apply_input(&mut self, input: &pxl8_input_msg, dt: f32) {
let Some(player) = self.player else { return };
let speed = 200.0;
let yaw = input.yaw;
let sin_yaw = sinf(yaw);
let cos_yaw = cosf(yaw);
let move_x = input.move_x;
let move_y = input.move_y;
let len_sq = move_x * move_x + move_y * move_y;
if len_sq > 0.0 {
let len = libm::sqrtf(len_sq);
let norm_x = move_x / len;
let norm_y = move_y / len;
let forward_x = -sin_yaw * norm_y * speed * dt;
let forward_z = -cos_yaw * norm_y * speed * dt;
let strafe_x = cos_yaw * norm_x * speed * dt;
let strafe_z = -sin_yaw * norm_x * speed * dt;
if let Some(mut pos) = self.world.get_mut::<Position>(player) {
pos.x += forward_x + strafe_x;
pos.z += forward_z + strafe_z;
}
}
if let Some(mut rot) = self.world.get_mut::<Rotation>(player) {
rot.yaw = yaw;
rot.pitch = (rot.pitch - input.look_dy * 0.008).clamp(-1.5, 1.5);
}
}
pub fn spawn_entity(&mut self) -> Entity {
self.world.spawn((
Position::default(),
Rotation::default(),
Velocity::default(),
)).id()
}
pub fn spawn_player(&mut self, x: f32, y: f32, z: f32) -> Entity {
let entity = self.world.spawn((
Player,
Position { x, y, z },
Rotation::default(),
Velocity::default(),
Health::default(),
)).id();
self.player = Some(entity);
entity
}
pub fn generate_snapshot<F>(&mut self, player_id: u64, mut writer: F)
where
F: FnMut(&pxl8_entity_state),
{
let mut query = self.world.query::<(Entity, &Position, &Rotation)>();
for (entity, pos, rot) in query.iter(&self.world) {
let mut state = pxl8_entity_state {
entity_id: entity.to_bits(),
userdata: [0u8; 56],
};
let bytes = &mut state.userdata;
bytes[0..4].copy_from_slice(&pos.x.to_be_bytes());
bytes[4..8].copy_from_slice(&pos.y.to_be_bytes());
bytes[8..12].copy_from_slice(&pos.z.to_be_bytes());
bytes[12..16].copy_from_slice(&rot.yaw.to_be_bytes());
bytes[16..20].copy_from_slice(&rot.pitch.to_be_bytes());
writer(&state);
}
let _ = player_id;
}
pub fn entity_count(&self) -> usize {
self.world.entities().len() as usize
}
}
impl Default for Simulation {
fn default() -> Self {
Self::new()
}
}

281
server/src/transport.rs Normal file
View file

@ -0,0 +1,281 @@
use crate::protocol::*;
pub const DEFAULT_PORT: u16 = 7777;
#[cfg(unix)]
mod sys {
use libc::{c_int, c_void, sockaddr, sockaddr_in, socklen_t};
pub type RawSocket = c_int;
pub const INVALID_SOCKET: RawSocket = -1;
pub fn socket() -> RawSocket {
unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0) }
}
pub fn bind(sock: RawSocket, port: u16) -> c_int {
let addr = sockaddr_in {
sin_family: libc::AF_INET as u16,
sin_port: port.to_be(),
sin_addr: libc::in_addr { s_addr: u32::from_be_bytes([127, 0, 0, 1]).to_be() },
sin_zero: [0; 8],
};
unsafe { libc::bind(sock, &addr as *const _ as *const sockaddr, core::mem::size_of::<sockaddr_in>() as socklen_t) }
}
pub fn set_nonblocking(sock: RawSocket) -> c_int {
unsafe {
let flags = libc::fcntl(sock, libc::F_GETFL, 0);
libc::fcntl(sock, libc::F_SETFL, flags | libc::O_NONBLOCK)
}
}
pub fn recvfrom(sock: RawSocket, buf: &mut [u8], addr: &mut sockaddr_in) -> isize {
let mut addr_len = core::mem::size_of::<sockaddr_in>() as socklen_t;
unsafe {
libc::recvfrom(
sock,
buf.as_mut_ptr() as *mut c_void,
buf.len(),
0,
addr as *mut _ as *mut sockaddr,
&mut addr_len,
)
}
}
pub fn sendto(sock: RawSocket, buf: &[u8], addr: &sockaddr_in) -> isize {
unsafe {
libc::sendto(
sock,
buf.as_ptr() as *const c_void,
buf.len(),
0,
addr as *const _ as *const sockaddr,
core::mem::size_of::<sockaddr_in>() as socklen_t,
)
}
}
pub fn close(sock: RawSocket) {
unsafe { libc::close(sock) };
}
}
#[cfg(windows)]
mod sys {
use windows_sys::Win32::Networking::WinSock::*;
pub type RawSocket = SOCKET;
pub const INVALID_SOCKET_VAL: RawSocket = INVALID_SOCKET;
pub fn socket() -> RawSocket {
unsafe { socket(AF_INET as i32, SOCK_DGRAM as i32, 0) }
}
pub fn bind(sock: RawSocket, port: u16) -> i32 {
let addr = SOCKADDR_IN {
sin_family: AF_INET,
sin_port: port.to_be(),
sin_addr: IN_ADDR { S_un: IN_ADDR_0 { S_addr: u32::from_be_bytes([127, 0, 0, 1]).to_be() } },
sin_zero: [0; 8],
};
unsafe { bind(sock, &addr as *const _ as *const SOCKADDR, core::mem::size_of::<SOCKADDR_IN>() as i32) }
}
pub fn set_nonblocking(sock: RawSocket) -> i32 {
let mut nonblocking: u32 = 1;
unsafe { ioctlsocket(sock, FIONBIO as i32, &mut nonblocking) }
}
pub fn recvfrom(sock: RawSocket, buf: &mut [u8], addr: &mut SOCKADDR_IN) -> i32 {
let mut addr_len = core::mem::size_of::<SOCKADDR_IN>() as i32;
unsafe {
recvfrom(
sock,
buf.as_mut_ptr(),
buf.len() as i32,
0,
addr as *mut _ as *mut SOCKADDR,
&mut addr_len,
)
}
}
pub fn sendto(sock: RawSocket, buf: &[u8], addr: &SOCKADDR_IN) -> i32 {
unsafe {
sendto(
sock,
buf.as_ptr(),
buf.len() as i32,
0,
addr as *const _ as *const SOCKADDR,
core::mem::size_of::<SOCKADDR_IN>() as i32,
)
}
}
pub fn close(sock: RawSocket) {
unsafe { closesocket(sock) };
}
}
#[cfg(unix)]
type SockAddr = libc::sockaddr_in;
#[cfg(windows)]
type SockAddr = windows_sys::Win32::Networking::WinSock::SOCKADDR_IN;
pub struct Transport {
client_addr: SockAddr,
has_client: bool,
recv_buf: [u8; 4096],
send_buf: [u8; 4096],
socket: sys::RawSocket,
}
impl Transport {
pub fn bind(port: u16) -> Option<Self> {
let sock = sys::socket();
if sock == sys::INVALID_SOCKET {
return None;
}
if sys::bind(sock, port) < 0 {
sys::close(sock);
return None;
}
if sys::set_nonblocking(sock) < 0 {
sys::close(sock);
return None;
}
Some(Self {
client_addr: unsafe { core::mem::zeroed() },
has_client: false,
recv_buf: [0u8; 4096],
send_buf: [0u8; 4096],
socket: sock,
})
}
pub fn recv(&mut self) -> Option<u8> {
let mut addr: SockAddr = unsafe { core::mem::zeroed() };
let len = sys::recvfrom(self.socket, &mut self.recv_buf, &mut addr);
if len <= 0 || (len as usize) < size_of::<pxl8_msg_header>() {
return None;
}
self.client_addr = addr;
self.has_client = true;
let header = self.deserialize_header();
Some(header.type_)
}
pub fn get_input(&self) -> pxl8_input_msg {
self.deserialize_input()
}
pub fn get_command(&self) -> pxl8_command_msg {
self.deserialize_command()
}
pub fn send_snapshot(
&mut self,
header: &pxl8_snapshot_header,
entities: &[pxl8_entity_state],
sequence: u32,
) {
if !self.has_client {
return;
}
let mut offset = 0;
let msg_header = pxl8_msg_header {
sequence,
size: 0,
type_: pxl8_msg_type::PXL8_MSG_SNAPSHOT as u8,
version: PXL8_PROTOCOL_VERSION as u8,
};
offset += self.serialize_header(&msg_header, offset);
offset += self.serialize_snapshot_header(header, offset);
for entity in entities {
offset += self.serialize_entity_state(entity, offset);
}
sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr);
}
fn serialize_header(&mut self, h: &pxl8_msg_header, offset: usize) -> usize {
let buf = &mut self.send_buf[offset..];
buf[0..4].copy_from_slice(&h.sequence.to_be_bytes());
buf[4..6].copy_from_slice(&h.size.to_be_bytes());
buf[6] = h.type_;
buf[7] = h.version;
8
}
fn serialize_snapshot_header(&mut self, h: &pxl8_snapshot_header, offset: usize) -> usize {
let buf = &mut self.send_buf[offset..];
buf[0..2].copy_from_slice(&h.entity_count.to_be_bytes());
buf[2..4].copy_from_slice(&h.event_count.to_be_bytes());
buf[4..12].copy_from_slice(&h.player_id.to_be_bytes());
buf[12..20].copy_from_slice(&h.tick.to_be_bytes());
buf[20..24].copy_from_slice(&h.time.to_be_bytes());
24
}
fn serialize_entity_state(&mut self, e: &pxl8_entity_state, offset: usize) -> usize {
let buf = &mut self.send_buf[offset..];
buf[0..8].copy_from_slice(&e.entity_id.to_be_bytes());
buf[8..64].copy_from_slice(&e.userdata);
64
}
fn deserialize_header(&self) -> pxl8_msg_header {
let buf = &self.recv_buf;
pxl8_msg_header {
sequence: u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]),
size: u16::from_be_bytes([buf[4], buf[5]]),
type_: buf[6],
version: buf[7],
}
}
fn deserialize_input(&self) -> pxl8_input_msg {
let buf = &self.recv_buf[8..];
pxl8_input_msg {
buttons: u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]),
look_dx: f32::from_be_bytes([buf[4], buf[5], buf[6], buf[7]]),
look_dy: f32::from_be_bytes([buf[8], buf[9], buf[10], buf[11]]),
move_x: f32::from_be_bytes([buf[12], buf[13], buf[14], buf[15]]),
move_y: f32::from_be_bytes([buf[16], buf[17], buf[18], buf[19]]),
yaw: f32::from_be_bytes([buf[20], buf[21], buf[22], buf[23]]),
tick: u64::from_be_bytes([buf[24], buf[25], buf[26], buf[27], buf[28], buf[29], buf[30], buf[31]]),
timestamp: u64::from_be_bytes([buf[32], buf[33], buf[34], buf[35], buf[36], buf[37], buf[38], buf[39]]),
}
}
fn deserialize_command(&self) -> pxl8_command_msg {
let buf = &self.recv_buf[8..];
let mut cmd = pxl8_command_msg {
cmd_type: u16::from_be_bytes([buf[0], buf[1]]),
payload: [0u8; 64],
payload_size: u16::from_be_bytes([buf[66], buf[67]]),
tick: u64::from_be_bytes([buf[68], buf[69], buf[70], buf[71], buf[72], buf[73], buf[74], buf[75]]),
};
cmd.payload.copy_from_slice(&buf[2..66]);
cmd
}
}
impl Drop for Transport {
fn drop(&mut self) {
sys::close(self.socket);
}
}

View file

@ -8,55 +8,67 @@ static const char embed_fennel[] = {
, 0 };
static const char embed_pxl8[] = {
#embed "client/src/lua/pxl8.lua"
#embed "src/lua/pxl8.lua"
, 0 };
static const char embed_pxl8_anim[] = {
#embed "client/src/lua/pxl8/anim.lua"
#embed "src/lua/pxl8/anim.lua"
, 0 };
static const char embed_pxl8_bytes[] = {
#embed "src/lua/pxl8/bytes.lua"
, 0 };
static const char embed_pxl8_core[] = {
#embed "client/src/lua/pxl8/core.lua"
#embed "src/lua/pxl8/core.lua"
, 0 };
static const char embed_pxl8_effects[] = {
#embed "src/lua/pxl8/effects.lua"
, 0 };
static const char embed_pxl8_gfx2d[] = {
#embed "client/src/lua/pxl8/gfx2d.lua"
#embed "src/lua/pxl8/gfx2d.lua"
, 0 };
static const char embed_pxl8_gfx3d[] = {
#embed "client/src/lua/pxl8/gfx3d.lua"
#embed "src/lua/pxl8/gfx3d.lua"
, 0 };
static const char embed_pxl8_gui[] = {
#embed "client/src/lua/pxl8/gui.lua"
#embed "src/lua/pxl8/gui.lua"
, 0 };
static const char embed_pxl8_input[] = {
#embed "client/src/lua/pxl8/input.lua"
#embed "src/lua/pxl8/input.lua"
, 0 };
static const char embed_pxl8_math[] = {
#embed "client/src/lua/pxl8/math.lua"
#embed "src/lua/pxl8/math.lua"
, 0 };
static const char embed_pxl8_net[] = {
#embed "src/lua/pxl8/net.lua"
, 0 };
static const char embed_pxl8_particles[] = {
#embed "client/src/lua/pxl8/particles.lua"
#embed "src/lua/pxl8/particles.lua"
, 0 };
static const char embed_pxl8_sfx[] = {
#embed "client/src/lua/pxl8/sfx.lua"
#embed "src/lua/pxl8/sfx.lua"
, 0 };
static const char embed_pxl8_tilemap[] = {
#embed "client/src/lua/pxl8/tilemap.lua"
#embed "src/lua/pxl8/tilemap.lua"
, 0 };
static const char embed_pxl8_transition[] = {
#embed "client/src/lua/pxl8/transition.lua"
#embed "src/lua/pxl8/transition.lua"
, 0 };
static const char embed_pxl8_world[] = {
#embed "client/src/lua/pxl8/world.lua"
#embed "src/lua/pxl8/world.lua"
, 0 };
#define PXL8_EMBED_ENTRY(var, name) {name, var, sizeof(var) - 1}
@ -67,12 +79,15 @@ static const pxl8_embed pxl8_embeds[] = {
PXL8_EMBED_ENTRY(embed_fennel, "fennel"),
PXL8_EMBED_ENTRY(embed_pxl8, "pxl8"),
PXL8_EMBED_ENTRY(embed_pxl8_anim, "pxl8.anim"),
PXL8_EMBED_ENTRY(embed_pxl8_bytes, "pxl8.bytes"),
PXL8_EMBED_ENTRY(embed_pxl8_core, "pxl8.core"),
PXL8_EMBED_ENTRY(embed_pxl8_effects, "pxl8.effects"),
PXL8_EMBED_ENTRY(embed_pxl8_gfx2d, "pxl8.gfx2d"),
PXL8_EMBED_ENTRY(embed_pxl8_gfx3d, "pxl8.gfx3d"),
PXL8_EMBED_ENTRY(embed_pxl8_gui, "pxl8.gui"),
PXL8_EMBED_ENTRY(embed_pxl8_input, "pxl8.input"),
PXL8_EMBED_ENTRY(embed_pxl8_math, "pxl8.math"),
PXL8_EMBED_ENTRY(embed_pxl8_net, "pxl8.net"),
PXL8_EMBED_ENTRY(embed_pxl8_particles, "pxl8.particles"),
PXL8_EMBED_ENTRY(embed_pxl8_sfx, "pxl8.sfx"),
PXL8_EMBED_ENTRY(embed_pxl8_tilemap, "pxl8.tilemap"),

234
src/core/pxl8_bytes.c Normal file
View file

@ -0,0 +1,234 @@
#include "pxl8_bytes.h"
#include <string.h>
void pxl8_pack_u8(u8* buf, size_t offset, u8 val) {
buf[offset] = val;
}
void pxl8_pack_u16_le(u8* buf, size_t offset, u16 val) {
buf[offset] = (u8)(val);
buf[offset + 1] = (u8)(val >> 8);
}
void pxl8_pack_u16_be(u8* buf, size_t offset, u16 val) {
buf[offset] = (u8)(val >> 8);
buf[offset + 1] = (u8)(val);
}
void pxl8_pack_u32_le(u8* buf, size_t offset, u32 val) {
buf[offset] = (u8)(val);
buf[offset + 1] = (u8)(val >> 8);
buf[offset + 2] = (u8)(val >> 16);
buf[offset + 3] = (u8)(val >> 24);
}
void pxl8_pack_u32_be(u8* buf, size_t offset, u32 val) {
buf[offset] = (u8)(val >> 24);
buf[offset + 1] = (u8)(val >> 16);
buf[offset + 2] = (u8)(val >> 8);
buf[offset + 3] = (u8)(val);
}
void pxl8_pack_u64_le(u8* buf, size_t offset, u64 val) {
buf[offset] = (u8)(val);
buf[offset + 1] = (u8)(val >> 8);
buf[offset + 2] = (u8)(val >> 16);
buf[offset + 3] = (u8)(val >> 24);
buf[offset + 4] = (u8)(val >> 32);
buf[offset + 5] = (u8)(val >> 40);
buf[offset + 6] = (u8)(val >> 48);
buf[offset + 7] = (u8)(val >> 56);
}
void pxl8_pack_u64_be(u8* buf, size_t offset, u64 val) {
buf[offset] = (u8)(val >> 56);
buf[offset + 1] = (u8)(val >> 48);
buf[offset + 2] = (u8)(val >> 40);
buf[offset + 3] = (u8)(val >> 32);
buf[offset + 4] = (u8)(val >> 24);
buf[offset + 5] = (u8)(val >> 16);
buf[offset + 6] = (u8)(val >> 8);
buf[offset + 7] = (u8)(val);
}
void pxl8_pack_i8(u8* buf, size_t offset, i8 val) {
buf[offset] = (u8)val;
}
void pxl8_pack_i16_le(u8* buf, size_t offset, i16 val) {
pxl8_pack_u16_le(buf, offset, (u16)val);
}
void pxl8_pack_i16_be(u8* buf, size_t offset, i16 val) {
pxl8_pack_u16_be(buf, offset, (u16)val);
}
void pxl8_pack_i32_le(u8* buf, size_t offset, i32 val) {
pxl8_pack_u32_le(buf, offset, (u32)val);
}
void pxl8_pack_i32_be(u8* buf, size_t offset, i32 val) {
pxl8_pack_u32_be(buf, offset, (u32)val);
}
void pxl8_pack_i64_le(u8* buf, size_t offset, i64 val) {
pxl8_pack_u64_le(buf, offset, (u64)val);
}
void pxl8_pack_i64_be(u8* buf, size_t offset, i64 val) {
pxl8_pack_u64_be(buf, offset, (u64)val);
}
void pxl8_pack_f32_le(u8* buf, size_t offset, f32 val) {
u32 bits;
memcpy(&bits, &val, sizeof(bits));
pxl8_pack_u32_le(buf, offset, bits);
}
void pxl8_pack_f32_be(u8* buf, size_t offset, f32 val) {
u32 bits;
memcpy(&bits, &val, sizeof(bits));
pxl8_pack_u32_be(buf, offset, bits);
}
void pxl8_pack_f64_le(u8* buf, size_t offset, f64 val) {
u64 bits;
memcpy(&bits, &val, sizeof(bits));
pxl8_pack_u64_le(buf, offset, bits);
}
void pxl8_pack_f64_be(u8* buf, size_t offset, f64 val) {
u64 bits;
memcpy(&bits, &val, sizeof(bits));
pxl8_pack_u64_be(buf, offset, bits);
}
u8 pxl8_unpack_u8(const u8* buf, size_t offset) {
return buf[offset];
}
u16 pxl8_unpack_u16_le(const u8* buf, size_t offset) {
return (u16)buf[offset] | ((u16)buf[offset + 1] << 8);
}
u16 pxl8_unpack_u16_be(const u8* buf, size_t offset) {
return ((u16)buf[offset] << 8) | (u16)buf[offset + 1];
}
u32 pxl8_unpack_u32_le(const u8* buf, size_t offset) {
return (u32)buf[offset] |
((u32)buf[offset + 1] << 8) |
((u32)buf[offset + 2] << 16) |
((u32)buf[offset + 3] << 24);
}
u32 pxl8_unpack_u32_be(const u8* buf, size_t offset) {
return ((u32)buf[offset] << 24) |
((u32)buf[offset + 1] << 16) |
((u32)buf[offset + 2] << 8) |
(u32)buf[offset + 3];
}
u64 pxl8_unpack_u64_le(const u8* buf, size_t offset) {
return (u64)buf[offset] |
((u64)buf[offset + 1] << 8) |
((u64)buf[offset + 2] << 16) |
((u64)buf[offset + 3] << 24) |
((u64)buf[offset + 4] << 32) |
((u64)buf[offset + 5] << 40) |
((u64)buf[offset + 6] << 48) |
((u64)buf[offset + 7] << 56);
}
u64 pxl8_unpack_u64_be(const u8* buf, size_t offset) {
return ((u64)buf[offset] << 56) |
((u64)buf[offset + 1] << 48) |
((u64)buf[offset + 2] << 40) |
((u64)buf[offset + 3] << 32) |
((u64)buf[offset + 4] << 24) |
((u64)buf[offset + 5] << 16) |
((u64)buf[offset + 6] << 8) |
(u64)buf[offset + 7];
}
i8 pxl8_unpack_i8(const u8* buf, size_t offset) {
return (i8)buf[offset];
}
i16 pxl8_unpack_i16_le(const u8* buf, size_t offset) {
return (i16)pxl8_unpack_u16_le(buf, offset);
}
i16 pxl8_unpack_i16_be(const u8* buf, size_t offset) {
return (i16)pxl8_unpack_u16_be(buf, offset);
}
i32 pxl8_unpack_i32_le(const u8* buf, size_t offset) {
return (i32)pxl8_unpack_u32_le(buf, offset);
}
i32 pxl8_unpack_i32_be(const u8* buf, size_t offset) {
return (i32)pxl8_unpack_u32_be(buf, offset);
}
i64 pxl8_unpack_i64_le(const u8* buf, size_t offset) {
return (i64)pxl8_unpack_u64_le(buf, offset);
}
i64 pxl8_unpack_i64_be(const u8* buf, size_t offset) {
return (i64)pxl8_unpack_u64_be(buf, offset);
}
f32 pxl8_unpack_f32_le(const u8* buf, size_t offset) {
u32 bits = pxl8_unpack_u32_le(buf, offset);
f32 result;
memcpy(&result, &bits, sizeof(result));
return result;
}
f32 pxl8_unpack_f32_be(const u8* buf, size_t offset) {
u32 bits = pxl8_unpack_u32_be(buf, offset);
f32 result;
memcpy(&result, &bits, sizeof(result));
return result;
}
f64 pxl8_unpack_f64_le(const u8* buf, size_t offset) {
u64 bits = pxl8_unpack_u64_le(buf, offset);
f64 result;
memcpy(&result, &bits, sizeof(result));
return result;
}
f64 pxl8_unpack_f64_be(const u8* buf, size_t offset) {
u64 bits = pxl8_unpack_u64_be(buf, offset);
f64 result;
memcpy(&result, &bits, sizeof(result));
return result;
}
void pxl8_bit_set(u32* val, u8 bit) {
*val |= (1u << bit);
}
void pxl8_bit_clear(u32* val, u8 bit) {
*val &= ~(1u << bit);
}
bool pxl8_bit_test(u32 val, u8 bit) {
return (val & (1u << bit)) != 0;
}
u32 pxl8_bit_count(u32 val) {
#if defined(__GNUC__) || defined(__clang__)
return (u32)__builtin_popcount(val);
#else
val = val - ((val >> 1) & 0x55555555);
val = (val & 0x33333333) + ((val >> 2) & 0x33333333);
return (((val + (val >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
#endif
}
void pxl8_bit_toggle(u32* val, u8 bit) {
*val ^= (1u << bit);
}

251
src/core/pxl8_bytes.h Normal file
View file

@ -0,0 +1,251 @@
#pragma once
#include <string.h>
#include "pxl8_types.h"
void pxl8_bit_clear(u32* val, u8 bit);
u32 pxl8_bit_count(u32 val);
void pxl8_bit_set(u32* val, u8 bit);
bool pxl8_bit_test(u32 val, u8 bit);
void pxl8_bit_toggle(u32* val, u8 bit);
void pxl8_pack_u8(u8* buf, size_t offset, u8 val);
void pxl8_pack_u16_be(u8* buf, size_t offset, u16 val);
void pxl8_pack_u16_le(u8* buf, size_t offset, u16 val);
void pxl8_pack_u32_be(u8* buf, size_t offset, u32 val);
void pxl8_pack_u32_le(u8* buf, size_t offset, u32 val);
void pxl8_pack_u64_be(u8* buf, size_t offset, u64 val);
void pxl8_pack_u64_le(u8* buf, size_t offset, u64 val);
void pxl8_pack_i8(u8* buf, size_t offset, i8 val);
void pxl8_pack_i16_be(u8* buf, size_t offset, i16 val);
void pxl8_pack_i16_le(u8* buf, size_t offset, i16 val);
void pxl8_pack_i32_be(u8* buf, size_t offset, i32 val);
void pxl8_pack_i32_le(u8* buf, size_t offset, i32 val);
void pxl8_pack_i64_be(u8* buf, size_t offset, i64 val);
void pxl8_pack_i64_le(u8* buf, size_t offset, i64 val);
void pxl8_pack_f32_be(u8* buf, size_t offset, f32 val);
void pxl8_pack_f32_le(u8* buf, size_t offset, f32 val);
void pxl8_pack_f64_be(u8* buf, size_t offset, f64 val);
void pxl8_pack_f64_le(u8* buf, size_t offset, f64 val);
u8 pxl8_unpack_u8(const u8* buf, size_t offset);
u16 pxl8_unpack_u16_be(const u8* buf, size_t offset);
u16 pxl8_unpack_u16_le(const u8* buf, size_t offset);
u32 pxl8_unpack_u32_be(const u8* buf, size_t offset);
u32 pxl8_unpack_u32_le(const u8* buf, size_t offset);
u64 pxl8_unpack_u64_be(const u8* buf, size_t offset);
u64 pxl8_unpack_u64_le(const u8* buf, size_t offset);
i8 pxl8_unpack_i8(const u8* buf, size_t offset);
i16 pxl8_unpack_i16_be(const u8* buf, size_t offset);
i16 pxl8_unpack_i16_le(const u8* buf, size_t offset);
i32 pxl8_unpack_i32_be(const u8* buf, size_t offset);
i32 pxl8_unpack_i32_le(const u8* buf, size_t offset);
i64 pxl8_unpack_i64_be(const u8* buf, size_t offset);
i64 pxl8_unpack_i64_le(const u8* buf, size_t offset);
f32 pxl8_unpack_f32_be(const u8* buf, size_t offset);
f32 pxl8_unpack_f32_le(const u8* buf, size_t offset);
f64 pxl8_unpack_f64_be(const u8* buf, size_t offset);
f64 pxl8_unpack_f64_le(const u8* buf, size_t offset);
typedef struct {
const u8* bytes;
u32 offset;
u32 size;
bool overflow;
} pxl8_stream;
typedef struct {
u8* bytes;
u32 capacity;
u32 offset;
bool overflow;
} pxl8_write_stream;
static inline pxl8_stream pxl8_stream_create(const u8* bytes, u32 size) {
return (pxl8_stream){
.bytes = bytes,
.offset = 0,
.size = size,
.overflow = false
};
}
static inline pxl8_write_stream pxl8_write_stream_create(u8* bytes, u32 capacity) {
return (pxl8_write_stream){
.bytes = bytes,
.capacity = capacity,
.offset = 0,
.overflow = false
};
}
static inline bool pxl8_stream_can_read(const pxl8_stream* s, u32 count) {
return !s->overflow && s->offset + count <= s->size;
}
static inline bool pxl8_stream_has_overflow(const pxl8_stream* s) {
return s->overflow;
}
static inline bool pxl8_write_stream_has_overflow(const pxl8_write_stream* s) {
return s->overflow;
}
static inline u32 pxl8_stream_position(const pxl8_stream* s) {
return s->offset;
}
static inline u32 pxl8_write_stream_position(const pxl8_write_stream* s) {
return s->offset;
}
static inline void pxl8_stream_seek(pxl8_stream* s, u32 offset) {
s->offset = offset;
}
static inline u8 pxl8_read_u8(pxl8_stream* s) {
if (s->offset + 1 > s->size) { s->overflow = true; return 0; }
return pxl8_unpack_u8(s->bytes, s->offset++);
}
static inline u16 pxl8_read_u16(pxl8_stream* s) {
if (s->offset + 2 > s->size) { s->overflow = true; return 0; }
u16 val = pxl8_unpack_u16_le(s->bytes, s->offset);
s->offset += 2;
return val;
}
static inline u16 pxl8_read_u16_be(pxl8_stream* s) {
if (s->offset + 2 > s->size) { s->overflow = true; return 0; }
u16 val = pxl8_unpack_u16_be(s->bytes, s->offset);
s->offset += 2;
return val;
}
static inline u32 pxl8_read_u32(pxl8_stream* s) {
if (s->offset + 4 > s->size) { s->overflow = true; return 0; }
u32 val = pxl8_unpack_u32_le(s->bytes, s->offset);
s->offset += 4;
return val;
}
static inline u32 pxl8_read_u32_be(pxl8_stream* s) {
if (s->offset + 4 > s->size) { s->overflow = true; return 0; }
u32 val = pxl8_unpack_u32_be(s->bytes, s->offset);
s->offset += 4;
return val;
}
static inline u64 pxl8_read_u64(pxl8_stream* s) {
if (s->offset + 8 > s->size) { s->overflow = true; return 0; }
u64 val = pxl8_unpack_u64_le(s->bytes, s->offset);
s->offset += 8;
return val;
}
static inline u64 pxl8_read_u64_be(pxl8_stream* s) {
if (s->offset + 8 > s->size) { s->overflow = true; return 0; }
u64 val = pxl8_unpack_u64_be(s->bytes, s->offset);
s->offset += 8;
return val;
}
static inline i16 pxl8_read_i16(pxl8_stream* s) {
return (i16)pxl8_read_u16(s);
}
static inline i32 pxl8_read_i32(pxl8_stream* s) {
return (i32)pxl8_read_u32(s);
}
static inline f32 pxl8_read_f32(pxl8_stream* s) {
if (s->offset + 4 > s->size) { s->overflow = true; return 0; }
f32 val = pxl8_unpack_f32_le(s->bytes, s->offset);
s->offset += 4;
return val;
}
static inline f32 pxl8_read_f32_be(pxl8_stream* s) {
if (s->offset + 4 > s->size) { s->overflow = true; return 0; }
f32 val = pxl8_unpack_f32_be(s->bytes, s->offset);
s->offset += 4;
return val;
}
static inline void pxl8_read_bytes(pxl8_stream* s, void* dest, u32 count) {
if (s->offset + count > s->size) { s->overflow = true; return; }
memcpy(dest, &s->bytes[s->offset], count);
s->offset += count;
}
static inline void pxl8_skip_bytes(pxl8_stream* s, u32 count) {
if (s->offset + count > s->size) { s->overflow = true; return; }
s->offset += count;
}
static inline const u8* pxl8_read_ptr(pxl8_stream* s, u32 count) {
if (s->offset + count > s->size) { s->overflow = true; return NULL; }
const u8* ptr = &s->bytes[s->offset];
s->offset += count;
return ptr;
}
static inline void pxl8_write_u8(pxl8_write_stream* s, u8 val) {
if (s->offset + 1 > s->capacity) { s->overflow = true; return; }
pxl8_pack_u8(s->bytes, s->offset++, val);
}
static inline void pxl8_write_u16(pxl8_write_stream* s, u16 val) {
if (s->offset + 2 > s->capacity) { s->overflow = true; return; }
pxl8_pack_u16_le(s->bytes, s->offset, val);
s->offset += 2;
}
static inline void pxl8_write_u16_be(pxl8_write_stream* s, u16 val) {
if (s->offset + 2 > s->capacity) { s->overflow = true; return; }
pxl8_pack_u16_be(s->bytes, s->offset, val);
s->offset += 2;
}
static inline void pxl8_write_u32(pxl8_write_stream* s, u32 val) {
if (s->offset + 4 > s->capacity) { s->overflow = true; return; }
pxl8_pack_u32_le(s->bytes, s->offset, val);
s->offset += 4;
}
static inline void pxl8_write_u32_be(pxl8_write_stream* s, u32 val) {
if (s->offset + 4 > s->capacity) { s->overflow = true; return; }
pxl8_pack_u32_be(s->bytes, s->offset, val);
s->offset += 4;
}
static inline void pxl8_write_u64(pxl8_write_stream* s, u64 val) {
if (s->offset + 8 > s->capacity) { s->overflow = true; return; }
pxl8_pack_u64_le(s->bytes, s->offset, val);
s->offset += 8;
}
static inline void pxl8_write_u64_be(pxl8_write_stream* s, u64 val) {
if (s->offset + 8 > s->capacity) { s->overflow = true; return; }
pxl8_pack_u64_be(s->bytes, s->offset, val);
s->offset += 8;
}
static inline void pxl8_write_f32(pxl8_write_stream* s, f32 val) {
if (s->offset + 4 > s->capacity) { s->overflow = true; return; }
pxl8_pack_f32_le(s->bytes, s->offset, val);
s->offset += 4;
}
static inline void pxl8_write_f32_be(pxl8_write_stream* s, f32 val) {
if (s->offset + 4 > s->capacity) { s->overflow = true; return; }
pxl8_pack_f32_be(s->bytes, s->offset, val);
s->offset += 4;
}
static inline void pxl8_write_bytes(pxl8_write_stream* s, const void* src, u32 count) {
if (s->offset + count > s->capacity) { s->overflow = true; return; }
memcpy(&s->bytes[s->offset], src, count);
s->offset += count;
}

38
src/core/pxl8_io.h Normal file
View file

@ -0,0 +1,38 @@
#pragma once
#include <stdio.h>
#include <sys/stat.h>
#include "pxl8_bytes.h"
#include "pxl8_types.h"
#ifdef __cplusplus
extern "C" {
#endif
pxl8_result pxl8_io_create_directory(const char* path);
bool pxl8_io_file_exists(const char* path);
void pxl8_io_free_binary_data(u8* data);
void pxl8_io_free_file_content(char* content);
f64 pxl8_io_get_file_modified_time(const char* path);
pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, size_t* size);
pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size);
pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, size_t size);
pxl8_result pxl8_io_write_file(const char* path, const char* content, size_t size);
bool pxl8_key_down(const pxl8_input_state* input, const char* key_name);
bool pxl8_key_pressed(const pxl8_input_state* input, const char* key_name);
bool pxl8_key_released(const pxl8_input_state* input, const char* key_name);
i32 pxl8_mouse_dx(const pxl8_input_state* input);
i32 pxl8_mouse_dy(const pxl8_input_state* input);
bool pxl8_mouse_pressed(const pxl8_input_state* input, i32 button);
bool pxl8_mouse_released(const pxl8_input_state* input, i32 button);
i32 pxl8_mouse_wheel_x(const pxl8_input_state* input);
i32 pxl8_mouse_wheel_y(const pxl8_input_state* input);
i32 pxl8_mouse_x(const pxl8_input_state* input);
i32 pxl8_mouse_y(const pxl8_input_state* input);
#ifdef __cplusplus
}
#endif

View file

@ -29,8 +29,9 @@ typedef __uint128_t u128;
#endif
typedef enum pxl8_pixel_mode {
PXL8_PIXEL_INDEXED,
PXL8_PIXEL_HICOLOR
PXL8_PIXEL_INDEXED = 1,
PXL8_PIXEL_HICOLOR = 2,
PXL8_PIXEL_RGBA = 4,
} pxl8_pixel_mode;
typedef enum pxl8_cursor {

194
src/gfx/pxl8_blend.c Normal file
View file

@ -0,0 +1,194 @@
#include "pxl8_blend.h"
#include "pxl8_colormap.h"
#include <stdlib.h>
struct pxl8_palette_cube {
u8 colors[PXL8_PALETTE_SIZE * 3];
u8 table[PXL8_CUBE_ENTRIES];
u8 stable[PXL8_CUBE_ENTRIES];
};
struct pxl8_additive_table {
u8 table[PXL8_BLEND_TABLE_SIZE];
};
struct pxl8_overbright_table {
u8 table[PXL8_OVERBRIGHT_TABLE_SIZE];
};
static u8 find_closest_stable(const pxl8_palette* pal, u8 r, u8 g, u8 b) {
u8 best_idx = 1;
u32 best_dist = 0xFFFFFFFF;
u8 dynamic_end = PXL8_DYNAMIC_RANGE_START + PXL8_DYNAMIC_RANGE_COUNT;
for (u32 i = 1; i < PXL8_FULLBRIGHT_START; i++) {
if (i >= PXL8_DYNAMIC_RANGE_START && i < dynamic_end) {
continue;
}
u8 pr, pg, pb;
pxl8_palette_get_rgb(pal, (u8)i, &pr, &pg, &pb);
i32 dr = (i32)r - (i32)pr;
i32 dg = (i32)g - (i32)pg;
i32 db = (i32)b - (i32)pb;
u32 dist = (u32)(dr * dr + dg * dg + db * db);
if (dist < best_dist) {
best_dist = dist;
best_idx = (u8)i;
if (dist == 0) break;
}
}
return best_idx;
}
pxl8_palette_cube* pxl8_palette_cube_create(const pxl8_palette* pal) {
pxl8_palette_cube* cube = calloc(1, sizeof(pxl8_palette_cube));
if (!cube) return NULL;
pxl8_palette_cube_rebuild(cube, pal);
return cube;
}
void pxl8_palette_cube_destroy(pxl8_palette_cube* cube) {
free(cube);
}
void pxl8_palette_cube_rebuild(pxl8_palette_cube* cube, const pxl8_palette* pal) {
if (!cube || !pal) return;
for (u32 i = 0; i < PXL8_PALETTE_SIZE; i++) {
u8 r, g, b;
pxl8_palette_get_rgb(pal, (u8)i, &r, &g, &b);
cube->colors[i * 3 + 0] = r;
cube->colors[i * 3 + 1] = g;
cube->colors[i * 3 + 2] = b;
}
for (u32 bi = 0; bi < PXL8_CUBE_SIZE; bi++) {
for (u32 gi = 0; gi < PXL8_CUBE_SIZE; gi++) {
for (u32 ri = 0; ri < PXL8_CUBE_SIZE; ri++) {
u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri;
u8 r8 = (u8)((ri * 255) / (PXL8_CUBE_SIZE - 1));
u8 g8 = (u8)((gi * 255) / (PXL8_CUBE_SIZE - 1));
u8 b8 = (u8)((bi * 255) / (PXL8_CUBE_SIZE - 1));
cube->table[idx] = pxl8_palette_find_closest(pal, r8, g8, b8);
cube->stable[idx] = find_closest_stable(pal, r8, g8, b8);
}
}
}
}
u8 pxl8_palette_cube_lookup(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b) {
u32 ri = (r * (PXL8_CUBE_SIZE - 1)) / 255;
u32 gi = (g * (PXL8_CUBE_SIZE - 1)) / 255;
u32 bi = (b * (PXL8_CUBE_SIZE - 1)) / 255;
u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri;
return cube->table[idx];
}
u8 pxl8_palette_cube_lookup_stable(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b) {
u32 ri = (r * (PXL8_CUBE_SIZE - 1)) / 255;
u32 gi = (g * (PXL8_CUBE_SIZE - 1)) / 255;
u32 bi = (b * (PXL8_CUBE_SIZE - 1)) / 255;
u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri;
return cube->stable[idx];
}
void pxl8_palette_cube_get_rgb(const pxl8_palette_cube* cube, u8 idx, u8* r, u8* g, u8* b) {
*r = cube->colors[idx * 3 + 0];
*g = cube->colors[idx * 3 + 1];
*b = cube->colors[idx * 3 + 2];
}
pxl8_additive_table* pxl8_additive_table_create(const pxl8_palette* pal) {
pxl8_additive_table* table = calloc(1, sizeof(pxl8_additive_table));
if (!table) return NULL;
pxl8_additive_table_rebuild(table, pal);
return table;
}
void pxl8_additive_table_destroy(pxl8_additive_table* table) {
free(table);
}
void pxl8_additive_table_rebuild(pxl8_additive_table* table, const pxl8_palette* pal) {
if (!table || !pal) return;
for (u32 src = 0; src < 256; src++) {
u8 sr, sg, sb;
pxl8_palette_get_rgb(pal, (u8)src, &sr, &sg, &sb);
for (u32 dst = 0; dst < 256; dst++) {
u32 idx = src * 256 + dst;
if (src == PXL8_TRANSPARENT) {
table->table[idx] = (u8)dst;
continue;
}
u8 dr, dg, db;
pxl8_palette_get_rgb(pal, (u8)dst, &dr, &dg, &db);
u16 ar = (u16)sr + (u16)dr;
u16 ag = (u16)sg + (u16)dg;
u16 ab = (u16)sb + (u16)db;
if (ar > 255) ar = 255;
if (ag > 255) ag = 255;
if (ab > 255) ab = 255;
table->table[idx] = find_closest_stable(pal, (u8)ar, (u8)ag, (u8)ab);
}
}
}
u8 pxl8_additive_blend(const pxl8_additive_table* table, u8 src, u8 dst) {
return table->table[(u32)src * 256 + dst];
}
pxl8_overbright_table* pxl8_overbright_table_create(const pxl8_palette* pal) {
pxl8_overbright_table* table = calloc(1, sizeof(pxl8_overbright_table));
if (!table) return NULL;
pxl8_overbright_table_rebuild(table, pal);
return table;
}
void pxl8_overbright_table_destroy(pxl8_overbright_table* table) {
free(table);
}
void pxl8_overbright_table_rebuild(pxl8_overbright_table* table, const pxl8_palette* pal) {
if (!table || !pal) return;
for (u32 level = 0; level < PXL8_OVERBRIGHT_LEVELS; level++) {
f32 overbright = (f32)level / (f32)(PXL8_OVERBRIGHT_LEVELS - 1);
for (u32 pal_idx = 0; pal_idx < 256; pal_idx++) {
u32 idx = level * 256 + pal_idx;
if (pal_idx == PXL8_TRANSPARENT) {
table->table[idx] = PXL8_TRANSPARENT;
continue;
}
u8 r, g, b;
pxl8_palette_get_rgb(pal, (u8)pal_idx, &r, &g, &b);
u8 or = (u8)(r + (255 - r) * overbright);
u8 og = (u8)(g + (255 - g) * overbright);
u8 ob = (u8)(b + (255 - b) * overbright);
table->table[idx] = find_closest_stable(pal, or, og, ob);
}
}
}
u8 pxl8_overbright_lookup(const pxl8_overbright_table* table, u8 pal_idx, f32 emissive) {
u32 level = (u32)(emissive * (PXL8_OVERBRIGHT_LEVELS - 1));
if (level >= PXL8_OVERBRIGHT_LEVELS) level = PXL8_OVERBRIGHT_LEVELS - 1;
return table->table[level * 256 + pal_idx];
}

39
src/gfx/pxl8_blend.h Normal file
View file

@ -0,0 +1,39 @@
#pragma once
#include "pxl8_palette.h"
#include "pxl8_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#define PXL8_CUBE_SIZE 32
#define PXL8_CUBE_ENTRIES (PXL8_CUBE_SIZE * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE)
#define PXL8_BLEND_TABLE_SIZE (256 * 256)
#define PXL8_OVERBRIGHT_LEVELS 16
#define PXL8_OVERBRIGHT_TABLE_SIZE (256 * PXL8_OVERBRIGHT_LEVELS)
typedef struct pxl8_additive_table pxl8_additive_table;
typedef struct pxl8_overbright_table pxl8_overbright_table;
typedef struct pxl8_palette_cube pxl8_palette_cube;
pxl8_additive_table* pxl8_additive_table_create(const pxl8_palette* pal);
void pxl8_additive_table_destroy(pxl8_additive_table* table);
void pxl8_additive_table_rebuild(pxl8_additive_table* table, const pxl8_palette* pal);
u8 pxl8_additive_blend(const pxl8_additive_table* table, u8 src, u8 dst);
pxl8_overbright_table* pxl8_overbright_table_create(const pxl8_palette* pal);
void pxl8_overbright_table_destroy(pxl8_overbright_table* table);
void pxl8_overbright_table_rebuild(pxl8_overbright_table* table, const pxl8_palette* pal);
u8 pxl8_overbright_lookup(const pxl8_overbright_table* table, u8 pal_idx, f32 emissive);
pxl8_palette_cube* pxl8_palette_cube_create(const pxl8_palette* pal);
void pxl8_palette_cube_destroy(pxl8_palette_cube* cube);
void pxl8_palette_cube_rebuild(pxl8_palette_cube* cube, const pxl8_palette* pal);
u8 pxl8_palette_cube_lookup(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b);
u8 pxl8_palette_cube_lookup_stable(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b);
void pxl8_palette_cube_get_rgb(const pxl8_palette_cube* cube, u8 idx, u8* r, u8* g, u8* b);
#ifdef __cplusplus
}
#endif

View file

@ -3,7 +3,7 @@
#include "pxl8_types.h"
static inline i32 pxl8_bytes_per_pixel(pxl8_pixel_mode mode) {
return (mode == PXL8_PIXEL_HICOLOR) ? 2 : 1;
return (i32)mode;
}
static inline u16 pxl8_rgb565_pack(u8 r, u8 g, u8 b) {

View file

@ -1,26 +1,63 @@
#include "pxl8_colormap.h"
#include <string.h>
static void rgb_to_hsl(u8 r, u8 g, u8 b, i32* h, i32* s, i32* l) {
i32 max = r > g ? (r > b ? r : b) : (g > b ? g : b);
i32 min = r < g ? (r < b ? r : b) : (g < b ? g : b);
i32 chroma = max - min;
*l = (max + min) / 2;
if (chroma == 0) {
*h = 0;
*s = 0;
} else {
i32 denom = 255 - (*l > 127 ? 2 * (*l) - 255 : 255 - 2 * (*l));
if (denom <= 0) denom = 1;
*s = (chroma * 255) / denom;
if (*s > 255) *s = 255;
if (max == (i32)r) {
*h = (((i32)g - (i32)b) * 60) / chroma;
if (*h < 0) *h += 360;
} else if (max == (i32)g) {
*h = 120 + (((i32)b - (i32)r) * 60) / chroma;
} else {
*h = 240 + (((i32)r - (i32)g) * 60) / chroma;
}
}
}
static u8 find_closest_color(const u32* palette, u8 target_r, u8 target_g, u8 target_b) {
u8 best_idx = 1;
u32 best_dist = 0xFFFFFFFF;
u8 dynamic_end = PXL8_DYNAMIC_RANGE_START + PXL8_DYNAMIC_RANGE_COUNT;
i32 th, ts, tl;
rgb_to_hsl(target_r, target_g, target_b, &th, &ts, &tl);
for (u32 i = 1; i < PXL8_FULLBRIGHT_START; i++) {
if (i >= PXL8_DYNAMIC_RANGE_START && i < dynamic_end) {
continue;
}
u32 c = palette[i];
u8 pr = (c >> 24) & 0xFF;
u8 pg = (c >> 16) & 0xFF;
u8 pb = (c >> 8) & 0xFF;
u8 pr = c & 0xFF;
u8 pg = (c >> 8) & 0xFF;
u8 pb = (c >> 16) & 0xFF;
i32 dr = (i32)target_r - (i32)pr;
i32 dg = (i32)target_g - (i32)pg;
i32 db = (i32)target_b - (i32)pb;
u32 dist = (u32)(dr * dr + dg * dg + db * db);
i32 ph, ps, pl;
rgb_to_hsl(pr, pg, pb, &ph, &ps, &pl);
i32 dh = th - ph;
if (dh > 180) dh -= 360;
if (dh < -180) dh += 360;
i32 ds = ts - ps;
i32 dl = tl - pl;
u32 dist = (u32)(dh * dh * 4 + ds * ds + dl * dl);
if (dist < best_dist) {
best_dist = dist;
@ -62,9 +99,9 @@ void pxl8_colormap_generate(pxl8_colormap* cm, const u32* palette, const pxl8_le
result_idx = (u8)pal_idx;
} else {
u32 c = palette[pal_idx];
u8 r = (c >> 24) & 0xFF;
u8 g = (c >> 16) & 0xFF;
u8 b = (c >> 8) & 0xFF;
u8 r = c & 0xFF;
u8 g = (c >> 8) & 0xFF;
u8 b = (c >> 16) & 0xFF;
u8 target_r = (u8)(dark_r + (r - dark_r) * brightness);
u8 target_g = (u8)(dark_g + (g - dark_g) * brightness);

View file

@ -9,10 +9,109 @@ struct pxl8_cpu_render_target {
u8* framebuffer;
u32 height;
u32 width;
f32* zbuffer;
u16* zbuffer;
u32* light_accum;
};
static inline u16 depth_to_u16(f32 z) {
f32 d = (z + 1.0f) * 32767.5f;
if (d < 0.0f) d = 0.0f;
if (d > 65535.0f) d = 65535.0f;
return (u16)d;
}
static inline f32 calc_light_intensity(const pxl8_light* light, pxl8_vec3 world_pos, pxl8_vec3 normal) {
pxl8_vec3 to_light = pxl8_vec3_sub(light->position, world_pos);
f32 dist_sq = pxl8_vec3_dot(to_light, to_light);
if (dist_sq >= light->radius_sq) {
return 0.0f;
}
f32 intensity_norm = light->intensity * (1.0f / 255.0f);
if (dist_sq < 0.001f) {
return intensity_norm;
}
f32 inv_dist = pxl8_fast_inv_sqrt(dist_sq);
pxl8_vec3 light_dir = pxl8_vec3_scale(to_light, inv_dist);
f32 n_dot_l = pxl8_vec3_dot(normal, light_dir);
if (n_dot_l <= 0.0f) {
return 0.0f;
}
f32 falloff = 1.0f - dist_sq * light->inv_radius_sq;
return n_dot_l * falloff * intensity_norm;
}
typedef struct pxl8_light_result {
u8 light;
u32 light_color;
} pxl8_light_result;
static pxl8_light_result calc_vertex_light(
pxl8_vec3 world_pos,
pxl8_vec3 normal,
const pxl8_3d_frame* frame
) {
f32 intensity = 0.25f + frame->uniforms.ambient * (1.0f / 255.0f);
f32 accum_r = 0.0f;
f32 accum_g = 0.0f;
f32 accum_b = 0.0f;
f32 total_dynamic = 0.0f;
f32 celestial_dot = -pxl8_vec3_dot(normal, frame->uniforms.celestial_dir);
if (celestial_dot > 0.0f) {
intensity += celestial_dot * frame->uniforms.celestial_intensity;
}
f32 sky_factor = normal.y * 0.5f + 0.5f;
if (sky_factor < 0.0f) sky_factor = 0.0f;
intensity += sky_factor * frame->uniforms.celestial_intensity * 0.3f;
for (u32 i = 0; i < frame->uniforms.num_lights; i++) {
const pxl8_light* light = &frame->uniforms.lights[i];
f32 contrib = calc_light_intensity(light, world_pos, normal);
if (contrib > 0.0f) {
intensity += contrib;
total_dynamic += contrib;
accum_r += light->r * contrib;
accum_g += light->g * contrib;
accum_b += light->b * contrib;
}
}
u32 light_color = 0;
if (total_dynamic > 0.001f) {
f32 inv_total = 1.0f / total_dynamic;
f32 tint_strength = total_dynamic * 1.5f;
if (tint_strength > 1.0f) tint_strength = 1.0f;
u32 r = (u32)(accum_r * inv_total);
u32 g = (u32)(accum_g * inv_total);
u32 b = (u32)(accum_b * inv_total);
u32 a = (u32)(tint_strength * 255.0f);
if (r > 255) r = 255;
if (g > 255) g = 255;
if (b > 255) b = 255;
light_color = r | (g << 8) | (b << 16) | (a << 24);
}
if (intensity < 0.0f) intensity = 0.0f;
if (intensity > 1.0f) intensity = 1.0f;
return (pxl8_light_result){
.light = (u8)(intensity * 255.0f),
.light_color = light_color,
};
}
#define PXL8_MAX_TARGET_STACK 8
struct pxl8_cpu_backend {
@ -23,6 +122,8 @@ struct pxl8_cpu_backend {
const u32* palette;
pxl8_3d_frame frame;
pxl8_mat4 mvp;
u32* output;
u32 output_size;
};
static inline void clip_line_2d(i32* x0, i32* y0, i32* x1, i32* y1, i32 w, i32 h, bool* visible) {
@ -90,6 +191,15 @@ pxl8_cpu_backend* pxl8_cpu_create(u32 width, u32 height) {
cpu->target_stack[0] = base_target;
cpu->target_stack_depth = 1;
cpu->current_target = base_target;
cpu->output_size = width * height;
cpu->output = calloc(cpu->output_size, sizeof(u32));
if (!cpu->output) {
pxl8_cpu_destroy_render_target(base_target);
free(cpu);
return NULL;
}
return cpu;
}
@ -98,6 +208,7 @@ void pxl8_cpu_destroy(pxl8_cpu_backend* cpu) {
for (u32 i = 0; i < cpu->target_stack_depth; i++) {
pxl8_cpu_destroy_render_target(cpu->target_stack[i]);
}
free(cpu->output);
free(cpu);
}
@ -121,7 +232,10 @@ void pxl8_cpu_clear_depth(pxl8_cpu_backend* cpu) {
if (!cpu || !cpu->current_target) return;
pxl8_cpu_render_target* render_target = cpu->current_target;
if (render_target->zbuffer) {
memset(render_target->zbuffer, 0x7F, render_target->width * render_target->height * sizeof(f32));
u32 count = render_target->width * render_target->height;
for (u32 i = 0; i < count; i++) {
render_target->zbuffer[i] = 0xFFFF;
}
}
if (render_target->light_accum) {
memset(render_target->light_accum, 0, render_target->width * render_target->height * sizeof(u32));
@ -275,8 +389,44 @@ typedef struct {
f32 u, v;
u8 color;
u8 light;
u32 light_color;
} vertex_output;
static inline u32 blend_tri_light_color(u32 lc0, u32 lc1, u32 lc2) {
u32 a0 = (lc0 >> 24) & 0xFF;
u32 a1 = (lc1 >> 24) & 0xFF;
u32 a2 = (lc2 >> 24) & 0xFF;
u32 total_a = a0 + a1 + a2;
if (total_a == 0) return 0;
u32 r = ((lc0 & 0xFF) * a0 + (lc1 & 0xFF) * a1 + (lc2 & 0xFF) * a2) / total_a;
u32 g = (((lc0 >> 8) & 0xFF) * a0 + ((lc1 >> 8) & 0xFF) * a1 + ((lc2 >> 8) & 0xFF) * a2) / total_a;
u32 b = (((lc0 >> 16) & 0xFF) * a0 + ((lc1 >> 16) & 0xFF) * a1 + ((lc2 >> 16) & 0xFF) * a2) / total_a;
u32 a = total_a / 3;
if (a > 255) a = 255;
return r | (g << 8) | (b << 16) | (a << 24);
}
static inline u32 lerp_light_color(u32 a, u32 b, f32 t) {
u32 ar = a & 0xFF;
u32 ag = (a >> 8) & 0xFF;
u32 ab = (a >> 16) & 0xFF;
u32 aa = (a >> 24) & 0xFF;
u32 br = b & 0xFF;
u32 bg = (b >> 8) & 0xFF;
u32 bb = (b >> 16) & 0xFF;
u32 ba = (b >> 24) & 0xFF;
u32 or = ar + (u32)((i32)(br - ar) * t);
u32 og = ag + (u32)((i32)(bg - ag) * t);
u32 ob = ab + (u32)((i32)(bb - ab) * t);
u32 oa = aa + (u32)((i32)(ba - aa) * t);
return or | (og << 8) | (ob << 16) | (oa << 24);
}
static vertex_output lerp_vertex(const vertex_output* a, const vertex_output* b, f32 t) {
vertex_output out;
out.clip_pos.x = a->clip_pos.x + (b->clip_pos.x - a->clip_pos.x) * t;
@ -293,6 +443,7 @@ static vertex_output lerp_vertex(const vertex_output* a, const vertex_output* b,
out.v = a->v + (b->v - a->v) * t;
out.color = t < 0.5f ? a->color : b->color;
out.light = (u8)(a->light + (b->light - a->light) * t);
out.light_color = lerp_light_color(a->light_color, b->light_color, t);
return out;
}
@ -348,6 +499,8 @@ typedef struct {
f32 w0_recip, w1_recip, w2_recip;
f32 u0_w, v0_w, u1_w, v1_w, u2_w, v2_w;
f32 l0_w, l1_w, l2_w;
u32 lc0, lc1, lc2;
f32 c0_w, c1_w, c2_w;
i32 y_start, y_end, total_height;
f32 inv_total;
u32 target_width, target_height;
@ -419,6 +572,14 @@ static bool setup_triangle(
setup->l1_w = sorted[1]->light * setup->w1_recip;
setup->l2_w = sorted[2]->light * setup->w2_recip;
setup->lc0 = sorted[0]->light_color;
setup->lc1 = sorted[1]->light_color;
setup->lc2 = sorted[2]->light_color;
setup->c0_w = (f32)sorted[0]->color * setup->w0_recip;
setup->c1_w = (f32)sorted[1]->color * setup->w1_recip;
setup->c2_w = (f32)sorted[2]->color * setup->w2_recip;
setup->inv_total = 1.0f / (f32)setup->total_height;
setup->target_width = width;
setup->target_height = height;
@ -432,6 +593,8 @@ static void rasterize_triangle_opaque(
const pxl8_atlas* textures, u32 texture_id, bool dither
) {
pxl8_cpu_render_target* render_target = cpu->current_target;
u32* light_accum = render_target->light_accum;
u32 tri_light_color = light_accum ? blend_tri_light_color(setup->lc0, setup->lc1, setup->lc2) : 0;
const pxl8_atlas_entry* tex_entry = textures ? pxl8_atlas_get_entry(textures, texture_id) : NULL;
u32 atlas_width = textures ? pxl8_atlas_get_width(textures) : 1;
f32 tex_w = tex_entry ? (f32)tex_entry->w : 1.0f;
@ -516,7 +679,7 @@ static void rasterize_triangle_opaque(
u32 row_start = (u32)y * render_target->width;
u8* prow = render_target->framebuffer + row_start;
f32* zrow = render_target->zbuffer + row_start;
u16* zrow = render_target->zbuffer + row_start;
const i32 SUBDIV = 32;
i32 x = x_start;
@ -553,7 +716,8 @@ static void rasterize_triangle_opaque(
f32 z_a = z;
for (i32 px = x; px <= span_end; px++) {
if (z_a <= zrow[px]) {
u16 z16 = depth_to_u16(z_a);
if (z16 < zrow[px]) {
i32 tx = (u_fixed >> 16) & tex_mask_w;
i32 ty = (v_fixed >> 16) & tex_mask_h;
u8 tex_idx = tex_pixels ? tex_pixels[tex_base + (u32)ty * (u32)tex_stride + (u32)tx] : 1;
@ -565,7 +729,10 @@ static void rasterize_triangle_opaque(
}
u8 pal_idx = cpu->colormap ? pxl8_colormap_lookup(cpu->colormap, tex_idx, light) : tex_idx;
prow[px] = pal_idx;
zrow[px] = z_a;
zrow[px] = z16;
if (light_accum) {
light_accum[row_start + (u32)px] = tri_light_color;
}
}
}
@ -587,11 +754,9 @@ static void rasterize_triangle_opaque(
static void rasterize_triangle_passthrough(
pxl8_cpu_backend* cpu,
const tri_setup* setup,
const vertex_output* vo0
const tri_setup* setup
) {
pxl8_cpu_render_target* render_target = cpu->current_target;
u8 color = vo0->color;
for (i32 y = setup->y_start; y <= setup->y_end; y++) {
bool second_half = y > setup->y1 || setup->y1 == setup->y0;
@ -603,20 +768,33 @@ static void rasterize_triangle_passthrough(
f32 ax = (f32)setup->x0 + (f32)(setup->x2 - setup->x0) * alpha;
f32 az = setup->z0 + (setup->z2 - setup->z0) * alpha;
f32 bx, bz;
f32 a_wr = setup->w0_recip + (setup->w2_recip - setup->w0_recip) * alpha;
f32 a_cw = setup->c0_w + (setup->c2_w - setup->c0_w) * alpha;
f32 bx, bz, b_wr, b_cw;
if (second_half) {
bx = (f32)setup->x1 + (f32)(setup->x2 - setup->x1) * beta;
bz = setup->z1 + (setup->z2 - setup->z1) * beta;
b_wr = setup->w1_recip + (setup->w2_recip - setup->w1_recip) * beta;
b_cw = setup->c1_w + (setup->c2_w - setup->c1_w) * beta;
} else {
bx = (f32)setup->x0 + (f32)(setup->x1 - setup->x0) * beta;
bz = setup->z0 + (setup->z1 - setup->z0) * beta;
b_wr = setup->w0_recip + (setup->w1_recip - setup->w0_recip) * beta;
b_cw = setup->c0_w + (setup->c1_w - setup->c0_w) * beta;
}
f32 x_start_f, x_end_f, z_start, z_end;
f32 x_start_f, x_end_f, z_start, z_end, wr_start, wr_end, cw_start, cw_end;
if (ax <= bx) {
x_start_f = ax; x_end_f = bx; z_start = az; z_end = bz;
x_start_f = ax; x_end_f = bx;
z_start = az; z_end = bz;
wr_start = a_wr; wr_end = b_wr;
cw_start = a_cw; cw_end = b_cw;
} else {
x_start_f = bx; x_end_f = ax; z_start = bz; z_end = az;
x_start_f = bx; x_end_f = ax;
z_start = bz; z_end = az;
wr_start = b_wr; wr_end = a_wr;
cw_start = b_cw; cw_end = a_cw;
}
i32 x_start_orig = (i32)(x_start_f + 0.5f);
@ -630,19 +808,30 @@ static void rasterize_triangle_passthrough(
f32 inv_width = 1.0f / (f32)width_orig;
f32 dz = (z_end - z_start) * inv_width;
f32 dwr = (wr_end - wr_start) * inv_width;
f32 dcw = (cw_end - cw_start) * inv_width;
f32 skip = (f32)(x_start - x_start_orig);
f32 z = z_start + dz * skip;
f32 wr = wr_start + dwr * skip;
f32 cw = cw_start + dcw * skip;
u32 row_start = (u32)y * render_target->width;
u8* prow = render_target->framebuffer + row_start;
f32* zrow = render_target->zbuffer + row_start;
u16* zrow = render_target->zbuffer + row_start;
for (i32 px = x_start; px <= x_end; px++) {
if (z <= zrow[px]) {
u16 z16 = depth_to_u16(z);
if (z16 < zrow[px]) {
f32 w = 1.0f / wr;
f32 color_f = cw * w;
u8 color = pxl8_dither_float(color_f, (u32)px, (u32)y);
prow[px] = color;
zrow[px] = z;
zrow[px] = z16;
}
z += dz;
wr += dwr;
cw += dcw;
}
}
}
@ -653,6 +842,8 @@ static void rasterize_triangle_alpha(
const pxl8_atlas* textures, u32 texture_id, u8 mat_alpha, bool dither
) {
pxl8_cpu_render_target* render_target = cpu->current_target;
u32* light_accum = render_target->light_accum;
u32 tri_light_color = light_accum ? blend_tri_light_color(setup->lc0, setup->lc1, setup->lc2) : 0;
const pxl8_atlas_entry* tex_entry = textures ? pxl8_atlas_get_entry(textures, texture_id) : NULL;
u32 atlas_width = textures ? pxl8_atlas_get_width(textures) : 1;
f32 tex_w = tex_entry ? (f32)tex_entry->w : 1.0f;
@ -737,7 +928,7 @@ static void rasterize_triangle_alpha(
u32 row_start = (u32)y * render_target->width;
u8* prow = render_target->framebuffer + row_start;
f32* zrow = render_target->zbuffer + row_start;
u16* zrow = render_target->zbuffer + row_start;
const i32 SUBDIV = 32;
i32 x = x_start;
@ -787,7 +978,11 @@ static void rasterize_triangle_alpha(
if (mat_alpha >= 128) {
prow[px] = src_idx;
if (z_a <= zrow[px]) zrow[px] = z_a;
u16 z16 = depth_to_u16(z_a);
if (z16 < zrow[px]) zrow[px] = z16;
if (light_accum) {
light_accum[row_start + (u32)px] = tri_light_color;
}
}
}
@ -824,7 +1019,7 @@ static void dispatch_triangle(
if (alpha_blend) {
rasterize_triangle_alpha(cpu, &setup, textures, material->texture_id, material->alpha, material->dither);
} else if (passthrough) {
rasterize_triangle_passthrough(cpu, &setup, vo0);
rasterize_triangle_passthrough(cpu, &setup);
} else {
rasterize_triangle_opaque(cpu, &setup, textures, material->texture_id, material->dither);
}
@ -833,13 +1028,13 @@ static void dispatch_triangle(
void pxl8_cpu_draw_mesh(
pxl8_cpu_backend* cpu,
const pxl8_mesh* mesh,
pxl8_mat4 model,
pxl8_material material,
const pxl8_mat4* model,
const pxl8_material* material,
const pxl8_atlas* textures
) {
if (!cpu || !mesh || mesh->index_count < 3 || !cpu->current_target) return;
if (!cpu || !mesh || !model || !material || mesh->index_count < 3 || !cpu->current_target) return;
pxl8_mat4 mv = pxl8_mat4_mul(cpu->frame.view, model);
pxl8_mat4 mv = pxl8_mat4_mul(cpu->frame.view, *model);
pxl8_mat4 mvp = pxl8_mat4_mul(cpu->frame.projection, mv);
f32 near = cpu->frame.near_clip > 0.0f ? cpu->frame.near_clip : 0.1f;
@ -863,9 +1058,9 @@ void pxl8_cpu_draw_mesh(
vo1.clip_pos = pxl8_mat4_mul_vec4(mvp, p1);
vo2.clip_pos = pxl8_mat4_mul_vec4(mvp, p2);
pxl8_vec4 w0 = pxl8_mat4_mul_vec4(model, p0);
pxl8_vec4 w1 = pxl8_mat4_mul_vec4(model, p1);
pxl8_vec4 w2 = pxl8_mat4_mul_vec4(model, p2);
pxl8_vec4 w0 = pxl8_mat4_mul_vec4(*model, p0);
pxl8_vec4 w1 = pxl8_mat4_mul_vec4(*model, p1);
pxl8_vec4 w2 = pxl8_mat4_mul_vec4(*model, p2);
vo0.world_pos = (pxl8_vec3){w0.x, w0.y, w0.z};
vo1.world_pos = (pxl8_vec3){w1.x, w1.y, w1.z};
vo2.world_pos = (pxl8_vec3){w2.x, w2.y, w2.z};
@ -882,15 +1077,36 @@ void pxl8_cpu_draw_mesh(
vo1.color = v1->color;
vo2.color = v2->color;
vo0.light = material.dynamic_lighting ? v0->light : 255;
vo1.light = material.dynamic_lighting ? v1->light : 255;
vo2.light = material.dynamic_lighting ? v2->light : 255;
if (material->dynamic_lighting) {
pxl8_vec3 n0 = pxl8_vec3_normalize(pxl8_mat4_mul_vec3(*model, v0->normal));
pxl8_vec3 n1 = pxl8_vec3_normalize(pxl8_mat4_mul_vec3(*model, v1->normal));
pxl8_vec3 n2 = pxl8_vec3_normalize(pxl8_mat4_mul_vec3(*model, v2->normal));
pxl8_light_result lr0 = calc_vertex_light(vo0.world_pos, n0, &cpu->frame);
pxl8_light_result lr1 = calc_vertex_light(vo1.world_pos, n1, &cpu->frame);
pxl8_light_result lr2 = calc_vertex_light(vo2.world_pos, n2, &cpu->frame);
vo0.light = lr0.light;
vo1.light = lr1.light;
vo2.light = lr2.light;
vo0.light_color = lr0.light_color;
vo1.light_color = lr1.light_color;
vo2.light_color = lr2.light_color;
} else {
vo0.light = 255;
vo1.light = 255;
vo2.light = 255;
vo0.light_color = 0;
vo1.light_color = 0;
vo2.light_color = 0;
}
vertex_output clipped[6];
i32 clipped_count = clip_triangle_near(&vo0, &vo1, &vo2, near, clipped);
for (i32 t = 0; t < clipped_count; t += 3) {
dispatch_triangle(cpu, &clipped[t], &clipped[t+1], &clipped[t+2], textures, &material);
dispatch_triangle(cpu, &clipped[t], &clipped[t+1], &clipped[t+2], textures, material);
}
}
}
@ -964,7 +1180,7 @@ pxl8_cpu_render_target* pxl8_cpu_create_render_target(const pxl8_cpu_render_targ
}
if (desc->with_depth) {
target->zbuffer = calloc(size, sizeof(f32));
target->zbuffer = calloc(size, sizeof(u16));
if (!target->zbuffer) {
free(target->framebuffer);
free(target);
@ -1073,3 +1289,202 @@ u32 pxl8_cpu_render_target_get_height(const pxl8_cpu_render_target* target) {
u32 pxl8_cpu_render_target_get_width(const pxl8_cpu_render_target* target) {
return target ? target->width : 0;
}
u32* pxl8_cpu_render_target_get_light_accum(pxl8_cpu_render_target* target) {
return target ? target->light_accum : NULL;
}
u32* pxl8_cpu_get_output(pxl8_cpu_backend* cpu) {
return cpu ? cpu->output : NULL;
}
void pxl8_cpu_render_glows(
pxl8_cpu_backend* cpu,
const pxl8_glow_source* glows,
u32 glow_count,
const pxl8_additive_table* additive,
const pxl8_palette_cube* palette_cube,
const pxl8_overbright_table* overbright
) {
(void)overbright;
if (!cpu || cpu->target_stack_depth == 0) return;
pxl8_cpu_render_target* target = cpu->target_stack[cpu->target_stack_depth - 1];
if (!target || !target->framebuffer) return;
u32 width = target->width;
u32 height = target->height;
u8* pixels = target->framebuffer;
u16* zbuffer = target->zbuffer;
u32* light_accum = target->light_accum;
for (u32 gi = 0; gi < glow_count; gi++) {
const pxl8_glow_source* glow = &glows[gi];
i32 cx = glow->x;
i32 cy = glow->y;
i32 radius = glow->radius;
if (radius <= 0 || glow->intensity == 0) continue;
i32 x0 = cx - radius;
i32 y0 = cy - radius;
i32 x1 = cx + radius;
i32 y1 = cy + radius;
if (x0 < 0) x0 = 0;
if (y0 < 0) y0 = 0;
if (x1 >= (i32)width) x1 = (i32)width - 1;
if (y1 >= (i32)height) y1 = (i32)height - 1;
if (x0 >= x1 || y0 >= y1) continue;
u16 glow_depth = glow->depth;
u8 base_color = glow->color;
f32 base_intensity = glow->intensity / 255.0f;
f32 radius_f = (f32)radius;
f32 radius_sq = radius_f * radius_f;
f32 inv_radius_sq = 1.0f / radius_sq;
f32 inv_radius = 1.0f / radius_f;
for (i32 y = y0; y <= y1; y++) {
u32 row = (u32)y * width;
f32 dy = (f32)(y - cy);
for (i32 x = x0; x <= x1; x++) {
u32 idx = row + (u32)x;
if (zbuffer && glow_depth > zbuffer[idx]) continue;
f32 dx = (f32)(x - cx);
f32 intensity = 0.0f;
switch (glow->shape) {
case PXL8_GLOW_CIRCLE: {
f32 dist_sq = dx * dx + dy * dy;
if (dist_sq >= radius_sq) continue;
f32 falloff = 1.0f - dist_sq * inv_radius_sq;
intensity = base_intensity * falloff * falloff;
break;
}
case PXL8_GLOW_DIAMOND: {
f32 abs_dx = dx < 0 ? -dx : dx;
f32 abs_dy = dy < 0 ? -dy : dy;
f32 manhattan = abs_dx + abs_dy;
if (manhattan >= radius_f) continue;
f32 falloff = 1.0f - manhattan * inv_radius;
intensity = base_intensity * falloff * falloff;
break;
}
case PXL8_GLOW_SHAFT: {
f32 abs_dx = dx < 0 ? -dx : dx;
f32 abs_dy = dy < 0 ? -dy : dy;
f32 height_f = glow->height > 0 ? (f32)glow->height : radius_f;
if (abs_dx >= radius_f || abs_dy >= height_f) continue;
f32 falloff_x = 1.0f - abs_dx * inv_radius;
f32 falloff_y = 1.0f - abs_dy / height_f;
intensity = base_intensity * falloff_x * falloff_x * falloff_y;
break;
}
}
if (intensity < 0.02f) continue;
u8 int_val = intensity < 1.0f ? (u8)(intensity * 255.0f) : 255;
u8 light_level = pxl8_ordered_dither(int_val, (u32)x, (u32)y);
// Skip very dark pixels to create stippled transparency effect
if (light_level < 8) continue;
u8 shaded = pxl8_colormap_lookup(cpu->colormap, base_color, light_level);
if (intensity > 1.0f && palette_cube) {
f32 over = intensity - 1.0f;
if (over > 1.0f) over = 1.0f;
u8 r, g, b;
pxl8_palette_cube_get_rgb(palette_cube, shaded, &r, &g, &b);
u8 or = (u8)(r + (255 - r) * over);
u8 og = (u8)(g + (255 - g) * over);
u8 ob = (u8)(b + (255 - b) * over);
shaded = pxl8_palette_cube_lookup_stable(palette_cube, or, og, ob);
}
u8 dst = pixels[idx];
if (additive) {
pixels[idx] = pxl8_additive_blend(additive, shaded, dst);
} else {
pixels[idx] = shaded;
}
if (light_accum) {
light_accum[idx] = 0;
}
}
}
}
}
void pxl8_cpu_resolve(pxl8_cpu_backend* cpu) {
if (!cpu || !cpu->palette || cpu->target_stack_depth == 0) return;
pxl8_cpu_render_target* target = cpu->target_stack[0];
if (!target || !target->framebuffer) return;
u32 pixel_count = target->width * target->height;
const u8* pixels = target->framebuffer;
const u32* light_accum = target->light_accum;
const u32* palette = cpu->palette;
u32* output = cpu->output;
for (u32 i = 0; i < pixel_count; i++) {
u32 base = palette[pixels[i]];
if (light_accum && light_accum[i] != 0) {
u32 light_color = light_accum[i];
u32 tint_alpha = (light_color >> 24) & 0xFF;
if (tint_alpha > 0) {
u32 lr = light_color & 0xFF;
u32 lg = (light_color >> 8) & 0xFF;
u32 lb = (light_color >> 16) & 0xFF;
u32 br = base & 0xFF;
u32 bg = (base >> 8) & 0xFF;
u32 bb = (base >> 16) & 0xFF;
u32 ba = (base >> 24) & 0xFF;
u32 t = (tint_alpha * tint_alpha) / 255;
u32 inv_t = 255 - t;
u32 lr_norm = (lr * 255) / 240;
u32 lg_norm = (lg * 255) / 240;
u32 lb_norm = (lb * 255) / 220;
u32 or = ((br * inv_t + (br * lr_norm / 255) * t) / 255);
u32 og = ((bg * inv_t + (bg * lg_norm / 255) * t) / 255);
u32 ob = ((bb * inv_t + (bb * lb_norm / 255) * t) / 255);
if (or > 255) or = 255;
if (og > 255) og = 255;
if (ob > 255) ob = 255;
output[i] = or | (og << 8) | (ob << 16) | (ba << 24);
continue;
}
}
output[i] = base;
}
for (u32 t = 1; t < cpu->target_stack_depth; t++) {
pxl8_cpu_render_target* overlay = cpu->target_stack[t];
if (!overlay || !overlay->framebuffer) continue;
const u8* overlay_pixels = overlay->framebuffer;
for (u32 i = 0; i < pixel_count; i++) {
u8 idx = overlay_pixels[i];
if (idx != 0) {
output[i] = palette[idx];
}
}
}
}

View file

@ -1,7 +1,10 @@
#pragma once
#include "pxl8_atlas.h"
#include "pxl8_blend.h"
#include "pxl8_colormap.h"
#include "pxl8_gfx.h"
#include "pxl8_gfx3d.h"
#include "pxl8_math.h"
#include "pxl8_mesh.h"
#include "pxl8_types.h"
@ -20,19 +23,6 @@ typedef struct pxl8_cpu_render_target_desc {
bool with_lighting;
} pxl8_cpu_render_target_desc;
typedef struct pxl8_3d_frame {
u8 ambient_light;
pxl8_vec3 camera_dir;
pxl8_vec3 camera_pos;
f32 far_clip;
u8 fog_color;
f32 fog_density;
f32 near_clip;
pxl8_mat4 projection;
f32 time;
pxl8_mat4 view;
} pxl8_3d_frame;
pxl8_cpu_backend* pxl8_cpu_create(u32 width, u32 height);
void pxl8_cpu_destroy(pxl8_cpu_backend* cpu);
@ -57,8 +47,8 @@ void pxl8_cpu_draw_line_3d(pxl8_cpu_backend* cpu, pxl8_vec3 v0, pxl8_vec3 v1, u8
void pxl8_cpu_draw_mesh(
pxl8_cpu_backend* cpu,
const pxl8_mesh* mesh,
pxl8_mat4 model,
pxl8_material material,
const pxl8_mat4* model,
const pxl8_material* material,
const pxl8_atlas* textures
);
@ -70,9 +60,21 @@ void pxl8_cpu_draw_mesh_wireframe(
);
u8* pxl8_cpu_get_framebuffer(pxl8_cpu_backend* cpu);
u32* pxl8_cpu_get_output(pxl8_cpu_backend* cpu);
u32 pxl8_cpu_get_height(const pxl8_cpu_backend* cpu);
u32 pxl8_cpu_get_width(const pxl8_cpu_backend* cpu);
void pxl8_cpu_render_glows(
pxl8_cpu_backend* cpu,
const pxl8_glow_source* glows,
u32 glow_count,
const pxl8_additive_table* additive,
const pxl8_palette_cube* palette_cube,
const pxl8_overbright_table* overbright
);
void pxl8_cpu_resolve(pxl8_cpu_backend* cpu);
pxl8_cpu_render_target* pxl8_cpu_create_render_target(const pxl8_cpu_render_target_desc* desc);
void pxl8_cpu_destroy_render_target(pxl8_cpu_render_target* target);
@ -88,6 +90,7 @@ u32 pxl8_cpu_get_target_depth(const pxl8_cpu_backend* cpu);
u8* pxl8_cpu_render_target_get_framebuffer(pxl8_cpu_render_target* target);
u32 pxl8_cpu_render_target_get_height(const pxl8_cpu_render_target* target);
u32 pxl8_cpu_render_target_get_width(const pxl8_cpu_render_target* target);
u32* pxl8_cpu_render_target_get_light_accum(pxl8_cpu_render_target* target);
#ifdef __cplusplus
}

View file

@ -6,6 +6,7 @@
#include "pxl8_ase.h"
#include "pxl8_atlas.h"
#include "pxl8_backend.h"
#include "pxl8_blend.h"
#include "pxl8_blit.h"
#include "pxl8_color.h"
#include "pxl8_colormap.h"
@ -24,6 +25,7 @@ typedef struct pxl8_sprite_cache_entry {
} pxl8_sprite_cache_entry;
struct pxl8_gfx {
pxl8_additive_table* additive_table;
pxl8_atlas* atlas;
pxl8_gfx_backend backend;
pxl8_colormap* colormap;
@ -33,7 +35,9 @@ struct pxl8_gfx {
pxl8_frustum frustum;
const pxl8_hal* hal;
bool initialized;
pxl8_overbright_table* overbright_table;
pxl8_palette* palette;
pxl8_palette_cube* palette_cube;
pxl8_pixel_mode pixel_mode;
void* platform_data;
pxl8_sprite_cache_entry* sprite_cache;
@ -183,11 +187,14 @@ pxl8_gfx* pxl8_gfx_create(
void pxl8_gfx_destroy(pxl8_gfx* gfx) {
if (!gfx) return;
pxl8_additive_table_destroy(gfx->additive_table);
pxl8_atlas_destroy(gfx->atlas);
if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) {
pxl8_cpu_destroy(gfx->backend.cpu);
}
free(gfx->colormap);
pxl8_overbright_table_destroy(gfx->overbright_table);
pxl8_palette_cube_destroy(gfx->palette_cube);
pxl8_palette_destroy(gfx->palette);
free(gfx->sprite_cache);
@ -309,15 +316,30 @@ pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx) {
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) {
if (!gfx || !gfx->initialized || !gfx->hal) return;
u32 bpp = (gfx->pixel_mode == PXL8_PIXEL_HICOLOR) ? 2 : 1;
if (gfx->backend.type == PXL8_GFX_BACKEND_CPU && gfx->pixel_mode == PXL8_PIXEL_INDEXED) {
pxl8_cpu_resolve(gfx->backend.cpu);
u32* output = pxl8_cpu_get_output(gfx->backend.cpu);
if (output) {
gfx->hal->upload_texture(
gfx->platform_data,
output,
gfx->framebuffer_width,
gfx->framebuffer_height,
PXL8_PIXEL_RGBA,
NULL
);
return;
}
}
u32* colors = gfx->palette ? pxl8_palette_colors(gfx->palette) : NULL;
gfx->hal->upload_texture(
gfx->platform_data,
gfx->framebuffer,
gfx->framebuffer_width,
gfx->framebuffer_height,
colors,
bpp
gfx->pixel_mode,
colors
);
}
@ -438,6 +460,7 @@ void pxl8_2d_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) {
if (!gfx || !text) return;
u8* framebuffer = NULL;
u32* light_accum = NULL;
i32 fb_width = 0;
i32 fb_height = 0;
@ -446,6 +469,7 @@ void pxl8_2d_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) {
pxl8_cpu_render_target* render_target = pxl8_cpu_get_target(gfx->backend.cpu);
if (!render_target) return;
framebuffer = pxl8_cpu_render_target_get_framebuffer(render_target);
light_accum = pxl8_cpu_render_target_get_light_accum(render_target);
fb_width = (i32)pxl8_cpu_render_target_get_width(render_target);
fb_height = (i32)pxl8_cpu_render_target_get_height(render_target);
break;
@ -483,7 +507,9 @@ void pxl8_2d_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) {
}
if (pixel_bit) {
framebuffer[py * fb_width + px] = (u8)color;
i32 idx = py * fb_width + px;
framebuffer[idx] = (u8)color;
if (light_accum) light_accum[idx] = 0;
}
}
}
@ -568,30 +594,32 @@ void pxl8_gfx_update(pxl8_gfx* gfx, f32 dt) {
}
}
pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms) {
pxl8_3d_frame frame = {0};
if (!camera) return frame;
frame.view = pxl8_3d_camera_get_view(camera);
frame.projection = pxl8_3d_camera_get_projection(camera);
frame.camera_pos = pxl8_3d_camera_get_position(camera);
frame.camera_dir = pxl8_3d_camera_get_forward(camera);
frame.near_clip = 1.0f;
frame.far_clip = 4096.0f;
if (uniforms) {
frame.uniforms = *uniforms;
}
return frame;
}
void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms) {
if (!gfx || !camera) return;
pxl8_mat4 view = pxl8_3d_camera_get_view(camera);
pxl8_mat4 projection = pxl8_3d_camera_get_projection(camera);
pxl8_vec3 position = pxl8_3d_camera_get_position(camera);
pxl8_vec3 forward = pxl8_3d_camera_get_forward(camera);
pxl8_3d_frame frame = pxl8_3d_frame_from_camera(camera, uniforms);
pxl8_mat4 vp = pxl8_mat4_mul(projection, view);
pxl8_mat4 vp = pxl8_mat4_mul(frame.projection, frame.view);
gfx->frustum = pxl8_frustum_from_matrix(vp);
pxl8_3d_frame frame = {
.ambient_light = uniforms ? uniforms->ambient : 0,
.camera_dir = forward,
.camera_pos = position,
.far_clip = 4096.0f,
.fog_color = uniforms ? uniforms->fog_color : 0,
.fog_density = uniforms ? uniforms->fog_density : 0.0f,
.near_clip = 1.0f,
.projection = projection,
.time = uniforms ? uniforms->time : 0.0f,
.view = view,
};
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
pxl8_cpu_begin_frame(gfx->backend.cpu, &frame);
@ -639,8 +667,8 @@ 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, pxl8_mat4 model, pxl8_material material) {
if (!gfx || !mesh) return;
void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_material* material) {
if (!gfx || !mesh || !model || !material) return;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
pxl8_cpu_draw_mesh(gfx->backend.cpu, mesh, model, material, gfx->atlas);
@ -704,3 +732,67 @@ void pxl8_gfx_pop_target(pxl8_gfx* gfx) {
break;
}
}
static void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx) {
if (!gfx || !gfx->palette) return;
if (!gfx->additive_table) {
gfx->additive_table = pxl8_additive_table_create(gfx->palette);
}
if (!gfx->overbright_table) {
gfx->overbright_table = pxl8_overbright_table_create(gfx->palette);
}
if (!gfx->palette_cube) {
gfx->palette_cube = pxl8_palette_cube_create(gfx->palette);
}
}
void pxl8_gfx_blend_tables_update(pxl8_gfx* gfx) {
if (!gfx || !gfx->palette) return;
if (gfx->additive_table) {
pxl8_additive_table_rebuild(gfx->additive_table, gfx->palette);
}
if (gfx->overbright_table) {
pxl8_overbright_table_rebuild(gfx->overbright_table, gfx->palette);
}
if (gfx->palette_cube) {
pxl8_palette_cube_rebuild(gfx->palette_cube, gfx->palette);
}
}
void pxl8_gfx_colormap_update(pxl8_gfx* gfx) {
if (!gfx || !gfx->palette || !gfx->colormap) return;
u32* colors = pxl8_palette_colors(gfx->palette);
if (colors) {
pxl8_colormap_generate(gfx->colormap, colors, NULL);
}
}
void pxl8_gfx_apply_effect(pxl8_gfx* gfx, pxl8_gfx_effect effect, const void* params, u32 count) {
if (!gfx || !params || count == 0) return;
switch (effect) {
case PXL8_GFX_EFFECT_GLOWS: {
pxl8_gfx_ensure_blend_tables(gfx);
if (!gfx->additive_table || !gfx->overbright_table || !gfx->palette_cube) return;
const pxl8_glow_source* glows = (const pxl8_glow_source*)params;
switch (gfx->backend.type) {
case PXL8_GFX_BACKEND_CPU:
pxl8_cpu_render_glows(
gfx->backend.cpu,
glows,
count,
gfx->additive_table,
gfx->palette_cube,
gfx->overbright_table
);
break;
case PXL8_GFX_BACKEND_GPU:
break;
}
break;
}
}
}

View file

@ -8,6 +8,57 @@
typedef struct pxl8_gfx pxl8_gfx;
typedef enum pxl8_gfx_effect {
PXL8_GFX_EFFECT_GLOWS = 0,
} pxl8_gfx_effect;
#define PXL8_MAX_GLOWS 256
typedef enum pxl8_glow_shape {
PXL8_GLOW_CIRCLE = 0,
PXL8_GLOW_DIAMOND = 1,
PXL8_GLOW_SHAFT = 2,
} pxl8_glow_shape;
typedef struct pxl8_glow_source {
u8 color;
u16 depth;
u8 height;
u16 intensity;
u8 radius;
pxl8_glow_shape shape;
i16 x;
i16 y;
} pxl8_glow_source;
static inline pxl8_glow_source pxl8_glow_create(i32 x, i32 y, u8 radius, u16 intensity, u8 color) {
return (pxl8_glow_source){
.color = color,
.depth = 0xFFFF,
.height = 0,
.intensity = intensity,
.radius = radius,
.shape = PXL8_GLOW_CIRCLE,
.x = (i16)x,
.y = (i16)y,
};
}
static inline pxl8_glow_source pxl8_glow_with_depth(pxl8_glow_source g, u16 depth) {
g.depth = depth;
return g;
}
static inline pxl8_glow_source pxl8_glow_with_shape(pxl8_glow_source g, pxl8_glow_shape shape) {
g.shape = shape;
return g;
}
static inline pxl8_glow_source pxl8_glow_with_height(pxl8_glow_source g, u8 height) {
g.height = height;
return g;
}
#ifdef __cplusplus
extern "C" {
#endif
@ -43,6 +94,10 @@ pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path);
bool pxl8_gfx_push_target(pxl8_gfx* gfx);
void pxl8_gfx_pop_target(pxl8_gfx* gfx);
void pxl8_gfx_apply_effect(pxl8_gfx* gfx, pxl8_gfx_effect effect, const void* params, u32 count);
void pxl8_gfx_blend_tables_update(pxl8_gfx* gfx);
void pxl8_gfx_colormap_update(pxl8_gfx* gfx);
#ifdef __cplusplus
}
#endif

72
src/gfx/pxl8_gfx3d.h Normal file
View file

@ -0,0 +1,72 @@
#pragma once
#include "pxl8_3d_camera.h"
#include "pxl8_math.h"
#include "pxl8_mesh.h"
#include "pxl8_types.h"
typedef struct pxl8_gfx pxl8_gfx;
#define PXL8_MAX_LIGHTS 16
typedef struct pxl8_light {
pxl8_vec3 position;
u8 r, g, b;
u8 intensity;
f32 radius;
f32 radius_sq;
f32 inv_radius_sq;
} pxl8_light;
static inline pxl8_light pxl8_light_create(pxl8_vec3 pos, u8 r, u8 g, u8 b, u8 intensity, f32 radius) {
f32 radius_sq = radius * radius;
return (pxl8_light){
.position = pos,
.r = r, .g = g, .b = b,
.intensity = intensity,
.radius = radius,
.radius_sq = radius_sq,
.inv_radius_sq = radius_sq > 0.0f ? 1.0f / radius_sq : 0.0f,
};
}
typedef struct pxl8_3d_uniforms {
u8 ambient;
pxl8_vec3 celestial_dir;
f32 celestial_intensity;
u8 fog_color;
f32 fog_density;
pxl8_light lights[PXL8_MAX_LIGHTS];
u32 num_lights;
f32 time;
} pxl8_3d_uniforms;
typedef struct pxl8_3d_frame {
pxl8_3d_uniforms uniforms;
pxl8_vec3 camera_dir;
pxl8_vec3 camera_pos;
f32 far_clip;
f32 near_clip;
pxl8_mat4 projection;
pxl8_mat4 view;
} pxl8_3d_frame;
#ifdef __cplusplus
extern "C" {
#endif
void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms);
void pxl8_3d_clear(pxl8_gfx* gfx, u8 color);
void pxl8_3d_clear_depth(pxl8_gfx* gfx);
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_material* material);
void pxl8_3d_draw_mesh_wireframe(pxl8_gfx* gfx, const pxl8_mesh* mesh, pxl8_mat4 model, u8 color);
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);
pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms);
#ifdef __cplusplus
}
#endif

View file

@ -62,7 +62,7 @@ static inline u32 pxl8_mesh_triangle_count(const pxl8_mesh* mesh) {
return mesh->index_count / 3;
}
static inline pxl8_material pxl8_material_new(u32 texture_id) {
static inline pxl8_material pxl8_material_create(u32 texture_id) {
return (pxl8_material){
.texture_id = texture_id,
.alpha = 255,

View file

@ -449,7 +449,7 @@ void pxl8_palette_tick(pxl8_palette* pal, u16 delta_ticks) {
}
}
pxl8_cycle_range pxl8_cycle_range_new(u8 start, u8 len, u16 period) {
pxl8_cycle_range pxl8_cycle_range_create(u8 start, u8 len, u16 period) {
pxl8_cycle_range range = {
.easing = PXL8_EASE_LINEAR,
.interpolate = true,

View file

@ -62,7 +62,7 @@ void pxl8_palette_set_cycle_colors(pxl8_palette* pal, u8 slot, const u32* colors
void pxl8_palette_set_cycle_phase(pxl8_palette* pal, u8 slot, f32 phase);
void pxl8_palette_tick(pxl8_palette* pal, u16 delta_ticks);
pxl8_cycle_range pxl8_cycle_range_new(u8 start, u8 len, u16 period);
pxl8_cycle_range pxl8_cycle_range_create(u8 start, u8 len, u16 period);
pxl8_cycle_range pxl8_cycle_range_disabled(void);
#ifdef __cplusplus

View file

@ -11,8 +11,8 @@ typedef struct pxl8_hal {
void (*present)(void* platform_data);
void (*set_cursor)(void* platform_data, u32 cursor);
void (*set_relative_mouse_mode)(void* platform_data, bool enabled);
void (*upload_texture)(void* platform_data, const u8* pixels, u32 w, u32 h,
const u32* palette, u32 bpp);
void (*upload_texture)(void* platform_data, const void* pixels, u32 w, u32 h,
u32 bpp, const u32* palette);
void* (*audio_create)(i32 sample_rate, i32 channels);
void (*audio_destroy)(void* audio_handle);

View file

@ -81,7 +81,6 @@ static u64 sdl3_get_ticks(void) {
return SDL_GetTicksNS();
}
static void sdl3_present(void* platform_data) {
if (!platform_data) return;
@ -97,28 +96,34 @@ static void sdl3_present(void* platform_data) {
SDL_RenderPresent(ctx->renderer);
}
static void sdl3_upload_texture(void* platform_data, const u8* pixels, u32 w, u32 h,
const u32* palette, u32 bpp) {
static void sdl3_upload_texture(void* platform_data, const void* pixels, u32 w, u32 h,
u32 bpp, const u32* palette) {
if (!platform_data || !pixels) return;
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
size_t needed_size = w * h;
size_t pixel_count = w * h;
if (ctx->rgba_buffer_size < needed_size) {
u32* new_buffer = (u32*)SDL_realloc(ctx->rgba_buffer, needed_size * 4);
if (bpp == 4) {
SDL_UpdateTexture(ctx->framebuffer, NULL, pixels, w * 4);
return;
}
if (ctx->rgba_buffer_size < pixel_count) {
u32* new_buffer = (u32*)SDL_realloc(ctx->rgba_buffer, pixel_count * 4);
if (!new_buffer) return;
ctx->rgba_buffer = new_buffer;
ctx->rgba_buffer_size = needed_size;
ctx->rgba_buffer_size = pixel_count;
}
if (bpp == 2) {
const u16* pixels16 = (const u16*)pixels;
for (u32 i = 0; i < w * h; i++) {
for (u32 i = 0; i < pixel_count; i++) {
ctx->rgba_buffer[i] = pxl8_rgb565_to_rgba32(pixels16[i]);
}
} else {
for (u32 i = 0; i < w * h; i++) {
ctx->rgba_buffer[i] = palette[pixels[i]];
const u8* pixels8 = (const u8*)pixels;
for (u32 i = 0; i < pixel_count; i++) {
ctx->rgba_buffer[i] = palette[pixels8[i]];
}
}

View file

@ -1,10 +1,12 @@
local anim = require("pxl8.anim")
local bytes = require("pxl8.bytes")
local core = require("pxl8.core")
local gfx2d = require("pxl8.gfx2d")
local gfx3d = require("pxl8.gfx3d")
local gui = require("pxl8.gui")
local input = require("pxl8.input")
local math3d = require("pxl8.math")
local net = require("pxl8.net")
local particles = require("pxl8.particles")
local sfx = require("pxl8.sfx")
local tilemap = require("pxl8.tilemap")
@ -34,6 +36,7 @@ pxl8.find_color = core.find_color
pxl8.palette_color = core.palette_color
pxl8.palette_index = core.palette_index
pxl8.ramp_index = core.ramp_index
pxl8.set_palette_rgb = core.set_palette_rgb
pxl8.clear = gfx2d.clear
pxl8.pixel = gfx2d.pixel
@ -71,87 +74,95 @@ pxl8.center_cursor = input.center_cursor
pxl8.set_cursor = input.set_cursor
pxl8.set_relative_mouse_mode = input.set_relative_mouse_mode
pxl8.Particles = particles.Particles
pxl8.create_particles = function(max_count) return particles.Particles.new(max_count) end
pxl8.Anim = anim.Anim
pxl8.create_anim = anim.Anim.new
pxl8.create_anim_from_ase = anim.Anim.from_ase
pxl8.Tilesheet = tilemap.Tilesheet
pxl8.Tilemap = tilemap.Tilemap
pxl8.create_tilesheet = function(tile_size) return tilemap.Tilesheet.new(tile_size) end
pxl8.create_tilemap = function(w, h, tile_size) return tilemap.Tilemap.new(w, h, tile_size) end
pxl8.TILE_FLIP_X = tilemap.TILE_FLIP_X
pxl8.TILE_FLIP_Y = tilemap.TILE_FLIP_Y
pxl8.TILE_SOLID = tilemap.TILE_SOLID
pxl8.TILE_TRIGGER = tilemap.TILE_TRIGGER
pxl8.bounds = math3d.bounds
pxl8.Camera3D = gfx3d.Camera3D
pxl8.create_camera_3d = function() return gfx3d.Camera3D.new() end
pxl8.create_camera_3d = gfx3d.Camera3D.new
pxl8.begin_frame_3d = gfx3d.begin_frame
pxl8.end_frame_3d = gfx3d.end_frame
pxl8.clear_3d = gfx3d.clear
pxl8.clear_depth = gfx3d.clear_depth
pxl8.draw_line_3d = gfx3d.draw_line
pxl8.Mesh = gfx3d.Mesh
pxl8.create_mesh = function(vertices, indices) return gfx3d.Mesh.new(vertices, indices) end
pxl8.draw_mesh = gfx3d.draw_mesh
pxl8.end_frame_3d = gfx3d.end_frame
pxl8.Mesh = gfx3d.Mesh
pxl8.create_mesh = gfx3d.Mesh.new
pxl8.Compressor = sfx.Compressor
pxl8.create_compressor = sfx.Compressor.new
pxl8.Delay = sfx.Delay
pxl8.create_delay = sfx.Delay.new
pxl8.Reverb = sfx.Reverb
pxl8.create_reverb = sfx.Reverb.new
pxl8.Gui = gui.Gui
pxl8.create_gui = gui.Gui.new
pxl8.gui_label = gui.label
pxl8.gui_window = gui.window
pxl8.mat4_identity = math3d.mat4_identity
pxl8.mat4_lookat = math3d.mat4_lookat
pxl8.mat4_multiply = math3d.mat4_multiply
pxl8.mat4_translate = math3d.mat4_translate
pxl8.mat4_ortho = math3d.mat4_ortho
pxl8.mat4_perspective = math3d.mat4_perspective
pxl8.mat4_rotate_x = math3d.mat4_rotate_x
pxl8.mat4_rotate_y = math3d.mat4_rotate_y
pxl8.mat4_rotate_z = math3d.mat4_rotate_z
pxl8.mat4_scale = math3d.mat4_scale
pxl8.mat4_ortho = math3d.mat4_ortho
pxl8.mat4_perspective = math3d.mat4_perspective
pxl8.mat4_lookat = math3d.mat4_lookat
pxl8.bounds = math3d.bounds
pxl8.mat4_translate = math3d.mat4_translate
pxl8.Gui = gui.Gui
pxl8.create_gui = function() return gui.Gui.new() end
pxl8.gui_label = gui.label
pxl8.gui_window = gui.window
pxl8.Net = net.Net
pxl8.create_net = net.Net.new
pxl8.NET_MODE_LOCAL = net.MODE_LOCAL
pxl8.NET_MODE_REMOTE = net.MODE_REMOTE
pxl8.pack_f32_be = bytes.pack_f32_be
pxl8.pack_f32_le = bytes.pack_f32_le
pxl8.pack_f64_be = bytes.pack_f64_be
pxl8.pack_f64_le = bytes.pack_f64_le
pxl8.pack_i8 = bytes.pack_i8
pxl8.pack_i16_be = bytes.pack_i16_be
pxl8.pack_i16_le = bytes.pack_i16_le
pxl8.pack_i32_be = bytes.pack_i32_be
pxl8.pack_i32_le = bytes.pack_i32_le
pxl8.pack_i64_be = bytes.pack_i64_be
pxl8.pack_i64_le = bytes.pack_i64_le
pxl8.pack_u8 = bytes.pack_u8
pxl8.pack_u16_be = bytes.pack_u16_be
pxl8.pack_u16_le = bytes.pack_u16_le
pxl8.pack_u32_be = bytes.pack_u32_be
pxl8.pack_u32_le = bytes.pack_u32_le
pxl8.pack_u64_be = bytes.pack_u64_be
pxl8.pack_u64_le = bytes.pack_u64_le
pxl8.Particles = particles.Particles
pxl8.create_particles = particles.Particles.new
pxl8.World = world.World
pxl8.create_world = function() return world.World.new() end
pxl8.procgen_tex = world.procgen_tex
pxl8.PROCGEN_ROOMS = world.PROCGEN_ROOMS
pxl8.PROCGEN_TERRAIN = world.PROCGEN_TERRAIN
pxl8.Transition = transition.Transition
pxl8.create_transition = function(type_name, duration) return transition.Transition.new(type_name, duration) end
pxl8.TRANSITION_TYPES = transition.TYPES
pxl8.Anim = anim.Anim
pxl8.create_anim = function(frame_ids, frame_durations) return anim.Anim.new(frame_ids, frame_durations) end
pxl8.create_anim_from_ase = function(filepath) return anim.Anim.from_ase(filepath) end
pxl8.procgen_tex = world.procgen_tex
pxl8.SfxContext = sfx.SfxContext
pxl8.SfxNode = sfx.SfxNode
pxl8.Compressor = sfx.Compressor
pxl8.Delay = sfx.Delay
pxl8.Reverb = sfx.Reverb
pxl8.create_sfx_context = function() return sfx.SfxContext.new() end
pxl8.create_compressor = function(opts) return sfx.Compressor.new(opts) end
pxl8.create_delay = function(opts) return sfx.Delay.new(opts) end
pxl8.create_reverb = function(opts) return sfx.Reverb.new(opts) end
pxl8.create_sfx_context = sfx.SfxContext.new
pxl8.sfx_get_master_volume = sfx.get_master_volume
pxl8.sfx_set_master_volume = sfx.set_master_volume
pxl8.sfx_note_to_freq = sfx.note_to_freq
pxl8.sfx_set_master_volume = sfx.set_master_volume
pxl8.sfx_voice_params = sfx.voice_params
pxl8.SFX_FILTER_BANDPASS = sfx.FILTER_BANDPASS
pxl8.SFX_FILTER_HIGHPASS = sfx.FILTER_HIGHPASS
pxl8.SFX_FILTER_LOWPASS = sfx.FILTER_LOWPASS
pxl8.SFX_FILTER_NONE = sfx.FILTER_NONE
pxl8.SFX_LFO_AMPLITUDE = sfx.LFO_AMPLITUDE
pxl8.SFX_LFO_FILTER = sfx.LFO_FILTER
pxl8.SFX_LFO_PITCH = sfx.LFO_PITCH
pxl8.SFX_NODE_COMPRESSOR = sfx.NODE_COMPRESSOR
pxl8.SFX_NODE_DELAY = sfx.NODE_DELAY
pxl8.SFX_NODE_REVERB = sfx.NODE_REVERB
pxl8.SFX_WAVE_NOISE = sfx.WAVE_NOISE
pxl8.SFX_WAVE_PULSE = sfx.WAVE_PULSE
pxl8.SFX_WAVE_SAW = sfx.WAVE_SAW
@ -159,4 +170,39 @@ pxl8.SFX_WAVE_SINE = sfx.WAVE_SINE
pxl8.SFX_WAVE_SQUARE = sfx.WAVE_SQUARE
pxl8.SFX_WAVE_TRIANGLE = sfx.WAVE_TRIANGLE
pxl8.TILE_FLIP_X = tilemap.TILE_FLIP_X
pxl8.TILE_FLIP_Y = tilemap.TILE_FLIP_Y
pxl8.TILE_SOLID = tilemap.TILE_SOLID
pxl8.TILE_TRIGGER = tilemap.TILE_TRIGGER
pxl8.Tilemap = tilemap.Tilemap
pxl8.create_tilemap = tilemap.Tilemap.new
pxl8.Tilesheet = tilemap.Tilesheet
pxl8.create_tilesheet = tilemap.Tilesheet.new
pxl8.Transition = transition.Transition
pxl8.create_transition = transition.Transition.new
pxl8.TRANSITION_TYPES = transition.TYPES
pxl8.unpack_f32_be = bytes.unpack_f32_be
pxl8.unpack_f32_le = bytes.unpack_f32_le
pxl8.unpack_f64_be = bytes.unpack_f64_be
pxl8.unpack_f64_le = bytes.unpack_f64_le
pxl8.unpack_i8 = bytes.unpack_i8
pxl8.unpack_i16_be = bytes.unpack_i16_be
pxl8.unpack_i16_le = bytes.unpack_i16_le
pxl8.unpack_i32_be = bytes.unpack_i32_be
pxl8.unpack_i32_le = bytes.unpack_i32_le
pxl8.unpack_i64_be = bytes.unpack_i64_be
pxl8.unpack_i64_le = bytes.unpack_i64_le
pxl8.unpack_u8 = bytes.unpack_u8
pxl8.unpack_u16_be = bytes.unpack_u16_be
pxl8.unpack_u16_le = bytes.unpack_u16_le
pxl8.unpack_u32_be = bytes.unpack_u32_be
pxl8.unpack_u32_le = bytes.unpack_u32_le
pxl8.unpack_u64_be = bytes.unpack_u64_be
pxl8.unpack_u64_le = bytes.unpack_u64_le
pxl8.World = world.World
pxl8.create_world = world.World.new
return pxl8

44
src/lua/pxl8/bytes.lua Normal file
View file

@ -0,0 +1,44 @@
local ffi = require("ffi")
local C = ffi.C
local bytes = {}
bytes.pack_f32_be = C.pxl8_pack_f32_be
bytes.pack_f32_le = C.pxl8_pack_f32_le
bytes.pack_f64_be = C.pxl8_pack_f64_be
bytes.pack_f64_le = C.pxl8_pack_f64_le
bytes.pack_i8 = C.pxl8_pack_i8
bytes.pack_i16_be = C.pxl8_pack_i16_be
bytes.pack_i16_le = C.pxl8_pack_i16_le
bytes.pack_i32_be = C.pxl8_pack_i32_be
bytes.pack_i32_le = C.pxl8_pack_i32_le
bytes.pack_i64_be = C.pxl8_pack_i64_be
bytes.pack_i64_le = C.pxl8_pack_i64_le
bytes.pack_u8 = C.pxl8_pack_u8
bytes.pack_u16_be = C.pxl8_pack_u16_be
bytes.pack_u16_le = C.pxl8_pack_u16_le
bytes.pack_u32_be = C.pxl8_pack_u32_be
bytes.pack_u32_le = C.pxl8_pack_u32_le
bytes.pack_u64_be = C.pxl8_pack_u64_be
bytes.pack_u64_le = C.pxl8_pack_u64_le
bytes.unpack_f32_be = C.pxl8_unpack_f32_be
bytes.unpack_f32_le = C.pxl8_unpack_f32_le
bytes.unpack_f64_be = C.pxl8_unpack_f64_be
bytes.unpack_f64_le = C.pxl8_unpack_f64_le
bytes.unpack_i8 = C.pxl8_unpack_i8
bytes.unpack_i16_be = C.pxl8_unpack_i16_be
bytes.unpack_i16_le = C.pxl8_unpack_i16_le
bytes.unpack_i32_be = C.pxl8_unpack_i32_be
bytes.unpack_i32_le = C.pxl8_unpack_i32_le
bytes.unpack_i64_be = C.pxl8_unpack_i64_be
bytes.unpack_i64_le = C.pxl8_unpack_i64_le
bytes.unpack_u8 = C.pxl8_unpack_u8
bytes.unpack_u16_be = C.pxl8_unpack_u16_be
bytes.unpack_u16_le = C.pxl8_unpack_u16_le
bytes.unpack_u32_be = C.pxl8_unpack_u32_be
bytes.unpack_u32_le = C.pxl8_unpack_u32_le
bytes.unpack_u64_be = C.pxl8_unpack_u64_be
bytes.unpack_u64_le = C.pxl8_unpack_u64_le
return bytes

View file

@ -31,6 +31,14 @@ function core.palette_index(color)
return C.pxl8_palette_index(pal, color)
end
function core.set_palette_rgb(index, r, g, b)
local pal = C.pxl8_gfx_get_palette(core.gfx)
if pal == nil then return end
C.pxl8_palette_set_rgb(pal, index, r, g, b)
C.pxl8_gfx_blend_tables_update(core.gfx)
C.pxl8_gfx_colormap_update(core.gfx)
end
function core.ramp_index(position)
local pal = C.pxl8_gfx_get_palette(core.gfx)
if pal == nil then return 0 end

32
src/lua/pxl8/effects.lua Normal file
View file

@ -0,0 +1,32 @@
local ffi = require("ffi")
local C = ffi.C
local core = require("pxl8.core")
local effects = {}
effects.GLOW_CIRCLE = 0
effects.GLOW_DIAMOND = 1
effects.GLOW_SHAFT = 2
function effects.glows(glows)
if not glows or #glows == 0 then return end
local count = #glows
local glow_array = ffi.new("pxl8_glow_source[?]", count)
for i, g in ipairs(glows) do
local idx = i - 1
glow_array[idx].x = g.x or 0
glow_array[idx].y = g.y or 0
glow_array[idx].radius = g.radius or 8
glow_array[idx].intensity = g.intensity or 255
glow_array[idx].color = g.color or 15
glow_array[idx].depth = g.depth or 0xFFFF
glow_array[idx].height = g.height or 0
glow_array[idx].shape = g.shape or 0
end
C.pxl8_gfx_apply_effect(core.gfx, C.PXL8_GFX_EFFECT_GLOWS, glow_array, count)
end
return effects

View file

@ -42,6 +42,14 @@ function Camera3D:get_up()
return {v.x, v.y, v.z}
end
function Camera3D:get_view()
return C.pxl8_3d_camera_get_view(self._ptr)
end
function Camera3D:get_projection()
return C.pxl8_3d_camera_get_projection(self._ptr)
end
function Camera3D:lookat(eye, target, up)
up = up or {0, 1, 0}
local eye_vec = ffi.new("pxl8_vec3", {x = eye[1], y = eye[2], z = eye[3]})
@ -114,7 +122,14 @@ gfx3d.Mesh = Mesh
function gfx3d.draw_mesh(mesh, opts)
opts = opts or {}
local model = C.pxl8_mat4_identity()
local model = ffi.new("pxl8_mat4")
model.m[0] = 1
model.m[5] = 1
model.m[10] = 1
model.m[15] = 1
if opts.x then model.m[12] = opts.x end
if opts.y then model.m[13] = opts.y end
if opts.z then model.m[14] = opts.z end
local material = ffi.new("pxl8_material", {
texture_id = opts.texture or 0,
alpha = opts.alpha or 255,
@ -131,12 +146,44 @@ end
function gfx3d.begin_frame(camera, uniforms)
uniforms = uniforms or {}
local u = ffi.new("pxl8_3d_uniforms", {
ambient = uniforms.ambient or 0,
fog_color = uniforms.fog_color or 0,
fog_density = uniforms.fog_density or 0.0,
time = uniforms.time or 0.0,
})
local u = ffi.new("pxl8_3d_uniforms")
u.ambient = uniforms.ambient or 0
u.fog_color = uniforms.fog_color or 0
u.fog_density = uniforms.fog_density or 0.0
u.time = uniforms.time or 0.0
if uniforms.celestial_dir then
u.celestial_dir.x = uniforms.celestial_dir[1] or 0
u.celestial_dir.y = uniforms.celestial_dir[2] or -1
u.celestial_dir.z = uniforms.celestial_dir[3] or 0
else
u.celestial_dir.x = 0
u.celestial_dir.y = -1
u.celestial_dir.z = 0
end
u.celestial_intensity = uniforms.celestial_intensity or 0.0
u.num_lights = 0
if uniforms.lights then
for i, light in ipairs(uniforms.lights) do
if i > 16 then break end
local idx = i - 1
u.lights[idx].position.x = light.x or 0
u.lights[idx].position.y = light.y or 0
u.lights[idx].position.z = light.z or 0
u.lights[idx].r = light.r or 255
u.lights[idx].g = light.g or 255
u.lights[idx].b = light.b or 255
u.lights[idx].intensity = light.intensity or 255
u.lights[idx].radius = light.radius or 100
local radius_sq = u.lights[idx].radius * u.lights[idx].radius
u.lights[idx].radius_sq = radius_sq
u.lights[idx].inv_radius_sq = radius_sq > 0 and (1.0 / radius_sq) or 0
u.num_lights = i
end
end
C.pxl8_3d_begin_frame(core.gfx, camera._ptr, u)
end

182
src/lua/pxl8/net.lua Normal file
View file

@ -0,0 +1,182 @@
local ffi = require("ffi")
local C = ffi.C
local net = {}
local Net = {}
Net.__index = Net
net.MODE_LOCAL = C.PXL8_NET_LOCAL
net.MODE_REMOTE = C.PXL8_NET_REMOTE
function Net.new(config)
config = config or {}
local cfg = ffi.new("pxl8_net_config")
cfg.address = config.address or "127.0.0.1"
cfg.mode = config.mode or C.PXL8_NET_REMOTE
cfg.port = config.port or 7777
local n = C.pxl8_net_create(cfg)
if n == nil then
return nil
end
return setmetatable({ _ptr = n }, Net)
end
function Net:connect()
return C.pxl8_net_connect(self._ptr) == 0
end
function Net:connected()
return C.pxl8_net_connected(self._ptr)
end
function Net:destroy()
if self._ptr then
C.pxl8_net_destroy(self._ptr)
self._ptr = nil
end
end
function Net:disconnect()
C.pxl8_net_disconnect(self._ptr)
end
function Net:entities()
local snap = C.pxl8_net_snapshot(self._ptr)
if snap == nil then
return {}
end
local ents = C.pxl8_net_entities(self._ptr)
if ents == nil then
return {}
end
local result = {}
for i = 0, snap.entity_count - 1 do
result[i + 1] = {
entity_id = tonumber(ents[i].entity_id),
userdata = ents[i].userdata
}
end
return result
end
function Net:entity_prev_userdata(entity_id)
return C.pxl8_net_entity_prev_userdata(self._ptr, entity_id)
end
function Net:entity_userdata(entity_id)
return C.pxl8_net_entity_userdata(self._ptr, entity_id)
end
function Net:input_at(tick)
local input = C.pxl8_net_input_at(self._ptr, tick)
if input == nil then return nil end
return {
buttons = input.buttons,
look_dx = input.look_dx,
look_dy = input.look_dy,
move_x = input.move_x,
move_y = input.move_y,
yaw = input.yaw,
tick = tonumber(input.tick),
timestamp = tonumber(input.timestamp)
}
end
function Net:input_oldest_tick()
return tonumber(C.pxl8_net_input_oldest_tick(self._ptr))
end
function Net:input_push(input)
local msg = ffi.new("pxl8_input_msg")
msg.buttons = input.buttons or 0
msg.look_dx = input.look_dx or 0
msg.look_dy = input.look_dy or 0
msg.move_x = input.move_x or 0
msg.move_y = input.move_y or 0
msg.yaw = input.yaw or 0
msg.tick = input.tick or 0
msg.timestamp = input.timestamp or 0
C.pxl8_net_input_push(self._ptr, msg)
end
function Net:lerp_alpha()
return C.pxl8_net_lerp_alpha(self._ptr)
end
function Net:needs_correction()
return C.pxl8_net_needs_correction(self._ptr)
end
function Net:player_id()
return tonumber(C.pxl8_net_player_id(self._ptr))
end
function Net:poll()
return C.pxl8_net_poll(self._ptr)
end
function Net:predicted_state()
return C.pxl8_net_predicted_state(self._ptr)
end
function Net:predicted_tick_set(tick)
C.pxl8_net_predicted_tick_set(self._ptr, tick)
end
function Net:send_command(cmd)
return C.pxl8_net_send_command(self._ptr, cmd) == 0
end
function Net:send_input(input)
local msg = ffi.new("pxl8_input_msg")
msg.buttons = input.buttons or 0
msg.look_dx = input.look_dx or 0
msg.look_dy = input.look_dy or 0
msg.move_x = input.move_x or 0
msg.move_y = input.move_y or 0
msg.yaw = input.yaw or 0
msg.tick = input.tick or 0
msg.timestamp = input.timestamp or 0
return C.pxl8_net_send_input(self._ptr, msg) == 0
end
function Net:snapshot()
local snap = C.pxl8_net_snapshot(self._ptr)
if snap == nil then
return nil
end
return {
entity_count = snap.entity_count,
event_count = snap.event_count,
player_id = tonumber(snap.player_id),
tick = tonumber(snap.tick),
time = snap.time
}
end
function Net:spawn(x, y, z, yaw, pitch)
local cmd = ffi.new("pxl8_command_msg")
cmd.cmd_type = C.PXL8_CMD_SPAWN_ENTITY
C.pxl8_pack_f32_be(cmd.payload, 0, x or 0)
C.pxl8_pack_f32_be(cmd.payload, 4, y or 0)
C.pxl8_pack_f32_be(cmd.payload, 8, z or 0)
C.pxl8_pack_f32_be(cmd.payload, 12, yaw or 0)
C.pxl8_pack_f32_be(cmd.payload, 16, pitch or 0)
cmd.payload_size = 20
cmd.tick = 0
return self:send_command(cmd)
end
function Net:tick()
return tonumber(C.pxl8_net_tick(self._ptr))
end
function Net:update(dt)
C.pxl8_net_update(self._ptr, dt)
end
net.Net = Net
return net

View file

@ -200,9 +200,7 @@ function sfx.get_master_volume()
return C.pxl8_sfx_mixer_get_master_volume(core.sfx)
end
function sfx.note_to_freq(note)
return C.pxl8_sfx_note_to_freq(note)
end
sfx.note_to_freq = C.pxl8_sfx_note_to_freq
function sfx.set_master_volume(volume)
C.pxl8_sfx_mixer_set_master_volume(core.sfx, volume)

Some files were not shown because too many files have changed in this diff Show more