Compare commits

..

2 commits

136 changed files with 13441 additions and 1914 deletions

View file

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

View file

@ -1,79 +0,0 @@
#include "pxl8_colormap.h"
#include <string.h>
static u8 find_closest_color(const u32* palette, u8 target_r, u8 target_g, u8 target_b) {
u8 best_idx = 1;
u32 best_dist = 0xFFFFFFFF;
u8 dynamic_end = PXL8_DYNAMIC_RANGE_START + PXL8_DYNAMIC_RANGE_COUNT;
for (u32 i = 1; i < PXL8_FULLBRIGHT_START; i++) {
if (i >= PXL8_DYNAMIC_RANGE_START && i < dynamic_end) {
continue;
}
u32 c = palette[i];
u8 pr = (c >> 24) & 0xFF;
u8 pg = (c >> 16) & 0xFF;
u8 pb = (c >> 8) & 0xFF;
i32 dr = (i32)target_r - (i32)pr;
i32 dg = (i32)target_g - (i32)pg;
i32 db = (i32)target_b - (i32)pb;
u32 dist = (u32)(dr * dr + dg * dg + db * db);
if (dist < best_dist) {
best_dist = dist;
best_idx = (u8)i;
if (dist == 0) break;
}
}
return best_idx;
}
void pxl8_colormap_generate(pxl8_colormap* cm, const u32* palette, const pxl8_level_tint* tint) {
if (!cm || !palette) return;
u8 dark_r, dark_g, dark_b;
if (tint && tint->tint_strength > 0.0f) {
f32 t = tint->tint_strength;
f32 inv = 1.0f - t;
dark_r = (u8)(tint->dark_r * inv + tint->tint_r * t);
dark_g = (u8)(tint->dark_g * inv + tint->tint_g * t);
dark_b = (u8)(tint->dark_b * inv + tint->tint_b * t);
} else if (tint) {
dark_r = tint->dark_r;
dark_g = tint->dark_g;
dark_b = tint->dark_b;
} else {
dark_r = dark_g = dark_b = 0;
}
for (u32 light = 0; light < PXL8_LIGHT_LEVELS; light++) {
f32 brightness = (f32)light / (f32)(PXL8_LIGHT_LEVELS - 1);
for (u32 pal_idx = 0; pal_idx < 256; pal_idx++) {
u8 result_idx;
if (pal_idx == PXL8_TRANSPARENT) {
result_idx = PXL8_TRANSPARENT;
} else if (pal_idx >= PXL8_FULLBRIGHT_START) {
result_idx = (u8)pal_idx;
} else {
u32 c = palette[pal_idx];
u8 r = (c >> 24) & 0xFF;
u8 g = (c >> 16) & 0xFF;
u8 b = (c >> 8) & 0xFF;
u8 target_r = (u8)(dark_r + (r - dark_r) * brightness);
u8 target_g = (u8)(dark_g + (g - dark_g) * brightness);
u8 target_b = (u8)(dark_b + (b - dark_b) * brightness);
result_idx = find_closest_color(palette, target_r, target_g, target_b);
}
cm->table[light * 256 + pal_idx] = result_idx;
}
}
}

View file

@ -1,43 +0,0 @@
#pragma once
#include "pxl8_dither.h"
#include "pxl8_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#define PXL8_LIGHT_LEVELS 64
#define PXL8_COLORMAP_SIZE (256 * PXL8_LIGHT_LEVELS)
#define PXL8_FULLBRIGHT_START 240
#define PXL8_TRANSPARENT 0
#define PXL8_DYNAMIC_RANGE_START 144
#define PXL8_DYNAMIC_RANGE_COUNT 16
typedef struct {
u8 table[PXL8_COLORMAP_SIZE];
} pxl8_colormap;
typedef struct {
u8 dark_r, dark_g, dark_b;
u8 tint_r, tint_g, tint_b;
f32 tint_strength;
} pxl8_level_tint;
void pxl8_colormap_generate(pxl8_colormap* cm, const u32* palette, const pxl8_level_tint* tint);
static inline u8 pxl8_colormap_lookup(const pxl8_colormap* cm, u8 pal_idx, u8 light) {
u32 light_idx = light >> 2;
return cm->table[light_idx * 256 + pal_idx];
}
static inline u8 pxl8_colormap_lookup_dithered(const pxl8_colormap* cm, u8 pal_idx, u8 light, u32 x, u32 y) {
u8 dithered = pxl8_dither_light(light, x, y);
u32 light_idx = dithered >> 2;
return cm->table[light_idx * 256 + pal_idx];
}
#ifdef __cplusplus
}
#endif

View file

@ -1,48 +0,0 @@
#pragma once
#include "pxl8_gfx2d.h"
#include "pxl8_gfx3d.h"
#include "pxl8_hal.h"
#include "pxl8_palette.h"
#include "pxl8_types.h"
typedef struct pxl8_gfx pxl8_gfx;
#ifdef __cplusplus
extern "C" {
#endif
pxl8_gfx* pxl8_gfx_create(const pxl8_hal* hal, void* platform_data, pxl8_pixel_mode mode, pxl8_resolution resolution);
void pxl8_gfx_destroy(pxl8_gfx* gfx);
void pxl8_gfx_present(pxl8_gfx* gfx);
void pxl8_gfx_update(pxl8_gfx* gfx, f32 dt);
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx);
u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color);
pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx);
u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx);
u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx);
i32 pxl8_gfx_get_height(const pxl8_gfx* gfx);
pxl8_palette* pxl8_gfx_get_palette(pxl8_gfx* gfx);
pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx);
i32 pxl8_gfx_get_width(const pxl8_gfx* gfx);
i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath);
void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom);
void pxl8_gfx_set_palette(pxl8_gfx* gfx, pxl8_palette* pal);
void pxl8_gfx_set_viewport(pxl8_gfx* gfx, pxl8_viewport vp);
pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height);
void pxl8_gfx_clear_textures(pxl8_gfx* gfx);
pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width, u32 height);
pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx);
pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path);
bool pxl8_gfx_push_target(pxl8_gfx* gfx);
void pxl8_gfx_pop_target(pxl8_gfx* gfx);
#ifdef __cplusplus
}
#endif

View file

@ -1,33 +0,0 @@
#pragma once
#include "pxl8_3d_camera.h"
#include "pxl8_math.h"
#include "pxl8_mesh.h"
#include "pxl8_types.h"
typedef struct pxl8_gfx pxl8_gfx;
typedef struct pxl8_3d_uniforms {
u8 ambient;
u8 fog_color;
f32 fog_density;
f32 time;
} pxl8_3d_uniforms;
#ifdef __cplusplus
extern "C" {
#endif
void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms);
void pxl8_3d_clear(pxl8_gfx* gfx, u8 color);
void pxl8_3d_clear_depth(pxl8_gfx* gfx);
void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color);
void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, pxl8_mat4 model, pxl8_material material);
void pxl8_3d_draw_mesh_wireframe(pxl8_gfx* gfx, const pxl8_mesh* mesh, pxl8_mat4 model, u8 color);
void pxl8_3d_end_frame(pxl8_gfx* gfx);
u8* pxl8_3d_get_framebuffer(pxl8_gfx* gfx);
const pxl8_frustum* pxl8_3d_get_frustum(pxl8_gfx* gfx);
#ifdef __cplusplus
}
#endif

View file

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

View file

