add networking, 3d improvements, reorganize src structure
This commit is contained in:
parent
39b604b333
commit
415d424057
122 changed files with 5358 additions and 721 deletions
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
(local pxl8 (require :pxl8))
|
(local pxl8 (require :pxl8))
|
||||||
(local menu (require :mod.menu))
|
(local menu (require :mod.menu))
|
||||||
(local music (require :mod.music))
|
(local music (require :mod.music))
|
||||||
(local worldgen (require :mod.worldgen))
|
(local first_person3d (require :mod.first_person3d))
|
||||||
|
|
||||||
(var time 0)
|
(var time 0)
|
||||||
(var active-demo :logo)
|
(var active-demo :logo)
|
||||||
|
|
@ -34,13 +34,12 @@
|
||||||
(set particles (pxl8.create_particles 1000))
|
(set particles (pxl8.create_particles 1000))
|
||||||
(set particles2 (pxl8.create_particles 500))
|
(set particles2 (pxl8.create_particles 500))
|
||||||
(music.init)
|
(music.init)
|
||||||
(music.start)
|
(first_person3d.init)))
|
||||||
(worldgen.init)))
|
|
||||||
|
|
||||||
(global update (fn [dt]
|
(global update (fn [dt]
|
||||||
(when (pxl8.key_pressed "escape")
|
(when (pxl8.key_pressed "escape")
|
||||||
(menu.toggle)
|
(menu.toggle)
|
||||||
(when (= active-demo :worldgen)
|
(when (= active-demo :first_person3d)
|
||||||
(pxl8.set_relative_mouse_mode (not (menu.is-paused)))))
|
(pxl8.set_relative_mouse_mode (not (menu.is-paused)))))
|
||||||
|
|
||||||
(when (not (menu.is-paused))
|
(when (not (menu.is-paused))
|
||||||
|
|
@ -50,9 +49,9 @@
|
||||||
(transition:update dt)
|
(transition:update dt)
|
||||||
(when (transition:is_complete)
|
(when (transition:is_complete)
|
||||||
(when transition-pending
|
(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))
|
(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))
|
(pxl8.set_relative_mouse_mode true))
|
||||||
(set active-demo transition-pending)
|
(set active-demo transition-pending)
|
||||||
(set transition-pending nil)
|
(set transition-pending nil)
|
||||||
|
|
@ -69,12 +68,14 @@
|
||||||
(when (pxl8.key_pressed "5") (switch-demo :fire))
|
(when (pxl8.key_pressed "5") (switch-demo :fire))
|
||||||
(when (pxl8.key_pressed "6") (switch-demo :rain))
|
(when (pxl8.key_pressed "6") (switch-demo :rain))
|
||||||
(when (pxl8.key_pressed "7") (switch-demo :snow))
|
(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 "=")
|
(when (pxl8.key_pressed "=")
|
||||||
(set use-famicube-palette? (not use-famicube-palette?))
|
(set use-famicube-palette? (not use-famicube-palette?))
|
||||||
(local palette-path (if use-famicube-palette? "res/palettes/famicube.ase" "res/sprites/pxl8_logo.ase"))
|
(local palette-path (if use-famicube-palette? "res/palettes/famicube.ase" "res/sprites/pxl8_logo.ase"))
|
||||||
(pxl8.load_palette palette-path))
|
(pxl8.load_palette palette-path))
|
||||||
|
|
||||||
|
(music.update dt)
|
||||||
|
|
||||||
(case active-demo
|
(case active-demo
|
||||||
:logo (do
|
:logo (do
|
||||||
(set logo-x (+ logo-x (* logo-dx dt)))
|
(set logo-x (+ logo-x (* logo-dx dt)))
|
||||||
|
|
@ -91,9 +92,7 @@
|
||||||
(when (> logo-y 296)
|
(when (> logo-y 296)
|
||||||
(set logo-y 296)
|
(set logo-y 296)
|
||||||
(set logo-dy (- (math.abs logo-dy)))))
|
(set logo-dy (- (math.abs logo-dy)))))
|
||||||
:worldgen (worldgen.update dt))
|
:first_person3d (first_person3d.update dt))
|
||||||
|
|
||||||
(music.update dt)
|
|
||||||
|
|
||||||
(when particles
|
(when particles
|
||||||
(particles:update dt))
|
(particles:update dt))
|
||||||
|
|
@ -173,7 +172,7 @@
|
||||||
(set snow-init? true))
|
(set snow-init? true))
|
||||||
(particles:render)))
|
(particles:render)))
|
||||||
|
|
||||||
:worldgen (worldgen.frame)
|
:first_person3d (first_person3d.frame)
|
||||||
|
|
||||||
_ (pxl8.clear 0))
|
_ (pxl8.clear 0))
|
||||||
|
|
||||||
|
|
|
||||||
393
demo/mod/first_person3d.fnl
Normal file
393
demo/mod/first_person3d.fnl
Normal 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}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
(local pxl8 (require :pxl8))
|
(local pxl8 (require :pxl8))
|
||||||
|
(local music (require :mod.music))
|
||||||
|
|
||||||
(var paused false)
|
(var paused false)
|
||||||
(var gui nil)
|
(var gui nil)
|
||||||
|
|
@ -36,12 +37,18 @@
|
||||||
(when gui
|
(when gui
|
||||||
(gui:begin_frame)
|
(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")
|
(when (gui:button 1 215 145 210 32 "Resume")
|
||||||
(hide))
|
(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))
|
(pxl8.quit))
|
||||||
|
|
||||||
(if (gui:is_hovering)
|
(if (gui:is_hovering)
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@
|
||||||
(fn update [dt]
|
(fn update [dt]
|
||||||
(when playing
|
(when playing
|
||||||
(set time (+ time dt))
|
(set time (+ time dt))
|
||||||
(when (>= time step-duration)
|
(while (>= time step-duration)
|
||||||
(set time (- time step-duration))
|
(set time (- time step-duration))
|
||||||
|
|
||||||
(local melody-idx (+ 1 (% step (length melody))))
|
(local melody-idx (+ 1 (% step (length melody))))
|
||||||
|
|
@ -125,4 +125,5 @@
|
||||||
:start start
|
:start start
|
||||||
:stop stop
|
:stop stop
|
||||||
:update update
|
:update update
|
||||||
:is-playing (fn [] playing)}
|
:is-playing (fn [] playing)
|
||||||
|
:get-step (fn [] step)}
|
||||||
|
|
|
||||||
250
demo/mod/sky.fnl
Normal file
250
demo/mod/sky.fnl
Normal 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
75
demo/mod/vfx.fnl
Normal 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
|
||||||
|
|
@ -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
127
pxl8.sh
|
|
@ -56,6 +56,45 @@ build_luajit() {
|
||||||
fi
|
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() {
|
build_sdl() {
|
||||||
if [[ ! -f "lib/SDL/build/libSDL3.so" ]] && [[ ! -f "lib/SDL/build/libSDL3.a" ]] && [[ ! -f "lib/SDL/build/libSDL3.dylib" ]]; then
|
if [[ ! -f "lib/SDL/build/libSDL3.so" ]] && [[ ! -f "lib/SDL/build/libSDL3.a" ]] && [[ ! -f "lib/SDL/build/libSDL3.dylib" ]]; then
|
||||||
print_info "Building SDL3"
|
print_info "Building SDL3"
|
||||||
|
|
@ -113,6 +152,7 @@ print_usage() {
|
||||||
echo " --all Clean both build artifacts and dependencies"
|
echo " --all Clean both build artifacts and dependencies"
|
||||||
echo " --deps Clean only dependencies"
|
echo " --deps Clean only dependencies"
|
||||||
echo " --release Build/run/clean in release mode (default: debug)"
|
echo " --release Build/run/clean in release mode (default: debug)"
|
||||||
|
echo " --server Start game server before running (for networked games)"
|
||||||
}
|
}
|
||||||
|
|
||||||
setup_sdl3() {
|
setup_sdl3() {
|
||||||
|
|
@ -320,7 +360,7 @@ case "$COMMAND" in
|
||||||
print_info "Compiler cache: ccache enabled"
|
print_info "Compiler cache: ccache enabled"
|
||||||
fi
|
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"
|
COMPILE_FLAGS="$CFLAGS $INCLUDES"
|
||||||
DEP_COMPILE_FLAGS="$DEP_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"
|
LIB_SOURCE_FILES="lib/linenoise/linenoise.c lib/miniz/miniz.c"
|
||||||
|
|
||||||
PXL8_SOURCE_FILES="
|
PXL8_SOURCE_FILES="
|
||||||
client/src/core/pxl8.c
|
src/core/pxl8.c
|
||||||
client/src/core/pxl8_io.c
|
src/core/pxl8_bytes.c
|
||||||
client/src/core/pxl8_log.c
|
src/core/pxl8_io.c
|
||||||
client/src/core/pxl8_rng.c
|
src/core/pxl8_log.c
|
||||||
client/src/math/pxl8_math.c
|
src/core/pxl8_rng.c
|
||||||
client/src/gfx/pxl8_anim.c
|
src/math/pxl8_math.c
|
||||||
client/src/gfx/pxl8_atlas.c
|
src/gfx/pxl8_anim.c
|
||||||
client/src/gfx/pxl8_blit.c
|
src/gfx/pxl8_atlas.c
|
||||||
client/src/gfx/pxl8_3d_camera.c
|
src/gfx/pxl8_blend.c
|
||||||
client/src/gfx/pxl8_colormap.c
|
src/gfx/pxl8_blit.c
|
||||||
client/src/gfx/pxl8_cpu.c
|
src/gfx/pxl8_3d_camera.c
|
||||||
client/src/gfx/pxl8_dither.c
|
src/gfx/pxl8_colormap.c
|
||||||
client/src/gfx/pxl8_font.c
|
src/gfx/pxl8_cpu.c
|
||||||
client/src/gfx/pxl8_gfx.c
|
src/gfx/pxl8_dither.c
|
||||||
client/src/gfx/pxl8_mesh.c
|
src/gfx/pxl8_font.c
|
||||||
client/src/gfx/pxl8_palette.c
|
src/gfx/pxl8_gfx.c
|
||||||
client/src/gfx/pxl8_particles.c
|
src/gfx/pxl8_mesh.c
|
||||||
client/src/gfx/pxl8_tilemap.c
|
src/gfx/pxl8_palette.c
|
||||||
client/src/gfx/pxl8_tilesheet.c
|
src/gfx/pxl8_particles.c
|
||||||
client/src/gfx/pxl8_transition.c
|
src/gfx/pxl8_tilemap.c
|
||||||
client/src/sfx/pxl8_sfx.c
|
src/gfx/pxl8_tilesheet.c
|
||||||
client/src/script/pxl8_repl.c
|
src/gfx/pxl8_transition.c
|
||||||
client/src/script/pxl8_script.c
|
src/sfx/pxl8_sfx.c
|
||||||
client/src/hal/pxl8_sdl3.c
|
src/script/pxl8_repl.c
|
||||||
client/src/world/pxl8_bsp.c
|
src/script/pxl8_script.c
|
||||||
client/src/world/pxl8_gen.c
|
src/hal/pxl8_sdl3.c
|
||||||
client/src/world/pxl8_world.c
|
src/world/pxl8_bsp.c
|
||||||
client/src/asset/pxl8_ase.c
|
src/world/pxl8_gen.c
|
||||||
client/src/asset/pxl8_cart.c
|
src/world/pxl8_world.c
|
||||||
client/src/asset/pxl8_save.c
|
src/asset/pxl8_ase.c
|
||||||
client/src/game/pxl8_gui.c
|
src/asset/pxl8_cart.c
|
||||||
client/src/game/pxl8_replay.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"
|
LUAJIT_LIB="lib/luajit/src/libluajit.a"
|
||||||
|
|
@ -390,13 +434,13 @@ case "$COMMAND" in
|
||||||
|
|
||||||
NEEDS_REBUILD=false
|
NEEDS_REBUILD=false
|
||||||
if [[ "$src_file" -nt "$obj_file" ]] || \
|
if [[ "$src_file" -nt "$obj_file" ]] || \
|
||||||
[[ "client/src/core/pxl8_types.h" -nt "$obj_file" ]] || \
|
[[ "src/core/pxl8_types.h" -nt "$obj_file" ]] || \
|
||||||
[[ "client/src/core/pxl8_macros.h" -nt "$obj_file" ]]; then
|
[[ "src/core/pxl8_macros.h" -nt "$obj_file" ]]; then
|
||||||
NEEDS_REBUILD=true
|
NEEDS_REBUILD=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "$src_file" == "client/src/script/pxl8_script.c" ]]; then
|
if [[ "$src_file" == "src/script/pxl8_script.c" ]]; then
|
||||||
for lua_file in client/src/lua/*.lua client/src/lua/pxl8/*.lua lib/fennel/fennel.lua; do
|
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
|
if [[ -f "$lua_file" ]] && [[ "$lua_file" -nt "$obj_file" ]]; then
|
||||||
NEEDS_REBUILD=true
|
NEEDS_REBUILD=true
|
||||||
break
|
break
|
||||||
|
|
@ -430,16 +474,25 @@ case "$COMMAND" in
|
||||||
|
|
||||||
CART=""
|
CART=""
|
||||||
EXTRA_ARGS=""
|
EXTRA_ARGS=""
|
||||||
|
RUN_SERVER=false
|
||||||
for arg in "$@"; do
|
for arg in "$@"; do
|
||||||
if [[ "$arg" == "--release" ]]; then
|
if [[ "$arg" == "--release" ]]; then
|
||||||
continue
|
continue
|
||||||
elif [[ "$arg" == "--repl" ]]; then
|
elif [[ "$arg" == "--repl" ]]; then
|
||||||
EXTRA_ARGS="$EXTRA_ARGS --repl"
|
EXTRA_ARGS="$EXTRA_ARGS --repl"
|
||||||
|
elif [[ "$arg" == "--server" ]]; then
|
||||||
|
RUN_SERVER=true
|
||||||
elif [[ -z "$CART" ]]; then
|
elif [[ -z "$CART" ]]; then
|
||||||
CART="$arg"
|
CART="$arg"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
if [[ "$RUN_SERVER" == true ]]; then
|
||||||
|
build_server "$MODE"
|
||||||
|
start_server "$MODE"
|
||||||
|
trap stop_server EXIT
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ -z "$CART" ]]; then
|
if [[ -z "$CART" ]]; then
|
||||||
"$BINDIR/pxl8" $EXTRA_ARGS
|
"$BINDIR/pxl8" $EXTRA_ARGS
|
||||||
else
|
else
|
||||||
|
|
|
||||||
2
server/.cargo/config.toml
Normal file
2
server/.cargo/config.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
[unstable]
|
||||||
|
build-std = ["core", "alloc"]
|
||||||
1
server/.gitignore
vendored
Normal file
1
server/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
target/
|
||||||
1090
server/Cargo.lock
generated
Normal file
1090
server/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
34
server/Cargo.toml
Normal file
34
server/Cargo.toml
Normal 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
23
server/build.rs
Normal 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");
|
||||||
|
}
|
||||||
2
server/rust-toolchain.toml
Normal file
2
server/rust-toolchain.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
[toolchain]
|
||||||
|
channel = "nightly"
|
||||||
36
server/src/allocator.rs
Normal file
36
server/src/allocator.rs
Normal 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
45
server/src/components.rs
Normal 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
18
server/src/lib.rs
Normal 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
142
server/src/main.rs
Normal 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
133
server/src/simulation.rs
Normal 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
281
server/src/transport.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,55 +8,67 @@ static const char embed_fennel[] = {
|
||||||
, 0 };
|
, 0 };
|
||||||
|
|
||||||
static const char embed_pxl8[] = {
|
static const char embed_pxl8[] = {
|
||||||
#embed "client/src/lua/pxl8.lua"
|
#embed "src/lua/pxl8.lua"
|
||||||
, 0 };
|
, 0 };
|
||||||
|
|
||||||
static const char embed_pxl8_anim[] = {
|
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 };
|
, 0 };
|
||||||
|
|
||||||
static const char embed_pxl8_core[] = {
|
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 };
|
, 0 };
|
||||||
|
|
||||||
static const char embed_pxl8_gfx2d[] = {
|
static const char embed_pxl8_gfx2d[] = {
|
||||||
#embed "client/src/lua/pxl8/gfx2d.lua"
|
#embed "src/lua/pxl8/gfx2d.lua"
|
||||||
, 0 };
|
, 0 };
|
||||||
|
|
||||||
static const char embed_pxl8_gfx3d[] = {
|
static const char embed_pxl8_gfx3d[] = {
|
||||||
#embed "client/src/lua/pxl8/gfx3d.lua"
|
#embed "src/lua/pxl8/gfx3d.lua"
|
||||||
, 0 };
|
, 0 };
|
||||||
|
|
||||||
static const char embed_pxl8_gui[] = {
|
static const char embed_pxl8_gui[] = {
|
||||||
#embed "client/src/lua/pxl8/gui.lua"
|
#embed "src/lua/pxl8/gui.lua"
|
||||||
, 0 };
|
, 0 };
|
||||||
|
|
||||||
static const char embed_pxl8_input[] = {
|
static const char embed_pxl8_input[] = {
|
||||||
#embed "client/src/lua/pxl8/input.lua"
|
#embed "src/lua/pxl8/input.lua"
|
||||||
, 0 };
|
, 0 };
|
||||||
|
|
||||||
static const char embed_pxl8_math[] = {
|
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 };
|
, 0 };
|
||||||
|
|
||||||
static const char embed_pxl8_particles[] = {
|
static const char embed_pxl8_particles[] = {
|
||||||
#embed "client/src/lua/pxl8/particles.lua"
|
#embed "src/lua/pxl8/particles.lua"
|
||||||
, 0 };
|
, 0 };
|
||||||
|
|
||||||
static const char embed_pxl8_sfx[] = {
|
static const char embed_pxl8_sfx[] = {
|
||||||
#embed "client/src/lua/pxl8/sfx.lua"
|
#embed "src/lua/pxl8/sfx.lua"
|
||||||
, 0 };
|
, 0 };
|
||||||
|
|
||||||
static const char embed_pxl8_tilemap[] = {
|
static const char embed_pxl8_tilemap[] = {
|
||||||
#embed "client/src/lua/pxl8/tilemap.lua"
|
#embed "src/lua/pxl8/tilemap.lua"
|
||||||
, 0 };
|
, 0 };
|
||||||
|
|
||||||
static const char embed_pxl8_transition[] = {
|
static const char embed_pxl8_transition[] = {
|
||||||
#embed "client/src/lua/pxl8/transition.lua"
|
#embed "src/lua/pxl8/transition.lua"
|
||||||
, 0 };
|
, 0 };
|
||||||
|
|
||||||
static const char embed_pxl8_world[] = {
|
static const char embed_pxl8_world[] = {
|
||||||
#embed "client/src/lua/pxl8/world.lua"
|
#embed "src/lua/pxl8/world.lua"
|
||||||
, 0 };
|
, 0 };
|
||||||
|
|
||||||
#define PXL8_EMBED_ENTRY(var, name) {name, var, sizeof(var) - 1}
|
#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_fennel, "fennel"),
|
||||||
PXL8_EMBED_ENTRY(embed_pxl8, "pxl8"),
|
PXL8_EMBED_ENTRY(embed_pxl8, "pxl8"),
|
||||||
PXL8_EMBED_ENTRY(embed_pxl8_anim, "pxl8.anim"),
|
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_core, "pxl8.core"),
|
||||||
|
PXL8_EMBED_ENTRY(embed_pxl8_effects, "pxl8.effects"),
|
||||||
PXL8_EMBED_ENTRY(embed_pxl8_gfx2d, "pxl8.gfx2d"),
|
PXL8_EMBED_ENTRY(embed_pxl8_gfx2d, "pxl8.gfx2d"),
|
||||||
PXL8_EMBED_ENTRY(embed_pxl8_gfx3d, "pxl8.gfx3d"),
|
PXL8_EMBED_ENTRY(embed_pxl8_gfx3d, "pxl8.gfx3d"),
|
||||||
PXL8_EMBED_ENTRY(embed_pxl8_gui, "pxl8.gui"),
|
PXL8_EMBED_ENTRY(embed_pxl8_gui, "pxl8.gui"),
|
||||||
PXL8_EMBED_ENTRY(embed_pxl8_input, "pxl8.input"),
|
PXL8_EMBED_ENTRY(embed_pxl8_input, "pxl8.input"),
|
||||||
PXL8_EMBED_ENTRY(embed_pxl8_math, "pxl8.math"),
|
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_particles, "pxl8.particles"),
|
||||||
PXL8_EMBED_ENTRY(embed_pxl8_sfx, "pxl8.sfx"),
|
PXL8_EMBED_ENTRY(embed_pxl8_sfx, "pxl8.sfx"),
|
||||||
PXL8_EMBED_ENTRY(embed_pxl8_tilemap, "pxl8.tilemap"),
|
PXL8_EMBED_ENTRY(embed_pxl8_tilemap, "pxl8.tilemap"),
|
||||||
234
src/core/pxl8_bytes.c
Normal file
234
src/core/pxl8_bytes.c
Normal 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
251
src/core/pxl8_bytes.h
Normal 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
38
src/core/pxl8_io.h
Normal 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
|
||||||
|
|
@ -29,8 +29,9 @@ typedef __uint128_t u128;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef enum pxl8_pixel_mode {
|
typedef enum pxl8_pixel_mode {
|
||||||
PXL8_PIXEL_INDEXED,
|
PXL8_PIXEL_INDEXED = 1,
|
||||||
PXL8_PIXEL_HICOLOR
|
PXL8_PIXEL_HICOLOR = 2,
|
||||||
|
PXL8_PIXEL_RGBA = 4,
|
||||||
} pxl8_pixel_mode;
|
} pxl8_pixel_mode;
|
||||||
|
|
||||||
typedef enum pxl8_cursor {
|
typedef enum pxl8_cursor {
|
||||||
194
src/gfx/pxl8_blend.c
Normal file
194
src/gfx/pxl8_blend.c
Normal 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
39
src/gfx/pxl8_blend.h
Normal 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
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
#include "pxl8_types.h"
|
#include "pxl8_types.h"
|
||||||
|
|
||||||
static inline i32 pxl8_bytes_per_pixel(pxl8_pixel_mode mode) {
|
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) {
|
static inline u16 pxl8_rgb565_pack(u8 r, u8 g, u8 b) {
|
||||||
|
|
@ -1,26 +1,63 @@
|
||||||
#include "pxl8_colormap.h"
|
#include "pxl8_colormap.h"
|
||||||
#include <string.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) {
|
static u8 find_closest_color(const u32* palette, u8 target_r, u8 target_g, u8 target_b) {
|
||||||
u8 best_idx = 1;
|
u8 best_idx = 1;
|
||||||
u32 best_dist = 0xFFFFFFFF;
|
u32 best_dist = 0xFFFFFFFF;
|
||||||
|
|
||||||
u8 dynamic_end = PXL8_DYNAMIC_RANGE_START + PXL8_DYNAMIC_RANGE_COUNT;
|
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++) {
|
for (u32 i = 1; i < PXL8_FULLBRIGHT_START; i++) {
|
||||||
if (i >= PXL8_DYNAMIC_RANGE_START && i < dynamic_end) {
|
if (i >= PXL8_DYNAMIC_RANGE_START && i < dynamic_end) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 c = palette[i];
|
u32 c = palette[i];
|
||||||
u8 pr = (c >> 24) & 0xFF;
|
u8 pr = c & 0xFF;
|
||||||
u8 pg = (c >> 16) & 0xFF;
|
u8 pg = (c >> 8) & 0xFF;
|
||||||
u8 pb = (c >> 8) & 0xFF;
|
u8 pb = (c >> 16) & 0xFF;
|
||||||
|
|
||||||
i32 dr = (i32)target_r - (i32)pr;
|
i32 ph, ps, pl;
|
||||||
i32 dg = (i32)target_g - (i32)pg;
|
rgb_to_hsl(pr, pg, pb, &ph, &ps, &pl);
|
||||||
i32 db = (i32)target_b - (i32)pb;
|
|
||||||
u32 dist = (u32)(dr * dr + dg * dg + db * db);
|
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) {
|
if (dist < best_dist) {
|
||||||
best_dist = 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;
|
result_idx = (u8)pal_idx;
|
||||||
} else {
|
} else {
|
||||||
u32 c = palette[pal_idx];
|
u32 c = palette[pal_idx];
|
||||||
u8 r = (c >> 24) & 0xFF;
|
u8 r = c & 0xFF;
|
||||||
u8 g = (c >> 16) & 0xFF;
|
u8 g = (c >> 8) & 0xFF;
|
||||||
u8 b = (c >> 8) & 0xFF;
|
u8 b = (c >> 16) & 0xFF;
|
||||||
|
|
||||||
u8 target_r = (u8)(dark_r + (r - dark_r) * brightness);
|
u8 target_r = (u8)(dark_r + (r - dark_r) * brightness);
|
||||||
u8 target_g = (u8)(dark_g + (g - dark_g) * brightness);
|
u8 target_g = (u8)(dark_g + (g - dark_g) * brightness);
|
||||||
|
|
@ -9,10 +9,109 @@ struct pxl8_cpu_render_target {
|
||||||
u8* framebuffer;
|
u8* framebuffer;
|
||||||
u32 height;
|
u32 height;
|
||||||
u32 width;
|
u32 width;
|
||||||
f32* zbuffer;
|
u16* zbuffer;
|
||||||
u32* light_accum;
|
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
|
#define PXL8_MAX_TARGET_STACK 8
|
||||||
|
|
||||||
struct pxl8_cpu_backend {
|
struct pxl8_cpu_backend {
|
||||||
|
|
@ -23,6 +122,8 @@ struct pxl8_cpu_backend {
|
||||||
const u32* palette;
|
const u32* palette;
|
||||||
pxl8_3d_frame frame;
|
pxl8_3d_frame frame;
|
||||||
pxl8_mat4 mvp;
|
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) {
|
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[0] = base_target;
|
||||||
cpu->target_stack_depth = 1;
|
cpu->target_stack_depth = 1;
|
||||||
cpu->current_target = base_target;
|
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;
|
return cpu;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -98,6 +208,7 @@ void pxl8_cpu_destroy(pxl8_cpu_backend* cpu) {
|
||||||
for (u32 i = 0; i < cpu->target_stack_depth; i++) {
|
for (u32 i = 0; i < cpu->target_stack_depth; i++) {
|
||||||
pxl8_cpu_destroy_render_target(cpu->target_stack[i]);
|
pxl8_cpu_destroy_render_target(cpu->target_stack[i]);
|
||||||
}
|
}
|
||||||
|
free(cpu->output);
|
||||||
free(cpu);
|
free(cpu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,7 +232,10 @@ void pxl8_cpu_clear_depth(pxl8_cpu_backend* cpu) {
|
||||||
if (!cpu || !cpu->current_target) return;
|
if (!cpu || !cpu->current_target) return;
|
||||||
pxl8_cpu_render_target* render_target = cpu->current_target;
|
pxl8_cpu_render_target* render_target = cpu->current_target;
|
||||||
if (render_target->zbuffer) {
|
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) {
|
if (render_target->light_accum) {
|
||||||
memset(render_target->light_accum, 0, render_target->width * render_target->height * sizeof(u32));
|
memset(render_target->light_accum, 0, render_target->width * render_target->height * sizeof(u32));
|
||||||
|
|
@ -275,8 +389,44 @@ typedef struct {
|
||||||
f32 u, v;
|
f32 u, v;
|
||||||
u8 color;
|
u8 color;
|
||||||
u8 light;
|
u8 light;
|
||||||
|
u32 light_color;
|
||||||
} vertex_output;
|
} 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) {
|
static vertex_output lerp_vertex(const vertex_output* a, const vertex_output* b, f32 t) {
|
||||||
vertex_output out;
|
vertex_output out;
|
||||||
out.clip_pos.x = a->clip_pos.x + (b->clip_pos.x - a->clip_pos.x) * t;
|
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.v = a->v + (b->v - a->v) * t;
|
||||||
out.color = t < 0.5f ? a->color : b->color;
|
out.color = t < 0.5f ? a->color : b->color;
|
||||||
out.light = (u8)(a->light + (b->light - a->light) * t);
|
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;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -348,6 +499,8 @@ typedef struct {
|
||||||
f32 w0_recip, w1_recip, w2_recip;
|
f32 w0_recip, w1_recip, w2_recip;
|
||||||
f32 u0_w, v0_w, u1_w, v1_w, u2_w, v2_w;
|
f32 u0_w, v0_w, u1_w, v1_w, u2_w, v2_w;
|
||||||
f32 l0_w, l1_w, l2_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;
|
i32 y_start, y_end, total_height;
|
||||||
f32 inv_total;
|
f32 inv_total;
|
||||||
u32 target_width, target_height;
|
u32 target_width, target_height;
|
||||||
|
|
@ -419,6 +572,14 @@ static bool setup_triangle(
|
||||||
setup->l1_w = sorted[1]->light * setup->w1_recip;
|
setup->l1_w = sorted[1]->light * setup->w1_recip;
|
||||||
setup->l2_w = sorted[2]->light * setup->w2_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->inv_total = 1.0f / (f32)setup->total_height;
|
||||||
setup->target_width = width;
|
setup->target_width = width;
|
||||||
setup->target_height = height;
|
setup->target_height = height;
|
||||||
|
|
@ -432,6 +593,8 @@ static void rasterize_triangle_opaque(
|
||||||
const pxl8_atlas* textures, u32 texture_id, bool dither
|
const pxl8_atlas* textures, u32 texture_id, bool dither
|
||||||
) {
|
) {
|
||||||
pxl8_cpu_render_target* render_target = cpu->current_target;
|
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;
|
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;
|
u32 atlas_width = textures ? pxl8_atlas_get_width(textures) : 1;
|
||||||
f32 tex_w = tex_entry ? (f32)tex_entry->w : 1.0f;
|
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;
|
u32 row_start = (u32)y * render_target->width;
|
||||||
u8* prow = render_target->framebuffer + row_start;
|
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;
|
const i32 SUBDIV = 32;
|
||||||
i32 x = x_start;
|
i32 x = x_start;
|
||||||
|
|
@ -553,7 +716,8 @@ static void rasterize_triangle_opaque(
|
||||||
f32 z_a = z;
|
f32 z_a = z;
|
||||||
|
|
||||||
for (i32 px = x; px <= span_end; px++) {
|
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 tx = (u_fixed >> 16) & tex_mask_w;
|
||||||
i32 ty = (v_fixed >> 16) & tex_mask_h;
|
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;
|
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;
|
u8 pal_idx = cpu->colormap ? pxl8_colormap_lookup(cpu->colormap, tex_idx, light) : tex_idx;
|
||||||
prow[px] = pal_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(
|
static void rasterize_triangle_passthrough(
|
||||||
pxl8_cpu_backend* cpu,
|
pxl8_cpu_backend* cpu,
|
||||||
const tri_setup* setup,
|
const tri_setup* setup
|
||||||
const vertex_output* vo0
|
|
||||||
) {
|
) {
|
||||||
pxl8_cpu_render_target* render_target = cpu->current_target;
|
pxl8_cpu_render_target* render_target = cpu->current_target;
|
||||||
u8 color = vo0->color;
|
|
||||||
|
|
||||||
for (i32 y = setup->y_start; y <= setup->y_end; y++) {
|
for (i32 y = setup->y_start; y <= setup->y_end; y++) {
|
||||||
bool second_half = y > setup->y1 || setup->y1 == setup->y0;
|
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 ax = (f32)setup->x0 + (f32)(setup->x2 - setup->x0) * alpha;
|
||||||
f32 az = setup->z0 + (setup->z2 - setup->z0) * 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) {
|
if (second_half) {
|
||||||
bx = (f32)setup->x1 + (f32)(setup->x2 - setup->x1) * beta;
|
bx = (f32)setup->x1 + (f32)(setup->x2 - setup->x1) * beta;
|
||||||
bz = setup->z1 + (setup->z2 - setup->z1) * 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 {
|
} else {
|
||||||
bx = (f32)setup->x0 + (f32)(setup->x1 - setup->x0) * beta;
|
bx = (f32)setup->x0 + (f32)(setup->x1 - setup->x0) * beta;
|
||||||
bz = setup->z0 + (setup->z1 - setup->z0) * 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) {
|
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 {
|
} 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);
|
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 inv_width = 1.0f / (f32)width_orig;
|
||||||
|
|
||||||
f32 dz = (z_end - z_start) * inv_width;
|
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 skip = (f32)(x_start - x_start_orig);
|
||||||
f32 z = z_start + dz * skip;
|
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;
|
u32 row_start = (u32)y * render_target->width;
|
||||||
u8* prow = render_target->framebuffer + row_start;
|
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++) {
|
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;
|
prow[px] = color;
|
||||||
zrow[px] = z;
|
zrow[px] = z16;
|
||||||
}
|
}
|
||||||
z += dz;
|
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
|
const pxl8_atlas* textures, u32 texture_id, u8 mat_alpha, bool dither
|
||||||
) {
|
) {
|
||||||
pxl8_cpu_render_target* render_target = cpu->current_target;
|
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;
|
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;
|
u32 atlas_width = textures ? pxl8_atlas_get_width(textures) : 1;
|
||||||
f32 tex_w = tex_entry ? (f32)tex_entry->w : 1.0f;
|
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;
|
u32 row_start = (u32)y * render_target->width;
|
||||||
u8* prow = render_target->framebuffer + row_start;
|
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;
|
const i32 SUBDIV = 32;
|
||||||
i32 x = x_start;
|
i32 x = x_start;
|
||||||
|
|
@ -787,7 +978,11 @@ static void rasterize_triangle_alpha(
|
||||||
|
|
||||||
if (mat_alpha >= 128) {
|
if (mat_alpha >= 128) {
|
||||||
prow[px] = src_idx;
|
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) {
|
if (alpha_blend) {
|
||||||
rasterize_triangle_alpha(cpu, &setup, textures, material->texture_id, material->alpha, material->dither);
|
rasterize_triangle_alpha(cpu, &setup, textures, material->texture_id, material->alpha, material->dither);
|
||||||
} else if (passthrough) {
|
} else if (passthrough) {
|
||||||
rasterize_triangle_passthrough(cpu, &setup, vo0);
|
rasterize_triangle_passthrough(cpu, &setup);
|
||||||
} else {
|
} else {
|
||||||
rasterize_triangle_opaque(cpu, &setup, textures, material->texture_id, material->dither);
|
rasterize_triangle_opaque(cpu, &setup, textures, material->texture_id, material->dither);
|
||||||
}
|
}
|
||||||
|
|
@ -833,13 +1028,13 @@ static void dispatch_triangle(
|
||||||
void pxl8_cpu_draw_mesh(
|
void pxl8_cpu_draw_mesh(
|
||||||
pxl8_cpu_backend* cpu,
|
pxl8_cpu_backend* cpu,
|
||||||
const pxl8_mesh* mesh,
|
const pxl8_mesh* mesh,
|
||||||
pxl8_mat4 model,
|
const pxl8_mat4* model,
|
||||||
pxl8_material material,
|
const pxl8_material* material,
|
||||||
const pxl8_atlas* textures
|
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);
|
pxl8_mat4 mvp = pxl8_mat4_mul(cpu->frame.projection, mv);
|
||||||
|
|
||||||
f32 near = cpu->frame.near_clip > 0.0f ? cpu->frame.near_clip : 0.1f;
|
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);
|
vo1.clip_pos = pxl8_mat4_mul_vec4(mvp, p1);
|
||||||
vo2.clip_pos = pxl8_mat4_mul_vec4(mvp, p2);
|
vo2.clip_pos = pxl8_mat4_mul_vec4(mvp, p2);
|
||||||
|
|
||||||
pxl8_vec4 w0 = pxl8_mat4_mul_vec4(model, p0);
|
pxl8_vec4 w0 = pxl8_mat4_mul_vec4(*model, p0);
|
||||||
pxl8_vec4 w1 = pxl8_mat4_mul_vec4(model, p1);
|
pxl8_vec4 w1 = pxl8_mat4_mul_vec4(*model, p1);
|
||||||
pxl8_vec4 w2 = pxl8_mat4_mul_vec4(model, p2);
|
pxl8_vec4 w2 = pxl8_mat4_mul_vec4(*model, p2);
|
||||||
vo0.world_pos = (pxl8_vec3){w0.x, w0.y, w0.z};
|
vo0.world_pos = (pxl8_vec3){w0.x, w0.y, w0.z};
|
||||||
vo1.world_pos = (pxl8_vec3){w1.x, w1.y, w1.z};
|
vo1.world_pos = (pxl8_vec3){w1.x, w1.y, w1.z};
|
||||||
vo2.world_pos = (pxl8_vec3){w2.x, w2.y, w2.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;
|
vo1.color = v1->color;
|
||||||
vo2.color = v2->color;
|
vo2.color = v2->color;
|
||||||
|
|
||||||
vo0.light = material.dynamic_lighting ? v0->light : 255;
|
if (material->dynamic_lighting) {
|
||||||
vo1.light = material.dynamic_lighting ? v1->light : 255;
|
pxl8_vec3 n0 = pxl8_vec3_normalize(pxl8_mat4_mul_vec3(*model, v0->normal));
|
||||||
vo2.light = material.dynamic_lighting ? v2->light : 255;
|
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];
|
vertex_output clipped[6];
|
||||||
i32 clipped_count = clip_triangle_near(&vo0, &vo1, &vo2, near, clipped);
|
i32 clipped_count = clip_triangle_near(&vo0, &vo1, &vo2, near, clipped);
|
||||||
|
|
||||||
for (i32 t = 0; t < clipped_count; t += 3) {
|
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) {
|
if (desc->with_depth) {
|
||||||
target->zbuffer = calloc(size, sizeof(f32));
|
target->zbuffer = calloc(size, sizeof(u16));
|
||||||
if (!target->zbuffer) {
|
if (!target->zbuffer) {
|
||||||
free(target->framebuffer);
|
free(target->framebuffer);
|
||||||
free(target);
|
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) {
|
u32 pxl8_cpu_render_target_get_width(const pxl8_cpu_render_target* target) {
|
||||||
return target ? target->width : 0;
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "pxl8_atlas.h"
|
#include "pxl8_atlas.h"
|
||||||
|
#include "pxl8_blend.h"
|
||||||
#include "pxl8_colormap.h"
|
#include "pxl8_colormap.h"
|
||||||
|
#include "pxl8_gfx.h"
|
||||||
|
#include "pxl8_gfx3d.h"
|
||||||
#include "pxl8_math.h"
|
#include "pxl8_math.h"
|
||||||
#include "pxl8_mesh.h"
|
#include "pxl8_mesh.h"
|
||||||
#include "pxl8_types.h"
|
#include "pxl8_types.h"
|
||||||
|
|
@ -20,19 +23,6 @@ typedef struct pxl8_cpu_render_target_desc {
|
||||||
bool with_lighting;
|
bool with_lighting;
|
||||||
} pxl8_cpu_render_target_desc;
|
} 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);
|
pxl8_cpu_backend* pxl8_cpu_create(u32 width, u32 height);
|
||||||
void pxl8_cpu_destroy(pxl8_cpu_backend* cpu);
|
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(
|
void pxl8_cpu_draw_mesh(
|
||||||
pxl8_cpu_backend* cpu,
|
pxl8_cpu_backend* cpu,
|
||||||
const pxl8_mesh* mesh,
|
const pxl8_mesh* mesh,
|
||||||
pxl8_mat4 model,
|
const pxl8_mat4* model,
|
||||||
pxl8_material material,
|
const pxl8_material* material,
|
||||||
const pxl8_atlas* textures
|
const pxl8_atlas* textures
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -70,9 +60,21 @@ void pxl8_cpu_draw_mesh_wireframe(
|
||||||
);
|
);
|
||||||
|
|
||||||
u8* pxl8_cpu_get_framebuffer(pxl8_cpu_backend* cpu);
|
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_height(const pxl8_cpu_backend* cpu);
|
||||||
u32 pxl8_cpu_get_width(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);
|
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);
|
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);
|
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_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_width(const pxl8_cpu_render_target* target);
|
||||||
|
u32* pxl8_cpu_render_target_get_light_accum(pxl8_cpu_render_target* target);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
#include "pxl8_ase.h"
|
#include "pxl8_ase.h"
|
||||||
#include "pxl8_atlas.h"
|
#include "pxl8_atlas.h"
|
||||||
#include "pxl8_backend.h"
|
#include "pxl8_backend.h"
|
||||||
|
#include "pxl8_blend.h"
|
||||||
#include "pxl8_blit.h"
|
#include "pxl8_blit.h"
|
||||||
#include "pxl8_color.h"
|
#include "pxl8_color.h"
|
||||||
#include "pxl8_colormap.h"
|
#include "pxl8_colormap.h"
|
||||||
|
|
@ -24,6 +25,7 @@ typedef struct pxl8_sprite_cache_entry {
|
||||||
} pxl8_sprite_cache_entry;
|
} pxl8_sprite_cache_entry;
|
||||||
|
|
||||||
struct pxl8_gfx {
|
struct pxl8_gfx {
|
||||||
|
pxl8_additive_table* additive_table;
|
||||||
pxl8_atlas* atlas;
|
pxl8_atlas* atlas;
|
||||||
pxl8_gfx_backend backend;
|
pxl8_gfx_backend backend;
|
||||||
pxl8_colormap* colormap;
|
pxl8_colormap* colormap;
|
||||||
|
|
@ -33,7 +35,9 @@ struct pxl8_gfx {
|
||||||
pxl8_frustum frustum;
|
pxl8_frustum frustum;
|
||||||
const pxl8_hal* hal;
|
const pxl8_hal* hal;
|
||||||
bool initialized;
|
bool initialized;
|
||||||
|
pxl8_overbright_table* overbright_table;
|
||||||
pxl8_palette* palette;
|
pxl8_palette* palette;
|
||||||
|
pxl8_palette_cube* palette_cube;
|
||||||
pxl8_pixel_mode pixel_mode;
|
pxl8_pixel_mode pixel_mode;
|
||||||
void* platform_data;
|
void* platform_data;
|
||||||
pxl8_sprite_cache_entry* sprite_cache;
|
pxl8_sprite_cache_entry* sprite_cache;
|
||||||
|
|
@ -183,11 +187,14 @@ pxl8_gfx* pxl8_gfx_create(
|
||||||
void pxl8_gfx_destroy(pxl8_gfx* gfx) {
|
void pxl8_gfx_destroy(pxl8_gfx* gfx) {
|
||||||
if (!gfx) return;
|
if (!gfx) return;
|
||||||
|
|
||||||
|
pxl8_additive_table_destroy(gfx->additive_table);
|
||||||
pxl8_atlas_destroy(gfx->atlas);
|
pxl8_atlas_destroy(gfx->atlas);
|
||||||
if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) {
|
if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) {
|
||||||
pxl8_cpu_destroy(gfx->backend.cpu);
|
pxl8_cpu_destroy(gfx->backend.cpu);
|
||||||
}
|
}
|
||||||
free(gfx->colormap);
|
free(gfx->colormap);
|
||||||
|
pxl8_overbright_table_destroy(gfx->overbright_table);
|
||||||
|
pxl8_palette_cube_destroy(gfx->palette_cube);
|
||||||
pxl8_palette_destroy(gfx->palette);
|
pxl8_palette_destroy(gfx->palette);
|
||||||
free(gfx->sprite_cache);
|
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) {
|
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) {
|
||||||
if (!gfx || !gfx->initialized || !gfx->hal) return;
|
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;
|
u32* colors = gfx->palette ? pxl8_palette_colors(gfx->palette) : NULL;
|
||||||
gfx->hal->upload_texture(
|
gfx->hal->upload_texture(
|
||||||
gfx->platform_data,
|
gfx->platform_data,
|
||||||
gfx->framebuffer,
|
gfx->framebuffer,
|
||||||
gfx->framebuffer_width,
|
gfx->framebuffer_width,
|
||||||
gfx->framebuffer_height,
|
gfx->framebuffer_height,
|
||||||
colors,
|
gfx->pixel_mode,
|
||||||
bpp
|
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;
|
if (!gfx || !text) return;
|
||||||
|
|
||||||
u8* framebuffer = NULL;
|
u8* framebuffer = NULL;
|
||||||
|
u32* light_accum = NULL;
|
||||||
i32 fb_width = 0;
|
i32 fb_width = 0;
|
||||||
i32 fb_height = 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);
|
pxl8_cpu_render_target* render_target = pxl8_cpu_get_target(gfx->backend.cpu);
|
||||||
if (!render_target) return;
|
if (!render_target) return;
|
||||||
framebuffer = pxl8_cpu_render_target_get_framebuffer(render_target);
|
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_width = (i32)pxl8_cpu_render_target_get_width(render_target);
|
||||||
fb_height = (i32)pxl8_cpu_render_target_get_height(render_target);
|
fb_height = (i32)pxl8_cpu_render_target_get_height(render_target);
|
||||||
break;
|
break;
|
||||||
|
|
@ -483,7 +507,9 @@ void pxl8_2d_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pixel_bit) {
|
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) {
|
void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms) {
|
||||||
if (!gfx || !camera) return;
|
if (!gfx || !camera) return;
|
||||||
|
|
||||||
pxl8_mat4 view = pxl8_3d_camera_get_view(camera);
|
pxl8_3d_frame frame = pxl8_3d_frame_from_camera(camera, uniforms);
|
||||||
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_mat4 vp = pxl8_mat4_mul(projection, view);
|
pxl8_mat4 vp = pxl8_mat4_mul(frame.projection, frame.view);
|
||||||
gfx->frustum = pxl8_frustum_from_matrix(vp);
|
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) {
|
switch (gfx->backend.type) {
|
||||||
case PXL8_GFX_BACKEND_CPU:
|
case PXL8_GFX_BACKEND_CPU:
|
||||||
pxl8_cpu_begin_frame(gfx->backend.cpu, &frame);
|
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) {
|
void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_material* material) {
|
||||||
if (!gfx || !mesh) return;
|
if (!gfx || !mesh || !model || !material) return;
|
||||||
switch (gfx->backend.type) {
|
switch (gfx->backend.type) {
|
||||||
case PXL8_GFX_BACKEND_CPU:
|
case PXL8_GFX_BACKEND_CPU:
|
||||||
pxl8_cpu_draw_mesh(gfx->backend.cpu, mesh, model, material, gfx->atlas);
|
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;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,57 @@
|
||||||
|
|
||||||
typedef struct pxl8_gfx pxl8_gfx;
|
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
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#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);
|
bool pxl8_gfx_push_target(pxl8_gfx* gfx);
|
||||||
void pxl8_gfx_pop_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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
72
src/gfx/pxl8_gfx3d.h
Normal file
72
src/gfx/pxl8_gfx3d.h
Normal 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
|
||||||
|
|
@ -62,7 +62,7 @@ static inline u32 pxl8_mesh_triangle_count(const pxl8_mesh* mesh) {
|
||||||
return mesh->index_count / 3;
|
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){
|
return (pxl8_material){
|
||||||
.texture_id = texture_id,
|
.texture_id = texture_id,
|
||||||
.alpha = 255,
|
.alpha = 255,
|
||||||
|
|
@ -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 = {
|
pxl8_cycle_range range = {
|
||||||
.easing = PXL8_EASE_LINEAR,
|
.easing = PXL8_EASE_LINEAR,
|
||||||
.interpolate = true,
|
.interpolate = true,
|
||||||
|
|
@ -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_set_cycle_phase(pxl8_palette* pal, u8 slot, f32 phase);
|
||||||
void pxl8_palette_tick(pxl8_palette* pal, u16 delta_ticks);
|
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);
|
pxl8_cycle_range pxl8_cycle_range_disabled(void);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
@ -11,8 +11,8 @@ typedef struct pxl8_hal {
|
||||||
void (*present)(void* platform_data);
|
void (*present)(void* platform_data);
|
||||||
void (*set_cursor)(void* platform_data, u32 cursor);
|
void (*set_cursor)(void* platform_data, u32 cursor);
|
||||||
void (*set_relative_mouse_mode)(void* platform_data, bool enabled);
|
void (*set_relative_mouse_mode)(void* platform_data, bool enabled);
|
||||||
void (*upload_texture)(void* platform_data, const u8* pixels, u32 w, u32 h,
|
void (*upload_texture)(void* platform_data, const void* pixels, u32 w, u32 h,
|
||||||
const u32* palette, u32 bpp);
|
u32 bpp, const u32* palette);
|
||||||
|
|
||||||
void* (*audio_create)(i32 sample_rate, i32 channels);
|
void* (*audio_create)(i32 sample_rate, i32 channels);
|
||||||
void (*audio_destroy)(void* audio_handle);
|
void (*audio_destroy)(void* audio_handle);
|
||||||
|
|
@ -81,7 +81,6 @@ static u64 sdl3_get_ticks(void) {
|
||||||
return SDL_GetTicksNS();
|
return SDL_GetTicksNS();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void sdl3_present(void* platform_data) {
|
static void sdl3_present(void* platform_data) {
|
||||||
if (!platform_data) return;
|
if (!platform_data) return;
|
||||||
|
|
||||||
|
|
@ -97,28 +96,34 @@ static void sdl3_present(void* platform_data) {
|
||||||
SDL_RenderPresent(ctx->renderer);
|
SDL_RenderPresent(ctx->renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sdl3_upload_texture(void* platform_data, const u8* pixels, u32 w, u32 h,
|
static void sdl3_upload_texture(void* platform_data, const void* pixels, u32 w, u32 h,
|
||||||
const u32* palette, u32 bpp) {
|
u32 bpp, const u32* palette) {
|
||||||
if (!platform_data || !pixels) return;
|
if (!platform_data || !pixels) return;
|
||||||
|
|
||||||
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
|
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) {
|
if (bpp == 4) {
|
||||||
u32* new_buffer = (u32*)SDL_realloc(ctx->rgba_buffer, needed_size * 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;
|
if (!new_buffer) return;
|
||||||
ctx->rgba_buffer = new_buffer;
|
ctx->rgba_buffer = new_buffer;
|
||||||
ctx->rgba_buffer_size = needed_size;
|
ctx->rgba_buffer_size = pixel_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bpp == 2) {
|
if (bpp == 2) {
|
||||||
const u16* pixels16 = (const u16*)pixels;
|
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]);
|
ctx->rgba_buffer[i] = pxl8_rgb565_to_rgba32(pixels16[i]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (u32 i = 0; i < w * h; i++) {
|
const u8* pixels8 = (const u8*)pixels;
|
||||||
ctx->rgba_buffer[i] = palette[pixels[i]];
|
for (u32 i = 0; i < pixel_count; i++) {
|
||||||
|
ctx->rgba_buffer[i] = palette[pixels8[i]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
local anim = require("pxl8.anim")
|
local anim = require("pxl8.anim")
|
||||||
|
local bytes = require("pxl8.bytes")
|
||||||
local core = require("pxl8.core")
|
local core = require("pxl8.core")
|
||||||
local gfx2d = require("pxl8.gfx2d")
|
local gfx2d = require("pxl8.gfx2d")
|
||||||
local gfx3d = require("pxl8.gfx3d")
|
local gfx3d = require("pxl8.gfx3d")
|
||||||
local gui = require("pxl8.gui")
|
local gui = require("pxl8.gui")
|
||||||
local input = require("pxl8.input")
|
local input = require("pxl8.input")
|
||||||
local math3d = require("pxl8.math")
|
local math3d = require("pxl8.math")
|
||||||
|
local net = require("pxl8.net")
|
||||||
local particles = require("pxl8.particles")
|
local particles = require("pxl8.particles")
|
||||||
local sfx = require("pxl8.sfx")
|
local sfx = require("pxl8.sfx")
|
||||||
local tilemap = require("pxl8.tilemap")
|
local tilemap = require("pxl8.tilemap")
|
||||||
|
|
@ -34,6 +36,7 @@ pxl8.find_color = core.find_color
|
||||||
pxl8.palette_color = core.palette_color
|
pxl8.palette_color = core.palette_color
|
||||||
pxl8.palette_index = core.palette_index
|
pxl8.palette_index = core.palette_index
|
||||||
pxl8.ramp_index = core.ramp_index
|
pxl8.ramp_index = core.ramp_index
|
||||||
|
pxl8.set_palette_rgb = core.set_palette_rgb
|
||||||
|
|
||||||
pxl8.clear = gfx2d.clear
|
pxl8.clear = gfx2d.clear
|
||||||
pxl8.pixel = gfx2d.pixel
|
pxl8.pixel = gfx2d.pixel
|
||||||
|
|
@ -71,87 +74,95 @@ pxl8.center_cursor = input.center_cursor
|
||||||
pxl8.set_cursor = input.set_cursor
|
pxl8.set_cursor = input.set_cursor
|
||||||
pxl8.set_relative_mouse_mode = input.set_relative_mouse_mode
|
pxl8.set_relative_mouse_mode = input.set_relative_mouse_mode
|
||||||
|
|
||||||
pxl8.Particles = particles.Particles
|
pxl8.Anim = anim.Anim
|
||||||
pxl8.create_particles = function(max_count) return particles.Particles.new(max_count) end
|
pxl8.create_anim = anim.Anim.new
|
||||||
|
pxl8.create_anim_from_ase = anim.Anim.from_ase
|
||||||
|
|
||||||
pxl8.Tilesheet = tilemap.Tilesheet
|
pxl8.bounds = math3d.bounds
|
||||||
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.Camera3D = gfx3d.Camera3D
|
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.begin_frame_3d = gfx3d.begin_frame
|
||||||
pxl8.end_frame_3d = gfx3d.end_frame
|
|
||||||
pxl8.clear_3d = gfx3d.clear
|
pxl8.clear_3d = gfx3d.clear
|
||||||
pxl8.clear_depth = gfx3d.clear_depth
|
pxl8.clear_depth = gfx3d.clear_depth
|
||||||
pxl8.draw_line_3d = gfx3d.draw_line
|
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.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_identity = math3d.mat4_identity
|
||||||
|
pxl8.mat4_lookat = math3d.mat4_lookat
|
||||||
pxl8.mat4_multiply = math3d.mat4_multiply
|
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_x = math3d.mat4_rotate_x
|
||||||
pxl8.mat4_rotate_y = math3d.mat4_rotate_y
|
pxl8.mat4_rotate_y = math3d.mat4_rotate_y
|
||||||
pxl8.mat4_rotate_z = math3d.mat4_rotate_z
|
pxl8.mat4_rotate_z = math3d.mat4_rotate_z
|
||||||
pxl8.mat4_scale = math3d.mat4_scale
|
pxl8.mat4_scale = math3d.mat4_scale
|
||||||
pxl8.mat4_ortho = math3d.mat4_ortho
|
pxl8.mat4_translate = math3d.mat4_translate
|
||||||
pxl8.mat4_perspective = math3d.mat4_perspective
|
|
||||||
pxl8.mat4_lookat = math3d.mat4_lookat
|
|
||||||
pxl8.bounds = math3d.bounds
|
|
||||||
|
|
||||||
pxl8.Gui = gui.Gui
|
pxl8.Net = net.Net
|
||||||
pxl8.create_gui = function() return gui.Gui.new() end
|
pxl8.create_net = net.Net.new
|
||||||
pxl8.gui_label = gui.label
|
pxl8.NET_MODE_LOCAL = net.MODE_LOCAL
|
||||||
pxl8.gui_window = gui.window
|
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_ROOMS = world.PROCGEN_ROOMS
|
||||||
pxl8.PROCGEN_TERRAIN = world.PROCGEN_TERRAIN
|
pxl8.PROCGEN_TERRAIN = world.PROCGEN_TERRAIN
|
||||||
|
pxl8.procgen_tex = world.procgen_tex
|
||||||
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.SfxContext = sfx.SfxContext
|
pxl8.SfxContext = sfx.SfxContext
|
||||||
pxl8.SfxNode = sfx.SfxNode
|
pxl8.SfxNode = sfx.SfxNode
|
||||||
pxl8.Compressor = sfx.Compressor
|
pxl8.create_sfx_context = sfx.SfxContext.new
|
||||||
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.sfx_get_master_volume = sfx.get_master_volume
|
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_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_voice_params = sfx.voice_params
|
||||||
|
|
||||||
pxl8.SFX_FILTER_BANDPASS = sfx.FILTER_BANDPASS
|
pxl8.SFX_FILTER_BANDPASS = sfx.FILTER_BANDPASS
|
||||||
pxl8.SFX_FILTER_HIGHPASS = sfx.FILTER_HIGHPASS
|
pxl8.SFX_FILTER_HIGHPASS = sfx.FILTER_HIGHPASS
|
||||||
pxl8.SFX_FILTER_LOWPASS = sfx.FILTER_LOWPASS
|
pxl8.SFX_FILTER_LOWPASS = sfx.FILTER_LOWPASS
|
||||||
pxl8.SFX_FILTER_NONE = sfx.FILTER_NONE
|
pxl8.SFX_FILTER_NONE = sfx.FILTER_NONE
|
||||||
|
|
||||||
pxl8.SFX_LFO_AMPLITUDE = sfx.LFO_AMPLITUDE
|
pxl8.SFX_LFO_AMPLITUDE = sfx.LFO_AMPLITUDE
|
||||||
pxl8.SFX_LFO_FILTER = sfx.LFO_FILTER
|
pxl8.SFX_LFO_FILTER = sfx.LFO_FILTER
|
||||||
pxl8.SFX_LFO_PITCH = sfx.LFO_PITCH
|
pxl8.SFX_LFO_PITCH = sfx.LFO_PITCH
|
||||||
|
|
||||||
pxl8.SFX_NODE_COMPRESSOR = sfx.NODE_COMPRESSOR
|
pxl8.SFX_NODE_COMPRESSOR = sfx.NODE_COMPRESSOR
|
||||||
pxl8.SFX_NODE_DELAY = sfx.NODE_DELAY
|
pxl8.SFX_NODE_DELAY = sfx.NODE_DELAY
|
||||||
pxl8.SFX_NODE_REVERB = sfx.NODE_REVERB
|
pxl8.SFX_NODE_REVERB = sfx.NODE_REVERB
|
||||||
|
|
||||||
pxl8.SFX_WAVE_NOISE = sfx.WAVE_NOISE
|
pxl8.SFX_WAVE_NOISE = sfx.WAVE_NOISE
|
||||||
pxl8.SFX_WAVE_PULSE = sfx.WAVE_PULSE
|
pxl8.SFX_WAVE_PULSE = sfx.WAVE_PULSE
|
||||||
pxl8.SFX_WAVE_SAW = sfx.WAVE_SAW
|
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_SQUARE = sfx.WAVE_SQUARE
|
||||||
pxl8.SFX_WAVE_TRIANGLE = sfx.WAVE_TRIANGLE
|
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
|
return pxl8
|
||||||
44
src/lua/pxl8/bytes.lua
Normal file
44
src/lua/pxl8/bytes.lua
Normal 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
|
||||||
|
|
@ -31,6 +31,14 @@ function core.palette_index(color)
|
||||||
return C.pxl8_palette_index(pal, color)
|
return C.pxl8_palette_index(pal, color)
|
||||||
end
|
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)
|
function core.ramp_index(position)
|
||||||
local pal = C.pxl8_gfx_get_palette(core.gfx)
|
local pal = C.pxl8_gfx_get_palette(core.gfx)
|
||||||
if pal == nil then return 0 end
|
if pal == nil then return 0 end
|
||||||
32
src/lua/pxl8/effects.lua
Normal file
32
src/lua/pxl8/effects.lua
Normal 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
|
||||||
|
|
@ -42,6 +42,14 @@ function Camera3D:get_up()
|
||||||
return {v.x, v.y, v.z}
|
return {v.x, v.y, v.z}
|
||||||
end
|
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)
|
function Camera3D:lookat(eye, target, up)
|
||||||
up = up or {0, 1, 0}
|
up = up or {0, 1, 0}
|
||||||
local eye_vec = ffi.new("pxl8_vec3", {x = eye[1], y = eye[2], z = eye[3]})
|
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)
|
function gfx3d.draw_mesh(mesh, opts)
|
||||||
opts = opts or {}
|
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", {
|
local material = ffi.new("pxl8_material", {
|
||||||
texture_id = opts.texture or 0,
|
texture_id = opts.texture or 0,
|
||||||
alpha = opts.alpha or 255,
|
alpha = opts.alpha or 255,
|
||||||
|
|
@ -131,12 +146,44 @@ end
|
||||||
|
|
||||||
function gfx3d.begin_frame(camera, uniforms)
|
function gfx3d.begin_frame(camera, uniforms)
|
||||||
uniforms = uniforms or {}
|
uniforms = uniforms or {}
|
||||||
local u = ffi.new("pxl8_3d_uniforms", {
|
local u = ffi.new("pxl8_3d_uniforms")
|
||||||
ambient = uniforms.ambient or 0,
|
|
||||||
fog_color = uniforms.fog_color or 0,
|
u.ambient = uniforms.ambient or 0
|
||||||
fog_density = uniforms.fog_density or 0.0,
|
u.fog_color = uniforms.fog_color or 0
|
||||||
time = uniforms.time or 0.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)
|
C.pxl8_3d_begin_frame(core.gfx, camera._ptr, u)
|
||||||
end
|
end
|
||||||
|
|
||||||
182
src/lua/pxl8/net.lua
Normal file
182
src/lua/pxl8/net.lua
Normal 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
|
||||||
|
|
@ -200,9 +200,7 @@ function sfx.get_master_volume()
|
||||||
return C.pxl8_sfx_mixer_get_master_volume(core.sfx)
|
return C.pxl8_sfx_mixer_get_master_volume(core.sfx)
|
||||||
end
|
end
|
||||||
|
|
||||||
function sfx.note_to_freq(note)
|
sfx.note_to_freq = C.pxl8_sfx_note_to_freq
|
||||||
return C.pxl8_sfx_note_to_freq(note)
|
|
||||||
end
|
|
||||||
|
|
||||||
function sfx.set_master_volume(volume)
|
function sfx.set_master_volume(volume)
|
||||||
C.pxl8_sfx_mixer_set_master_volume(core.sfx, 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
Loading…
Add table
Add a link
Reference in a new issue