refactor atlas implementation

This commit is contained in:
asrael 2025-10-05 16:25:17 -05:00
parent 6008ebf5ed
commit 10fe609edc
12 changed files with 868 additions and 303 deletions

View file

@ -8,6 +8,17 @@
(var wireframe true)
(var time 0)
(var zoom 5.0)
(var texture-id nil)
(var use-texture false)
(var affine false)
(var texture-initialized false)
(fn init-texture []
(when (not texture-initialized)
(pxl8.load_palette "sprites/pxl8_logo.ase")
(set texture-id (pxl8.load_sprite "sprites/pxl8_logo.ase"))
(pxl8.upload_atlas)
(set texture-initialized true)))
(fn make-cube-vertices []
[[-1 -1 -1] [1 -1 -1] [1 1 -1] [-1 1 -1]
@ -21,6 +32,20 @@
[3 2 6] [3 6 7]
[4 5 1] [4 1 0]])
(fn make-cube-faces-with-uvs []
[{:tri [0 1 2] :uvs [[0 0] [1 0] [1 1]]}
{:tri [0 2 3] :uvs [[0 0] [1 1] [0 1]]}
{:tri [1 5 6] :uvs [[0 0] [1 0] [1 1]]}
{:tri [1 6 2] :uvs [[0 0] [1 1] [0 1]]}
{:tri [5 4 7] :uvs [[0 0] [1 0] [1 1]]}
{:tri [5 7 6] :uvs [[0 0] [1 1] [0 1]]}
{:tri [4 0 3] :uvs [[0 0] [1 0] [1 1]]}
{:tri [4 3 7] :uvs [[0 0] [1 1] [0 1]]}
{:tri [3 2 6] :uvs [[0 0] [1 0] [1 1]]}
{:tri [3 6 7] :uvs [[0 0] [1 1] [0 1]]}
{:tri [4 5 1] :uvs [[0 0] [1 0] [1 1]]}
{:tri [4 1 0] :uvs [[0 0] [1 1] [0 1]]}])
(fn get-face-color [face-idx]
(let [colors [12 22 30 16 28 20]]
(. colors (+ 1 (% face-idx 6)))))
@ -43,10 +68,16 @@
(when (pxl8.key_pressed " ")
(set wireframe (not wireframe)))
(when (pxl8.key_pressed "r")
(set auto-rotate (not auto-rotate)))
(when (pxl8.key_pressed "f")
(set affine (not affine)))
(when (pxl8.key_pressed "p")
(set orthographic (not orthographic)))
(when (pxl8.key_pressed "r")
(set auto-rotate (not auto-rotate)))
(when (pxl8.key_pressed "t")
(set use-texture (not use-texture))
(when use-texture
(init-texture)))
(when (pxl8.key_down "=")
(set zoom (- zoom (* dt 2.0))))
@ -63,8 +94,9 @@
(pxl8.clr 0)
(pxl8.clear_zbuffer)
(pxl8.set_wireframe wireframe)
(pxl8.set_affine_textures affine)
(pxl8.set_backface_culling true)
(pxl8.set_wireframe wireframe)
(if orthographic
(let [size (* 2.5 (/ zoom 5.0))
@ -84,15 +116,36 @@
(pxl8.mat4_multiply (pxl8.mat4_rotate_z angle-z)))]
(pxl8.set_model model))
(let [vertices (make-cube-vertices)
faces (make-cube-faces)]
(let [vertices (make-cube-vertices)]
(if (and use-texture texture-id)
(let [faces (make-cube-faces-with-uvs)]
(each [i face-data (ipairs faces)]
(let [tri-indices face-data.tri
tri-uvs face-data.uvs
v0 (. vertices (+ 1 (. tri-indices 1)))
v1 (. vertices (+ 1 (. tri-indices 2)))
v2 (. vertices (+ 1 (. tri-indices 3)))
uv0 (. tri-uvs 1)
uv1 (. tri-uvs 2)
uv2 (. tri-uvs 3)]
(pxl8.draw_triangle_3d_textured
v0 v1 v2
uv0 uv1 uv2
texture-id))))
(let [faces (make-cube-faces)]
(each [i face (ipairs faces)]
(let [[i0 i1 i2] face
v0 (. vertices (+ 1 i0))
v1 (. vertices (+ 1 i1))
v2 (. vertices (+ 1 i2))
color (get-face-color (math.floor (/ (- i 1) 2)))]
(pxl8.draw_triangle_3d v0 v1 v2 color)))))
(pxl8.draw_triangle_3d v0 v1 v2 color))))))
(pxl8.text "WASD/QE: Rotate | +/-: Zoom | Space: Wire | R: Auto | P: Proj" 5 5 15)
(pxl8.text (.. "T: Texture | F: Affine | Mode: "
(if wireframe "Wire" "Fill")
" | Texture: " (if use-texture "On" "Off")
" | Mapping: " (if affine "Affine" "Persp")) 5 15 15))
{:update cube-update
:frame cube-frame}

View file