@ -1,299 +0,0 @@
#pragma once
#include "pxl8_types.h"
#if defined(__x86_64__) || defined(_M_X64)
#define PXL8_SIMD_SSE2 1
#include <emmintrin.h>
#elif defined(__aarch64__) || defined(_M_ARM64)
#define PXL8_SIMD_NEON 1
#include <arm_neon.h>
#else
#define PXL8_SIMD_SCALAR 1
#endif
#ifdef __cplusplus
extern "C" {
#endif
#if defined(PXL8_SIMD_SSE2)
typedef struct { __m128 v; } pxl8_f32x4;
typedef struct { __m128i v; } pxl8_i32x4;
typedef struct { __m128i v; } pxl8_u16x8;
static inline pxl8_f32x4 pxl8_f32x4_splat(f32 x) {
return (pxl8_f32x4){ _mm_set1_ps(x) };
}
static inline pxl8_f32x4 pxl8_f32x4_new(f32 a, f32 b, f32 c, f32 d) {
return (pxl8_f32x4){ _mm_set_ps(d, c, b, a) };
}
static inline pxl8_f32x4 pxl8_f32x4_add(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_add_ps(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_sub(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_sub_ps(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_mul(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_mul_ps(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_div(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_div_ps(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_min(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_min_ps(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_max(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_max_ps(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_cmplt(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ _mm_cmplt_ps(a.v, b.v) };
}
static inline i32 pxl8_f32x4_movemask(pxl8_f32x4 a) {
return _mm_movemask_ps(a.v);
}
static inline pxl8_i32x4 pxl8_f32x4_to_i32x4(pxl8_f32x4 a) {
return (pxl8_i32x4){ _mm_cvttps_epi32(a.v) };
}
static inline void pxl8_f32x4_store(pxl8_f32x4 a, f32* out) {
_mm_storeu_ps(out, a.v);
}
static inline pxl8_i32x4 pxl8_i32x4_splat(i32 x) {
return (pxl8_i32x4){ _mm_set1_epi32(x) };
}
static inline pxl8_i32x4 pxl8_i32x4_slli(pxl8_i32x4 a, i32 n) {
return (pxl8_i32x4){ _mm_slli_epi32(a.v, n) };
}
static inline pxl8_i32x4 pxl8_i32x4_srai(pxl8_i32x4 a, i32 n) {
return (pxl8_i32x4){ _mm_srai_epi32(a.v, n) };
}
static inline pxl8_i32x4 pxl8_i32x4_and(pxl8_i32x4 a, pxl8_i32x4 b) {
return (pxl8_i32x4){ _mm_and_si128(a.v, b.v) };
}
static inline pxl8_i32x4 pxl8_i32x4_or(pxl8_i32x4 a, pxl8_i32x4 b) {
return (pxl8_i32x4){ _mm_or_si128(a.v, b.v) };
}
static inline void pxl8_i32x4_store(pxl8_i32x4 a, i32* out) {
_mm_storeu_si128((__m128i*)out, a.v);
}
static inline pxl8_u16x8 pxl8_u16x8_cmplt(pxl8_u16x8 a, pxl8_u16x8 b) {
return (pxl8_u16x8){ _mm_cmplt_epi16(a.v, b.v) };
}
static inline pxl8_u16x8 pxl8_u16x8_blend(pxl8_u16x8 a, pxl8_u16x8 b, pxl8_u16x8 mask) {
__m128i not_mask = _mm_andnot_si128(mask.v, a.v);
__m128i and_mask = _mm_and_si128(mask.v, b.v);
return (pxl8_u16x8){ _mm_or_si128(not_mask, and_mask) };
}
static inline i32 pxl8_u16x8_movemask(pxl8_u16x8 a) {
return _mm_movemask_epi8(a.v);
}
#elif defined(PXL8_SIMD_NEON)
typedef struct { float32x4_t v; } pxl8_f32x4;
typedef struct { int32x4_t v; } pxl8_i32x4;
typedef struct { uint16x8_t v; } pxl8_u16x8;
static inline pxl8_f32x4 pxl8_f32x4_splat(f32 x) {
return (pxl8_f32x4){ vdupq_n_f32(x) };
}
static inline pxl8_f32x4 pxl8_f32x4_new(f32 a, f32 b, f32 c, f32 d) {
f32 arr[4] = {a, b, c, d};
return (pxl8_f32x4){ vld1q_f32(arr) };
}
static inline pxl8_f32x4 pxl8_f32x4_add(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ vaddq_f32(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_sub(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ vsubq_f32(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_mul(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ vmulq_f32(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_div(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ vdivq_f32(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_min(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ vminq_f32(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_max(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){ vmaxq_f32(a.v, b.v) };
}
static inline pxl8_f32x4 pxl8_f32x4_cmplt(pxl8_f32x4 a, pxl8_f32x4 b) {
uint32x4_t cmp = vcltq_f32(a.v, b.v);
return (pxl8_f32x4){ vreinterpretq_f32_u32(cmp) };
}
static inline i32 pxl8_f32x4_movemask(pxl8_f32x4 a) {
uint32x4_t input = vreinterpretq_u32_f32(a.v);
static const i32 shifts[4] = {0, 1, 2, 3};
uint32x4_t shifted = vshrq_n_u32(input, 31);
return vgetq_lane_u32(shifted, 0) | (vgetq_lane_u32(shifted, 1) << 1) |
(vgetq_lane_u32(shifted, 2) << 2) | (vgetq_lane_u32(shifted, 3) << 3);
}
static inline pxl8_i32x4 pxl8_f32x4_to_i32x4(pxl8_f32x4 a) {
return (pxl8_i32x4){ vcvtq_s32_f32(a.v) };
}
static inline void pxl8_f32x4_store(pxl8_f32x4 a, f32* out) {
vst1q_f32(out, a.v);
}
static inline pxl8_i32x4 pxl8_i32x4_splat(i32 x) {
return (pxl8_i32x4){ vdupq_n_s32(x) };
}
static inline pxl8_i32x4 pxl8_i32x4_slli(pxl8_i32x4 a, i32 n) {
return (pxl8_i32x4){ vshlq_s32(a.v, vdupq_n_s32(n)) };
}
static inline pxl8_i32x4 pxl8_i32x4_srai(pxl8_i32x4 a, i32 n) {
return (pxl8_i32x4){ vshlq_s32(a.v, vdupq_n_s32(-n)) };
}
static inline pxl8_i32x4 pxl8_i32x4_and(pxl8_i32x4 a, pxl8_i32x4 b) {
return (pxl8_i32x4){ vandq_s32(a.v, b.v) };
}
static inline pxl8_i32x4 pxl8_i32x4_or(pxl8_i32x4 a, pxl8_i32x4 b) {
return (pxl8_i32x4){ vorrq_s32(a.v, b.v) };
}
static inline void pxl8_i32x4_store(pxl8_i32x4 a, i32* out) {
vst1q_s32(out, a.v);
}
#else
typedef struct { f32 v[4]; } pxl8_f32x4;
typedef struct { i32 v[4]; } pxl8_i32x4;
typedef struct { u16 v[8]; } pxl8_u16x8;
static inline pxl8_f32x4 pxl8_f32x4_splat(f32 x) {
return (pxl8_f32x4){{ x, x, x, x }};
}
static inline pxl8_f32x4 pxl8_f32x4_new(f32 a, f32 b, f32 c, f32 d) {
return (pxl8_f32x4){{ a, b, c, d }};
}
static inline pxl8_f32x4 pxl8_f32x4_add(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){{ a.v[0]+b.v[0], a.v[1]+b.v[1], a.v[2]+b.v[2], a.v[3]+b.v[3] }};
}
static inline pxl8_f32x4 pxl8_f32x4_sub(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){{ a.v[0]-b.v[0], a.v[1]-b.v[1], a.v[2]-b.v[2], a.v[3]-b.v[3] }};
}
static inline pxl8_f32x4 pxl8_f32x4_mul(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){{ a.v[0]*b.v[0], a.v[1]*b.v[1], a.v[2]*b.v[2], a.v[3]*b.v[3] }};
}
static inline pxl8_f32x4 pxl8_f32x4_div(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){{ a.v[0]/b.v[0], a.v[1]/b.v[1], a.v[2]/b.v[2], a.v[3]/b.v[3] }};
}
static inline pxl8_f32x4 pxl8_f32x4_min(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){{
a.v[0]<b.v[0]?a.v[0]:b.v[0], a.v[1]<b.v[1]?a.v[1]:b.v[1],
a.v[2]<b.v[2]?a.v[2]:b.v[2], a.v[3]<b.v[3]?a.v[3]:b.v[3]
}};
}
static inline pxl8_f32x4 pxl8_f32x4_max(pxl8_f32x4 a, pxl8_f32x4 b) {
return (pxl8_f32x4){{
a.v[0]>b.v[0]?a.v[0]:b.v[0], a.v[1]>b.v[1]?a.v[1]:b.v[1],
a.v[2]>b.v[2]?a.v[2]:b.v[2], a.v[3]>b.v[3]?a.v[3]:b.v[3]
}};
}
static inline pxl8_f32x4 pxl8_f32x4_cmplt(pxl8_f32x4 a, pxl8_f32x4 b) {
pxl8_f32x4 r;
u32* rv = (u32*)r.v;
rv[0] = a.v[0] < b.v[0] ? 0xFFFFFFFF : 0;
rv[1] = a.v[1] < b.v[1] ? 0xFFFFFFFF : 0;
rv[2] = a.v[2] < b.v[2] ? 0xFFFFFFFF : 0;
rv[3] = a.v[3] < b.v[3] ? 0xFFFFFFFF : 0;
return r;
}
static inline i32 pxl8_f32x4_movemask(pxl8_f32x4 a) {
u32* av = (u32*)a.v;
return ((av[0] >> 31) & 1) | ((av[1] >> 31) & 1) << 1 |
((av[2] >> 31) & 1) << 2 | ((av[3] >> 31) & 1) << 3;
}
static inline pxl8_i32x4 pxl8_f32x4_to_i32x4(pxl8_f32x4 a) {
return (pxl8_i32x4){{ (i32)a.v[0], (i32)a.v[1], (i32)a.v[2], (i32)a.v[3] }};
}
static inline void pxl8_f32x4_store(pxl8_f32x4 a, f32* out) {
out[0] = a.v[0]; out[1] = a.v[1]; out[2] = a.v[2]; out[3] = a.v[3];
}
static inline pxl8_i32x4 pxl8_i32x4_splat(i32 x) {
return (pxl8_i32x4){{ x, x, x, x }};
}
static inline pxl8_i32x4 pxl8_i32x4_slli(pxl8_i32x4 a, i32 n) {
return (pxl8_i32x4){{ a.v[0]<<n, a.v[1]<<n, a.v[2]<<n, a.v[3]<<n }};
}
static inline pxl8_i32x4 pxl8_i32x4_srai(pxl8_i32x4 a, i32 n) {
return (pxl8_i32x4){{ a.v[0]>>n, a.v[1]>>n, a.v[2]>>n, a.v[3]>>n }};
}
static inline pxl8_i32x4 pxl8_i32x4_and(pxl8_i32x4 a, pxl8_i32x4 b) {
return (pxl8_i32x4){{ a.v[0]&b.v[0], a.v[1]&b.v[1], a.v[2]&b.v[2], a.v[3]&b.v[3] }};
}
static inline pxl8_i32x4 pxl8_i32x4_or(pxl8_i32x4 a, pxl8_i32x4 b) {
return (pxl8_i32x4){{ a.v[0]|b.v[0], a.v[1]|b.v[1], a.v[2]|b.v[2], a.v[3]|b.v[3] }};
}
static inline void pxl8_i32x4_store(pxl8_i32x4 a, i32* out) {
out[0] = a.v[0]; out[1] = a.v[1]; out[2] = a.v[2]; out[3] = a.v[3];
}
#endif
static inline f32 pxl8_fast_inv_sqrt(f32 x) {
f32 half = 0.5f * x;
i32 i = *(i32*)&x;
i = 0x5f375a86 - (i >> 1);
f32 y = *(f32*)&i;
return y * (1.5f - half * y * y);
}
#ifdef __cplusplus
}
#endif

View file

@ -1,504 +0,0 @@
#include "pxl8_gen.h"
#include <stdlib.h>
#include <string.h>
#include "pxl8_log.h"
#include "pxl8_rng.h"
typedef struct room_grid {
u8* cells;
i32 width;
i32 height;
} room_grid;
static bool room_grid_init(room_grid* grid, i32 width, i32 height) {
grid->width = width;
grid->height = height;
grid->cells = calloc(width * height, sizeof(u8));
return grid->cells != NULL;
}
static u8 room_grid_get(const room_grid* grid, i32 x, i32 y) {
if (x < 0 || x >= grid->width || y < 0 || y >= grid->height) {
return 1;
}
return grid->cells[y * grid->width + x];
}
static void room_grid_set(room_grid* grid, i32 x, i32 y, u8 value) {
if (x < 0 || x >= grid->width || y < 0 || y >= grid->height) {
return;
}
grid->cells[y * grid->width + x] = value;
}
static inline void compute_face_aabb(pxl8_bsp_face* face, const pxl8_bsp_vertex* verts, u32 vert_idx) {
face->aabb_min = (pxl8_vec3){1e30f, 1e30f, 1e30f};
face->aabb_max = (pxl8_vec3){-1e30f, -1e30f, -1e30f};
for (u32 i = 0; i < 4; i++) {
pxl8_vec3 v = verts[vert_idx + i].position;
if (v.x < face->aabb_min.x) face->aabb_min.x = v.x;
if (v.x > face->aabb_max.x) face->aabb_max.x = v.x;
if (v.y < face->aabb_min.y) face->aabb_min.y = v.y;
if (v.y > face->aabb_max.y) face->aabb_max.y = v.y;
if (v.z < face->aabb_min.z) face->aabb_min.z = v.z;
if (v.z > face->aabb_max.z) face->aabb_max.z = v.z;
}
}
static void room_grid_fill(room_grid* grid, u8 value) {
for (i32 y = 0; y < grid->height; y++) {
for (i32 x = 0; x < grid->width; x++) {
room_grid_set(grid, x, y, value);
}
}
}
static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
i32 vertex_count = 0;
i32 face_count = 0;
i32 floor_ceiling_count = 0;
for (i32 y = 0; y < grid->height; y++) {
for (i32 x = 0; x < grid->width; x++) {
if (room_grid_get(grid, x, y) == 0) {
if (room_grid_get(grid, x - 1, y) == 1) face_count++;
if (room_grid_get(grid, x + 1, y) == 1) face_count++;
if (room_grid_get(grid, x, y - 1) == 1) face_count++;
if (room_grid_get(grid, x, y + 1) == 1) face_count++;
floor_ceiling_count++;
}
}
}
face_count += floor_ceiling_count * 2;
vertex_count = face_count * 4;
pxl8_debug("Cave generation: %dx%d grid -> %d faces, %d vertices",
grid->width, grid->height, face_count, vertex_count);
bsp->vertices = calloc(vertex_count, sizeof(pxl8_bsp_vertex));
bsp->faces = calloc(face_count, sizeof(pxl8_bsp_face));
bsp->planes = calloc(face_count, sizeof(pxl8_bsp_plane));
bsp->edges = calloc(vertex_count, sizeof(pxl8_bsp_edge));
bsp->surfedges = calloc(vertex_count, sizeof(i32));
if (!bsp->vertices || !bsp->faces || !bsp->planes || !bsp->edges || !bsp->surfedges) {
return PXL8_ERROR_OUT_OF_MEMORY;
}
bsp->texinfo = NULL;
bsp->num_texinfo = 0;
i32 vert_idx = 0;
i32 face_idx = 0;
i32 edge_idx = 0;
const f32 cell_size = 64.0f;
const f32 wall_height = 128.0f;
for (i32 y = 0; y < grid->height; y++) {
for (i32 x = 0; x < grid->width; x++) {
if (room_grid_get(grid, x, y) == 0) {
f32 fx = (f32)x * cell_size;
f32 fy = (f32)y * cell_size;
if (room_grid_get(grid, x - 1, y) == 1) {
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy};
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx, wall_height, fy};
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx, wall_height, fy + cell_size};
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx, 0, fy + cell_size};
bsp->planes[face_idx].normal = (pxl8_vec3){-1, 0, 0};
bsp->planes[face_idx].dist = -fx;
bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4;
bsp->faces[face_idx].first_edge = edge_idx;
bsp->faces[face_idx].texinfo_id = 0;
for (i32 i = 0; i < 4; i++) {
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
bsp->edges[edge_idx + i].vertex[1] = vert_idx + ((i + 1) % 4);
bsp->surfedges[edge_idx + i] = edge_idx + i;
}
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
vert_idx += 4;
edge_idx += 4;
face_idx++;
}
if (room_grid_get(grid, x + 1, y) == 1) {
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx + cell_size, 0, fy};
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx + cell_size, 0, fy + cell_size};
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy + cell_size};
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx + cell_size, wall_height, fy};
bsp->planes[face_idx].normal = (pxl8_vec3){1, 0, 0};
bsp->planes[face_idx].dist = fx + cell_size;
bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4;
bsp->faces[face_idx].first_edge = edge_idx;
bsp->faces[face_idx].texinfo_id = 0;
for (i32 i = 0; i < 4; i++) {
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
bsp->edges[edge_idx + i].vertex[1] = vert_idx + ((i + 1) % 4);
bsp->surfedges[edge_idx + i] = edge_idx + i;
}
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
vert_idx += 4;
edge_idx += 4;
face_idx++;
}
if (room_grid_get(grid, x, y - 1) == 1) {
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy};
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx + cell_size, 0, fy};
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy};
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx, wall_height, fy};
bsp->planes[face_idx].normal = (pxl8_vec3){0, 0, -1};
bsp->planes[face_idx].dist = -fy;
bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4;
bsp->faces[face_idx].first_edge = edge_idx;
bsp->faces[face_idx].texinfo_id = 0;
for (i32 i = 0; i < 4; i++) {
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
bsp->edges[edge_idx + i].vertex[1] = vert_idx + ((i + 1) % 4);
bsp->surfedges[edge_idx + i] = edge_idx + i;
}
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
vert_idx += 4;
edge_idx += 4;
face_idx++;
}
if (room_grid_get(grid, x, y + 1) == 1) {
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy + cell_size};
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx, wall_height, fy + cell_size};
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy + cell_size};
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx + cell_size, 0, fy + cell_size};
bsp->planes[face_idx].normal = (pxl8_vec3){0, 0, 1};
bsp->planes[face_idx].dist = fy + cell_size;
bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4;
bsp->faces[face_idx].first_edge = edge_idx;
bsp->faces[face_idx].texinfo_id = 0;
for (i32 i = 0; i < 4; i++) {
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
bsp->edges[edge_idx + i].vertex[1] = vert_idx + ((i + 1) % 4);
bsp->surfedges[edge_idx + i] = edge_idx + i;
}
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
vert_idx += 4;
edge_idx += 4;
face_idx++;
}
}
}
}
for (i32 y = 0; y < grid->height; y++) {
for (i32 x = 0; x < grid->width; x++) {
if (room_grid_get(grid, x, y) == 0) {
f32 fx = (f32)x * cell_size;
f32 fy = (f32)y * cell_size;
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy};
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx, 0, fy + cell_size};
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, 0, fy + cell_size};
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx + cell_size, 0, fy};
bsp->planes[face_idx].normal = (pxl8_vec3){0, 1, 0};
bsp->planes[face_idx].dist = 0;
bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4;
bsp->faces[face_idx].first_edge = edge_idx;
bsp->faces[face_idx].texinfo_id = 0;
for (i32 i = 0; i < 4; i++) {
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
bsp->edges[edge_idx + i].vertex[1] = vert_idx + ((i + 1) % 4);
bsp->surfedges[edge_idx + i] = edge_idx + i;
}
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
vert_idx += 4;
edge_idx += 4;
face_idx++;
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, wall_height, fy};
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx + cell_size, wall_height, fy};
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy + cell_size};
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx, wall_height, fy + cell_size};
bsp->planes[face_idx].normal = (pxl8_vec3){0, -1, 0};
bsp->planes[face_idx].dist = -wall_height;
bsp->faces[face_idx].plane_id = face_idx;
bsp->faces[face_idx].num_edges = 4;
bsp->faces[face_idx].first_edge = edge_idx;
bsp->faces[face_idx].texinfo_id = 0;
for (i32 i = 0; i < 4; i++) {
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
bsp->edges[edge_idx + i].vertex[1] = vert_idx + ((i + 1) % 4);
bsp->surfedges[edge_idx + i] = edge_idx + i;
}
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
vert_idx += 4;
edge_idx += 4;
face_idx++;
}
}
}
bsp->num_vertices = vertex_count;
bsp->num_faces = face_count;
bsp->num_planes = face_count;
bsp->num_edges = vertex_count;
bsp->num_surfedges = vertex_count;
bsp->leafs = calloc(1, sizeof(pxl8_bsp_leaf));
bsp->marksurfaces = calloc(face_count, sizeof(u16));
if (!bsp->leafs || !bsp->marksurfaces) {
return PXL8_ERROR_OUT_OF_MEMORY;
}
bsp->num_leafs = 1;
bsp->num_marksurfaces = face_count;
bsp->leafs[0].first_marksurface = 0;
bsp->leafs[0].num_marksurfaces = face_count;
bsp->leafs[0].contents = -2;
for (i32 i = 0; i < face_count; i++) {
bsp->marksurfaces[i] = i;
}
return PXL8_OK;
}
static bool bounds_intersects(const pxl8_bounds* a, const pxl8_bounds* b) {
return !(a->x + a->w <= b->x || b->x + b->w <= a->x ||
a->y + a->h <= b->y || b->y + b->h <= a->y);
}
static void carve_corridor_h(room_grid* grid, i32 x1, i32 x2, i32 y) {
i32 start = (x1 < x2) ? x1 : x2;
i32 end = (x1 > x2) ? x1 : x2;
for (i32 x = start; x <= end; x++) {
room_grid_set(grid, x, y, 0);
room_grid_set(grid, x, y - 1, 0);
room_grid_set(grid, x, y + 1, 0);
}
}
static void carve_corridor_v(room_grid* grid, i32 y1, i32 y2, i32 x) {
i32 start = (y1 < y2) ? y1 : y2;
i32 end = (y1 > y2) ? y1 : y2;
for (i32 y = start; y <= end; y++) {
room_grid_set(grid, x, y, 0);
room_grid_set(grid, x - 1, y, 0);
room_grid_set(grid, x + 1, y, 0);
}
}
static pxl8_result procgen_rooms(pxl8_bsp* bsp, const pxl8_procgen_params* params) {
pxl8_debug("procgen_rooms called: %dx%d, seed=%u, min=%d, max=%d, num=%d",
params->width, params->height, params->seed,
params->min_room_size, params->max_room_size, params->num_rooms);
pxl8_rng rng;
pxl8_rng_seed(&rng, params->seed);
room_grid grid;
if (!room_grid_init(&grid, params->width, params->height)) {
pxl8_error("Failed to allocate room grid");
return PXL8_ERROR_OUT_OF_MEMORY;
}
room_grid_fill(&grid, 1);
pxl8_bounds rooms[256];
i32 room_count = 0;
i32 max_attempts = params->num_rooms * 10;
for (i32 attempt = 0; attempt < max_attempts && room_count < params->num_rooms && room_count < 256; attempt++) {
i32 w = params->min_room_size + (pxl8_rng_next(&rng) % (params->max_room_size - params->min_room_size + 1));
i32 h = params->min_room_size + (pxl8_rng_next(&rng) % (params->max_room_size - params->min_room_size + 1));
i32 x = 1 + (pxl8_rng_next(&rng) % (params->width - w - 2));
i32 y = 1 + (pxl8_rng_next(&rng) % (params->height - h - 2));
pxl8_bounds new_room = {x, y, w, h};
bool overlaps = false;
for (i32 i = 0; i < room_count; i++) {
if (bounds_intersects(&new_room, &rooms[i])) {
overlaps = true;
break;
}
}
if (!overlaps) {
for (i32 ry = y; ry < y + h; ry++) {
for (i32 rx = x; rx < x + w; rx++) {
room_grid_set(&grid, rx, ry, 0);
}
}
if (room_count > 0) {
i32 new_cx = x + w / 2;
i32 new_cy = y + h / 2;
i32 prev_cx = rooms[room_count - 1].x + rooms[room_count - 1].w / 2;
i32 prev_cy = rooms[room_count - 1].y + rooms[room_count - 1].h / 2;
if (pxl8_rng_next(&rng) % 2 == 0) {
carve_corridor_h(&grid, prev_cx, new_cx, prev_cy);
carve_corridor_v(&grid, prev_cy, new_cy, new_cx);
} else {
carve_corridor_v(&grid, prev_cy, new_cy, prev_cx);
carve_corridor_h(&grid, prev_cx, new_cx, new_cy);
}
}
rooms[room_count++] = new_room;
}
}
pxl8_debug("Room generation: %dx%d grid -> %d rooms created",
params->width, params->height, room_count);
pxl8_result result = grid_to_bsp(bsp, &grid);
free(grid.cells);
return result;
}
pxl8_result pxl8_procgen(pxl8_bsp* bsp, const pxl8_procgen_params* params) {
if (!bsp || !params) {
return PXL8_ERROR_NULL_POINTER;
}
switch (params->type) {
case PXL8_PROCGEN_ROOMS:
return procgen_rooms(bsp, params);
case PXL8_PROCGEN_TERRAIN:
pxl8_error("Terrain generation not yet implemented");
return PXL8_ERROR_NOT_INITIALIZED;
default:
pxl8_error("Unknown procgen type: %d", params->type);
return PXL8_ERROR_INVALID_ARGUMENT;
}
}
static u32 hash2d(i32 x, i32 y) {
u32 h = ((u32)x * 374761393u) + ((u32)y * 668265263u);
h ^= h >> 13;
h ^= h << 17;
h ^= h >> 5;
return h;
}
void pxl8_procgen_tex(u8* buffer, const pxl8_procgen_tex_params* params) {
if (!buffer || !params) return;
for (i32 y = 0; y < params->height; y++) {
for (i32 x = 0; x < params->width; x++) {
f32 u = (f32)x / (f32)params->width;
f32 v = (f32)y / (f32)params->height;
u8 color = params->base_color;
// Tile-based pattern (floor style)
if (params->seed == 11111) {
i32 tile_x = (i32)floorf(u * 8.0f);
i32 tile_y = (i32)floorf(v * 8.0f);
u32 h = hash2d(tile_x, tile_y);
f32 pattern = (f32)(h & 0xFF) / 255.0f;
i32 quantized = (pattern < 0.3f) ? 0 : (pattern < 0.7f) ? 1 : 2;
color = params->base_color + quantized;
// Checkerboard dither
if (((tile_x + tile_y) & 1) == 0 && (h & 0x100)) {
color = (color < 255) ? color + 1 : color;
}
}
// Large tile pattern (ceiling style)
else if (params->seed == 22222) {
i32 coarse_x = (i32)floorf(u * 2.0f);
i32 coarse_y = (i32)floorf(v * 2.0f);
u32 coarse_h = hash2d(coarse_x, coarse_y);
i32 subdivision = (coarse_h >> 8) & 0x3;
i32 tile_x, tile_y;
switch (subdivision) {
case 0: tile_x = (i32)floorf(u * 3.0f); tile_y = (i32)floorf(v * 3.0f); break;
case 1: tile_x = (i32)floorf(u * 5.0f); tile_y = (i32)floorf(v * 5.0f); break;
case 2: tile_x = (i32)floorf(u * 2.0f); tile_y = (i32)floorf(v * 4.0f); break;
default: tile_x = (i32)floorf(u * 4.0f); tile_y = (i32)floorf(v * 2.0f); break;
}
u32 h = hash2d(tile_x, tile_y);
f32 pattern = (f32)(h & 0xFF) / 255.0f;
if (pattern < 0.25f) color = params->base_color;
else if (pattern < 0.50f) color = params->base_color + 1;
else if (pattern < 0.75f) color = params->base_color + 2;
else color = params->base_color + 3;
}
// Brick pattern (wall style)
else {
f32 brick_y = floorf(v * 4.0f);
f32 offset = ((i32)brick_y & 1) ? 0.5f : 0.0f;
i32 brick_x = (i32)floorf(u * 4.0f + offset);
brick_y = (i32)brick_y;
f32 brick_u = fabsf((u * 4.0f + offset) - floorf(u * 4.0f + offset) - 0.5f);
f32 brick_v = fabsf((v * 4.0f) - floorf(v * 4.0f) - 0.5f);
u32 h = hash2d(brick_x, (i32)brick_y);
f32 noise = (f32)(h & 0xFF) / 255.0f;
// Mortar lines
if (brick_u > 0.47f || brick_v > 0.47f) {
color = params->base_color - 2;
} else {
i32 shade = (i32)(noise * 3.0f);
color = params->base_color + shade;
}
}
buffer[y * params->width + x] = color;
}
}
}

