2025-09-24 00:39:44 -05:00
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
2025-11-01 12:39:59 -05:00
|
|
|
#include "pxl8_ase.h"
|
2025-10-01 12:56:13 -05:00
|
|
|
#include "pxl8_macros.h"
|
|
|
|
|
#include "pxl8_tilemap.h"
|
|
|
|
|
#include "pxl8_tilesheet.h"
|
|
|
|
|
|
2025-11-01 12:39:59 -05:00
|
|
|
struct pxl8_tilesheet {
|
|
|
|
|
u8* data;
|
|
|
|
|
bool* tile_valid;
|
|
|
|
|
u32 height;
|
|
|
|
|
u32 tile_size;
|
|
|
|
|
u32 tiles_per_row;
|
|
|
|
|
u32 total_tiles;
|
|
|
|
|
u32 width;
|
|
|
|
|
pxl8_color_mode color_mode;
|
|
|
|
|
u32 ref_count;
|
|
|
|
|
pxl8_tile_animation* animations;
|
|
|
|
|
u32 animation_count;
|
|
|
|
|
pxl8_tile_properties* properties;
|
|
|
|
|
};
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
struct pxl8_tilemap_layer {
|
|
|
|
|
u32 allocated_chunks;
|
|
|
|
|
u32 chunk_count;
|
|
|
|
|
pxl8_tile_chunk** chunks;
|
|
|
|
|
u32 chunks_high;
|
|
|
|
|
u32 chunks_wide;
|
|
|
|
|
u8 opacity;
|
|
|
|
|
bool visible;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct pxl8_tilemap {
|
|
|
|
|
u32 active_layers;
|
|
|
|
|
i32 camera_x;
|
|
|
|
|
i32 camera_y;
|
|
|
|
|
u32 height;
|
|
|
|
|
pxl8_tilemap_layer layers[PXL8_MAX_TILE_LAYERS];
|
|
|
|
|
u32 tile_size;
|
|
|
|
|
pxl8_tilesheet* tilesheet;
|
2025-11-01 12:39:59 -05:00
|
|
|
u32 tile_user_data_capacity;
|
|
|
|
|
void** tile_user_data;
|
2025-10-04 04:13:48 -05:00
|
|
|
u32 width;
|
|
|
|
|
};
|
|
|
|
|
|
2025-09-27 11:03:36 -05:00
|
|
|
static inline u32 pxl8_chunk_index(u32 x, u32 y, u32 chunks_wide) {
|
|
|
|
|
return (y >> 4) * chunks_wide + (x >> 4);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static pxl8_tile_chunk* pxl8_get_or_create_chunk(pxl8_tilemap_layer* layer, u32 x, u32 y) {
|
|
|
|
|
u32 chunk_x = x >> 4;
|
|
|
|
|
u32 chunk_y = y >> 4;
|
|
|
|
|
u32 idx = chunk_y * layer->chunks_wide + chunk_x;
|
|
|
|
|
|
|
|
|
|
if (idx >= layer->chunks_wide * layer->chunks_high) return NULL;
|
|
|
|
|
|
|
|
|
|
if (!layer->chunks[idx]) {
|
|
|
|
|
layer->chunks[idx] = calloc(1, sizeof(pxl8_tile_chunk));
|
|
|
|
|
if (!layer->chunks[idx]) return NULL;
|
|
|
|
|
|
|
|
|
|
layer->chunks[idx]->chunk_x = chunk_x;
|
|
|
|
|
layer->chunks[idx]->chunk_y = chunk_y;
|
|
|
|
|
layer->chunks[idx]->empty = true;
|
|
|
|
|
layer->allocated_chunks++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return layer->chunks[idx];
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_tilemap* pxl8_tilemap_create(u32 width, u32 height, u32 tile_size) {
|
2025-09-24 00:39:44 -05:00
|
|
|
if (width > PXL8_MAX_TILEMAP_WIDTH || height > PXL8_MAX_TILEMAP_HEIGHT) {
|
2025-10-04 04:13:48 -05:00
|
|
|
return NULL;
|
2025-09-24 00:39:44 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
pxl8_tilemap* tilemap = calloc(1, sizeof(pxl8_tilemap));
|
|
|
|
|
if (!tilemap) return NULL;
|
|
|
|
|
|
2025-09-24 00:39:44 -05:00
|
|
|
tilemap->width = width;
|
|
|
|
|
tilemap->height = height;
|
|
|
|
|
tilemap->tile_size = tile_size ? tile_size : PXL8_TILE_SIZE;
|
|
|
|
|
tilemap->active_layers = 1;
|
|
|
|
|
|
2025-11-01 12:39:59 -05:00
|
|
|
tilemap->tilesheet = pxl8_tilesheet_create(tilemap->tile_size);
|
|
|
|
|
if (!tilemap->tilesheet) {
|
|
|
|
|
free(tilemap);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tilemap->tile_user_data = NULL;
|
|
|
|
|
tilemap->tile_user_data_capacity = 0;
|
|
|
|
|
|
2025-09-27 11:03:36 -05:00
|
|
|
u32 chunks_wide = (width + PXL8_CHUNK_SIZE - 1) / PXL8_CHUNK_SIZE;
|
|
|
|
|
u32 chunks_high = (height + PXL8_CHUNK_SIZE - 1) / PXL8_CHUNK_SIZE;
|
|
|
|
|
|
2025-09-24 00:39:44 -05:00
|
|
|
for (u32 i = 0; i < PXL8_MAX_TILE_LAYERS; i++) {
|
2025-09-27 11:03:36 -05:00
|
|
|
pxl8_tilemap_layer* layer = &tilemap->layers[i];
|
|
|
|
|
layer->chunks_wide = chunks_wide;
|
|
|
|
|
layer->chunks_high = chunks_high;
|
|
|
|
|
layer->chunk_count = chunks_wide * chunks_high;
|
|
|
|
|
layer->visible = (i == 0);
|
|
|
|
|
layer->opacity = 255;
|
|
|
|
|
|
|
|
|
|
layer->chunks = calloc(layer->chunk_count, sizeof(pxl8_tile_chunk*));
|
|
|
|
|
if (!layer->chunks) {
|
2025-09-24 00:39:44 -05:00
|
|
|
for (u32 j = 0; j < i; j++) {
|
2025-09-27 11:03:36 -05:00
|
|
|
free(tilemap->layers[j].chunks);
|
2025-09-24 00:39:44 -05:00
|
|
|
}
|
2025-11-01 12:39:59 -05:00
|
|
|
if (tilemap->tilesheet) pxl8_tilesheet_destroy(tilemap->tilesheet);
|
2025-10-04 04:13:48 -05:00
|
|
|
free(tilemap);
|
|
|
|
|
return NULL;
|
2025-09-24 00:39:44 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
return tilemap;
|
2025-09-24 00:39:44 -05:00
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_tilemap_destroy(pxl8_tilemap* tilemap) {
|
2025-09-24 00:39:44 -05:00
|
|
|
if (!tilemap) return;
|
|
|
|
|
|
|
|
|
|
for (u32 i = 0; i < PXL8_MAX_TILE_LAYERS; i++) {
|
2025-09-27 11:03:36 -05:00
|
|
|
pxl8_tilemap_layer* layer = &tilemap->layers[i];
|
|
|
|
|
if (layer->chunks) {
|
|
|
|
|
for (u32 j = 0; j < layer->chunk_count; j++) {
|
|
|
|
|
if (layer->chunks[j]) {
|
|
|
|
|
free(layer->chunks[j]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
free(layer->chunks);
|
2025-09-24 00:39:44 -05:00
|
|
|
}
|
|
|
|
|
}
|
2025-09-27 11:03:36 -05:00
|
|
|
|
2025-11-01 12:39:59 -05:00
|
|
|
if (tilemap->tilesheet) pxl8_tilesheet_unref(tilemap->tilesheet);
|
|
|
|
|
if (tilemap->tile_user_data) free(tilemap->tile_user_data);
|
2025-10-04 04:13:48 -05:00
|
|
|
|
|
|
|
|
free(tilemap);
|
2025-09-24 00:39:44 -05:00
|
|
|
}
|
|
|
|
|
|
2025-11-01 12:39:59 -05:00
|
|
|
u32 pxl8_tilemap_get_width(const pxl8_tilemap* tilemap) {
|
|
|
|
|
return tilemap ? tilemap->width : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
u32 pxl8_tilemap_get_height(const pxl8_tilemap* tilemap) {
|
|
|
|
|
return tilemap ? tilemap->height : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
u32 pxl8_tilemap_get_tile_size(const pxl8_tilemap* tilemap) {
|
|
|
|
|
return tilemap ? tilemap->tile_size : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_tilemap_set_tile_user_data(pxl8_tilemap* tilemap, u16 tile_id, void* user_data) {
|
|
|
|
|
if (!tilemap || tile_id == 0) return;
|
|
|
|
|
|
|
|
|
|
if (tile_id >= tilemap->tile_user_data_capacity) {
|
|
|
|
|
u32 new_capacity = tile_id + 64;
|
|
|
|
|
void** new_data = realloc(tilemap->tile_user_data, new_capacity * sizeof(void*));
|
|
|
|
|
if (!new_data) return;
|
|
|
|
|
|
|
|
|
|
for (u32 i = tilemap->tile_user_data_capacity; i < new_capacity; i++) {
|
|
|
|
|
new_data[i] = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tilemap->tile_user_data = new_data;
|
|
|
|
|
tilemap->tile_user_data_capacity = new_capacity;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tilemap->tile_user_data[tile_id] = user_data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void* pxl8_tilemap_get_tile_user_data(const pxl8_tilemap* tilemap, u16 tile_id) {
|
|
|
|
|
if (!tilemap || tile_id == 0 || tile_id >= tilemap->tile_user_data_capacity) return NULL;
|
|
|
|
|
return tilemap->tile_user_data[tile_id];
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-24 00:39:44 -05:00
|
|
|
pxl8_result pxl8_tilemap_set_tilesheet(pxl8_tilemap* tilemap, pxl8_tilesheet* tilesheet) {
|
|
|
|
|
if (!tilemap || !tilesheet) return PXL8_ERROR_NULL_POINTER;
|
|
|
|
|
|
2025-09-27 11:03:36 -05:00
|
|
|
if (tilemap->tilesheet) {
|
|
|
|
|
pxl8_tilesheet_unref(tilemap->tilesheet);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-24 00:39:44 -05:00
|
|
|
tilemap->tilesheet = tilesheet;
|
2025-09-27 11:03:36 -05:00
|
|
|
pxl8_tilesheet_ref(tilesheet);
|
2025-09-24 00:39:44 -05:00
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
u32 tilesheet_size = pxl8_tilesheet_get_tile_size(tilesheet);
|
|
|
|
|
if (tilesheet_size != tilemap->tile_size) {
|
2025-09-24 00:39:44 -05:00
|
|
|
pxl8_warn("Tilesheet tile size (%d) differs from tilemap tile size (%d)",
|
2025-10-04 04:13:48 -05:00
|
|
|
tilesheet_size, tilemap->tile_size);
|
|
|
|
|
tilemap->tile_size = tilesheet_size;
|
2025-09-24 00:39:44 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return PXL8_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_result pxl8_tilemap_set_tile(pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y, u16 tile_id, u8 flags) {
|
|
|
|
|
if (!tilemap) return PXL8_ERROR_NULL_POINTER;
|
|
|
|
|
if (layer >= PXL8_MAX_TILE_LAYERS) return PXL8_ERROR_INVALID_ARGUMENT;
|
|
|
|
|
if (x >= tilemap->width || y >= tilemap->height) return PXL8_ERROR_INVALID_COORDINATE;
|
|
|
|
|
|
|
|
|
|
pxl8_tilemap_layer* l = &tilemap->layers[layer];
|
2025-09-27 11:03:36 -05:00
|
|
|
pxl8_tile_chunk* chunk = pxl8_get_or_create_chunk(l, x, y);
|
|
|
|
|
if (!chunk) return PXL8_ERROR_OUT_OF_MEMORY;
|
|
|
|
|
|
|
|
|
|
u32 local_x = x & PXL8_CHUNK_MASK;
|
|
|
|
|
u32 local_y = y & PXL8_CHUNK_MASK;
|
|
|
|
|
u32 idx = local_y * PXL8_CHUNK_SIZE + local_x;
|
|
|
|
|
|
|
|
|
|
chunk->tiles[idx] = pxl8_tile_pack(tile_id, flags, 0);
|
|
|
|
|
|
|
|
|
|
if (tile_id != 0) {
|
|
|
|
|
chunk->empty = false;
|
|
|
|
|
}
|
2025-09-24 00:39:44 -05:00
|
|
|
|
|
|
|
|
if (layer >= tilemap->active_layers) {
|
|
|
|
|
tilemap->active_layers = layer + 1;
|
|
|
|
|
l->visible = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return PXL8_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_tile pxl8_tilemap_get_tile(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y) {
|
2025-09-27 11:03:36 -05:00
|
|
|
if (!tilemap || layer >= PXL8_MAX_TILE_LAYERS) return 0;
|
|
|
|
|
if (x >= tilemap->width || y >= tilemap->height) return 0;
|
|
|
|
|
|
|
|
|
|
const pxl8_tilemap_layer* l = &tilemap->layers[layer];
|
|
|
|
|
u32 chunk_idx = pxl8_chunk_index(x, y, l->chunks_wide);
|
|
|
|
|
|
|
|
|
|
if (chunk_idx >= l->chunk_count || !l->chunks[chunk_idx]) return 0;
|
2025-09-24 00:39:44 -05:00
|
|
|
|
2025-09-27 11:03:36 -05:00
|
|
|
const pxl8_tile_chunk* chunk = l->chunks[chunk_idx];
|
|
|
|
|
u32 local_x = x & PXL8_CHUNK_MASK;
|
|
|
|
|
u32 local_y = y & PXL8_CHUNK_MASK;
|
|
|
|
|
u32 idx = local_y * PXL8_CHUNK_SIZE + local_x;
|
2025-09-24 00:39:44 -05:00
|
|
|
|
2025-09-27 11:03:36 -05:00
|
|
|
return chunk->tiles[idx];
|
2025-09-24 00:39:44 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_tilemap_set_camera(pxl8_tilemap* tilemap, i32 x, i32 y) {
|
|
|
|
|
if (!tilemap) return;
|
|
|
|
|
tilemap->camera_x = x;
|
|
|
|
|
tilemap->camera_y = y;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_tilemap_get_view(const pxl8_tilemap* tilemap, const pxl8_gfx* gfx, pxl8_tilemap_view* view) {
|
2025-09-24 00:39:44 -05:00
|
|
|
if (!tilemap || !gfx || !view) return;
|
|
|
|
|
|
|
|
|
|
view->x = -tilemap->camera_x;
|
|
|
|
|
view->y = -tilemap->camera_y;
|
2025-10-04 04:13:48 -05:00
|
|
|
view->width = pxl8_gfx_get_width(gfx);
|
|
|
|
|
view->height = pxl8_gfx_get_height(gfx);
|
2025-09-24 00:39:44 -05:00
|
|
|
|
|
|
|
|
view->tile_start_x = pxl8_max(0, tilemap->camera_x / (i32)tilemap->tile_size);
|
|
|
|
|
view->tile_start_y = pxl8_max(0, tilemap->camera_y / (i32)tilemap->tile_size);
|
|
|
|
|
view->tile_end_x = pxl8_min((i32)tilemap->width,
|
|
|
|
|
(tilemap->camera_x + view->width) / (i32)tilemap->tile_size + 1);
|
|
|
|
|
view->tile_end_y = pxl8_min((i32)tilemap->height,
|
|
|
|
|
(tilemap->camera_y + view->height) / (i32)tilemap->tile_size + 1);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_tilemap_render_tile(const pxl8_tilemap* tilemap, pxl8_gfx* gfx, u16 tile_id, i32 x, i32 y, u8 flags) {
|
2025-09-24 00:39:44 -05:00
|
|
|
if (!tilemap || !gfx || !tilemap->tilesheet) return;
|
|
|
|
|
pxl8_tilesheet_render_tile(tilemap->tilesheet, gfx, tile_id, x, y, flags);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_tilemap_render_layer(const pxl8_tilemap* tilemap, pxl8_gfx* gfx, u32 layer) {
|
2025-09-24 00:39:44 -05:00
|
|
|
if (!tilemap || !gfx || layer >= tilemap->active_layers) return;
|
|
|
|
|
if (!tilemap->tilesheet) {
|
|
|
|
|
pxl8_warn("No tilesheet set for tilemap");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const pxl8_tilemap_layer* l = &tilemap->layers[layer];
|
|
|
|
|
if (!l->visible) return;
|
|
|
|
|
|
2025-09-27 11:03:36 -05:00
|
|
|
i32 view_left = tilemap->camera_x / tilemap->tile_size;
|
|
|
|
|
i32 view_top = tilemap->camera_y / tilemap->tile_size;
|
2025-10-04 04:13:48 -05:00
|
|
|
i32 view_right = (tilemap->camera_x + pxl8_gfx_get_width(gfx)) / tilemap->tile_size + 1;
|
|
|
|
|
i32 view_bottom = (tilemap->camera_y + pxl8_gfx_get_height(gfx)) / tilemap->tile_size + 1;
|
2025-09-27 11:03:36 -05:00
|
|
|
|
|
|
|
|
u32 chunk_left = pxl8_max(0, view_left >> 4);
|
|
|
|
|
u32 chunk_top = pxl8_max(0, view_top >> 4);
|
2025-09-28 13:10:29 -05:00
|
|
|
u32 chunk_right = pxl8_min((tilemap->width + 15) >> 4, (u32)((view_right >> 4) + 1));
|
|
|
|
|
u32 chunk_bottom = pxl8_min((tilemap->height + 15) >> 4, (u32)((view_bottom >> 4) + 1));
|
2025-09-27 11:03:36 -05:00
|
|
|
|
|
|
|
|
for (u32 cy = chunk_top; cy < chunk_bottom; cy++) {
|
|
|
|
|
for (u32 cx = chunk_left; cx < chunk_right; cx++) {
|
|
|
|
|
u32 chunk_idx = cy * l->chunks_wide + cx;
|
|
|
|
|
if (chunk_idx >= l->chunk_count || !l->chunks[chunk_idx]) continue;
|
|
|
|
|
|
|
|
|
|
const pxl8_tile_chunk* chunk = l->chunks[chunk_idx];
|
|
|
|
|
if (chunk->empty) continue;
|
|
|
|
|
|
|
|
|
|
u32 tile_start_x = pxl8_max(0, view_left - (i32)(cx << 4));
|
|
|
|
|
u32 tile_end_x = pxl8_min(PXL8_CHUNK_SIZE, view_right - (i32)(cx << 4));
|
|
|
|
|
u32 tile_start_y = pxl8_max(0, view_top - (i32)(cy << 4));
|
|
|
|
|
u32 tile_end_y = pxl8_min(PXL8_CHUNK_SIZE, view_bottom - (i32)(cy << 4));
|
|
|
|
|
|
|
|
|
|
for (u32 ty = tile_start_y; ty < tile_end_y; ty++) {
|
|
|
|
|
for (u32 tx = tile_start_x; tx < tile_end_x; tx++) {
|
|
|
|
|
u32 idx = ty * PXL8_CHUNK_SIZE + tx;
|
|
|
|
|
pxl8_tile tile = chunk->tiles[idx];
|
|
|
|
|
u16 tile_id = pxl8_tile_get_id(tile);
|
|
|
|
|
if (tile_id == 0) continue;
|
|
|
|
|
|
|
|
|
|
i32 world_x = (cx << 4) + tx;
|
|
|
|
|
i32 world_y = (cy << 4) + ty;
|
|
|
|
|
i32 screen_x = world_x * tilemap->tile_size - tilemap->camera_x;
|
|
|
|
|
i32 screen_y = world_y * tilemap->tile_size - tilemap->camera_y;
|
|
|
|
|
|
|
|
|
|
u8 flags = pxl8_tile_get_flags(tile);
|
|
|
|
|
if (flags & PXL8_TILE_ANIMATED && tilemap->tilesheet) {
|
|
|
|
|
tile_id = pxl8_tilesheet_get_animated_frame(tilemap->tilesheet, tile_id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_tilesheet_render_tile(tilemap->tilesheet, gfx, tile_id, screen_x, screen_y, flags);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-09-24 00:39:44 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-04 04:13:48 -05:00
|
|
|
void pxl8_tilemap_render(const pxl8_tilemap* tilemap, pxl8_gfx* gfx) {
|
2025-09-24 00:39:44 -05:00
|
|
|
if (!tilemap || !gfx) return;
|
|
|
|
|
|
|
|
|
|
for (u32 layer = 0; layer < tilemap->active_layers; layer++) {
|
|
|
|
|
pxl8_tilemap_render_layer(tilemap, gfx, layer);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool pxl8_tilemap_is_solid(const pxl8_tilemap* tilemap, u32 x, u32 y) {
|
|
|
|
|
if (!tilemap) return true;
|
|
|
|
|
|
|
|
|
|
u32 tile_x = x / tilemap->tile_size;
|
|
|
|
|
u32 tile_y = y / tilemap->tile_size;
|
|
|
|
|
|
|
|
|
|
for (u32 layer = 0; layer < tilemap->active_layers; layer++) {
|
|
|
|
|
pxl8_tile tile = pxl8_tilemap_get_tile(tilemap, layer, tile_x, tile_y);
|
2025-09-27 11:03:36 -05:00
|
|
|
if (pxl8_tile_get_flags(tile) & PXL8_TILE_SOLID) return true;
|
2025-09-24 00:39:44 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool pxl8_tilemap_check_collision(const pxl8_tilemap* tilemap, i32 x, i32 y, i32 w, i32 h) {
|
|
|
|
|
if (!tilemap) return true;
|
|
|
|
|
|
|
|
|
|
i32 left = x / tilemap->tile_size;
|
|
|
|
|
i32 top = y / tilemap->tile_size;
|
|
|
|
|
i32 right = (x + w - 1) / tilemap->tile_size;
|
|
|
|
|
i32 bottom = (y + h - 1) / tilemap->tile_size;
|
|
|
|
|
|
|
|
|
|
for (i32 ty = top; ty <= bottom; ty++) {
|
|
|
|
|
for (i32 tx = left; tx <= right; tx++) {
|
|
|
|
|
if (tx < 0 || tx >= (i32)tilemap->width ||
|
|
|
|
|
ty < 0 || ty >= (i32)tilemap->height) return true;
|
|
|
|
|
|
|
|
|
|
for (u32 layer = 0; layer < tilemap->active_layers; layer++) {
|
|
|
|
|
pxl8_tile tile = pxl8_tilemap_get_tile(tilemap, layer, tx, ty);
|
2025-09-27 11:03:36 -05:00
|
|
|
if (pxl8_tile_get_flags(tile) & PXL8_TILE_SOLID) return true;
|
2025-09-24 00:39:44 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
u16 pxl8_tilemap_get_tile_id(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y) {
|
|
|
|
|
if (!tilemap) return 0;
|
|
|
|
|
pxl8_tile tile = pxl8_tilemap_get_tile(tilemap, layer, x, y);
|
2025-09-27 11:03:36 -05:00
|
|
|
return pxl8_tile_get_id(tile);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_tilemap_update(pxl8_tilemap* tilemap, f32 delta_time) {
|
|
|
|
|
if (!tilemap || !tilemap->tilesheet) return;
|
|
|
|
|
pxl8_tilesheet_update_animations(tilemap->tilesheet, delta_time);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static u8 pxl8_tilemap_get_neighbors(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y, u16 match_id) {
|
|
|
|
|
u8 neighbors = 0;
|
|
|
|
|
|
|
|
|
|
if (y > 0 && pxl8_tilemap_get_tile_id(tilemap, layer, x, y - 1) == match_id)
|
|
|
|
|
neighbors |= 1 << 0;
|
|
|
|
|
if (x < tilemap->width - 1 && pxl8_tilemap_get_tile_id(tilemap, layer, x + 1, y) == match_id)
|
|
|
|
|
neighbors |= 1 << 1;
|
|
|
|
|
if (y < tilemap->height - 1 && pxl8_tilemap_get_tile_id(tilemap, layer, x, y + 1) == match_id)
|
|
|
|
|
neighbors |= 1 << 2;
|
|
|
|
|
if (x > 0 && pxl8_tilemap_get_tile_id(tilemap, layer, x - 1, y) == match_id)
|
|
|
|
|
neighbors |= 1 << 3;
|
|
|
|
|
|
|
|
|
|
if (y > 0 && x < tilemap->width - 1 && pxl8_tilemap_get_tile_id(tilemap, layer, x + 1, y - 1) == match_id)
|
|
|
|
|
neighbors |= 1 << 4;
|
|
|
|
|
if (y < tilemap->height - 1 && x < tilemap->width - 1 && pxl8_tilemap_get_tile_id(tilemap, layer, x + 1, y + 1) == match_id)
|
|
|
|
|
neighbors |= 1 << 5;
|
|
|
|
|
if (y < tilemap->height - 1 && x > 0 && pxl8_tilemap_get_tile_id(tilemap, layer, x - 1, y + 1) == match_id)
|
|
|
|
|
neighbors |= 1 << 6;
|
|
|
|
|
if (y > 0 && x > 0 && pxl8_tilemap_get_tile_id(tilemap, layer, x - 1, y - 1) == match_id)
|
|
|
|
|
neighbors |= 1 << 7;
|
|
|
|
|
|
|
|
|
|
return neighbors;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_result pxl8_tilemap_set_tile_auto(pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y,
|
|
|
|
|
u16 base_tile_id, u8 flags) {
|
|
|
|
|
if (!tilemap || !tilemap->tilesheet) return PXL8_ERROR_NULL_POINTER;
|
|
|
|
|
|
|
|
|
|
pxl8_result result = pxl8_tilemap_set_tile(tilemap, layer, x, y, base_tile_id, flags | PXL8_TILE_AUTOTILE);
|
|
|
|
|
if (result != PXL8_OK) return result;
|
|
|
|
|
|
|
|
|
|
pxl8_tilemap_update_autotiles(tilemap, layer,
|
|
|
|
|
x > 0 ? x - 1 : x, y > 0 ? y - 1 : y,
|
|
|
|
|
x < tilemap->width - 1 ? 3 : 2,
|
|
|
|
|
y < tilemap->height - 1 ? 3 : 2);
|
|
|
|
|
|
|
|
|
|
return PXL8_OK;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_tilemap_update_autotiles(pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y, u32 w, u32 h) {
|
|
|
|
|
if (!tilemap || !tilemap->tilesheet) return;
|
|
|
|
|
|
|
|
|
|
for (u32 ty = y; ty < y + h && ty < tilemap->height; ty++) {
|
|
|
|
|
for (u32 tx = x; tx < x + w && tx < tilemap->width; tx++) {
|
|
|
|
|
pxl8_tile tile = pxl8_tilemap_get_tile(tilemap, layer, tx, ty);
|
|
|
|
|
u8 flags = pxl8_tile_get_flags(tile);
|
|
|
|
|
|
|
|
|
|
if (flags & PXL8_TILE_AUTOTILE) {
|
|
|
|
|
u16 base_id = pxl8_tile_get_id(tile);
|
|
|
|
|
u8 neighbors = pxl8_tilemap_get_neighbors(tilemap, layer, tx, ty, base_id);
|
|
|
|
|
u16 new_id = pxl8_tilesheet_apply_autotile(tilemap->tilesheet, base_id, neighbors);
|
|
|
|
|
|
|
|
|
|
if (new_id != base_id) {
|
|
|
|
|
pxl8_tilemap_layer* l = &tilemap->layers[layer];
|
|
|
|
|
pxl8_tile_chunk* chunk = pxl8_get_or_create_chunk(l, tx, ty);
|
|
|
|
|
if (chunk) {
|
|
|
|
|
u32 local_x = tx & PXL8_CHUNK_MASK;
|
|
|
|
|
u32 local_y = ty & PXL8_CHUNK_MASK;
|
|
|
|
|
u32 idx = local_y * PXL8_CHUNK_SIZE + local_x;
|
|
|
|
|
chunk->tiles[idx] = pxl8_tile_pack(new_id, flags, pxl8_tile_get_palette(tile));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
u32 pxl8_tilemap_get_memory_usage(const pxl8_tilemap* tilemap) {
|
|
|
|
|
if (!tilemap) return 0;
|
|
|
|
|
|
|
|
|
|
u32 total = sizeof(pxl8_tilemap);
|
|
|
|
|
|
|
|
|
|
for (u32 i = 0; i < PXL8_MAX_TILE_LAYERS; i++) {
|
|
|
|
|
const pxl8_tilemap_layer* layer = &tilemap->layers[i];
|
|
|
|
|
total += layer->chunk_count * sizeof(pxl8_tile_chunk*);
|
|
|
|
|
total += layer->allocated_chunks * sizeof(pxl8_tile_chunk);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return total;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void pxl8_tilemap_compress(pxl8_tilemap* tilemap) {
|
|
|
|
|
if (!tilemap) return;
|
|
|
|
|
|
|
|
|
|
for (u32 i = 0; i < PXL8_MAX_TILE_LAYERS; i++) {
|
|
|
|
|
pxl8_tilemap_layer* layer = &tilemap->layers[i];
|
|
|
|
|
|
|
|
|
|
for (u32 j = 0; j < layer->chunk_count; j++) {
|
|
|
|
|
pxl8_tile_chunk* chunk = layer->chunks[j];
|
|
|
|
|
if (!chunk) continue;
|
|
|
|
|
|
|
|
|
|
bool has_tiles = false;
|
|
|
|
|
for (u32 k = 0; k < PXL8_CHUNK_SIZE * PXL8_CHUNK_SIZE; k++) {
|
|
|
|
|
if (pxl8_tile_get_id(chunk->tiles[k]) != 0) {
|
|
|
|
|
has_tiles = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!has_tiles) {
|
|
|
|
|
free(chunk);
|
|
|
|
|
layer->chunks[j] = NULL;
|
|
|
|
|
layer->allocated_chunks--;
|
|
|
|
|
} else {
|
|
|
|
|
chunk->empty = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-11-01 12:39:59 -05:00
|
|
|
|
|
|
|
|
pxl8_result pxl8_tilemap_load_ase(pxl8_tilemap* tilemap, const char* filepath, u32 layer) {
|
|
|
|
|
if (!tilemap || !filepath) return PXL8_ERROR_NULL_POINTER;
|
|
|
|
|
if (!tilemap->tilesheet) return PXL8_ERROR_NULL_POINTER;
|
|
|
|
|
if (layer >= PXL8_MAX_TILE_LAYERS) return PXL8_ERROR_INVALID_ARGUMENT;
|
|
|
|
|
|
|
|
|
|
pxl8_ase_file ase_file = {0};
|
|
|
|
|
pxl8_result result = pxl8_ase_load(filepath, &ase_file);
|
|
|
|
|
if (result != PXL8_OK) {
|
|
|
|
|
pxl8_error("Failed to load ASE file: %s", filepath);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ase_file.tileset_count == 0) {
|
|
|
|
|
pxl8_error("ASE file has no tileset - must be created as Tilemap in Aseprite: %s", filepath);
|
|
|
|
|
pxl8_ase_destroy(&ase_file);
|
|
|
|
|
return PXL8_ERROR_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_ase_tileset* tileset = &ase_file.tilesets[0];
|
|
|
|
|
|
|
|
|
|
if (tileset->tile_width != tilemap->tile_size || tileset->tile_height != tilemap->tile_size) {
|
|
|
|
|
pxl8_error("Tileset tile size (%ux%u) doesn't match tilemap tile size (%u)",
|
|
|
|
|
tileset->tile_width, tileset->tile_height, tilemap->tile_size);
|
|
|
|
|
pxl8_ase_destroy(&ase_file);
|
|
|
|
|
return PXL8_ERROR_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_info("Loading tilemap from %s: %u tiles, %ux%u each",
|
|
|
|
|
filepath, tileset->tile_count, tileset->tile_width, tileset->tile_height);
|
|
|
|
|
|
|
|
|
|
if (!(tileset->flags & 2)) {
|
|
|
|
|
pxl8_error("Tileset has no embedded tiles - external tilesets not yet supported");
|
|
|
|
|
pxl8_ase_destroy(&ase_file);
|
|
|
|
|
return PXL8_ERROR_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!tileset->pixels) {
|
|
|
|
|
pxl8_error("Tileset has no pixel data");
|
|
|
|
|
pxl8_ase_destroy(&ase_file);
|
|
|
|
|
return PXL8_ERROR_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
u32 tiles_per_row = 16;
|
|
|
|
|
u32 tilesheet_rows = (tileset->tile_count + tiles_per_row - 1) / tiles_per_row;
|
|
|
|
|
u32 tilesheet_width = tiles_per_row * tilemap->tile_size;
|
|
|
|
|
u32 tilesheet_height = tilesheet_rows * tilemap->tile_size;
|
|
|
|
|
|
|
|
|
|
if (tilemap->tilesheet->data) free(tilemap->tilesheet->data);
|
|
|
|
|
tilemap->tilesheet->data = calloc(tilesheet_width * tilesheet_height, 1);
|
|
|
|
|
if (!tilemap->tilesheet->data) {
|
|
|
|
|
pxl8_ase_destroy(&ase_file);
|
|
|
|
|
return PXL8_ERROR_OUT_OF_MEMORY;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tilemap->tilesheet->width = tilesheet_width;
|
|
|
|
|
tilemap->tilesheet->height = tilesheet_height;
|
|
|
|
|
tilemap->tilesheet->tiles_per_row = tiles_per_row;
|
|
|
|
|
tilemap->tilesheet->total_tiles = tileset->tile_count;
|
|
|
|
|
tilemap->tilesheet->color_mode = PXL8_COLOR_MODE_MEGA;
|
|
|
|
|
|
|
|
|
|
if (tilemap->tilesheet->tile_valid) free(tilemap->tilesheet->tile_valid);
|
|
|
|
|
tilemap->tilesheet->tile_valid = calloc(tileset->tile_count + 1, sizeof(bool));
|
|
|
|
|
|
|
|
|
|
for (u32 i = 0; i < tileset->tile_count; i++) {
|
|
|
|
|
u32 sheet_row = i / tiles_per_row;
|
|
|
|
|
u32 sheet_col = i % tiles_per_row;
|
|
|
|
|
u32 src_offset = i * tilemap->tile_size * tilemap->tile_size;
|
|
|
|
|
|
|
|
|
|
for (u32 y = 0; y < tilemap->tile_size; y++) {
|
|
|
|
|
for (u32 x = 0; x < tilemap->tile_size; x++) {
|
|
|
|
|
u32 dst_x = sheet_col * tilemap->tile_size + x;
|
|
|
|
|
u32 dst_y = sheet_row * tilemap->tile_size + y;
|
|
|
|
|
u32 dst_idx = dst_y * tilesheet_width + dst_x;
|
|
|
|
|
u32 src_idx = src_offset + y * tilemap->tile_size + x;
|
|
|
|
|
|
|
|
|
|
if (src_idx < tileset->pixels_size && dst_idx < tilesheet_width * tilesheet_height) {
|
|
|
|
|
tilemap->tilesheet->data[dst_idx] = tileset->pixels[src_idx];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
tilemap->tilesheet->tile_valid[i] = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_info("Loaded %u tiles into tilesheet (%ux%u)", tileset->tile_count, tilesheet_width, tilesheet_height);
|
|
|
|
|
|
|
|
|
|
if (ase_file.frame_count == 0 || !ase_file.frames) {
|
|
|
|
|
pxl8_error("ASE file has no frames");
|
|
|
|
|
pxl8_ase_destroy(&ase_file);
|
|
|
|
|
return PXL8_ERROR_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_ase_frame* frame = &ase_file.frames[0];
|
|
|
|
|
pxl8_ase_cel* tilemap_cel = NULL;
|
|
|
|
|
|
|
|
|
|
for (u32 i = 0; i < frame->cel_count; i++) {
|
|
|
|
|
if (frame->cels[i].cel_type == 3) {
|
|
|
|
|
tilemap_cel = &frame->cels[i];
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!tilemap_cel || !tilemap_cel->tilemap.tiles) {
|
|
|
|
|
pxl8_error("No tilemap cel found in frame 0");
|
|
|
|
|
pxl8_ase_destroy(&ase_file);
|
|
|
|
|
return PXL8_ERROR_INVALID_ARGUMENT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_info("Found tilemap cel: %ux%u tiles", tilemap_cel->tilemap.width, tilemap_cel->tilemap.height);
|
|
|
|
|
|
|
|
|
|
for (u32 ty = 0; ty < tilemap_cel->tilemap.height && ty < tilemap->height; ty++) {
|
|
|
|
|
for (u32 tx = 0; tx < tilemap_cel->tilemap.width && tx < tilemap->width; tx++) {
|
|
|
|
|
u32 tile_idx = ty * tilemap_cel->tilemap.width + tx;
|
|
|
|
|
u32 tile_value = tilemap_cel->tilemap.tiles[tile_idx];
|
|
|
|
|
u16 tile_id = (u16)(tile_value & tilemap_cel->tilemap.tile_id_mask);
|
|
|
|
|
u8 flags = 0;
|
|
|
|
|
if (tile_value & tilemap_cel->tilemap.x_flip_mask) flags |= PXL8_TILE_FLIP_X;
|
|
|
|
|
if (tile_value & tilemap_cel->tilemap.y_flip_mask) flags |= PXL8_TILE_FLIP_Y;
|
|
|
|
|
pxl8_tilemap_set_tile(tilemap, layer, tx, ty, tile_id, flags);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_ase_destroy(&ase_file);
|
|
|
|
|
return PXL8_OK;
|
|
|
|
|
}
|