add tilemap/tilesheet

This commit is contained in:
asrael 2025-09-24 00:39:44 -05:00
parent c18896def0
commit ff698730f1
13 changed files with 841 additions and 28 deletions

182
src/pxl8_tilesheet.c Normal file
View file

@ -0,0 +1,182 @@
#include "pxl8_tilesheet.h"
#include "pxl8_tilemap.h"
#include "pxl8_ase.h"
#include "pxl8_macros.h"
#include <stdlib.h>
#include <string.h>
pxl8_result pxl8_tilesheet_init(pxl8_tilesheet* tilesheet, u32 tile_size) {
if (!tilesheet) return PXL8_ERROR_NULL_POINTER;
memset(tilesheet, 0, sizeof(pxl8_tilesheet));
tilesheet->tile_size = tile_size ? tile_size : PXL8_TILE_SIZE;
return PXL8_OK;
}
void pxl8_tilesheet_free(pxl8_tilesheet* tilesheet) {
if (!tilesheet) return;
if (tilesheet->data) {
free(tilesheet->data);
tilesheet->data = NULL;
}
if (tilesheet->tile_valid) {
free(tilesheet->tile_valid);
tilesheet->tile_valid = NULL;
}
}
pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, pxl8_gfx_ctx* gfx) {
if (!tilesheet || !filepath || !gfx) return PXL8_ERROR_NULL_POINTER;
pxl8_ase_file ase_file;
pxl8_result result = pxl8_ase_load(filepath, &ase_file);
if (result != PXL8_OK) {
pxl8_error("Failed to load tilesheet: %s", filepath);
return result;
}
if (tilesheet->data) {
free(tilesheet->data);
}
u32 width = ase_file.header.width;
u32 height = ase_file.header.height;
if (ase_file.header.grid_width > 0 && ase_file.header.grid_height > 0) {
tilesheet->tile_size = ase_file.header.grid_width;
pxl8_info("Using Aseprite grid size: %dx%d",
ase_file.header.grid_width, ase_file.header.grid_height);
}
tilesheet->width = width;
tilesheet->height = height;
tilesheet->tiles_per_row = width / tilesheet->tile_size;
tilesheet->total_tiles = (width / tilesheet->tile_size) * (height / tilesheet->tile_size);
tilesheet->color_mode = gfx->color_mode;
size_t data_size = width * height;
if (gfx->color_mode == PXL8_COLOR_MODE_HICOLOR) {
data_size *= 4;
}
tilesheet->data = malloc(data_size);
if (!tilesheet->data) {
pxl8_ase_free(&ase_file);
return PXL8_ERROR_OUT_OF_MEMORY;
}
if (ase_file.frame_count > 0 && ase_file.frames[0].pixels) {
memcpy(tilesheet->data, ase_file.frames[0].pixels, data_size);
}
tilesheet->tile_valid = calloc(tilesheet->total_tiles + 1, sizeof(bool));
if (!tilesheet->tile_valid) {
free(tilesheet->data);
tilesheet->data = NULL;
pxl8_ase_free(&ase_file);
return PXL8_ERROR_OUT_OF_MEMORY;
}
u32 valid_tiles = 0;
bool is_hicolor = (tilesheet->color_mode == PXL8_COLOR_MODE_HICOLOR);
for (u32 tile_id = 1; tile_id <= tilesheet->total_tiles; tile_id++) {
u32 tile_x = ((tile_id - 1) % tilesheet->tiles_per_row) * tilesheet->tile_size;
u32 tile_y = ((tile_id - 1) / tilesheet->tiles_per_row) * tilesheet->tile_size;
bool has_content = false;
for (u32 py = 0; py < tilesheet->tile_size && !has_content; py++) {
for (u32 px = 0; px < tilesheet->tile_size; px++) {
if (is_hicolor) {
u32 idx = ((tile_y + py) * width + (tile_x + px)) * 4;
u8 alpha = tilesheet->data[idx + 3];
if (alpha > 0) {
has_content = true;
break;
}
} else {
u32 idx = (tile_y + py) * width + (tile_x + px);
if (tilesheet->data[idx] != 0) {
has_content = true;
break;
}
}
}
}
if (has_content) {
tilesheet->tile_valid[tile_id] = true;
valid_tiles++;
}
}
pxl8_info("Loaded tilesheet %s: %dx%d, %d valid tiles out of %d slots (%dx%d each)",
filepath, width, height, valid_tiles, tilesheet->total_tiles,
tilesheet->tile_size, tilesheet->tile_size);
pxl8_ase_free(&ase_file);
return PXL8_OK;
}
void pxl8_tilesheet_render_tile(const pxl8_tilesheet* tilesheet, pxl8_gfx_ctx* gfx,
u16 tile_id, i32 x, i32 y, u8 flags) {
if (!tilesheet || !gfx || !tilesheet->data) return;
if (tile_id == 0 || tile_id > tilesheet->total_tiles) return;
if (tilesheet->tile_valid && !tilesheet->tile_valid[tile_id]) return;
u32 tile_x = ((tile_id - 1) % tilesheet->tiles_per_row) * tilesheet->tile_size;
u32 tile_y = ((tile_id - 1) / tilesheet->tiles_per_row) * tilesheet->tile_size;
for (u32 py = 0; py < tilesheet->tile_size; py++) {
for (u32 px = 0; px < tilesheet->tile_size; px++) {
u32 src_x = tile_x + px;
u32 src_y = tile_y + py;
if (flags & PXL8_TILE_FLIP_X) src_x = tile_x + (tilesheet->tile_size - 1 - px);
if (flags & PXL8_TILE_FLIP_Y) src_y = tile_y + (tilesheet->tile_size - 1 - py);
u32 src_idx = src_y * tilesheet->width + src_x;
u8 color_idx = tilesheet->data[src_idx];
if (color_idx != 0) {
i32 screen_x = x + px;
i32 screen_y = y + py;
if (screen_x >= 0 && screen_x < gfx->framebuffer_width &&
screen_y >= 0 && screen_y < gfx->framebuffer_height) {
pxl8_pixel(gfx, screen_x, screen_y, color_idx);
}
}
}
}
}
bool pxl8_tilesheet_is_tile_valid(const pxl8_tilesheet* tilesheet, u16 tile_id) {
if (!tilesheet || tile_id == 0 || tile_id > tilesheet->total_tiles) return false;
return tilesheet->tile_valid ? tilesheet->tile_valid[tile_id] : true;
}
pxl8_tilesheet* pxl8_tilesheet_new(u32 tile_size) {
pxl8_tilesheet* tilesheet = calloc(1, sizeof(pxl8_tilesheet));
if (!tilesheet) {
pxl8_error("Failed to allocate tilesheet");
return NULL;
}
pxl8_result result = pxl8_tilesheet_init(tilesheet, tile_size);
if (result != PXL8_OK) {
pxl8_error("Failed to initialize tilesheet");
free(tilesheet);
return NULL;
}
return tilesheet;
}
void pxl8_tilesheet_destroy(pxl8_tilesheet* tilesheet) {
if (!tilesheet) return;
pxl8_tilesheet_free(tilesheet);
free(tilesheet);
}