View file

@ -1,16 +1,15 @@
(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)
(var particles nil) (var particles nil)
(var particles2 nil)
(var fire-init? false) (var fire-init? false)
(var rain-init? false) (var rain-init? false)
(var snow-init? false) (var snow-init? false)
(var snow-init2? false) (var first_person3d-init? false)
(var use-famicube-palette? false) (var use-famicube-palette? false)
(var logo-x 256) (var logo-x 256)
@ -32,15 +31,12 @@
(pxl8.load_palette "res/sprites/pxl8_logo.ase") (pxl8.load_palette "res/sprites/pxl8_logo.ase")
(set logo-sprite (pxl8.load_sprite "res/sprites/pxl8_logo.ase")) (set logo-sprite (pxl8.load_sprite "res/sprites/pxl8_logo.ase"))
(set particles (pxl8.create_particles 1000)) (set particles (pxl8.create_particles 1000))
(set particles2 (pxl8.create_particles 500)) (music.init)))
(music.init)
(music.start)
(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,15 +46,16 @@
(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)
(when (= active-demo :fire) (set fire-init? false)) (when (= active-demo :fire) (set fire-init? false))
(when (= active-demo :rain) (set rain-init? false)) (when (= active-demo :rain) (set rain-init? false))
(when (= active-demo :snow) (set snow-init? false) (set snow-init2? false))) (when (= active-demo :snow) (set snow-init? false))
(when (= active-demo :first_person3d) (set first_person3d-init? false)))
(transition:destroy) (transition:destroy)
(set transition nil))) (set transition nil)))
@ -69,12 +66,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,14 +90,14 @@
(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 (do
(when (not first_person3d-init?)
(music.update dt) (first_person3d.init)
(set first_person3d-init? true))
(first_person3d.update dt)))
(when particles (when particles
(particles:update dt)) (particles:update dt)))
(when particles2
(particles2:update dt)))
(when (menu.is-paused) (when (menu.is-paused)
(menu.update)))) (menu.update))))
@ -173,7 +172,8 @@
(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))

4103
demo/mod/blendtable.fnl Normal file

File diff suppressed because it is too large Load diff

1031
demo/mod/colormap.fnl Normal file

File diff suppressed because it is too large Load diff

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

@ -0,0 +1,516 @@
(local pxl8 (require :pxl8))
(local effects (require :pxl8.effects))
(local net (require :pxl8.net))
(local colormap (require :mod.colormap))
(local menu (require :mod.menu))
(local palette (require :mod.palette))
(local sky (require :mod.sky))
(local textures (require :mod.textures))
(local bob-amount 4.0)
(local bob-speed 8.0)
(local cam-smoothing 0.25)
(local cell-size 64)
(local cursor-sensitivity 0.010)
(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 4.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 grounded? true)
(var land-squash 0)
(var light-time 0)
(var real-time 0)
(var network nil)
(var smooth-cam-x 1000)
(var smooth-cam-z 1000)
(var velocity-y 0)
(var world nil)
(var fps-avg 0)
(var fps-sample-count 0)
(var fireball-mesh nil)
(var last-dt 0.016)
(local cursor-look? true)
(local FIREBALL_COLOR 218)
(local STONE_FLOOR_START 37)
(local STONE_WALL_START 2)
(local MOSS_COLOR 200)
(local trail-positions [])
(local TRAIL_LENGTH 8)
(fn create-fireball-mesh []
(let [verts []
indices []
radius 5
rings 4
segments 6
core-color (+ FIREBALL_COLOR 6)
spike-color (+ FIREBALL_COLOR 2)]
;; top pole
(table.insert verts {:x 0 :y radius :z 0 :nx 0 :ny 1 :nz 0 :color core-color :light 255})
;; sphere rings
(for [ring 1 (- rings 1)]
(let [phi (* (/ ring rings) math.pi)
sin-phi (math.sin phi)
cos-phi (math.cos phi)
y (* radius cos-phi)
ring-radius (* radius sin-phi)]
(for [seg 0 (- segments 1)]
(let [theta (* (/ seg segments) math.pi 2)
x (* ring-radius (math.cos theta))
z (* ring-radius (math.sin theta))
nx (* sin-phi (math.cos theta))
nz (* sin-phi (math.sin theta))]
(table.insert verts {:x x :y y :z z :nx nx :ny cos-phi :nz nz :color core-color :light 255})))))
;; bottom pole
(let [bottom-idx (length verts)]
(table.insert verts {:x 0 :y (- radius) :z 0 :nx 0 :ny -1 :nz 0 :color core-color :light 255})
;; top cap triangles
(for [seg 0 (- segments 1)]
(let [next-seg (% (+ seg 1) segments)]
(table.insert indices 0)
(table.insert indices (+ 1 next-seg))
(table.insert indices (+ 1 seg))))
;; middle quads
(for [ring 0 (- rings 3)]
(for [seg 0 (- segments 1)]
(let [next-seg (% (+ seg 1) segments)
curr-row (+ 1 (* ring segments))
next-row (+ 1 (* (+ ring 1) segments))]
(table.insert indices (+ curr-row seg))
(table.insert indices (+ curr-row next-seg))
(table.insert indices (+ next-row seg))
(table.insert indices (+ curr-row next-seg))
(table.insert indices (+ next-row next-seg))
(table.insert indices (+ next-row seg)))))
;; bottom cap triangles
(let [last-ring-start (+ 1 (* (- rings 2) segments))]
(for [seg 0 (- segments 1)]
(let [next-seg (% (+ seg 1) segments)]
(table.insert indices bottom-idx)
(table.insert indices (+ last-ring-start seg))
(table.insert indices (+ last-ring-start next-seg))))))
;; add spikes - evenly distributed using golden ratio
(let [num-spikes 12
spike-len 8
base-size 1.2
golden-ratio (/ (+ 1 (math.sqrt 5)) 2)]
(for [i 0 (- num-spikes 1)]
(let [;; fibonacci sphere distribution
y (- 1 (* (/ i (- num-spikes 1)) 2))
r-at-y (math.sqrt (- 1 (* y y)))
theta (* math.pi 2 i golden-ratio)
nx (* r-at-y (math.cos theta))
ny y
nz (* r-at-y (math.sin theta))
;; tangent vectors for base
tx (if (> (math.abs ny) 0.9) 1 0)
ty (if (> (math.abs ny) 0.9) 0 1)
tz 0
;; cross product for perpendicular
px (- (* ty nz) (* tz ny))
py (- (* tz nx) (* tx nz))
pz (- (* tx ny) (* ty nx))
pl (math.sqrt (+ (* px px) (* py py) (* pz pz)))
px (/ px pl) py (/ py pl) pz (/ pz pl)
;; second perpendicular
qx (- (* ny pz) (* nz py))
qy (- (* nz px) (* nx pz))
qz (- (* nx py) (* ny px))
;; base center inside sphere
bx (* radius 0.8 nx)
by (* radius 0.8 ny)
bz (* radius 0.8 nz)
;; spike tip
sx (* (+ radius spike-len) nx)
sy (* (+ radius spike-len) ny)
sz (* (+ radius spike-len) nz)
base-idx (length verts)]
;; 4 base vertices forming a square
(table.insert verts {:x (+ bx (* base-size px) (* base-size qx))
:y (+ by (* base-size py) (* base-size qy))
:z (+ bz (* base-size pz) (* base-size qz))
:nx nx :ny ny :nz nz :color core-color :light 255})
(table.insert verts {:x (+ bx (* base-size px) (* (- base-size) qx))
:y (+ by (* base-size py) (* (- base-size) qy))
:z (+ bz (* base-size pz) (* (- base-size) qz))
:nx nx :ny ny :nz nz :color core-color :light 255})
(table.insert verts {:x (+ bx (* (- base-size) px) (* (- base-size) qx))
:y (+ by (* (- base-size) py) (* (- base-size) qy))
:z (+ bz (* (- base-size) pz) (* (- base-size) qz))
:nx nx :ny ny :nz nz :color core-color :light 255})
(table.insert verts {:x (+ bx (* (- base-size) px) (* base-size qx))
:y (+ by (* (- base-size) py) (* base-size qy))
:z (+ bz (* (- base-size) pz) (* base-size qz))
:nx nx :ny ny :nz nz :color core-color :light 255})
;; spike tip
(table.insert verts {:x sx :y sy :z sz :nx nx :ny ny :nz nz :color spike-color :light 255})
;; 4 triangular faces of pyramid
(table.insert indices base-idx)
(table.insert indices (+ base-idx 1))
(table.insert indices (+ base-idx 4))
(table.insert indices (+ base-idx 1))
(table.insert indices (+ base-idx 2))
(table.insert indices (+ base-idx 4))
(table.insert indices (+ base-idx 2))
(table.insert indices (+ base-idx 3))
(table.insert indices (+ base-idx 4))
(table.insert indices (+ base-idx 3))
(table.insert indices base-idx)
(table.insert indices (+ base-idx 4)))))
(set fireball-mesh (pxl8.create_mesh verts indices))))
(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 []
(pxl8.set_relative_mouse_mode true)
(pxl8.set_palette palette 256)
(pxl8.set_colormap colormap 16384)
(for [i 0 7]
(let [t (/ i 7)
r 0xFF
g (math.floor (+ 0x60 (* t (- 0xE0 0x60))))
b (math.floor (+ 0x10 (* t (- 0x80 0x10))))]
(pxl8.set_palette_rgb (+ FIREBALL_COLOR i) r g b)))
(sky.update-gradient 1 2 6 6 10 18)
(pxl8.update_palette_deps)
(set camera (pxl8.create_camera_3d))
(set world (pxl8.create_world))
(sky.generate-stars 12345)
(create-fireball-mesh)
(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 (textures.mossy-cobblestone 44444 STONE_FLOOR_START MOSS_COLOR)
wall-tex (textures.ashlar-wall 55555 STONE_WALL_START MOSS_COLOR)
sky-tex (pxl8.create_texture [0] 1 1)]
(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]
(set last-dt 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.5)))
(set real-time (+ real-time dt)))))
(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-x (+ 1000 (* 50 (math.cos light-time)))
light-z (+ 940 (* 50 (math.sin light-time)))
light-y 80
phase (+ (* light-x 0.01) 1.7)
f1 (* 0.08 (math.sin (+ (* real-time 2.5) phase)))
f2 (* 0.05 (math.sin (+ (* real-time 4.1) (* phase 0.7))))
f3 (* 0.03 (math.sin (+ (* real-time 7.3) (* phase 1.2))))
flicker (+ 0.92 f1 f2 f3)
light-intensity (math.floor (math.max 0 (math.min 255 (* 255 flicker))))
r1 (* 0.06 (math.sin (+ (* real-time 1.8) (* phase 0.5))))
r2 (* 0.04 (math.sin (+ (* real-time 3.2) phase)))
light-radius (* 150 (+ 0.95 r1 r2))]
(pxl8.begin_frame_3d camera {
:ambient 30
:fog_density 0.0
: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 light-intensity
:radius light-radius}]})
(pxl8.clear_depth)
(sky.update-gradient 1 2 6 6 10 18)
(sky.render smooth-cam-x eye-y smooth-cam-z (menu.is-wireframe))
(pxl8.clear_depth)
(world:set_wireframe (menu.is-wireframe) 15)
(world:render [smooth-cam-x eye-y smooth-cam-z])
(when fireball-mesh
(let [wire (menu.is-wireframe)]
(pxl8.draw_mesh fireball-mesh {:x light-x :y light-y :z light-z
:passthrough true
:wireframe wire
:emissive 1.0})))
(pxl8.end_frame_3d))
(sky.render-stars cam-yaw cam-pitch 1.0 last-dt)
(let [cx (/ (pxl8.get_width) 2)
cy (/ (pxl8.get_height) 2)
crosshair-size 4
crosshair-color 240
text-color 251]
(pxl8.line (- cx crosshair-size) cy (+ cx crosshair-size) cy crosshair-color)
(pxl8.line cx (- cy crosshair-size) cx (+ cy crosshair-size) crosshair-color)
(pxl8.text (.. "fps: " (string.format "%.0f" fps-avg)) 5 5 text-color)
(pxl8.text (.. "pos: " (string.format "%.0f" cam-x) ","
(string.format "%.0f" cam-y) ","
(string.format "%.0f" cam-z)) 5 15 text-color)))))
{:init init
:update update
:frame frame}

View file

@ -1,6 +1,8 @@
(local pxl8 (require :pxl8)) (local pxl8 (require :pxl8))
(local music (require :mod.music))
(var paused false) (var paused false)
(var wireframe false)
(var gui nil) (var gui nil)
(fn init [] (fn init []
@ -36,12 +38,22 @@
(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 200 "pxl8 demo")
(when (gui:button 1 215 145 210 32 "Resume") (when (gui:button 1 215 140 210 30 "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 175 210 30 music-label)
(if (music.is-playing)
(music.stop)
(music.start))))
(let [wire-label (if wireframe "Wireframe: On" "Wireframe: Off")]
(when (gui:button 4 215 210 210 30 wire-label)
(set wireframe (not wireframe))))
(when (gui:button 2 215 245 210 30 "Quit")
(pxl8.quit)) (pxl8.quit))
(if (gui:is_hovering) (if (gui:is_hovering)
@ -51,6 +63,7 @@
(gui:end_frame))) (gui:end_frame)))
{:is-paused (fn [] paused) {:is-paused (fn [] paused)
:is-wireframe (fn [] wireframe)
:toggle toggle :toggle toggle
:show show :show show
:hide hide :hide hide

View file

@ -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)}

263
demo/mod/palette.fnl Normal file
View file

@ -0,0 +1,263 @@
(require :pxl8)
(local ffi (require :ffi))
(local data (ffi.new "u32[256]" [
0x080602
0x14120E
0x23211E
0x31302C
0x403E3B
0x4B4946
0x595755
0x676664
0x767573
0x858382
0x939290
0xA2A09F
0xB0AFAE
0xBEBDBC
0xCDCCCC
0xDADAD9
0x594625
0x544023
0x4F3C24
0x4C3A22
0x453821
0x40321F
0x3E2F20
0x382D1D
0x33291E
0x30271F
0x2F251D
0x2D231E
0x28211C
0x251F1D
0x23201A
0x221F1B
0x646269
0x5F5C61
0x5C545A
0x584F55
0x5B514F
0x554A47
0x4B413F
0x423C36
0x463D31
0x3E352A
0x362E25
0x2D2922
0x26221D
0x1C1916
0x151310
0x100F0D
0x8C6F52
0x7E6045
0x73553B
0x715134
0xBA8346
0xA1723B
0x815C2E
0x745226
0xCC8926
0xBB7E22
0x9F6B1F
0x875A1C
0x6E4918
0x553712
0x3B250D
0x24180A
0xA34331
0x9B3728
0x923220
0x882E18
0x842B16
0x772312
0x69200D
0x5A1C06
0x541C04
0x4C1A03
0x411701
0x371000
0x2E0D00
0x250B00
0x1B0600
0x130500
0x7D5741
0x76503A
0x6E4C37
0x684833
0x5D3F2F
0x553A2C
0x4F3628
0x483024
0x4A3126
0x483025
0x432D22
0x3C2C22
0x352922
0x2C241F
0x221C1B
0x1A1916
0x6E4626
0x5F4025
0x523924
0x433322
0x352B1E
0x28231A
0x1A1A14
0x1C1815
0x96544B
0xAC7369
0xB48C86
0xBCA7A4
0xB1BCC2
0x9DB0B9
0x8A9FAA
0x77929F
0x738995
0x5E7C8B
0x4A6C7D
0x345E72
0x1F4C64
0x19445C
0x143C51
0x10384B
0x183748
0x1A3341
0x192F39
0x152B34
0x13262E
0x101E23
0x0E1519
0x0B0E10
0x896463
0x815C5B
0x785352
0x6F4C4D
0x664444
0x5F3C3D
0x573738
0x523233
0x442929
0x392324
0x2D1D1D
0x241414
0x1A0E0E
0x100909
0x070403
0x000000
0x98936F
0x918B68
0x887F60
0x807759
0x797055
0x73684D
0x6B6146
0x63593F
0x5B523A
0x504834
0x423D2D
0x373226
0x2E2B1F
0x222018
0x161511
0x0E0F0A
0x9A554F
0x904D48
0x87453F
0x7D4037
0x743831
0x693329
0x612C24
0x572720
0x4F231A
0x441E16
0x391914
0x2D150F
0x22110D
0x1A0B06
0x0D0403
0x040202
0x7F77C0
0x7770B5
0x6E68A8
0x686099
0x60588C
0x575381
0x4E4C72
0x454263
0x3D3957
0x34324A
0x2C2940
0x242135
0x1E1928
0x16121D
0x0C0A12
0x050306
0x88AF7B
0x81A473
0x7B9A67
0x728E5D
0x6D8553
0x61794A
0x5B7144
0x61734B
0x586A3D
0x4D5E2D
0x465422
0x3F4D17
0x36420E
0x2F3507
0x272804
0x211F02
0x1EF708
0x3CE10D
0x51CC1B
0x64B621
0x6DA12C
0x69882B
0x727F3B
0xE4DDCE
0xEEE6BA
0xEAE290
0xE9E26D
0xE5DE43
0xE3DB20
0xE1CC18
0xDFB911
0xDCA60B
0xE8A306
0xDF9312
0xE17B05
0xC86815
0xBC5908
0xB14805
0xA63D07
0xB6431E
0xAA381A
0x9A2E12
0x8C270F
0x892B17
0x762311
0x5F1F0D
0x491B09
0x3B1809
0xE50F19
0x6A34C4
0xE00B28
0x2B08C8
0x322A33
0x281C0E
0x2F1E15
0xD48067
0xC26B4C
0x974928
0x814123
0xD5B3A9
0xBE9D93
0x9A7B6C
0x7F5F51
0x8E504C
]))
data

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

