2025-10-04 04:13:48 -05:00
|
|
|
#include <math.h>
|
2025-10-01 12:56:13 -05:00
|
|
|
#include <stdlib.h>
|
2025-10-04 04:13:48 -05:00
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
|
|
#include <SDL3/SDL.h>
|
2025-10-01 12:56:13 -05:00
|
|
|
|
2025-08-13 15:04:49 -05:00
|
|
|
#include "pxl8_ase.h"
|
2025-10-01 12:56:13 -05:00
|
|
|
#include "pxl8_blit.h"
|
|
|
|
|
#include "pxl8_gfx.h"
|
2025-08-13 15:04:49 -05:00
|
|
|
#include "pxl8_macros.h"
|
2025-10-04 04:13:48 -05:00
|
|
|
#include "pxl8_math.h"
|
2025-08-13 15:04:49 -05:00
|
|
|
#include "pxl8_types.h"
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
i32 viewport_height;
|
|
|
|
|
i32 viewport_width;
|
|
|
|
|
i32 viewport_x;
|
|
|
|
|
i32 viewport_y;
|
|
|
|
|
|
|
|
|
|
bool backface_culling;
|
|
|
|
|
pxl8_mat4 model;
|
|
|
|
|
pxl8_mat4 projection;
|
|
|
|
|
pxl8_mat4 view;
|
|
|
|
|
bool wireframe;
|
|
|
|
|
f32* zbuffer;
|
|
|
|
|
i32 zbuffer_height;
|
|
|
|
|
i32 zbuffer_width;
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-13 15:04:49 -05:00
|
|
|
static u32 pxl8_get_palette_size(pxl8_color_mode mode) {
|
|
|
|
|
switch (mode) {
|
|
|
|
|
case PXL8_COLOR_MODE_HICOLOR: return 0;
|
|
|
|
|
case PXL8_COLOR_MODE_FAMI: return 64;
|
|
|
|
|
case PXL8_COLOR_MODE_MEGA: return 512;
|
|
|
|
|
case PXL8_COLOR_MODE_GBA: return 32768;
|
|
|
|
|
case PXL8_COLOR_MODE_SUPERFAMI: return 32768;
|
|
|
|
|
default: return 256;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_gfx_get_resolution_dimensions(pxl8_resolution resolution, i32* width, i32* height) {
|
|
|
|
|
switch (resolution) {
|
|
|
|
|
case PXL8_RESOLUTION_240x160:
|
|
|
|
|
*width = 240; *height = 160; break;
|
|
|
|
|
case PXL8_RESOLUTION_320x180:
|
|
|
|
|
*width = 320; *height = 180; break;
|
|
|
|
|
case PXL8_RESOLUTION_320x240:
|
|
|
|
|
*width = 320; *height = 240; break;
|
|
|
|
|
case PXL8_RESOLUTION_640x360:
|
|
|
|
|
*width = 640; *height = 360; break;
|
|
|
|
|
case PXL8_RESOLUTION_640x480:
|
|
|
|
|
*width = 640; *height = 480; break;
|
|
|
|
|
case PXL8_RESOLUTION_800x600:
|
|
|
|
|
*width = 800; *height = 600; break;
|
|
|
|
|
case PXL8_RESOLUTION_960x540:
|
|
|
|
|
*width = 960; *height = 540; break;
|
|
|
|
|
default:
|
|
|
|
|
*width = 640; *height = 360; break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx) {
|
|
|
|
|
pxl8_bounds bounds = {0};
|
|
|
|
|
if (!gfx || !gfx->window) {
|
|
|
|
|
return bounds;
|
|
|
|
|
}
|
|
|
|
|
SDL_GetWindowPosition(gfx->window, &bounds.x, &bounds.y);
|
|
|
|
|
SDL_GetWindowSize(gfx->window, &bounds.w, &bounds.h);
|
|
|
|
|
return bounds;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_color_mode pxl8_gfx_get_color_mode(pxl8_gfx* gfx) {
|
|
|
|
|
return gfx ? gfx->color_mode : PXL8_COLOR_MODE_FAMI;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
u8* pxl8_gfx_get_framebuffer(pxl8_gfx* gfx) {
|
|
|
|
|
return gfx ? gfx->framebuffer : NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
i32 pxl8_gfx_get_height(const pxl8_gfx* gfx) {
|
|
|
|
|
return gfx ? gfx->framebuffer_height : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
i32 pxl8_gfx_get_width(const pxl8_gfx* gfx) {
|
|
|
|
|
return gfx ? gfx->framebuffer_width : 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) {
|
|
|
|
|
pxl8_error("Failed to allocate graphics context");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gfx->color_mode = mode;
|
|
|
|
|
pxl8_gfx_get_resolution_dimensions(resolution, &gfx->framebuffer_width, &gfx->framebuffer_height);
|
|
|
|
|
|
|
|
|
|
gfx->window = SDL_CreateWindow(
|
2025-08-13 15:04:49 -05:00
|
|
|
title,
|
|
|
|
|
window_width, window_height,
|
|
|
|
|
SDL_WINDOW_RESIZABLE
|
|
|
|
|
);
|
2025-10-04 04:13:48 -05:00
|
|
|
|
|
|
|
|
if (!gfx->window) {
|
2025-08-13 15:04:49 -05:00
|
|
|
pxl8_error("Failed to create window: %s", SDL_GetError());
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_gfx_destroy(gfx);
|
|
|
|
|
return NULL;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
2025-10-04 04:13:48 -05:00
|
|
|
|
|
|
|
|
gfx->renderer = SDL_CreateRenderer(gfx->window, NULL);
|
|
|
|
|
if (!gfx->renderer) {
|
2025-08-13 15:04:49 -05:00
|
|
|
pxl8_error("Failed to create renderer: %s", SDL_GetError());
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_gfx_destroy(gfx);
|
|
|
|
|
return NULL;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
2025-10-04 04:13:48 -05:00
|
|
|
|
|
|
|
|
SDL_SetRenderLogicalPresentation(gfx->renderer,
|
|
|
|
|
gfx->framebuffer_width,
|
|
|
|
|
gfx->framebuffer_height,
|
2025-08-13 15:04:49 -05:00
|
|
|
SDL_LOGICAL_PRESENTATION_INTEGER_SCALE);
|
2025-10-04 04:13:48 -05:00
|
|
|
|
|
|
|
|
i32 bytes_per_pixel = (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1;
|
|
|
|
|
i32 fb_size = gfx->framebuffer_width * gfx->framebuffer_height * bytes_per_pixel;
|
|
|
|
|
gfx->framebuffer = (u8*)SDL_calloc(1, fb_size);
|
|
|
|
|
if (!gfx->framebuffer) {
|
2025-08-13 15:04:49 -05:00
|
|
|
pxl8_error("Failed to allocate framebuffer");
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_gfx_destroy(gfx);
|
|
|
|
|
return NULL;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
2025-10-04 04:13:48 -05:00
|
|
|
|
|
|
|
|
gfx->framebuffer_texture = SDL_CreateTexture(
|
|
|
|
|
gfx->renderer,
|
2025-08-13 15:04:49 -05:00
|
|
|
SDL_PIXELFORMAT_RGBA32,
|
|
|
|
|
SDL_TEXTUREACCESS_STREAMING,
|
2025-10-04 04:13:48 -05:00
|
|
|
gfx->framebuffer_width,
|
|
|
|
|
gfx->framebuffer_height
|
2025-08-13 15:04:49 -05:00
|
|
|
);
|
2025-10-04 04:13:48 -05:00
|
|
|
|
|
|
|
|
SDL_SetTextureScaleMode(gfx->framebuffer_texture, SDL_SCALEMODE_NEAREST);
|
|
|
|
|
|
|
|
|
|
if (!gfx->framebuffer_texture) {
|
2025-08-13 15:04:49 -05:00
|
|
|
pxl8_error("Failed to create framebuffer texture: %s", SDL_GetError());
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_gfx_destroy(gfx);
|
|
|
|
|
return NULL;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
2025-10-04 04:13:48 -05:00
|
|
|
|
|
|
|
|
gfx->palette_size = pxl8_get_palette_size(mode);
|
|
|
|
|
if (gfx->palette_size > 0) {
|
|
|
|
|
gfx->palette = (u32*)SDL_calloc(gfx->palette_size, sizeof(u32));
|
|
|
|
|
if (!gfx->palette) {
|
2025-08-13 15:04:49 -05:00
|
|
|
pxl8_error("Failed to allocate palette");
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_gfx_destroy(gfx);
|
|
|
|
|
return NULL;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
2025-10-04 04:13:48 -05:00
|
|
|
|
|
|
|
|
for (u32 i = 0; i < 256 && i < gfx->palette_size; i++) {
|
2025-08-13 15:04:49 -05:00
|
|
|
u8 gray = (u8)(i * 255 / 255);
|
2025-10-04 04:13:48 -05:00
|
|
|
gfx->palette[i] = 0xFF000000 | (gray << 16) | (gray << 8) | gray;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
2025-10-04 04:13:48 -05:00
|
|
|
|
|
|
|
|
gfx->viewport_x = 0;
|
|
|
|
|
gfx->viewport_y = 0;
|
|
|
|
|
gfx->viewport_width = gfx->framebuffer_width;
|
|
|
|
|
gfx->viewport_height = gfx->framebuffer_height;
|
|
|
|
|
|
|
|
|
|
gfx->backface_culling = true;
|
|
|
|
|
gfx->model = pxl8_mat4_identity();
|
|
|
|
|
gfx->projection = pxl8_mat4_identity();
|
|
|
|
|
gfx->view = pxl8_mat4_identity();
|
|
|
|
|
gfx->wireframe = false;
|
|
|
|
|
gfx->zbuffer = NULL;
|
|
|
|
|
gfx->zbuffer_height = 0;
|
|
|
|
|
gfx->zbuffer_width = 0;
|
|
|
|
|
|
|
|
|
|
gfx->initialized = true;
|
|
|
|
|
return gfx;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_gfx_destroy(pxl8_gfx* gfx) {
|
|
|
|
|
if (!gfx) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
if (gfx->framebuffer_texture) {
|
|
|
|
|
SDL_DestroyTexture(gfx->framebuffer_texture);
|
|
|
|
|
gfx->framebuffer_texture = NULL;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
if (gfx->sprite_atlas_texture) {
|
|
|
|
|
SDL_DestroyTexture(gfx->sprite_atlas_texture);
|
|
|
|
|
gfx->sprite_atlas_texture = NULL;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
if (gfx->renderer) {
|
|
|
|
|
SDL_DestroyRenderer(gfx->renderer);
|
|
|
|
|
gfx->renderer = NULL;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
if (gfx->window) {
|
|
|
|
|
SDL_DestroyWindow(gfx->window);
|
|
|
|
|
gfx->window = NULL;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
SDL_free(gfx->framebuffer);
|
|
|
|
|
SDL_free(gfx->palette);
|
|
|
|
|
SDL_free(gfx->atlas);
|
|
|
|
|
SDL_free(gfx->atlas_entries);
|
|
|
|
|
SDL_free(gfx->zbuffer);
|
|
|
|
|
|
|
|
|
|
SDL_free(gfx);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-09-28 13:10:29 -05:00
|
|
|
// resource loading
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_result pxl8_gfx_init_atlas(pxl8_gfx* gfx, u32 width, u32 height) {
|
|
|
|
|
if (!gfx || !gfx->initialized) return PXL8_ERROR_INVALID_ARGUMENT;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
gfx->sprite_atlas_width = width;
|
|
|
|
|
gfx->sprite_atlas_height = height;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
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) {
|
2025-08-13 15:04:49 -05:00
|
|
|
return PXL8_ERROR_OUT_OF_MEMORY;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
gfx->sprite_atlas_texture = SDL_CreateTexture(
|
|
|
|
|
gfx->renderer,
|
2025-08-13 15:04:49 -05:00
|
|
|
SDL_PIXELFORMAT_RGBA32,
|
|
|
|
|
SDL_TEXTUREACCESS_STREAMING,
|
|
|
|
|
width,
|
|
|
|
|
height
|
|
|
|
|
);
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
if (!gfx->sprite_atlas_texture) {
|
|
|
|
|
SDL_free(gfx->atlas);
|
|
|
|
|
gfx->atlas = NULL;
|
2025-08-13 15:04:49 -05:00
|
|
|
return PXL8_ERROR_SYSTEM_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
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);
|
2025-08-13 15:04:49 -05:00
|
|
|
return PXL8_ERROR_OUT_OF_MEMORY;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
gfx->sprite_frames_per_row = width / 128;
|
|
|
|
|
if (gfx->sprite_frames_per_row == 0) gfx->sprite_frames_per_row = 1;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
return PXL8_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
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) {
|
2025-08-13 15:04:49 -05:00
|
|
|
pxl8_error("Atlas not initialized");
|
|
|
|
|
return PXL8_ERROR_NOT_INITIALIZED;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
for (u32 i = 0; i < gfx->atlas_entries_len; i++) {
|
|
|
|
|
if (strcmp(gfx->atlas_entries[i].path, path) == 0) {
|
2025-08-13 15:04:49 -05:00
|
|
|
return PXL8_OK;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_ase_file ase_file;
|
|
|
|
|
pxl8_result result = pxl8_ase_load(path, &ase_file);
|
|
|
|
|
if (result != PXL8_OK) {
|
|
|
|
|
pxl8_error("Failed to load ASE file: %s", path);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ase_file.frame_count == 0) {
|
|
|
|
|
pxl8_error("No frames in ASE file");
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_ase_destroy(&ase_file);
|
2025-08-13 15:04:49 -05:00
|
|
|
return PXL8_ERROR_INVALID_FORMAT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
u32 sprite_w = ase_file.header.width;
|
|
|
|
|
u32 sprite_h = ase_file.header.height;
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
if (gfx->sprite_frame_width == 0 || gfx->sprite_frame_height == 0) {
|
|
|
|
|
gfx->sprite_frame_width = sprite_w;
|
|
|
|
|
gfx->sprite_frame_height = sprite_h;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
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;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
if (atlas_x + sprite_w > gfx->sprite_atlas_width ||
|
|
|
|
|
atlas_y + sprite_h > gfx->sprite_atlas_height) {
|
2025-08-13 15:04:49 -05:00
|
|
|
pxl8_error("Sprite doesn't fit in atlas");
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_ase_destroy(&ase_file);
|
2025-08-13 15:04:49 -05:00
|
|
|
return PXL8_ERROR_INVALID_SIZE;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
i32 bytes_per_pixel = (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
for (u32 y = 0; y < sprite_h; y++) {
|
|
|
|
|
for (u32 x = 0; x < sprite_w; x++) {
|
|
|
|
|
u32 frame_idx = y * sprite_w + x;
|
2025-10-04 04:13:48 -05:00
|
|
|
u32 atlas_idx = (atlas_y + y) * gfx->sprite_atlas_width + (atlas_x + x);
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
if (bytes_per_pixel == 4) {
|
2025-10-04 04:13:48 -05:00
|
|
|
((u32*)gfx->atlas)[atlas_idx] = ((u32*)ase_file.frames[0].pixels)[frame_idx];
|
2025-08-13 15:04:49 -05:00
|
|
|
} else {
|
2025-10-04 04:13:48 -05:00
|
|
|
gfx->atlas[atlas_idx] = ase_file.frames[0].pixels[frame_idx];
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
if (gfx->atlas_entries_len >= gfx->atlas_entries_cap) {
|
|
|
|
|
gfx->atlas_entries_cap *= 2;
|
2025-08-13 15:04:49 -05:00
|
|
|
pxl8_atlas_entry* new_entries = (pxl8_atlas_entry*)SDL_realloc(
|
2025-10-04 04:13:48 -05:00
|
|
|
gfx->atlas_entries,
|
|
|
|
|
gfx->atlas_entries_cap * sizeof(pxl8_atlas_entry)
|
2025-08-13 15:04:49 -05:00
|
|
|
);
|
|
|
|
|
if (!new_entries) {
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_ase_destroy(&ase_file);
|
2025-08-13 15:04:49 -05:00
|
|
|
return PXL8_ERROR_OUT_OF_MEMORY;
|
|
|
|
|
}
|
2025-10-04 04:13:48 -05:00
|
|
|
gfx->atlas_entries = new_entries;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
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++;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
gfx->atlas_dirty = true;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_ase_destroy(&ase_file);
|
2025-08-13 15:04:49 -05:00
|
|
|
pxl8_debug("Loaded sprite %u: %ux%u at (%u,%u)", id, sprite_w, sprite_h, atlas_x, atlas_y);
|
|
|
|
|
|
|
|
|
|
return id;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_result pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* path) {
|
|
|
|
|
if (!gfx || !gfx->initialized || !path) return PXL8_ERROR_INVALID_ARGUMENT;
|
|
|
|
|
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) return PXL8_OK;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
pxl8_debug("Loading palette from: %s", path);
|
|
|
|
|
|
|
|
|
|
pxl8_ase_file ase_file;
|
|
|
|
|
pxl8_result result = pxl8_ase_load(path, &ase_file);
|
|
|
|
|
if (result != PXL8_OK) {
|
|
|
|
|
pxl8_error("Failed to load ASE file for palette: %s", path);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ase_file.palette.entry_count == 0) {
|
|
|
|
|
pxl8_error("No palette data in ASE file");
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_ase_destroy(&ase_file);
|
2025-08-13 15:04:49 -05:00
|
|
|
return PXL8_ERROR_INVALID_FORMAT;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
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));
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
if (gfx->color_mode != PXL8_COLOR_MODE_HICOLOR && gfx->framebuffer_texture) {
|
2025-08-13 15:04:49 -05:00
|
|
|
SDL_Color colors[256];
|
2025-10-04 04:13:48 -05:00
|
|
|
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;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
SDL_SetPaletteColors(SDL_CreateSurfacePalette(NULL), colors, 0, 256);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_ase_destroy(&ase_file);
|
2025-08-13 15:04:49 -05:00
|
|
|
pxl8_debug("Loaded palette with %u colors", copy_size);
|
|
|
|
|
|
|
|
|
|
return PXL8_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx) {
|
|
|
|
|
(void)gfx;
|
2025-08-13 15:04:49 -05:00
|
|
|
return PXL8_OK;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 13:10:29 -05:00
|
|
|
// rendering pipeline
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) {
|
|
|
|
|
if (!gfx || !gfx->initialized || !gfx->framebuffer_texture) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
|
|
|
|
|
SDL_UpdateTexture(gfx->framebuffer_texture, NULL, gfx->framebuffer,
|
|
|
|
|
gfx->framebuffer_width * 4);
|
2025-08-13 15:04:49 -05:00
|
|
|
} else {
|
|
|
|
|
static u32* rgba_buffer = NULL;
|
|
|
|
|
static size_t buffer_size = 0;
|
2025-10-04 04:13:48 -05:00
|
|
|
size_t needed_size = gfx->framebuffer_width * gfx->framebuffer_height;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
if (buffer_size < needed_size) {
|
|
|
|
|
rgba_buffer = (u32*)SDL_realloc(rgba_buffer, needed_size * 4);
|
|
|
|
|
buffer_size = needed_size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!rgba_buffer) return;
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
for (i32 i = 0; i < gfx->framebuffer_width * gfx->framebuffer_height; i++) {
|
|
|
|
|
u8 index = gfx->framebuffer[i];
|
|
|
|
|
rgba_buffer[i] = (index < gfx->palette_size) ? gfx->palette[index] : 0xFF000000;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
SDL_UpdateTexture(gfx->framebuffer_texture, NULL, rgba_buffer,
|
|
|
|
|
gfx->framebuffer_width * 4);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_gfx_upload_atlas(pxl8_gfx* gfx) {
|
|
|
|
|
if (!gfx || !gfx->initialized || !gfx->sprite_atlas_texture || !gfx->atlas_dirty) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
|
|
|
|
|
SDL_UpdateTexture(gfx->sprite_atlas_texture, NULL, gfx->atlas,
|
|
|
|
|
gfx->sprite_atlas_width * 4);
|
2025-08-13 15:04:49 -05:00
|
|
|
} else {
|
2025-10-04 04:13:48 -05:00
|
|
|
u32* rgba_buffer = (u32*)SDL_malloc(gfx->sprite_atlas_width * gfx->sprite_atlas_height * 4);
|
2025-08-13 15:04:49 -05:00
|
|
|
if (!rgba_buffer) return;
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
for (u32 i = 0; i < gfx->sprite_atlas_width * gfx->sprite_atlas_height; i++) {
|
|
|
|
|
u8 index = gfx->atlas[i];
|
|
|
|
|
rgba_buffer[i] = (index < gfx->palette_size) ? gfx->palette[index] : 0x00000000;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
SDL_UpdateTexture(gfx->sprite_atlas_texture, NULL, rgba_buffer,
|
|
|
|
|
gfx->sprite_atlas_width * 4);
|
2025-08-13 15:04:49 -05:00
|
|
|
SDL_free(rgba_buffer);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
gfx->atlas_dirty = false;
|
2025-08-13 15:04:49 -05:00
|
|
|
pxl8_debug("Atlas uploaded to GPU");
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_gfx_present(pxl8_gfx* gfx) {
|
|
|
|
|
if (!gfx || !gfx->initialized) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
SDL_SetRenderDrawColor(gfx->renderer, 0, 0, 0, 255);
|
|
|
|
|
SDL_RenderClear(gfx->renderer);
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
if (gfx->framebuffer_texture) {
|
|
|
|
|
SDL_RenderTexture(gfx->renderer, gfx->framebuffer_texture, NULL, NULL);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
SDL_RenderPresent(gfx->renderer);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_gfx_viewport(pxl8_gfx* gfx, i32 x, i32 y, i32 width, i32 height) {
|
|
|
|
|
if (!gfx) return;
|
|
|
|
|
gfx->viewport_x = x;
|
|
|
|
|
gfx->viewport_y = y;
|
|
|
|
|
gfx->viewport_width = width;
|
|
|
|
|
gfx->viewport_height = height;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom) {
|
|
|
|
|
(void)gfx; (void)left; (void)right; (void)top; (void)bottom;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-09-28 13:10:29 -05:00
|
|
|
// drawing primitives
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_clr(pxl8_gfx* gfx, u32 color) {
|
|
|
|
|
if (!gfx || !gfx->framebuffer) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
i32 bytes_per_pixel = (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) ? 4 : 1;
|
|
|
|
|
i32 size = gfx->framebuffer_width * gfx->framebuffer_height;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
if (bytes_per_pixel == 4) {
|
2025-10-04 04:13:48 -05:00
|
|
|
u32* fb32 = (u32*)gfx->framebuffer;
|
2025-08-13 15:04:49 -05:00
|
|
|
for (i32 i = 0; i < size; i++) {
|
|
|
|
|
fb32[i] = color;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2025-10-04 04:13:48 -05:00
|
|
|
memset(gfx->framebuffer, color & 0xFF, size);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_pixel(pxl8_gfx* gfx, i32 x, i32 y, u32 color) {
|
|
|
|
|
if (!gfx || !gfx->framebuffer) return;
|
|
|
|
|
if (x < 0 || x >= gfx->framebuffer_width || y < 0 || y >= gfx->framebuffer_height) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
i32 idx = y * gfx->framebuffer_width + x;
|
|
|
|
|
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
|
|
|
|
|
((u32*)gfx->framebuffer)[idx] = color;
|
2025-08-13 15:04:49 -05:00
|
|
|
} else {
|
2025-10-04 04:13:48 -05:00
|
|
|
gfx->framebuffer[idx] = color & 0xFF;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
u32 pxl8_get_pixel(pxl8_gfx* gfx, i32 x, i32 y) {
|
|
|
|
|
if (!gfx || !gfx->framebuffer) return 0;
|
|
|
|
|
if (x < 0 || x >= gfx->framebuffer_width || y < 0 || y >= gfx->framebuffer_height) return 0;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
i32 idx = y * gfx->framebuffer_width + x;
|
|
|
|
|
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
|
|
|
|
|
return ((u32*)gfx->framebuffer)[idx];
|
2025-08-13 15:04:49 -05:00
|
|
|
} else {
|
2025-10-04 04:13:48 -05:00
|
|
|
return gfx->framebuffer[idx];
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_line(pxl8_gfx* gfx, i32 x0, i32 y0, i32 x1, i32 y1, u32 color) {
|
|
|
|
|
if (!gfx) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
i32 dx = abs(x1 - x0);
|
|
|
|
|
i32 dy = abs(y1 - y0);
|
|
|
|
|
i32 sx = x0 < x1 ? 1 : -1;
|
|
|
|
|
i32 sy = y0 < y1 ? 1 : -1;
|
|
|
|
|
i32 err = dx - dy;
|
|
|
|
|
|
|
|
|
|
while (1) {
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_pixel(gfx, x0, y0, color);
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
if (x0 == x1 && y0 == y1) break;
|
|
|
|
|
|
|
|
|
|
i32 e2 = 2 * err;
|
|
|
|
|
if (e2 > -dy) {
|
|
|
|
|
err -= dy;
|
|
|
|
|
x0 += sx;
|
|
|
|
|
}
|
|
|
|
|
if (e2 < dx) {
|
|
|
|
|
err += dx;
|
|
|
|
|
y0 += sy;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_rect(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color) {
|
|
|
|
|
if (!gfx) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_line(gfx, x, y, x + w - 1, y, color);
|
|
|
|
|
pxl8_line(gfx, x + w - 1, y, x + w - 1, y + h - 1, color);
|
|
|
|
|
pxl8_line(gfx, x + w - 1, y + h - 1, x, y + h - 1, color);
|
|
|
|
|
pxl8_line(gfx, x, y + h - 1, x, y, color);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_rect_fill(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color) {
|
|
|
|
|
if (!gfx) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
for (i32 py = y; py < y + h; py++) {
|
|
|
|
|
for (i32 px = x; px < x + w; px++) {
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_pixel(gfx, px, py, color);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_circle(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color) {
|
|
|
|
|
if (!gfx) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
i32 x = radius;
|
|
|
|
|
i32 y = 0;
|
|
|
|
|
i32 err = 0;
|
|
|
|
|
|
|
|
|
|
while (x >= y) {
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_pixel(gfx, cx + x, cy + y, color);
|
|
|
|
|
pxl8_pixel(gfx, cx + y, cy + x, color);
|
|
|
|
|
pxl8_pixel(gfx, cx - y, cy + x, color);
|
|
|
|
|
pxl8_pixel(gfx, cx - x, cy + y, color);
|
|
|
|
|
pxl8_pixel(gfx, cx - x, cy - y, color);
|
|
|
|
|
pxl8_pixel(gfx, cx - y, cy - x, color);
|
|
|
|
|
pxl8_pixel(gfx, cx + y, cy - x, color);
|
|
|
|
|
pxl8_pixel(gfx, cx + x, cy - y, color);
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
if (err <= 0) {
|
|
|
|
|
y += 1;
|
|
|
|
|
err += 2 * y + 1;
|
|
|
|
|
} else {
|
|
|
|
|
x -= 1;
|
|
|
|
|
err -= 2 * x + 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_circle_fill(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color) {
|
|
|
|
|
if (!gfx) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
for (i32 y = -radius; y <= radius; y++) {
|
|
|
|
|
for (i32 x = -radius; x <= radius; x++) {
|
|
|
|
|
if (x * x + y * y <= radius * radius) {
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_pixel(gfx, cx + x, cy + y, color);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) {
|
|
|
|
|
if (!gfx || !text) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
(void)x; (void)y; (void)color;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
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;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_atlas_entry* entry = &gfx->atlas_entries[sprite_id];
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
i32 clip_left = (x < 0) ? -x : 0;
|
|
|
|
|
i32 clip_top = (y < 0) ? -y : 0;
|
2025-10-04 04:13:48 -05:00
|
|
|
i32 clip_right = (x + w > gfx->framebuffer_width) ? x + w - gfx->framebuffer_width : 0;
|
|
|
|
|
i32 clip_bottom = (y + h > gfx->framebuffer_height) ? y + h - gfx->framebuffer_height : 0;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
i32 draw_width = w - clip_left - clip_right;
|
|
|
|
|
i32 draw_height = h - clip_top - clip_bottom;
|
|
|
|
|
|
|
|
|
|
if (draw_width <= 0 || draw_height <= 0) return;
|
|
|
|
|
|
|
|
|
|
i32 dest_x = x + clip_left;
|
|
|
|
|
i32 dest_y = y + clip_top;
|
|
|
|
|
|
|
|
|
|
bool is_1to1_scale = (w == entry->w && h == entry->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)) {
|
2025-10-04 04:13:48 -05:00
|
|
|
const u8* sprite_data = gfx->atlas + entry->y * gfx->sprite_atlas_width + entry->x;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
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);
|
2025-08-13 15:04:49 -05:00
|
|
|
} else {
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_blit_simd_indexed(gfx->framebuffer, gfx->framebuffer_width,
|
|
|
|
|
sprite_data, gfx->sprite_atlas_width, x, y, w, h);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
} 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;
|
2025-10-04 04:13:48 -05:00
|
|
|
i32 src_idx = src_y * gfx->sprite_atlas_width + src_x;
|
|
|
|
|
i32 dest_idx = (dest_y + py) * gfx->framebuffer_width + (dest_x + px);
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
|
|
|
|
|
u32 pixel = ((u32*)gfx->atlas)[src_idx];
|
2025-08-13 15:04:49 -05:00
|
|
|
if (pixel & 0xFF000000) {
|
2025-10-04 04:13:48 -05:00
|
|
|
((u32*)gfx->framebuffer)[dest_idx] = pixel;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
} else {
|
2025-10-04 04:13:48 -05:00
|
|
|
u8 pixel = gfx->atlas[src_idx];
|
2025-08-13 15:04:49 -05:00
|
|
|
if (pixel != 0) {
|
2025-10-04 04:13:48 -05:00
|
|
|
gfx->framebuffer[dest_idx] = pixel;
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-28 13:10:29 -05:00
|
|
|
// palette effects
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_gfx_cycle_palette(pxl8_gfx* gfx, u8 start, u8 count, i32 step) {
|
|
|
|
|
if (!gfx || !gfx->palette || count == 0) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
u32 temp[256];
|
|
|
|
|
for (u8 i = 0; i < count; i++) {
|
2025-10-04 04:13:48 -05:00
|
|
|
temp[i] = gfx->palette[start + i];
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (u8 i = 0; i < count; i++) {
|
|
|
|
|
u8 src_idx = i;
|
|
|
|
|
u8 dst_idx = (i + step) % count;
|
2025-10-04 04:13:48 -05:00
|
|
|
gfx->palette[start + dst_idx] = temp[src_idx];
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_gfx_swap_palette(pxl8_gfx* gfx, u8 start, u8 count, u32* new_colors) {
|
|
|
|
|
if (!gfx || !gfx->palette || !new_colors) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
for (u8 i = 0; i < count && (start + i) < gfx->palette_size; i++) {
|
|
|
|
|
gfx->palette[start + i] = new_colors[i];
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_gfx_fade_palette(pxl8_gfx* gfx, u8 start, u8 count, f32 amount, u32 target_color) {
|
|
|
|
|
if (!gfx || !gfx->palette || count == 0) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
if (amount < 0.0f) amount = 0.0f;
|
|
|
|
|
if (amount > 1.0f) amount = 1.0f;
|
|
|
|
|
|
|
|
|
|
u8 target_r = target_color & 0xFF;
|
|
|
|
|
u8 target_g = (target_color >> 8) & 0xFF;
|
|
|
|
|
u8 target_b = (target_color >> 16) & 0xFF;
|
|
|
|
|
u8 target_a = (target_color >> 24) & 0xFF;
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
for (u8 i = 0; i < count && (start + i) < gfx->palette_size; i++) {
|
|
|
|
|
u32 current = gfx->palette[start + i];
|
2025-08-13 15:04:49 -05:00
|
|
|
u8 cur_r = current & 0xFF;
|
|
|
|
|
u8 cur_g = (current >> 8) & 0xFF;
|
|
|
|
|
u8 cur_b = (current >> 16) & 0xFF;
|
|
|
|
|
u8 cur_a = (current >> 24) & 0xFF;
|
|
|
|
|
|
|
|
|
|
u8 new_r = cur_r + (i32)((target_r - cur_r) * amount);
|
|
|
|
|
u8 new_g = cur_g + (i32)((target_g - cur_g) * amount);
|
|
|
|
|
u8 new_b = cur_b + (i32)((target_b - cur_b) * amount);
|
|
|
|
|
u8 new_a = cur_a + (i32)((target_a - cur_a) * amount);
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
gfx->palette[start + i] = new_r | (new_g << 8) | (new_b << 16) | (new_a << 24);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_gfx_interpolate_palettes(pxl8_gfx* gfx, u32* palette1, u32* palette2, u8 start, u8 count, f32 t) {
|
|
|
|
|
if (!gfx || !gfx->palette || !palette1 || !palette2 || count == 0) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
if (t < 0.0f) t = 0.0f;
|
|
|
|
|
if (t > 1.0f) t = 1.0f;
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
for (u8 i = 0; i < count && (start + i) < gfx->palette_size; i++) {
|
2025-08-13 15:04:49 -05:00
|
|
|
u32 col1 = palette1[i];
|
|
|
|
|
u32 col2 = palette2[i];
|
|
|
|
|
|
|
|
|
|
u8 r1 = col1 & 0xFF;
|
|
|
|
|
u8 g1 = (col1 >> 8) & 0xFF;
|
|
|
|
|
u8 b1 = (col1 >> 16) & 0xFF;
|
|
|
|
|
u8 a1 = (col1 >> 24) & 0xFF;
|
|
|
|
|
|
|
|
|
|
u8 r2 = col2 & 0xFF;
|
|
|
|
|
u8 g2 = (col2 >> 8) & 0xFF;
|
|
|
|
|
u8 b2 = (col2 >> 16) & 0xFF;
|
|
|
|
|
u8 a2 = (col2 >> 24) & 0xFF;
|
|
|
|
|
|
|
|
|
|
u8 r = r1 + (i32)((r2 - r1) * t);
|
|
|
|
|
u8 g = g1 + (i32)((g2 - g1) * t);
|
|
|
|
|
u8 b = b1 + (i32)((b2 - b1) * t);
|
|
|
|
|
u8 a = a1 + (i32)((a2 - a1) * t);
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
gfx->palette[start + i] = r | (g << 8) | (b << 16) | (a << 24);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_gfx_color_ramp(pxl8_gfx* gfx, u8 start, u8 count, u32 from_color, u32 to_color) {
|
|
|
|
|
if (!gfx || !gfx->palette || count <= 1) return;
|
2025-08-13 15:04:49 -05:00
|
|
|
|
|
|
|
|
u8 from_r = from_color & 0xFF;
|
|
|
|
|
u8 from_g = (from_color >> 8) & 0xFF;
|
|
|
|
|
u8 from_b = (from_color >> 16) & 0xFF;
|
|
|
|
|
u8 from_a = (from_color >> 24) & 0xFF;
|
|
|
|
|
|
|
|
|
|
u8 to_r = to_color & 0xFF;
|
|
|
|
|
u8 to_g = (to_color >> 8) & 0xFF;
|
|
|
|
|
u8 to_b = (to_color >> 16) & 0xFF;
|
|
|
|
|
u8 to_a = (to_color >> 24) & 0xFF;
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
for (u8 i = 0; i < count && (start + i) < gfx->palette_size; i++) {
|
2025-08-13 15:04:49 -05:00
|
|
|
f32 t = (f32)i / (f32)(count - 1);
|
|
|
|
|
|
|
|
|
|
u8 r = from_r + (i32)((to_r - from_r) * t);
|
|
|
|
|
u8 g = from_g + (i32)((to_g - from_g) * t);
|
|
|
|
|
u8 b = from_b + (i32)((to_b - from_b) * t);
|
|
|
|
|
u8 a = from_a + (i32)((to_a - from_a) * t);
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
gfx->palette[start + i] = r | (g << 8) | (b << 16) | (a << 24);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_gfx_process_effects(pxl8_gfx* gfx, pxl8_effects* effects, f32 dt) {
|
|
|
|
|
if (!gfx || !effects) return;
|
|
|
|
|
|
2025-08-13 15:04:49 -05:00
|
|
|
effects->time += dt;
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-08-13 15:04:49 -05:00
|
|
|
for (i32 i = 0; i < 8; i++) {
|
|
|
|
|
pxl8_palette_cycle* cycle = &effects->palette_cycles[i];
|
|
|
|
|
if (!cycle->active) continue;
|
2025-10-04 04:13:48 -05:00
|
|
|
|
2025-08-13 15:04:49 -05:00
|
|
|
cycle->timer += dt * cycle->speed;
|
|
|
|
|
if (cycle->timer >= 1.0f) {
|
|
|
|
|
cycle->timer -= 1.0f;
|
|
|
|
|
i32 count = cycle->end_index - cycle->start_index + 1;
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_gfx_cycle_palette(gfx, cycle->start_index, count, 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool pxl8_3d_init_zbuffer(pxl8_gfx* gfx) {
|
|
|
|
|
if (gfx->zbuffer) return true;
|
|
|
|
|
|
|
|
|
|
gfx->zbuffer_width = gfx->framebuffer_width;
|
|
|
|
|
gfx->zbuffer_height = gfx->framebuffer_height;
|
|
|
|
|
|
|
|
|
|
gfx->zbuffer = (f32*)SDL_calloc(gfx->zbuffer_width * gfx->zbuffer_height, sizeof(f32));
|
|
|
|
|
if (!gfx->zbuffer) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_3d_clear_zbuffer(gfx);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_3d_clear_zbuffer(pxl8_gfx* gfx) {
|
|
|
|
|
if (!gfx || !gfx->zbuffer) return;
|
|
|
|
|
for (i32 i = 0; i < gfx->zbuffer_width * gfx->zbuffer_height; i++) {
|
|
|
|
|
gfx->zbuffer[i] = 1e30f;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_3d_set_backface_culling(pxl8_gfx* gfx, bool culling) {
|
|
|
|
|
if (!gfx) return;
|
|
|
|
|
gfx->backface_culling = culling;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_3d_set_model(pxl8_gfx* gfx, pxl8_mat4 mat) {
|
|
|
|
|
if (!gfx) return;
|
|
|
|
|
gfx->model = mat;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_3d_set_projection(pxl8_gfx* gfx, pxl8_mat4 mat) {
|
|
|
|
|
if (!gfx) return;
|
|
|
|
|
gfx->projection = mat;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_3d_set_view(pxl8_gfx* gfx, pxl8_mat4 mat) {
|
|
|
|
|
if (!gfx) return;
|
|
|
|
|
gfx->view = mat;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_3d_set_wireframe(pxl8_gfx* gfx, bool wireframe) {
|
|
|
|
|
if (!gfx) return;
|
|
|
|
|
gfx->wireframe = wireframe;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline pxl8_vec4 pxl8_transform_vertex(pxl8_gfx* gfx, pxl8_vec3 pos) {
|
|
|
|
|
pxl8_vec4 v = {
|
|
|
|
|
.x = pos.x,
|
|
|
|
|
.y = pos.y,
|
|
|
|
|
.z = pos.z,
|
|
|
|
|
.w = 1.0f,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pxl8_mat4 mvp = pxl8_mat4_multiply(gfx->projection,
|
|
|
|
|
pxl8_mat4_multiply(gfx->view, gfx->model));
|
|
|
|
|
|
|
|
|
|
return pxl8_mat4_multiply_vec4(mvp, v);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static inline void pxl8_project_to_screen(pxl8_gfx* gfx, pxl8_vec4 clip, i32* x, i32* y, f32* z) {
|
|
|
|
|
if (fabsf(clip.w) < 1e-6f) {
|
|
|
|
|
*x = *y = 0;
|
|
|
|
|
*z = 1e30f;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f32 inv_w = 1.0f / clip.w;
|
|
|
|
|
f32 ndc_x = clip.x * inv_w;
|
|
|
|
|
f32 ndc_y = clip.y * inv_w;
|
|
|
|
|
|
|
|
|
|
*x = (i32)((ndc_x + 1.0f) * 0.5f * gfx->framebuffer_width);
|
|
|
|
|
*y = (i32)((1.0f - ndc_y) * 0.5f * gfx->framebuffer_height);
|
|
|
|
|
*z = clip.z * inv_w;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_3d_draw_line_3d(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u32 color) {
|
|
|
|
|
if (!gfx) return;
|
|
|
|
|
|
|
|
|
|
pxl8_vec4 v0 = pxl8_transform_vertex(gfx, p0);
|
|
|
|
|
pxl8_vec4 v1 = pxl8_transform_vertex(gfx, p1);
|
|
|
|
|
|
|
|
|
|
i32 x0, y0, x1, y1;
|
|
|
|
|
f32 z0, z1;
|
|
|
|
|
pxl8_project_to_screen(gfx, v0, &x0, &y0, &z0);
|
|
|
|
|
pxl8_project_to_screen(gfx, v1, &x1, &y1, &z1);
|
|
|
|
|
|
|
|
|
|
pxl8_line(gfx, x0, y0, x1, y1, color);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void pxl8_draw_flat_bottom_triangle(
|
|
|
|
|
pxl8_gfx* gfx,
|
|
|
|
|
i32 x0, i32 y0, f32 z0,
|
|
|
|
|
i32 x1, i32 y1, f32 z1,
|
|
|
|
|
i32 x2, i32 y2, f32 z2,
|
|
|
|
|
u32 color
|
|
|
|
|
) {
|
|
|
|
|
(void)z2;
|
|
|
|
|
if (y1 == y0) return;
|
|
|
|
|
|
|
|
|
|
f32 inv_slope_1 = (f32)(x1 - x0) / (f32)(y1 - y0);
|
|
|
|
|
f32 inv_slope_2 = (f32)(x2 - x0) / (f32)(y2 - y0);
|
|
|
|
|
|
|
|
|
|
f32 cur_x1 = (f32)x0;
|
|
|
|
|
f32 cur_x2 = (f32)x0;
|
|
|
|
|
|
|
|
|
|
for (i32 y = y0; y <= y1; y++) {
|
|
|
|
|
if (y >= 0 && y < gfx->framebuffer_height) {
|
|
|
|
|
i32 xs = (i32)cur_x1;
|
|
|
|
|
i32 xe = (i32)cur_x2;
|
|
|
|
|
|
|
|
|
|
if (xs > xe) {
|
|
|
|
|
i32 tmp = xs; xs = xe; xe = tmp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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]) {
|
|
|
|
|
gfx->zbuffer[idx] = z;
|
|
|
|
|
pxl8_pixel(gfx, x, y, color);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
2025-10-04 04:13:48 -05:00
|
|
|
|
|
|
|
|
cur_x1 += inv_slope_1;
|
|
|
|
|
cur_x2 += inv_slope_2;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void pxl8_draw_flat_top_triangle(
|
|
|
|
|
pxl8_gfx* gfx,
|
|
|
|
|
i32 x0, i32 y0, f32 z0,
|
|
|
|
|
i32 x1, i32 y1, f32 z1,
|
|
|
|
|
i32 x2, i32 y2, f32 z2,
|
|
|
|
|
u32 color
|
|
|
|
|
) {
|
|
|
|
|
(void)z2;
|
|
|
|
|
if (y2 == y0) return;
|
|
|
|
|
|
|
|
|
|
f32 inv_slope_1 = (f32)(x2 - x0) / (f32)(y2 - y0);
|
|
|
|
|
f32 inv_slope_2 = (f32)(x2 - x1) / (f32)(y2 - y1);
|
|
|
|
|
|
|
|
|
|
f32 cur_x1 = (f32)x2;
|
|
|
|
|
f32 cur_x2 = (f32)x2;
|
|
|
|
|
|
|
|
|
|
for (i32 y = y2; y > y0; y--) {
|
|
|
|
|
if (y >= 0 && y < gfx->framebuffer_height) {
|
|
|
|
|
i32 xs = (i32)cur_x1;
|
|
|
|
|
i32 xe = (i32)cur_x2;
|
|
|
|
|
|
|
|
|
|
if (xs > xe) {
|
|
|
|
|
i32 tmp = xs; xs = xe; xe = tmp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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]) {
|
|
|
|
|
gfx->zbuffer[idx] = z;
|
|
|
|
|
pxl8_pixel(gfx, x, y, color);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cur_x1 -= inv_slope_1;
|
|
|
|
|
cur_x2 -= inv_slope_2;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_3d_draw_triangle_raw(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, pxl8_vec3 v2, u32 color) {
|
|
|
|
|
pxl8_triangle tri;
|
|
|
|
|
tri.v[0].position = v0;
|
|
|
|
|
tri.v[0].color = color;
|
|
|
|
|
tri.v[1].position = v1;
|
|
|
|
|
tri.v[1].color = color;
|
|
|
|
|
tri.v[2].position = v2;
|
|
|
|
|
tri.v[2].color = color;
|
|
|
|
|
pxl8_3d_draw_triangle(gfx, tri);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_3d_draw_triangle(pxl8_gfx* gfx, pxl8_triangle tri) {
|
|
|
|
|
if (!gfx || !pxl8_3d_init_zbuffer(gfx)) return;
|
|
|
|
|
|
|
|
|
|
pxl8_vec4 v0 = pxl8_transform_vertex(gfx, tri.v[0].position);
|
|
|
|
|
pxl8_vec4 v1 = pxl8_transform_vertex(gfx, tri.v[1].position);
|
|
|
|
|
pxl8_vec4 v2 = pxl8_transform_vertex(gfx, tri.v[2].position);
|
|
|
|
|
|
|
|
|
|
if (v0.w <= 0 || v1.w <= 0 || v2.w <= 0) return;
|
|
|
|
|
|
|
|
|
|
i32 x0, y0, x1, y1, x2, y2;
|
|
|
|
|
f32 z0, z1, z2;
|
|
|
|
|
pxl8_project_to_screen(gfx, v0, &x0, &y0, &z0);
|
|
|
|
|
pxl8_project_to_screen(gfx, v1, &x1, &y1, &z1);
|
|
|
|
|
pxl8_project_to_screen(gfx, v2, &x2, &y2, &z2);
|
|
|
|
|
|
|
|
|
|
if (gfx->backface_culling) {
|
|
|
|
|
i32 cross = (x1 - x0) * (y2 - y0) - (y1 - y0) * (x2 - x0);
|
|
|
|
|
if (cross >= 0) return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (gfx->wireframe) {
|
|
|
|
|
u32 color = tri.v[0].color;
|
|
|
|
|
pxl8_line(gfx, x0, y0, x1, y1, color);
|
|
|
|
|
pxl8_line(gfx, x1, y1, x2, y2, color);
|
|
|
|
|
pxl8_line(gfx, x2, y2, x0, y0, color);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (y0 > y1) {
|
|
|
|
|
i32 tmp_i = x0; x0 = x1; x1 = tmp_i;
|
|
|
|
|
tmp_i = y0; y0 = y1; y1 = tmp_i;
|
|
|
|
|
f32 tmp_f = z0; z0 = z1; z1 = tmp_f;
|
|
|
|
|
}
|
|
|
|
|
if (y0 > y2) {
|
|
|
|
|
i32 tmp_i = x0; x0 = x2; x2 = tmp_i;
|
|
|
|
|
tmp_i = y0; y0 = y2; y2 = tmp_i;
|
|
|
|
|
f32 tmp_f = z0; z0 = z2; z2 = tmp_f;
|
|
|
|
|
}
|
|
|
|
|
if (y1 > y2) {
|
|
|
|
|
i32 tmp_i = x1; x1 = x2; x2 = tmp_i;
|
|
|
|
|
tmp_i = y1; y1 = y2; y2 = tmp_i;
|
|
|
|
|
f32 tmp_f = z1; z1 = z2; z2 = tmp_f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
u32 color = tri.v[0].color;
|
|
|
|
|
|
|
|
|
|
if (y1 == y2) {
|
|
|
|
|
pxl8_draw_flat_bottom_triangle(gfx, x0, y0, z0, x1, y1, z1, x2, y2, z2, color);
|
|
|
|
|
} else if (y0 == y1) {
|
|
|
|
|
pxl8_draw_flat_top_triangle(gfx, x0, y0, z0, x1, y1, z1, x2, y2, z2, color);
|
|
|
|
|
} else {
|
|
|
|
|
i32 x3 = x0 + (i32)(((f32)(y1 - y0) / (f32)(y2 - y0)) * (x2 - x0));
|
|
|
|
|
i32 y3 = y1;
|
|
|
|
|
f32 z3 = z0 + ((f32)(y1 - y0) / (f32)(y2 - y0)) * (z2 - z0);
|
|
|
|
|
|
|
|
|
|
pxl8_draw_flat_bottom_triangle(gfx, x0, y0, z0, x1, y1, z1, x3, y3, z3, color);
|
|
|
|
|
pxl8_draw_flat_top_triangle(gfx, x1, y1, z1, x3, y3, z3, x2, y2, z2, color);
|
2025-08-13 15:04:49 -05:00
|
|
|
}
|
|
|
|
|
}
|