@ -7,6 +7,7 @@
(var fire-init false)
(var rain-init false)
(var snow-init false)
(var use-nes-palette false)
(var logo-x 256)
(var logo-y 148)
@ -15,7 +16,7 @@
(var logo-sprite nil)
(global init (fn []
(pxl8.load_palette "palettes/gruvbox.ase")
(pxl8.load_palette "sprites/pxl8_logo.ase")
(set logo-sprite (pxl8.load_sprite "sprites/pxl8_logo.ase"))
(set particles (pxl8.particles_new 1000))))
@ -41,6 +42,12 @@
(set snow-init false))
(when (pxl8.key_pressed "8")
(set current-effect 8))
(when (pxl8.key_pressed "9")
(set use-nes-palette (not use-nes-palette))
(local palette-path (if use-nes-palette "palettes/nes.ase" "sprites/pxl8_logo.ase"))
(print (.. "Switching to palette: " palette-path))
(pxl8.load_palette palette-path)
(print "Palette loaded"))
(case current-effect
1 (do
@ -63,15 +70,15 @@
(when logo-sprite
(pxl8.sprite logo-sprite logo-x logo-y 128 64)))
2 (pxl8.vfx_plasma time 0.10 0.04 0)
2 (pxl8.vfx_plasma time 0.10 0.04 1)
3 (pxl8.vfx_tunnel time 2.0 0.25)
4 (do
(pxl8.clr 0)
(local bars [{:base_y 60 :amplitude 30 :height 16 :speed 2.0 :phase 0 :color 20 :fade_color 10}
{:base_y 180 :amplitude 35 :height 16 :speed 1.8 :phase 2.0 :color 26 :fade_color 10}
{:base_y 300 :amplitude 25 :height 16 :speed 2.2 :phase 4.0 :color 14 :fade_color 10}])
(local bars [{:base_y 60 :amplitude 30 :height 16 :speed 2.0 :phase 0 :color 1 :fade_color 18}
{:base_y 180 :amplitude 35 :height 16 :speed 1.8 :phase 2.0 :color 1 :fade_color 27}
{:base_y 300 :amplitude 25 :height 16 :speed 2.2 :phase 4.0 :color 1 :fade_color 24}])
(pxl8.vfx_raster_bars bars time))
5 (do

BIN
demo/palettes/nes.ase Normal file

Binary file not shown.

View file

@ -69,6 +69,22 @@ function pxl8.load_sprite(filepath)
end
end
function pxl8.create_texture(pixels, width, height)
local pixel_data = ffi.new("u8[?]", width * height)
for i = 0, width * height - 1 do
pixel_data[i] = pixels[i + 1] or 0
end
local result = C.pxl8_gfx_create_texture(gfx, pixel_data, width, height)
if result < 0 then
return nil
end
return result
end
function pxl8.upload_atlas()
C.pxl8_gfx_upload_atlas(gfx)
end
-- log
function pxl8.info(msg)
C.pxl8_lua_info(msg)
@ -267,6 +283,10 @@ function pxl8.set_wireframe(wireframe)
C.pxl8_3d_set_wireframe(gfx, wireframe)
end
function pxl8.set_affine_textures(affine)
C.pxl8_3d_set_affine_textures(gfx, affine)
end
function pxl8.set_backface_culling(culling)
C.pxl8_3d_set_backface_culling(gfx, culling)
end
@ -278,6 +298,14 @@ function pxl8.draw_triangle_3d(v0, v1, v2, color)
C.pxl8_3d_draw_triangle_raw(gfx, vec0, vec1, vec2, color)
end
function pxl8.draw_triangle_3d_textured(v0, v1, v2, uv0, uv1, uv2, texture_id)
local vec0 = ffi.new("pxl8_vec3", {x = v0[1], y = v0[2], z = v0[3]})
local vec1 = ffi.new("pxl8_vec3", {x = v1[1], y = v1[2], z = v1[3]})
local vec2 = ffi.new("pxl8_vec3", {x = v2[1], y = v2[2], z = v2[3]})
C.pxl8_3d_draw_triangle_textured(gfx, vec0, vec1, vec2,
uv0[1], uv0[2], uv1[1], uv1[2], uv2[1], uv2[2], texture_id)
end
function pxl8.draw_line_3d(p0, p1, color)
local vec0 = ffi.new("pxl8_vec3", {x = p0[1], y = p0[2], z = p0[3]})
local vec1 = ffi.new("pxl8_vec3", {x = p1[1], y = p1[2], z = p1[3]})

View file

@ -245,11 +245,6 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {
return SDL_APP_FAILURE;
}
if (pxl8_gfx_init_atlas(app.gfx, 1024, 1024) != PXL8_OK) {
pxl8_error("Failed to initialize sprite atlas");
return SDL_APP_FAILURE;
}
app.ui = pxl8_ui_create(app.gfx);
if (!app.ui) {
pxl8_error("Failed to create UI");

View file

@ -261,15 +261,10 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
const u8* chunk_payload = chunk_data + 6;
pxl8_debug("Found chunk: type=0x%04X, size=%d", chunk_header.chunk_type, chunk_header.chunk_size);
switch (chunk_header.chunk_type) {
case PXL8_ASE_CHUNK_OLD_PALETTE: // 0x0004
if (!ase_file->palette.colors) {
result = parse_old_palette_chunk(chunk_payload, &ase_file->palette);
pxl8_debug("Parsed old palette: %d colors, indices %d-%d",
ase_file->palette.entry_count, ase_file->palette.first_color, ase_file->palette.last_color);
} else {
pxl8_debug("Ignoring old palette (0x0004) - new palette (0x2019) already loaded");
}
break;
@ -284,11 +279,6 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
result = parse_layer_chunk(chunk_payload, &ase_file->layers[ase_file->layer_count]);
if (result == PXL8_OK) {
pxl8_debug("Parsed layer %d: '%s', blend_mode=%d, opacity=%d",
ase_file->layer_count,
ase_file->layers[ase_file->layer_count].name ? ase_file->layers[ase_file->layer_count].name : "(unnamed)",
ase_file->layers[ase_file->layer_count].blend_mode,
ase_file->layers[ase_file->layer_count].opacity);
ase_file->layer_count++;
}
break;
@ -296,14 +286,11 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
case PXL8_ASE_CHUNK_CEL: { // 0x2005
pxl8_ase_cel cel = {0};
pxl8_debug("Found CEL chunk: size=%d", chunk_header.chunk_size - 6);
result = parse_cel_chunk(chunk_payload, chunk_header.chunk_size - 6, &cel);
if (result == PXL8_OK && cel.pixel_data) {
pxl8_debug("CEL data loaded: %dx%d, type=%d, first pixel = %d", cel.width, cel.height, cel.cel_type, cel.pixel_data[0]);
u32 copy_width = (cel.width < frame->width) ? cel.width : frame->width;
u32 copy_height = (cel.height < frame->height) ? cel.height : frame->height;
pxl8_debug("Copying cel to frame: cel_pos=(%d,%d), copy_size=%dx%d", cel.x, cel.y, copy_width, copy_height);
for (u32 y = 0; y < copy_height; y++) {
u32 src_offset = y * cel.width;
u32 dst_offset = (y + cel.y) * frame->width + cel.x;
@ -321,10 +308,6 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
frame->pixels[dst_offset + x] = src_pixel;
}
}
if (y < 3) {
pxl8_debug("Row %d: cel[%d]=%d, frame[%d]=%d",
y, src_offset, cel.pixel_data[src_offset], dst_offset, frame->pixels[dst_offset]);
}
}
}
SDL_free(cel.pixel_data);
@ -337,8 +320,6 @@ pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
SDL_free(ase_file->palette.colors);
}
result = parse_palette_chunk(chunk_payload, &ase_file->palette);
pxl8_debug("Parsed new palette: %d colors, indices %d-%d",
ase_file->palette.entry_count, ase_file->palette.first_color, ase_file->palette.last_color);
break;
default:

View file

@ -12,6 +12,367 @@
#include "pxl8_math.h"
#include "pxl8_types.h"
typedef struct pxl8_atlas_entry {
bool active;
char path[256];
u32 texture_id;
i32 x, y, w, h;
} pxl8_atlas_entry;
typedef struct pxl8_skyline_fit {
bool found;
u32 node_idx;
pxl8_point pos;
} pxl8_skyline_fit;
typedef struct pxl8_skyline_node {
i32 x, y, width;
} pxl8_skyline_node;
typedef struct pxl8_skyline {
pxl8_skyline_node* nodes;
u32 count;
u32 capacity;
} pxl8_skyline;
struct pxl8_atlas {
u32 height, width;
u8* pixels;
SDL_Texture* texture;
bool dirty;
u32 entry_capacity, entry_count;
pxl8_atlas_entry* entries;
u32 free_capacity, free_count;
u32* free_list;
pxl8_skyline skyline;
};
struct pxl8_gfx {
SDL_Renderer* renderer;
SDL_Texture* framebuffer_texture;
SDL_Window* window;
pxl8_atlas* atlas;
pxl8_color_mode color_mode;
u8* framebuffer;
i32 framebuffer_height;
i32 framebuffer_width;
bool initialized;
u32* palette;
u32 palette_size;
pxl8_viewport viewport;
bool backface_culling;
pxl8_mat4 model;
pxl8_mat4 projection;
pxl8_mat4 view;
bool wireframe;
f32* zbuffer;
i32 zbuffer_height;
i32 zbuffer_width;
bool affine_textures;
};
static pxl8_skyline_fit pxl8_skyline_find_position(const pxl8_skyline* skyline, u32 atlas_w, u32 atlas_h, u32 rect_w, u32 rect_h) {
pxl8_skyline_fit result = {.found = false};
i32 best_y = INT32_MAX;
i32 best_x = 0;
u32 best_idx = 0;
for (u32 i = 0; i < skyline->count; i++) {
i32 x = skyline->nodes[i].x;
i32 y = skyline->nodes[i].y;
if (x + (i32)rect_w > (i32)atlas_w) continue;
i32 max_y = y;
for (u32 j = i; j < skyline->count && skyline->nodes[j].x < x + (i32)rect_w; j++) {
if (skyline->nodes[j].y > max_y) {
max_y = skyline->nodes[j].y;
}
}
if (max_y + (i32)rect_h > (i32)atlas_h) continue;
if (max_y < best_y || (max_y == best_y && x < best_x)) {
best_y = max_y;
best_x = x;
best_idx = i;
}
}
if (best_y != INT32_MAX) {
result.found = true;
result.pos.x = best_x;
result.pos.y = best_y;
result.node_idx = best_idx;
}
return result;
}
static void pxl8_skyline_add_rect(pxl8_skyline* skyline, pxl8_point pos, u32 w, u32 h) {
u32 node_idx = 0;
for (u32 i = 0; i < skyline->count; i++) {
if (skyline->nodes[i].x == pos.x) {
node_idx = i;
break;
}
}
pxl8_skyline_node new_node = {pos.x, pos.y + (i32)h, (i32)w};
u32 nodes_to_remove = 0;
for (u32 i = node_idx; i < skyline->count; i++) {
if (skyline->nodes[i].x < pos.x + (i32)w) {
nodes_to_remove++;
} else {
break;
}
}
if (skyline->count - nodes_to_remove + 1 > skyline->capacity) {
skyline->capacity = (skyline->count - nodes_to_remove + 1) * 2;
skyline->nodes = (pxl8_skyline_node*)SDL_realloc(skyline->nodes, skyline->capacity * sizeof(pxl8_skyline_node));
}
if (nodes_to_remove > 0) {
SDL_memmove(&skyline->nodes[node_idx + 1], &skyline->nodes[node_idx + nodes_to_remove],
(skyline->count - node_idx - nodes_to_remove) * sizeof(pxl8_skyline_node));
}
skyline->nodes[node_idx] = new_node;
skyline->count = skyline->count - nodes_to_remove + 1;
}
static void pxl8_skyline_compact(pxl8_skyline* skyline) {
for (u32 i = 0; i < skyline->count - 1; ) {
if (skyline->nodes[i].y == skyline->nodes[i + 1].y) {
skyline->nodes[i].width += skyline->nodes[i + 1].width;
SDL_memmove(&skyline->nodes[i + 1], &skyline->nodes[i + 2],
(skyline->count - i - 2) * sizeof(pxl8_skyline_node));
skyline->count--;
} else {
i++;
}
}
}
static pxl8_atlas* pxl8_atlas_create(SDL_Renderer* renderer, u32 width, u32 height, pxl8_color_mode color_mode) {
pxl8_atlas* atlas = (pxl8_atlas*)SDL_calloc(1, sizeof(pxl8_atlas));
if (!atlas) return NULL;
atlas->height = height;
atlas->width = width;
i32 bytes_per_pixel = (color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1;
atlas->pixels = (u8*)SDL_calloc(width * height, bytes_per_pixel);
if (!atlas->pixels) {
SDL_free(atlas);
return NULL;
}
atlas->texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32,
SDL_TEXTUREACCESS_STREAMING, width, height);
if (!atlas->texture) {
SDL_free(atlas->pixels);
SDL_free(atlas);
return NULL;
}
atlas->entry_capacity = 64;
atlas->entries = (pxl8_atlas_entry*)SDL_calloc(atlas->entry_capacity, sizeof(pxl8_atlas_entry));
if (!atlas->entries) {
SDL_DestroyTexture(atlas->texture);
SDL_free(atlas->pixels);
SDL_free(atlas);
return NULL;
}
atlas->free_capacity = 16;
atlas->free_list = (u32*)SDL_calloc(atlas->free_capacity, sizeof(u32));
if (!atlas->free_list) {
SDL_free(atlas->entries);
SDL_DestroyTexture(atlas->texture);
SDL_free(atlas->pixels);
SDL_free(atlas);
return NULL;
}
atlas->skyline.capacity = 16;
atlas->skyline.nodes = (pxl8_skyline_node*)SDL_calloc(atlas->skyline.capacity, sizeof(pxl8_skyline_node));
if (!atlas->skyline.nodes) {
SDL_free(atlas->free_list);
SDL_free(atlas->entries);
SDL_DestroyTexture(atlas->texture);
SDL_free(atlas->pixels);
SDL_free(atlas);
return NULL;
}
atlas->skyline.nodes[0] = (pxl8_skyline_node){0, 0, (i32)width};
atlas->skyline.count = 1;
return atlas;
}
static void pxl8_atlas_destroy(pxl8_atlas* atlas) {
if (!atlas) return;
SDL_free(atlas->free_list);
SDL_free(atlas->entries);
SDL_free(atlas->skyline.nodes);
if (atlas->texture) SDL_DestroyTexture(atlas->texture);
SDL_free(atlas->pixels);
SDL_free(atlas);
}
static bool pxl8_atlas_expand(pxl8_atlas* atlas, SDL_Renderer* renderer, pxl8_color_mode color_mode) {
if (!atlas || atlas->width >= 4096) return false;
u32 new_size = atlas->width * 2;
u32 old_width = atlas->width;
i32 bytes_per_pixel = (color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1;
u8* new_pixels = (u8*)SDL_calloc(new_size * new_size, bytes_per_pixel);
if (!new_pixels) return false;
SDL_Texture* new_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32,
SDL_TEXTUREACCESS_STREAMING, new_size, new_size);
if (!new_texture) {
SDL_free(new_pixels);
return false;
}
pxl8_skyline new_skyline;
new_skyline.nodes = (pxl8_skyline_node*)SDL_calloc(16, sizeof(pxl8_skyline_node));
if (!new_skyline.nodes) {
SDL_DestroyTexture(new_texture);
SDL_free(new_pixels);
return false;
}
new_skyline.nodes[0] = (pxl8_skyline_node){0, 0, (i32)new_size};
new_skyline.count = 1;
new_skyline.capacity = 16;
for (u32 i = 0; i < atlas->entry_count; i++) {
if (!atlas->entries[i].active) continue;
pxl8_skyline_fit fit = pxl8_skyline_find_position(&new_skyline, new_size, new_size,
atlas->entries[i].w, atlas->entries[i].h);
if (!fit.found) {
SDL_free(new_skyline.nodes);
SDL_DestroyTexture(new_texture);
SDL_free(new_pixels);
return false;
}
for (u32 y = 0; y < (u32)atlas->entries[i].h; y++) {
for (u32 x = 0; x < (u32)atlas->entries[i].w; x++) {
u32 src_idx = (atlas->entries[i].y + y) * old_width + (atlas->entries[i].x + x);
u32 dst_idx = (fit.pos.y + y) * new_size + (fit.pos.x + x);
if (bytes_per_pixel == 4) {
((u32*)new_pixels)[dst_idx] = ((u32*)atlas->pixels)[src_idx];
} else {
new_pixels[dst_idx] = atlas->pixels[src_idx];
}
}
}
atlas->entries[i].x = fit.pos.x;
atlas->entries[i].y = fit.pos.y;
pxl8_skyline_add_rect(&new_skyline, fit.pos, atlas->entries[i].w, atlas->entries[i].h);
pxl8_skyline_compact(&new_skyline);
}
SDL_DestroyTexture(atlas->texture);
SDL_free(atlas->pixels);
SDL_free(atlas->skyline.nodes);
atlas->pixels = new_pixels;
atlas->texture = new_texture;
atlas->skyline = new_skyline;
atlas->width = new_size;
atlas->height = new_size;
atlas->dirty = true;
pxl8_debug("Atlas expanded to %ux%u", atlas->width, atlas->height);
return true;
}
static u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u32 h, const char* path, pxl8_color_mode color_mode) {
if (!atlas || !pixels) return UINT32_MAX;
pxl8_skyline_fit fit = pxl8_skyline_find_position(&atlas->skyline, atlas->width, atlas->height, w, h);
if (!fit.found) {
if (!pxl8_atlas_expand(atlas, atlas->texture ? SDL_GetRendererFromTexture(atlas->texture) : NULL, color_mode)) {
return UINT32_MAX;
}
fit = pxl8_skyline_find_position(&atlas->skyline, atlas->width, atlas->height, w, h);
if (!fit.found) return UINT32_MAX;
}
u32 texture_id;
if (atlas->free_count > 0) {
texture_id = atlas->free_list[--atlas->free_count];
} else {
if (atlas->entry_count >= atlas->entry_capacity) {
atlas->entry_capacity *= 2;
atlas->entries = (pxl8_atlas_entry*)SDL_realloc(atlas->entries,
atlas->entry_capacity * sizeof(pxl8_atlas_entry));
}
texture_id = atlas->entry_count++;
}
pxl8_atlas_entry* entry = &atlas->entries[texture_id];
entry->active = true;
entry->texture_id = texture_id;
entry->x = fit.pos.x;
entry->y = fit.pos.y;
entry->w = w;
entry->h = h;
if (path) {
strncpy(entry->path, path, sizeof(entry->path) - 1);
entry->path[sizeof(entry->path) - 1] = '\0';
} else {
snprintf(entry->path, sizeof(entry->path), "procgen_%u", texture_id);
}
i32 bytes_per_pixel = (color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1;
for (u32 y = 0; y < h; y++) {
for (u32 x = 0; x < w; x++) {
u32 src_idx = y * w + x;
u32 dst_idx = (fit.pos.y + y) * atlas->width + (fit.pos.x + x);
if (bytes_per_pixel == 4) {
((u32*)atlas->pixels)[dst_idx] = ((const u32*)pixels)[src_idx];
} else {
atlas->pixels[dst_idx] = pixels[src_idx];
}
}
}
pxl8_skyline_add_rect(&atlas->skyline, fit.pos, w, h);
pxl8_skyline_compact(&atlas->skyline);
atlas->dirty = true;
return texture_id;
}
static inline void pxl8_color_unpack(u32 color, u8* r, u8* g, u8* b, u8* a) {
*r = color & 0xFF;
*g = (color >> 8) & 0xFF;
@ -27,61 +388,6 @@ static inline u8 pxl8_color_lerp_channel(u8 c1, u8 c2, f32 t) {
return c1 + (i32)((c2 - c1) * t);
}
pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height) {
pxl8_viewport vp = {0};
vp.scale = fminf(bounds.w / (f32)width, bounds.h / (f32)height);
vp.scaled_width = (i32)(width * vp.scale);
vp.scaled_height = (i32)(height * vp.scale);
vp.offset_x = (bounds.w - vp.scaled_width) / 2;
vp.offset_y = (bounds.h - vp.scaled_height) / 2;
return vp;
}
typedef struct pxl8_atlas_entry {
char path[256];
u32 sprite_id;
i32 x, y, w, h;
} pxl8_atlas_entry;
struct pxl8_gfx {
SDL_Renderer* renderer;
SDL_Texture* framebuffer_texture;
SDL_Texture* sprite_atlas_texture;
SDL_Window* window;
u8* atlas;
bool atlas_dirty;
pxl8_atlas_entry* atlas_entries;
u32 atlas_entries_cap;
u32 atlas_entries_len;
pxl8_color_mode color_mode;
u8* framebuffer;
i32 framebuffer_height;
i32 framebuffer_width;
bool initialized;
u32* palette;
u32 palette_size;
u32 sprite_atlas_height;
u32 sprite_atlas_width;
u32 sprite_frame_height;
u32 sprite_frame_width;
u32 sprite_frames_per_row;
pxl8_viewport viewport;
bool backface_culling;
pxl8_mat4 model;
pxl8_mat4 projection;
pxl8_mat4 view;
bool wireframe;
f32* zbuffer;
i32 zbuffer_height;
i32 zbuffer_width;
};
static u32 pxl8_get_palette_size(pxl8_color_mode mode) {
switch (mode) {
case PXL8_COLOR_MODE_HICOLOR: return 0;
@ -140,6 +446,10 @@ i32 pxl8_gfx_get_width(const pxl8_gfx* gfx) {
return gfx ? gfx->framebuffer_width : 0;
}
u32 pxl8_gfx_get_palette_size(const pxl8_gfx* gfx) {
return gfx ? gfx->palette_size : 0;
}
pxl8_gfx* pxl8_gfx_create(pxl8_color_mode mode, pxl8_resolution resolution, const char* title, i32 window_width, i32 window_height) {
pxl8_gfx* gfx = (pxl8_gfx*)SDL_calloc(1, sizeof(*gfx));
if (!gfx) {
@ -228,6 +538,7 @@ pxl8_gfx* pxl8_gfx_create(pxl8_color_mode mode, pxl8_resolution resolution, cons
gfx->zbuffer = NULL;
gfx->zbuffer_height = 0;
gfx->zbuffer_width = 0;
gfx->affine_textures = false;
gfx->initialized = true;
return gfx;
@ -236,16 +547,13 @@ pxl8_gfx* pxl8_gfx_create(pxl8_color_mode mode, pxl8_resolution resolution, cons
void pxl8_gfx_destroy(pxl8_gfx* gfx) {
if (!gfx) return;
pxl8_atlas_destroy(gfx->atlas);
if (gfx->framebuffer_texture) {
SDL_DestroyTexture(gfx->framebuffer_texture);
gfx->framebuffer_texture = NULL;
}
if (gfx->sprite_atlas_texture) {
SDL_DestroyTexture(gfx->sprite_atlas_texture);
gfx->sprite_atlas_texture = NULL;
}
if (gfx->renderer) {
SDL_DestroyRenderer(gfx->renderer);
gfx->renderer = NULL;
@ -258,63 +566,45 @@ void pxl8_gfx_destroy(pxl8_gfx* gfx) {
SDL_free(gfx->framebuffer);
SDL_free(gfx->palette);
SDL_free(gfx->atlas);
SDL_free(gfx->atlas_entries);
SDL_free(gfx->zbuffer);
SDL_free(gfx);
}
pxl8_result pxl8_gfx_init_atlas(pxl8_gfx* gfx, u32 width, u32 height) {
if (!gfx || !gfx->initialized) return PXL8_ERROR_INVALID_ARGUMENT;
gfx->sprite_atlas_width = width;
gfx->sprite_atlas_height = height;
pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width, u32 height) {
if (!gfx || !gfx->initialized || !pixels) return PXL8_ERROR_INVALID_ARGUMENT;
i32 bytes_per_pixel = (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1;
gfx->atlas = (u8*)SDL_calloc(width * height, bytes_per_pixel);
if (!gfx->atlas) {
return PXL8_ERROR_OUT_OF_MEMORY;
gfx->atlas = pxl8_atlas_create(gfx->renderer, 1024, 1024, gfx->color_mode);
if (!gfx->atlas) return PXL8_ERROR_OUT_OF_MEMORY;
}
gfx->sprite_atlas_texture = SDL_CreateTexture(
gfx->renderer,
SDL_PIXELFORMAT_RGBA32,
SDL_TEXTUREACCESS_STREAMING,
width,
height
);
if (!gfx->sprite_atlas_texture) {
SDL_free(gfx->atlas);
gfx->atlas = NULL;
return PXL8_ERROR_SYSTEM_FAILURE;
u32 texture_id = pxl8_atlas_add_texture(gfx->atlas, pixels, width, height, NULL, gfx->color_mode);
if (texture_id == UINT32_MAX) {
pxl8_error("Texture doesn't fit in atlas");
return PXL8_ERROR_INVALID_SIZE;
}
gfx->atlas_entries_cap = 256;
gfx->atlas_entries = (pxl8_atlas_entry*)SDL_calloc(gfx->atlas_entries_cap, sizeof(pxl8_atlas_entry));
if (!gfx->atlas_entries) {
SDL_DestroyTexture(gfx->sprite_atlas_texture);
SDL_free(gfx->atlas);
return PXL8_ERROR_OUT_OF_MEMORY;
}
pxl8_debug("Created texture %u: %ux%u at (%d,%d)",
texture_id, width, height,
gfx->atlas->entries[texture_id].x,
gfx->atlas->entries[texture_id].y);
gfx->sprite_frames_per_row = width / 128;
if (gfx->sprite_frames_per_row == 0) gfx->sprite_frames_per_row = 1;
return PXL8_OK;
return texture_id;
}
pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) {
if (!gfx || !gfx->initialized || !path) return PXL8_ERROR_INVALID_ARGUMENT;
if (!gfx->atlas) {
pxl8_error("Atlas not initialized");
return PXL8_ERROR_NOT_INITIALIZED;
gfx->atlas = pxl8_atlas_create(gfx->renderer, 1024, 1024, gfx->color_mode);
if (!gfx->atlas) return PXL8_ERROR_OUT_OF_MEMORY;
}
for (u32 i = 0; i < gfx->atlas_entries_len; i++) {
if (strcmp(gfx->atlas_entries[i].path, path) == 0) {
return PXL8_OK;
for (u32 i = 0; i < gfx->atlas->entry_count; i++) {
if (gfx->atlas->entries[i].active && strcmp(gfx->atlas->entries[i].path, path) == 0) {
return gfx->atlas->entries[i].texture_id;
}
}
@ -334,66 +624,21 @@ pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path) {
u32 sprite_w = ase_file.header.width;
u32 sprite_h = ase_file.header.height;
if (gfx->sprite_frame_width == 0 || gfx->sprite_frame_height == 0) {
gfx->sprite_frame_width = sprite_w;
gfx->sprite_frame_height = sprite_h;
}
u32 texture_id = pxl8_atlas_add_texture(gfx->atlas, ase_file.frames[0].pixels, sprite_w, sprite_h, path, gfx->color_mode);
u32 id = gfx->atlas_entries_len;
u32 frames_per_row = gfx->sprite_frames_per_row;
u32 atlas_x = (id % frames_per_row) * gfx->sprite_frame_width;
u32 atlas_y = (id / frames_per_row) * gfx->sprite_frame_height;
if (atlas_x + sprite_w > gfx->sprite_atlas_width ||
atlas_y + sprite_h > gfx->sprite_atlas_height) {
pxl8_error("Sprite doesn't fit in atlas");
pxl8_ase_destroy(&ase_file);
if (texture_id == UINT32_MAX) {
pxl8_error("Sprite doesn't fit in atlas");
return PXL8_ERROR_INVALID_SIZE;
}
i32 bytes_per_pixel = (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1;
pxl8_debug("Loaded sprite %u: %ux%u at (%d,%d)",
texture_id, sprite_w, sprite_h,
gfx->atlas->entries[texture_id].x,
gfx->atlas->entries[texture_id].y);
for (u32 y = 0; y < sprite_h; y++) {
for (u32 x = 0; x < sprite_w; x++) {
u32 frame_idx = y * sprite_w + x;
u32 atlas_idx = (atlas_y + y) * gfx->sprite_atlas_width + (atlas_x + x);
if (bytes_per_pixel == 4) {
((u32*)gfx->atlas)[atlas_idx] = ((u32*)ase_file.frames[0].pixels)[frame_idx];
} else {
gfx->atlas[atlas_idx] = ase_file.frames[0].pixels[frame_idx];
}
}
}
if (gfx->atlas_entries_len >= gfx->atlas_entries_cap) {
gfx->atlas_entries_cap *= 2;
pxl8_atlas_entry* new_entries = (pxl8_atlas_entry*)SDL_realloc(
gfx->atlas_entries,
gfx->atlas_entries_cap * sizeof(pxl8_atlas_entry)
);
if (!new_entries) {
pxl8_ase_destroy(&ase_file);
return PXL8_ERROR_OUT_OF_MEMORY;
}
gfx->atlas_entries = new_entries;
}
gfx->atlas_entries[id].x = atlas_x;
gfx->atlas_entries[id].y = atlas_y;
gfx->atlas_entries[id].w = sprite_w;
gfx->atlas_entries[id].h = sprite_h;
gfx->atlas_entries[id].sprite_id = id;
strncpy(gfx->atlas_entries[id].path, path, sizeof(gfx->atlas_entries[id].path) - 1);
gfx->atlas_entries[id].path[sizeof(gfx->atlas_entries[id].path) - 1] = '\0';
gfx->atlas_entries_len++;
gfx->atlas_dirty = true;
pxl8_ase_destroy(&ase_file);
pxl8_debug("Loaded sprite %u: %ux%u at (%u,%u)", id, sprite_w, sprite_h, atlas_x, atlas_y);
return id;
return texture_id;
}
pxl8_result pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* path) {
@ -418,15 +663,9 @@ pxl8_result pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* path) {
u32 copy_size = (ase_file.palette.entry_count < gfx->palette_size) ? ase_file.palette.entry_count : gfx->palette_size;
memcpy(gfx->palette, ase_file.palette.colors, copy_size * sizeof(u32));
if (gfx->color_mode != PXL8_COLOR_MODE_HICOLOR && gfx->framebuffer_texture) {
SDL_Color colors[256];
for (u32 i = 0; i < 256 && i < gfx->palette_size; i++) {
colors[i].r = (gfx->palette[i] >> 16) & 0xFF;
colors[i].g = (gfx->palette[i] >> 8) & 0xFF;
colors[i].b = gfx->palette[i] & 0xFF;
colors[i].a = (gfx->palette[i] >> 24) & 0xFF;
}
SDL_SetPaletteColors(SDL_CreateSurfacePalette(NULL), colors, 0, 256);
u32 last_color = (copy_size > 0) ? gfx->palette[copy_size - 1] : 0xFF000000;
for (u32 i = copy_size; i < gfx->palette_size; i++) {
gfx->palette[i] = last_color;
}
pxl8_ase_destroy(&ase_file);
@ -475,19 +714,19 @@ void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) {
}
void pxl8_gfx_upload_atlas(pxl8_gfx* gfx) {
if (!gfx || !gfx->initialized || !gfx->sprite_atlas_texture || !gfx->atlas_dirty) return;
if (!gfx || !gfx->initialized || !gfx->atlas || !gfx->atlas->texture || !gfx->atlas->dirty) return;
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
SDL_UpdateTexture(gfx->sprite_atlas_texture, NULL, gfx->atlas,
gfx->sprite_atlas_width * 4);
SDL_UpdateTexture(gfx->atlas->texture, NULL, gfx->atlas->pixels,
gfx->atlas->width * 4);
} else {
pxl8_upload_indexed_texture(gfx->sprite_atlas_texture, gfx->atlas,
pxl8_upload_indexed_texture(gfx->atlas->texture, gfx->atlas->pixels,
gfx->palette, gfx->palette_size,
gfx->sprite_atlas_width, gfx->sprite_atlas_height,
gfx->atlas->width, gfx->atlas->height,
0x00000000);
}
gfx->atlas_dirty = false;
gfx->atlas->dirty = false;
pxl8_debug("Atlas uploaded to GPU");
}
@ -504,6 +743,16 @@ void pxl8_gfx_present(pxl8_gfx* gfx) {
SDL_RenderPresent(gfx->renderer);
}
pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height) {
pxl8_viewport vp = {0};
vp.scale = fminf(bounds.w / (f32)width, bounds.h / (f32)height);
vp.scaled_width = (i32)(width * vp.scale);
vp.scaled_height = (i32)(height * vp.scale);
vp.offset_x = (bounds.w - vp.scaled_width) / 2;
vp.offset_y = (bounds.h - vp.scaled_height) / 2;
return vp;
}
void pxl8_gfx_set_viewport(pxl8_gfx* gfx, pxl8_viewport vp) {
if (!gfx) return;
gfx->viewport = vp;
@ -703,9 +952,10 @@ void pxl8_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) {
void pxl8_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h) {
if (!gfx || !gfx->atlas || !gfx->framebuffer || sprite_id >= gfx->atlas_entries_len) return;
if (!gfx || !gfx->atlas || !gfx->framebuffer || sprite_id >= gfx->atlas->entry_count) return;
if (!gfx->atlas->entries[sprite_id].active) return;
pxl8_atlas_entry* entry = &gfx->atlas_entries[sprite_id];
pxl8_atlas_entry* entry = &gfx->atlas->entries[sprite_id];
i32 clip_left = (x < 0) ? -x : 0;
i32 clip_top = (y < 0) ? -y : 0;
@ -724,30 +974,30 @@ void pxl8_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h) {
bool is_unclipped = (clip_left == 0 && clip_top == 0 && clip_right == 0 && clip_bottom == 0);
if (is_1to1_scale && is_unclipped && pxl8_is_simd_aligned(w)) {
const u8* sprite_data = gfx->atlas + entry->y * gfx->sprite_atlas_width + entry->x;
const u8* sprite_data = gfx->atlas->pixels + entry->y * gfx->atlas->width + entry->x;
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
pxl8_blit_simd_hicolor((u32*)gfx->framebuffer, gfx->framebuffer_width,
(const u32*)sprite_data, gfx->sprite_atlas_width, x, y, w, h);
(const u32*)sprite_data, gfx->atlas->width, x, y, w, h);
} else {
pxl8_blit_simd_indexed(gfx->framebuffer, gfx->framebuffer_width,
sprite_data, gfx->sprite_atlas_width, x, y, w, h);
sprite_data, gfx->atlas->width, x, y, w, h);
}
} else {
for (i32 py = 0; py < draw_height; py++) {
for (i32 px = 0; px < draw_width; px++) {
i32 src_x = entry->x + ((px + clip_left) * entry->w) / w;
i32 src_y = entry->y + ((py + clip_top) * entry->h) / h;
i32 src_idx = src_y * gfx->sprite_atlas_width + src_x;
i32 src_idx = src_y * gfx->atlas->width + src_x;
i32 dest_idx = (dest_y + py) * gfx->framebuffer_width + (dest_x + px);
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
u32 pixel = ((u32*)gfx->atlas)[src_idx];
u32 pixel = ((u32*)gfx->atlas->pixels)[src_idx];
if (pixel & 0xFF000000) {
((u32*)gfx->framebuffer)[dest_idx] = pixel;
}
} else {
u8 pixel = gfx->atlas[src_idx];
u8 pixel = gfx->atlas->pixels[src_idx];
if (pixel != 0) {
gfx->framebuffer[dest_idx] = pixel;
}
@ -907,6 +1157,11 @@ void pxl8_3d_set_wireframe(pxl8_gfx* gfx, bool wireframe) {
gfx->wireframe = wireframe;
}
void pxl8_3d_set_affine_textures(pxl8_gfx* gfx, bool affine) {
if (!gfx) return;
gfx->affine_textures = affine;
}
static inline pxl8_vec4 pxl8_transform_vertex(pxl8_gfx* gfx, pxl8_vec3 pos) {
pxl8_vec4 v = {
.x = pos.x,
@ -971,6 +1226,81 @@ static inline void pxl8_fill_scanline(pxl8_gfx* gfx, i32 y, i32 xs, i32 xe, f32
}
}
static inline u32 pxl8_sample_texture(pxl8_gfx* gfx, u32 texture_id, f32 u, f32 v) {
if (!gfx->atlas || texture_id >= gfx->atlas->entry_count) return 0;
if (!gfx->atlas->entries[texture_id].active) return 0;
pxl8_atlas_entry* entry = &gfx->atlas->entries[texture_id];
u = u - floorf(u);
v = v - floorf(v);
i32 tx = (i32)(u * entry->w) % entry->w;
i32 ty = (i32)(v * entry->h) % entry->h;
i32 atlas_x = entry->x + tx;
i32 atlas_y = entry->y + ty;
i32 idx = atlas_y * gfx->atlas->width + atlas_x;
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
return ((u32*)gfx->atlas->pixels)[idx];
} else {
return gfx->atlas->pixels[idx];
}
}
static inline void pxl8_fill_scanline_textured(
pxl8_gfx* gfx, i32 y, i32 xs, i32 xe,
f32 z0, f32 z1,
f32 u0, f32 v0, f32 w0,
f32 u1, f32 v1, f32 w1,
u32 texture_id
) {
if (y < 0 || y >= gfx->framebuffer_height) return;
if (xs > xe) {
i32 tmp = xs; xs = xe; xe = tmp;
f32 tmpf;
tmpf = z0; z0 = z1; z1 = tmpf;
tmpf = u0; u0 = u1; u1 = tmpf;
tmpf = v0; v0 = v1; v1 = tmpf;
tmpf = w0; w0 = w1; w1 = tmpf;
}
for (i32 x = xs; x <= xe; x++) {
if (x >= 0 && x < gfx->framebuffer_width) {
f32 t = (xe == xs) ? 0.0f : (f32)(x - xs) / (f32)(xe - xs);
f32 z = z0 + t * (z1 - z0);
i32 idx = y * gfx->zbuffer_width + x;
if (z <= gfx->zbuffer[idx]) {
f32 u = u0 + t * (u1 - u0);
f32 v = v0 + t * (v1 - v0);
if (!gfx->affine_textures) {
f32 w = w0 + t * (w1 - w0);
if (fabsf(w) > 1e-6f) {
u /= w;
v /= w;
}
}
u32 color = pxl8_sample_texture(gfx, texture_id, u, v);
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
if (color & 0xFF000000) {
gfx->zbuffer[idx] = z;
pxl8_pixel(gfx, x, y, color);
}
} else {
if (color != 0) {
gfx->zbuffer[idx] = z;
pxl8_pixel(gfx, x, y, color);
}
}
}
}
}
}
static void pxl8_draw_flat_bottom_triangle(
pxl8_gfx* gfx,
i32 x0, i32 y0, f32 z0,
@ -1087,3 +1417,145 @@ void pxl8_3d_draw_triangle(pxl8_gfx* gfx, pxl8_triangle tri) {
pxl8_draw_flat_top_triangle(gfx, x1, y1, z1, x3, y3, z3, x2, y2, z2, color);
}
}
typedef struct pxl8_textured_vertex {
i32 x, y;
f32 z, u, v, w;
} pxl8_textured_vertex;
static void pxl8_draw_flat_bottom_triangle_textured(
pxl8_gfx* gfx,
pxl8_textured_vertex v0,
pxl8_textured_vertex v1,
pxl8_textured_vertex v2,
u32 texture_id
) {
if (v1.y == v0.y) return;
f32 inv_slope_1 = (f32)(v1.x - v0.x) / (f32)(v1.y - v0.y);
f32 inv_slope_2 = (f32)(v2.x - v0.x) / (f32)(v2.y - v0.y);
f32 inv_slope_z1 = (v1.z - v0.z) / (f32)(v1.y - v0.y);
f32 inv_slope_z2 = (v2.z - v0.z) / (f32)(v2.y - v0.y);
f32 inv_slope_u1 = (v1.u - v0.u) / (f32)(v1.y - v0.y);
f32 inv_slope_u2 = (v2.u - v0.u) / (f32)(v2.y - v0.y);
f32 inv_slope_v1 = (v1.v - v0.v) / (f32)(v1.y - v0.y);
f32 inv_slope_v2 = (v2.v - v0.v) / (f32)(v2.y - v0.y);
f32 inv_slope_w1 = (v1.w - v0.w) / (f32)(v1.y - v0.y);
f32 inv_slope_w2 = (v2.w - v0.w) / (f32)(v2.y - v0.y);
f32 cur_x1 = (f32)v0.x, cur_x2 = (f32)v0.x;
f32 cur_z1 = v0.z, cur_z2 = v0.z;
f32 cur_u1 = v0.u, cur_u2 = v0.u;
f32 cur_v1 = v0.v, cur_v2 = v0.v;
f32 cur_w1 = v0.w, cur_w2 = v0.w;
for (i32 y = v0.y; y <= v1.y; y++) {
pxl8_fill_scanline_textured(gfx, y, (i32)cur_x1, (i32)cur_x2,
cur_z1, cur_z2, cur_u1, cur_v1, cur_w1, cur_u2, cur_v2, cur_w2, texture_id);
cur_x1 += inv_slope_1; cur_x2 += inv_slope_2;
cur_z1 += inv_slope_z1; cur_z2 += inv_slope_z2;
cur_u1 += inv_slope_u1; cur_u2 += inv_slope_u2;
cur_v1 += inv_slope_v1; cur_v2 += inv_slope_v2;
cur_w1 += inv_slope_w1; cur_w2 += inv_slope_w2;
}
}
static void pxl8_draw_flat_top_triangle_textured(
pxl8_gfx* gfx,
pxl8_textured_vertex v0,
pxl8_textured_vertex v1,
pxl8_textured_vertex v2,
u32 texture_id
) {
if (v2.y == v0.y) return;
f32 inv_slope_1 = (f32)(v2.x - v0.x) / (f32)(v2.y - v0.y);
f32 inv_slope_2 = (f32)(v2.x - v1.x) / (f32)(v2.y - v1.y);
f32 inv_slope_z1 = (v2.z - v0.z) / (f32)(v2.y - v0.y);
f32 inv_slope_z2 = (v2.z - v1.z) / (f32)(v2.y - v1.y);
f32 inv_slope_u1 = (v2.u - v0.u) / (f32)(v2.y - v0.y);
f32 inv_slope_u2 = (v2.u - v1.u) / (f32)(v2.y - v1.y);
f32 inv_slope_v1 = (v2.v - v0.v) / (f32)(v2.y - v0.y);
f32 inv_slope_v2 = (v2.v - v1.v) / (f32)(v2.y - v1.y);
f32 inv_slope_w1 = (v2.w - v0.w) / (f32)(v2.y - v0.y);
f32 inv_slope_w2 = (v2.w - v1.w) / (f32)(v2.y - v1.y);
f32 cur_x1 = (f32)v2.x, cur_x2 = (f32)v2.x;
f32 cur_z1 = v2.z, cur_z2 = v2.z;
f32 cur_u1 = v2.u, cur_u2 = v2.u;
f32 cur_v1 = v2.v, cur_v2 = v2.v;
f32 cur_w1 = v2.w, cur_w2 = v2.w;
for (i32 y = v2.y; y > v0.y; y--) {
pxl8_fill_scanline_textured(gfx, y, (i32)cur_x1, (i32)cur_x2,
cur_z1, cur_z2, cur_u1, cur_v1, cur_w1, cur_u2, cur_v2, cur_w2, texture_id);
cur_x1 -= inv_slope_1; cur_x2 -= inv_slope_2;
cur_z1 -= inv_slope_z1; cur_z2 -= inv_slope_z2;
cur_u1 -= inv_slope_u1; cur_u2 -= inv_slope_u2;
cur_v1 -= inv_slope_v1; cur_v2 -= inv_slope_v2;
cur_w1 -= inv_slope_w1; cur_w2 -= inv_slope_w2;
}
}
void pxl8_3d_draw_triangle_textured(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, f32 u0, f32 v0f, f32 u1, f32 v1f, f32 u2, f32 v2f, u32 texture_id) {
if (!gfx || !pxl8_3d_init_zbuffer(gfx)) return;
pxl8_vec4 cv0 = pxl8_transform_vertex(gfx, v0);
pxl8_vec4 cv1 = pxl8_transform_vertex(gfx, v1);
pxl8_vec4 cv2 = pxl8_transform_vertex(gfx, v2);
if (cv0.w <= 0 || cv1.w <= 0 || cv2.w <= 0) return;
pxl8_textured_vertex tv[3];
f32 z_tmp;
pxl8_project_to_screen(gfx, cv0, &tv[0].x, &tv[0].y, &z_tmp);
tv[0].z = z_tmp;
tv[0].u = gfx->affine_textures ? u0 : u0 * cv0.w;
tv[0].v = gfx->affine_textures ? v0f : v0f * cv0.w;
tv[0].w = cv0.w;
pxl8_project_to_screen(gfx, cv1, &tv[1].x, &tv[1].y, &z_tmp);
tv[1].z = z_tmp;
tv[1].u = gfx->affine_textures ? u1 : u1 * cv1.w;
tv[1].v = gfx->affine_textures ? v1f : v1f * cv1.w;
tv[1].w = cv1.w;
pxl8_project_to_screen(gfx, cv2, &tv[2].x, &tv[2].y, &z_tmp);
tv[2].z = z_tmp;
tv[2].u = gfx->affine_textures ? u2 : u2 * cv2.w;
tv[2].v = gfx->affine_textures ? v2f : v2f * cv2.w;
tv[2].w = cv2.w;
if (gfx->backface_culling) {
i32 cross = (tv[1].x - tv[0].x) * (tv[2].y - tv[0].y) - (tv[1].y - tv[0].y) * (tv[2].x - tv[0].x);
if (cross >= 0) return;
}
if (tv[0].y > tv[1].y) {
pxl8_textured_vertex tmp = tv[0]; tv[0] = tv[1]; tv[1] = tmp;
}
if (tv[0].y > tv[2].y) {
pxl8_textured_vertex tmp = tv[0]; tv[0] = tv[2]; tv[2] = tmp;
}
if (tv[1].y > tv[2].y) {
pxl8_textured_vertex tmp = tv[1]; tv[1] = tv[2]; tv[2] = tmp;
}
if (tv[1].y == tv[2].y) {
pxl8_draw_flat_bottom_triangle_textured(gfx, tv[0], tv[1], tv[2], texture_id);
} else if (tv[0].y == tv[1].y) {
pxl8_draw_flat_top_triangle_textured(gfx, tv[0], tv[1], tv[2], texture_id);
} else {
f32 t = (f32)(tv[1].y - tv[0].y) / (f32)(tv[2].y - tv[0].y);
pxl8_textured_vertex v3;
v3.x = tv[0].x + (i32)(t * (tv[2].x - tv[0].x));
v3.y = tv[1].y;
v3.z = tv[0].z + t * (tv[2].z - tv[0].z);
v3.u = tv[0].u + t * (tv[2].u - tv[0].u);
v3.v = tv[0].v + t * (tv[2].v - tv[0].v);
v3.w = tv[0].w + t * (tv[2].w - tv[0].w);
pxl8_draw_flat_bottom_triangle_textured(gfx, tv[0], tv[1], v3, texture_id);
pxl8_draw_flat_top_triangle_textured(gfx, tv[1], v3, tv[2], texture_id);
}
}

View file

@ -3,6 +3,7 @@
#include "pxl8_math.h"
#include "pxl8_types.h"
typedef struct pxl8_atlas pxl8_atlas;
typedef struct pxl8_gfx pxl8_gfx;
typedef enum pxl8_blend_mode {
@ -48,6 +49,7 @@ typedef struct pxl8_effects {
typedef struct pxl8_triangle {
pxl8_vertex v[3];
u32 texture_id;
} pxl8_triangle;
#ifdef __cplusplus
@ -61,12 +63,13 @@ pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx);
pxl8_color_mode pxl8_gfx_get_color_mode(pxl8_gfx* gfx);
u8* pxl8_gfx_get_framebuffer(pxl8_gfx* gfx);
i32 pxl8_gfx_get_height(const pxl8_gfx* gfx);
u32 pxl8_gfx_get_palette_size(const pxl8_gfx* gfx);
void pxl8_gfx_get_resolution_dimensions(pxl8_resolution resolution, i32* width, i32* height);
i32 pxl8_gfx_get_width(const pxl8_gfx* gfx);
pxl8_result pxl8_gfx_init_atlas(pxl8_gfx* gfx, u32 width, u32 height);
pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx);
pxl8_result pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* path);
pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path);
pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width, u32 height);
void pxl8_gfx_present(pxl8_gfx* gfx);
void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom);
void pxl8_gfx_upload_atlas(pxl8_gfx* gfx);
@ -96,6 +99,8 @@ void pxl8_3d_clear_zbuffer(pxl8_gfx* gfx);
void pxl8_3d_draw_line_3d(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u32 color);
void pxl8_3d_draw_triangle(pxl8_gfx* gfx, pxl8_triangle tri);
void pxl8_3d_draw_triangle_raw(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, u32 color);
void pxl8_3d_draw_triangle_textured(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, f32 u0, f32 v0f, f32 u1, f32 v1f, f32 u2, f32 v2f, u32 texture_id);
void pxl8_3d_set_affine_textures(pxl8_gfx* gfx, bool affine);
void pxl8_3d_set_backface_culling(pxl8_gfx* gfx, bool culling);
void pxl8_3d_set_model(pxl8_gfx* gfx, pxl8_mat4 mat);
void pxl8_3d_set_projection(pxl8_gfx* gfx, pxl8_mat4 mat);

View file

@ -4,21 +4,21 @@
#include "pxl8_simd.h"
pxl8_vec2 pxl8_vec2_add(pxl8_vec2 a, pxl8_vec2 b) {
return (pxl8_vec2) {
return (pxl8_vec2){
.x = a.x + b.x,
.y = a.y + b.y,
};
}
pxl8_vec2 pxl8_vec2_sub(pxl8_vec2 a, pxl8_vec2 b) {
return (pxl8_vec2) {
return (pxl8_vec2){
.x = a.x - b.x,
.y = a.y - b.y,
};
}
pxl8_vec2 pxl8_vec2_scale(pxl8_vec2 v, f32 s) {
return (pxl8_vec2) {
return (pxl8_vec2){
.x = v.x * s,
.y = v.y * s,
};
@ -34,7 +34,9 @@ f32 pxl8_vec2_length(pxl8_vec2 v) {
pxl8_vec2 pxl8_vec2_normalize(pxl8_vec2 v) {
f32 len = pxl8_vec2_length(v);
if (len < 1e-6f) return (pxl8_vec2){0};
return pxl8_vec2_scale(v, 1.0f / len);
}
@ -43,7 +45,7 @@ pxl8_vec3 pxl8_vec3_add(pxl8_vec3 a, pxl8_vec3 b) {
pxl8_simd_vec_f32 vb = pxl8_simd_set_f32(b.x, b.y, b.z, 0);
pxl8_simd_vec_f32 result = pxl8_simd_add_f32(va, vb);
return (pxl8_vec3) {
return (pxl8_vec3){
.x = result.f32_array[0],
.y = result.f32_array[1],
.z = result.f32_array[2],
@ -55,7 +57,7 @@ pxl8_vec3 pxl8_vec3_sub(pxl8_vec3 a, pxl8_vec3 b) {
pxl8_simd_vec_f32 vb = pxl8_simd_set_f32(b.x, b.y, b.z, 0);
pxl8_simd_vec_f32 result = pxl8_simd_sub_f32(va, vb);
return (pxl8_vec3) {
return (pxl8_vec3){
.x = result.f32_array[0],
.y = result.f32_array[1],
.z = result.f32_array[2],
@ -66,7 +68,7 @@ pxl8_vec3 pxl8_vec3_scale(pxl8_vec3 v, f32 s) {
pxl8_simd_vec_f32 vv = pxl8_simd_set_f32(v.x, v.y, v.z, 0);
pxl8_simd_vec_f32 result = pxl8_simd_scale_f32(vv, s);
return (pxl8_vec3) {
return (pxl8_vec3){
.x = result.f32_array[0],
.y = result.f32_array[1],
.z = result.f32_array[2],
@ -76,11 +78,12 @@ pxl8_vec3 pxl8_vec3_scale(pxl8_vec3 v, f32 s) {
f32 pxl8_vec3_dot(pxl8_vec3 a, pxl8_vec3 b) {
pxl8_simd_vec_f32 va = pxl8_simd_set_f32(a.x, a.y, a.z, 0);
pxl8_simd_vec_f32 vb = pxl8_simd_set_f32(b.x, b.y, b.z, 0);
return pxl8_simd_dot3_f32(va, vb);
}
pxl8_vec3 pxl8_vec3_cross(pxl8_vec3 a, pxl8_vec3 b) {
return (pxl8_vec3) {
return (pxl8_vec3){
.x = a.y * b.z - a.z * b.y,
.y = a.z * b.x - a.x * b.z,
.z = a.x * b.y - a.y * b.x,
@ -93,18 +96,23 @@ f32 pxl8_vec3_length(pxl8_vec3 v) {
pxl8_vec3 pxl8_vec3_normalize(pxl8_vec3 v) {
f32 len = pxl8_vec3_length(v);
if (len < 1e-6f) return (pxl8_vec3){0};
return pxl8_vec3_scale(v, 1.0f / len);
}
pxl8_mat4 pxl8_mat4_identity(void) {
pxl8_mat4 mat = {0};
mat.m[0] = mat.m[5] = mat.m[10] = mat.m[15] = 1.0f;
return mat;
}
pxl8_mat4 pxl8_mat4_multiply(pxl8_mat4 a, pxl8_mat4 b) {
pxl8_mat4 result = {0};
pxl8_mat4 mat = {0};
for (i32 i = 0; i < 4; i++) {
for (i32 j = 0; j < 4; j++) {
pxl8_simd_vec_f32 row = pxl8_simd_set_f32(
@ -113,34 +121,35 @@ pxl8_mat4 pxl8_mat4_multiply(pxl8_mat4 a, pxl8_mat4 b) {
pxl8_simd_vec_f32 col = pxl8_simd_set_f32(
b.m[0 * 4 + j], b.m[1 * 4 + j], b.m[2 * 4 + j], b.m[3 * 4 + j]
);
result.m[i * 4 + j] = pxl8_simd_dot4_f32(row, col);
mat.m[i * 4 + j] = pxl8_simd_dot4_f32(row, col);
}
}
return result;
return mat;
}
pxl8_vec4 pxl8_mat4_multiply_vec4(pxl8_mat4 m, pxl8_vec4 v) {
pxl8_simd_vec_f32 vec = pxl8_simd_set_f32(v.x, v.y, v.z, v.w);
pxl8_vec4 result;
pxl8_simd_vec_f32 row0 = pxl8_simd_set_f32(m.m[0], m.m[1], m.m[2], m.m[3]);
pxl8_simd_vec_f32 row1 = pxl8_simd_set_f32(m.m[4], m.m[5], m.m[6], m.m[7]);
pxl8_simd_vec_f32 row2 = pxl8_simd_set_f32(m.m[8], m.m[9], m.m[10], m.m[11]);
pxl8_simd_vec_f32 row3 = pxl8_simd_set_f32(m.m[12], m.m[13], m.m[14], m.m[15]);
pxl8_simd_vec_f32 vec = pxl8_simd_set_f32(v.x, v.y, v.z, v.w);
result.x = pxl8_simd_dot4_f32(row0, vec);
result.y = pxl8_simd_dot4_f32(row1, vec);
result.z = pxl8_simd_dot4_f32(row2, vec);
result.w = pxl8_simd_dot4_f32(row3, vec);
return result;
return (pxl8_vec4){
.x = pxl8_simd_dot4_f32(row0, vec),
.y = pxl8_simd_dot4_f32(row1, vec),
.z = pxl8_simd_dot4_f32(row2, vec),
.w = pxl8_simd_dot4_f32(row3, vec),
};
}
pxl8_mat4 pxl8_mat4_translate(f32 x, f32 y, f32 z) {
pxl8_mat4 mat = pxl8_mat4_identity();
mat.m[3] = x;
mat.m[7] = y;
mat.m[11] = z;
return mat;
}
@ -148,10 +157,12 @@ pxl8_mat4 pxl8_mat4_rotate_x(f32 angle) {
pxl8_mat4 mat = pxl8_mat4_identity();
f32 c = cosf(angle);
f32 s = sinf(angle);
mat.m[5] = c;
mat.m[6] = -s;
mat.m[9] = s;
mat.m[10] = c;
return mat;
}
@ -159,10 +170,12 @@ pxl8_mat4 pxl8_mat4_rotate_y(f32 angle) {
pxl8_mat4 mat = pxl8_mat4_identity();
f32 c = cosf(angle);
f32 s = sinf(angle);
mat.m[0] = c;
mat.m[2] = s;
mat.m[8] = -s;
mat.m[10] = c;
return mat;
}
@ -170,18 +183,22 @@ pxl8_mat4 pxl8_mat4_rotate_z(f32 angle) {
pxl8_mat4 mat = pxl8_mat4_identity();
f32 c = cosf(angle);
f32 s = sinf(angle);
mat.m[0] = c;
mat.m[1] = -s;
mat.m[4] = s;
mat.m[5] = c;
return mat;
}
pxl8_mat4 pxl8_mat4_scale(f32 x, f32 y, f32 z) {
pxl8_mat4 mat = pxl8_mat4_identity();
mat.m[0] = x;
mat.m[5] = y;
mat.m[10] = z;
return mat;
}
@ -213,11 +230,11 @@ pxl8_mat4 pxl8_mat4_perspective(f32 fov, f32 aspect, f32 near, f32 far) {
}
pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up) {
pxl8_mat4 mat = pxl8_mat4_identity();
pxl8_vec3 f = pxl8_vec3_normalize(pxl8_vec3_sub(center, eye));
pxl8_vec3 s = pxl8_vec3_normalize(pxl8_vec3_cross(f, up));
pxl8_vec3 u = pxl8_vec3_cross(s, f);
pxl8_mat4 mat = pxl8_mat4_identity();
mat.m[0] = s.x;
mat.m[1] = s.y;
mat.m[2] = s.z;

View file

@ -56,6 +56,8 @@ static const char* pxl8_ffi_cdefs =
"void pxl8_gfx_fade_palette(pxl8_gfx* ctx, u8 start, u8 count, f32 amount, u32 target_color);\n"
"i32 pxl8_gfx_load_palette(pxl8_gfx* ctx, const char* filepath);\n"
"i32 pxl8_gfx_load_sprite(pxl8_gfx* ctx, const char* filepath, u32* sprite_id);\n"
"i32 pxl8_gfx_create_texture(pxl8_gfx* ctx, const u8* pixels, u32 width, u32 height);\n"
"void pxl8_gfx_upload_atlas(pxl8_gfx* ctx);\n"
"typedef struct pxl8_input_state pxl8_input_state;\n"
"bool pxl8_key_down(const pxl8_input_state* input, i32 key);\n"
"bool pxl8_key_pressed(const pxl8_input_state* input, i32 key);\n"
@ -133,6 +135,8 @@ static const char* pxl8_ffi_cdefs =
"void pxl8_3d_clear_zbuffer(pxl8_gfx* gfx);\n"
"void pxl8_3d_draw_line_3d(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u32 color);\n"
"void pxl8_3d_draw_triangle_raw(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, u32 color);\n"
"void pxl8_3d_draw_triangle_textured(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, f32 u0, f32 v0, f32 u1, f32 v1, f32 u2, f32 v2, u32 texture_id);\n"
"void pxl8_3d_set_affine_textures(pxl8_gfx* gfx, bool affine);\n"
"void pxl8_3d_set_backface_culling(pxl8_gfx* gfx, bool culling);\n"
"void pxl8_3d_set_model(pxl8_gfx* gfx, pxl8_mat4 mat);\n"
"void pxl8_3d_set_projection(pxl8_gfx* gfx, pxl8_mat4 mat);\n"

View file

@ -28,11 +28,11 @@ struct pxl8_particles {
void* userdata;
};
void pxl8_vfx_plasma(pxl8_gfx* ctx, f32 time, f32 scale1, f32 scale2, u8 palette_offset) {
if (!ctx || !pxl8_gfx_get_framebuffer(ctx)) return;
void pxl8_vfx_plasma(pxl8_gfx* gfx, f32 time, f32 scale1, f32 scale2, u8 palette_offset) {
if (!gfx || !pxl8_gfx_get_framebuffer(gfx)) return;
for (i32 y = 0; y < pxl8_gfx_get_height(ctx); y++) {
for (i32 x = 0; x < pxl8_gfx_get_width(ctx); x++) {
for (i32 y = 0; y < pxl8_gfx_get_height(gfx); y++) {
for (i32 x = 0; x < pxl8_gfx_get_width(gfx); x++) {
f32 v1 = sinf(x * scale1 + time);
f32 v2 = sinf(y * scale1 + time * 0.7f);
f32 v3 = sinf((x + y) * scale2 + time * 1.3f);
@ -44,13 +44,13 @@ void pxl8_vfx_plasma(pxl8_gfx* ctx, f32 time, f32 scale1, f32 scale2, u8 palette
if (normalized > 1.0f) normalized = 1.0f;
u8 color = palette_offset + (u8)(15.0f * normalized);
pxl8_pixel(ctx, x, y, color);
pxl8_pixel(gfx, x, y, color);
}
}
}
void pxl8_vfx_raster_bars(pxl8_gfx* ctx, pxl8_raster_bar* bars, u32 bar_count, f32 time) {
if (!ctx || !bars) return;
void pxl8_vfx_raster_bars(pxl8_gfx* gfx, pxl8_raster_bar* bars, u32 bar_count, f32 time) {
if (!gfx || !bars) return;
for (u32 i = 0; i < bar_count; i++) {
pxl8_raster_bar* bar = &bars[i];
@ -72,24 +72,24 @@ void pxl8_vfx_raster_bars(pxl8_gfx* ctx, pxl8_raster_bar* bars, u32 bar_count, f
color_idx = bar->color - 1;
}
pxl8_rect_fill(ctx, 0, y_int + dy, pxl8_gfx_get_width(ctx), 1, color_idx);
pxl8_rect_fill(gfx, 0, y_int + dy, pxl8_gfx_get_width(gfx), 1, color_idx);
}
}
}
void pxl8_vfx_rotozoom(pxl8_gfx* ctx, f32 angle, f32 zoom, i32 cx, i32 cy) {
if (!ctx || !pxl8_gfx_get_framebuffer(ctx)) return;
void pxl8_vfx_rotozoom(pxl8_gfx* gfx, f32 angle, f32 zoom, i32 cx, i32 cy) {
if (!gfx || !pxl8_gfx_get_framebuffer(gfx)) return;
f32 cos_a = cosf(angle);
f32 sin_a = sinf(angle);
u8* temp_buffer = (u8*)SDL_malloc(pxl8_gfx_get_width(ctx) * pxl8_gfx_get_height(ctx));
u8* temp_buffer = (u8*)SDL_malloc(pxl8_gfx_get_width(gfx) * pxl8_gfx_get_height(gfx));
if (!temp_buffer) return;
SDL_memcpy(temp_buffer, pxl8_gfx_get_framebuffer(ctx), pxl8_gfx_get_width(ctx) * pxl8_gfx_get_height(ctx));
SDL_memcpy(temp_buffer, pxl8_gfx_get_framebuffer(gfx), pxl8_gfx_get_width(gfx) * pxl8_gfx_get_height(gfx));
for (i32 y = 0; y < pxl8_gfx_get_height(ctx); y++) {
for (i32 x = 0; x < pxl8_gfx_get_width(ctx); x++) {
for (i32 y = 0; y < pxl8_gfx_get_height(gfx); y++) {
for (i32 x = 0; x < pxl8_gfx_get_width(gfx); x++) {
f32 dx = x - cx;
f32 dy = y - cy;
@ -99,8 +99,8 @@ void pxl8_vfx_rotozoom(pxl8_gfx* ctx, f32 angle, f32 zoom, i32 cx, i32 cy) {
i32 sx = (i32)src_x;
i32 sy = (i32)src_y;
if (sx >= 0 && sx < pxl8_gfx_get_width(ctx) && sy >= 0 && sy < pxl8_gfx_get_height(ctx)) {
pxl8_gfx_get_framebuffer(ctx)[y * pxl8_gfx_get_width(ctx) + x] = temp_buffer[sy * pxl8_gfx_get_width(ctx) + sx];
if (sx >= 0 && sx < pxl8_gfx_get_width(gfx) && sy >= 0 && sy < pxl8_gfx_get_height(gfx)) {
pxl8_gfx_get_framebuffer(gfx)[y * pxl8_gfx_get_width(gfx) + x] = temp_buffer[sy * pxl8_gfx_get_width(gfx) + sx];
}
}
}
@ -108,14 +108,16 @@ void pxl8_vfx_rotozoom(pxl8_gfx* ctx, f32 angle, f32 zoom, i32 cx, i32 cy) {
SDL_free(temp_buffer);
}
void pxl8_vfx_tunnel(pxl8_gfx* ctx, f32 time, f32 speed, f32 twist) {
if (!ctx || !pxl8_gfx_get_framebuffer(ctx)) return;
void pxl8_vfx_tunnel(pxl8_gfx* gfx, f32 time, f32 speed, f32 twist) {
if (!gfx || !pxl8_gfx_get_framebuffer(gfx)) return;
f32 cx = pxl8_gfx_get_width(ctx) / 2.0f;
f32 cy = pxl8_gfx_get_height(ctx) / 2.0f;
f32 cx = pxl8_gfx_get_width(gfx) / 2.0f;
f32 cy = pxl8_gfx_get_height(gfx) / 2.0f;
u32 palette_size = pxl8_gfx_get_palette_size(gfx);
if (palette_size == 0) palette_size = 256;
for (i32 y = 0; y < pxl8_gfx_get_height(ctx); y++) {
for (i32 x = 0; x < pxl8_gfx_get_width(ctx); x++) {
for (i32 y = 0; y < pxl8_gfx_get_height(gfx); y++) {
for (i32 x = 0; x < pxl8_gfx_get_width(gfx); x++) {
f32 dx = x - cx;
f32 dy = y - cy;
f32 dist = sqrtf(dx * dx + dy * dy);
@ -127,19 +129,20 @@ void pxl8_vfx_tunnel(pxl8_gfx* ctx, f32 time, f32 speed, f32 twist) {
u8 tx = (u8)((i32)(u * 256.0f) & 0xFF);
u8 ty = (u8)((i32)(v * 256.0f) & 0xFF);
u8 color = tx ^ ty;
u8 pattern = tx ^ ty;
u8 color = (pattern / 8) % palette_size;
pxl8_pixel(ctx, x, y, color);
pxl8_pixel(gfx, x, y, color);
}
}
}
}
void pxl8_vfx_water_ripple(pxl8_gfx* ctx, f32* height_map, i32 drop_x, i32 drop_y, f32 damping) {
if (!ctx || !height_map) return;
void pxl8_vfx_water_ripple(pxl8_gfx* gfx, f32* height_map, i32 drop_x, i32 drop_y, f32 damping) {
if (!gfx || !height_map) return;
i32 w = pxl8_gfx_get_width(ctx);
i32 h = pxl8_gfx_get_height(ctx);
i32 w = pxl8_gfx_get_width(gfx);
i32 h = pxl8_gfx_get_height(gfx);
static f32* prev_height = NULL;
if (!prev_height) {

View file

@ -46,8 +46,8 @@ void pxl8_vfx_snow(pxl8_particles* particles, i32 width, f32 wind);
void pxl8_vfx_sparks(pxl8_particles* particles, i32 x, i32 y, u32 color);
void pxl8_vfx_starfield(pxl8_particles* particles, f32 speed, f32 spread);
void pxl8_vfx_plasma(pxl8_gfx* ctx, f32 time, f32 scale1, f32 scale2, u8 palette_offset);
void pxl8_vfx_raster_bars(pxl8_gfx* ctx, pxl8_raster_bar* bars, u32 bar_count, f32 time);
void pxl8_vfx_rotozoom(pxl8_gfx* ctx, f32 angle, f32 zoom, i32 cx, i32 cy);
void pxl8_vfx_tunnel(pxl8_gfx* ctx, f32 time, f32 speed, f32 twist);
void pxl8_vfx_water_ripple(pxl8_gfx* ctx, f32* height_map, i32 drop_x, i32 drop_y, f32 damping);
void pxl8_vfx_plasma(pxl8_gfx* gfx, f32 time, f32 scale1, f32 scale2, u8 palette_offset);
void pxl8_vfx_raster_bars(pxl8_gfx* gfx, pxl8_raster_bar* bars, u32 bar_count, f32 time);
void pxl8_vfx_rotozoom(pxl8_gfx* gfx, f32 angle, f32 zoom, i32 cx, i32 cy);
void pxl8_vfx_tunnel(pxl8_gfx* gfx, f32 time, f32 speed, f32 twist);
void pxl8_vfx_water_ripple(pxl8_gfx* gfx, f32* height_map, i32 drop_x, i32 drop_y, f32 damping);