@ -0,0 +1,271 @@
(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 NUM_RANDOM_STARS 300)
(local NUM_TINY_STARS 7000)
(local STAR_SEED 0xDEADBEEF)
(local STAR_CYCLE_PERIOD 7200)
;; Use existing bright palette colors
;; Silver/white: indices 14-15 (brightest grays)
(local IDX_SILVER 14)
;; Warm/torch: indices 216-218 (bright creams/yellows)
(local IDX_TORCH 216)
;; Blue/magic: indices 176-178 (purples - brightest of the range)
(local IDX_MAGIC 176)
(var sky-mesh nil)
(var star-time 0)
(var last-gradient-key nil)
(var random-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) math.pi 0.5)
theta1 (* (/ (+ i 1) sky-rings) math.pi 0.5)
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
(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})
(table.insert indices base-idx)
(table.insert indices (+ base-idx 2))
(table.insert indices (+ base-idx 1)))
(do
(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})
(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 galactic-band-factor [dx dy dz]
(let [band-len (math.sqrt (+ (* 0.6 0.6) (* 0.3 0.3) (* 0.742 0.742)))
bx (/ 0.6 band-len)
by (/ 0.3 band-len)
bz (/ 0.742 band-len)
dist-from-band (math.abs (+ (* dx bx) (* dy by) (* dz bz)))
in-band (- 1 (math.min (* dist-from-band 3) 1))]
(* in-band in-band)))
(fn generate-stars []
(set random-stars [])
(set tiny-stars [])
;; Generate random stars - use full upper hemisphere (dy > -0.1)
(for [i 0 (- NUM_RANDOM_STARS 1)]
(let [h1 (pxl8.hash32 (+ STAR_SEED (* i 5)))
h2 (pxl8.hash32 (+ STAR_SEED (* i 5) 1))
h3 (pxl8.hash32 (+ STAR_SEED (* i 5) 2))
h4 (pxl8.hash32 (+ STAR_SEED (* i 5) 3))
theta (* (/ h1 0xFFFFFFFF) math.pi 2)
phi (math.acos (- 1 (* (/ h2 0xFFFFFFFF) 1.0)))
sin-phi (math.sin phi)
cos-phi (math.cos phi)
dx (* sin-phi (math.cos theta))
dy cos-phi
dz (* sin-phi (math.sin theta))
brightness-raw (/ (% h3 256) 255)
brightness (math.floor (+ 60 (* brightness-raw brightness-raw 195)))
color-type (% h4 100)
color (if (< color-type 8) (+ IDX_TORCH (% (bit.rshift h4 8) 2))
(< color-type 16) (+ IDX_MAGIC (% (bit.rshift h4 8) 2))
(+ IDX_SILVER (% (bit.rshift h4 8) 2)))]
(when (> dy -0.1)
(table.insert random-stars {:dx dx :dy dy :dz dz
:brightness brightness
:color color}))))
(let [tiny-seed (+ STAR_SEED 0xCAFEBABE)]
(for [i 0 (- NUM_TINY_STARS 1)]
(let [h1 (pxl8.hash32 (+ tiny-seed (* i 4)))
h2 (pxl8.hash32 (+ tiny-seed (* i 4) 1))
h3 (pxl8.hash32 (+ tiny-seed (* i 4) 2))
h4 (pxl8.hash32 (+ tiny-seed (* i 4) 3))
theta (* (/ h1 0xFFFFFFFF) math.pi 2)
phi (math.acos (- 1 (* (/ h2 0xFFFFFFFF) 1.0)))
sin-phi (math.sin phi)
cos-phi (math.cos phi)
dx (* sin-phi (math.cos theta))
dy cos-phi
dz (* sin-phi (math.sin theta))
band-boost (galactic-band-factor dx dy dz)
base-bright (+ 40 (% h3 50))
brightness (+ base-bright (math.floor (* band-boost 40)))
color-shift (% h4 100)
color (if (< color-shift 3) (+ IDX_TORCH (% (bit.rshift h4 8) 2))
(< color-shift 12) (+ IDX_MAGIC (% (bit.rshift h4 8) 2))
(+ IDX_SILVER (% (bit.rshift h4 8) 2)))]
(when (> dy -0.1)
(table.insert tiny-stars {:dx dx :dy dy :dz dz
:brightness brightness
:color color}))))))
(fn project-direction [dir-x dir-y dir-z yaw pitch cos-rot sin-rot width height]
(let [rot-x (- (* dir-x cos-rot) (* dir-z sin-rot))
rot-z (+ (* dir-x sin-rot) (* dir-z cos-rot))
cos-yaw (math.cos yaw)
sin-yaw (math.sin yaw)
cos-pitch (math.cos pitch)
sin-pitch (math.sin pitch)
rotated-x (+ (* rot-x cos-yaw) (* rot-z sin-yaw))
rotated-z (+ (* (- rot-x) sin-yaw) (* rot-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 dt]
(set star-time (+ star-time (or dt 0)))
(when (> intensity 0)
(let [width (pxl8.get_width)
height (pxl8.get_height)
glows []
fade-in (* intensity intensity)
time-factor (/ star-time 60)
star-rotation (/ (* star-time math.pi 2) STAR_CYCLE_PERIOD)
cos-rot (math.cos star-rotation)
sin-rot (math.sin star-rotation)]
(each [i star (ipairs tiny-stars)]
(let [screen (project-direction star.dx star.dy star.dz yaw pitch cos-rot sin-rot width height)]
(when screen
(let [int (math.floor (* star.brightness fade-in))]
(when (> int 8)
(table.insert glows {:x screen.x :y screen.y
:radius 1
:intensity int
:color star.color
:shape effects.GLOW_CIRCLE}))))))
(each [i star (ipairs random-stars)]
(let [screen (project-direction star.dx star.dy star.dz yaw pitch cos-rot sin-rot width height)]
(when screen
(let [phase (+ (* i 2.137) (* time-factor 3.0))
twinkle (+ 0.75 (* 0.25 (math.sin (* phase 6.28))))
int (math.floor (* star.brightness fade-in twinkle))]
(if (> star.brightness 220)
(do
(table.insert glows {:x screen.x :y screen.y
:radius 3
:intensity (math.floor (* int 1.5))
:color star.color
:shape effects.GLOW_DIAMOND})
(table.insert glows {:x screen.x :y screen.y
:radius 5
:intensity (math.floor (/ int 2))
:color star.color
:shape effects.GLOW_CIRCLE}))
(> star.brightness 180)
(do
(table.insert glows {:x screen.x :y screen.y
:radius 2
:intensity int
:color star.color
:shape effects.GLOW_DIAMOND})
(table.insert glows {:x screen.x :y screen.y
:radius 4
:intensity (math.floor (/ int 3))
:color star.color
:shape effects.GLOW_CIRCLE}))
(> star.brightness 120)
(do
(table.insert glows {:x screen.x :y screen.y
:radius 2
:intensity (math.floor (* int 0.67))
:color star.color
:shape effects.GLOW_DIAMOND})
(table.insert glows {:x screen.x :y screen.y
:radius 3
:intensity (math.floor (/ int 4))
:color star.color
:shape effects.GLOW_CIRCLE}))
(table.insert glows {:x screen.x :y screen.y
:radius 2
:intensity (math.floor (* int 0.5))
:color star.color
:shape effects.GLOW_CIRCLE}))))))
(when (> (length glows) 0)
(effects.glows glows)))))
(fn render [cam-x cam-y cam-z wireframe]
(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 :wireframe wireframe})))
{: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}

168
demo/mod/textures.fnl Normal file
View file

@ -0,0 +1,168 @@
(local pxl8 (require :pxl8))
(local procgen (require :pxl8.procgen))
(local textures {})
(fn build-graph [seed builder]
(let [g (pxl8.create_graph 128)
x (g:add_node procgen.OP_INPUT_X 0 0 0 0 0)
y (g:add_node procgen.OP_INPUT_Y 0 0 0 0 0)
ctx {:graph g :x x :y y}]
(g:set_seed seed)
(let [output (builder ctx)]
(g:set_output output)
g)))
(fn const [ctx val]
(ctx.graph:add_node procgen.OP_CONST 0 0 0 0 val))
(fn add [ctx a b]
(ctx.graph:add_node procgen.OP_ADD a b 0 0 0))
(fn sub [ctx a b]
(ctx.graph:add_node procgen.OP_SUB a b 0 0 0))
(fn mul [ctx a b]
(ctx.graph:add_node procgen.OP_MUL a b 0 0 0))
(fn div [ctx a b]
(ctx.graph:add_node procgen.OP_DIV a b 0 0 0))
(fn min-op [ctx a b]
(ctx.graph:add_node procgen.OP_MIN a b 0 0 0))
(fn max-op [ctx a b]
(ctx.graph:add_node procgen.OP_MAX a b 0 0 0))
(fn abs [ctx a]
(ctx.graph:add_node procgen.OP_ABS a 0 0 0 0))
(fn floor [ctx a]
(ctx.graph:add_node procgen.OP_FLOOR a 0 0 0 0))
(fn fract [ctx a]
(ctx.graph:add_node procgen.OP_FRACT a 0 0 0 0))
(fn lerp [ctx a b t]
(ctx.graph:add_node procgen.OP_LERP a b t 0 0))
(fn clamp [ctx val lo hi]
(ctx.graph:add_node procgen.OP_CLAMP val lo hi 0 0))
(fn select [ctx cond a b]
(ctx.graph:add_node procgen.OP_SELECT a b cond 0 0))
(fn smoothstep [ctx edge0 edge1 x]
(ctx.graph:add_node procgen.OP_SMOOTHSTEP edge0 edge1 x 0 0))
(fn noise-value [ctx scale]
(let [s (const ctx scale)]
(ctx.graph:add_node procgen.OP_NOISE_VALUE ctx.x ctx.y s 0 0)))
(fn noise-perlin [ctx scale]
(let [s (const ctx scale)]
(ctx.graph:add_node procgen.OP_NOISE_PERLIN ctx.x ctx.y s 0 0)))
(fn noise-fbm [ctx octaves scale persistence]
(let [s (const ctx scale)
p (const ctx persistence)]
(ctx.graph:add_node procgen.OP_NOISE_FBM ctx.x ctx.y s p octaves)))
(fn noise-ridged [ctx octaves scale persistence]
(let [s (const ctx scale)
p (const ctx persistence)]
(ctx.graph:add_node procgen.OP_NOISE_RIDGED ctx.x ctx.y s p octaves)))
(fn noise-turbulence [ctx octaves scale persistence]
(let [s (const ctx scale)
p (const ctx persistence)]
(ctx.graph:add_node procgen.OP_NOISE_TURBULENCE ctx.x ctx.y s p octaves)))
(fn voronoi-cell [ctx scale]
(let [s (const ctx scale)]
(ctx.graph:add_node procgen.OP_VORONOI_CELL ctx.x ctx.y s 0 0)))
(fn voronoi-edge [ctx scale]
(let [s (const ctx scale)]
(ctx.graph:add_node procgen.OP_VORONOI_EDGE ctx.x ctx.y s 0 0)))
(fn voronoi-id [ctx scale]
(let [s (const ctx scale)]
(ctx.graph:add_node procgen.OP_VORONOI_ID ctx.x ctx.y s 0 0)))
(fn gradient-linear [ctx angle]
(let [a (const ctx angle)]
(ctx.graph:add_node procgen.OP_GRADIENT_LINEAR ctx.x ctx.y a 0 0)))
(fn gradient-radial [ctx cx cy]
(let [center-x (const ctx cx)
center-y (const ctx cy)]
(ctx.graph:add_node procgen.OP_GRADIENT_RADIAL ctx.x ctx.y center-x center-y 0)))
(fn quantize [ctx val base range]
(let [r (const ctx range)]
(ctx.graph:add_node procgen.OP_QUANTIZE val r 0 0 base)))
(fn textures.mossy-cobblestone [seed base-color moss-color]
(let [g (build-graph seed
(fn [ctx]
(let [cell (voronoi-cell ctx 6)
edge (voronoi-edge ctx 6)
mortar-threshold (const ctx 0.05)
is-mortar (sub ctx mortar-threshold edge)
mortar-color (const ctx (- base-color 2))
stone-detail (noise-value ctx 48)
stone-base (mul ctx cell (const ctx 0.6))
stone-combined (add ctx stone-base (mul ctx stone-detail (const ctx 0.4)))
stone-quant (quantize ctx stone-combined base-color 8)
moss-pattern (noise-fbm ctx 4 10 0.5)
moss-detail (noise-value ctx 64)
moss-var (add ctx (mul ctx moss-pattern (const ctx 0.7))
(mul ctx moss-detail (const ctx 0.3)))
moss-threshold (const ctx 0.55)
has-moss (sub ctx moss-pattern moss-threshold)
moss-quant (quantize ctx moss-var moss-color 6)
stone-or-moss (select ctx has-moss moss-quant stone-quant)]
(select ctx is-mortar mortar-color stone-or-moss))))]
(let [tex-id (g:eval_texture 64 64)]
(g:destroy)
tex-id)))
(fn textures.ashlar-wall [seed base-color moss-color]
(let [g (build-graph seed
(fn [ctx]
(let [cell (voronoi-cell ctx 5)
edge (voronoi-edge ctx 5)
cell-id (voronoi-id ctx 5)
mortar-threshold (const ctx 0.12)
is-mortar (sub ctx mortar-threshold edge)
stone-detail (noise-fbm ctx 3 32 0.5)
stone-tint (mul ctx cell-id (const ctx 0.4))
stone-shade (mul ctx cell (const ctx 0.3))
stone-combined (add ctx stone-detail (add ctx stone-tint stone-shade))
stone-quant (quantize ctx stone-combined base-color 10)
crack-moss (noise-fbm ctx 3 16 0.5)
moss-in-crack (mul ctx crack-moss (sub ctx (const ctx 0.2) edge))
moss-threshold (const ctx 0.06)
has-moss (sub ctx moss-in-crack moss-threshold)
moss-quant (quantize ctx crack-moss moss-color 4)
mortar-color (const ctx (+ base-color 1))
stone-or-moss (select ctx has-moss moss-quant stone-quant)]
(select ctx is-mortar mortar-color stone-or-moss))))]
(let [tex-id (g:eval_texture 64 64)]
(g:destroy)
tex-id)))
(fn textures.gradient-sky [seed zenith-color horizon-color]
(let [g (build-graph seed
(fn [ctx]
(let [grad (gradient-linear ctx (* math.pi 0.5))
zenith (const ctx zenith-color)
horizon (const ctx horizon-color)
range (const ctx (- horizon-color zenith-color))]
(quantize ctx grad zenith-color (- horizon-color zenith-color)))))]
(let [tex-id (g:eval_texture 64 64)]
(g:destroy)
tex-id)))
textures

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

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

147
pxl8.sh
View file

@ -56,6 +56,55 @@ 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
else
cargo build
fi
local status=$?
cd - > /dev/null
if [[ $status -eq 0 ]]; then
print_info "Built server"
else
print_error "Server build failed"
fi
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
print_info "Server mode: $mode, binary: $server_bin"
if [[ -f "$server_bin" ]]; then
print_info "Starting server..."
./$server_bin &
SERVER_PID=$!
print_info "Server started with PID $SERVER_PID"
sleep 0.5
else
print_error "Server binary not found: $server_bin"
print_error "Build the server first with: cd server && cargo build"
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 +162,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() {
@ -173,7 +223,7 @@ timestamp() {
update_fennel() { update_fennel() {
print_info "Fetching Fennel" print_info "Fetching Fennel"
local version="1.6.0" local version="1.6.1"
if curl -sL --max-time 5 -o lib/fennel/fennel.lua "https://fennel-lang.org/downloads/fennel-${version}.lua" 2>/dev/null; then if curl -sL --max-time 5 -o lib/fennel/fennel.lua "https://fennel-lang.org/downloads/fennel-${version}.lua" 2>/dev/null; then
if [[ -f "lib/fennel/fennel.lua" ]] && [[ -s "lib/fennel/fennel.lua" ]]; then if [[ -f "lib/fennel/fennel.lua" ]] && [[ -s "lib/fennel/fennel.lua" ]]; then
@ -264,7 +314,8 @@ for arg in "$@"; do
done done
if [ "$MODE" = "release" ]; then if [ "$MODE" = "release" ]; then
CFLAGS="$CFLAGS -O3 -ffast-math -funroll-loops -fno-unwind-tables -fno-asynchronous-unwind-tables" CFLAGS="$CFLAGS -O3 -flto -ffast-math -funroll-loops -fno-unwind-tables -fno-asynchronous-unwind-tables"
LINKER_FLAGS="$LINKER_FLAGS -flto"
BUILDDIR="$BUILDDIR/release" BUILDDIR="$BUILDDIR/release"
BINDIR="$BINDIR/release" BINDIR="$BINDIR/release"
else else
@ -273,7 +324,7 @@ else
BINDIR="$BINDIR/debug" BINDIR="$BINDIR/debug"
fi fi
DEP_CFLAGS="-O3 -ffast-math -funroll-loops" DEP_CFLAGS="-O3 -funroll-loops"
case "$COMMAND" in case "$COMMAND" in
build) build)
@ -320,7 +371,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/asset -Isrc/core -Isrc/gfx -Isrc/gui -Isrc/hal -Isrc/math -Isrc/net -Isrc/procgen -Isrc/script -Isrc/sfx -Isrc/world -Ilib/linenoise -Ilib/luajit/src -Ilib/miniz"
COMPILE_FLAGS="$CFLAGS $INCLUDES" COMPILE_FLAGS="$CFLAGS $INCLUDES"
DEP_COMPILE_FLAGS="$DEP_CFLAGS $INCLUDES" DEP_COMPILE_FLAGS="$DEP_CFLAGS $INCLUDES"
@ -329,38 +380,43 @@ 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_blit.c
client/src/gfx/pxl8_colormap.c src/gfx/pxl8_3d_camera.c
client/src/gfx/pxl8_cpu.c src/gfx/pxl8_colormap.c
client/src/gfx/pxl8_dither.c src/gfx/pxl8_cpu.c
client/src/gfx/pxl8_font.c src/gfx/pxl8_dither.c
client/src/gfx/pxl8_gfx.c src/gfx/pxl8_font.c
client/src/gfx/pxl8_mesh.c src/gfx/pxl8_gfx.c
client/src/gfx/pxl8_palette.c src/gfx/pxl8_lightmap.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/procgen/pxl8_graph.c
client/src/game/pxl8_gui.c src/asset/pxl8_ase.c
client/src/game/pxl8_replay.c src/asset/pxl8_cart.c
src/asset/pxl8_save.c
src/gui/pxl8_gui.c
src/core/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 +446,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 +486,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 MODE="release"
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

View file

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

1
server/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
target/

1090
server/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

34
server/Cargo.toml Normal file
View file

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

23
server/build.rs Normal file
View file

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

View file

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

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

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

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

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

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

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

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

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

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

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

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

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

View file

@ -8,55 +8,67 @@ static const char embed_fennel[] = {
, 0 }; , 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"),

View file

@ -6,7 +6,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <pthread.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
@ -310,7 +309,7 @@ pxl8_result pxl8_update(pxl8* sys) {
if (pxl8_script_load_module(game->script, "pxl8") != PXL8_OK) { if (pxl8_script_load_module(game->script, "pxl8") != PXL8_OK) {
const char* err_msg = pxl8_script_get_last_error(game->script); const char* err_msg = pxl8_script_get_last_error(game->script);
pxl8_error("failed to setup pxl8 global: %s", err_msg); pxl8_error("failed to load pxl8 global: %s", err_msg);
} }
sys->repl = pxl8_repl_create(); sys->repl = pxl8_repl_create();

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

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

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

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

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

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

View file

@ -29,8 +29,9 @@ typedef __uint128_t u128;
#endif #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 {

View file

@ -56,8 +56,8 @@ void pxl8_3d_camera_lookat(pxl8_3d_camera* cam, pxl8_vec3 eye, pxl8_vec3 target,
pxl8_vec3 forward = pxl8_vec3_normalize(pxl8_vec3_sub(target, eye)); pxl8_vec3 forward = pxl8_vec3_normalize(pxl8_vec3_sub(target, eye));
cam->pitch = asinf(-forward.y); cam->pitch = asinf(forward.y);
cam->yaw = atan2f(forward.x, forward.z); cam->yaw = atan2f(-forward.x, -forward.z);
cam->roll = 0; cam->roll = 0;
(void)up; (void)up;
@ -104,9 +104,9 @@ pxl8_vec3 pxl8_3d_camera_get_forward(const pxl8_3d_camera* cam) {
f32 sy = sinf(cam->yaw); f32 sy = sinf(cam->yaw);
return (pxl8_vec3){ return (pxl8_vec3){
cp * sy, -sy * cp,
-sp, sp,
cp * cy -cy * cp
}; };
} }
@ -183,8 +183,8 @@ void pxl8_3d_camera_follow(pxl8_3d_camera* cam, pxl8_vec3 target, pxl8_vec3 offs
cam->position = pxl8_vec3_lerp(cam->position, desired, t); cam->position = pxl8_vec3_lerp(cam->position, desired, t);
pxl8_vec3 forward = pxl8_vec3_normalize(pxl8_vec3_sub(target, cam->position)); pxl8_vec3 forward = pxl8_vec3_normalize(pxl8_vec3_sub(target, cam->position));
cam->pitch = asinf(-forward.y); cam->pitch = asinf(forward.y);
cam->yaw = atan2f(forward.x, forward.z); cam->yaw = atan2f(-forward.x, -forward.z);
} }
void pxl8_3d_camera_shake(pxl8_3d_camera* cam, f32 intensity, f32 duration) { void pxl8_3d_camera_shake(pxl8_3d_camera* cam, f32 intensity, f32 duration) {
@ -212,3 +212,30 @@ void pxl8_3d_camera_update(pxl8_3d_camera* cam, f32 dt) {
} }
} }
} }
pxl8_projected_point pxl8_3d_camera_world_to_screen(const pxl8_3d_camera* cam, pxl8_vec3 world_pos, u32 screen_width, u32 screen_height) {
pxl8_projected_point result = {0, 0, 0.0f, false};
if (!cam) return result;
pxl8_mat4 view = pxl8_3d_camera_get_view(cam);
pxl8_mat4 proj = pxl8_3d_camera_get_projection(cam);
pxl8_mat4 vp = pxl8_mat4_mul(proj, view);
pxl8_vec4 clip = pxl8_mat4_mul_vec4(vp, (pxl8_vec4){world_pos.x, world_pos.y, world_pos.z, 1.0f});
if (clip.w <= 0.0f) return result;
f32 inv_w = 1.0f / clip.w;
f32 ndc_x = clip.x * inv_w;
f32 ndc_y = clip.y * inv_w;
f32 ndc_z = clip.z * inv_w;
if (ndc_x < -1.0f || ndc_x > 1.0f || ndc_y < -1.0f || ndc_y > 1.0f) return result;
result.x = (i32)((ndc_x + 1.0f) * 0.5f * (f32)screen_width);
result.y = (i32)((1.0f - ndc_y) * 0.5f * (f32)screen_height);
result.depth = ndc_z;
result.visible = true;
return result;
}

View file

@ -32,6 +32,7 @@ pxl8_mat4 pxl8_3d_camera_get_view(const pxl8_3d_camera* cam);
void pxl8_3d_camera_blend(pxl8_3d_camera* dest, const pxl8_3d_camera* a, const pxl8_3d_camera* b, f32 t); void pxl8_3d_camera_blend(pxl8_3d_camera* dest, const pxl8_3d_camera* a, const pxl8_3d_camera* b, f32 t);
void pxl8_3d_camera_follow(pxl8_3d_camera* cam, pxl8_vec3 target, pxl8_vec3 offset, f32 smoothing, f32 dt); void pxl8_3d_camera_follow(pxl8_3d_camera* cam, pxl8_vec3 target, pxl8_vec3 offset, f32 smoothing, f32 dt);
pxl8_projected_point pxl8_3d_camera_world_to_screen(const pxl8_3d_camera* cam, pxl8_vec3 world_pos, u32 screen_width, u32 screen_height);
void pxl8_3d_camera_shake(pxl8_3d_camera* cam, f32 intensity, f32 duration); void pxl8_3d_camera_shake(pxl8_3d_camera* cam, f32 intensity, f32 duration);
void pxl8_3d_camera_update(pxl8_3d_camera* cam, f32 dt); void pxl8_3d_camera_update(pxl8_3d_camera* cam, f32 dt);

View file

@ -27,6 +27,8 @@ typedef struct pxl8_skyline {
struct pxl8_atlas { struct pxl8_atlas {
u32 height, width; u32 height, width;
u8* pixels; u8* pixels;
u8* pixels_tiled;
u32 tiled_capacity, tiled_size;
bool dirty; bool dirty;
@ -195,6 +197,7 @@ void pxl8_atlas_destroy(pxl8_atlas* atlas) {
free(atlas->entries); free(atlas->entries);
free(atlas->free_list); free(atlas->free_list);
free(atlas->pixels); free(atlas->pixels);
free(atlas->pixels_tiled);
free(atlas->skyline.nodes); free(atlas->skyline.nodes);
free(atlas); free(atlas);
} }
@ -209,6 +212,13 @@ void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count) {
atlas->entry_count = preserve_count; atlas->entry_count = preserve_count;
atlas->free_count = 0; atlas->free_count = 0;
if (preserve_count == 0) {
atlas->tiled_size = 0;
} else {
pxl8_atlas_entry* last = &atlas->entries[preserve_count - 1];
atlas->tiled_size = last->tiled_base + (u32)(last->w * last->h);
}
atlas->skyline.nodes[0] = (pxl8_skyline_node){0, 0, (i32)atlas->width}; atlas->skyline.nodes[0] = (pxl8_skyline_node){0, 0, (i32)atlas->width};
atlas->skyline.count = 1; atlas->skyline.count = 1;
@ -334,6 +344,7 @@ u32 pxl8_atlas_add_texture(
entry->y = fit.pos.y; entry->y = fit.pos.y;
entry->w = w; entry->w = w;
entry->h = h; entry->h = h;
entry->log2_w = pxl8_log2(w);
i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode); i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode);
for (u32 y = 0; y < h; y++) { for (u32 y = 0; y < h; y++) {
@ -349,6 +360,30 @@ u32 pxl8_atlas_add_texture(
} }
} }
u32 tiled_tex_size = w * h;
u32 new_tiled_size = atlas->tiled_size + tiled_tex_size;
if (new_tiled_size > atlas->tiled_capacity) {
u32 new_cap = atlas->tiled_capacity ? atlas->tiled_capacity * 2 : 4096;
while (new_cap < new_tiled_size) new_cap *= 2;
u8* new_tiled = (u8*)realloc(atlas->pixels_tiled, new_cap);
if (!new_tiled) {
entry->active = false;
return UINT32_MAX;
}
atlas->pixels_tiled = new_tiled;
atlas->tiled_capacity = new_cap;
}
entry->tiled_base = atlas->tiled_size;
u8* tiled_dst = atlas->pixels_tiled + entry->tiled_base;
for (u32 ty = 0; ty < h; ty++) {
for (u32 tx = 0; tx < w; tx++) {
u32 tiled_offset = pxl8_tile_addr(tx, ty, entry->log2_w);
tiled_dst[tiled_offset] = pixels[ty * w + tx];
}
}
atlas->tiled_size = new_tiled_size;
if (!pxl8_skyline_add_rect(&atlas->skyline, fit.pos, w, h)) { if (!pxl8_skyline_add_rect(&atlas->skyline, fit.pos, w, h)) {
entry->active = false; entry->active = false;
return UINT32_MAX; return UINT32_MAX;
@ -377,6 +412,10 @@ const u8* pxl8_atlas_get_pixels(const pxl8_atlas* atlas) {
return atlas ? atlas->pixels : NULL; return atlas ? atlas->pixels : NULL;
} }
const u8* pxl8_atlas_get_pixels_tiled(const pxl8_atlas* atlas) {
return atlas ? atlas->pixels_tiled : NULL;
}
u32 pxl8_atlas_get_width(const pxl8_atlas* atlas) { u32 pxl8_atlas_get_width(const pxl8_atlas* atlas) {
return atlas ? atlas->width : 0; return atlas ? atlas->width : 0;
} }

View file

@ -8,28 +8,42 @@ typedef struct pxl8_atlas_entry {
bool active; bool active;
u32 texture_id; u32 texture_id;
i32 x, y, w, h; i32 x, y, w, h;
u32 tiled_base;
u8 log2_w;
} pxl8_atlas_entry; } pxl8_atlas_entry;
static inline u32 pxl8_tile_addr(u32 u, u32 v, u8 log2_w) {
u32 tile_y = v >> 3;
u32 tile_x = u >> 3;
u32 local_y = v & 7;
u32 local_x = u & 7;
return (tile_y << (log2_w + 3)) | (tile_x << 6) | (local_y << 3) | local_x;
}
static inline u8 pxl8_log2(u32 v) {
u8 r = 0;
while (v >>= 1) r++;
return r;
}
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u32 h, pxl8_pixel_mode pixel_mode);
void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count);
pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_pixel_mode pixel_mode); pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_pixel_mode pixel_mode);
void pxl8_atlas_destroy(pxl8_atlas* atlas); void pxl8_atlas_destroy(pxl8_atlas* atlas);
bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode);
const pxl8_atlas_entry* pxl8_atlas_get_entry(const pxl8_atlas* atlas, u32 id); const pxl8_atlas_entry* pxl8_atlas_get_entry(const pxl8_atlas* atlas, u32 id);
u32 pxl8_atlas_get_entry_count(const pxl8_atlas* atlas); u32 pxl8_atlas_get_entry_count(const pxl8_atlas* atlas);
u32 pxl8_atlas_get_height(const pxl8_atlas* atlas); u32 pxl8_atlas_get_height(const pxl8_atlas* atlas);
const u8* pxl8_atlas_get_pixels(const pxl8_atlas* atlas); const u8* pxl8_atlas_get_pixels(const pxl8_atlas* atlas);
const u8* pxl8_atlas_get_pixels_tiled(const pxl8_atlas* atlas);
u32 pxl8_atlas_get_width(const pxl8_atlas* atlas); u32 pxl8_atlas_get_width(const pxl8_atlas* atlas);
bool pxl8_atlas_is_dirty(const pxl8_atlas* atlas); bool pxl8_atlas_is_dirty(const pxl8_atlas* atlas);
void pxl8_atlas_mark_clean(pxl8_atlas* atlas); void pxl8_atlas_mark_clean(pxl8_atlas* atlas);
u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u32 h, pxl8_pixel_mode pixel_mode);
void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count);
bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View file

@ -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) {

123
src/gfx/pxl8_colormap.c Normal file
View file

@ -0,0 +1,123 @@
#include "pxl8_colormap.h"
#include <string.h>
static u8 find_closest_color(const u32* palette, u8 target_r, u8 target_g, u8 target_b) {
u8 best_idx = 1;
u32 best_dist = 0xFFFFFFFF;
u8 dynamic_end = PXL8_DYNAMIC_RANGE_START + PXL8_DYNAMIC_RANGE_COUNT;
for (u32 i = 1; i < PXL8_FULLBRIGHT_START; i++) {
if (i >= PXL8_DYNAMIC_RANGE_START && i < dynamic_end) {
continue;
}
u32 c = palette[i];
u8 pr = c & 0xFF;
u8 pg = (c >> 8) & 0xFF;
u8 pb = (c >> 16) & 0xFF;
i32 dr = (i32)target_r - (i32)pr;
i32 dg = (i32)target_g - (i32)pg;
i32 db = (i32)target_b - (i32)pb;
u32 dist = (u32)(dr * dr + dg * dg + db * db);
if (dist < best_dist) {
best_dist = dist;
best_idx = (u8)i;
if (dist == 0) break;
}
}
return best_idx;
}
void pxl8_set_colormap(pxl8_colormap* cm, const u8* data, u32 size) {
if (!cm || !data || size == 0) return;
u32 copy_size = size > PXL8_COLORMAP_SIZE ? PXL8_COLORMAP_SIZE : size;
memcpy(cm->table, data, copy_size);
}
static void generate_light_table(pxl8_colormap* cm, const u32* palette, pxl8_light_color light_color) {
pxl8_rgb light = pxl8_light_colors[light_color];
u32 base_row = (u32)light_color * PXL8_LIGHT_LEVELS;
for (u32 level = 0; level < PXL8_LIGHT_LEVELS; level++) {
f32 brightness = (f32)level / (f32)(PXL8_LIGHT_LEVELS - 1);
u32 row = base_row + level;
for (u32 pal_idx = 0; pal_idx < 256; pal_idx++) {
u8 result_idx;
if (pal_idx == PXL8_TRANSPARENT) {
result_idx = PXL8_TRANSPARENT;
} else if (pal_idx >= PXL8_FULLBRIGHT_START) {
result_idx = (u8)pal_idx;
} else {
u32 c = palette[pal_idx];
u8 r = c & 0xFF;
u8 g = (c >> 8) & 0xFF;
u8 b = (c >> 16) & 0xFF;
f32 lr = (f32)light.r / 255.0f;
f32 lg = (f32)light.g / 255.0f;
f32 lb = (f32)light.b / 255.0f;
u8 target_r = (u8)(r * brightness * lr);
u8 target_g = (u8)(g * brightness * lg);
u8 target_b = (u8)(b * brightness * lb);
result_idx = find_closest_color(palette, target_r, target_g, target_b);
}
cm->table[row * 256 + pal_idx] = result_idx;
}
}
}
static void generate_blend_table(pxl8_colormap* cm, const u32* palette) {
for (u32 src = 0; src < 256; src++) {
u32 row = PXL8_LIGHT_ROWS + src;
u8 sr, sg, sb;
if (src == PXL8_TRANSPARENT) {
sr = sg = sb = 0;
} else {
u32 sc = palette[src];
sr = sc & 0xFF;
sg = (sc >> 8) & 0xFF;
sb = (sc >> 16) & 0xFF;
}
for (u32 dst = 0; dst < 256; dst++) {
u8 result_idx;
if (src == PXL8_TRANSPARENT) {
result_idx = (u8)dst;
} else {
u32 dc = palette[dst];
u8 dr = dc & 0xFF;
u8 dg = (dc >> 8) & 0xFF;
u8 db = (dc >> 16) & 0xFF;
u8 blend_r = (u8)((sr + dr) / 2);
u8 blend_g = (u8)((sg + dg) / 2);
u8 blend_b = (u8)((sb + db) / 2);
result_idx = find_closest_color(palette, blend_r, blend_g, blend_b);
}
cm->table[row * 256 + dst] = result_idx;
}
}
}
void pxl8_colormap_generate(pxl8_colormap* cm, const u32* palette) {
if (!cm || !palette) return;
for (u32 light = 0; light < PXL8_LIGHT_COLORS; light++) {
generate_light_table(cm, palette, (pxl8_light_color)light);
}
generate_blend_table(cm, palette);
}

73
src/gfx/pxl8_colormap.h Normal file
View file

@ -0,0 +1,73 @@
#pragma once
#include "pxl8_dither.h"
#include "pxl8_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#define PXL8_LIGHT_COLORS 8
#define PXL8_LIGHT_LEVELS 8
#define PXL8_LIGHT_ROWS (PXL8_LIGHT_COLORS * PXL8_LIGHT_LEVELS)
#define PXL8_BLEND_ROWS 256
#define PXL8_COLORMAP_ROWS (PXL8_LIGHT_ROWS + PXL8_BLEND_ROWS)
#define PXL8_COLORMAP_SIZE (256 * PXL8_COLORMAP_ROWS)
#define PXL8_FULLBRIGHT_START 240
#define PXL8_TRANSPARENT 0
#define PXL8_DYNAMIC_RANGE_START 144
#define PXL8_DYNAMIC_RANGE_COUNT 16
typedef enum {
PXL8_LIGHT_WHITE = 0,
PXL8_LIGHT_RED = 1,
PXL8_LIGHT_ORANGE = 2,
PXL8_LIGHT_YELLOW = 3,
PXL8_LIGHT_GREEN = 4,
PXL8_LIGHT_CYAN = 5,
PXL8_LIGHT_BLUE = 6,
PXL8_LIGHT_PURPLE = 7,
} pxl8_light_color;
typedef struct {
u8 r, g, b;
} pxl8_rgb;
static const pxl8_rgb pxl8_light_colors[PXL8_LIGHT_COLORS] = {
{255, 255, 255},
{255, 64, 64},
{255, 160, 64},
{255, 255, 64},
{64, 255, 64},
{64, 255, 255},
{64, 64, 255},
{255, 64, 255},
};
typedef struct {
u8 table[PXL8_COLORMAP_SIZE];
} pxl8_colormap;
void pxl8_colormap_generate(pxl8_colormap* cm, const u32* palette);
void pxl8_set_colormap(pxl8_colormap* cm, const u8* data, u32 size);
static inline u8 pxl8_colormap_lookup(const pxl8_colormap* cm, u8 pal_idx, pxl8_light_color light_color, u8 intensity) {
u32 light_row = ((u32)light_color << 3) + (intensity >> 5);
return cm->table[(light_row << 8) + pal_idx];
}
static inline u8 pxl8_colormap_lookup_dithered(const pxl8_colormap* cm, u8 pal_idx, pxl8_light_color light_color, u8 intensity, u32 x, u32 y) {
u8 dithered = pxl8_dither_light(intensity, x, y);
u32 light_row = ((u32)light_color << 3) + (dithered >> 5);
return cm->table[(light_row << 8) + pal_idx];
}
static inline u8 pxl8_colormap_blend(const pxl8_colormap* cm, u8 src, u8 dst) {
u32 blend_row = PXL8_LIGHT_ROWS + src;
return cm->table[(blend_row << 8) + dst];
}
#ifdef __cplusplus
}
#endif

View file

@ -1,18 +1,116 @@
#include "pxl8_cpu.h" #include "pxl8_cpu.h"
#include <math.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "pxl8_simd.h"
struct pxl8_cpu_render_target { 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 {
@ -20,9 +118,12 @@ struct pxl8_cpu_backend {
pxl8_cpu_render_target* target_stack[PXL8_MAX_TARGET_STACK]; pxl8_cpu_render_target* target_stack[PXL8_MAX_TARGET_STACK];
u32 target_stack_depth; u32 target_stack_depth;
const pxl8_colormap* colormap; const pxl8_colormap* colormap;
const pxl8_palette_cube* palette_cube;
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);
} }
@ -120,11 +231,12 @@ void pxl8_cpu_clear(pxl8_cpu_backend* cpu, u8 color) {
void pxl8_cpu_clear_depth(pxl8_cpu_backend* cpu) { 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;
u32 count = render_target->width * render_target->height;
if (render_target->zbuffer) { if (render_target->zbuffer) {
memset(render_target->zbuffer, 0x7F, render_target->width * render_target->height * sizeof(f32)); memset(render_target->zbuffer, 0xFF, count * sizeof(u16));
} }
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, count * sizeof(u32));
} }
} }
@ -132,6 +244,10 @@ void pxl8_cpu_set_colormap(pxl8_cpu_backend* cpu, const pxl8_colormap* cm) {
if (cpu) cpu->colormap = cm; if (cpu) cpu->colormap = cm;
} }
void pxl8_cpu_set_palette_cube(pxl8_cpu_backend* cpu, const pxl8_palette_cube* cube) {
if (cpu) cpu->palette_cube = cube;
}
void pxl8_cpu_set_palette(pxl8_cpu_backend* cpu, const u32* palette) { void pxl8_cpu_set_palette(pxl8_cpu_backend* cpu, const u32* palette) {
if (cpu) cpu->palette = palette; if (cpu) cpu->palette = palette;
} }
@ -275,8 +391,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 +445,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 +501,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 +574,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 +595,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 +681,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;
@ -536,6 +701,22 @@ static void rasterize_triangle_opaque(
f32 v_end = (vw + dvw * steps) * pw_end; f32 v_end = (vw + dvw * steps) * pw_end;
f32 l_start = lw * pw_start; f32 l_start = lw * pw_start;
f32 l_end = (lw + d_lw * steps) * pw_end; f32 l_end = (lw + d_lw * steps) * pw_end;
f32 fog_density = cpu->frame.uniforms.fog_density;
if (fog_density > 0.0f) {
f32 z_end_fog = z + dz * steps;
f32 t_start = (z + 1.0f) * 0.5f;
f32 t_end = (z_end_fog + 1.0f) * 0.5f;
if (t_start < 0.0f) t_start = 0.0f;
if (t_end < 0.0f) t_end = 0.0f;
f32 fog_start = t_start * t_start * fog_density;
f32 fog_end = t_end * t_end * fog_density;
if (fog_start > 1.0f) fog_start = 1.0f;
if (fog_end > 1.0f) fog_end = 1.0f;
l_start *= (1.0f - fog_start);
l_end *= (1.0f - fog_end);
}
if (l_start > 255.0f) l_start = 255.0f; if (l_start > 255.0f) l_start = 255.0f;
if (l_end > 255.0f) l_end = 255.0f; if (l_end > 255.0f) l_end = 255.0f;
@ -553,7 +734,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;
@ -563,9 +745,12 @@ static void rasterize_triangle_opaque(
if (dither) { if (dither) {
light = pxl8_dither_light(light, (u32)px, (u32)y); light = pxl8_dither_light(light, (u32)px, (u32)y);
} }
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, PXL8_LIGHT_WHITE, 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 +772,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 +786,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 +826,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 +860,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 +946,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;
@ -757,6 +966,22 @@ static void rasterize_triangle_alpha(
f32 v_end = (vw + dvw * steps) * pw_end; f32 v_end = (vw + dvw * steps) * pw_end;
f32 l_start = lw * pw_start; f32 l_start = lw * pw_start;
f32 l_end = (lw + d_lw * steps) * pw_end; f32 l_end = (lw + d_lw * steps) * pw_end;
f32 fog_density = cpu->frame.uniforms.fog_density;
if (fog_density > 0.0f) {
f32 z_end_fog = z + dz * steps;
f32 t_start = (z + 1.0f) * 0.5f;
f32 t_end = (z_end_fog + 1.0f) * 0.5f;
if (t_start < 0.0f) t_start = 0.0f;
if (t_end < 0.0f) t_end = 0.0f;
f32 fog_start = t_start * t_start * fog_density;
f32 fog_end = t_end * t_end * fog_density;
if (fog_start > 1.0f) fog_start = 1.0f;
if (fog_end > 1.0f) fog_end = 1.0f;
l_start *= (1.0f - fog_start);
l_end *= (1.0f - fog_end);
}
if (l_start > 255.0f) l_start = 255.0f; if (l_start > 255.0f) l_start = 255.0f;
if (l_end > 255.0f) l_end = 255.0f; if (l_end > 255.0f) l_end = 255.0f;
@ -783,11 +1008,15 @@ static void rasterize_triangle_alpha(
if (dither) { if (dither) {
light = pxl8_dither_light(light, (u32)px, (u32)y); light = pxl8_dither_light(light, (u32)px, (u32)y);
} }
u8 src_idx = cpu->colormap ? pxl8_colormap_lookup(cpu->colormap, tex_idx, light) : tex_idx; u8 src_idx = cpu->colormap ? pxl8_colormap_lookup(cpu->colormap, tex_idx, PXL8_LIGHT_WHITE, light) : tex_idx;
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;
}
} }
} }
@ -807,11 +1036,43 @@ static void rasterize_triangle_alpha(
} }
} }
static void rasterize_triangle_wireframe(
pxl8_cpu_backend* cpu,
const vertex_output* vo0, const vertex_output* vo1, const vertex_output* vo2,
u8 color, bool double_sided
) {
pxl8_cpu_render_target* render_target = cpu->current_target;
f32 hw = (f32)render_target->width * 0.5f;
f32 hh = (f32)render_target->height * 0.5f;
i32 x0 = (i32)(hw + vo0->clip_pos.x / vo0->clip_pos.w * hw);
i32 y0 = (i32)(hh - vo0->clip_pos.y / vo0->clip_pos.w * hh);
i32 x1 = (i32)(hw + vo1->clip_pos.x / vo1->clip_pos.w * hw);
i32 y1 = (i32)(hh - vo1->clip_pos.y / vo1->clip_pos.w * hh);
i32 x2 = (i32)(hw + vo2->clip_pos.x / vo2->clip_pos.w * hw);
i32 y2 = (i32)(hh - vo2->clip_pos.y / vo2->clip_pos.w * hh);
if (!double_sided) {
i32 cross = (x1 - x0) * (y2 - y0) - (y1 - y0) * (x2 - x0);
if (cross >= 0) return;
}
pxl8_cpu_draw_line_2d(cpu, x0, y0, x1, y1, color);
pxl8_cpu_draw_line_2d(cpu, x1, y1, x2, y2, color);
pxl8_cpu_draw_line_2d(cpu, x2, y2, x0, y0, color);
}
static void dispatch_triangle( static void dispatch_triangle(
pxl8_cpu_backend* cpu, pxl8_cpu_backend* cpu,
const vertex_output* vo0, const vertex_output* vo1, const vertex_output* vo2, const vertex_output* vo0, const vertex_output* vo1, const vertex_output* vo2,
const pxl8_atlas* textures, const pxl8_material* material const pxl8_atlas* textures, const pxl8_gfx_material* material
) { ) {
if (material->wireframe) {
u8 color = (material->texture_id > 0) ? (u8)material->texture_id : 15;
rasterize_triangle_wireframe(cpu, vo0, vo1, vo2, color, material->double_sided);
return;
}
pxl8_cpu_render_target* render_target = cpu->current_target; pxl8_cpu_render_target* render_target = cpu->current_target;
tri_setup setup; tri_setup setup;
if (!setup_triangle(&setup, vo0, vo1, vo2, render_target->width, render_target->height, material->double_sided)) { if (!setup_triangle(&setup, vo0, vo1, vo2, render_target->width, render_target->height, material->double_sided)) {
@ -824,7 +1085,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 +1094,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_gfx_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 +1124,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 +1143,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 +1246,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);
@ -1021,10 +1303,12 @@ void pxl8_cpu_blit(pxl8_cpu_backend* cpu, pxl8_cpu_render_target* src, i32 x, i3
for (i32 row = 0; row < copy_h; row++) { for (i32 row = 0; row < copy_h; row++) {
u8* src_row = src->framebuffer + (src_y0 + row) * src->width + src_x0; u8* src_row = src->framebuffer + (src_y0 + row) * src->width + src_x0;
u8* dst_row = dst->framebuffer + (dst_y0 + row) * dst->width + dst_x0; u8* dst_row = dst->framebuffer + (dst_y0 + row) * dst->width + dst_x0;
u32* light_row = dst->light_accum ? dst->light_accum + (dst_y0 + row) * dst->width + dst_x0 : NULL;
for (i32 col = 0; col < copy_w; col++) { for (i32 col = 0; col < copy_w; col++) {
u8 pixel = src_row[col]; u8 pixel = src_row[col];
if (pixel != transparent_idx) { if (pixel != transparent_idx) {
dst_row[col] = pixel; dst_row[col] = pixel;
if (light_row) light_row[col] = 0;
} }
} }
} }
@ -1073,3 +1357,194 @@ 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
) {
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);
if (light_level < 8) continue;
u8 shaded = pxl8_colormap_lookup(cpu->colormap, base_color, PXL8_LIGHT_WHITE, light_level);
if (cpu->palette_cube) {
u8 sr, sg, sb, dr, dg, db;
pxl8_palette_cube_get_rgb(cpu->palette_cube, shaded, &sr, &sg, &sb);
pxl8_palette_cube_get_rgb(cpu->palette_cube, pixels[idx], &dr, &dg, &db);
u32 r = (u32)sr + (u32)dr;
u32 g = (u32)sg + (u32)dg;
u32 b = (u32)sb + (u32)db;
if (r > 255) r = 255;
if (g > 255) g = 255;
if (b > 255) b = 255;
pixels[idx] = pxl8_palette_cube_lookup(cpu->palette_cube, (u8)r, (u8)g, (u8)b);
} else {
pixels[idx] = shaded;
}
if (light_accum) {
light_accum[idx] = 0;
}
}
}
}
}
void pxl8_cpu_resolve(pxl8_cpu_backend* cpu) {
if (!cpu || !cpu->palette || cpu->target_stack_depth == 0) return;
pxl8_cpu_render_target* target = cpu->target_stack[0];
if (!target || !target->framebuffer) return;
u32 pixel_count = target->width * target->height;
const u8* pixels = target->framebuffer;
const u32* light_accum = target->light_accum;
const u32* palette = cpu->palette;
u32* output = cpu->output;
for (u32 i = 0; i < pixel_count; i++) {
u32 base = palette[pixels[i]];
if (light_accum && light_accum[i] != 0) {
u32 light_color = light_accum[i];
u32 tint_alpha = (light_color >> 24) & 0xFF;
if (tint_alpha > 0) {
u32 lr = light_color & 0xFF;
u32 lg = (light_color >> 8) & 0xFF;
u32 lb = (light_color >> 16) & 0xFF;
u32 br = base & 0xFF;
u32 bg = (base >> 8) & 0xFF;
u32 bb = (base >> 16) & 0xFF;
u32 ba = (base >> 24) & 0xFF;
u32 t = (tint_alpha * tint_alpha) / 255;
u32 inv_t = 255 - t;
u32 lr_norm = (lr * 255) / 240;
u32 lg_norm = (lg * 255) / 240;
u32 lb_norm = (lb * 255) / 220;
u32 or = ((br * inv_t + (br * lr_norm / 255) * t) / 255);
u32 og = ((bg * inv_t + (bg * lg_norm / 255) * t) / 255);
u32 ob = ((bb * inv_t + (bb * lb_norm / 255) * t) / 255);
if (or > 255) or = 255;
if (og > 255) og = 255;
if (ob > 255) ob = 255;
output[i] = or | (og << 8) | (ob << 16) | (ba << 24);
continue;
}
}
output[i] = base;
}
for (u32 t = 1; t < cpu->target_stack_depth; t++) {
pxl8_cpu_render_target* overlay = cpu->target_stack[t];
if (!overlay || !overlay->framebuffer) continue;
const u8* overlay_pixels = overlay->framebuffer;
for (u32 i = 0; i < pixel_count; i++) {
u8 idx = overlay_pixels[i];
if (idx != 0) {
output[i] = palette[idx];
}
}
}
}

View file

@ -2,8 +2,12 @@
#include "pxl8_atlas.h" #include "pxl8_atlas.h"
#include "pxl8_colormap.h" #include "pxl8_colormap.h"
#include "pxl8_gfx.h"
#include "pxl8_gfx3d.h"
#include "pxl8_lightmap.h"
#include "pxl8_math.h" #include "pxl8_math.h"
#include "pxl8_mesh.h" #include "pxl8_mesh.h"
#include "pxl8_palette.h"
#include "pxl8_types.h" #include "pxl8_types.h"
#ifdef __cplusplus #ifdef __cplusplus
@ -20,19 +24,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);
@ -44,6 +35,10 @@ void pxl8_cpu_clear_depth(pxl8_cpu_backend* cpu);
void pxl8_cpu_set_colormap(pxl8_cpu_backend* cpu, const pxl8_colormap* cm); void pxl8_cpu_set_colormap(pxl8_cpu_backend* cpu, const pxl8_colormap* cm);
void pxl8_cpu_set_palette(pxl8_cpu_backend* cpu, const u32* palette); void pxl8_cpu_set_palette(pxl8_cpu_backend* cpu, const u32* palette);
void pxl8_cpu_set_palette_cube(pxl8_cpu_backend* cpu, const pxl8_palette_cube* cube);
u32 pxl8_cpu_add_lightmap(pxl8_cpu_backend* cpu, pxl8_lightmap* lm);
pxl8_lightmap* pxl8_cpu_get_lightmap(pxl8_cpu_backend* cpu, u32 id);
void pxl8_cpu_draw_pixel(pxl8_cpu_backend* cpu, i32 x, i32 y, u8 color); void pxl8_cpu_draw_pixel(pxl8_cpu_backend* cpu, i32 x, i32 y, u8 color);
u8 pxl8_cpu_get_pixel(pxl8_cpu_backend* cpu, i32 x, i32 y); u8 pxl8_cpu_get_pixel(pxl8_cpu_backend* cpu, i32 x, i32 y);
@ -57,8 +52,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_gfx_material* material,
const pxl8_atlas* textures const pxl8_atlas* textures
); );
@ -70,9 +65,18 @@ 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
);
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 +92,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
} }

View file

@ -34,6 +34,7 @@ struct pxl8_gfx {
const pxl8_hal* hal; const pxl8_hal* hal;
bool initialized; bool initialized;
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;
@ -74,10 +75,14 @@ i32 pxl8_gfx_get_width(const pxl8_gfx* gfx) {
return gfx ? gfx->framebuffer_width : 0; return gfx ? gfx->framebuffer_width : 0;
} }
pxl8_palette* pxl8_gfx_get_palette(pxl8_gfx* gfx) { pxl8_palette* pxl8_gfx_palette(pxl8_gfx* gfx) {
return gfx ? gfx->palette : NULL; return gfx ? gfx->palette : NULL;
} }
pxl8_colormap* pxl8_gfx_colormap(pxl8_gfx* gfx) {
return gfx ? gfx->colormap : NULL;
}
u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color) { u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color) {
if (!gfx || !gfx->palette) return 0; if (!gfx || !gfx->palette) return 0;
if (color <= 0xFFFFFF) color = (color << 8) | 0xFF; if (color <= 0xFFFFFF) color = (color << 8) | 0xFF;
@ -87,37 +92,24 @@ u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color) {
return pxl8_palette_find_closest(gfx->palette, r, g, b); return pxl8_palette_find_closest(gfx->palette, r, g, b);
} }
void pxl8_gfx_set_palette(pxl8_gfx* gfx, pxl8_palette* pal) { void pxl8_gfx_set_palette_colors(pxl8_gfx* gfx, const u32* colors, u16 count) {
if (!gfx) return; if (!gfx || !gfx->palette || !colors) return;
if (gfx->palette) { pxl8_set_palette(gfx->palette, colors, count);
pxl8_palette_destroy(gfx->palette); if (gfx->pixel_mode != PXL8_PIXEL_HICOLOR && gfx->backend.type == PXL8_GFX_BACKEND_CPU) {
} pxl8_cpu_set_palette(gfx->backend.cpu, pxl8_palette_colors(gfx->palette));
gfx->palette = pal;
if (gfx->palette && gfx->pixel_mode != PXL8_PIXEL_HICOLOR) {
u32* colors = pxl8_palette_colors(gfx->palette);
if (gfx->colormap) {
pxl8_colormap_generate(gfx->colormap, colors, NULL);
}
if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) {
pxl8_cpu_set_palette(gfx->backend.cpu, colors);
}
} }
} }
i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath) { i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath) {
if (!gfx || !filepath) return -1; if (!gfx || !gfx->palette || !filepath) return -1;
pxl8_palette* pal = gfx->palette; pxl8_result result = pxl8_palette_load_ase(gfx->palette, filepath);
if (!pal) return -1;
pxl8_result result = pxl8_palette_load_ase(pal, filepath);
if (result != PXL8_OK) return (i32)result; if (result != PXL8_OK) return (i32)result;
if (gfx->pixel_mode != PXL8_PIXEL_HICOLOR) { if (gfx->pixel_mode != PXL8_PIXEL_HICOLOR) {
u32* colors = pxl8_palette_colors(pal);
if (gfx->colormap) { if (gfx->colormap) {
pxl8_colormap_generate(gfx->colormap, colors, NULL); pxl8_colormap_generate(gfx->colormap, pxl8_palette_colors(gfx->palette));
} }
if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) { if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) {
pxl8_cpu_set_palette(gfx->backend.cpu, colors); pxl8_cpu_set_palette(gfx->backend.cpu, pxl8_palette_colors(gfx->palette));
} }
} }
return 0; return 0;
@ -188,6 +180,7 @@ void pxl8_gfx_destroy(pxl8_gfx* gfx) {
pxl8_cpu_destroy(gfx->backend.cpu); pxl8_cpu_destroy(gfx->backend.cpu);
} }
free(gfx->colormap); free(gfx->colormap);
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 +302,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 +446,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 +455,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 +493,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;
} }
} }
} }
@ -497,6 +509,7 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo
if (!gfx || !gfx->atlas) return; if (!gfx || !gfx->atlas) 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;
@ -505,6 +518,7 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo
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;
@ -541,6 +555,11 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo
if (is_1to1_scale && is_unclipped && !is_flipped) { if (is_1to1_scale && is_unclipped && !is_flipped) {
const u8* sprite_data = atlas_pixels + entry->y * atlas_width + entry->x; const u8* sprite_data = atlas_pixels + entry->y * atlas_width + entry->x;
pxl8_blit_indexed(framebuffer, fb_width, sprite_data, atlas_width, x, y, w, h); pxl8_blit_indexed(framebuffer, fb_width, sprite_data, atlas_width, x, y, w, h);
if (light_accum) {
for (i32 py = 0; py < h; py++) {
memset(light_accum + (y + py) * fb_width + x, 0, w * sizeof(u32));
}
}
} else { } else {
for (i32 py = 0; py < draw_height; py++) { for (i32 py = 0; py < draw_height; py++) {
for (i32 px = 0; px < draw_width; px++) { for (i32 px = 0; px < draw_width; px++) {
@ -554,6 +573,7 @@ void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bo
i32 dest_idx = (dest_y + py) * fb_width + (dest_x + px); i32 dest_idx = (dest_y + py) * fb_width + (dest_x + px);
framebuffer[dest_idx] = pxl8_blend_indexed(atlas_pixels[src_idx], framebuffer[dest_idx]); framebuffer[dest_idx] = pxl8_blend_indexed(atlas_pixels[src_idx], framebuffer[dest_idx]);
if (light_accum) light_accum[dest_idx] = 0;
} }
} }
} }
@ -568,30 +588,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 +661,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_gfx_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 +726,67 @@ void pxl8_gfx_pop_target(pxl8_gfx* gfx) {
break; break;
} }
} }
void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx) {
if (!gfx || !gfx->palette) return;
if (!gfx->palette_cube) {
gfx->palette_cube = pxl8_palette_cube_create(gfx->palette);
if (gfx->backend.cpu) {
pxl8_cpu_set_palette_cube(gfx->backend.cpu, gfx->palette_cube);
}
}
}
void pxl8_gfx_blend_tables_update(pxl8_gfx* gfx) {
if (!gfx || !gfx->palette) return;
pxl8_gfx_ensure_blend_tables(gfx);
if (gfx->palette_cube) {
pxl8_palette_cube_rebuild(gfx->palette_cube, gfx->palette);
if (gfx->backend.cpu) {
pxl8_cpu_set_palette_cube(gfx->backend.cpu, gfx->palette_cube);
}
}
}
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);
}
}
u8 pxl8_gfx_find_closest_color(pxl8_gfx* gfx, u8 r, u8 g, u8 b) {
if (!gfx || !gfx->palette) return 0;
return pxl8_palette_find_closest(gfx->palette, r, g, b);
}
u8 pxl8_gfx_ui_color(pxl8_gfx* gfx, u8 index) {
if (!gfx || !gfx->palette || index >= PXL8_UI_PALETTE_SIZE) return 0;
u32 abgr = pxl8_ui_palette[index];
u8 r = (abgr >> 0) & 0xFF;
u8 g = (abgr >> 8) & 0xFF;
u8 b = (abgr >> 16) & 0xFF;
return pxl8_palette_find_closest(gfx->palette, r, g, b);
}
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: {
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);
break;
case PXL8_GFX_BACKEND_GPU:
break;
}
break;
}
}
}

110
src/gfx/pxl8_gfx.h Normal file
View file

@ -0,0 +1,110 @@
#pragma once
#include "pxl8_gfx2d.h"
#include "pxl8_gfx3d.h"
#include "pxl8_hal.h"
#include "pxl8_colormap.h"
#include "pxl8_palette.h"
#include "pxl8_types.h"
#include "pxl8_gui_palette.h"
typedef struct pxl8_gfx pxl8_gfx;
typedef enum pxl8_gfx_effect {
PXL8_GFX_EFFECT_GLOWS = 0,
} pxl8_gfx_effect;
#define PXL8_MAX_GLOWS 256
typedef enum pxl8_glow_shape {
PXL8_GLOW_CIRCLE = 0,
PXL8_GLOW_DIAMOND = 1,
PXL8_GLOW_SHAFT = 2,
} pxl8_glow_shape;
typedef struct pxl8_glow_source {
u8 color;
u16 depth;
u8 height;
u16 intensity;
u8 radius;
pxl8_glow_shape shape;
i16 x;
i16 y;
} pxl8_glow_source;
static inline pxl8_glow_source pxl8_glow_create(i32 x, i32 y, u8 radius, u16 intensity, u8 color) {
return (pxl8_glow_source){
.color = color,
.depth = 0xFFFF,
.height = 0,
.intensity = intensity,
.radius = radius,
.shape = PXL8_GLOW_CIRCLE,
.x = (i16)x,
.y = (i16)y,
};
}
static inline pxl8_glow_source pxl8_glow_with_depth(pxl8_glow_source g, u16 depth) {
g.depth = depth;
return g;
}
static inline pxl8_glow_source pxl8_glow_with_shape(pxl8_glow_source g, pxl8_glow_shape shape) {
g.shape = shape;
return g;
}
static inline pxl8_glow_source pxl8_glow_with_height(pxl8_glow_source g, u8 height) {
g.height = height;
return g;
}
#ifdef __cplusplus
extern "C" {
#endif
pxl8_gfx* pxl8_gfx_create(const pxl8_hal* hal, void* platform_data, pxl8_pixel_mode mode, pxl8_resolution resolution);
void pxl8_gfx_destroy(pxl8_gfx* gfx);
void pxl8_gfx_present(pxl8_gfx* gfx);
void pxl8_gfx_update(pxl8_gfx* gfx, f32 dt);
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx);
u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color);
pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx);
u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx);
u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx);
i32 pxl8_gfx_get_height(const pxl8_gfx* gfx);
pxl8_palette* pxl8_gfx_palette(pxl8_gfx* gfx);
pxl8_colormap* pxl8_gfx_colormap(pxl8_gfx* gfx);
pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx);
i32 pxl8_gfx_get_width(const pxl8_gfx* gfx);
i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath);
void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom);
void pxl8_gfx_set_palette_colors(pxl8_gfx* gfx, const u32* colors, u16 count);
void pxl8_gfx_set_viewport(pxl8_gfx* gfx, pxl8_viewport vp);
pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height);
void pxl8_gfx_clear_textures(pxl8_gfx* gfx);
pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width, u32 height);
pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx);
pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path);
bool pxl8_gfx_push_target(pxl8_gfx* gfx);
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);
void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx);
u8 pxl8_gfx_find_closest_color(pxl8_gfx* gfx, u8 r, u8 g, u8 b);
u8 pxl8_gfx_ui_color(pxl8_gfx* gfx, u8 index);
#ifdef __cplusplus
}
#endif

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

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

113
src/gfx/pxl8_lightmap.c Normal file
View file

@ -0,0 +1,113 @@
#include "pxl8_lightmap.h"
#include <stdlib.h>
#include <string.h>
pxl8_lightmap* pxl8_lightmap_create(u32 width, u32 height, u32 scale) {
pxl8_lightmap* lm = calloc(1, sizeof(pxl8_lightmap));
if (!lm) return NULL;
lm->width = width;
lm->height = height;
lm->scale = scale;
lm->data = calloc(width * height * 3, sizeof(u8));
if (!lm->data) {
free(lm);
return NULL;
}
pxl8_lightmap_clear(lm, PXL8_LIGHTMAP_NEUTRAL, PXL8_LIGHTMAP_NEUTRAL, PXL8_LIGHTMAP_NEUTRAL);
return lm;
}
void pxl8_lightmap_destroy(pxl8_lightmap* lm) {
if (!lm) return;
free(lm->data);
free(lm);
}
void pxl8_lightmap_clear(pxl8_lightmap* lm, u8 r, u8 g, u8 b) {
if (!lm || !lm->data) return;
u32 count = lm->width * lm->height;
for (u32 i = 0; i < count; i++) {
lm->data[i * 3 + 0] = r;
lm->data[i * 3 + 1] = g;
lm->data[i * 3 + 2] = b;
}
}
void pxl8_lightmap_set(pxl8_lightmap* lm, u32 x, u32 y, u8 r, u8 g, u8 b) {
if (!lm || !lm->data || x >= lm->width || y >= lm->height) return;
u32 idx = (y * lm->width + x) * 3;
lm->data[idx + 0] = r;
lm->data[idx + 1] = g;
lm->data[idx + 2] = b;
}
void pxl8_lightmap_get(const pxl8_lightmap* lm, u32 x, u32 y, u8* r, u8* g, u8* b) {
if (!lm || !lm->data || x >= lm->width || y >= lm->height) {
*r = *g = *b = PXL8_LIGHTMAP_NEUTRAL;
return;
}
u32 idx = (y * lm->width + x) * 3;
*r = lm->data[idx + 0];
*g = lm->data[idx + 1];
*b = lm->data[idx + 2];
}
void pxl8_lightmap_add_point(
pxl8_lightmap* lm,
f32 lx, f32 ly,
u8 r, u8 g, u8 b,
f32 radius,
f32 intensity
) {
if (!lm || !lm->data || radius <= 0.0f) return;
f32 radius_sq = radius * radius;
f32 inv_radius_sq = 1.0f / radius_sq;
i32 cx = (i32)(lx * (f32)lm->width);
i32 cy = (i32)(ly * (f32)lm->height);
i32 rad_pixels = (i32)(radius * (f32)lm->width) + 1;
i32 x0 = cx - rad_pixels;
i32 y0 = cy - rad_pixels;
i32 x1 = cx + rad_pixels;
i32 y1 = cy + rad_pixels;
if (x0 < 0) x0 = 0;
if (y0 < 0) y0 = 0;
if (x1 >= (i32)lm->width) x1 = (i32)lm->width - 1;
if (y1 >= (i32)lm->height) y1 = (i32)lm->height - 1;
f32 scale_x = 1.0f / (f32)lm->width;
f32 scale_y = 1.0f / (f32)lm->height;
for (i32 y = y0; y <= y1; y++) {
f32 dy = ((f32)y * scale_y) - ly;
for (i32 x = x0; x <= x1; x++) {
f32 dx = ((f32)x * scale_x) - lx;
f32 dist_sq = dx * dx + dy * dy;
if (dist_sq >= radius_sq) continue;
f32 falloff = 1.0f - dist_sq * inv_radius_sq;
f32 contrib = falloff * falloff * intensity;
u32 idx = ((u32)y * lm->width + (u32)x) * 3;
i32 nr = (i32)lm->data[idx + 0] + (i32)((f32)(r - 128) * contrib);
i32 ng = (i32)lm->data[idx + 1] + (i32)((f32)(g - 128) * contrib);
i32 nb = (i32)lm->data[idx + 2] + (i32)((f32)(b - 128) * contrib);
if (nr < 0) nr = 0; if (nr > 255) nr = 255;
if (ng < 0) ng = 0; if (ng > 255) ng = 255;
if (nb < 0) nb = 0; if (nb > 255) nb = 255;
lm->data[idx + 0] = (u8)nr;
lm->data[idx + 1] = (u8)ng;
lm->data[idx + 2] = (u8)nb;
}
}
}

48
src/gfx/pxl8_lightmap.h Normal file
View file

@ -0,0 +1,48 @@
#pragma once
#include "pxl8_types.h"
#ifdef __cplusplus
extern "C" {
#endif
#define PXL8_LIGHTMAP_MAX 16
#define PXL8_LIGHTMAP_NEUTRAL 128
typedef struct pxl8_lightmap {
u8* data;
u32 width;
u32 height;
u32 scale;
} pxl8_lightmap;
pxl8_lightmap* pxl8_lightmap_create(u32 width, u32 height, u32 scale);
void pxl8_lightmap_destroy(pxl8_lightmap* lm);
void pxl8_lightmap_clear(pxl8_lightmap* lm, u8 r, u8 g, u8 b);
void pxl8_lightmap_set(pxl8_lightmap* lm, u32 x, u32 y, u8 r, u8 g, u8 b);
void pxl8_lightmap_get(const pxl8_lightmap* lm, u32 x, u32 y, u8* r, u8* g, u8* b);
void pxl8_lightmap_add_point(
pxl8_lightmap* lm,
f32 lx, f32 ly,
u8 r, u8 g, u8 b,
f32 radius,
f32 intensity
);
static inline void pxl8_lightmap_sample(
const pxl8_lightmap* lm,
f32 u, f32 v,
u8* r, u8* g, u8* b
) {
i32 x = (i32)(u * (f32)lm->width) & (i32)(lm->width - 1);
i32 y = (i32)(v * (f32)lm->height) & (i32)(lm->height - 1);
u32 idx = ((u32)y * lm->width + (u32)x) * 3;
*r = lm->data[idx + 0];
*g = lm->data[idx + 1];
*b = lm->data[idx + 2];
}
#ifdef __cplusplus
}
#endif

View file

@ -17,8 +17,9 @@ typedef enum pxl8_blend_mode {
PXL8_BLEND_ADDITIVE, PXL8_BLEND_ADDITIVE,
} pxl8_blend_mode; } pxl8_blend_mode;
typedef struct pxl8_material { typedef struct pxl8_gfx_material {
u32 texture_id; u32 texture_id;
u32 lightmap_id;
u8 alpha; u8 alpha;
u8 blend_mode; u8 blend_mode;
bool dither; bool dither;
@ -26,13 +27,15 @@ typedef struct pxl8_material {
bool dynamic_lighting; bool dynamic_lighting;
bool per_pixel; bool per_pixel;
bool vertex_color_passthrough; bool vertex_color_passthrough;
bool wireframe;
f32 emissive_intensity; f32 emissive_intensity;
} pxl8_material; } pxl8_gfx_material;
typedef struct pxl8_vertex { typedef struct pxl8_vertex {
pxl8_vec3 position; pxl8_vec3 position;
pxl8_vec3 normal; pxl8_vec3 normal;
f32 u, v; f32 u, v;
f32 lu, lv;
u8 color; u8 color;
u8 light; u8 light;
u8 _pad[2]; u8 _pad[2];
@ -62,62 +65,6 @@ 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) {
return (pxl8_material){
.texture_id = texture_id,
.alpha = 255,
.blend_mode = PXL8_BLEND_OPAQUE,
.dither = true,
.double_sided = false,
.dynamic_lighting = false,
.per_pixel = false,
.vertex_color_passthrough = false,
.emissive_intensity = 0.0f,
};
}
static inline pxl8_material pxl8_material_with_alpha(pxl8_material m, u8 alpha) {
m.alpha = alpha;
if (alpha < 255 && m.blend_mode == PXL8_BLEND_OPAQUE) {
m.blend_mode = PXL8_BLEND_ALPHA;
}
return m;
}
static inline pxl8_material pxl8_material_with_blend(pxl8_material m, pxl8_blend_mode mode) {
m.blend_mode = mode;
return m;
}
static inline pxl8_material pxl8_material_with_double_sided(pxl8_material m) {
m.double_sided = true;
return m;
}
static inline pxl8_material pxl8_material_with_emissive(pxl8_material m, f32 intensity) {
m.emissive_intensity = intensity;
return m;
}
static inline pxl8_material pxl8_material_with_lighting(pxl8_material m) {
m.dynamic_lighting = true;
return m;
}
static inline pxl8_material pxl8_material_with_no_dither(pxl8_material m) {
m.dither = false;
return m;
}
static inline pxl8_material pxl8_material_with_passthrough(pxl8_material m) {
m.vertex_color_passthrough = true;
return m;
}
static inline pxl8_material pxl8_material_with_per_pixel(pxl8_material m) {
m.per_pixel = true;
return m;
}
#ifdef __cplusplus #ifdef __cplusplus
} }

View file

@ -5,10 +5,17 @@
#include "pxl8_ase.h" #include "pxl8_ase.h"
#include "pxl8_color.h" #include "pxl8_color.h"
#include "pxl8_colormap.h"
#include "pxl8_log.h" #include "pxl8_log.h"
#define PXL8_PALETTE_HASH_SIZE 512 #define PXL8_PALETTE_HASH_SIZE 512
struct pxl8_palette_cube {
u8 colors[PXL8_PALETTE_SIZE * 3];
u8 table[PXL8_CUBE_ENTRIES];
u8 stable[PXL8_CUBE_ENTRIES];
};
typedef struct { typedef struct {
u32 color; u32 color;
i16 index; i16 index;
@ -332,10 +339,6 @@ void pxl8_palette_get_rgba(const pxl8_palette* pal, u8 idx, u8* r, u8* g, u8* b,
unpack_rgba(pal->colors[idx], r, g, b, a); unpack_rgba(pal->colors[idx], r, g, b, a);
} }
void pxl8_palette_set(pxl8_palette* pal, u8 idx, u32 color) {
if (pal) pal->colors[idx] = color;
}
void pxl8_palette_set_rgb(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b) { void pxl8_palette_set_rgb(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b) {
if (pal) pal->colors[idx] = pack_rgb(r, g, b); if (pal) pal->colors[idx] = pack_rgb(r, g, b);
} }
@ -344,6 +347,17 @@ void pxl8_palette_set_rgba(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b, u8 a) {
if (pal) pal->colors[idx] = pack_rgba(r, g, b, a); if (pal) pal->colors[idx] = pack_rgba(r, g, b, a);
} }
void pxl8_set_palette(pxl8_palette* pal, const u32* colors, u16 count) {
if (!pal || !colors) return;
for (u16 i = 0; i < count; i++) {
u32 rgb = colors[i];
u8 r = (rgb >> 16) & 0xFF;
u8 g = (rgb >> 8) & 0xFF;
u8 b = rgb & 0xFF;
pal->colors[i] = pack_rgb(r, g, b);
}
}
void pxl8_palette_fill_gradient(pxl8_palette* pal, u8 start, u8 count, u32 from, u32 to) { void pxl8_palette_fill_gradient(pxl8_palette* pal, u8 start, u8 count, u32 from, u32 to) {
if (!pal || count == 0) return; if (!pal || count == 0) return;
@ -449,7 +463,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,
@ -472,3 +486,91 @@ pxl8_cycle_range pxl8_cycle_range_disabled(void) {
}; };
return range; return range;
} }
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];
}

View file

@ -5,8 +5,11 @@
#define PXL8_PALETTE_SIZE 256 #define PXL8_PALETTE_SIZE 256
#define PXL8_MAX_CYCLES 8 #define PXL8_MAX_CYCLES 8
#define PXL8_MAX_CYCLE_LEN 16 #define PXL8_MAX_CYCLE_LEN 16
#define PXL8_CUBE_SIZE 32
#define PXL8_CUBE_ENTRIES (PXL8_CUBE_SIZE * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE)
typedef struct pxl8_palette pxl8_palette; typedef struct pxl8_palette pxl8_palette;
typedef struct pxl8_palette_cube pxl8_palette_cube;
typedef enum pxl8_cycle_mode { typedef enum pxl8_cycle_mode {
PXL8_CYCLE_LOOP, PXL8_CYCLE_LOOP,
@ -49,9 +52,9 @@ u32 pxl8_palette_color(const pxl8_palette* pal, u8 idx);
i32 pxl8_palette_index(const pxl8_palette* pal, u32 color); i32 pxl8_palette_index(const pxl8_palette* pal, u32 color);
void pxl8_palette_get_rgb(const pxl8_palette* pal, u8 idx, u8* r, u8* g, u8* b); void pxl8_palette_get_rgb(const pxl8_palette* pal, u8 idx, u8* r, u8* g, u8* b);
void pxl8_palette_get_rgba(const pxl8_palette* pal, u8 idx, u8* r, u8* g, u8* b, u8* a); void pxl8_palette_get_rgba(const pxl8_palette* pal, u8 idx, u8* r, u8* g, u8* b, u8* a);
void pxl8_palette_set(pxl8_palette* pal, u8 idx, u32 color);
void pxl8_palette_set_rgb(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b); void pxl8_palette_set_rgb(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b);
void pxl8_palette_set_rgba(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b, u8 a); void pxl8_palette_set_rgba(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b, u8 a);
void pxl8_set_palette(pxl8_palette* pal, const u32* colors, u16 count);
void pxl8_palette_fill_gradient(pxl8_palette* pal, u8 start, u8 count, u32 from, u32 to); void pxl8_palette_fill_gradient(pxl8_palette* pal, u8 start, u8 count, u32 from, u32 to);
void pxl8_palette_fill_gradient_rgb(pxl8_palette* pal, u8 start, u8 count, u8 r0, u8 g0, u8 b0, u8 r1, u8 g1, u8 b1); void pxl8_palette_fill_gradient_rgb(pxl8_palette* pal, u8 start, u8 count, u8 r0, u8 g0, u8 b0, u8 r1, u8 g1, u8 b1);
@ -62,9 +65,16 @@ 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);
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 #ifdef __cplusplus
} }
#endif #endif

View file

@ -18,18 +18,20 @@ void pxl8_gui_state_destroy(pxl8_gui_state* state) {
free(state); free(state);
} }
void pxl8_gui_begin_frame(pxl8_gui_state* state) { void pxl8_gui_begin_frame(pxl8_gui_state* state, pxl8_gfx* gfx) {
if (!state) return; if (!state) return;
state->hot_id = 0; state->hot_id = 0;
if (gfx) pxl8_gfx_push_target(gfx);
} }
void pxl8_gui_end_frame(pxl8_gui_state* state) { void pxl8_gui_end_frame(pxl8_gui_state* state, pxl8_gfx* gfx) {
if (!state) return; if (!state) return;
if (!state->cursor_down) { if (!state->cursor_down) {
state->active_id = 0; state->active_id = 0;
} }
state->cursor_clicked = false; state->cursor_clicked = false;
if (gfx) pxl8_gfx_pop_target(gfx);
} }
void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y) { void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y) {
@ -80,16 +82,16 @@ bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y,
i32 offset_y = 0; i32 offset_y = 0;
if (is_active) { if (is_active) {
bg_color = 4; bg_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG3);
border_color = 3; border_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG2);
offset_x = 1; offset_x = 1;
offset_y = 1; offset_y = 1;
} else if (is_hot || cursor_over) { } else if (is_hot || cursor_over) {
bg_color = 4; bg_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG3);
border_color = 8; border_color = pxl8_gfx_ui_color(gfx, PXL8_UI_FG0);
} else { } else {
bg_color = 3; bg_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG2);
border_color = 4; border_color = pxl8_gfx_ui_color(gfx, PXL8_UI_BG3);
} }
pxl8_2d_rect_fill(gfx, x, y, w, h, bg_color); pxl8_2d_rect_fill(gfx, x, y, w, h, bg_color);
@ -98,7 +100,7 @@ bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y,
i32 text_len = (i32)strlen(label); i32 text_len = (i32)strlen(label);
i32 text_x = x + (w / 2) - ((text_len * 8) / 2) + offset_x; i32 text_x = x + (w / 2) - ((text_len * 8) / 2) + offset_x;
i32 text_y = y + (h / 2) - 5 + offset_y; i32 text_y = y + (h / 2) - 5 + offset_y;
pxl8_2d_text(gfx, label, text_x, text_y, 6); pxl8_2d_text(gfx, label, text_x, text_y, pxl8_gfx_ui_color(gfx, PXL8_UI_FG1));
return clicked; return clicked;
} }
@ -106,14 +108,19 @@ bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y,
void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title) { void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title) {
if (!gfx || !title) return; if (!gfx || !title) return;
pxl8_2d_rect_fill(gfx, x, y, w, 28, 1); u8 title_bg = pxl8_gfx_ui_color(gfx, PXL8_UI_BG1);
pxl8_2d_rect_fill(gfx, x, y + 28, w, h - 28, 2); u8 body_bg = pxl8_gfx_ui_color(gfx, PXL8_UI_BG2);
pxl8_2d_rect(gfx, x, y, w, h, 4); u8 border = pxl8_gfx_ui_color(gfx, PXL8_UI_BG3);
pxl8_2d_rect_fill(gfx, x, y + 28, w, 1, 4); u8 title_fg = pxl8_gfx_ui_color(gfx, PXL8_UI_FG0);
pxl8_2d_rect_fill(gfx, x, y, w, 28, title_bg);
pxl8_2d_rect_fill(gfx, x, y + 28, w, h - 28, body_bg);
pxl8_2d_rect(gfx, x, y, w, h, border);
pxl8_2d_rect_fill(gfx, x, y + 28, w, 1, border);
i32 title_x = x + 10; i32 title_x = x + 10;
i32 title_y = y + (28 / 2) - 5; i32 title_y = y + (28 / 2) - 5;
pxl8_2d_text(gfx, title, title_x, title_y, 8); pxl8_2d_text(gfx, title, title_x, title_y, title_fg);
} }
void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color) { void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color) {

View file

@ -22,8 +22,8 @@ void pxl8_gui_state_destroy(pxl8_gui_state* state);
void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y); void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y);
bool pxl8_gui_is_hovering(const pxl8_gui_state* state); bool pxl8_gui_is_hovering(const pxl8_gui_state* state);
void pxl8_gui_begin_frame(pxl8_gui_state* state); void pxl8_gui_begin_frame(pxl8_gui_state* state, pxl8_gfx* gfx);
void pxl8_gui_end_frame(pxl8_gui_state* state); void pxl8_gui_end_frame(pxl8_gui_state* state, pxl8_gfx* gfx);
void pxl8_gui_cursor_down(pxl8_gui_state* state); void pxl8_gui_cursor_down(pxl8_gui_state* state);
void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y); void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y);

View file

@ -0,0 +1,41 @@
#pragma once
#include "pxl8_types.h"
#define PXL8_UI_PALETTE_SIZE 16
#define PXL8_UI_BG0 0
#define PXL8_UI_BG1 1
#define PXL8_UI_BG2 2
#define PXL8_UI_BG3 3
#define PXL8_UI_FG0 4
#define PXL8_UI_FG1 5
#define PXL8_UI_FG2 6
#define PXL8_UI_FG3 7
#define PXL8_UI_RED 8
#define PXL8_UI_GREEN 9
#define PXL8_UI_YELLOW 10
#define PXL8_UI_BLUE 11
#define PXL8_UI_PURPLE 12
#define PXL8_UI_AQUA 13
#define PXL8_UI_ORANGE 14
#define PXL8_UI_GRAY 15
static const u32 pxl8_ui_palette[PXL8_UI_PALETTE_SIZE] = {
0xFF282828,
0xFF3c3836,
0xFF504945,
0xFF665c54,
0xFFc7f1fb,
0xFFb2dbeb,
0xFFa1c4d5,
0xFF93aebd,
0xFF3449fb,
0xFF26bbb8,
0xFF2fbdfa,
0xFF98a583,
0xFF9b86d3,
0xFF7cc08e,
0xFF1980fe,
0xFF928374,
};

View file

@ -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);

View file

@ -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]];
} }
} }

View file

@ -1,11 +1,14 @@
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 math = require("pxl8.math")
local net = require("pxl8.net")
local particles = require("pxl8.particles") local particles = require("pxl8.particles")
local procgen = require("pxl8.procgen")
local sfx = require("pxl8.sfx") local sfx = require("pxl8.sfx")
local tilemap = require("pxl8.tilemap") local tilemap = require("pxl8.tilemap")
local transition = require("pxl8.transition") local transition = require("pxl8.transition")
@ -25,15 +28,20 @@ pxl8.debug = core.debug
pxl8.trace = core.trace pxl8.trace = core.trace
pxl8.quit = core.quit pxl8.quit = core.quit
pxl8.rng_seed = core.rng_seed pxl8.hash32 = math.hash32
pxl8.rng_next = core.rng_next
pxl8.rng_f32 = core.rng_f32 pxl8.rng_f32 = core.rng_f32
pxl8.rng_next = core.rng_next
pxl8.rng_range = core.rng_range pxl8.rng_range = core.rng_range
pxl8.rng_seed = core.rng_seed
pxl8.find_color = core.find_color 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_colormap = core.set_colormap
pxl8.set_palette = core.set_palette
pxl8.set_palette_rgb = core.set_palette_rgb
pxl8.update_palette_deps = core.update_palette_deps
pxl8.clear = gfx2d.clear pxl8.clear = gfx2d.clear
pxl8.pixel = gfx2d.pixel pxl8.pixel = gfx2d.pixel
@ -71,87 +79,96 @@ 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 = math.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.mat4_identity = math3d.mat4_identity pxl8.Compressor = sfx.Compressor
pxl8.mat4_multiply = math3d.mat4_multiply pxl8.create_compressor = sfx.Compressor.new
pxl8.mat4_translate = math3d.mat4_translate pxl8.Delay = sfx.Delay
pxl8.mat4_rotate_x = math3d.mat4_rotate_x pxl8.create_delay = sfx.Delay.new
pxl8.mat4_rotate_y = math3d.mat4_rotate_y pxl8.Reverb = sfx.Reverb
pxl8.mat4_rotate_z = math3d.mat4_rotate_z pxl8.create_reverb = sfx.Reverb.new
pxl8.mat4_scale = math3d.mat4_scale
pxl8.mat4_ortho = math3d.mat4_ortho
pxl8.mat4_perspective = math3d.mat4_perspective
pxl8.mat4_lookat = math3d.mat4_lookat
pxl8.bounds = math3d.bounds
pxl8.Gui = gui.Gui pxl8.Gui = gui.Gui
pxl8.create_gui = function() return gui.Gui.new() end pxl8.create_gui = gui.Gui.new
pxl8.gui_label = gui.label pxl8.gui_label = gui.label
pxl8.gui_window = gui.window pxl8.gui_window = gui.window
pxl8.World = world.World pxl8.mat4_identity = math.mat4_identity
pxl8.create_world = function() return world.World.new() end pxl8.mat4_lookat = math.mat4_lookat
pxl8.procgen_tex = world.procgen_tex pxl8.mat4_multiply = math.mat4_multiply
pxl8.mat4_ortho = math.mat4_ortho
pxl8.mat4_perspective = math.mat4_perspective
pxl8.mat4_rotate_x = math.mat4_rotate_x
pxl8.mat4_rotate_y = math.mat4_rotate_y
pxl8.mat4_rotate_z = math.mat4_rotate_z
pxl8.mat4_scale = math.mat4_scale
pxl8.mat4_translate = math.mat4_translate
pxl8.Net = net.Net
pxl8.create_net = net.Net.new
pxl8.NET_MODE_LOCAL = net.MODE_LOCAL
pxl8.NET_MODE_REMOTE = net.MODE_REMOTE
pxl8.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.Graph = procgen.Graph
pxl8.create_graph = procgen.create_graph
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.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 +176,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

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