add networking, 3d improvements, reorganize src structure
This commit is contained in:
parent
39b604b333
commit
415d424057
122 changed files with 5358 additions and 721 deletions
636
src/asset/pxl8_ase.c
Normal file
636
src/asset/pxl8_ase.c
Normal file
|
|
@ -0,0 +1,636 @@
|
|||
#include "pxl8_ase.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define MINIZ_NO_STDIO
|
||||
#define MINIZ_NO_TIME
|
||||
#define MINIZ_NO_ARCHIVE_APIS
|
||||
#define MINIZ_NO_ARCHIVE_WRITING_APIS
|
||||
#define MINIZ_NO_DEFLATE_APIS
|
||||
#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES
|
||||
|
||||
#include <miniz.h>
|
||||
|
||||
#include "pxl8_io.h"
|
||||
#include "pxl8_log.h"
|
||||
|
||||
static pxl8_result parse_ase_header(pxl8_stream* stream, pxl8_ase_header* header) {
|
||||
header->file_size = pxl8_read_u32(stream);
|
||||
header->magic = pxl8_read_u16(stream);
|
||||
header->frames = pxl8_read_u16(stream);
|
||||
header->width = pxl8_read_u16(stream);
|
||||
header->height = pxl8_read_u16(stream);
|
||||
header->color_depth = pxl8_read_u16(stream);
|
||||
header->flags = pxl8_read_u32(stream);
|
||||
header->speed = pxl8_read_u16(stream);
|
||||
pxl8_skip_bytes(stream, 4);
|
||||
header->transparent_index = pxl8_read_u32(stream);
|
||||
header->n_colors = pxl8_read_u8(stream);
|
||||
header->pixel_width = pxl8_read_u8(stream);
|
||||
header->pixel_height = pxl8_read_u8(stream);
|
||||
header->grid_x = pxl8_read_i16(stream);
|
||||
header->grid_y = pxl8_read_i16(stream);
|
||||
header->grid_width = pxl8_read_u16(stream);
|
||||
header->grid_height = pxl8_read_u16(stream);
|
||||
|
||||
if (header->magic != PXL8_ASE_MAGIC) {
|
||||
pxl8_error("Invalid ASE file magic: 0x%04X", header->magic);
|
||||
return PXL8_ERROR_ASE_INVALID_MAGIC;
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result parse_old_palette_chunk(pxl8_stream* stream, pxl8_ase_palette* palette) {
|
||||
u16 packet_count = pxl8_read_u16(stream);
|
||||
|
||||
u32 total_colors = 0;
|
||||
u32 temp_pos = pxl8_stream_position(stream);
|
||||
for (u16 packet = 0; packet < packet_count; packet++) {
|
||||
u8 skip_colors = pxl8_read_u8(stream);
|
||||
u8 colors_in_packet = pxl8_read_u8(stream);
|
||||
u32 actual_colors = (colors_in_packet == 0) ? 256 : colors_in_packet;
|
||||
total_colors += skip_colors + actual_colors;
|
||||
pxl8_skip_bytes(stream, actual_colors * 3);
|
||||
}
|
||||
|
||||
palette->entry_count = total_colors;
|
||||
palette->first_color = 0;
|
||||
palette->last_color = total_colors - 1;
|
||||
palette->colors = (u32*)malloc(total_colors * sizeof(u32));
|
||||
if (!palette->colors) {
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
pxl8_stream_seek(stream, temp_pos);
|
||||
|
||||
u32 color_index = 0;
|
||||
for (u16 packet = 0; packet < packet_count; packet++) {
|
||||
u8 skip_colors = pxl8_read_u8(stream);
|
||||
u8 colors_in_packet = pxl8_read_u8(stream);
|
||||
u32 actual_colors = (colors_in_packet == 0) ? 256 : colors_in_packet;
|
||||
|
||||
for (u32 skip = 0; skip < skip_colors && color_index < total_colors; skip++) {
|
||||
palette->colors[color_index++] = 0xFF000000;
|
||||
}
|
||||
|
||||
for (u32 color = 0; color < actual_colors && color_index < total_colors; color++) {
|
||||
u8 r = pxl8_read_u8(stream);
|
||||
u8 g = pxl8_read_u8(stream);
|
||||
u8 b = pxl8_read_u8(stream);
|
||||
palette->colors[color_index++] = 0xFF000000 | ((u32)b << 16) | ((u32)g << 8) | r;
|
||||
}
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result parse_layer_chunk(pxl8_stream* stream, pxl8_ase_layer* layer) {
|
||||
layer->flags = pxl8_read_u16(stream);
|
||||
layer->layer_type = pxl8_read_u16(stream);
|
||||
layer->child_level = pxl8_read_u16(stream);
|
||||
pxl8_skip_bytes(stream, 4);
|
||||
layer->blend_mode = pxl8_read_u16(stream);
|
||||
layer->opacity = pxl8_read_u8(stream);
|
||||
pxl8_skip_bytes(stream, 3);
|
||||
|
||||
u16 name_len = pxl8_read_u16(stream);
|
||||
if (name_len > 0) {
|
||||
layer->name = (char*)malloc(name_len + 1);
|
||||
if (!layer->name) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
pxl8_read_bytes(stream, layer->name, name_len);
|
||||
layer->name[name_len] = '\0';
|
||||
} else {
|
||||
layer->name = NULL;
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result parse_palette_chunk(pxl8_stream* stream, pxl8_ase_palette* palette) {
|
||||
pxl8_skip_bytes(stream, 4);
|
||||
palette->first_color = pxl8_read_u32(stream);
|
||||
palette->last_color = pxl8_read_u32(stream);
|
||||
pxl8_skip_bytes(stream, 8);
|
||||
|
||||
u32 color_count = palette->last_color - palette->first_color + 1;
|
||||
|
||||
if (color_count == 0 || color_count > 65536) {
|
||||
return PXL8_ERROR_ASE_MALFORMED_CHUNK;
|
||||
}
|
||||
|
||||
palette->colors = (u32*)malloc(color_count * sizeof(u32));
|
||||
if (!palette->colors) {
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
palette->entry_count = color_count;
|
||||
|
||||
for (u32 i = 0; i < color_count; i++) {
|
||||
u16 flags = pxl8_read_u16(stream);
|
||||
u8 r = pxl8_read_u8(stream);
|
||||
u8 g = pxl8_read_u8(stream);
|
||||
u8 b = pxl8_read_u8(stream);
|
||||
u8 a = pxl8_read_u8(stream);
|
||||
|
||||
palette->colors[i] = ((u32)a << 24) | ((u32)b << 16) | ((u32)g << 8) | r;
|
||||
|
||||
if (flags & 1) {
|
||||
u16 name_len = pxl8_read_u16(stream);
|
||||
pxl8_skip_bytes(stream, name_len);
|
||||
}
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result parse_user_data_chunk(pxl8_stream* stream, pxl8_ase_user_data* user_data) {
|
||||
u32 flags = pxl8_read_u32(stream);
|
||||
|
||||
user_data->has_text = (flags & 1) != 0;
|
||||
user_data->has_color = (flags & 2) != 0;
|
||||
|
||||
if (user_data->has_text) {
|
||||
u16 text_len = pxl8_read_u16(stream);
|
||||
if (text_len > 0) {
|
||||
user_data->text = (char*)malloc(text_len + 1);
|
||||
if (!user_data->text) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
pxl8_read_bytes(stream, user_data->text, text_len);
|
||||
user_data->text[text_len] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
if (user_data->has_color) {
|
||||
u8 r = pxl8_read_u8(stream);
|
||||
u8 g = pxl8_read_u8(stream);
|
||||
u8 b = pxl8_read_u8(stream);
|
||||
u8 a = pxl8_read_u8(stream);
|
||||
user_data->color = ((u32)a << 24) | ((u32)b << 16) | ((u32)g << 8) | r;
|
||||
}
|
||||
|
||||
if (flags & 4) {
|
||||
pxl8_skip_bytes(stream, 4);
|
||||
u32 num_properties = pxl8_read_u32(stream);
|
||||
|
||||
if (num_properties > 0) {
|
||||
user_data->properties = (pxl8_ase_property*)calloc(num_properties, sizeof(pxl8_ase_property));
|
||||
if (!user_data->properties) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
user_data->property_count = num_properties;
|
||||
|
||||
for (u32 i = 0; i < num_properties; i++) {
|
||||
u16 name_len = pxl8_read_u16(stream);
|
||||
if (name_len > 0) {
|
||||
user_data->properties[i].name = (char*)malloc(name_len + 1);
|
||||
if (!user_data->properties[i].name) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
pxl8_read_bytes(stream, user_data->properties[i].name, name_len);
|
||||
user_data->properties[i].name[name_len] = '\0';
|
||||
}
|
||||
|
||||
u16 type = pxl8_read_u16(stream);
|
||||
user_data->properties[i].type = type;
|
||||
|
||||
switch (type) {
|
||||
case 1:
|
||||
user_data->properties[i].bool_val = pxl8_read_u8(stream) != 0;
|
||||
break;
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
user_data->properties[i].int_val = pxl8_read_i32(stream);
|
||||
break;
|
||||
case 6:
|
||||
case 7:
|
||||
user_data->properties[i].float_val = pxl8_read_f32(stream);
|
||||
break;
|
||||
case 8: {
|
||||
u16 str_len = pxl8_read_u16(stream);
|
||||
if (str_len > 0) {
|
||||
user_data->properties[i].string_val = (char*)malloc(str_len + 1);
|
||||
if (!user_data->properties[i].string_val) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
pxl8_read_bytes(stream, user_data->properties[i].string_val, str_len);
|
||||
user_data->properties[i].string_val[str_len] = '\0';
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
pxl8_skip_bytes(stream, 4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result parse_tileset_chunk(pxl8_stream* stream, pxl8_ase_tileset* tileset) {
|
||||
tileset->id = pxl8_read_u32(stream);
|
||||
tileset->flags = pxl8_read_u32(stream);
|
||||
tileset->tile_count = pxl8_read_u32(stream);
|
||||
tileset->tile_width = pxl8_read_u16(stream);
|
||||
tileset->tile_height = pxl8_read_u16(stream);
|
||||
tileset->base_index = pxl8_read_i16(stream);
|
||||
pxl8_skip_bytes(stream, 14);
|
||||
|
||||
u16 name_len = pxl8_read_u16(stream);
|
||||
if (name_len > 0) {
|
||||
tileset->name = (char*)malloc(name_len + 1);
|
||||
if (!tileset->name) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
pxl8_read_bytes(stream, tileset->name, name_len);
|
||||
tileset->name[name_len] = '\0';
|
||||
}
|
||||
|
||||
if (tileset->flags & 1) {
|
||||
pxl8_skip_bytes(stream, 8); // external_file_id + tileset_id
|
||||
}
|
||||
|
||||
if (tileset->flags & 2) {
|
||||
u32 compressed_size = pxl8_read_u32(stream);
|
||||
tileset->pixels_size = tileset->tile_width * tileset->tile_height * tileset->tile_count;
|
||||
tileset->pixels = (u8*)malloc(tileset->pixels_size);
|
||||
if (!tileset->pixels) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
const u8* compressed_data = pxl8_read_ptr(stream, compressed_size);
|
||||
mz_ulong dest_len = tileset->pixels_size;
|
||||
i32 result = mz_uncompress(tileset->pixels, &dest_len, compressed_data, compressed_size);
|
||||
if (result != MZ_OK) {
|
||||
pxl8_error("Failed to decompress tileset data: miniz error %d", result);
|
||||
free(tileset->pixels);
|
||||
tileset->pixels = NULL;
|
||||
return PXL8_ERROR_ASE_MALFORMED_CHUNK;
|
||||
}
|
||||
}
|
||||
|
||||
tileset->tile_user_data = (pxl8_ase_user_data*)calloc(tileset->tile_count, sizeof(pxl8_ase_user_data));
|
||||
if (!tileset->tile_user_data) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result parse_cel_chunk(pxl8_stream* stream, u32 chunk_size, pxl8_ase_cel* cel) {
|
||||
if (chunk_size < 9) return PXL8_ERROR_ASE_MALFORMED_CHUNK;
|
||||
|
||||
cel->layer_index = pxl8_read_u16(stream);
|
||||
cel->x = pxl8_read_i16(stream);
|
||||
cel->y = pxl8_read_i16(stream);
|
||||
cel->opacity = pxl8_read_u8(stream);
|
||||
cel->cel_type = pxl8_read_u16(stream);
|
||||
pxl8_skip_bytes(stream, 7);
|
||||
|
||||
if (cel->cel_type == 2) {
|
||||
if (chunk_size < 20) return PXL8_ERROR_ASE_MALFORMED_CHUNK;
|
||||
|
||||
cel->image.width = pxl8_read_u16(stream);
|
||||
cel->image.height = pxl8_read_u16(stream);
|
||||
|
||||
u32 pixels_size = cel->image.width * cel->image.height;
|
||||
u32 compressed_size = chunk_size - 20;
|
||||
|
||||
cel->image.pixels = (u8*)malloc(pixels_size);
|
||||
if (!cel->image.pixels) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
const u8* compressed_data = pxl8_read_ptr(stream, compressed_size);
|
||||
mz_ulong dest_len = pixels_size;
|
||||
i32 result = mz_uncompress(cel->image.pixels, &dest_len, compressed_data, compressed_size);
|
||||
if (result != MZ_OK) {
|
||||
pxl8_error("Failed to decompress cel data: miniz error %d", result);
|
||||
free(cel->image.pixels);
|
||||
cel->image.pixels = NULL;
|
||||
return PXL8_ERROR_ASE_MALFORMED_CHUNK;
|
||||
}
|
||||
|
||||
if (dest_len != pixels_size) {
|
||||
pxl8_warn("Decompressed size mismatch: expected %u, got %lu", pixels_size, dest_len);
|
||||
}
|
||||
} else if (cel->cel_type == 3) {
|
||||
if (chunk_size < 36) return PXL8_ERROR_ASE_MALFORMED_CHUNK;
|
||||
|
||||
cel->tilemap.width = pxl8_read_u16(stream);
|
||||
cel->tilemap.height = pxl8_read_u16(stream);
|
||||
cel->tilemap.bits_per_tile = pxl8_read_u16(stream);
|
||||
cel->tilemap.tile_id_mask = pxl8_read_u32(stream);
|
||||
cel->tilemap.x_flip_mask = pxl8_read_u32(stream);
|
||||
cel->tilemap.y_flip_mask = pxl8_read_u32(stream);
|
||||
cel->tilemap.diag_flip_mask = pxl8_read_u32(stream);
|
||||
pxl8_skip_bytes(stream, 10);
|
||||
|
||||
u32 tile_count = cel->tilemap.width * cel->tilemap.height;
|
||||
u32 bytes_per_tile = (cel->tilemap.bits_per_tile + 7) / 8;
|
||||
u32 uncompressed_size = tile_count * bytes_per_tile;
|
||||
u32 compressed_size = chunk_size - 36;
|
||||
|
||||
u8* temp_buffer = (u8*)malloc(uncompressed_size);
|
||||
if (!temp_buffer) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
const u8* compressed_data = pxl8_read_ptr(stream, compressed_size);
|
||||
mz_ulong dest_len = uncompressed_size;
|
||||
i32 result = mz_uncompress(temp_buffer, &dest_len, compressed_data, compressed_size);
|
||||
if (result != MZ_OK) {
|
||||
pxl8_error("Failed to decompress tilemap data: miniz error %d", result);
|
||||
free(temp_buffer);
|
||||
return PXL8_ERROR_ASE_MALFORMED_CHUNK;
|
||||
}
|
||||
|
||||
cel->tilemap.tiles = (u32*)calloc(tile_count, sizeof(u32));
|
||||
if (!cel->tilemap.tiles) {
|
||||
free(temp_buffer);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < tile_count; i++) {
|
||||
if (cel->tilemap.bits_per_tile == 32) {
|
||||
cel->tilemap.tiles[i] = ((u32)temp_buffer[i * 4 + 0]) |
|
||||
((u32)temp_buffer[i * 4 + 1] << 8) |
|
||||
((u32)temp_buffer[i * 4 + 2] << 16) |
|
||||
((u32)temp_buffer[i * 4 + 3] << 24);
|
||||
} else if (cel->tilemap.bits_per_tile == 16) {
|
||||
cel->tilemap.tiles[i] = ((u32)temp_buffer[i * 2 + 0]) |
|
||||
((u32)temp_buffer[i * 2 + 1] << 8);
|
||||
} else if (cel->tilemap.bits_per_tile == 8) {
|
||||
cel->tilemap.tiles[i] = temp_buffer[i];
|
||||
}
|
||||
}
|
||||
|
||||
free(temp_buffer);
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file) {
|
||||
if (!filepath || !ase_file) {
|
||||
return PXL8_ERROR_NULL_POINTER;
|
||||
}
|
||||
|
||||
memset(ase_file, 0, sizeof(pxl8_ase_file));
|
||||
|
||||
u8* file_data;
|
||||
size_t file_size;
|
||||
pxl8_result result = pxl8_io_read_binary_file(filepath, &file_data, &file_size);
|
||||
if (result != PXL8_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (file_size < 128) {
|
||||
pxl8_io_free_binary_data(file_data);
|
||||
return PXL8_ERROR_ASE_TRUNCATED_FILE;
|
||||
}
|
||||
|
||||
pxl8_stream stream = pxl8_stream_create(file_data, (u32)file_size);
|
||||
|
||||
result = parse_ase_header(&stream, &ase_file->header);
|
||||
if (result != PXL8_OK) {
|
||||
pxl8_io_free_binary_data(file_data);
|
||||
return result;
|
||||
}
|
||||
|
||||
ase_file->frame_count = ase_file->header.frames;
|
||||
ase_file->frames = (pxl8_ase_frame*)calloc(ase_file->frame_count, sizeof(pxl8_ase_frame));
|
||||
if (!ase_file->frames) {
|
||||
pxl8_io_free_binary_data(file_data);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
pxl8_stream_seek(&stream, 128);
|
||||
|
||||
for (u16 frame_idx = 0; frame_idx < ase_file->header.frames; frame_idx++) {
|
||||
u32 frame_start = pxl8_stream_position(&stream);
|
||||
|
||||
pxl8_ase_frame_header frame_header;
|
||||
frame_header.frame_bytes = pxl8_read_u32(&stream);
|
||||
frame_header.magic = pxl8_read_u16(&stream);
|
||||
u16 old_chunks = pxl8_read_u16(&stream);
|
||||
frame_header.duration = pxl8_read_u16(&stream);
|
||||
pxl8_skip_bytes(&stream, 2);
|
||||
|
||||
if (frame_header.magic != PXL8_ASE_FRAME_MAGIC) {
|
||||
pxl8_error("Invalid frame magic: 0x%04X", frame_header.magic);
|
||||
result = PXL8_ERROR_ASE_INVALID_FRAME_MAGIC;
|
||||
break;
|
||||
}
|
||||
|
||||
if (old_chunks == 0xFFFF || old_chunks == 0xFFFE) {
|
||||
frame_header.chunks = pxl8_read_u32(&stream);
|
||||
} else {
|
||||
frame_header.chunks = old_chunks;
|
||||
pxl8_skip_bytes(&stream, 4);
|
||||
}
|
||||
|
||||
pxl8_ase_frame* frame = &ase_file->frames[frame_idx];
|
||||
frame->frame_id = frame_idx;
|
||||
frame->width = ase_file->header.width;
|
||||
frame->height = ase_file->header.height;
|
||||
frame->duration = frame_header.duration;
|
||||
|
||||
u32 pixel_count = frame->width * frame->height;
|
||||
frame->pixels = (u8*)calloc(pixel_count, sizeof(u8));
|
||||
if (!frame->pixels) {
|
||||
result = PXL8_ERROR_OUT_OF_MEMORY;
|
||||
break;
|
||||
}
|
||||
|
||||
u32 last_tileset_idx = 0xFFFFFFFF;
|
||||
u32 tileset_tile_idx = 0;
|
||||
|
||||
for (u16 chunk_idx = 0; chunk_idx < frame_header.chunks; chunk_idx++) {
|
||||
u32 chunk_start = pxl8_stream_position(&stream);
|
||||
|
||||
pxl8_ase_chunk_header chunk_header;
|
||||
chunk_header.chunk_size = pxl8_read_u32(&stream);
|
||||
chunk_header.chunk_type = pxl8_read_u16(&stream);
|
||||
|
||||
if (chunk_header.chunk_size < 6 || chunk_header.chunk_size > frame_header.frame_bytes) {
|
||||
pxl8_error("Invalid chunk size %u in ASE file (frame_bytes=%u)",
|
||||
chunk_header.chunk_size, frame_header.frame_bytes);
|
||||
result = PXL8_ERROR_ASE_MALFORMED_CHUNK;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (chunk_header.chunk_type) {
|
||||
case PXL8_ASE_CHUNK_OLD_PALETTE:
|
||||
if (!ase_file->palette.colors) {
|
||||
result = parse_old_palette_chunk(&stream, &ase_file->palette);
|
||||
}
|
||||
break;
|
||||
|
||||
case PXL8_ASE_CHUNK_LAYER: {
|
||||
pxl8_ase_layer* new_layers =
|
||||
(pxl8_ase_layer*)realloc(ase_file->layers,
|
||||
(ase_file->layer_count + 1) * sizeof(pxl8_ase_layer));
|
||||
if (!new_layers) {
|
||||
result = PXL8_ERROR_OUT_OF_MEMORY;
|
||||
break;
|
||||
}
|
||||
ase_file->layers = new_layers;
|
||||
|
||||
result = parse_layer_chunk(&stream, &ase_file->layers[ase_file->layer_count]);
|
||||
if (result == PXL8_OK) {
|
||||
ase_file->layer_count++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PXL8_ASE_CHUNK_CEL: {
|
||||
pxl8_ase_cel cel = {0};
|
||||
result = parse_cel_chunk(&stream, chunk_header.chunk_size - 6, &cel);
|
||||
if (result == PXL8_OK) {
|
||||
pxl8_ase_cel* new_cels = (pxl8_ase_cel*)realloc(frame->cels, (frame->cel_count + 1) * sizeof(pxl8_ase_cel));
|
||||
if (!new_cels) {
|
||||
result = PXL8_ERROR_OUT_OF_MEMORY;
|
||||
break;
|
||||
}
|
||||
frame->cels = new_cels;
|
||||
frame->cels[frame->cel_count] = cel;
|
||||
frame->cel_count++;
|
||||
|
||||
if (cel.cel_type == 2 && cel.image.pixels) {
|
||||
u32 copy_width = (cel.image.width < frame->width) ? cel.image.width : frame->width;
|
||||
u32 copy_height = (cel.image.height < frame->height) ? cel.image.height : frame->height;
|
||||
|
||||
for (u32 y = 0; y < copy_height; y++) {
|
||||
u32 src_offset = y * cel.image.width;
|
||||
u32 dst_offset = (y + cel.y) * frame->width + cel.x;
|
||||
if (dst_offset + copy_width <= pixel_count) {
|
||||
for (u32 x = 0; x < copy_width; x++) {
|
||||
u8 src_pixel = cel.image.pixels[src_offset + x];
|
||||
bool is_transparent = false;
|
||||
|
||||
if (src_pixel < ase_file->palette.entry_count && ase_file->palette.colors) {
|
||||
u32 color = ase_file->palette.colors[src_pixel];
|
||||
is_transparent = ((color >> 24) & 0xFF) == 0;
|
||||
}
|
||||
|
||||
if (!is_transparent) {
|
||||
frame->pixels[dst_offset + x] = src_pixel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PXL8_ASE_CHUNK_PALETTE:
|
||||
if (ase_file->palette.colors) {
|
||||
free(ase_file->palette.colors);
|
||||
ase_file->palette.colors = NULL;
|
||||
}
|
||||
result = parse_palette_chunk(&stream, &ase_file->palette);
|
||||
break;
|
||||
|
||||
case PXL8_ASE_CHUNK_TILESET: {
|
||||
pxl8_ase_tileset* new_tilesets = (pxl8_ase_tileset*)realloc(ase_file->tilesets,
|
||||
(ase_file->tileset_count + 1) * sizeof(pxl8_ase_tileset));
|
||||
if (!new_tilesets) {
|
||||
result = PXL8_ERROR_OUT_OF_MEMORY;
|
||||
break;
|
||||
}
|
||||
ase_file->tilesets = new_tilesets;
|
||||
|
||||
memset(&ase_file->tilesets[ase_file->tileset_count], 0, sizeof(pxl8_ase_tileset));
|
||||
result = parse_tileset_chunk(&stream, &ase_file->tilesets[ase_file->tileset_count]);
|
||||
if (result == PXL8_OK) {
|
||||
last_tileset_idx = ase_file->tileset_count;
|
||||
tileset_tile_idx = 0;
|
||||
ase_file->tileset_count++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PXL8_ASE_CHUNK_USER_DATA: {
|
||||
if (last_tileset_idx != 0xFFFFFFFF) {
|
||||
pxl8_ase_tileset* tileset = &ase_file->tilesets[last_tileset_idx];
|
||||
if (tileset_tile_idx < tileset->tile_count) {
|
||||
result = parse_user_data_chunk(&stream, &tileset->tile_user_data[tileset_tile_idx]);
|
||||
tileset_tile_idx++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (result != PXL8_OK) break;
|
||||
|
||||
pxl8_stream_seek(&stream, chunk_start + chunk_header.chunk_size);
|
||||
}
|
||||
|
||||
if (result != PXL8_OK) break;
|
||||
|
||||
pxl8_stream_seek(&stream, frame_start + frame_header.frame_bytes);
|
||||
}
|
||||
|
||||
pxl8_io_free_binary_data(file_data);
|
||||
|
||||
if (result != PXL8_OK) {
|
||||
pxl8_ase_destroy(ase_file);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void pxl8_ase_destroy(pxl8_ase_file* ase_file) {
|
||||
if (!ase_file) return;
|
||||
|
||||
if (ase_file->frames) {
|
||||
for (u32 i = 0; i < ase_file->frame_count; i++) {
|
||||
if (ase_file->frames[i].pixels) free(ase_file->frames[i].pixels);
|
||||
if (ase_file->frames[i].cels) {
|
||||
for (u32 j = 0; j < ase_file->frames[i].cel_count; j++) {
|
||||
pxl8_ase_cel* cel = &ase_file->frames[i].cels[j];
|
||||
if (cel->cel_type == 2 && cel->image.pixels) {
|
||||
free(cel->image.pixels);
|
||||
} else if (cel->cel_type == 3 && cel->tilemap.tiles) {
|
||||
free(cel->tilemap.tiles);
|
||||
}
|
||||
}
|
||||
free(ase_file->frames[i].cels);
|
||||
}
|
||||
}
|
||||
free(ase_file->frames);
|
||||
}
|
||||
|
||||
if (ase_file->palette.colors) {
|
||||
free(ase_file->palette.colors);
|
||||
}
|
||||
|
||||
if (ase_file->layers) {
|
||||
for (u32 i = 0; i < ase_file->layer_count; i++) {
|
||||
if (ase_file->layers[i].name) {
|
||||
free(ase_file->layers[i].name);
|
||||
}
|
||||
}
|
||||
free(ase_file->layers);
|
||||
}
|
||||
|
||||
if (ase_file->tilesets) {
|
||||
for (u32 i = 0; i < ase_file->tileset_count; i++) {
|
||||
if (ase_file->tilesets[i].name) free(ase_file->tilesets[i].name);
|
||||
if (ase_file->tilesets[i].pixels) free(ase_file->tilesets[i].pixels);
|
||||
if (ase_file->tilesets[i].tile_user_data) {
|
||||
for (u32 j = 0; j < ase_file->tilesets[i].tile_count; j++) {
|
||||
pxl8_ase_user_data* ud = &ase_file->tilesets[i].tile_user_data[j];
|
||||
if (ud->text) free(ud->text);
|
||||
if (ud->properties) {
|
||||
for (u32 k = 0; k < ud->property_count; k++) {
|
||||
if (ud->properties[k].name) free(ud->properties[k].name);
|
||||
if (ud->properties[k].type == 8 && ud->properties[k].string_val) {
|
||||
free(ud->properties[k].string_val);
|
||||
}
|
||||
}
|
||||
free(ud->properties);
|
||||
}
|
||||
}
|
||||
free(ase_file->tilesets[i].tile_user_data);
|
||||
}
|
||||
}
|
||||
free(ase_file->tilesets);
|
||||
}
|
||||
|
||||
memset(ase_file, 0, sizeof(pxl8_ase_file));
|
||||
}
|
||||
153
src/asset/pxl8_ase.h
Normal file
153
src/asset/pxl8_ase.h
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#define PXL8_ASE_MAGIC 0xA5E0
|
||||
#define PXL8_ASE_FRAME_MAGIC 0xF1FA
|
||||
#define PXL8_ASE_CHUNK_CEL 0x2005
|
||||
#define PXL8_ASE_CHUNK_LAYER 0x2004
|
||||
#define PXL8_ASE_CHUNK_OLD_PALETTE 0x0004
|
||||
#define PXL8_ASE_CHUNK_PALETTE 0x2019
|
||||
#define PXL8_ASE_CHUNK_TILESET 0x2023
|
||||
#define PXL8_ASE_CHUNK_USER_DATA 0x2020
|
||||
|
||||
typedef struct pxl8_ase_header {
|
||||
u32 file_size;
|
||||
u16 magic;
|
||||
u16 frames;
|
||||
u16 width;
|
||||
u16 height;
|
||||
u16 color_depth;
|
||||
u32 flags;
|
||||
u16 speed;
|
||||
u32 transparent_index;
|
||||
u8 n_colors;
|
||||
u8 pixel_width;
|
||||
u8 pixel_height;
|
||||
i16 grid_x;
|
||||
i16 grid_y;
|
||||
u16 grid_width;
|
||||
u16 grid_height;
|
||||
} pxl8_ase_header;
|
||||
|
||||
typedef struct pxl8_ase_frame_header {
|
||||
u32 frame_bytes;
|
||||
u16 magic;
|
||||
u16 chunks;
|
||||
u16 duration;
|
||||
} pxl8_ase_frame_header;
|
||||
|
||||
typedef struct pxl8_ase_chunk_header {
|
||||
u32 chunk_size;
|
||||
u16 chunk_type;
|
||||
} pxl8_ase_chunk_header;
|
||||
|
||||
typedef struct pxl8_ase_cel {
|
||||
u16 layer_index;
|
||||
i16 x;
|
||||
i16 y;
|
||||
u8 opacity;
|
||||
u16 cel_type;
|
||||
|
||||
union {
|
||||
struct {
|
||||
u16 width;
|
||||
u16 height;
|
||||
u8* pixels;
|
||||
} image;
|
||||
|
||||
struct {
|
||||
u16 width;
|
||||
u16 height;
|
||||
u16 bits_per_tile;
|
||||
u32 tile_id_mask;
|
||||
u32 x_flip_mask;
|
||||
u32 y_flip_mask;
|
||||
u32 diag_flip_mask;
|
||||
u32* tiles;
|
||||
} tilemap;
|
||||
};
|
||||
} pxl8_ase_cel;
|
||||
|
||||
typedef struct pxl8_ase_layer {
|
||||
u16 flags;
|
||||
u16 layer_type;
|
||||
u16 child_level;
|
||||
u16 blend_mode;
|
||||
u8 opacity;
|
||||
char* name;
|
||||
} pxl8_ase_layer;
|
||||
|
||||
typedef struct pxl8_ase_palette {
|
||||
u32 entry_count;
|
||||
u32 first_color;
|
||||
u32 last_color;
|
||||
u32* colors;
|
||||
} pxl8_ase_palette;
|
||||
|
||||
typedef struct pxl8_ase_frame {
|
||||
u16 frame_id;
|
||||
u16 width;
|
||||
u16 height;
|
||||
i16 x;
|
||||
i16 y;
|
||||
u16 duration;
|
||||
u8* pixels;
|
||||
u32 cel_count;
|
||||
pxl8_ase_cel* cels;
|
||||
} pxl8_ase_frame;
|
||||
|
||||
typedef struct pxl8_ase_property {
|
||||
char* name;
|
||||
u32 type;
|
||||
union {
|
||||
bool bool_val;
|
||||
i32 int_val;
|
||||
f32 float_val;
|
||||
char* string_val;
|
||||
};
|
||||
} pxl8_ase_property;
|
||||
|
||||
typedef struct pxl8_ase_user_data {
|
||||
char* text;
|
||||
u32 color;
|
||||
bool has_color;
|
||||
bool has_text;
|
||||
u32 property_count;
|
||||
pxl8_ase_property* properties;
|
||||
} pxl8_ase_user_data;
|
||||
|
||||
typedef struct pxl8_ase_tileset {
|
||||
u32 id;
|
||||
u32 flags;
|
||||
u32 tile_count;
|
||||
u16 tile_width;
|
||||
u16 tile_height;
|
||||
i16 base_index;
|
||||
char* name;
|
||||
u32 pixels_size;
|
||||
u8* pixels;
|
||||
pxl8_ase_user_data* tile_user_data;
|
||||
} pxl8_ase_tileset;
|
||||
|
||||
typedef struct pxl8_ase_file {
|
||||
u32 frame_count;
|
||||
pxl8_ase_frame* frames;
|
||||
pxl8_ase_header header;
|
||||
u32 layer_count;
|
||||
pxl8_ase_layer* layers;
|
||||
pxl8_ase_palette palette;
|
||||
u32 tileset_count;
|
||||
pxl8_ase_tileset* tilesets;
|
||||
} pxl8_ase_file;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_result pxl8_ase_load(const char* filepath, pxl8_ase_file* ase_file);
|
||||
void pxl8_ase_destroy(pxl8_ase_file* ase_file);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
634
src/asset/pxl8_cart.c
Normal file
634
src/asset/pxl8_cart.c
Normal file
|
|
@ -0,0 +1,634 @@
|
|||
#include "pxl8_cart.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "pxl8_log.h"
|
||||
|
||||
#define PXL8_CART_MAGIC 0x43585850
|
||||
#define PXL8_CART_VERSION 1
|
||||
#define PXL8_CART_TRAILER_MAGIC 0x544E4250
|
||||
|
||||
typedef struct {
|
||||
u32 magic;
|
||||
u16 version;
|
||||
u16 flags;
|
||||
u32 file_count;
|
||||
u32 toc_size;
|
||||
} pxl8_cart_header;
|
||||
|
||||
typedef struct {
|
||||
u32 offset;
|
||||
u32 size;
|
||||
u16 path_len;
|
||||
} pxl8_cart_entry;
|
||||
|
||||
typedef struct {
|
||||
u32 magic;
|
||||
u32 cart_offset;
|
||||
u32 cart_size;
|
||||
} pxl8_cart_trailer;
|
||||
|
||||
typedef struct {
|
||||
char* path;
|
||||
u32 offset;
|
||||
u32 size;
|
||||
} pxl8_cart_file;
|
||||
|
||||
struct pxl8_cart {
|
||||
u8* data;
|
||||
u32 data_size;
|
||||
pxl8_cart_file* files;
|
||||
u32 file_count;
|
||||
char* base_path;
|
||||
char* title;
|
||||
pxl8_resolution resolution;
|
||||
pxl8_size window_size;
|
||||
pxl8_pixel_mode pixel_mode;
|
||||
bool is_folder;
|
||||
bool is_mounted;
|
||||
};
|
||||
|
||||
static pxl8_cart* pxl8_current_cart = NULL;
|
||||
static char* pxl8_original_cwd = NULL;
|
||||
|
||||
static bool is_directory(const char* path) {
|
||||
struct stat st;
|
||||
return stat(path, &st) == 0 && S_ISDIR(st.st_mode);
|
||||
}
|
||||
|
||||
static bool is_pxc_file(const char* path) {
|
||||
size_t len = strlen(path);
|
||||
return len > 4 && strcmp(path + len - 4, ".pxc") == 0;
|
||||
}
|
||||
|
||||
static bool has_main_script(const char* base_path) {
|
||||
char path[512];
|
||||
snprintf(path, sizeof(path), "%s/main.fnl", base_path);
|
||||
if (access(path, F_OK) == 0) return true;
|
||||
snprintf(path, sizeof(path), "%s/main.lua", base_path);
|
||||
return access(path, F_OK) == 0;
|
||||
}
|
||||
|
||||
static void collect_files_recursive(const char* dir_path, const char* prefix,
|
||||
char*** paths, u32* count, u32* capacity) {
|
||||
DIR* dir = opendir(dir_path);
|
||||
if (!dir) return;
|
||||
|
||||
struct dirent* entry;
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;
|
||||
|
||||
char full_path[1024];
|
||||
char rel_path[1024];
|
||||
snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name);
|
||||
snprintf(rel_path, sizeof(rel_path), "%s%s", prefix, entry->d_name);
|
||||
|
||||
struct stat st;
|
||||
if (stat(full_path, &st) == 0) {
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
char new_prefix[1025];
|
||||
snprintf(new_prefix, sizeof(new_prefix), "%s/", rel_path);
|
||||
collect_files_recursive(full_path, new_prefix, paths, count, capacity);
|
||||
} else {
|
||||
if (*count >= *capacity) {
|
||||
*capacity = (*capacity == 0) ? 64 : (*capacity * 2);
|
||||
*paths = realloc(*paths, *capacity * sizeof(char*));
|
||||
}
|
||||
(*paths)[(*count)++] = strdup(rel_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
static pxl8_cart_file* find_file(const pxl8_cart* cart, const char* path) {
|
||||
if (!cart || !cart->files) return NULL;
|
||||
for (u32 i = 0; i < cart->file_count; i++) {
|
||||
if (strcmp(cart->files[i].path, path) == 0) {
|
||||
return &cart->files[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static pxl8_result load_packed_cart(pxl8_cart* cart, const u8* data, u32 size) {
|
||||
if (size < sizeof(pxl8_cart_header)) return PXL8_ERROR_INVALID_FORMAT;
|
||||
|
||||
const pxl8_cart_header* header = (const pxl8_cart_header*)data;
|
||||
if (header->magic != PXL8_CART_MAGIC) return PXL8_ERROR_INVALID_FORMAT;
|
||||
if (header->version > PXL8_CART_VERSION) return PXL8_ERROR_INVALID_FORMAT;
|
||||
|
||||
cart->data = malloc(size);
|
||||
if (!cart->data) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
memcpy(cart->data, data, size);
|
||||
cart->data_size = size;
|
||||
|
||||
cart->file_count = header->file_count;
|
||||
cart->files = calloc(cart->file_count, sizeof(pxl8_cart_file));
|
||||
if (!cart->files) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
const u8* toc = cart->data + sizeof(pxl8_cart_header);
|
||||
for (u32 i = 0; i < cart->file_count; i++) {
|
||||
const pxl8_cart_entry* entry = (const pxl8_cart_entry*)toc;
|
||||
toc += sizeof(pxl8_cart_entry);
|
||||
|
||||
cart->files[i].path = malloc(entry->path_len + 1);
|
||||
memcpy(cart->files[i].path, toc, entry->path_len);
|
||||
cart->files[i].path[entry->path_len] = '\0';
|
||||
toc += entry->path_len;
|
||||
|
||||
cart->files[i].offset = entry->offset;
|
||||
cart->files[i].size = entry->size;
|
||||
}
|
||||
|
||||
cart->is_folder = false;
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_cart* pxl8_cart_create(void) {
|
||||
pxl8_cart* cart = calloc(1, sizeof(pxl8_cart));
|
||||
if (cart) {
|
||||
cart->resolution = PXL8_RESOLUTION_640x360;
|
||||
cart->window_size = (pxl8_size){1280, 720};
|
||||
cart->pixel_mode = PXL8_PIXEL_INDEXED;
|
||||
}
|
||||
return cart;
|
||||
}
|
||||
|
||||
pxl8_cart* pxl8_get_cart(void) {
|
||||
return pxl8_current_cart;
|
||||
}
|
||||
|
||||
void pxl8_cart_destroy(pxl8_cart* cart) {
|
||||
if (!cart) return;
|
||||
pxl8_cart_unload(cart);
|
||||
free(cart);
|
||||
}
|
||||
|
||||
pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) {
|
||||
if (!cart || !path) return PXL8_ERROR_NULL_POINTER;
|
||||
pxl8_cart_unload(cart);
|
||||
|
||||
if (is_directory(path)) {
|
||||
cart->base_path = realpath(path, NULL);
|
||||
if (!cart->base_path) {
|
||||
pxl8_error("Failed to resolve cart path: %s", path);
|
||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
cart->is_folder = true;
|
||||
|
||||
if (!has_main_script(cart->base_path)) {
|
||||
pxl8_error("No main.fnl or main.lua found in cart: %s", path);
|
||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
pxl8_info("Loaded cart");
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
if (is_pxc_file(path)) {
|
||||
FILE* file = fopen(path, "rb");
|
||||
if (!file) {
|
||||
pxl8_error("Failed to open cart: %s", path);
|
||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
u32 size = (u32)ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
|
||||
u8* data = malloc(size);
|
||||
if (!data || fread(data, 1, size, file) != size) {
|
||||
free(data);
|
||||
fclose(file);
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
fclose(file);
|
||||
|
||||
pxl8_result result = load_packed_cart(cart, data, size);
|
||||
free(data);
|
||||
|
||||
if (result == PXL8_OK) {
|
||||
pxl8_info("Loaded cart");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
pxl8_error("Unknown cart format: %s", path);
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_cart_load_embedded(pxl8_cart* cart, const char* exe_path) {
|
||||
if (!cart || !exe_path) return PXL8_ERROR_NULL_POINTER;
|
||||
|
||||
FILE* file = fopen(exe_path, "rb");
|
||||
if (!file) return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
|
||||
fseek(file, -(i32)sizeof(pxl8_cart_trailer), SEEK_END);
|
||||
pxl8_cart_trailer trailer;
|
||||
if (fread(&trailer, sizeof(trailer), 1, file) != 1) {
|
||||
fclose(file);
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
if (trailer.magic != PXL8_CART_TRAILER_MAGIC) {
|
||||
fclose(file);
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
fseek(file, trailer.cart_offset, SEEK_SET);
|
||||
u8* data = malloc(trailer.cart_size);
|
||||
if (!data || fread(data, 1, trailer.cart_size, file) != trailer.cart_size) {
|
||||
free(data);
|
||||
fclose(file);
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
fclose(file);
|
||||
|
||||
pxl8_result result = load_packed_cart(cart, data, trailer.cart_size);
|
||||
free(data);
|
||||
|
||||
if (result == PXL8_OK) {
|
||||
pxl8_info("Loaded embedded cart");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void pxl8_cart_unload(pxl8_cart* cart) {
|
||||
if (!cart) return;
|
||||
|
||||
if (cart->title) {
|
||||
pxl8_info("Unloaded cart: %s", cart->title);
|
||||
pxl8_cart_unmount(cart);
|
||||
free(cart->title);
|
||||
cart->title = NULL;
|
||||
} else if (cart->base_path || cart->data) {
|
||||
pxl8_info("Unloaded cart");
|
||||
pxl8_cart_unmount(cart);
|
||||
}
|
||||
|
||||
if (cart->files) {
|
||||
for (u32 i = 0; i < cart->file_count; i++) {
|
||||
free(cart->files[i].path);
|
||||
}
|
||||
free(cart->files);
|
||||
cart->files = NULL;
|
||||
}
|
||||
cart->file_count = 0;
|
||||
|
||||
free(cart->data);
|
||||
cart->data = NULL;
|
||||
cart->data_size = 0;
|
||||
|
||||
free(cart->base_path);
|
||||
cart->base_path = NULL;
|
||||
cart->is_folder = false;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_cart_mount(pxl8_cart* cart) {
|
||||
if (!cart) return PXL8_ERROR_NULL_POINTER;
|
||||
if (cart->is_mounted) return PXL8_OK;
|
||||
|
||||
if (pxl8_current_cart) {
|
||||
pxl8_cart_unmount(pxl8_current_cart);
|
||||
}
|
||||
|
||||
if (cart->is_folder) {
|
||||
pxl8_original_cwd = getcwd(NULL, 0);
|
||||
if (chdir(cart->base_path) != 0) {
|
||||
pxl8_error("Failed to change to cart directory: %s", cart->base_path);
|
||||
free(pxl8_original_cwd);
|
||||
pxl8_original_cwd = NULL;
|
||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
|
||||
cart->is_mounted = true;
|
||||
pxl8_current_cart = cart;
|
||||
if (cart->title) {
|
||||
pxl8_info("Mounted cart: %s", cart->title);
|
||||
} else {
|
||||
pxl8_info("Mounted cart");
|
||||
}
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
void pxl8_cart_unmount(pxl8_cart* cart) {
|
||||
if (!cart || !cart->is_mounted) return;
|
||||
|
||||
if (pxl8_original_cwd) {
|
||||
chdir(pxl8_original_cwd);
|
||||
free(pxl8_original_cwd);
|
||||
pxl8_original_cwd = NULL;
|
||||
}
|
||||
|
||||
cart->is_mounted = false;
|
||||
if (pxl8_current_cart == cart) {
|
||||
pxl8_current_cart = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
const char* pxl8_cart_get_base_path(const pxl8_cart* cart) {
|
||||
return cart ? cart->base_path : NULL;
|
||||
}
|
||||
|
||||
const char* pxl8_cart_get_title(const pxl8_cart* cart) {
|
||||
return cart ? cart->title : NULL;
|
||||
}
|
||||
|
||||
void pxl8_cart_set_title(pxl8_cart* cart, const char* title) {
|
||||
if (!cart || !title) return;
|
||||
free(cart->title);
|
||||
cart->title = strdup(title);
|
||||
}
|
||||
|
||||
pxl8_resolution pxl8_cart_get_resolution(const pxl8_cart* cart) {
|
||||
return cart ? cart->resolution : PXL8_RESOLUTION_640x360;
|
||||
}
|
||||
|
||||
void pxl8_cart_set_resolution(pxl8_cart* cart, pxl8_resolution resolution) {
|
||||
if (cart) cart->resolution = resolution;
|
||||
}
|
||||
|
||||
pxl8_size pxl8_cart_get_window_size(const pxl8_cart* cart) {
|
||||
return cart ? cart->window_size : (pxl8_size){1280, 720};
|
||||
}
|
||||
|
||||
void pxl8_cart_set_window_size(pxl8_cart* cart, pxl8_size size) {
|
||||
if (cart) cart->window_size = size;
|
||||
}
|
||||
|
||||
pxl8_pixel_mode pxl8_cart_get_pixel_mode(const pxl8_cart* cart) {
|
||||
return cart ? cart->pixel_mode : PXL8_PIXEL_INDEXED;
|
||||
}
|
||||
|
||||
void pxl8_cart_set_pixel_mode(pxl8_cart* cart, pxl8_pixel_mode mode) {
|
||||
if (cart) cart->pixel_mode = mode;
|
||||
}
|
||||
|
||||
bool pxl8_cart_is_packed(const pxl8_cart* cart) {
|
||||
return cart && !cart->is_folder;
|
||||
}
|
||||
|
||||
bool pxl8_cart_has_embedded(const char* exe_path) {
|
||||
FILE* file = fopen(exe_path, "rb");
|
||||
if (!file) return false;
|
||||
|
||||
fseek(file, -(i32)sizeof(pxl8_cart_trailer), SEEK_END);
|
||||
pxl8_cart_trailer trailer;
|
||||
bool has = (fread(&trailer, sizeof(trailer), 1, file) == 1 &&
|
||||
trailer.magic == PXL8_CART_TRAILER_MAGIC);
|
||||
fclose(file);
|
||||
return has;
|
||||
}
|
||||
|
||||
bool pxl8_cart_file_exists(const pxl8_cart* cart, const char* path) {
|
||||
if (!cart || !path) return false;
|
||||
|
||||
if (cart->is_folder) {
|
||||
char full_path[512];
|
||||
if (!pxl8_cart_resolve_path(cart, path, full_path, sizeof(full_path))) return false;
|
||||
return access(full_path, F_OK) == 0;
|
||||
}
|
||||
|
||||
return find_file(cart, path) != NULL;
|
||||
}
|
||||
|
||||
bool pxl8_cart_resolve_path(const pxl8_cart* cart, const char* relative_path, char* out_path, size_t out_size) {
|
||||
if (!cart || !relative_path || !out_path || out_size == 0) return false;
|
||||
|
||||
if (cart->is_folder && cart->base_path) {
|
||||
i32 written = snprintf(out_path, out_size, "%s/%s", cart->base_path, relative_path);
|
||||
return written >= 0 && (size_t)written < out_size;
|
||||
}
|
||||
|
||||
i32 written = snprintf(out_path, out_size, "%s", relative_path);
|
||||
return written >= 0 && (size_t)written < out_size;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_cart_read_file(const pxl8_cart* cart, const char* path, u8** data_out, u32* size_out) {
|
||||
if (!cart || !path || !data_out || !size_out) return PXL8_ERROR_NULL_POINTER;
|
||||
|
||||
if (cart->is_folder) {
|
||||
char full_path[512];
|
||||
if (!pxl8_cart_resolve_path(cart, path, full_path, sizeof(full_path))) {
|
||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
FILE* file = fopen(full_path, "rb");
|
||||
if (!file) return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
*size_out = (u32)ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
|
||||
*data_out = malloc(*size_out);
|
||||
if (!*data_out || fread(*data_out, 1, *size_out, file) != *size_out) {
|
||||
free(*data_out);
|
||||
*data_out = NULL;
|
||||
fclose(file);
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
fclose(file);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_cart_file* cf = find_file(cart, path);
|
||||
if (!cf) return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
|
||||
*size_out = cf->size;
|
||||
*data_out = malloc(cf->size);
|
||||
if (!*data_out) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
memcpy(*data_out, cart->data + cf->offset, cf->size);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
void pxl8_cart_free_file(u8* data) {
|
||||
free(data);
|
||||
}
|
||||
|
||||
pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path) {
|
||||
if (!folder_path || !output_path) return PXL8_ERROR_NULL_POINTER;
|
||||
|
||||
if (!is_directory(folder_path)) {
|
||||
pxl8_error("Cart folder not found: %s", folder_path);
|
||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (!has_main_script(folder_path)) {
|
||||
pxl8_error("No main.fnl or main.lua found in cart: %s", folder_path);
|
||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
char** paths = NULL;
|
||||
u32 count = 0, capacity = 0;
|
||||
collect_files_recursive(folder_path, "", &paths, &count, &capacity);
|
||||
|
||||
if (count == 0) {
|
||||
pxl8_error("No files found in cart folder");
|
||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
u32 toc_size = 0;
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
toc_size += sizeof(pxl8_cart_entry) + strlen(paths[i]);
|
||||
}
|
||||
|
||||
u32 data_offset = sizeof(pxl8_cart_header) + toc_size;
|
||||
u32 total_size = data_offset;
|
||||
|
||||
u32* file_sizes = malloc(count * sizeof(u32));
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
char full_path[1024];
|
||||
snprintf(full_path, sizeof(full_path), "%s/%s", folder_path, paths[i]);
|
||||
struct stat st;
|
||||
if (stat(full_path, &st) == 0) {
|
||||
file_sizes[i] = (u32)st.st_size;
|
||||
total_size += file_sizes[i];
|
||||
} else {
|
||||
file_sizes[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
u8* buffer = calloc(1, total_size);
|
||||
if (!buffer) {
|
||||
free(file_sizes);
|
||||
for (u32 i = 0; i < count; i++) free(paths[i]);
|
||||
free(paths);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
pxl8_cart_header* header = (pxl8_cart_header*)buffer;
|
||||
header->magic = PXL8_CART_MAGIC;
|
||||
header->version = PXL8_CART_VERSION;
|
||||
header->flags = 0;
|
||||
header->file_count = count;
|
||||
header->toc_size = toc_size;
|
||||
|
||||
u8* toc = buffer + sizeof(pxl8_cart_header);
|
||||
u32 file_offset = data_offset;
|
||||
|
||||
pxl8_info("Packing cart: %s -> %s", folder_path, output_path);
|
||||
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
pxl8_cart_entry* entry = (pxl8_cart_entry*)toc;
|
||||
entry->offset = file_offset;
|
||||
entry->size = file_sizes[i];
|
||||
entry->path_len = (u16)strlen(paths[i]);
|
||||
toc += sizeof(pxl8_cart_entry);
|
||||
|
||||
memcpy(toc, paths[i], entry->path_len);
|
||||
toc += entry->path_len;
|
||||
|
||||
char full_path[1024];
|
||||
snprintf(full_path, sizeof(full_path), "%s/%s", folder_path, paths[i]);
|
||||
FILE* file = fopen(full_path, "rb");
|
||||
if (file) {
|
||||
fread(buffer + file_offset, 1, file_sizes[i], file);
|
||||
fclose(file);
|
||||
pxl8_info(" %s (%u bytes)", paths[i], file_sizes[i]);
|
||||
}
|
||||
|
||||
file_offset += file_sizes[i];
|
||||
free(paths[i]);
|
||||
}
|
||||
free(paths);
|
||||
free(file_sizes);
|
||||
|
||||
FILE* out = fopen(output_path, "wb");
|
||||
if (!out) {
|
||||
free(buffer);
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
fwrite(buffer, 1, total_size, out);
|
||||
fclose(out);
|
||||
free(buffer);
|
||||
|
||||
pxl8_info("Cart packed: %u files, %u bytes", count, total_size);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_cart_bundle(const char* input_path, const char* output_path, const char* exe_path) {
|
||||
if (!input_path || !output_path || !exe_path) return PXL8_ERROR_NULL_POINTER;
|
||||
|
||||
u8* cart_data = NULL;
|
||||
u32 cart_size = 0;
|
||||
bool free_cart = false;
|
||||
|
||||
if (is_directory(input_path)) {
|
||||
char temp_pxc[256];
|
||||
snprintf(temp_pxc, sizeof(temp_pxc), "/tmp/pxl8_bundle_%d.pxc", getpid());
|
||||
pxl8_result result = pxl8_cart_pack(input_path, temp_pxc);
|
||||
if (result != PXL8_OK) return result;
|
||||
|
||||
FILE* f = fopen(temp_pxc, "rb");
|
||||
if (!f) return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
fseek(f, 0, SEEK_END);
|
||||
cart_size = (u32)ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
cart_data = malloc(cart_size);
|
||||
fread(cart_data, 1, cart_size, f);
|
||||
fclose(f);
|
||||
unlink(temp_pxc);
|
||||
free_cart = true;
|
||||
} else if (is_pxc_file(input_path)) {
|
||||
FILE* f = fopen(input_path, "rb");
|
||||
if (!f) return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
fseek(f, 0, SEEK_END);
|
||||
cart_size = (u32)ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
cart_data = malloc(cart_size);
|
||||
fread(cart_data, 1, cart_size, f);
|
||||
fclose(f);
|
||||
free_cart = true;
|
||||
} else {
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
FILE* exe = fopen(exe_path, "rb");
|
||||
if (!exe) {
|
||||
if (free_cart) free(cart_data);
|
||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
fseek(exe, 0, SEEK_END);
|
||||
u32 exe_size = (u32)ftell(exe);
|
||||
fseek(exe, 0, SEEK_SET);
|
||||
|
||||
u8* exe_data = malloc(exe_size);
|
||||
fread(exe_data, 1, exe_size, exe);
|
||||
fclose(exe);
|
||||
|
||||
FILE* out = fopen(output_path, "wb");
|
||||
if (!out) {
|
||||
free(exe_data);
|
||||
if (free_cart) free(cart_data);
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
fwrite(exe_data, 1, exe_size, out);
|
||||
free(exe_data);
|
||||
|
||||
u32 cart_offset = exe_size;
|
||||
fwrite(cart_data, 1, cart_size, out);
|
||||
if (free_cart) free(cart_data);
|
||||
|
||||
pxl8_cart_trailer trailer = {
|
||||
.magic = PXL8_CART_TRAILER_MAGIC,
|
||||
.cart_offset = cart_offset,
|
||||
.cart_size = cart_size
|
||||
};
|
||||
fwrite(&trailer, sizeof(trailer), 1, out);
|
||||
fclose(out);
|
||||
|
||||
chmod(output_path, 0755);
|
||||
|
||||
pxl8_info("Bundle created: %s", output_path);
|
||||
return PXL8_OK;
|
||||
}
|
||||
43
src/asset/pxl8_cart.h
Normal file
43
src/asset/pxl8_cart.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_cart pxl8_cart;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_cart* pxl8_cart_create(void);
|
||||
void pxl8_cart_destroy(pxl8_cart* cart);
|
||||
|
||||
pxl8_result pxl8_cart_bundle(const char* input_path, const char* output_path, const char* exe_path);
|
||||
pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path);
|
||||
|
||||
pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path);
|
||||
pxl8_result pxl8_cart_load_embedded(pxl8_cart* cart, const char* exe_path);
|
||||
void pxl8_cart_unload(pxl8_cart* cart);
|
||||
pxl8_result pxl8_cart_mount(pxl8_cart* cart);
|
||||
void pxl8_cart_unmount(pxl8_cart* cart);
|
||||
|
||||
pxl8_cart* pxl8_get_cart(void);
|
||||
const char* pxl8_cart_get_base_path(const pxl8_cart* cart);
|
||||
const char* pxl8_cart_get_title(const pxl8_cart* cart);
|
||||
pxl8_resolution pxl8_cart_get_resolution(const pxl8_cart* cart);
|
||||
pxl8_size pxl8_cart_get_window_size(const pxl8_cart* cart);
|
||||
pxl8_pixel_mode pxl8_cart_get_pixel_mode(const pxl8_cart* cart);
|
||||
void pxl8_cart_set_title(pxl8_cart* cart, const char* title);
|
||||
void pxl8_cart_set_resolution(pxl8_cart* cart, pxl8_resolution resolution);
|
||||
void pxl8_cart_set_window_size(pxl8_cart* cart, pxl8_size size);
|
||||
void pxl8_cart_set_pixel_mode(pxl8_cart* cart, pxl8_pixel_mode mode);
|
||||
bool pxl8_cart_is_packed(const pxl8_cart* cart);
|
||||
bool pxl8_cart_has_embedded(const char* exe_path);
|
||||
|
||||
bool pxl8_cart_file_exists(const pxl8_cart* cart, const char* path);
|
||||
bool pxl8_cart_resolve_path(const pxl8_cart* cart, const char* relative_path, char* out_path, size_t out_size);
|
||||
pxl8_result pxl8_cart_read_file(const pxl8_cart* cart, const char* path, u8** data_out, u32* size_out);
|
||||
void pxl8_cart_free_file(u8* data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
103
src/asset/pxl8_embed.h
Normal file
103
src/asset/pxl8_embed.h
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
#include <string.h>
|
||||
|
||||
static const char embed_fennel[] = {
|
||||
#embed "lib/fennel/fennel.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8[] = {
|
||||
#embed "src/lua/pxl8.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_anim[] = {
|
||||
#embed "src/lua/pxl8/anim.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_bytes[] = {
|
||||
#embed "src/lua/pxl8/bytes.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_core[] = {
|
||||
#embed "src/lua/pxl8/core.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_effects[] = {
|
||||
#embed "src/lua/pxl8/effects.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_gfx2d[] = {
|
||||
#embed "src/lua/pxl8/gfx2d.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_gfx3d[] = {
|
||||
#embed "src/lua/pxl8/gfx3d.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_gui[] = {
|
||||
#embed "src/lua/pxl8/gui.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_input[] = {
|
||||
#embed "src/lua/pxl8/input.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_math[] = {
|
||||
#embed "src/lua/pxl8/math.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_net[] = {
|
||||
#embed "src/lua/pxl8/net.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_particles[] = {
|
||||
#embed "src/lua/pxl8/particles.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_sfx[] = {
|
||||
#embed "src/lua/pxl8/sfx.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_tilemap[] = {
|
||||
#embed "src/lua/pxl8/tilemap.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_transition[] = {
|
||||
#embed "src/lua/pxl8/transition.lua"
|
||||
, 0 };
|
||||
|
||||
static const char embed_pxl8_world[] = {
|
||||
#embed "src/lua/pxl8/world.lua"
|
||||
, 0 };
|
||||
|
||||
#define PXL8_EMBED_ENTRY(var, name) {name, var, sizeof(var) - 1}
|
||||
|
||||
typedef struct { const char* name; const char* data; u32 size; } pxl8_embed;
|
||||
|
||||
static const pxl8_embed pxl8_embeds[] = {
|
||||
PXL8_EMBED_ENTRY(embed_fennel, "fennel"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8, "pxl8"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_anim, "pxl8.anim"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_bytes, "pxl8.bytes"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_core, "pxl8.core"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_effects, "pxl8.effects"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_gfx2d, "pxl8.gfx2d"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_gfx3d, "pxl8.gfx3d"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_gui, "pxl8.gui"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_input, "pxl8.input"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_math, "pxl8.math"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_net, "pxl8.net"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_particles, "pxl8.particles"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_sfx, "pxl8.sfx"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_tilemap, "pxl8.tilemap"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_transition, "pxl8.transition"),
|
||||
PXL8_EMBED_ENTRY(embed_pxl8_world, "pxl8.world"),
|
||||
{0}
|
||||
};
|
||||
|
||||
static inline const pxl8_embed* pxl8_embed_find(const char* name) {
|
||||
for (const pxl8_embed* e = pxl8_embeds; e->name; e++)
|
||||
if (strcmp(e->name, name) == 0) return e;
|
||||
return NULL;
|
||||
}
|
||||
272
src/asset/pxl8_save.c
Normal file
272
src/asset/pxl8_save.c
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
#include "pxl8_save.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <direct.h>
|
||||
#include <shlobj.h>
|
||||
#define PATH_SEP '\\'
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <pwd.h>
|
||||
#define PATH_SEP '/'
|
||||
#endif
|
||||
|
||||
#include "pxl8_log.h"
|
||||
|
||||
typedef struct {
|
||||
u32 magic;
|
||||
u32 version;
|
||||
u32 size;
|
||||
u32 checksum;
|
||||
} pxl8_save_header;
|
||||
|
||||
struct pxl8_save {
|
||||
char directory[PXL8_SAVE_MAX_PATH];
|
||||
u32 magic;
|
||||
u32 version;
|
||||
};
|
||||
|
||||
static u32 pxl8_save_checksum(const u8* data, u32 size) {
|
||||
u32 hash = 2166136261u;
|
||||
for (u32 i = 0; i < size; i++) {
|
||||
hash ^= data[i];
|
||||
hash *= 16777619u;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
static void pxl8_save_get_slot_path(pxl8_save* save, u8 slot, char* path, size_t path_size) {
|
||||
if (slot == PXL8_SAVE_HOTRELOAD_SLOT) {
|
||||
snprintf(path, path_size, "%s%chotreload.sav", save->directory, PATH_SEP);
|
||||
} else {
|
||||
snprintf(path, path_size, "%s%csave%d.sav", save->directory, PATH_SEP, slot);
|
||||
}
|
||||
}
|
||||
|
||||
static pxl8_result pxl8_save_ensure_directory(const char* path) {
|
||||
struct stat st;
|
||||
if (stat(path, &st) == 0) {
|
||||
return S_ISDIR(st.st_mode) ? PXL8_OK : PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (_mkdir(path) != 0 && errno != EEXIST) {
|
||||
#else
|
||||
if (mkdir(path, 0755) != 0 && errno != EEXIST) {
|
||||
#endif
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version) {
|
||||
if (!game_name) return NULL;
|
||||
|
||||
pxl8_save* save = (pxl8_save*)calloc(1, sizeof(pxl8_save));
|
||||
if (!save) return NULL;
|
||||
|
||||
save->magic = magic;
|
||||
save->version = version;
|
||||
|
||||
char base_dir[PXL8_SAVE_MAX_PATH];
|
||||
|
||||
#ifdef _WIN32
|
||||
if (SUCCEEDED(SHGetFolderPathA(NULL, CSIDL_APPDATA, NULL, 0, base_dir))) {
|
||||
snprintf(save->directory, sizeof(save->directory),
|
||||
"%s%cpxl8%c%s", base_dir, PATH_SEP, PATH_SEP, game_name);
|
||||
} else {
|
||||
free(save);
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
const char* home = getenv("HOME");
|
||||
if (!home) {
|
||||
struct passwd* pw = getpwuid(getuid());
|
||||
if (pw) home = pw->pw_dir;
|
||||
}
|
||||
if (!home) {
|
||||
free(save);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
snprintf(base_dir, sizeof(base_dir), "%s/.local/share", home);
|
||||
pxl8_save_ensure_directory(base_dir);
|
||||
|
||||
snprintf(base_dir, sizeof(base_dir), "%s/.local/share/pxl8", home);
|
||||
pxl8_save_ensure_directory(base_dir);
|
||||
|
||||
snprintf(save->directory, sizeof(save->directory),
|
||||
"%s/.local/share/pxl8/%s", home, game_name);
|
||||
#endif
|
||||
|
||||
if (pxl8_save_ensure_directory(save->directory) != PXL8_OK) {
|
||||
free(save);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pxl8_info("Save system initialized: %s", save->directory);
|
||||
return save;
|
||||
}
|
||||
|
||||
void pxl8_save_destroy(pxl8_save* save) {
|
||||
if (save) {
|
||||
free(save);
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_result pxl8_save_write(pxl8_save* save, u8 slot, const u8* data, u32 size) {
|
||||
if (!save) return PXL8_ERROR_NULL_POINTER;
|
||||
if (!data || size == 0) return PXL8_ERROR_NULL_POINTER;
|
||||
if (slot >= PXL8_SAVE_MAX_SLOTS && slot != PXL8_SAVE_HOTRELOAD_SLOT) {
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
char path[PXL8_SAVE_MAX_PATH];
|
||||
pxl8_save_get_slot_path(save, slot, path, sizeof(path));
|
||||
|
||||
FILE* file = fopen(path, "wb");
|
||||
if (!file) {
|
||||
pxl8_error("Failed to open save file for writing: %s", path);
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
pxl8_save_header header = {
|
||||
.magic = save->magic,
|
||||
.version = save->version,
|
||||
.size = size,
|
||||
.checksum = pxl8_save_checksum(data, size)
|
||||
};
|
||||
|
||||
bool success = true;
|
||||
if (fwrite(&header, sizeof(header), 1, file) != 1) success = false;
|
||||
if (success && fwrite(data, 1, size, file) != size) success = false;
|
||||
|
||||
fclose(file);
|
||||
|
||||
if (!success) {
|
||||
pxl8_error("Failed to write save data");
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
if (slot == PXL8_SAVE_HOTRELOAD_SLOT) {
|
||||
pxl8_debug("Hot reload state saved (%u bytes)", size);
|
||||
} else {
|
||||
pxl8_info("Game saved to slot %d (%u bytes)", slot, size);
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_save_read(pxl8_save* save, u8 slot, u8** data_out, u32* size_out) {
|
||||
if (!save) return PXL8_ERROR_NULL_POINTER;
|
||||
if (!data_out || !size_out) return PXL8_ERROR_NULL_POINTER;
|
||||
if (slot >= PXL8_SAVE_MAX_SLOTS && slot != PXL8_SAVE_HOTRELOAD_SLOT) {
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
*data_out = NULL;
|
||||
*size_out = 0;
|
||||
|
||||
char path[PXL8_SAVE_MAX_PATH];
|
||||
pxl8_save_get_slot_path(save, slot, path, sizeof(path));
|
||||
|
||||
FILE* file = fopen(path, "rb");
|
||||
if (!file) {
|
||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
pxl8_save_header header;
|
||||
if (fread(&header, sizeof(header), 1, file) != 1) {
|
||||
fclose(file);
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
if (header.magic != save->magic) {
|
||||
fclose(file);
|
||||
pxl8_error("Invalid save file magic");
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
if (header.version > save->version) {
|
||||
fclose(file);
|
||||
pxl8_error("Save file version too new: %u", header.version);
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
u8* data = (u8*)malloc(header.size);
|
||||
if (!data) {
|
||||
fclose(file);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
if (fread(data, 1, header.size, file) != header.size) {
|
||||
free(data);
|
||||
fclose(file);
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
|
||||
u32 checksum = pxl8_save_checksum(data, header.size);
|
||||
if (checksum != header.checksum) {
|
||||
free(data);
|
||||
pxl8_error("Save file checksum mismatch");
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
*data_out = data;
|
||||
*size_out = header.size;
|
||||
|
||||
if (slot == PXL8_SAVE_HOTRELOAD_SLOT) {
|
||||
pxl8_debug("Hot reload state loaded (%u bytes)", header.size);
|
||||
} else {
|
||||
pxl8_info("Game loaded from slot %d (%u bytes)", slot, header.size);
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
void pxl8_save_free(u8* data) {
|
||||
if (data) {
|
||||
free(data);
|
||||
}
|
||||
}
|
||||
|
||||
bool pxl8_save_exists(pxl8_save* save, u8 slot) {
|
||||
if (!save) return false;
|
||||
if (slot >= PXL8_SAVE_MAX_SLOTS && slot != PXL8_SAVE_HOTRELOAD_SLOT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char path[PXL8_SAVE_MAX_PATH];
|
||||
pxl8_save_get_slot_path(save, slot, path, sizeof(path));
|
||||
|
||||
struct stat st;
|
||||
return stat(path, &st) == 0;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_save_delete(pxl8_save* save, u8 slot) {
|
||||
if (!save) return PXL8_ERROR_NULL_POINTER;
|
||||
if (slot >= PXL8_SAVE_MAX_SLOTS && slot != PXL8_SAVE_HOTRELOAD_SLOT) {
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
char path[PXL8_SAVE_MAX_PATH];
|
||||
pxl8_save_get_slot_path(save, slot, path, sizeof(path));
|
||||
|
||||
if (remove(path) != 0 && errno != ENOENT) {
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
const char* pxl8_save_get_directory(pxl8_save* save) {
|
||||
return save ? save->directory : NULL;
|
||||
}
|
||||
27
src/asset/pxl8_save.h
Normal file
27
src/asset/pxl8_save.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#define PXL8_SAVE_MAX_SLOTS 10
|
||||
#define PXL8_SAVE_HOTRELOAD_SLOT 255
|
||||
#define PXL8_SAVE_MAX_PATH 512
|
||||
|
||||
typedef struct pxl8_save pxl8_save;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version);
|
||||
void pxl8_save_destroy(pxl8_save* save);
|
||||
|
||||
pxl8_result pxl8_save_delete(pxl8_save* save, u8 slot);
|
||||
bool pxl8_save_exists(pxl8_save* save, u8 slot);
|
||||
void pxl8_save_free(u8* data);
|
||||
const char* pxl8_save_get_directory(pxl8_save* save);
|
||||
pxl8_result pxl8_save_read(pxl8_save* save, u8 slot, u8** data_out, u32* size_out);
|
||||
pxl8_result pxl8_save_write(pxl8_save* save, u8 slot, const u8* data, u32 size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
485
src/core/pxl8.c
Normal file
485
src/core/pxl8.c
Normal file
|
|
@ -0,0 +1,485 @@
|
|||
#define PXL8_AUTHORS "asrael <asrael@pxl8.org>"
|
||||
#define PXL8_COPYRIGHT "Copyright (c) 2024-2025 pxl8.org"
|
||||
#define PXL8_VERSION "0.1.0"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "pxl8_game.h"
|
||||
#include "pxl8_hal.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_macros.h"
|
||||
#include "pxl8_repl.h"
|
||||
#include "pxl8_replay.h"
|
||||
#include "pxl8_script.h"
|
||||
#include "pxl8_sfx.h"
|
||||
#include "pxl8_sys.h"
|
||||
|
||||
struct pxl8 {
|
||||
pxl8_cart* cart;
|
||||
pxl8_game* game;
|
||||
pxl8_repl* repl;
|
||||
pxl8_log log;
|
||||
const pxl8_hal* hal;
|
||||
void* platform_data;
|
||||
};
|
||||
|
||||
#ifndef NDEBUG
|
||||
static void pxl8_audio_event_callback(u8 event_type, u8 context_id, u8 note, f32 volume, void* userdata) {
|
||||
pxl8_game* game = (pxl8_game*)userdata;
|
||||
if (game && game->debug_replay) {
|
||||
pxl8_replay_write_audio_event(game->debug_replay, game->frame_count, event_type, context_id, note, volume);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
pxl8* pxl8_create(const pxl8_hal* hal) {
|
||||
pxl8* sys = (pxl8*)calloc(1, sizeof(pxl8));
|
||||
if (!sys) return NULL;
|
||||
|
||||
pxl8_log_init(&sys->log);
|
||||
|
||||
if (!hal) {
|
||||
pxl8_error("hal cannot be null");
|
||||
free(sys);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sys->hal = hal;
|
||||
|
||||
sys->game = (pxl8_game*)calloc(1, sizeof(pxl8_game));
|
||||
if (!sys->game) {
|
||||
pxl8_error("failed to allocate game");
|
||||
free(sys);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return sys;
|
||||
}
|
||||
|
||||
void pxl8_destroy(pxl8* sys) {
|
||||
if (!sys) return;
|
||||
|
||||
if (sys->game) free(sys->game);
|
||||
if (sys->cart) pxl8_cart_destroy(sys->cart);
|
||||
if (sys->hal && sys->platform_data) sys->hal->destroy(sys->platform_data);
|
||||
|
||||
free(sys);
|
||||
}
|
||||
|
||||
static void pxl8_print_help(void) {
|
||||
printf("pxl8 %s - pixel art game framework\n", PXL8_VERSION);
|
||||
printf("%s\n\n", PXL8_COPYRIGHT);
|
||||
printf("Usage: pxl8 [path] [--repl]\n\n");
|
||||
printf(" pxl8 Run main.fnl from current directory\n");
|
||||
printf(" pxl8 ./game Run game from folder\n");
|
||||
printf(" pxl8 game.pxc Run packed cart file\n");
|
||||
printf(" pxl8 --repl Run with REPL enabled\n\n");
|
||||
printf("Other commands:\n");
|
||||
printf(" pxl8 pack <folder> <out.pxc> Pack folder into cart file\n");
|
||||
printf(" pxl8 bundle <in> <out> Bundle cart into executable\n");
|
||||
printf(" pxl8 help Show this help\n");
|
||||
}
|
||||
|
||||
pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]) {
|
||||
if (!sys || !sys->game) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
pxl8_game* game = sys->game;
|
||||
const char* script_arg = NULL;
|
||||
bool bundle_mode = false;
|
||||
bool pack_mode = false;
|
||||
bool run_mode = false;
|
||||
const char* pack_input = NULL;
|
||||
const char* pack_output = NULL;
|
||||
|
||||
bool has_embedded = pxl8_cart_has_embedded(argv[0]);
|
||||
|
||||
for (i32 i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "help") == 0 || strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
|
||||
pxl8_print_help();
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
} else if (strcmp(argv[i], "run") == 0) {
|
||||
run_mode = true;
|
||||
} else if (strcmp(argv[i], "--repl") == 0) {
|
||||
game->repl_mode = true;
|
||||
} else if (strcmp(argv[i], "bundle") == 0 || strcmp(argv[i], "--bundle") == 0) {
|
||||
bundle_mode = true;
|
||||
if (i + 2 < argc) {
|
||||
pack_input = argv[++i];
|
||||
pack_output = argv[++i];
|
||||
} else {
|
||||
pxl8_error("bundle requires <folder|.pxc> <output>");
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
} else if (strcmp(argv[i], "pack") == 0 || strcmp(argv[i], "--pack") == 0) {
|
||||
pack_mode = true;
|
||||
if (i + 2 < argc) {
|
||||
pack_input = argv[++i];
|
||||
pack_output = argv[++i];
|
||||
} else {
|
||||
pxl8_error("pack requires <folder> <output.pxc>");
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
} else if (!script_arg) {
|
||||
script_arg = argv[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (!run_mode && !bundle_mode && !pack_mode) {
|
||||
run_mode = true;
|
||||
}
|
||||
|
||||
if (bundle_mode) {
|
||||
char exe_path[1024];
|
||||
ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1);
|
||||
if (len == -1) {
|
||||
pxl8_error("failed to resolve executable path");
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
exe_path[len] = '\0';
|
||||
pxl8_result result = pxl8_cart_bundle(pack_input, pack_output, exe_path);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (pack_mode) {
|
||||
pxl8_result result = pxl8_cart_pack(pack_input, pack_output);
|
||||
return result;
|
||||
}
|
||||
|
||||
pxl8_info("Starting up");
|
||||
|
||||
game->script = pxl8_script_create(game->repl_mode);
|
||||
if (!game->script) {
|
||||
pxl8_error("failed to initialize scripting: %s", pxl8_script_get_last_error(game->script));
|
||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
|
||||
const char* cart_path = script_arg;
|
||||
char* original_cwd = getcwd(NULL, 0);
|
||||
|
||||
bool load_embedded = has_embedded && !run_mode;
|
||||
bool load_from_path = false;
|
||||
|
||||
if (!load_embedded && run_mode) {
|
||||
if (!cart_path) {
|
||||
if (access("main.fnl", F_OK) == 0 || access("main.lua", F_OK) == 0) {
|
||||
cart_path = ".";
|
||||
} else {
|
||||
pxl8_error("no main.fnl or main.lua found in current directory");
|
||||
free(original_cwd);
|
||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
}
|
||||
struct stat st;
|
||||
load_from_path = (stat(cart_path, &st) == 0 && S_ISDIR(st.st_mode)) ||
|
||||
strstr(cart_path, ".pxc");
|
||||
}
|
||||
|
||||
if (load_embedded || load_from_path) {
|
||||
sys->cart = pxl8_cart_create();
|
||||
pxl8_result load_result = load_embedded
|
||||
? pxl8_cart_load_embedded(sys->cart, argv[0])
|
||||
: pxl8_cart_load(sys->cart, cart_path);
|
||||
|
||||
if (!sys->cart || load_result != PXL8_OK) {
|
||||
pxl8_error("failed to load cart%s%s", load_from_path ? ": " : "", load_from_path ? cart_path : "");
|
||||
if (sys->cart) pxl8_cart_destroy(sys->cart);
|
||||
sys->cart = NULL;
|
||||
free(original_cwd);
|
||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
|
||||
pxl8_cart_mount(sys->cart);
|
||||
pxl8_script_load_cart_manifest(game->script, sys->cart);
|
||||
if (load_from_path) {
|
||||
pxl8_script_set_cart_path(game->script, pxl8_cart_get_base_path(sys->cart), original_cwd);
|
||||
}
|
||||
pxl8_strncpy(game->script_path, "main.fnl", sizeof(game->script_path));
|
||||
} else if (script_arg) {
|
||||
pxl8_strncpy(game->script_path, script_arg, sizeof(game->script_path));
|
||||
}
|
||||
free(original_cwd);
|
||||
|
||||
const char* window_title = pxl8_cart_get_title(sys->cart);
|
||||
if (!window_title) window_title = "pxl8";
|
||||
pxl8_resolution resolution = pxl8_cart_get_resolution(sys->cart);
|
||||
pxl8_pixel_mode pixel_mode = pxl8_cart_get_pixel_mode(sys->cart);
|
||||
pxl8_size window_size = pxl8_cart_get_window_size(sys->cart);
|
||||
pxl8_size render_size = pxl8_get_resolution_dimensions(resolution);
|
||||
|
||||
sys->platform_data = sys->hal->create(render_size.w, render_size.h, window_title, window_size.w, window_size.h);
|
||||
if (!sys->platform_data) {
|
||||
pxl8_error("failed to create platform context");
|
||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
|
||||
game->gfx = pxl8_gfx_create(sys->hal, sys->platform_data, pixel_mode, resolution);
|
||||
if (!game->gfx) {
|
||||
pxl8_error("failed to create graphics context");
|
||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
|
||||
if (pxl8_gfx_load_font_atlas(game->gfx) != PXL8_OK) {
|
||||
pxl8_error("failed to load font atlas");
|
||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
|
||||
game->mixer = pxl8_sfx_mixer_create(sys->hal);
|
||||
if (!game->mixer) {
|
||||
pxl8_error("failed to create audio mixer");
|
||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
|
||||
pxl8_rng_seed(&game->rng, (u32)sys->hal->get_ticks());
|
||||
|
||||
#ifndef NDEBUG
|
||||
game->debug_replay = pxl8_replay_create_buffer(60, 60);
|
||||
pxl8_sfx_mixer_set_event_callback(game->mixer, pxl8_audio_event_callback, game);
|
||||
#endif
|
||||
|
||||
if (game->repl_mode) {
|
||||
pxl8_info("starting in REPL mode with script: %s", game->script_path);
|
||||
}
|
||||
|
||||
pxl8_script_set_gfx(game->script, game->gfx);
|
||||
pxl8_script_set_input(game->script, &game->input);
|
||||
pxl8_script_set_rng(game->script, &game->rng);
|
||||
pxl8_script_set_sfx(game->script, game->mixer);
|
||||
pxl8_script_set_sys(game->script, sys);
|
||||
|
||||
if (game->script_path[0] != '\0') {
|
||||
pxl8_result result = pxl8_script_load_main(game->script, game->script_path);
|
||||
game->script_loaded = (result == PXL8_OK);
|
||||
|
||||
if (game->script_loaded && !game->repl_mode) {
|
||||
pxl8_result init_result = pxl8_script_call_function(game->script, "init");
|
||||
if (init_result != PXL8_OK) {
|
||||
pxl8_script_error("%s", pxl8_script_get_last_error(game->script));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
game->last_time = sys->hal->get_ticks();
|
||||
game->running = true;
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_update(pxl8* sys) {
|
||||
if (!sys || !sys->game) {
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
pxl8_game* game = sys->game;
|
||||
u64 current_time = sys->hal->get_ticks();
|
||||
f32 dt = (f32)(current_time - game->last_time) / 1000000000.0f;
|
||||
|
||||
game->last_time = current_time;
|
||||
game->time += dt;
|
||||
game->fps_accumulator += dt;
|
||||
game->fps_frame_count++;
|
||||
|
||||
if (game->fps_accumulator >= 1.0f) {
|
||||
game->fps = (f32)game->fps_frame_count / game->fps_accumulator;
|
||||
game->fps_accumulator = 0.0f;
|
||||
game->fps_frame_count = 0;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
u32 rng_state_before_reload = game->rng.state;
|
||||
#endif
|
||||
bool reloaded = pxl8_script_check_reload(game->script);
|
||||
#ifndef NDEBUG
|
||||
if (reloaded) {
|
||||
game->rng.state = rng_state_before_reload;
|
||||
pxl8_debug("Hot-reload: restored RNG state 0x%08X", rng_state_before_reload);
|
||||
}
|
||||
#else
|
||||
(void)reloaded;
|
||||
#endif
|
||||
|
||||
if (game->repl_mode && !game->repl_started) {
|
||||
if (game->script_loaded) {
|
||||
pxl8_script_call_function(game->script, "init");
|
||||
}
|
||||
|
||||
if (pxl8_script_load_module(game->script, "pxl8") != PXL8_OK) {
|
||||
const char* err_msg = pxl8_script_get_last_error(game->script);
|
||||
pxl8_error("failed to setup pxl8 global: %s", err_msg);
|
||||
}
|
||||
|
||||
sys->repl = pxl8_repl_create();
|
||||
game->repl_started = true;
|
||||
}
|
||||
|
||||
if (game->repl_mode && sys->repl) {
|
||||
if (pxl8_repl_should_quit(sys->repl)) game->running = false;
|
||||
|
||||
pxl8_repl_command* cmd = pxl8_repl_pop_command(sys->repl);
|
||||
if (cmd) {
|
||||
pxl8_result result = pxl8_script_eval_repl(game->script, pxl8_repl_command_buffer(cmd));
|
||||
if (result != PXL8_OK) {
|
||||
if (pxl8_script_is_incomplete_input(game->script)) {
|
||||
pxl8_repl_signal_complete(sys->repl);
|
||||
} else {
|
||||
pxl8_error("%s", pxl8_script_get_last_error(game->script));
|
||||
pxl8_repl_clear_accumulator(sys->repl);
|
||||
pxl8_repl_signal_complete(sys->repl);
|
||||
}
|
||||
} else {
|
||||
pxl8_repl_clear_accumulator(sys->repl);
|
||||
pxl8_repl_signal_complete(sys->repl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_gfx_update(game->gfx, dt);
|
||||
pxl8_sfx_mixer_process(game->mixer);
|
||||
|
||||
if (game->script_loaded) {
|
||||
pxl8_script_call_function_f32(game->script, "update", dt);
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_frame(pxl8* sys) {
|
||||
if (!sys || !sys->game) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
pxl8_game* game = sys->game;
|
||||
pxl8_bounds bounds = pxl8_gfx_get_bounds(game->gfx);
|
||||
|
||||
if (game->script_loaded) {
|
||||
pxl8_result frame_result = pxl8_script_call_function(game->script, "frame");
|
||||
if (frame_result == PXL8_ERROR_SCRIPT_ERROR) {
|
||||
pxl8_error("error calling frame: %s", pxl8_script_get_last_error(game->script));
|
||||
}
|
||||
} else {
|
||||
pxl8_2d_clear(game->gfx, 32);
|
||||
|
||||
i32 render_w = pxl8_gfx_get_width(game->gfx);
|
||||
i32 render_h = pxl8_gfx_get_height(game->gfx);
|
||||
|
||||
for (i32 y = 0; y < render_h; y += 24) {
|
||||
for (i32 x = 0; x < render_w; x += 32) {
|
||||
u32 color = ((x / 32) + (y / 24) + (i32)(game->time * 2)) % 8;
|
||||
pxl8_2d_rect_fill(game->gfx, x, y, 31, 23, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_gfx_set_viewport(game->gfx, pxl8_gfx_viewport(bounds, pxl8_gfx_get_width(game->gfx), pxl8_gfx_get_height(game->gfx)));
|
||||
pxl8_gfx_upload_framebuffer(game->gfx);
|
||||
pxl8_gfx_present(game->gfx);
|
||||
|
||||
memset(game->input.keys_pressed, 0, sizeof(game->input.keys_pressed));
|
||||
memset(game->input.keys_released, 0, sizeof(game->input.keys_released));
|
||||
memset(game->input.mouse_buttons_pressed, 0, sizeof(game->input.mouse_buttons_pressed));
|
||||
memset(game->input.mouse_buttons_released, 0, sizeof(game->input.mouse_buttons_released));
|
||||
|
||||
game->frame_count++;
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (game->debug_replay) {
|
||||
if (game->frame_count % 60 == 0) {
|
||||
pxl8_replay_write_keyframe(game->debug_replay, game->frame_count, game->time, &game->rng, &game->input);
|
||||
} else {
|
||||
pxl8_replay_write_input(game->debug_replay, game->frame_count, &game->prev_input, &game->input);
|
||||
}
|
||||
game->prev_input = game->input;
|
||||
}
|
||||
#endif
|
||||
|
||||
game->input.mouse_dx = 0;
|
||||
game->input.mouse_dy = 0;
|
||||
game->input.mouse_wheel_x = 0;
|
||||
game->input.mouse_wheel_y = 0;
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
void pxl8_quit(pxl8* sys) {
|
||||
if (!sys || !sys->game) return;
|
||||
|
||||
pxl8_game* game = sys->game;
|
||||
|
||||
pxl8_info("Shutting down");
|
||||
|
||||
if (sys->cart) {
|
||||
pxl8_cart_unmount(sys->cart);
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
pxl8_replay_destroy(game->debug_replay);
|
||||
#endif
|
||||
|
||||
pxl8_sfx_mixer_destroy(game->mixer);
|
||||
pxl8_gfx_destroy(game->gfx);
|
||||
pxl8_script_destroy(game->script);
|
||||
}
|
||||
|
||||
bool pxl8_is_running(const pxl8* sys) {
|
||||
return sys && sys->game && sys->game->running;
|
||||
}
|
||||
|
||||
void pxl8_set_running(pxl8* sys, bool running) {
|
||||
if (sys && sys->game) {
|
||||
sys->game->running = running;
|
||||
if (!running && sys->repl) {
|
||||
pxl8_repl_destroy(sys->repl);
|
||||
sys->repl = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f32 pxl8_get_fps(const pxl8* sys) {
|
||||
return (sys && sys->game) ? sys->game->fps : 0.0f;
|
||||
}
|
||||
|
||||
pxl8_gfx* pxl8_get_gfx(const pxl8* sys) {
|
||||
return (sys && sys->game) ? sys->game->gfx : NULL;
|
||||
}
|
||||
|
||||
pxl8_input_state* pxl8_get_input(const pxl8* sys) {
|
||||
return (sys && sys->game) ? &sys->game->input : NULL;
|
||||
}
|
||||
|
||||
pxl8_sfx_mixer* pxl8_get_sfx_mixer(const pxl8* sys) {
|
||||
return (sys && sys->game) ? sys->game->mixer : NULL;
|
||||
}
|
||||
|
||||
void pxl8_center_cursor(pxl8* sys) {
|
||||
if (!sys || !sys->hal || !sys->hal->center_cursor) return;
|
||||
sys->hal->center_cursor(sys->platform_data);
|
||||
}
|
||||
|
||||
void pxl8_set_cursor(pxl8* sys, pxl8_cursor cursor) {
|
||||
if (!sys || !sys->hal || !sys->hal->set_cursor) return;
|
||||
sys->hal->set_cursor(sys->platform_data, cursor);
|
||||
}
|
||||
|
||||
void pxl8_set_relative_mouse_mode(pxl8* sys, bool enabled) {
|
||||
if (!sys || !sys->hal || !sys->hal->set_relative_mouse_mode) return;
|
||||
sys->hal->set_relative_mouse_mode(sys->platform_data, enabled);
|
||||
if (sys->game) {
|
||||
sys->game->input.mouse_relative_mode = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_size pxl8_get_resolution_dimensions(pxl8_resolution resolution) {
|
||||
switch (resolution) {
|
||||
case PXL8_RESOLUTION_240x160: return (pxl8_size){240, 160};
|
||||
case PXL8_RESOLUTION_320x180: return (pxl8_size){320, 180};
|
||||
case PXL8_RESOLUTION_320x240: return (pxl8_size){320, 240};
|
||||
case PXL8_RESOLUTION_640x360: return (pxl8_size){640, 360};
|
||||
case PXL8_RESOLUTION_640x480: return (pxl8_size){640, 480};
|
||||
case PXL8_RESOLUTION_800x600: return (pxl8_size){800, 600};
|
||||
case PXL8_RESOLUTION_960x540: return (pxl8_size){960, 540};
|
||||
default: return (pxl8_size){640, 360};
|
||||
}
|
||||
}
|
||||
234
src/core/pxl8_bytes.c
Normal file
234
src/core/pxl8_bytes.c
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
#include "pxl8_bytes.h"
|
||||
#include <string.h>
|
||||
|
||||
void pxl8_pack_u8(u8* buf, size_t offset, u8 val) {
|
||||
buf[offset] = val;
|
||||
}
|
||||
|
||||
void pxl8_pack_u16_le(u8* buf, size_t offset, u16 val) {
|
||||
buf[offset] = (u8)(val);
|
||||
buf[offset + 1] = (u8)(val >> 8);
|
||||
}
|
||||
|
||||
void pxl8_pack_u16_be(u8* buf, size_t offset, u16 val) {
|
||||
buf[offset] = (u8)(val >> 8);
|
||||
buf[offset + 1] = (u8)(val);
|
||||
}
|
||||
|
||||
void pxl8_pack_u32_le(u8* buf, size_t offset, u32 val) {
|
||||
buf[offset] = (u8)(val);
|
||||
buf[offset + 1] = (u8)(val >> 8);
|
||||
buf[offset + 2] = (u8)(val >> 16);
|
||||
buf[offset + 3] = (u8)(val >> 24);
|
||||
}
|
||||
|
||||
void pxl8_pack_u32_be(u8* buf, size_t offset, u32 val) {
|
||||
buf[offset] = (u8)(val >> 24);
|
||||
buf[offset + 1] = (u8)(val >> 16);
|
||||
buf[offset + 2] = (u8)(val >> 8);
|
||||
buf[offset + 3] = (u8)(val);
|
||||
}
|
||||
|
||||
void pxl8_pack_u64_le(u8* buf, size_t offset, u64 val) {
|
||||
buf[offset] = (u8)(val);
|
||||
buf[offset + 1] = (u8)(val >> 8);
|
||||
buf[offset + 2] = (u8)(val >> 16);
|
||||
buf[offset + 3] = (u8)(val >> 24);
|
||||
buf[offset + 4] = (u8)(val >> 32);
|
||||
buf[offset + 5] = (u8)(val >> 40);
|
||||
buf[offset + 6] = (u8)(val >> 48);
|
||||
buf[offset + 7] = (u8)(val >> 56);
|
||||
}
|
||||
|
||||
void pxl8_pack_u64_be(u8* buf, size_t offset, u64 val) {
|
||||
buf[offset] = (u8)(val >> 56);
|
||||
buf[offset + 1] = (u8)(val >> 48);
|
||||
buf[offset + 2] = (u8)(val >> 40);
|
||||
buf[offset + 3] = (u8)(val >> 32);
|
||||
buf[offset + 4] = (u8)(val >> 24);
|
||||
buf[offset + 5] = (u8)(val >> 16);
|
||||
buf[offset + 6] = (u8)(val >> 8);
|
||||
buf[offset + 7] = (u8)(val);
|
||||
}
|
||||
|
||||
void pxl8_pack_i8(u8* buf, size_t offset, i8 val) {
|
||||
buf[offset] = (u8)val;
|
||||
}
|
||||
|
||||
void pxl8_pack_i16_le(u8* buf, size_t offset, i16 val) {
|
||||
pxl8_pack_u16_le(buf, offset, (u16)val);
|
||||
}
|
||||
|
||||
void pxl8_pack_i16_be(u8* buf, size_t offset, i16 val) {
|
||||
pxl8_pack_u16_be(buf, offset, (u16)val);
|
||||
}
|
||||
|
||||
void pxl8_pack_i32_le(u8* buf, size_t offset, i32 val) {
|
||||
pxl8_pack_u32_le(buf, offset, (u32)val);
|
||||
}
|
||||
|
||||
void pxl8_pack_i32_be(u8* buf, size_t offset, i32 val) {
|
||||
pxl8_pack_u32_be(buf, offset, (u32)val);
|
||||
}
|
||||
|
||||
void pxl8_pack_i64_le(u8* buf, size_t offset, i64 val) {
|
||||
pxl8_pack_u64_le(buf, offset, (u64)val);
|
||||
}
|
||||
|
||||
void pxl8_pack_i64_be(u8* buf, size_t offset, i64 val) {
|
||||
pxl8_pack_u64_be(buf, offset, (u64)val);
|
||||
}
|
||||
|
||||
void pxl8_pack_f32_le(u8* buf, size_t offset, f32 val) {
|
||||
u32 bits;
|
||||
memcpy(&bits, &val, sizeof(bits));
|
||||
pxl8_pack_u32_le(buf, offset, bits);
|
||||
}
|
||||
|
||||
void pxl8_pack_f32_be(u8* buf, size_t offset, f32 val) {
|
||||
u32 bits;
|
||||
memcpy(&bits, &val, sizeof(bits));
|
||||
pxl8_pack_u32_be(buf, offset, bits);
|
||||
}
|
||||
|
||||
void pxl8_pack_f64_le(u8* buf, size_t offset, f64 val) {
|
||||
u64 bits;
|
||||
memcpy(&bits, &val, sizeof(bits));
|
||||
pxl8_pack_u64_le(buf, offset, bits);
|
||||
}
|
||||
|
||||
void pxl8_pack_f64_be(u8* buf, size_t offset, f64 val) {
|
||||
u64 bits;
|
||||
memcpy(&bits, &val, sizeof(bits));
|
||||
pxl8_pack_u64_be(buf, offset, bits);
|
||||
}
|
||||
|
||||
u8 pxl8_unpack_u8(const u8* buf, size_t offset) {
|
||||
return buf[offset];
|
||||
}
|
||||
|
||||
u16 pxl8_unpack_u16_le(const u8* buf, size_t offset) {
|
||||
return (u16)buf[offset] | ((u16)buf[offset + 1] << 8);
|
||||
}
|
||||
|
||||
u16 pxl8_unpack_u16_be(const u8* buf, size_t offset) {
|
||||
return ((u16)buf[offset] << 8) | (u16)buf[offset + 1];
|
||||
}
|
||||
|
||||
u32 pxl8_unpack_u32_le(const u8* buf, size_t offset) {
|
||||
return (u32)buf[offset] |
|
||||
((u32)buf[offset + 1] << 8) |
|
||||
((u32)buf[offset + 2] << 16) |
|
||||
((u32)buf[offset + 3] << 24);
|
||||
}
|
||||
|
||||
u32 pxl8_unpack_u32_be(const u8* buf, size_t offset) {
|
||||
return ((u32)buf[offset] << 24) |
|
||||
((u32)buf[offset + 1] << 16) |
|
||||
((u32)buf[offset + 2] << 8) |
|
||||
(u32)buf[offset + 3];
|
||||
}
|
||||
|
||||
u64 pxl8_unpack_u64_le(const u8* buf, size_t offset) {
|
||||
return (u64)buf[offset] |
|
||||
((u64)buf[offset + 1] << 8) |
|
||||
((u64)buf[offset + 2] << 16) |
|
||||
((u64)buf[offset + 3] << 24) |
|
||||
((u64)buf[offset + 4] << 32) |
|
||||
((u64)buf[offset + 5] << 40) |
|
||||
((u64)buf[offset + 6] << 48) |
|
||||
((u64)buf[offset + 7] << 56);
|
||||
}
|
||||
|
||||
u64 pxl8_unpack_u64_be(const u8* buf, size_t offset) {
|
||||
return ((u64)buf[offset] << 56) |
|
||||
((u64)buf[offset + 1] << 48) |
|
||||
((u64)buf[offset + 2] << 40) |
|
||||
((u64)buf[offset + 3] << 32) |
|
||||
((u64)buf[offset + 4] << 24) |
|
||||
((u64)buf[offset + 5] << 16) |
|
||||
((u64)buf[offset + 6] << 8) |
|
||||
(u64)buf[offset + 7];
|
||||
}
|
||||
|
||||
i8 pxl8_unpack_i8(const u8* buf, size_t offset) {
|
||||
return (i8)buf[offset];
|
||||
}
|
||||
|
||||
i16 pxl8_unpack_i16_le(const u8* buf, size_t offset) {
|
||||
return (i16)pxl8_unpack_u16_le(buf, offset);
|
||||
}
|
||||
|
||||
i16 pxl8_unpack_i16_be(const u8* buf, size_t offset) {
|
||||
return (i16)pxl8_unpack_u16_be(buf, offset);
|
||||
}
|
||||
|
||||
i32 pxl8_unpack_i32_le(const u8* buf, size_t offset) {
|
||||
return (i32)pxl8_unpack_u32_le(buf, offset);
|
||||
}
|
||||
|
||||
i32 pxl8_unpack_i32_be(const u8* buf, size_t offset) {
|
||||
return (i32)pxl8_unpack_u32_be(buf, offset);
|
||||
}
|
||||
|
||||
i64 pxl8_unpack_i64_le(const u8* buf, size_t offset) {
|
||||
return (i64)pxl8_unpack_u64_le(buf, offset);
|
||||
}
|
||||
|
||||
i64 pxl8_unpack_i64_be(const u8* buf, size_t offset) {
|
||||
return (i64)pxl8_unpack_u64_be(buf, offset);
|
||||
}
|
||||
|
||||
f32 pxl8_unpack_f32_le(const u8* buf, size_t offset) {
|
||||
u32 bits = pxl8_unpack_u32_le(buf, offset);
|
||||
f32 result;
|
||||
memcpy(&result, &bits, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
f32 pxl8_unpack_f32_be(const u8* buf, size_t offset) {
|
||||
u32 bits = pxl8_unpack_u32_be(buf, offset);
|
||||
f32 result;
|
||||
memcpy(&result, &bits, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
f64 pxl8_unpack_f64_le(const u8* buf, size_t offset) {
|
||||
u64 bits = pxl8_unpack_u64_le(buf, offset);
|
||||
f64 result;
|
||||
memcpy(&result, &bits, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
f64 pxl8_unpack_f64_be(const u8* buf, size_t offset) {
|
||||
u64 bits = pxl8_unpack_u64_be(buf, offset);
|
||||
f64 result;
|
||||
memcpy(&result, &bits, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
void pxl8_bit_set(u32* val, u8 bit) {
|
||||
*val |= (1u << bit);
|
||||
}
|
||||
|
||||
void pxl8_bit_clear(u32* val, u8 bit) {
|
||||
*val &= ~(1u << bit);
|
||||
}
|
||||
|
||||
bool pxl8_bit_test(u32 val, u8 bit) {
|
||||
return (val & (1u << bit)) != 0;
|
||||
}
|
||||
|
||||
u32 pxl8_bit_count(u32 val) {
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
return (u32)__builtin_popcount(val);
|
||||
#else
|
||||
val = val - ((val >> 1) & 0x55555555);
|
||||
val = (val & 0x33333333) + ((val >> 2) & 0x33333333);
|
||||
return (((val + (val >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
|
||||
#endif
|
||||
}
|
||||
|
||||
void pxl8_bit_toggle(u32* val, u8 bit) {
|
||||
*val ^= (1u << bit);
|
||||
}
|
||||
251
src/core/pxl8_bytes.h
Normal file
251
src/core/pxl8_bytes.h
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
#pragma once
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
void pxl8_bit_clear(u32* val, u8 bit);
|
||||
u32 pxl8_bit_count(u32 val);
|
||||
void pxl8_bit_set(u32* val, u8 bit);
|
||||
bool pxl8_bit_test(u32 val, u8 bit);
|
||||
void pxl8_bit_toggle(u32* val, u8 bit);
|
||||
|
||||
void pxl8_pack_u8(u8* buf, size_t offset, u8 val);
|
||||
void pxl8_pack_u16_be(u8* buf, size_t offset, u16 val);
|
||||
void pxl8_pack_u16_le(u8* buf, size_t offset, u16 val);
|
||||
void pxl8_pack_u32_be(u8* buf, size_t offset, u32 val);
|
||||
void pxl8_pack_u32_le(u8* buf, size_t offset, u32 val);
|
||||
void pxl8_pack_u64_be(u8* buf, size_t offset, u64 val);
|
||||
void pxl8_pack_u64_le(u8* buf, size_t offset, u64 val);
|
||||
void pxl8_pack_i8(u8* buf, size_t offset, i8 val);
|
||||
void pxl8_pack_i16_be(u8* buf, size_t offset, i16 val);
|
||||
void pxl8_pack_i16_le(u8* buf, size_t offset, i16 val);
|
||||
void pxl8_pack_i32_be(u8* buf, size_t offset, i32 val);
|
||||
void pxl8_pack_i32_le(u8* buf, size_t offset, i32 val);
|
||||
void pxl8_pack_i64_be(u8* buf, size_t offset, i64 val);
|
||||
void pxl8_pack_i64_le(u8* buf, size_t offset, i64 val);
|
||||
void pxl8_pack_f32_be(u8* buf, size_t offset, f32 val);
|
||||
void pxl8_pack_f32_le(u8* buf, size_t offset, f32 val);
|
||||
void pxl8_pack_f64_be(u8* buf, size_t offset, f64 val);
|
||||
void pxl8_pack_f64_le(u8* buf, size_t offset, f64 val);
|
||||
|
||||
u8 pxl8_unpack_u8(const u8* buf, size_t offset);
|
||||
u16 pxl8_unpack_u16_be(const u8* buf, size_t offset);
|
||||
u16 pxl8_unpack_u16_le(const u8* buf, size_t offset);
|
||||
u32 pxl8_unpack_u32_be(const u8* buf, size_t offset);
|
||||
u32 pxl8_unpack_u32_le(const u8* buf, size_t offset);
|
||||
u64 pxl8_unpack_u64_be(const u8* buf, size_t offset);
|
||||
u64 pxl8_unpack_u64_le(const u8* buf, size_t offset);
|
||||
i8 pxl8_unpack_i8(const u8* buf, size_t offset);
|
||||
i16 pxl8_unpack_i16_be(const u8* buf, size_t offset);
|
||||
i16 pxl8_unpack_i16_le(const u8* buf, size_t offset);
|
||||
i32 pxl8_unpack_i32_be(const u8* buf, size_t offset);
|
||||
i32 pxl8_unpack_i32_le(const u8* buf, size_t offset);
|
||||
i64 pxl8_unpack_i64_be(const u8* buf, size_t offset);
|
||||
i64 pxl8_unpack_i64_le(const u8* buf, size_t offset);
|
||||
f32 pxl8_unpack_f32_be(const u8* buf, size_t offset);
|
||||
f32 pxl8_unpack_f32_le(const u8* buf, size_t offset);
|
||||
f64 pxl8_unpack_f64_be(const u8* buf, size_t offset);
|
||||
f64 pxl8_unpack_f64_le(const u8* buf, size_t offset);
|
||||
|
||||
typedef struct {
|
||||
const u8* bytes;
|
||||
u32 offset;
|
||||
u32 size;
|
||||
bool overflow;
|
||||
} pxl8_stream;
|
||||
|
||||
typedef struct {
|
||||
u8* bytes;
|
||||
u32 capacity;
|
||||
u32 offset;
|
||||
bool overflow;
|
||||
} pxl8_write_stream;
|
||||
|
||||
static inline pxl8_stream pxl8_stream_create(const u8* bytes, u32 size) {
|
||||
return (pxl8_stream){
|
||||
.bytes = bytes,
|
||||
.offset = 0,
|
||||
.size = size,
|
||||
.overflow = false
|
||||
};
|
||||
}
|
||||
|
||||
static inline pxl8_write_stream pxl8_write_stream_create(u8* bytes, u32 capacity) {
|
||||
return (pxl8_write_stream){
|
||||
.bytes = bytes,
|
||||
.capacity = capacity,
|
||||
.offset = 0,
|
||||
.overflow = false
|
||||
};
|
||||
}
|
||||
|
||||
static inline bool pxl8_stream_can_read(const pxl8_stream* s, u32 count) {
|
||||
return !s->overflow && s->offset + count <= s->size;
|
||||
}
|
||||
|
||||
static inline bool pxl8_stream_has_overflow(const pxl8_stream* s) {
|
||||
return s->overflow;
|
||||
}
|
||||
|
||||
static inline bool pxl8_write_stream_has_overflow(const pxl8_write_stream* s) {
|
||||
return s->overflow;
|
||||
}
|
||||
|
||||
static inline u32 pxl8_stream_position(const pxl8_stream* s) {
|
||||
return s->offset;
|
||||
}
|
||||
|
||||
static inline u32 pxl8_write_stream_position(const pxl8_write_stream* s) {
|
||||
return s->offset;
|
||||
}
|
||||
|
||||
static inline void pxl8_stream_seek(pxl8_stream* s, u32 offset) {
|
||||
s->offset = offset;
|
||||
}
|
||||
|
||||
static inline u8 pxl8_read_u8(pxl8_stream* s) {
|
||||
if (s->offset + 1 > s->size) { s->overflow = true; return 0; }
|
||||
return pxl8_unpack_u8(s->bytes, s->offset++);
|
||||
}
|
||||
|
||||
static inline u16 pxl8_read_u16(pxl8_stream* s) {
|
||||
if (s->offset + 2 > s->size) { s->overflow = true; return 0; }
|
||||
u16 val = pxl8_unpack_u16_le(s->bytes, s->offset);
|
||||
s->offset += 2;
|
||||
return val;
|
||||
}
|
||||
|
||||
static inline u16 pxl8_read_u16_be(pxl8_stream* s) {
|
||||
if (s->offset + 2 > s->size) { s->overflow = true; return 0; }
|
||||
u16 val = pxl8_unpack_u16_be(s->bytes, s->offset);
|
||||
s->offset += 2;
|
||||
return val;
|
||||
}
|
||||
|
||||
static inline u32 pxl8_read_u32(pxl8_stream* s) {
|
||||
if (s->offset + 4 > s->size) { s->overflow = true; return 0; }
|
||||
u32 val = pxl8_unpack_u32_le(s->bytes, s->offset);
|
||||
s->offset += 4;
|
||||
return val;
|
||||
}
|
||||
|
||||
static inline u32 pxl8_read_u32_be(pxl8_stream* s) {
|
||||
if (s->offset + 4 > s->size) { s->overflow = true; return 0; }
|
||||
u32 val = pxl8_unpack_u32_be(s->bytes, s->offset);
|
||||
s->offset += 4;
|
||||
return val;
|
||||
}
|
||||
|
||||
static inline u64 pxl8_read_u64(pxl8_stream* s) {
|
||||
if (s->offset + 8 > s->size) { s->overflow = true; return 0; }
|
||||
u64 val = pxl8_unpack_u64_le(s->bytes, s->offset);
|
||||
s->offset += 8;
|
||||
return val;
|
||||
}
|
||||
|
||||
static inline u64 pxl8_read_u64_be(pxl8_stream* s) {
|
||||
if (s->offset + 8 > s->size) { s->overflow = true; return 0; }
|
||||
u64 val = pxl8_unpack_u64_be(s->bytes, s->offset);
|
||||
s->offset += 8;
|
||||
return val;
|
||||
}
|
||||
|
||||
static inline i16 pxl8_read_i16(pxl8_stream* s) {
|
||||
return (i16)pxl8_read_u16(s);
|
||||
}
|
||||
|
||||
static inline i32 pxl8_read_i32(pxl8_stream* s) {
|
||||
return (i32)pxl8_read_u32(s);
|
||||
}
|
||||
|
||||
static inline f32 pxl8_read_f32(pxl8_stream* s) {
|
||||
if (s->offset + 4 > s->size) { s->overflow = true; return 0; }
|
||||
f32 val = pxl8_unpack_f32_le(s->bytes, s->offset);
|
||||
s->offset += 4;
|
||||
return val;
|
||||
}
|
||||
|
||||
static inline f32 pxl8_read_f32_be(pxl8_stream* s) {
|
||||
if (s->offset + 4 > s->size) { s->overflow = true; return 0; }
|
||||
f32 val = pxl8_unpack_f32_be(s->bytes, s->offset);
|
||||
s->offset += 4;
|
||||
return val;
|
||||
}
|
||||
|
||||
static inline void pxl8_read_bytes(pxl8_stream* s, void* dest, u32 count) {
|
||||
if (s->offset + count > s->size) { s->overflow = true; return; }
|
||||
memcpy(dest, &s->bytes[s->offset], count);
|
||||
s->offset += count;
|
||||
}
|
||||
|
||||
static inline void pxl8_skip_bytes(pxl8_stream* s, u32 count) {
|
||||
if (s->offset + count > s->size) { s->overflow = true; return; }
|
||||
s->offset += count;
|
||||
}
|
||||
|
||||
static inline const u8* pxl8_read_ptr(pxl8_stream* s, u32 count) {
|
||||
if (s->offset + count > s->size) { s->overflow = true; return NULL; }
|
||||
const u8* ptr = &s->bytes[s->offset];
|
||||
s->offset += count;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static inline void pxl8_write_u8(pxl8_write_stream* s, u8 val) {
|
||||
if (s->offset + 1 > s->capacity) { s->overflow = true; return; }
|
||||
pxl8_pack_u8(s->bytes, s->offset++, val);
|
||||
}
|
||||
|
||||
static inline void pxl8_write_u16(pxl8_write_stream* s, u16 val) {
|
||||
if (s->offset + 2 > s->capacity) { s->overflow = true; return; }
|
||||
pxl8_pack_u16_le(s->bytes, s->offset, val);
|
||||
s->offset += 2;
|
||||
}
|
||||
|
||||
static inline void pxl8_write_u16_be(pxl8_write_stream* s, u16 val) {
|
||||
if (s->offset + 2 > s->capacity) { s->overflow = true; return; }
|
||||
pxl8_pack_u16_be(s->bytes, s->offset, val);
|
||||
s->offset += 2;
|
||||
}
|
||||
|
||||
static inline void pxl8_write_u32(pxl8_write_stream* s, u32 val) {
|
||||
if (s->offset + 4 > s->capacity) { s->overflow = true; return; }
|
||||
pxl8_pack_u32_le(s->bytes, s->offset, val);
|
||||
s->offset += 4;
|
||||
}
|
||||
|
||||
static inline void pxl8_write_u32_be(pxl8_write_stream* s, u32 val) {
|
||||
if (s->offset + 4 > s->capacity) { s->overflow = true; return; }
|
||||
pxl8_pack_u32_be(s->bytes, s->offset, val);
|
||||
s->offset += 4;
|
||||
}
|
||||
|
||||
static inline void pxl8_write_u64(pxl8_write_stream* s, u64 val) {
|
||||
if (s->offset + 8 > s->capacity) { s->overflow = true; return; }
|
||||
pxl8_pack_u64_le(s->bytes, s->offset, val);
|
||||
s->offset += 8;
|
||||
}
|
||||
|
||||
static inline void pxl8_write_u64_be(pxl8_write_stream* s, u64 val) {
|
||||
if (s->offset + 8 > s->capacity) { s->overflow = true; return; }
|
||||
pxl8_pack_u64_be(s->bytes, s->offset, val);
|
||||
s->offset += 8;
|
||||
}
|
||||
|
||||
static inline void pxl8_write_f32(pxl8_write_stream* s, f32 val) {
|
||||
if (s->offset + 4 > s->capacity) { s->overflow = true; return; }
|
||||
pxl8_pack_f32_le(s->bytes, s->offset, val);
|
||||
s->offset += 4;
|
||||
}
|
||||
|
||||
static inline void pxl8_write_f32_be(pxl8_write_stream* s, f32 val) {
|
||||
if (s->offset + 4 > s->capacity) { s->overflow = true; return; }
|
||||
pxl8_pack_f32_be(s->bytes, s->offset, val);
|
||||
s->offset += 4;
|
||||
}
|
||||
|
||||
static inline void pxl8_write_bytes(pxl8_write_stream* s, const void* src, u32 count) {
|
||||
if (s->offset + count > s->capacity) { s->overflow = true; return; }
|
||||
memcpy(&s->bytes[s->offset], src, count);
|
||||
s->offset += count;
|
||||
}
|
||||
221
src/core/pxl8_io.c
Normal file
221
src/core/pxl8_io.c
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
#include "pxl8_io.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_cart.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
static inline char pxl8_to_lower(char c) {
|
||||
return (c >= 'A' && c <= 'Z') ? c + 32 : c;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size) {
|
||||
if (!path || !content || !size) return PXL8_ERROR_NULL_POINTER;
|
||||
|
||||
pxl8_cart* cart = pxl8_get_cart();
|
||||
if (cart && pxl8_cart_is_packed(cart)) {
|
||||
u8* data = NULL;
|
||||
u32 cart_size = 0;
|
||||
pxl8_result result = pxl8_cart_read_file(cart, path, &data, &cart_size);
|
||||
if (result == PXL8_OK) {
|
||||
*content = realloc(data, cart_size + 1);
|
||||
if (!*content) {
|
||||
pxl8_cart_free_file(data);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
(*content)[cart_size] = '\0';
|
||||
*size = cart_size;
|
||||
return PXL8_OK;
|
||||
}
|
||||
}
|
||||
|
||||
FILE* file = fopen(path, "rb");
|
||||
if (!file) {
|
||||
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
long file_size = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
|
||||
if (file_size < 0) {
|
||||
fclose(file);
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
*content = malloc(file_size + 1);
|
||||
if (!*content) {
|
||||
fclose(file);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
size_t bytes_read = fread(*content, 1, file_size, file);
|
||||
(*content)[bytes_read] = '\0';
|
||||
*size = bytes_read;
|
||||
|
||||
fclose(file);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_io_write_file(const char* path, const char* content, size_t size) {
|
||||
if (!path || !content) return PXL8_ERROR_NULL_POINTER;
|
||||
|
||||
FILE* file = fopen(path, "wb");
|
||||
if (!file) {
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
size_t bytes_written = fwrite(content, 1, size, file);
|
||||
fclose(file);
|
||||
|
||||
return (bytes_written == size) ? PXL8_OK : PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, size_t* size) {
|
||||
return pxl8_io_read_file(path, (char**)data, size);
|
||||
}
|
||||
|
||||
pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, size_t size) {
|
||||
return pxl8_io_write_file(path, (const char*)data, size);
|
||||
}
|
||||
|
||||
bool pxl8_io_file_exists(const char* path) {
|
||||
if (!path) return false;
|
||||
|
||||
struct stat st;
|
||||
return stat(path, &st) == 0;
|
||||
}
|
||||
|
||||
f64 pxl8_io_get_file_modified_time(const char* path) {
|
||||
if (!path) return 0.0;
|
||||
|
||||
struct stat st;
|
||||
if (stat(path, &st) == 0) {
|
||||
return st.st_mtime;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_io_create_directory(const char* path) {
|
||||
if (!path) return PXL8_ERROR_NULL_POINTER;
|
||||
|
||||
#ifdef _WIN32
|
||||
if (mkdir(path) != 0) {
|
||||
#else
|
||||
if (mkdir(path, 0755) != 0) {
|
||||
#endif
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
void pxl8_io_free_file_content(char* content) {
|
||||
if (content) {
|
||||
free(content);
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_io_free_binary_data(u8* data) {
|
||||
if (data) {
|
||||
free(data);
|
||||
}
|
||||
}
|
||||
|
||||
static i32 pxl8_key_code(const char* key_name) {
|
||||
if (!key_name || !key_name[0]) return 0;
|
||||
|
||||
typedef struct { const char* name; i32 code; } KeyMapping;
|
||||
static const KeyMapping keys[] = {
|
||||
{"a", 4}, {"b", 5}, {"c", 6}, {"d", 7}, {"e", 8}, {"f", 9}, {"g", 10}, {"h", 11},
|
||||
{"i", 12}, {"j", 13}, {"k", 14}, {"l", 15}, {"m", 16}, {"n", 17}, {"o", 18}, {"p", 19},
|
||||
{"q", 20}, {"r", 21}, {"s", 22}, {"t", 23}, {"u", 24}, {"v", 25}, {"w", 26}, {"x", 27},
|
||||
{"y", 28}, {"z", 29},
|
||||
{"1", 30}, {"2", 31}, {"3", 32}, {"4", 33}, {"5", 34},
|
||||
{"6", 35}, {"7", 36}, {"8", 37}, {"9", 38}, {"0", 39},
|
||||
{"return", 40}, {"escape", 41}, {"backspace", 42}, {"tab", 43}, {"space", 44},
|
||||
{"-", 45}, {"=", 46}, {"`", 53},
|
||||
{"left", 80}, {"right", 79}, {"up", 82}, {"down", 81},
|
||||
{"f1", 58}, {"f2", 59}, {"f3", 60}, {"f4", 61}, {"f5", 62}, {"f6", 63},
|
||||
{"f7", 64}, {"f8", 65}, {"f9", 66}, {"f10", 67}, {"f11", 68}, {"f12", 69},
|
||||
{"capslock", 57}, {"lshift", 225}, {"rshift", 229},
|
||||
{"lctrl", 224}, {"rctrl", 228}, {"lalt", 226}, {"ralt", 230},
|
||||
{NULL, 0}
|
||||
};
|
||||
|
||||
char lower_name[64];
|
||||
size_t i;
|
||||
for (i = 0; i < sizeof(lower_name) - 1 && key_name[i]; i++) {
|
||||
lower_name[i] = pxl8_to_lower(key_name[i]);
|
||||
}
|
||||
lower_name[i] = '\0';
|
||||
|
||||
for (i = 0; keys[i].name; i++) {
|
||||
if (strcmp(lower_name, keys[i].name) == 0) {
|
||||
return keys[i].code;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool pxl8_key_down(const pxl8_input_state* input, const char* key_name) {
|
||||
if (!input) return false;
|
||||
i32 key = pxl8_key_code(key_name);
|
||||
if (key < 0 || key >= PXL8_MAX_KEYS) return false;
|
||||
return input->keys_down[key];
|
||||
}
|
||||
|
||||
bool pxl8_key_pressed(const pxl8_input_state* input, const char* key_name) {
|
||||
if (!input) return false;
|
||||
i32 key = pxl8_key_code(key_name);
|
||||
if (key < 0 || key >= PXL8_MAX_KEYS) return false;
|
||||
return input->keys_pressed[key];
|
||||
}
|
||||
|
||||
bool pxl8_key_released(const pxl8_input_state* input, const char* key_name) {
|
||||
if (!input) return false;
|
||||
i32 key = pxl8_key_code(key_name);
|
||||
if (key < 0 || key >= PXL8_MAX_KEYS) return false;
|
||||
return input->keys_released[key];
|
||||
}
|
||||
|
||||
i32 pxl8_mouse_wheel_x(const pxl8_input_state* input) {
|
||||
if (!input) return 0;
|
||||
return input->mouse_wheel_x;
|
||||
}
|
||||
|
||||
i32 pxl8_mouse_wheel_y(const pxl8_input_state* input) {
|
||||
if (!input) return 0;
|
||||
return input->mouse_wheel_y;
|
||||
}
|
||||
|
||||
bool pxl8_mouse_pressed(const pxl8_input_state* input, i32 button) {
|
||||
if (!input || button < 1 || button > 3) return false;
|
||||
return input->mouse_buttons_pressed[button - 1];
|
||||
}
|
||||
|
||||
bool pxl8_mouse_released(const pxl8_input_state* input, i32 button) {
|
||||
if (!input || button < 1 || button > 3) return false;
|
||||
return input->mouse_buttons_released[button - 1];
|
||||
}
|
||||
|
||||
i32 pxl8_mouse_x(const pxl8_input_state* input) {
|
||||
if (!input) return 0;
|
||||
return input->mouse_x;
|
||||
}
|
||||
|
||||
i32 pxl8_mouse_y(const pxl8_input_state* input) {
|
||||
if (!input) return 0;
|
||||
return input->mouse_y;
|
||||
}
|
||||
|
||||
i32 pxl8_mouse_dx(const pxl8_input_state* input) {
|
||||
if (!input) return 0;
|
||||
return input->mouse_dx;
|
||||
}
|
||||
|
||||
i32 pxl8_mouse_dy(const pxl8_input_state* input) {
|
||||
if (!input) return 0;
|
||||
return input->mouse_dy;
|
||||
}
|
||||
38
src/core/pxl8_io.h
Normal file
38
src/core/pxl8_io.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "pxl8_bytes.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_result pxl8_io_create_directory(const char* path);
|
||||
bool pxl8_io_file_exists(const char* path);
|
||||
void pxl8_io_free_binary_data(u8* data);
|
||||
void pxl8_io_free_file_content(char* content);
|
||||
f64 pxl8_io_get_file_modified_time(const char* path);
|
||||
pxl8_result pxl8_io_read_binary_file(const char* path, u8** data, size_t* size);
|
||||
pxl8_result pxl8_io_read_file(const char* path, char** content, size_t* size);
|
||||
pxl8_result pxl8_io_write_binary_file(const char* path, const u8* data, size_t size);
|
||||
pxl8_result pxl8_io_write_file(const char* path, const char* content, size_t size);
|
||||
|
||||
bool pxl8_key_down(const pxl8_input_state* input, const char* key_name);
|
||||
bool pxl8_key_pressed(const pxl8_input_state* input, const char* key_name);
|
||||
bool pxl8_key_released(const pxl8_input_state* input, const char* key_name);
|
||||
|
||||
i32 pxl8_mouse_dx(const pxl8_input_state* input);
|
||||
i32 pxl8_mouse_dy(const pxl8_input_state* input);
|
||||
bool pxl8_mouse_pressed(const pxl8_input_state* input, i32 button);
|
||||
bool pxl8_mouse_released(const pxl8_input_state* input, i32 button);
|
||||
i32 pxl8_mouse_wheel_x(const pxl8_input_state* input);
|
||||
i32 pxl8_mouse_wheel_y(const pxl8_input_state* input);
|
||||
i32 pxl8_mouse_x(const pxl8_input_state* input);
|
||||
i32 pxl8_mouse_y(const pxl8_input_state* input);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
113
src/core/pxl8_log.c
Normal file
113
src/core/pxl8_log.c
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
#include "pxl8_log.h"
|
||||
#include "pxl8_repl.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#define PXL8_LOG_COLOR_ERROR "\033[38;2;251;73;52m"
|
||||
#define PXL8_LOG_COLOR_WARN "\033[38;2;250;189;47m"
|
||||
#define PXL8_LOG_COLOR_INFO "\033[38;2;184;187;38m"
|
||||
#define PXL8_LOG_COLOR_DEBUG "\033[38;2;131;165;152m"
|
||||
#define PXL8_LOG_COLOR_TRACE "\033[38;2;211;134;155m"
|
||||
#define PXL8_LOG_COLOR_RESET "\033[0m"
|
||||
|
||||
static pxl8_log* g_log = NULL;
|
||||
|
||||
void pxl8_log_init(pxl8_log* log) {
|
||||
g_log = log;
|
||||
g_log->level = PXL8_LOG_LEVEL_DEBUG;
|
||||
|
||||
const char* env_level = getenv("PXL8_LOG_LEVEL");
|
||||
if (env_level) {
|
||||
if (strcmp(env_level, "trace") == 0) g_log->level = PXL8_LOG_LEVEL_TRACE;
|
||||
else if (strcmp(env_level, "debug") == 0) g_log->level = PXL8_LOG_LEVEL_DEBUG;
|
||||
else if (strcmp(env_level, "info") == 0) g_log->level = PXL8_LOG_LEVEL_INFO;
|
||||
else if (strcmp(env_level, "warn") == 0) g_log->level = PXL8_LOG_LEVEL_WARN;
|
||||
else if (strcmp(env_level, "error") == 0) g_log->level = PXL8_LOG_LEVEL_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_log_set_level(pxl8_log_level level) {
|
||||
if (g_log) g_log->level = level;
|
||||
}
|
||||
|
||||
static void log_timestamp(char* buffer, size_t size) {
|
||||
time_t now = time(NULL);
|
||||
struct tm* tm_info = localtime(&now);
|
||||
strftime(buffer, size, "%H:%M:%S", tm_info);
|
||||
}
|
||||
|
||||
static void log_output(const char* color, const char* level,
|
||||
const char* file, int line, const char* fmt, va_list args) {
|
||||
char buffer[4096];
|
||||
char timestamp[16];
|
||||
log_timestamp(timestamp, sizeof(timestamp));
|
||||
|
||||
int pos = 0;
|
||||
if (file) {
|
||||
pos = snprintf(buffer, sizeof(buffer), "%s[%s %s]" PXL8_LOG_COLOR_RESET " %s:%d: ",
|
||||
color, timestamp, level, file, line);
|
||||
} else {
|
||||
pos = snprintf(buffer, sizeof(buffer), "%s[%s %s]" PXL8_LOG_COLOR_RESET " ",
|
||||
color, timestamp, level);
|
||||
}
|
||||
|
||||
if (pos > 0 && pos < (int)sizeof(buffer)) {
|
||||
vsnprintf(buffer + pos, sizeof(buffer) - pos, fmt, args);
|
||||
}
|
||||
|
||||
strncat(buffer, "\n", sizeof(buffer) - strlen(buffer) - 1);
|
||||
|
||||
if (!pxl8_repl_push_log(buffer)) {
|
||||
printf("%s", buffer);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_log_write_trace(const char* file, int line, const char* fmt, ...) {
|
||||
if (!g_log || g_log->level > PXL8_LOG_LEVEL_TRACE) return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
log_output(PXL8_LOG_COLOR_TRACE, "TRACE", file, line, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void pxl8_log_write_debug(const char* file, int line, const char* fmt, ...) {
|
||||
if (!g_log || g_log->level > PXL8_LOG_LEVEL_DEBUG) return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
log_output(PXL8_LOG_COLOR_DEBUG, "DEBUG", file, line, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void pxl8_log_write_info(const char* fmt, ...) {
|
||||
if (!g_log || g_log->level > PXL8_LOG_LEVEL_INFO) return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
log_output(PXL8_LOG_COLOR_INFO, "INFO", NULL, 0, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void pxl8_log_write_warn(const char* file, int line, const char* fmt, ...) {
|
||||
if (!g_log || g_log->level > PXL8_LOG_LEVEL_WARN) return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
log_output(PXL8_LOG_COLOR_WARN, "WARN", file, line, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void pxl8_log_write_error(const char* file, int line, const char* fmt, ...) {
|
||||
if (!g_log || g_log->level > PXL8_LOG_LEVEL_ERROR) return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
log_output(PXL8_LOG_COLOR_ERROR, "ERROR", file, line, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
31
src/core/pxl8_log.h
Normal file
31
src/core/pxl8_log.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef enum {
|
||||
PXL8_LOG_LEVEL_TRACE = 0,
|
||||
PXL8_LOG_LEVEL_DEBUG = 1,
|
||||
PXL8_LOG_LEVEL_INFO = 2,
|
||||
PXL8_LOG_LEVEL_WARN = 3,
|
||||
PXL8_LOG_LEVEL_ERROR = 4,
|
||||
} pxl8_log_level;
|
||||
|
||||
typedef struct pxl8_log {
|
||||
pxl8_log_level level;
|
||||
} pxl8_log;
|
||||
|
||||
void pxl8_log_init(pxl8_log* log);
|
||||
void pxl8_log_set_level(pxl8_log_level level);
|
||||
|
||||
void pxl8_log_write_trace(const char* file, int line, const char* fmt, ...);
|
||||
void pxl8_log_write_debug(const char* file, int line, const char* fmt, ...);
|
||||
void pxl8_log_write_info(const char* fmt, ...);
|
||||
void pxl8_log_write_warn(const char* file, int line, const char* fmt, ...);
|
||||
void pxl8_log_write_error(const char* file, int line, const char* fmt, ...);
|
||||
|
||||
#define pxl8_trace(...) pxl8_log_write_trace(__FILE__, __LINE__, __VA_ARGS__)
|
||||
#define pxl8_debug(...) pxl8_log_write_debug(__FILE__, __LINE__, __VA_ARGS__)
|
||||
#define pxl8_info(...) pxl8_log_write_info(__VA_ARGS__)
|
||||
#define pxl8_warn(...) pxl8_log_write_warn(__FILE__, __LINE__, __VA_ARGS__)
|
||||
#define pxl8_error(...) pxl8_log_write_error(__FILE__, __LINE__, __VA_ARGS__)
|
||||
#define pxl8_script_error(...) pxl8_log_write_error(NULL, 0, __VA_ARGS__)
|
||||
18
src/core/pxl8_macros.h
Normal file
18
src/core/pxl8_macros.h
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#ifndef pxl8_min
|
||||
#define pxl8_min(a, b) ((a) < (b) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
#ifndef pxl8_max
|
||||
#define pxl8_max(a, b) ((a) > (b) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
#ifndef pxl8_strncpy
|
||||
#define pxl8_strncpy(dst, src, size) do { \
|
||||
strncpy((dst), (src), (size) - 1); \
|
||||
(dst)[(size) - 1] = '\0'; \
|
||||
} while (0)
|
||||
#endif
|
||||
24
src/core/pxl8_rng.c
Normal file
24
src/core/pxl8_rng.c
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#include "pxl8_rng.h"
|
||||
|
||||
void pxl8_rng_seed(pxl8_rng* rng, u32 seed) {
|
||||
if (!rng) return;
|
||||
rng->state = seed ? seed : 1;
|
||||
}
|
||||
|
||||
u32 pxl8_rng_next(pxl8_rng* rng) {
|
||||
if (!rng) return 0;
|
||||
rng->state ^= rng->state << 13;
|
||||
rng->state ^= rng->state >> 17;
|
||||
rng->state ^= rng->state << 5;
|
||||
return rng->state;
|
||||
}
|
||||
|
||||
f32 pxl8_rng_f32(pxl8_rng* rng) {
|
||||
return (f32)pxl8_rng_next(rng) / (f32)0xFFFFFFFF;
|
||||
}
|
||||
|
||||
i32 pxl8_rng_range(pxl8_rng* rng, i32 min, i32 max) {
|
||||
if (min >= max) return min;
|
||||
u32 range = (u32)(max - min);
|
||||
return min + (i32)(pxl8_rng_next(rng) % range);
|
||||
}
|
||||
20
src/core/pxl8_rng.h
Normal file
20
src/core/pxl8_rng.h
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_rng {
|
||||
u32 state;
|
||||
} pxl8_rng;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void pxl8_rng_seed(pxl8_rng* rng, u32 seed);
|
||||
u32 pxl8_rng_next(pxl8_rng* rng);
|
||||
f32 pxl8_rng_f32(pxl8_rng* rng);
|
||||
i32 pxl8_rng_range(pxl8_rng* rng, i32 min, i32 max);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
37
src/core/pxl8_sys.h
Normal file
37
src/core/pxl8_sys.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_hal.h"
|
||||
#include "pxl8_io.h"
|
||||
#include "pxl8_sfx.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8 pxl8;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8* pxl8_create(const pxl8_hal* hal);
|
||||
void pxl8_destroy(pxl8* sys);
|
||||
|
||||
pxl8_result pxl8_init(pxl8* sys, i32 argc, char* argv[]);
|
||||
pxl8_result pxl8_update(pxl8* sys);
|
||||
pxl8_result pxl8_frame(pxl8* sys);
|
||||
void pxl8_quit(pxl8* sys);
|
||||
|
||||
f32 pxl8_get_fps(const pxl8* sys);
|
||||
pxl8_gfx* pxl8_get_gfx(const pxl8* sys);
|
||||
pxl8_input_state* pxl8_get_input(const pxl8* sys);
|
||||
pxl8_size pxl8_get_resolution_dimensions(pxl8_resolution resolution);
|
||||
pxl8_sfx_mixer* pxl8_get_sfx_mixer(const pxl8* sys);
|
||||
bool pxl8_is_running(const pxl8* sys);
|
||||
|
||||
void pxl8_center_cursor(pxl8* sys);
|
||||
void pxl8_set_cursor(pxl8* sys, pxl8_cursor cursor);
|
||||
void pxl8_set_relative_mouse_mode(pxl8* sys, bool enabled);
|
||||
void pxl8_set_running(pxl8* sys, bool running);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
106
src/core/pxl8_types.h
Normal file
106
src/core/pxl8_types.h
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define PXL8_DEFAULT_ATLAS_ENTRY_CAPACITY 64
|
||||
#define PXL8_DEFAULT_ATLAS_SIZE 1024
|
||||
#define PXL8_DEFAULT_SPRITE_CACHE_CAPACITY 64
|
||||
#define PXL8_MAX_ERROR_SIZE 2048
|
||||
#define PXL8_MAX_KEYS 256
|
||||
#define PXL8_MAX_PALETTE_SIZE 256
|
||||
#define PXL8_MAX_PATH 256
|
||||
|
||||
typedef float f32;
|
||||
typedef double f64;
|
||||
typedef int8_t i8;
|
||||
typedef int16_t i16;
|
||||
typedef int32_t i32;
|
||||
typedef int64_t i64;
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
typedef uint64_t u64;
|
||||
|
||||
#if defined(__SIZEOF_INT128__)
|
||||
typedef __int128_t i128;
|
||||
typedef __uint128_t u128;
|
||||
#endif
|
||||
|
||||
typedef enum pxl8_pixel_mode {
|
||||
PXL8_PIXEL_INDEXED = 1,
|
||||
PXL8_PIXEL_HICOLOR = 2,
|
||||
PXL8_PIXEL_RGBA = 4,
|
||||
} pxl8_pixel_mode;
|
||||
|
||||
typedef enum pxl8_cursor {
|
||||
PXL8_CURSOR_ARROW,
|
||||
PXL8_CURSOR_HAND
|
||||
} pxl8_cursor;
|
||||
|
||||
typedef enum pxl8_resolution {
|
||||
PXL8_RESOLUTION_240x160,
|
||||
PXL8_RESOLUTION_320x180,
|
||||
PXL8_RESOLUTION_320x240,
|
||||
PXL8_RESOLUTION_640x360,
|
||||
PXL8_RESOLUTION_640x480,
|
||||
PXL8_RESOLUTION_800x600,
|
||||
PXL8_RESOLUTION_960x540
|
||||
} pxl8_resolution;
|
||||
|
||||
typedef enum pxl8_result {
|
||||
PXL8_OK = 0,
|
||||
PXL8_ERROR_ASE_INVALID_FRAME_MAGIC,
|
||||
PXL8_ERROR_ASE_INVALID_MAGIC,
|
||||
PXL8_ERROR_ASE_MALFORMED_CHUNK,
|
||||
PXL8_ERROR_ASE_TRUNCATED_FILE,
|
||||
PXL8_ERROR_FILE_NOT_FOUND,
|
||||
PXL8_ERROR_INITIALIZATION_FAILED,
|
||||
PXL8_ERROR_INVALID_ARGUMENT,
|
||||
PXL8_ERROR_INVALID_COORDINATE,
|
||||
PXL8_ERROR_INVALID_FORMAT,
|
||||
PXL8_ERROR_INVALID_SIZE,
|
||||
PXL8_ERROR_NOT_INITIALIZED,
|
||||
PXL8_ERROR_NULL_POINTER,
|
||||
PXL8_ERROR_OUT_OF_MEMORY,
|
||||
PXL8_ERROR_SCRIPT_ERROR,
|
||||
PXL8_ERROR_SYSTEM_FAILURE
|
||||
} pxl8_result;
|
||||
|
||||
typedef struct pxl8_bounds {
|
||||
i32 x, y;
|
||||
i32 w;
|
||||
i32 h;
|
||||
} pxl8_bounds;
|
||||
|
||||
typedef struct pxl8_input_state {
|
||||
bool keys_down[PXL8_MAX_KEYS];
|
||||
bool keys_pressed[PXL8_MAX_KEYS];
|
||||
bool keys_released[PXL8_MAX_KEYS];
|
||||
|
||||
bool mouse_buttons_down[3];
|
||||
bool mouse_buttons_pressed[3];
|
||||
bool mouse_buttons_released[3];
|
||||
i32 mouse_wheel_x;
|
||||
i32 mouse_wheel_y;
|
||||
i32 mouse_x;
|
||||
i32 mouse_y;
|
||||
i32 mouse_dx;
|
||||
i32 mouse_dy;
|
||||
bool mouse_relative_mode;
|
||||
} pxl8_input_state;
|
||||
|
||||
typedef struct pxl8_point {
|
||||
i32 x, y;
|
||||
} pxl8_point;
|
||||
|
||||
typedef struct pxl8_size {
|
||||
i32 w, h;
|
||||
} pxl8_size;
|
||||
|
||||
typedef struct pxl8_viewport {
|
||||
i32 offset_x, offset_y;
|
||||
i32 scaled_width, scaled_height;
|
||||
f32 scale;
|
||||
} pxl8_viewport;
|
||||
37
src/game/pxl8_game.h
Normal file
37
src/game/pxl8_game.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_rng.h"
|
||||
#include "pxl8_script.h"
|
||||
#include "pxl8_sfx.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_replay pxl8_replay;
|
||||
|
||||
typedef struct pxl8_game {
|
||||
pxl8_gfx* gfx;
|
||||
pxl8_script* script;
|
||||
pxl8_sfx_mixer* mixer;
|
||||
|
||||
pxl8_rng rng;
|
||||
i32 frame_count;
|
||||
u64 last_time;
|
||||
f32 time;
|
||||
|
||||
f32 fps_accumulator;
|
||||
i32 fps_frame_count;
|
||||
f32 fps;
|
||||
|
||||
pxl8_input_state input;
|
||||
pxl8_input_state prev_input;
|
||||
|
||||
#ifndef NDEBUG
|
||||
pxl8_replay* debug_replay;
|
||||
#endif
|
||||
|
||||
bool repl_mode;
|
||||
bool repl_started;
|
||||
bool running;
|
||||
bool script_loaded;
|
||||
char script_path[256];
|
||||
} pxl8_game;
|
||||
133
src/game/pxl8_gui.c
Normal file
133
src/game/pxl8_gui.c
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
#include "pxl8_gui.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_gfx.h"
|
||||
|
||||
pxl8_gui_state* pxl8_gui_state_create(void) {
|
||||
pxl8_gui_state* state = (pxl8_gui_state*)malloc(sizeof(pxl8_gui_state));
|
||||
if (!state) return NULL;
|
||||
|
||||
memset(state, 0, sizeof(pxl8_gui_state));
|
||||
return state;
|
||||
}
|
||||
|
||||
void pxl8_gui_state_destroy(pxl8_gui_state* state) {
|
||||
if (!state) return;
|
||||
free(state);
|
||||
}
|
||||
|
||||
void pxl8_gui_begin_frame(pxl8_gui_state* state) {
|
||||
if (!state) return;
|
||||
state->hot_id = 0;
|
||||
}
|
||||
|
||||
void pxl8_gui_end_frame(pxl8_gui_state* state) {
|
||||
if (!state) return;
|
||||
|
||||
if (!state->cursor_down) {
|
||||
state->active_id = 0;
|
||||
}
|
||||
state->cursor_clicked = false;
|
||||
}
|
||||
|
||||
void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y) {
|
||||
if (!state) return;
|
||||
state->cursor_x = x;
|
||||
state->cursor_y = y;
|
||||
}
|
||||
|
||||
void pxl8_gui_cursor_down(pxl8_gui_state* state) {
|
||||
if (!state) return;
|
||||
state->cursor_down = true;
|
||||
}
|
||||
|
||||
void pxl8_gui_cursor_up(pxl8_gui_state* state) {
|
||||
if (!state) return;
|
||||
state->cursor_down = false;
|
||||
state->cursor_clicked = true;
|
||||
}
|
||||
|
||||
static bool is_cursor_over(const pxl8_gui_state* state, i32 x, i32 y, i32 w, i32 h) {
|
||||
return state->cursor_x >= x && state->cursor_x < (x + w) &&
|
||||
state->cursor_y >= y && state->cursor_y < (y + h);
|
||||
}
|
||||
|
||||
bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, const char* label) {
|
||||
if (!state || !gfx || !label) return false;
|
||||
|
||||
bool cursor_over = is_cursor_over(state, x, y, w, h);
|
||||
bool is_hot = (state->hot_id == id);
|
||||
bool is_active = (state->active_id == id);
|
||||
|
||||
if (cursor_over) {
|
||||
state->hot_id = id;
|
||||
}
|
||||
|
||||
if (cursor_over && state->cursor_down && state->active_id == 0) {
|
||||
state->active_id = id;
|
||||
}
|
||||
|
||||
bool clicked = is_active && state->cursor_clicked && cursor_over;
|
||||
if (clicked) {
|
||||
state->active_id = 0;
|
||||
}
|
||||
|
||||
u8 bg_color;
|
||||
u8 border_color;
|
||||
i32 offset_x = 0;
|
||||
i32 offset_y = 0;
|
||||
|
||||
if (is_active) {
|
||||
bg_color = 4;
|
||||
border_color = 3;
|
||||
offset_x = 1;
|
||||
offset_y = 1;
|
||||
} else if (is_hot || cursor_over) {
|
||||
bg_color = 4;
|
||||
border_color = 8;
|
||||
} else {
|
||||
bg_color = 3;
|
||||
border_color = 4;
|
||||
}
|
||||
|
||||
pxl8_2d_rect_fill(gfx, x, y, w, h, bg_color);
|
||||
pxl8_2d_rect(gfx, x, y, w, h, border_color);
|
||||
|
||||
i32 text_len = (i32)strlen(label);
|
||||
i32 text_x = x + (w / 2) - ((text_len * 8) / 2) + offset_x;
|
||||
i32 text_y = y + (h / 2) - 5 + offset_y;
|
||||
pxl8_2d_text(gfx, label, text_x, text_y, 6);
|
||||
|
||||
return clicked;
|
||||
}
|
||||
|
||||
void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title) {
|
||||
if (!gfx || !title) return;
|
||||
|
||||
pxl8_2d_rect_fill(gfx, x, y, w, 28, 1);
|
||||
pxl8_2d_rect_fill(gfx, x, y + 28, w, h - 28, 2);
|
||||
pxl8_2d_rect(gfx, x, y, w, h, 4);
|
||||
pxl8_2d_rect_fill(gfx, x, y + 28, w, 1, 4);
|
||||
|
||||
i32 title_x = x + 10;
|
||||
i32 title_y = y + (28 / 2) - 5;
|
||||
pxl8_2d_text(gfx, title, title_x, title_y, 8);
|
||||
}
|
||||
|
||||
void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color) {
|
||||
if (!gfx || !text) return;
|
||||
pxl8_2d_text(gfx, text, x, y, color);
|
||||
}
|
||||
|
||||
bool pxl8_gui_is_hovering(const pxl8_gui_state* state) {
|
||||
if (!state) return false;
|
||||
return state->hot_id != 0;
|
||||
}
|
||||
|
||||
void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y) {
|
||||
if (!state) return;
|
||||
if (x) *x = state->cursor_x;
|
||||
if (y) *y = state->cursor_y;
|
||||
}
|
||||
38
src/game/pxl8_gui.h
Normal file
38
src/game/pxl8_gui.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct {
|
||||
i32 cursor_x;
|
||||
i32 cursor_y;
|
||||
bool cursor_down;
|
||||
bool cursor_clicked;
|
||||
u32 hot_id;
|
||||
u32 active_id;
|
||||
} pxl8_gui_state;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_gui_state* pxl8_gui_state_create(void);
|
||||
void pxl8_gui_state_destroy(pxl8_gui_state* state);
|
||||
|
||||
void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y);
|
||||
bool pxl8_gui_is_hovering(const pxl8_gui_state* state);
|
||||
|
||||
void pxl8_gui_begin_frame(pxl8_gui_state* state);
|
||||
void pxl8_gui_end_frame(pxl8_gui_state* state);
|
||||
|
||||
void pxl8_gui_cursor_down(pxl8_gui_state* state);
|
||||
void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y);
|
||||
void pxl8_gui_cursor_up(pxl8_gui_state* state);
|
||||
|
||||
bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, const char* label);
|
||||
void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color);
|
||||
void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
619
src/game/pxl8_replay.c
Normal file
619
src/game/pxl8_replay.c
Normal file
|
|
@ -0,0 +1,619 @@
|
|||
#include "pxl8_replay.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_sys.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct pxl8_replay_chunk {
|
||||
u8 type;
|
||||
u32 size;
|
||||
u8* data;
|
||||
struct pxl8_replay_chunk* next;
|
||||
} pxl8_replay_chunk;
|
||||
|
||||
typedef struct pxl8_keyframe_entry {
|
||||
pxl8_keyframe keyframe;
|
||||
pxl8_replay_chunk* input_deltas;
|
||||
struct pxl8_keyframe_entry* next;
|
||||
struct pxl8_keyframe_entry* prev;
|
||||
} pxl8_keyframe_entry;
|
||||
|
||||
struct pxl8_replay {
|
||||
FILE* file;
|
||||
pxl8_replay_header header;
|
||||
bool recording;
|
||||
bool playing;
|
||||
u32 current_frame;
|
||||
|
||||
pxl8_keyframe_entry* keyframes;
|
||||
pxl8_keyframe_entry* current_keyframe;
|
||||
u32 keyframe_count;
|
||||
u32 max_keyframes;
|
||||
|
||||
pxl8_replay_chunk* pending_inputs;
|
||||
pxl8_replay_chunk* pending_inputs_tail;
|
||||
|
||||
pxl8_replay_chunk* audio_events;
|
||||
pxl8_replay_chunk* audio_events_tail;
|
||||
};
|
||||
|
||||
static void pxl8_replay_chunk_free(pxl8_replay_chunk* chunk) {
|
||||
while (chunk) {
|
||||
pxl8_replay_chunk* next = chunk->next;
|
||||
free(chunk->data);
|
||||
free(chunk);
|
||||
chunk = next;
|
||||
}
|
||||
}
|
||||
|
||||
static void pxl8_replay_keyframe_entry_free(pxl8_keyframe_entry* entry) {
|
||||
while (entry) {
|
||||
pxl8_keyframe_entry* next = entry->next;
|
||||
pxl8_replay_chunk_free(entry->input_deltas);
|
||||
free(entry);
|
||||
entry = next;
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_replay* pxl8_replay_create(const char* path, u32 keyframe_interval) {
|
||||
FILE* f = fopen(path, "wb");
|
||||
if (!f) {
|
||||
pxl8_error("Failed to create replay file: %s", path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pxl8_replay* r = calloc(1, sizeof(pxl8_replay));
|
||||
if (!r) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
r->file = f;
|
||||
r->recording = true;
|
||||
r->playing = false;
|
||||
r->header.magic = PXL8_REPLAY_MAGIC;
|
||||
r->header.version = PXL8_REPLAY_VERSION;
|
||||
r->header.keyframe_interval = keyframe_interval;
|
||||
|
||||
fwrite(&r->header, sizeof(pxl8_replay_header), 1, f);
|
||||
fflush(f);
|
||||
|
||||
pxl8_info("Created replay file: %s (keyframe interval: %u)", path, keyframe_interval);
|
||||
return r;
|
||||
}
|
||||
|
||||
pxl8_replay* pxl8_replay_create_buffer(u32 keyframe_interval, u32 max_keyframes) {
|
||||
pxl8_replay* r = calloc(1, sizeof(pxl8_replay));
|
||||
if (!r) return NULL;
|
||||
|
||||
r->recording = true;
|
||||
r->playing = false;
|
||||
r->max_keyframes = max_keyframes;
|
||||
r->header.magic = PXL8_REPLAY_MAGIC;
|
||||
r->header.version = PXL8_REPLAY_VERSION;
|
||||
r->header.keyframe_interval = keyframe_interval;
|
||||
|
||||
pxl8_debug("Created replay buffer (keyframe interval: %u, max: %u)", keyframe_interval, max_keyframes);
|
||||
return r;
|
||||
}
|
||||
|
||||
pxl8_replay* pxl8_replay_open(const char* path) {
|
||||
FILE* f = fopen(path, "rb");
|
||||
if (!f) {
|
||||
pxl8_error("Failed to open replay file: %s", path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pxl8_replay_header header;
|
||||
if (fread(&header, sizeof(header), 1, f) != 1) {
|
||||
pxl8_error("Failed to read replay header");
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (header.magic != PXL8_REPLAY_MAGIC) {
|
||||
pxl8_error("Invalid replay magic: 0x%08X (expected 0x%08X)", header.magic, PXL8_REPLAY_MAGIC);
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (header.version > PXL8_REPLAY_VERSION) {
|
||||
pxl8_error("Unsupported replay version: %u (max supported: %u)", header.version, PXL8_REPLAY_VERSION);
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pxl8_replay* r = calloc(1, sizeof(pxl8_replay));
|
||||
if (!r) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
r->file = f;
|
||||
r->header = header;
|
||||
r->recording = false;
|
||||
r->playing = true;
|
||||
|
||||
while (!feof(f)) {
|
||||
u8 chunk_type;
|
||||
if (fread(&chunk_type, 1, 1, f) != 1) break;
|
||||
|
||||
if (chunk_type == PXL8_REPLAY_CHUNK_END) break;
|
||||
|
||||
u8 size_bytes[3];
|
||||
if (fread(size_bytes, 3, 1, f) != 1) break;
|
||||
u32 size = size_bytes[0] | (size_bytes[1] << 8) | (size_bytes[2] << 16);
|
||||
|
||||
u8* data = malloc(size);
|
||||
if (!data || fread(data, size, 1, f) != 1) {
|
||||
free(data);
|
||||
break;
|
||||
}
|
||||
|
||||
if (chunk_type == PXL8_REPLAY_CHUNK_KEYFRAME) {
|
||||
pxl8_keyframe_entry* entry = calloc(1, sizeof(pxl8_keyframe_entry));
|
||||
if (entry && size >= sizeof(pxl8_keyframe)) {
|
||||
memcpy(&entry->keyframe, data, sizeof(pxl8_keyframe));
|
||||
entry->prev = r->current_keyframe;
|
||||
if (r->current_keyframe) {
|
||||
r->current_keyframe->next = entry;
|
||||
}
|
||||
r->current_keyframe = entry;
|
||||
if (!r->keyframes) {
|
||||
r->keyframes = entry;
|
||||
}
|
||||
r->keyframe_count++;
|
||||
}
|
||||
free(data);
|
||||
} else if (chunk_type == PXL8_REPLAY_CHUNK_INPUT) {
|
||||
if (r->current_keyframe) {
|
||||
pxl8_replay_chunk* chunk = calloc(1, sizeof(pxl8_replay_chunk));
|
||||
if (chunk) {
|
||||
chunk->type = chunk_type;
|
||||
chunk->size = size;
|
||||
chunk->data = data;
|
||||
data = NULL;
|
||||
|
||||
if (!r->current_keyframe->input_deltas) {
|
||||
r->current_keyframe->input_deltas = chunk;
|
||||
} else {
|
||||
pxl8_replay_chunk* tail = r->current_keyframe->input_deltas;
|
||||
while (tail->next) tail = tail->next;
|
||||
tail->next = chunk;
|
||||
}
|
||||
}
|
||||
}
|
||||
free(data);
|
||||
} else if (chunk_type == PXL8_REPLAY_CHUNK_AUDIO_EVENT) {
|
||||
pxl8_replay_chunk* chunk = calloc(1, sizeof(pxl8_replay_chunk));
|
||||
if (chunk) {
|
||||
chunk->type = chunk_type;
|
||||
chunk->size = size;
|
||||
chunk->data = data;
|
||||
data = NULL;
|
||||
|
||||
if (!r->audio_events) {
|
||||
r->audio_events = chunk;
|
||||
r->audio_events_tail = chunk;
|
||||
} else {
|
||||
r->audio_events_tail->next = chunk;
|
||||
r->audio_events_tail = chunk;
|
||||
}
|
||||
}
|
||||
free(data);
|
||||
} else {
|
||||
free(data);
|
||||
}
|
||||
}
|
||||
|
||||
r->current_keyframe = r->keyframes;
|
||||
pxl8_info("Opened replay: %u frames, %u keyframes", r->header.total_frames, r->keyframe_count);
|
||||
return r;
|
||||
}
|
||||
|
||||
void pxl8_replay_destroy(pxl8_replay* r) {
|
||||
if (!r) return;
|
||||
|
||||
if (r->file) {
|
||||
if (r->recording) {
|
||||
u8 end_chunk = PXL8_REPLAY_CHUNK_END;
|
||||
fwrite(&end_chunk, 1, 1, r->file);
|
||||
|
||||
fseek(r->file, 0, SEEK_SET);
|
||||
fwrite(&r->header, sizeof(pxl8_replay_header), 1, r->file);
|
||||
}
|
||||
fclose(r->file);
|
||||
}
|
||||
|
||||
pxl8_replay_keyframe_entry_free(r->keyframes);
|
||||
pxl8_replay_chunk_free(r->pending_inputs);
|
||||
pxl8_replay_chunk_free(r->audio_events);
|
||||
|
||||
free(r);
|
||||
}
|
||||
|
||||
bool pxl8_replay_is_recording(pxl8_replay* r) {
|
||||
return r && r->recording;
|
||||
}
|
||||
|
||||
bool pxl8_replay_is_playing(pxl8_replay* r) {
|
||||
return r && r->playing;
|
||||
}
|
||||
|
||||
u32 pxl8_replay_get_frame(pxl8_replay* r) {
|
||||
return r ? r->current_frame : 0;
|
||||
}
|
||||
|
||||
u32 pxl8_replay_get_total_frames(pxl8_replay* r) {
|
||||
return r ? r->header.total_frames : 0;
|
||||
}
|
||||
|
||||
static void write_chunk(FILE* f, u8 type, const void* data, u32 size) {
|
||||
fwrite(&type, 1, 1, f);
|
||||
u8 size_bytes[3] = {
|
||||
(u8)(size & 0xFF),
|
||||
(u8)((size >> 8) & 0xFF),
|
||||
(u8)((size >> 16) & 0xFF)
|
||||
};
|
||||
fwrite(size_bytes, 3, 1, f);
|
||||
fwrite(data, size, 1, f);
|
||||
}
|
||||
|
||||
static void add_chunk_to_buffer(pxl8_replay* r, u8 type, const void* data, u32 size) {
|
||||
pxl8_replay_chunk* chunk = calloc(1, sizeof(pxl8_replay_chunk));
|
||||
if (!chunk) return;
|
||||
|
||||
chunk->type = type;
|
||||
chunk->size = size;
|
||||
chunk->data = malloc(size);
|
||||
if (!chunk->data) {
|
||||
free(chunk);
|
||||
return;
|
||||
}
|
||||
memcpy(chunk->data, data, size);
|
||||
|
||||
if (!r->pending_inputs) {
|
||||
r->pending_inputs = chunk;
|
||||
r->pending_inputs_tail = chunk;
|
||||
} else {
|
||||
r->pending_inputs_tail->next = chunk;
|
||||
r->pending_inputs_tail = chunk;
|
||||
}
|
||||
}
|
||||
|
||||
static void pack_keys(const bool* keys, u8* packed) {
|
||||
memset(packed, 0, 32);
|
||||
for (int i = 0; i < 256; i++) {
|
||||
if (keys[i]) {
|
||||
packed[i / 8] |= (1 << (i % 8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void unpack_keys(const u8* packed, bool* keys) {
|
||||
for (int i = 0; i < 256; i++) {
|
||||
keys[i] = (packed[i / 8] >> (i % 8)) & 1;
|
||||
}
|
||||
}
|
||||
|
||||
static u8 pack_mouse_buttons(const bool* buttons) {
|
||||
return (buttons[0] ? 1 : 0) | (buttons[1] ? 2 : 0) | (buttons[2] ? 4 : 0);
|
||||
}
|
||||
|
||||
static void unpack_mouse_buttons(u8 packed, bool* buttons) {
|
||||
buttons[0] = (packed & 1) != 0;
|
||||
buttons[1] = (packed & 2) != 0;
|
||||
buttons[2] = (packed & 4) != 0;
|
||||
}
|
||||
|
||||
void pxl8_replay_write_keyframe(pxl8_replay* r, u32 frame, f32 time, pxl8_rng* rng, pxl8_input_state* input) {
|
||||
if (!r || !r->recording) return;
|
||||
|
||||
pxl8_keyframe kf = {0};
|
||||
kf.frame_number = frame;
|
||||
kf.time = time;
|
||||
kf.rng_state = rng ? rng->state : 0;
|
||||
|
||||
if (input) {
|
||||
pack_keys(input->keys_down, kf.keys_down);
|
||||
kf.mouse_buttons = pack_mouse_buttons(input->mouse_buttons_down);
|
||||
kf.mouse_x = (i16)input->mouse_x;
|
||||
kf.mouse_y = (i16)input->mouse_y;
|
||||
}
|
||||
|
||||
if (r->file) {
|
||||
write_chunk(r->file, PXL8_REPLAY_CHUNK_KEYFRAME, &kf, sizeof(kf));
|
||||
fflush(r->file);
|
||||
} else {
|
||||
pxl8_keyframe_entry* entry = calloc(1, sizeof(pxl8_keyframe_entry));
|
||||
if (!entry) return;
|
||||
|
||||
entry->keyframe = kf;
|
||||
entry->input_deltas = r->pending_inputs;
|
||||
r->pending_inputs = NULL;
|
||||
r->pending_inputs_tail = NULL;
|
||||
|
||||
if (r->keyframe_count >= r->max_keyframes && r->keyframes) {
|
||||
pxl8_keyframe_entry* oldest = r->keyframes;
|
||||
r->keyframes = oldest->next;
|
||||
if (r->keyframes) {
|
||||
r->keyframes->prev = NULL;
|
||||
}
|
||||
pxl8_replay_chunk_free(oldest->input_deltas);
|
||||
free(oldest);
|
||||
r->keyframe_count--;
|
||||
}
|
||||
|
||||
entry->prev = r->current_keyframe;
|
||||
if (r->current_keyframe) {
|
||||
r->current_keyframe->next = entry;
|
||||
}
|
||||
r->current_keyframe = entry;
|
||||
if (!r->keyframes) {
|
||||
r->keyframes = entry;
|
||||
}
|
||||
r->keyframe_count++;
|
||||
}
|
||||
|
||||
r->current_frame = frame;
|
||||
r->header.total_frames = frame;
|
||||
}
|
||||
|
||||
void pxl8_replay_write_input(pxl8_replay* r, u32 frame, pxl8_input_state* prev, pxl8_input_state* curr) {
|
||||
if (!r || !r->recording || !prev || !curr) return;
|
||||
|
||||
u8 buffer[256];
|
||||
u32 offset = 0;
|
||||
|
||||
buffer[offset++] = (u8)(frame & 0xFF);
|
||||
buffer[offset++] = (u8)((frame >> 8) & 0xFF);
|
||||
buffer[offset++] = (u8)((frame >> 16) & 0xFF);
|
||||
buffer[offset++] = (u8)((frame >> 24) & 0xFF);
|
||||
|
||||
u8 key_event_count = 0;
|
||||
u8 key_events[64];
|
||||
u32 key_offset = 0;
|
||||
|
||||
for (int i = 0; i < 256 && key_event_count < 32; i++) {
|
||||
if (prev->keys_down[i] != curr->keys_down[i]) {
|
||||
key_events[key_offset++] = (u8)i;
|
||||
key_events[key_offset++] = curr->keys_down[i] ? 1 : 0;
|
||||
key_event_count++;
|
||||
}
|
||||
}
|
||||
|
||||
buffer[offset++] = key_event_count;
|
||||
memcpy(buffer + offset, key_events, key_offset);
|
||||
offset += key_offset;
|
||||
|
||||
u8 prev_mouse_btns = pack_mouse_buttons(prev->mouse_buttons_down);
|
||||
u8 curr_mouse_btns = pack_mouse_buttons(curr->mouse_buttons_down);
|
||||
|
||||
u8 mouse_flags = 0;
|
||||
if (curr->mouse_x != prev->mouse_x || curr->mouse_y != prev->mouse_y) {
|
||||
mouse_flags |= 0x01;
|
||||
}
|
||||
if (curr_mouse_btns != prev_mouse_btns) {
|
||||
mouse_flags |= 0x02;
|
||||
}
|
||||
if (curr->mouse_wheel_x != 0 || curr->mouse_wheel_y != 0) {
|
||||
mouse_flags |= 0x04;
|
||||
}
|
||||
|
||||
buffer[offset++] = mouse_flags;
|
||||
|
||||
if (mouse_flags & 0x01) {
|
||||
i16 dx = (i16)(curr->mouse_x - prev->mouse_x);
|
||||
i16 dy = (i16)(curr->mouse_y - prev->mouse_y);
|
||||
buffer[offset++] = (u8)(dx & 0xFF);
|
||||
buffer[offset++] = (u8)((dx >> 8) & 0xFF);
|
||||
buffer[offset++] = (u8)(dy & 0xFF);
|
||||
buffer[offset++] = (u8)((dy >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
if (mouse_flags & 0x02) {
|
||||
buffer[offset++] = curr_mouse_btns;
|
||||
}
|
||||
|
||||
if (mouse_flags & 0x04) {
|
||||
buffer[offset++] = (i8)curr->mouse_wheel_x;
|
||||
buffer[offset++] = (i8)curr->mouse_wheel_y;
|
||||
}
|
||||
|
||||
if (r->file) {
|
||||
write_chunk(r->file, PXL8_REPLAY_CHUNK_INPUT, buffer, offset);
|
||||
} else {
|
||||
add_chunk_to_buffer(r, PXL8_REPLAY_CHUNK_INPUT, buffer, offset);
|
||||
}
|
||||
|
||||
r->current_frame = frame;
|
||||
r->header.total_frames = frame;
|
||||
}
|
||||
|
||||
void pxl8_replay_write_audio_event(pxl8_replay* r, u32 frame, u8 event_type, u8 context_id, u8 note, f32 volume) {
|
||||
if (!r || !r->recording) return;
|
||||
|
||||
pxl8_audio_event evt = {
|
||||
.frame_number = frame,
|
||||
.event_type = event_type,
|
||||
.context_id = context_id,
|
||||
.note = note,
|
||||
.volume = volume
|
||||
};
|
||||
|
||||
if (r->file) {
|
||||
write_chunk(r->file, PXL8_REPLAY_CHUNK_AUDIO_EVENT, &evt, sizeof(evt));
|
||||
} else {
|
||||
pxl8_replay_chunk* chunk = calloc(1, sizeof(pxl8_replay_chunk));
|
||||
if (!chunk) return;
|
||||
|
||||
chunk->type = PXL8_REPLAY_CHUNK_AUDIO_EVENT;
|
||||
chunk->size = sizeof(evt);
|
||||
chunk->data = malloc(sizeof(evt));
|
||||
if (!chunk->data) {
|
||||
free(chunk);
|
||||
return;
|
||||
}
|
||||
memcpy(chunk->data, &evt, sizeof(evt));
|
||||
|
||||
if (!r->audio_events) {
|
||||
r->audio_events = chunk;
|
||||
r->audio_events_tail = chunk;
|
||||
} else {
|
||||
r->audio_events_tail->next = chunk;
|
||||
r->audio_events_tail = chunk;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_replay_close(pxl8_replay* r) {
|
||||
pxl8_replay_destroy(r);
|
||||
}
|
||||
|
||||
bool pxl8_replay_seek_frame(pxl8_replay* r, u32 frame) {
|
||||
if (!r || !r->playing) return false;
|
||||
|
||||
pxl8_keyframe_entry* target = NULL;
|
||||
for (pxl8_keyframe_entry* e = r->keyframes; e; e = e->next) {
|
||||
if (e->keyframe.frame_number <= frame) {
|
||||
target = e;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!target) return false;
|
||||
|
||||
r->current_keyframe = target;
|
||||
r->current_frame = target->keyframe.frame_number;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pxl8_replay_read_frame(pxl8_replay* r, pxl8_input_state* input_out) {
|
||||
if (!r || !r->playing || !r->current_keyframe) return false;
|
||||
|
||||
u32 target_frame = r->current_frame + 1;
|
||||
|
||||
if (target_frame > r->header.total_frames) return false;
|
||||
|
||||
pxl8_replay_chunk* delta = r->current_keyframe->input_deltas;
|
||||
while (delta) {
|
||||
if (delta->size >= 5) {
|
||||
u32 delta_frame = delta->data[0] | (delta->data[1] << 8) | (delta->data[2] << 16) | (delta->data[3] << 24);
|
||||
if (delta_frame == target_frame) {
|
||||
u32 offset = 4;
|
||||
u8 key_event_count = delta->data[offset++];
|
||||
|
||||
for (u8 i = 0; i < key_event_count && offset + 1 < delta->size; i++) {
|
||||
u8 scancode = delta->data[offset++];
|
||||
u8 pressed = delta->data[offset++];
|
||||
input_out->keys_down[scancode] = pressed != 0;
|
||||
}
|
||||
|
||||
if (offset < delta->size) {
|
||||
u8 mouse_flags = delta->data[offset++];
|
||||
|
||||
if (mouse_flags & 0x01 && offset + 3 < delta->size) {
|
||||
i16 dx = (i16)(delta->data[offset] | (delta->data[offset + 1] << 8));
|
||||
i16 dy = (i16)(delta->data[offset + 2] | (delta->data[offset + 3] << 8));
|
||||
offset += 4;
|
||||
input_out->mouse_x += dx;
|
||||
input_out->mouse_y += dy;
|
||||
}
|
||||
|
||||
if (mouse_flags & 0x02 && offset < delta->size) {
|
||||
u8 mouse_btns = delta->data[offset++];
|
||||
unpack_mouse_buttons(mouse_btns, input_out->mouse_buttons_down);
|
||||
}
|
||||
|
||||
if (mouse_flags & 0x04 && offset + 1 < delta->size) {
|
||||
input_out->mouse_wheel_x = (i8)delta->data[offset++];
|
||||
input_out->mouse_wheel_y = (i8)delta->data[offset++];
|
||||
}
|
||||
}
|
||||
|
||||
r->current_frame = target_frame;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
delta = delta->next;
|
||||
}
|
||||
|
||||
if (r->current_keyframe->next && r->current_keyframe->next->keyframe.frame_number <= target_frame) {
|
||||
r->current_keyframe = r->current_keyframe->next;
|
||||
}
|
||||
|
||||
r->current_frame = target_frame;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pxl8_replay_get_keyframe(pxl8_replay* r, u32 frame, pxl8_keyframe* out) {
|
||||
if (!r || !out) return false;
|
||||
|
||||
pxl8_keyframe_entry* target = NULL;
|
||||
for (pxl8_keyframe_entry* e = r->keyframes; e; e = e->next) {
|
||||
if (e->keyframe.frame_number <= frame) {
|
||||
target = e;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!target) return false;
|
||||
|
||||
*out = target->keyframe;
|
||||
return true;
|
||||
}
|
||||
|
||||
void pxl8_replay_apply_keyframe(pxl8_replay* r, pxl8_keyframe* kf, pxl8_rng* rng, pxl8_input_state* input) {
|
||||
if (!kf) return;
|
||||
|
||||
if (rng) {
|
||||
rng->state = kf->rng_state;
|
||||
}
|
||||
|
||||
if (input) {
|
||||
unpack_keys(kf->keys_down, input->keys_down);
|
||||
unpack_mouse_buttons(kf->mouse_buttons, input->mouse_buttons_down);
|
||||
input->mouse_x = kf->mouse_x;
|
||||
input->mouse_y = kf->mouse_y;
|
||||
}
|
||||
|
||||
if (r) {
|
||||
r->current_frame = kf->frame_number;
|
||||
}
|
||||
}
|
||||
|
||||
bool pxl8_replay_export(pxl8_replay* r, const char* path) {
|
||||
if (!r || !r->keyframes) return false;
|
||||
|
||||
FILE* f = fopen(path, "wb");
|
||||
if (!f) {
|
||||
pxl8_error("Failed to create export file: %s", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
pxl8_replay_header header = r->header;
|
||||
fwrite(&header, sizeof(header), 1, f);
|
||||
|
||||
for (pxl8_keyframe_entry* e = r->keyframes; e; e = e->next) {
|
||||
write_chunk(f, PXL8_REPLAY_CHUNK_KEYFRAME, &e->keyframe, sizeof(pxl8_keyframe));
|
||||
|
||||
for (pxl8_replay_chunk* c = e->input_deltas; c; c = c->next) {
|
||||
write_chunk(f, c->type, c->data, c->size);
|
||||
}
|
||||
}
|
||||
|
||||
for (pxl8_replay_chunk* c = r->audio_events; c; c = c->next) {
|
||||
write_chunk(f, c->type, c->data, c->size);
|
||||
}
|
||||
|
||||
u8 end_chunk = PXL8_REPLAY_CHUNK_END;
|
||||
fwrite(&end_chunk, 1, 1, f);
|
||||
|
||||
fclose(f);
|
||||
pxl8_info("Exported replay to: %s (%u frames)", path, header.total_frames);
|
||||
return true;
|
||||
}
|
||||
84
src/game/pxl8_replay.h
Normal file
84
src/game/pxl8_replay.h
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_io.h"
|
||||
#include "pxl8_rng.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#define PXL8_REPLAY_MAGIC 0x31525850
|
||||
#define PXL8_REPLAY_VERSION 1
|
||||
|
||||
#define PXL8_REPLAY_CHUNK_KEYFRAME 0x01
|
||||
#define PXL8_REPLAY_CHUNK_INPUT 0x02
|
||||
#define PXL8_REPLAY_CHUNK_AUDIO_EVENT 0x03
|
||||
#define PXL8_REPLAY_CHUNK_END 0xFF
|
||||
|
||||
#define PXL8_REPLAY_FLAG_HAS_PALETTE (1 << 0)
|
||||
#define PXL8_REPLAY_FLAG_HAS_GLOBALS (1 << 1)
|
||||
|
||||
typedef struct pxl8_replay pxl8_replay;
|
||||
|
||||
typedef struct pxl8_replay_header {
|
||||
u32 magic;
|
||||
u32 version;
|
||||
u32 flags;
|
||||
u32 keyframe_interval;
|
||||
u32 total_frames;
|
||||
u32 width;
|
||||
u32 height;
|
||||
u32 reserved;
|
||||
} pxl8_replay_header;
|
||||
|
||||
typedef struct pxl8_keyframe {
|
||||
u32 frame_number;
|
||||
f32 time;
|
||||
u32 rng_state;
|
||||
u8 keys_down[32];
|
||||
u8 mouse_buttons;
|
||||
i16 mouse_x;
|
||||
i16 mouse_y;
|
||||
u8 flags;
|
||||
} pxl8_keyframe;
|
||||
|
||||
typedef struct pxl8_input_delta {
|
||||
u32 frame_number;
|
||||
u8 key_event_count;
|
||||
u8 mouse_flags;
|
||||
} pxl8_input_delta;
|
||||
|
||||
typedef struct pxl8_audio_event {
|
||||
u32 frame_number;
|
||||
u8 event_type;
|
||||
u8 context_id;
|
||||
u8 note;
|
||||
f32 volume;
|
||||
} pxl8_audio_event;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_replay* pxl8_replay_create(const char* path, u32 keyframe_interval);
|
||||
pxl8_replay* pxl8_replay_create_buffer(u32 keyframe_interval, u32 max_keyframes);
|
||||
pxl8_replay* pxl8_replay_open(const char* path);
|
||||
void pxl8_replay_destroy(pxl8_replay* r);
|
||||
|
||||
bool pxl8_replay_is_recording(pxl8_replay* r);
|
||||
bool pxl8_replay_is_playing(pxl8_replay* r);
|
||||
u32 pxl8_replay_get_frame(pxl8_replay* r);
|
||||
u32 pxl8_replay_get_total_frames(pxl8_replay* r);
|
||||
|
||||
void pxl8_replay_write_keyframe(pxl8_replay* r, u32 frame, f32 time, pxl8_rng* rng, pxl8_input_state* input);
|
||||
void pxl8_replay_write_input(pxl8_replay* r, u32 frame, pxl8_input_state* prev, pxl8_input_state* curr);
|
||||
void pxl8_replay_write_audio_event(pxl8_replay* r, u32 frame, u8 event_type, u8 context_id, u8 note, f32 volume);
|
||||
void pxl8_replay_close(pxl8_replay* r);
|
||||
|
||||
bool pxl8_replay_seek_frame(pxl8_replay* r, u32 frame);
|
||||
bool pxl8_replay_read_frame(pxl8_replay* r, pxl8_input_state* input_out);
|
||||
bool pxl8_replay_get_keyframe(pxl8_replay* r, u32 frame, pxl8_keyframe* out);
|
||||
void pxl8_replay_apply_keyframe(pxl8_replay* r, pxl8_keyframe* kf, pxl8_rng* rng, pxl8_input_state* input);
|
||||
|
||||
bool pxl8_replay_export(pxl8_replay* r, const char* path);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
214
src/gfx/pxl8_3d_camera.c
Normal file
214
src/gfx/pxl8_3d_camera.c
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
#include "pxl8_3d_camera.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
struct pxl8_3d_camera {
|
||||
pxl8_vec3 position;
|
||||
f32 pitch;
|
||||
f32 roll;
|
||||
f32 yaw;
|
||||
|
||||
pxl8_3d_camera_mode mode;
|
||||
|
||||
f32 aspect;
|
||||
f32 far;
|
||||
f32 fov;
|
||||
f32 near;
|
||||
|
||||
f32 ortho_bottom;
|
||||
f32 ortho_left;
|
||||
f32 ortho_right;
|
||||
f32 ortho_top;
|
||||
|
||||
f32 shake_duration;
|
||||
f32 shake_intensity;
|
||||
pxl8_vec3 shake_offset;
|
||||
f32 shake_timer;
|
||||
};
|
||||
|
||||
pxl8_3d_camera* pxl8_3d_camera_create(void) {
|
||||
pxl8_3d_camera* cam = calloc(1, sizeof(pxl8_3d_camera));
|
||||
if (!cam) return NULL;
|
||||
|
||||
cam->position = (pxl8_vec3){0, 0, 0};
|
||||
cam->pitch = 0;
|
||||
cam->yaw = 0;
|
||||
cam->roll = 0;
|
||||
|
||||
cam->mode = PXL8_3D_CAMERA_PERSPECTIVE;
|
||||
cam->fov = 1.0f;
|
||||
cam->aspect = 16.0f / 9.0f;
|
||||
cam->near = 1.0f;
|
||||
cam->far = 1000.0f;
|
||||
|
||||
return cam;
|
||||
}
|
||||
|
||||
void pxl8_3d_camera_destroy(pxl8_3d_camera* cam) {
|
||||
free(cam);
|
||||
}
|
||||
|
||||
void pxl8_3d_camera_lookat(pxl8_3d_camera* cam, pxl8_vec3 eye, pxl8_vec3 target, pxl8_vec3 up) {
|
||||
if (!cam) return;
|
||||
|
||||
cam->position = eye;
|
||||
|
||||
pxl8_vec3 forward = pxl8_vec3_normalize(pxl8_vec3_sub(target, eye));
|
||||
|
||||
cam->pitch = asinf(-forward.y);
|
||||
cam->yaw = atan2f(forward.x, forward.z);
|
||||
cam->roll = 0;
|
||||
|
||||
(void)up;
|
||||
}
|
||||
|
||||
void pxl8_3d_camera_set_ortho(pxl8_3d_camera* cam, f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far) {
|
||||
if (!cam) return;
|
||||
cam->mode = PXL8_3D_CAMERA_ORTHO;
|
||||
cam->ortho_left = left;
|
||||
cam->ortho_right = right;
|
||||
cam->ortho_bottom = bottom;
|
||||
cam->ortho_top = top;
|
||||
cam->near = near;
|
||||
cam->far = far;
|
||||
}
|
||||
|
||||
void pxl8_3d_camera_set_perspective(pxl8_3d_camera* cam, f32 fov, f32 aspect, f32 near, f32 far) {
|
||||
if (!cam) return;
|
||||
cam->mode = PXL8_3D_CAMERA_PERSPECTIVE;
|
||||
cam->fov = fov;
|
||||
cam->aspect = aspect;
|
||||
cam->near = near;
|
||||
cam->far = far;
|
||||
}
|
||||
|
||||
void pxl8_3d_camera_set_position(pxl8_3d_camera* cam, pxl8_vec3 pos) {
|
||||
if (!cam) return;
|
||||
cam->position = pos;
|
||||
}
|
||||
|
||||
void pxl8_3d_camera_set_rotation(pxl8_3d_camera* cam, f32 pitch, f32 yaw, f32 roll) {
|
||||
if (!cam) return;
|
||||
cam->pitch = pitch;
|
||||
cam->yaw = yaw;
|
||||
cam->roll = roll;
|
||||
}
|
||||
|
||||
pxl8_vec3 pxl8_3d_camera_get_forward(const pxl8_3d_camera* cam) {
|
||||
if (!cam) return (pxl8_vec3){0, 0, -1};
|
||||
|
||||
f32 cp = cosf(cam->pitch);
|
||||
f32 sp = sinf(cam->pitch);
|
||||
f32 cy = cosf(cam->yaw);
|
||||
f32 sy = sinf(cam->yaw);
|
||||
|
||||
return (pxl8_vec3){
|
||||
cp * sy,
|
||||
-sp,
|
||||
cp * cy
|
||||
};
|
||||
}
|
||||
|
||||
pxl8_vec3 pxl8_3d_camera_get_position(const pxl8_3d_camera* cam) {
|
||||
if (!cam) return (pxl8_vec3){0, 0, 0};
|
||||
return pxl8_vec3_add(cam->position, cam->shake_offset);
|
||||
}
|
||||
|
||||
pxl8_mat4 pxl8_3d_camera_get_projection(const pxl8_3d_camera* cam) {
|
||||
if (!cam) return pxl8_mat4_identity();
|
||||
|
||||
if (cam->mode == PXL8_3D_CAMERA_PERSPECTIVE) {
|
||||
return pxl8_mat4_perspective(cam->fov, cam->aspect, cam->near, cam->far);
|
||||
} else {
|
||||
return pxl8_mat4_ortho(
|
||||
cam->ortho_left, cam->ortho_right,
|
||||
cam->ortho_bottom, cam->ortho_top,
|
||||
cam->near, cam->far
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_vec3 pxl8_3d_camera_get_right(const pxl8_3d_camera* cam) {
|
||||
if (!cam) return (pxl8_vec3){1, 0, 0};
|
||||
|
||||
f32 cy = cosf(cam->yaw);
|
||||
f32 sy = sinf(cam->yaw);
|
||||
|
||||
return (pxl8_vec3){cy, 0, -sy};
|
||||
}
|
||||
|
||||
pxl8_vec3 pxl8_3d_camera_get_up(const pxl8_3d_camera* cam) {
|
||||
if (!cam) return (pxl8_vec3){0, 1, 0};
|
||||
|
||||
pxl8_vec3 forward = pxl8_3d_camera_get_forward(cam);
|
||||
pxl8_vec3 right = pxl8_3d_camera_get_right(cam);
|
||||
|
||||
return pxl8_vec3_cross(forward, right);
|
||||
}
|
||||
|
||||
pxl8_mat4 pxl8_3d_camera_get_view(const pxl8_3d_camera* cam) {
|
||||
if (!cam) return pxl8_mat4_identity();
|
||||
|
||||
pxl8_vec3 pos = pxl8_3d_camera_get_position(cam);
|
||||
pxl8_vec3 forward = pxl8_3d_camera_get_forward(cam);
|
||||
pxl8_vec3 target = pxl8_vec3_add(pos, forward);
|
||||
pxl8_vec3 up = (pxl8_vec3){0, 1, 0};
|
||||
|
||||
return pxl8_mat4_lookat(pos, target, up);
|
||||
}
|
||||
|
||||
void pxl8_3d_camera_blend(pxl8_3d_camera* dest, const pxl8_3d_camera* a, const pxl8_3d_camera* b, f32 t) {
|
||||
if (!dest || !a || !b) return;
|
||||
|
||||
dest->position = pxl8_vec3_lerp(a->position, b->position, t);
|
||||
dest->pitch = a->pitch + (b->pitch - a->pitch) * t;
|
||||
dest->yaw = a->yaw + (b->yaw - a->yaw) * t;
|
||||
dest->roll = a->roll + (b->roll - a->roll) * t;
|
||||
|
||||
dest->fov = a->fov + (b->fov - a->fov) * t;
|
||||
dest->aspect = a->aspect + (b->aspect - a->aspect) * t;
|
||||
dest->near = a->near + (b->near - a->near) * t;
|
||||
dest->far = a->far + (b->far - a->far) * t;
|
||||
|
||||
dest->mode = (t < 0.5f) ? a->mode : b->mode;
|
||||
}
|
||||
|
||||
void pxl8_3d_camera_follow(pxl8_3d_camera* cam, pxl8_vec3 target, pxl8_vec3 offset, f32 smoothing, f32 dt) {
|
||||
if (!cam) return;
|
||||
|
||||
pxl8_vec3 desired = pxl8_vec3_add(target, offset);
|
||||
|
||||
f32 t = 1.0f - powf(1.0f - smoothing, dt * 60.0f);
|
||||
cam->position = pxl8_vec3_lerp(cam->position, desired, t);
|
||||
|
||||
pxl8_vec3 forward = pxl8_vec3_normalize(pxl8_vec3_sub(target, cam->position));
|
||||
cam->pitch = asinf(-forward.y);
|
||||
cam->yaw = atan2f(forward.x, forward.z);
|
||||
}
|
||||
|
||||
void pxl8_3d_camera_shake(pxl8_3d_camera* cam, f32 intensity, f32 duration) {
|
||||
if (!cam) return;
|
||||
cam->shake_intensity = intensity;
|
||||
cam->shake_duration = duration;
|
||||
cam->shake_timer = duration;
|
||||
}
|
||||
|
||||
void pxl8_3d_camera_update(pxl8_3d_camera* cam, f32 dt) {
|
||||
if (!cam) return;
|
||||
|
||||
if (cam->shake_timer > 0) {
|
||||
cam->shake_timer -= dt;
|
||||
|
||||
f32 decay = cam->shake_timer / cam->shake_duration;
|
||||
f32 intensity = cam->shake_intensity * decay;
|
||||
|
||||
cam->shake_offset.x = ((f32)rand() / (f32)RAND_MAX * 2.0f - 1.0f) * intensity;
|
||||
cam->shake_offset.y = ((f32)rand() / (f32)RAND_MAX * 2.0f - 1.0f) * intensity;
|
||||
cam->shake_offset.z = ((f32)rand() / (f32)RAND_MAX * 2.0f - 1.0f) * intensity;
|
||||
|
||||
if (cam->shake_timer <= 0) {
|
||||
cam->shake_offset = (pxl8_vec3){0, 0, 0};
|
||||
}
|
||||
}
|
||||
}
|
||||
40
src/gfx/pxl8_3d_camera.h
Normal file
40
src/gfx/pxl8_3d_camera.h
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_math.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef enum pxl8_3d_camera_mode {
|
||||
PXL8_3D_CAMERA_ORTHO,
|
||||
PXL8_3D_CAMERA_PERSPECTIVE
|
||||
} pxl8_3d_camera_mode;
|
||||
|
||||
typedef struct pxl8_3d_camera pxl8_3d_camera;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_3d_camera* pxl8_3d_camera_create(void);
|
||||
void pxl8_3d_camera_destroy(pxl8_3d_camera* cam);
|
||||
|
||||
void pxl8_3d_camera_lookat(pxl8_3d_camera* cam, pxl8_vec3 eye, pxl8_vec3 target, pxl8_vec3 up);
|
||||
void pxl8_3d_camera_set_ortho(pxl8_3d_camera* cam, f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far);
|
||||
void pxl8_3d_camera_set_perspective(pxl8_3d_camera* cam, f32 fov, f32 aspect, f32 near, f32 far);
|
||||
void pxl8_3d_camera_set_position(pxl8_3d_camera* cam, pxl8_vec3 pos);
|
||||
void pxl8_3d_camera_set_rotation(pxl8_3d_camera* cam, f32 pitch, f32 yaw, f32 roll);
|
||||
|
||||
pxl8_vec3 pxl8_3d_camera_get_forward(const pxl8_3d_camera* cam);
|
||||
pxl8_vec3 pxl8_3d_camera_get_position(const pxl8_3d_camera* cam);
|
||||
pxl8_mat4 pxl8_3d_camera_get_projection(const pxl8_3d_camera* cam);
|
||||
pxl8_vec3 pxl8_3d_camera_get_right(const pxl8_3d_camera* cam);
|
||||
pxl8_vec3 pxl8_3d_camera_get_up(const pxl8_3d_camera* cam);
|
||||
pxl8_mat4 pxl8_3d_camera_get_view(const pxl8_3d_camera* cam);
|
||||
|
||||
void pxl8_3d_camera_blend(pxl8_3d_camera* dest, const pxl8_3d_camera* a, const pxl8_3d_camera* b, f32 t);
|
||||
void pxl8_3d_camera_follow(pxl8_3d_camera* cam, pxl8_vec3 target, pxl8_vec3 offset, f32 smoothing, f32 dt);
|
||||
void pxl8_3d_camera_shake(pxl8_3d_camera* cam, f32 intensity, f32 duration);
|
||||
void pxl8_3d_camera_update(pxl8_3d_camera* cam, f32 dt);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
432
src/gfx/pxl8_anim.c
Normal file
432
src/gfx/pxl8_anim.c
Normal file
|
|
@ -0,0 +1,432 @@
|
|||
#include "pxl8_anim.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_ase.h"
|
||||
#include "pxl8_atlas.h"
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_log.h"
|
||||
|
||||
#define PXL8_ANIM_MAX_STATES 32
|
||||
|
||||
typedef struct pxl8_anim_state {
|
||||
char* name;
|
||||
pxl8_anim* anim;
|
||||
} pxl8_anim_state;
|
||||
|
||||
typedef struct pxl8_anim_state_machine {
|
||||
pxl8_anim_state states[PXL8_ANIM_MAX_STATES];
|
||||
u16 state_count;
|
||||
u16 current_state;
|
||||
} pxl8_anim_state_machine;
|
||||
|
||||
static inline pxl8_anim* pxl8_anim_get_active(pxl8_anim* anim) {
|
||||
if (anim->state_machine &&
|
||||
anim->state_machine->current_state < anim->state_machine->state_count) {
|
||||
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
|
||||
if (state_anim) return state_anim;
|
||||
}
|
||||
return anim;
|
||||
}
|
||||
|
||||
pxl8_anim* pxl8_anim_create(const u32* frame_ids, const u16* frame_durations, u16 frame_count) {
|
||||
if (!frame_ids || frame_count == 0) {
|
||||
pxl8_error("Invalid animation parameters");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pxl8_anim* anim = (pxl8_anim*)calloc(1, sizeof(pxl8_anim));
|
||||
if (!anim) {
|
||||
pxl8_error("Failed to allocate animation");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
anim->frame_ids = (u32*)malloc(frame_count * sizeof(u32));
|
||||
if (!anim->frame_ids) {
|
||||
pxl8_error("Failed to allocate frame IDs");
|
||||
free(anim);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(anim->frame_ids, frame_ids, frame_count * sizeof(u32));
|
||||
|
||||
if (frame_durations) {
|
||||
anim->frame_durations = (u16*)malloc(frame_count * sizeof(u16));
|
||||
if (!anim->frame_durations) {
|
||||
pxl8_error("Failed to allocate frame durations");
|
||||
free(anim->frame_ids);
|
||||
free(anim);
|
||||
return NULL;
|
||||
}
|
||||
memcpy(anim->frame_durations, frame_durations, frame_count * sizeof(u16));
|
||||
} else {
|
||||
anim->frame_durations = (u16*)calloc(frame_count, sizeof(u16));
|
||||
if (!anim->frame_durations) {
|
||||
pxl8_error("Failed to allocate frame durations");
|
||||
free(anim->frame_ids);
|
||||
free(anim);
|
||||
return NULL;
|
||||
}
|
||||
for (u16 i = 0; i < frame_count; i++) {
|
||||
anim->frame_durations[i] = 100;
|
||||
}
|
||||
}
|
||||
|
||||
anim->frame_count = frame_count;
|
||||
anim->current_frame = 0;
|
||||
anim->time_accumulator = 0.0f;
|
||||
anim->loop = true;
|
||||
anim->playing = true;
|
||||
anim->reverse = false;
|
||||
anim->speed = 1.0f;
|
||||
anim->state_machine = NULL;
|
||||
anim->on_complete = NULL;
|
||||
anim->on_frame_change = NULL;
|
||||
anim->userdata = NULL;
|
||||
|
||||
return anim;
|
||||
}
|
||||
|
||||
pxl8_anim* pxl8_anim_create_from_ase(pxl8_gfx* gfx, const char* path) {
|
||||
if (!gfx || !path) {
|
||||
pxl8_error("Invalid parameters for ASE animation creation");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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 NULL;
|
||||
}
|
||||
|
||||
if (ase_file.frame_count == 0) {
|
||||
pxl8_error("ASE file has no frames: %s", path);
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
u32* frame_ids = (u32*)malloc(ase_file.frame_count * sizeof(u32));
|
||||
u16* frame_durations = (u16*)malloc(ase_file.frame_count * sizeof(u16));
|
||||
if (!frame_ids || !frame_durations) {
|
||||
pxl8_error("Failed to allocate frame arrays");
|
||||
free(frame_ids);
|
||||
free(frame_durations);
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < ase_file.frame_count; i++) {
|
||||
pxl8_ase_frame* frame = &ase_file.frames[i];
|
||||
result = pxl8_gfx_create_texture(gfx, frame->pixels, frame->width, frame->height);
|
||||
if (result != PXL8_OK) {
|
||||
pxl8_error("Failed to create texture for frame %u", i);
|
||||
free(frame_ids);
|
||||
free(frame_durations);
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
return NULL;
|
||||
}
|
||||
frame_ids[i] = i;
|
||||
frame_durations[i] = frame->duration;
|
||||
}
|
||||
|
||||
pxl8_anim* anim = pxl8_anim_create(frame_ids, frame_durations, ase_file.frame_count);
|
||||
|
||||
free(frame_ids);
|
||||
free(frame_durations);
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
|
||||
return anim;
|
||||
}
|
||||
|
||||
void pxl8_anim_destroy(pxl8_anim* anim) {
|
||||
if (!anim) return;
|
||||
|
||||
if (anim->state_machine) {
|
||||
for (u16 i = 0; i < anim->state_machine->state_count; i++) {
|
||||
free(anim->state_machine->states[i].name);
|
||||
pxl8_anim_destroy(anim->state_machine->states[i].anim);
|
||||
}
|
||||
free(anim->state_machine);
|
||||
}
|
||||
|
||||
free(anim->frame_ids);
|
||||
free(anim->frame_durations);
|
||||
free(anim);
|
||||
}
|
||||
|
||||
pxl8_result pxl8_anim_add_state(pxl8_anim* anim, const char* name, pxl8_anim* state_anim) {
|
||||
if (!anim || !name || !state_anim) {
|
||||
return PXL8_ERROR_NULL_POINTER;
|
||||
}
|
||||
|
||||
if (!anim->state_machine) {
|
||||
anim->state_machine = (pxl8_anim_state_machine*)calloc(1, sizeof(pxl8_anim_state_machine));
|
||||
if (!anim->state_machine) {
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
}
|
||||
|
||||
if (anim->state_machine->state_count >= PXL8_ANIM_MAX_STATES) {
|
||||
pxl8_error("Cannot add more states, maximum %d reached", PXL8_ANIM_MAX_STATES);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
u16 idx = anim->state_machine->state_count;
|
||||
anim->state_machine->states[idx].name = strdup(name);
|
||||
if (!anim->state_machine->states[idx].name) {
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
anim->state_machine->states[idx].anim = state_anim;
|
||||
anim->state_machine->state_count++;
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
u16 pxl8_anim_get_current_frame(const pxl8_anim* anim) {
|
||||
if (!anim) return 0;
|
||||
|
||||
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
|
||||
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
|
||||
if (state_anim) {
|
||||
return state_anim->current_frame;
|
||||
}
|
||||
}
|
||||
|
||||
return anim->current_frame;
|
||||
}
|
||||
|
||||
u32 pxl8_anim_get_current_frame_id(const pxl8_anim* anim) {
|
||||
if (!anim || !anim->frame_ids) return 0;
|
||||
|
||||
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
|
||||
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
|
||||
if (state_anim && state_anim->frame_ids) {
|
||||
return state_anim->frame_ids[state_anim->current_frame];
|
||||
}
|
||||
}
|
||||
|
||||
return anim->frame_ids[anim->current_frame];
|
||||
}
|
||||
|
||||
const char* pxl8_anim_get_state(const pxl8_anim* anim) {
|
||||
if (!anim || !anim->state_machine) return NULL;
|
||||
|
||||
if (anim->state_machine->current_state < anim->state_machine->state_count) {
|
||||
return anim->state_machine->states[anim->state_machine->current_state].name;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool pxl8_anim_has_state_machine(const pxl8_anim* anim) {
|
||||
return anim && anim->state_machine && anim->state_machine->state_count > 0;
|
||||
}
|
||||
|
||||
bool pxl8_anim_is_complete(const pxl8_anim* anim) {
|
||||
if (!anim) return true;
|
||||
|
||||
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
|
||||
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
|
||||
if (state_anim) {
|
||||
return pxl8_anim_is_complete(state_anim);
|
||||
}
|
||||
}
|
||||
|
||||
if (anim->loop) return false;
|
||||
|
||||
if (anim->reverse) {
|
||||
return anim->current_frame == 0;
|
||||
} else {
|
||||
return anim->current_frame >= anim->frame_count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
bool pxl8_anim_is_playing(const pxl8_anim* anim) {
|
||||
if (!anim) return false;
|
||||
|
||||
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
|
||||
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
|
||||
if (state_anim) {
|
||||
return state_anim->playing;
|
||||
}
|
||||
}
|
||||
|
||||
return anim->playing;
|
||||
}
|
||||
|
||||
void pxl8_anim_pause(pxl8_anim* anim) {
|
||||
if (!anim) return;
|
||||
|
||||
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
|
||||
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
|
||||
if (state_anim) {
|
||||
state_anim->playing = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
anim->playing = false;
|
||||
}
|
||||
|
||||
void pxl8_anim_play(pxl8_anim* anim) {
|
||||
if (!anim) return;
|
||||
|
||||
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
|
||||
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
|
||||
if (state_anim) {
|
||||
state_anim->playing = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
anim->playing = true;
|
||||
}
|
||||
|
||||
void pxl8_anim_render_sprite(const pxl8_anim* anim, pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, bool flip_x, bool flip_y) {
|
||||
if (!anim || !gfx) return;
|
||||
|
||||
u32 sprite_id = pxl8_anim_get_current_frame_id(anim);
|
||||
pxl8_2d_sprite(gfx, sprite_id, x, y, w, h, flip_x, flip_y);
|
||||
}
|
||||
|
||||
void pxl8_anim_reset(pxl8_anim* anim) {
|
||||
if (!anim) return;
|
||||
|
||||
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
|
||||
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
|
||||
if (state_anim) {
|
||||
pxl8_anim_reset(state_anim);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
anim->current_frame = anim->reverse ? anim->frame_count - 1 : 0;
|
||||
anim->time_accumulator = 0.0f;
|
||||
}
|
||||
|
||||
void pxl8_anim_set_frame(pxl8_anim* anim, u16 frame) {
|
||||
if (!anim) return;
|
||||
pxl8_anim* target = pxl8_anim_get_active(anim);
|
||||
if (frame < target->frame_count) {
|
||||
target->current_frame = frame;
|
||||
target->time_accumulator = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_anim_set_loop(pxl8_anim* anim, bool loop) {
|
||||
if (!anim) return;
|
||||
pxl8_anim_get_active(anim)->loop = loop;
|
||||
}
|
||||
|
||||
void pxl8_anim_set_reverse(pxl8_anim* anim, bool reverse) {
|
||||
if (!anim) return;
|
||||
pxl8_anim_get_active(anim)->reverse = reverse;
|
||||
}
|
||||
|
||||
void pxl8_anim_set_speed(pxl8_anim* anim, f32 speed) {
|
||||
if (!anim) return;
|
||||
pxl8_anim_get_active(anim)->speed = speed;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_anim_set_state(pxl8_anim* anim, const char* name) {
|
||||
if (!anim || !name) {
|
||||
return PXL8_ERROR_NULL_POINTER;
|
||||
}
|
||||
|
||||
if (!anim->state_machine) {
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
for (u16 i = 0; i < anim->state_machine->state_count; i++) {
|
||||
if (strcmp(anim->state_machine->states[i].name, name) == 0) {
|
||||
if (anim->state_machine->current_state != i) {
|
||||
anim->state_machine->current_state = i;
|
||||
pxl8_anim_reset(anim->state_machine->states[i].anim);
|
||||
}
|
||||
return PXL8_OK;
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_error("Animation state not found: %s", name);
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
void pxl8_anim_stop(pxl8_anim* anim) {
|
||||
if (!anim) return;
|
||||
|
||||
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
|
||||
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
|
||||
if (state_anim) {
|
||||
pxl8_anim_stop(state_anim);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
anim->playing = false;
|
||||
pxl8_anim_reset(anim);
|
||||
}
|
||||
|
||||
void pxl8_anim_update(pxl8_anim* anim, f32 dt) {
|
||||
if (!anim || !anim->playing) return;
|
||||
|
||||
if (anim->state_machine && anim->state_machine->current_state < anim->state_machine->state_count) {
|
||||
pxl8_anim* state_anim = anim->state_machine->states[anim->state_machine->current_state].anim;
|
||||
if (state_anim) {
|
||||
pxl8_anim_update(state_anim, dt);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (anim->frame_count == 0 || !anim->frame_durations) return;
|
||||
|
||||
anim->time_accumulator += dt * anim->speed * 1000.0f;
|
||||
|
||||
u16 current_duration = anim->frame_durations[anim->current_frame];
|
||||
if (current_duration == 0) return;
|
||||
|
||||
while (anim->time_accumulator >= current_duration) {
|
||||
anim->time_accumulator -= current_duration;
|
||||
|
||||
u16 old_frame = anim->current_frame;
|
||||
|
||||
if (anim->reverse) {
|
||||
if (anim->current_frame > 0) {
|
||||
anim->current_frame--;
|
||||
} else {
|
||||
if (anim->loop) {
|
||||
anim->current_frame = anim->frame_count - 1;
|
||||
} else {
|
||||
anim->playing = false;
|
||||
if (anim->on_complete) {
|
||||
anim->on_complete(anim->userdata);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (anim->current_frame < anim->frame_count - 1) {
|
||||
anim->current_frame++;
|
||||
} else {
|
||||
if (anim->loop) {
|
||||
anim->current_frame = 0;
|
||||
} else {
|
||||
anim->playing = false;
|
||||
if (anim->on_complete) {
|
||||
anim->on_complete(anim->userdata);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (old_frame != anim->current_frame && anim->on_frame_change) {
|
||||
anim->on_frame_change(anim->current_frame, anim->userdata);
|
||||
}
|
||||
|
||||
current_duration = anim->frame_durations[anim->current_frame];
|
||||
if (current_duration == 0) break;
|
||||
}
|
||||
}
|
||||
56
src/gfx/pxl8_anim.h
Normal file
56
src/gfx/pxl8_anim.h
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_anim_state_machine pxl8_anim_state_machine;
|
||||
|
||||
typedef struct pxl8_anim {
|
||||
u32* frame_ids;
|
||||
u16* frame_durations;
|
||||
u16 frame_count;
|
||||
u16 current_frame;
|
||||
f32 time_accumulator;
|
||||
|
||||
bool loop;
|
||||
bool playing;
|
||||
bool reverse;
|
||||
f32 speed;
|
||||
|
||||
pxl8_anim_state_machine* state_machine;
|
||||
|
||||
void (*on_complete)(void* userdata);
|
||||
void (*on_frame_change)(u16 frame, void* userdata);
|
||||
void* userdata;
|
||||
} pxl8_anim;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_anim* pxl8_anim_create(const u32* frame_ids, const u16* frame_durations, u16 frame_count);
|
||||
pxl8_anim* pxl8_anim_create_from_ase(pxl8_gfx* gfx, const char* path);
|
||||
void pxl8_anim_destroy(pxl8_anim* anim);
|
||||
|
||||
pxl8_result pxl8_anim_add_state(pxl8_anim* anim, const char* name, pxl8_anim* state_anim);
|
||||
u16 pxl8_anim_get_current_frame(const pxl8_anim* anim);
|
||||
u32 pxl8_anim_get_current_frame_id(const pxl8_anim* anim);
|
||||
const char* pxl8_anim_get_state(const pxl8_anim* anim);
|
||||
bool pxl8_anim_has_state_machine(const pxl8_anim* anim);
|
||||
bool pxl8_anim_is_complete(const pxl8_anim* anim);
|
||||
bool pxl8_anim_is_playing(const pxl8_anim* anim);
|
||||
void pxl8_anim_pause(pxl8_anim* anim);
|
||||
void pxl8_anim_play(pxl8_anim* anim);
|
||||
void pxl8_anim_render_sprite(const pxl8_anim* anim, pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, bool flip_x, bool flip_y);
|
||||
void pxl8_anim_reset(pxl8_anim* anim);
|
||||
void pxl8_anim_set_frame(pxl8_anim* anim, u16 frame);
|
||||
void pxl8_anim_set_loop(pxl8_anim* anim, bool loop);
|
||||
void pxl8_anim_set_reverse(pxl8_anim* anim, bool reverse);
|
||||
void pxl8_anim_set_speed(pxl8_anim* anim, f32 speed);
|
||||
pxl8_result pxl8_anim_set_state(pxl8_anim* anim, const char* name);
|
||||
void pxl8_anim_stop(pxl8_anim* anim);
|
||||
void pxl8_anim_update(pxl8_anim* anim, f32 dt);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
392
src/gfx/pxl8_atlas.c
Normal file
392
src/gfx/pxl8_atlas.c
Normal file
|
|
@ -0,0 +1,392 @@
|
|||
#include "pxl8_atlas.h"
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_color.h"
|
||||
#include "pxl8_log.h"
|
||||
|
||||
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;
|
||||
|
||||
bool dirty;
|
||||
|
||||
u32 entry_capacity, entry_count;
|
||||
pxl8_atlas_entry* entries;
|
||||
|
||||
u32 free_capacity, free_count;
|
||||
u32* free_list;
|
||||
|
||||
pxl8_skyline skyline;
|
||||
};
|
||||
|
||||
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 bool 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;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
u32 new_capacity = (skyline->count - nodes_to_remove + 1) * 2;
|
||||
pxl8_skyline_node* new_nodes = (pxl8_skyline_node*)realloc(
|
||||
skyline->nodes,
|
||||
new_capacity * sizeof(pxl8_skyline_node)
|
||||
);
|
||||
if (!new_nodes) return false;
|
||||
skyline->nodes = new_nodes;
|
||||
skyline->capacity = new_capacity;
|
||||
}
|
||||
|
||||
if (nodes_to_remove > 0) {
|
||||
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] = (pxl8_skyline_node){pos.x, pos.y + (i32)h, (i32)w};
|
||||
skyline->count = skyline->count - nodes_to_remove + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
memmove(
|
||||
&skyline->nodes[i + 1],
|
||||
&skyline->nodes[i + 2],
|
||||
(skyline->count - i - 2) * sizeof(pxl8_skyline_node)
|
||||
);
|
||||
skyline->count--;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_pixel_mode pixel_mode) {
|
||||
pxl8_atlas* atlas = (pxl8_atlas*)calloc(1, sizeof(pxl8_atlas));
|
||||
if (!atlas) return NULL;
|
||||
|
||||
atlas->height = height;
|
||||
atlas->width = width;
|
||||
|
||||
i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode);
|
||||
atlas->pixels = (u8*)calloc(width * height, bytes_per_pixel);
|
||||
if (!atlas->pixels) {
|
||||
free(atlas);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
atlas->entry_capacity = PXL8_DEFAULT_ATLAS_ENTRY_CAPACITY;
|
||||
atlas->entries = (pxl8_atlas_entry*)calloc(atlas->entry_capacity, sizeof(pxl8_atlas_entry));
|
||||
if (!atlas->entries) {
|
||||
free(atlas->pixels);
|
||||
free(atlas);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
atlas->free_capacity = 16;
|
||||
atlas->free_list = (u32*)calloc(atlas->free_capacity, sizeof(u32));
|
||||
if (!atlas->free_list) {
|
||||
free(atlas->entries);
|
||||
free(atlas->pixels);
|
||||
free(atlas);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
atlas->skyline.capacity = 16;
|
||||
atlas->skyline.nodes =
|
||||
(pxl8_skyline_node*)calloc(atlas->skyline.capacity, sizeof(pxl8_skyline_node));
|
||||
if (!atlas->skyline.nodes) {
|
||||
free(atlas->free_list);
|
||||
free(atlas->entries);
|
||||
free(atlas->pixels);
|
||||
free(atlas);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
atlas->skyline.nodes[0] = (pxl8_skyline_node){0, 0, (i32)width};
|
||||
atlas->skyline.count = 1;
|
||||
|
||||
return atlas;
|
||||
}
|
||||
|
||||
void pxl8_atlas_destroy(pxl8_atlas* atlas) {
|
||||
if (!atlas) return;
|
||||
|
||||
free(atlas->entries);
|
||||
free(atlas->free_list);
|
||||
free(atlas->pixels);
|
||||
free(atlas->skyline.nodes);
|
||||
free(atlas);
|
||||
}
|
||||
|
||||
void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count) {
|
||||
if (!atlas) return;
|
||||
|
||||
for (u32 i = preserve_count; i < atlas->entry_count; i++) {
|
||||
atlas->entries[i].active = false;
|
||||
}
|
||||
|
||||
atlas->entry_count = preserve_count;
|
||||
atlas->free_count = 0;
|
||||
|
||||
atlas->skyline.nodes[0] = (pxl8_skyline_node){0, 0, (i32)atlas->width};
|
||||
atlas->skyline.count = 1;
|
||||
|
||||
atlas->dirty = true;
|
||||
}
|
||||
|
||||
bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode) {
|
||||
if (!atlas || atlas->width >= 4096) return false;
|
||||
|
||||
i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode);
|
||||
u32 new_size = atlas->width * 2;
|
||||
u32 old_width = atlas->width;
|
||||
|
||||
u8* new_pixels = (u8*)calloc(new_size * new_size, bytes_per_pixel);
|
||||
if (!new_pixels) return false;
|
||||
|
||||
pxl8_skyline new_skyline;
|
||||
new_skyline.nodes = (pxl8_skyline_node*)calloc(16, sizeof(pxl8_skyline_node));
|
||||
if (!new_skyline.nodes) {
|
||||
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) {
|
||||
free(new_skyline.nodes);
|
||||
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 == 2) {
|
||||
((u16*)new_pixels)[dst_idx] = ((u16*)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;
|
||||
|
||||
if (!pxl8_skyline_add_rect(&new_skyline, fit.pos, atlas->entries[i].w, atlas->entries[i].h)) {
|
||||
free(new_skyline.nodes);
|
||||
free(new_pixels);
|
||||
return false;
|
||||
}
|
||||
pxl8_skyline_compact(&new_skyline);
|
||||
}
|
||||
|
||||
free(atlas->pixels);
|
||||
free(atlas->skyline.nodes);
|
||||
|
||||
atlas->pixels = new_pixels;
|
||||
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;
|
||||
}
|
||||
|
||||
u32 pxl8_atlas_add_texture(
|
||||
pxl8_atlas* atlas,
|
||||
const u8* pixels,
|
||||
u32 w,
|
||||
u32 h,
|
||||
pxl8_pixel_mode pixel_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, pixel_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) {
|
||||
u32 new_capacity = atlas->entry_capacity * 2;
|
||||
pxl8_atlas_entry* new_entries = (pxl8_atlas_entry*)realloc(
|
||||
atlas->entries,
|
||||
new_capacity * sizeof(pxl8_atlas_entry)
|
||||
);
|
||||
if (!new_entries) return UINT32_MAX;
|
||||
atlas->entries = new_entries;
|
||||
atlas->entry_capacity = new_capacity;
|
||||
}
|
||||
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;
|
||||
|
||||
i32 bytes_per_pixel = pxl8_bytes_per_pixel(pixel_mode);
|
||||
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 == 2) {
|
||||
((u16*)atlas->pixels)[dst_idx] = ((const u16*)pixels)[src_idx];
|
||||
} else {
|
||||
atlas->pixels[dst_idx] = pixels[src_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!pxl8_skyline_add_rect(&atlas->skyline, fit.pos, w, h)) {
|
||||
entry->active = false;
|
||||
return UINT32_MAX;
|
||||
}
|
||||
pxl8_skyline_compact(&atlas->skyline);
|
||||
|
||||
atlas->dirty = true;
|
||||
|
||||
return texture_id;
|
||||
}
|
||||
|
||||
const pxl8_atlas_entry* pxl8_atlas_get_entry(const pxl8_atlas* atlas, u32 id) {
|
||||
if (!atlas || id >= atlas->entry_count) return NULL;
|
||||
return &atlas->entries[id];
|
||||
}
|
||||
|
||||
u32 pxl8_atlas_get_entry_count(const pxl8_atlas* atlas) {
|
||||
return atlas ? atlas->entry_count : 0;
|
||||
}
|
||||
|
||||
u32 pxl8_atlas_get_height(const pxl8_atlas* atlas) {
|
||||
return atlas ? atlas->height : 0;
|
||||
}
|
||||
|
||||
const u8* pxl8_atlas_get_pixels(const pxl8_atlas* atlas) {
|
||||
return atlas ? atlas->pixels : NULL;
|
||||
}
|
||||
|
||||
u32 pxl8_atlas_get_width(const pxl8_atlas* atlas) {
|
||||
return atlas ? atlas->width : 0;
|
||||
}
|
||||
|
||||
bool pxl8_atlas_is_dirty(const pxl8_atlas* atlas) {
|
||||
return atlas ? atlas->dirty : false;
|
||||
}
|
||||
|
||||
void pxl8_atlas_mark_clean(pxl8_atlas* atlas) {
|
||||
if (atlas) {
|
||||
atlas->dirty = false;
|
||||
}
|
||||
}
|
||||
35
src/gfx/pxl8_atlas.h
Normal file
35
src/gfx/pxl8_atlas.h
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_atlas pxl8_atlas;
|
||||
|
||||
typedef struct pxl8_atlas_entry {
|
||||
bool active;
|
||||
u32 texture_id;
|
||||
i32 x, y, w, h;
|
||||
} pxl8_atlas_entry;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_atlas* pxl8_atlas_create(u32 width, u32 height, pxl8_pixel_mode pixel_mode);
|
||||
void pxl8_atlas_destroy(pxl8_atlas* atlas);
|
||||
|
||||
const pxl8_atlas_entry* pxl8_atlas_get_entry(const pxl8_atlas* atlas, u32 id);
|
||||
u32 pxl8_atlas_get_entry_count(const pxl8_atlas* atlas);
|
||||
u32 pxl8_atlas_get_height(const pxl8_atlas* atlas);
|
||||
const u8* pxl8_atlas_get_pixels(const pxl8_atlas* atlas);
|
||||
u32 pxl8_atlas_get_width(const pxl8_atlas* atlas);
|
||||
bool pxl8_atlas_is_dirty(const pxl8_atlas* atlas);
|
||||
|
||||
void pxl8_atlas_mark_clean(pxl8_atlas* atlas);
|
||||
|
||||
u32 pxl8_atlas_add_texture(pxl8_atlas* atlas, const u8* pixels, u32 w, u32 h, pxl8_pixel_mode pixel_mode);
|
||||
void pxl8_atlas_clear(pxl8_atlas* atlas, u32 preserve_count);
|
||||
bool pxl8_atlas_expand(pxl8_atlas* atlas, pxl8_pixel_mode pixel_mode);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
19
src/gfx/pxl8_backend.h
Normal file
19
src/gfx/pxl8_backend.h
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#ifndef PXL8_BACKEND_H
|
||||
#define PXL8_BACKEND_H
|
||||
|
||||
#include "pxl8_cpu.h"
|
||||
|
||||
typedef enum {
|
||||
PXL8_GFX_BACKEND_CPU,
|
||||
PXL8_GFX_BACKEND_GPU,
|
||||
} pxl8_gfx_backend_type;
|
||||
|
||||
typedef struct {
|
||||
pxl8_gfx_backend_type type;
|
||||
union {
|
||||
pxl8_cpu_backend* cpu;
|
||||
void* gpu;
|
||||
};
|
||||
} pxl8_gfx_backend;
|
||||
|
||||
#endif
|
||||
194
src/gfx/pxl8_blend.c
Normal file
194
src/gfx/pxl8_blend.c
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
#include "pxl8_blend.h"
|
||||
#include "pxl8_colormap.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
struct pxl8_palette_cube {
|
||||
u8 colors[PXL8_PALETTE_SIZE * 3];
|
||||
u8 table[PXL8_CUBE_ENTRIES];
|
||||
u8 stable[PXL8_CUBE_ENTRIES];
|
||||
};
|
||||
|
||||
struct pxl8_additive_table {
|
||||
u8 table[PXL8_BLEND_TABLE_SIZE];
|
||||
};
|
||||
|
||||
struct pxl8_overbright_table {
|
||||
u8 table[PXL8_OVERBRIGHT_TABLE_SIZE];
|
||||
};
|
||||
|
||||
static u8 find_closest_stable(const pxl8_palette* pal, u8 r, u8 g, u8 b) {
|
||||
u8 best_idx = 1;
|
||||
u32 best_dist = 0xFFFFFFFF;
|
||||
|
||||
u8 dynamic_end = PXL8_DYNAMIC_RANGE_START + PXL8_DYNAMIC_RANGE_COUNT;
|
||||
|
||||
for (u32 i = 1; i < PXL8_FULLBRIGHT_START; i++) {
|
||||
if (i >= PXL8_DYNAMIC_RANGE_START && i < dynamic_end) {
|
||||
continue;
|
||||
}
|
||||
|
||||
u8 pr, pg, pb;
|
||||
pxl8_palette_get_rgb(pal, (u8)i, &pr, &pg, &pb);
|
||||
|
||||
i32 dr = (i32)r - (i32)pr;
|
||||
i32 dg = (i32)g - (i32)pg;
|
||||
i32 db = (i32)b - (i32)pb;
|
||||
u32 dist = (u32)(dr * dr + dg * dg + db * db);
|
||||
|
||||
if (dist < best_dist) {
|
||||
best_dist = dist;
|
||||
best_idx = (u8)i;
|
||||
if (dist == 0) break;
|
||||
}
|
||||
}
|
||||
|
||||
return best_idx;
|
||||
}
|
||||
|
||||
pxl8_palette_cube* pxl8_palette_cube_create(const pxl8_palette* pal) {
|
||||
pxl8_palette_cube* cube = calloc(1, sizeof(pxl8_palette_cube));
|
||||
if (!cube) return NULL;
|
||||
pxl8_palette_cube_rebuild(cube, pal);
|
||||
return cube;
|
||||
}
|
||||
|
||||
void pxl8_palette_cube_destroy(pxl8_palette_cube* cube) {
|
||||
free(cube);
|
||||
}
|
||||
|
||||
void pxl8_palette_cube_rebuild(pxl8_palette_cube* cube, const pxl8_palette* pal) {
|
||||
if (!cube || !pal) return;
|
||||
|
||||
for (u32 i = 0; i < PXL8_PALETTE_SIZE; i++) {
|
||||
u8 r, g, b;
|
||||
pxl8_palette_get_rgb(pal, (u8)i, &r, &g, &b);
|
||||
cube->colors[i * 3 + 0] = r;
|
||||
cube->colors[i * 3 + 1] = g;
|
||||
cube->colors[i * 3 + 2] = b;
|
||||
}
|
||||
|
||||
for (u32 bi = 0; bi < PXL8_CUBE_SIZE; bi++) {
|
||||
for (u32 gi = 0; gi < PXL8_CUBE_SIZE; gi++) {
|
||||
for (u32 ri = 0; ri < PXL8_CUBE_SIZE; ri++) {
|
||||
u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri;
|
||||
u8 r8 = (u8)((ri * 255) / (PXL8_CUBE_SIZE - 1));
|
||||
u8 g8 = (u8)((gi * 255) / (PXL8_CUBE_SIZE - 1));
|
||||
u8 b8 = (u8)((bi * 255) / (PXL8_CUBE_SIZE - 1));
|
||||
|
||||
cube->table[idx] = pxl8_palette_find_closest(pal, r8, g8, b8);
|
||||
cube->stable[idx] = find_closest_stable(pal, r8, g8, b8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u8 pxl8_palette_cube_lookup(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b) {
|
||||
u32 ri = (r * (PXL8_CUBE_SIZE - 1)) / 255;
|
||||
u32 gi = (g * (PXL8_CUBE_SIZE - 1)) / 255;
|
||||
u32 bi = (b * (PXL8_CUBE_SIZE - 1)) / 255;
|
||||
u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri;
|
||||
return cube->table[idx];
|
||||
}
|
||||
|
||||
u8 pxl8_palette_cube_lookup_stable(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b) {
|
||||
u32 ri = (r * (PXL8_CUBE_SIZE - 1)) / 255;
|
||||
u32 gi = (g * (PXL8_CUBE_SIZE - 1)) / 255;
|
||||
u32 bi = (b * (PXL8_CUBE_SIZE - 1)) / 255;
|
||||
u32 idx = (bi * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE) + (gi * PXL8_CUBE_SIZE) + ri;
|
||||
return cube->stable[idx];
|
||||
}
|
||||
|
||||
void pxl8_palette_cube_get_rgb(const pxl8_palette_cube* cube, u8 idx, u8* r, u8* g, u8* b) {
|
||||
*r = cube->colors[idx * 3 + 0];
|
||||
*g = cube->colors[idx * 3 + 1];
|
||||
*b = cube->colors[idx * 3 + 2];
|
||||
}
|
||||
|
||||
pxl8_additive_table* pxl8_additive_table_create(const pxl8_palette* pal) {
|
||||
pxl8_additive_table* table = calloc(1, sizeof(pxl8_additive_table));
|
||||
if (!table) return NULL;
|
||||
pxl8_additive_table_rebuild(table, pal);
|
||||
return table;
|
||||
}
|
||||
|
||||
void pxl8_additive_table_destroy(pxl8_additive_table* table) {
|
||||
free(table);
|
||||
}
|
||||
|
||||
void pxl8_additive_table_rebuild(pxl8_additive_table* table, const pxl8_palette* pal) {
|
||||
if (!table || !pal) return;
|
||||
|
||||
for (u32 src = 0; src < 256; src++) {
|
||||
u8 sr, sg, sb;
|
||||
pxl8_palette_get_rgb(pal, (u8)src, &sr, &sg, &sb);
|
||||
|
||||
for (u32 dst = 0; dst < 256; dst++) {
|
||||
u32 idx = src * 256 + dst;
|
||||
|
||||
if (src == PXL8_TRANSPARENT) {
|
||||
table->table[idx] = (u8)dst;
|
||||
continue;
|
||||
}
|
||||
|
||||
u8 dr, dg, db;
|
||||
pxl8_palette_get_rgb(pal, (u8)dst, &dr, &dg, &db);
|
||||
|
||||
u16 ar = (u16)sr + (u16)dr;
|
||||
u16 ag = (u16)sg + (u16)dg;
|
||||
u16 ab = (u16)sb + (u16)db;
|
||||
if (ar > 255) ar = 255;
|
||||
if (ag > 255) ag = 255;
|
||||
if (ab > 255) ab = 255;
|
||||
|
||||
table->table[idx] = find_closest_stable(pal, (u8)ar, (u8)ag, (u8)ab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u8 pxl8_additive_blend(const pxl8_additive_table* table, u8 src, u8 dst) {
|
||||
return table->table[(u32)src * 256 + dst];
|
||||
}
|
||||
|
||||
pxl8_overbright_table* pxl8_overbright_table_create(const pxl8_palette* pal) {
|
||||
pxl8_overbright_table* table = calloc(1, sizeof(pxl8_overbright_table));
|
||||
if (!table) return NULL;
|
||||
pxl8_overbright_table_rebuild(table, pal);
|
||||
return table;
|
||||
}
|
||||
|
||||
void pxl8_overbright_table_destroy(pxl8_overbright_table* table) {
|
||||
free(table);
|
||||
}
|
||||
|
||||
void pxl8_overbright_table_rebuild(pxl8_overbright_table* table, const pxl8_palette* pal) {
|
||||
if (!table || !pal) return;
|
||||
|
||||
for (u32 level = 0; level < PXL8_OVERBRIGHT_LEVELS; level++) {
|
||||
f32 overbright = (f32)level / (f32)(PXL8_OVERBRIGHT_LEVELS - 1);
|
||||
|
||||
for (u32 pal_idx = 0; pal_idx < 256; pal_idx++) {
|
||||
u32 idx = level * 256 + pal_idx;
|
||||
|
||||
if (pal_idx == PXL8_TRANSPARENT) {
|
||||
table->table[idx] = PXL8_TRANSPARENT;
|
||||
continue;
|
||||
}
|
||||
|
||||
u8 r, g, b;
|
||||
pxl8_palette_get_rgb(pal, (u8)pal_idx, &r, &g, &b);
|
||||
|
||||
u8 or = (u8)(r + (255 - r) * overbright);
|
||||
u8 og = (u8)(g + (255 - g) * overbright);
|
||||
u8 ob = (u8)(b + (255 - b) * overbright);
|
||||
|
||||
table->table[idx] = find_closest_stable(pal, or, og, ob);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u8 pxl8_overbright_lookup(const pxl8_overbright_table* table, u8 pal_idx, f32 emissive) {
|
||||
u32 level = (u32)(emissive * (PXL8_OVERBRIGHT_LEVELS - 1));
|
||||
if (level >= PXL8_OVERBRIGHT_LEVELS) level = PXL8_OVERBRIGHT_LEVELS - 1;
|
||||
return table->table[level * 256 + pal_idx];
|
||||
}
|
||||
39
src/gfx/pxl8_blend.h
Normal file
39
src/gfx/pxl8_blend.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_palette.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define PXL8_CUBE_SIZE 32
|
||||
#define PXL8_CUBE_ENTRIES (PXL8_CUBE_SIZE * PXL8_CUBE_SIZE * PXL8_CUBE_SIZE)
|
||||
#define PXL8_BLEND_TABLE_SIZE (256 * 256)
|
||||
#define PXL8_OVERBRIGHT_LEVELS 16
|
||||
#define PXL8_OVERBRIGHT_TABLE_SIZE (256 * PXL8_OVERBRIGHT_LEVELS)
|
||||
|
||||
typedef struct pxl8_additive_table pxl8_additive_table;
|
||||
typedef struct pxl8_overbright_table pxl8_overbright_table;
|
||||
typedef struct pxl8_palette_cube pxl8_palette_cube;
|
||||
|
||||
pxl8_additive_table* pxl8_additive_table_create(const pxl8_palette* pal);
|
||||
void pxl8_additive_table_destroy(pxl8_additive_table* table);
|
||||
void pxl8_additive_table_rebuild(pxl8_additive_table* table, const pxl8_palette* pal);
|
||||
u8 pxl8_additive_blend(const pxl8_additive_table* table, u8 src, u8 dst);
|
||||
|
||||
pxl8_overbright_table* pxl8_overbright_table_create(const pxl8_palette* pal);
|
||||
void pxl8_overbright_table_destroy(pxl8_overbright_table* table);
|
||||
void pxl8_overbright_table_rebuild(pxl8_overbright_table* table, const pxl8_palette* pal);
|
||||
u8 pxl8_overbright_lookup(const pxl8_overbright_table* table, u8 pal_idx, f32 emissive);
|
||||
|
||||
pxl8_palette_cube* pxl8_palette_cube_create(const pxl8_palette* pal);
|
||||
void pxl8_palette_cube_destroy(pxl8_palette_cube* cube);
|
||||
void pxl8_palette_cube_rebuild(pxl8_palette_cube* cube, const pxl8_palette* pal);
|
||||
u8 pxl8_palette_cube_lookup(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b);
|
||||
u8 pxl8_palette_cube_lookup_stable(const pxl8_palette_cube* cube, u8 r, u8 g, u8 b);
|
||||
void pxl8_palette_cube_get_rgb(const pxl8_palette_cube* cube, u8 idx, u8* r, u8* g, u8* b);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
57
src/gfx/pxl8_blit.c
Normal file
57
src/gfx/pxl8_blit.c
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
#include "pxl8_blit.h"
|
||||
|
||||
void pxl8_blit_hicolor(u16* fb, u32 fb_width, const u16* sprite, u32 atlas_width,
|
||||
i32 x, i32 y, u32 w, u32 h) {
|
||||
u16* dest_base = fb + y * fb_width + x;
|
||||
const u16* src_base = sprite;
|
||||
|
||||
for (u32 row = 0; row < h; row++) {
|
||||
u16* dest_row = dest_base + row * fb_width;
|
||||
const u16* src_row = src_base + row * atlas_width;
|
||||
|
||||
u32 col = 0;
|
||||
u32 count2 = w / 2;
|
||||
for (u32 i = 0; i < count2; i++) {
|
||||
u32 pixels = ((const u32*)src_row)[i];
|
||||
if (pixels == 0) {
|
||||
col += 2;
|
||||
continue;
|
||||
}
|
||||
dest_row[col] = pxl8_blend_hicolor((u16)(pixels), dest_row[col]);
|
||||
dest_row[col + 1] = pxl8_blend_hicolor((u16)(pixels >> 16), dest_row[col + 1]);
|
||||
col += 2;
|
||||
}
|
||||
if (w & 1) {
|
||||
dest_row[col] = pxl8_blend_hicolor(src_row[col], dest_row[col]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_blit_indexed(u8* fb, u32 fb_width, const u8* sprite, u32 atlas_width,
|
||||
i32 x, i32 y, u32 w, u32 h) {
|
||||
u8* dest_base = fb + y * fb_width + x;
|
||||
const u8* src_base = sprite;
|
||||
|
||||
for (u32 row = 0; row < h; row++) {
|
||||
u8* dest_row = dest_base + row * fb_width;
|
||||
const u8* src_row = src_base + row * atlas_width;
|
||||
|
||||
u32 col = 0;
|
||||
u32 count4 = w / 4;
|
||||
for (u32 i = 0; i < count4; i++) {
|
||||
u32 pixels = ((const u32*)src_row)[i];
|
||||
if (pixels == 0) {
|
||||
col += 4;
|
||||
continue;
|
||||
}
|
||||
dest_row[col] = pxl8_blend_indexed((u8)(pixels), dest_row[col]);
|
||||
dest_row[col + 1] = pxl8_blend_indexed((u8)(pixels >> 8), dest_row[col + 1]);
|
||||
dest_row[col + 2] = pxl8_blend_indexed((u8)(pixels >> 16), dest_row[col + 2]);
|
||||
dest_row[col + 3] = pxl8_blend_indexed((u8)(pixels >> 24), dest_row[col + 3]);
|
||||
col += 4;
|
||||
}
|
||||
for (; col < w; col++) {
|
||||
dest_row[col] = pxl8_blend_indexed(src_row[col], dest_row[col]);
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/gfx/pxl8_blit.h
Normal file
32
src/gfx/pxl8_blit.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
static inline u8 pxl8_blend_indexed(u8 src, u8 dst) {
|
||||
u8 m = (u8)(-(src != 0));
|
||||
return (src & m) | (dst & ~m);
|
||||
}
|
||||
|
||||
static inline u16 pxl8_blend_hicolor(u16 src, u16 dst) {
|
||||
u16 m = (u16)(-(src != 0));
|
||||
return (src & m) | (dst & ~m);
|
||||
}
|
||||
|
||||
void pxl8_blit_hicolor(
|
||||
u16* fb, u32 fb_width,
|
||||
const u16* sprite, u32 atlas_width,
|
||||
i32 x, i32 y, u32 w, u32 h
|
||||
);
|
||||
void pxl8_blit_indexed(
|
||||
u8* fb, u32 fb_width,
|
||||
const u8* sprite, u32 atlas_width,
|
||||
i32 x, i32 y, u32 w, u32 h
|
||||
);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
71
src/gfx/pxl8_color.h
Normal file
71
src/gfx/pxl8_color.h
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
static inline i32 pxl8_bytes_per_pixel(pxl8_pixel_mode mode) {
|
||||
return (i32)mode;
|
||||
}
|
||||
|
||||
static inline u16 pxl8_rgb565_pack(u8 r, u8 g, u8 b) {
|
||||
return ((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3);
|
||||
}
|
||||
|
||||
static inline void pxl8_rgb565_unpack(u16 color, u8* r, u8* g, u8* b) {
|
||||
*r = (color >> 11) << 3;
|
||||
*g = ((color >> 5) & 0x3F) << 2;
|
||||
*b = (color & 0x1F) << 3;
|
||||
}
|
||||
|
||||
static inline u16 pxl8_rgba32_to_rgb565(u32 rgba) {
|
||||
return pxl8_rgb565_pack(rgba & 0xFF, (rgba >> 8) & 0xFF, (rgba >> 16) & 0xFF);
|
||||
}
|
||||
|
||||
static inline u32 pxl8_rgb565_to_rgba32(u16 color) {
|
||||
u8 r, g, b;
|
||||
pxl8_rgb565_unpack(color, &r, &g, &b);
|
||||
return r | ((u32)g << 8) | ((u32)b << 16) | 0xFF000000;
|
||||
}
|
||||
|
||||
static inline void pxl8_rgba32_unpack(u32 color, u8* r, u8* g, u8* b, u8* a) {
|
||||
*r = color & 0xFF;
|
||||
*g = (color >> 8) & 0xFF;
|
||||
*b = (color >> 16) & 0xFF;
|
||||
*a = (color >> 24) & 0xFF;
|
||||
}
|
||||
|
||||
static inline u32 pxl8_rgba32_pack(u8 r, u8 g, u8 b, u8 a) {
|
||||
return r | ((u32)g << 8) | ((u32)b << 16) | ((u32)a << 24);
|
||||
}
|
||||
|
||||
static inline u32 pxl8_color_to_rgba(u32 abgr) {
|
||||
u8 r = abgr & 0xFF;
|
||||
u8 g = (abgr >> 8) & 0xFF;
|
||||
u8 b = (abgr >> 16) & 0xFF;
|
||||
u8 a = (abgr >> 24) & 0xFF;
|
||||
return ((u32)r << 24) | ((u32)g << 16) | ((u32)b << 8) | a;
|
||||
}
|
||||
|
||||
static inline u32 pxl8_color_from_rgba(u32 rgba) {
|
||||
u8 r = (rgba >> 24) & 0xFF;
|
||||
u8 g = (rgba >> 16) & 0xFF;
|
||||
u8 b = (rgba >> 8) & 0xFF;
|
||||
u8 a = rgba & 0xFF;
|
||||
return r | ((u32)g << 8) | ((u32)b << 16) | ((u32)a << 24);
|
||||
}
|
||||
|
||||
static inline u8 pxl8_color_lerp_channel(u8 c1, u8 c2, f32 t) {
|
||||
return c1 + (i32)((c2 - c1) * t);
|
||||
}
|
||||
|
||||
static inline u8 pxl8_rgb332_pack(u8 r, u8 g, u8 b) {
|
||||
return ((r >> 5) << 5) | ((g >> 5) << 2) | (b >> 6);
|
||||
}
|
||||
|
||||
static inline void pxl8_rgb332_unpack(u8 c, u8* r, u8* g, u8* b) {
|
||||
u8 ri = (c >> 5) & 0x07;
|
||||
u8 gi = (c >> 2) & 0x07;
|
||||
u8 bi = c & 0x03;
|
||||
*r = (ri << 5) | (ri << 2) | (ri >> 1);
|
||||
*g = (gi << 5) | (gi << 2) | (gi >> 1);
|
||||
*b = (bi << 6) | (bi << 4) | (bi << 2) | bi;
|
||||
}
|
||||
116
src/gfx/pxl8_colormap.c
Normal file
116
src/gfx/pxl8_colormap.c
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
#include "pxl8_colormap.h"
|
||||
#include <string.h>
|
||||
|
||||
static void rgb_to_hsl(u8 r, u8 g, u8 b, i32* h, i32* s, i32* l) {
|
||||
i32 max = r > g ? (r > b ? r : b) : (g > b ? g : b);
|
||||
i32 min = r < g ? (r < b ? r : b) : (g < b ? g : b);
|
||||
i32 chroma = max - min;
|
||||
|
||||
*l = (max + min) / 2;
|
||||
|
||||
if (chroma == 0) {
|
||||
*h = 0;
|
||||
*s = 0;
|
||||
} else {
|
||||
i32 denom = 255 - (*l > 127 ? 2 * (*l) - 255 : 255 - 2 * (*l));
|
||||
if (denom <= 0) denom = 1;
|
||||
*s = (chroma * 255) / denom;
|
||||
if (*s > 255) *s = 255;
|
||||
|
||||
if (max == (i32)r) {
|
||||
*h = (((i32)g - (i32)b) * 60) / chroma;
|
||||
if (*h < 0) *h += 360;
|
||||
} else if (max == (i32)g) {
|
||||
*h = 120 + (((i32)b - (i32)r) * 60) / chroma;
|
||||
} else {
|
||||
*h = 240 + (((i32)r - (i32)g) * 60) / chroma;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static u8 find_closest_color(const u32* palette, u8 target_r, u8 target_g, u8 target_b) {
|
||||
u8 best_idx = 1;
|
||||
u32 best_dist = 0xFFFFFFFF;
|
||||
|
||||
u8 dynamic_end = PXL8_DYNAMIC_RANGE_START + PXL8_DYNAMIC_RANGE_COUNT;
|
||||
|
||||
i32 th, ts, tl;
|
||||
rgb_to_hsl(target_r, target_g, target_b, &th, &ts, &tl);
|
||||
|
||||
for (u32 i = 1; i < PXL8_FULLBRIGHT_START; i++) {
|
||||
if (i >= PXL8_DYNAMIC_RANGE_START && i < dynamic_end) {
|
||||
continue;
|
||||
}
|
||||
|
||||
u32 c = palette[i];
|
||||
u8 pr = c & 0xFF;
|
||||
u8 pg = (c >> 8) & 0xFF;
|
||||
u8 pb = (c >> 16) & 0xFF;
|
||||
|
||||
i32 ph, ps, pl;
|
||||
rgb_to_hsl(pr, pg, pb, &ph, &ps, &pl);
|
||||
|
||||
i32 dh = th - ph;
|
||||
if (dh > 180) dh -= 360;
|
||||
if (dh < -180) dh += 360;
|
||||
|
||||
i32 ds = ts - ps;
|
||||
i32 dl = tl - pl;
|
||||
|
||||
u32 dist = (u32)(dh * dh * 4 + ds * ds + dl * dl);
|
||||
|
||||
if (dist < best_dist) {
|
||||
best_dist = dist;
|
||||
best_idx = (u8)i;
|
||||
if (dist == 0) break;
|
||||
}
|
||||
}
|
||||
|
||||
return best_idx;
|
||||
}
|
||||
|
||||
void pxl8_colormap_generate(pxl8_colormap* cm, const u32* palette, const pxl8_level_tint* tint) {
|
||||
if (!cm || !palette) return;
|
||||
|
||||
u8 dark_r, dark_g, dark_b;
|
||||
if (tint && tint->tint_strength > 0.0f) {
|
||||
f32 t = tint->tint_strength;
|
||||
f32 inv = 1.0f - t;
|
||||
dark_r = (u8)(tint->dark_r * inv + tint->tint_r * t);
|
||||
dark_g = (u8)(tint->dark_g * inv + tint->tint_g * t);
|
||||
dark_b = (u8)(tint->dark_b * inv + tint->tint_b * t);
|
||||
} else if (tint) {
|
||||
dark_r = tint->dark_r;
|
||||
dark_g = tint->dark_g;
|
||||
dark_b = tint->dark_b;
|
||||
} else {
|
||||
dark_r = dark_g = dark_b = 0;
|
||||
}
|
||||
|
||||
for (u32 light = 0; light < PXL8_LIGHT_LEVELS; light++) {
|
||||
f32 brightness = (f32)light / (f32)(PXL8_LIGHT_LEVELS - 1);
|
||||
|
||||
for (u32 pal_idx = 0; pal_idx < 256; pal_idx++) {
|
||||
u8 result_idx;
|
||||
|
||||
if (pal_idx == PXL8_TRANSPARENT) {
|
||||
result_idx = PXL8_TRANSPARENT;
|
||||
} else if (pal_idx >= PXL8_FULLBRIGHT_START) {
|
||||
result_idx = (u8)pal_idx;
|
||||
} else {
|
||||
u32 c = palette[pal_idx];
|
||||
u8 r = c & 0xFF;
|
||||
u8 g = (c >> 8) & 0xFF;
|
||||
u8 b = (c >> 16) & 0xFF;
|
||||
|
||||
u8 target_r = (u8)(dark_r + (r - dark_r) * brightness);
|
||||
u8 target_g = (u8)(dark_g + (g - dark_g) * brightness);
|
||||
u8 target_b = (u8)(dark_b + (b - dark_b) * brightness);
|
||||
|
||||
result_idx = find_closest_color(palette, target_r, target_g, target_b);
|
||||
}
|
||||
|
||||
cm->table[light * 256 + pal_idx] = result_idx;
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src/gfx/pxl8_colormap.h
Normal file
43
src/gfx/pxl8_colormap.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_dither.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define PXL8_LIGHT_LEVELS 64
|
||||
#define PXL8_COLORMAP_SIZE (256 * PXL8_LIGHT_LEVELS)
|
||||
|
||||
#define PXL8_FULLBRIGHT_START 240
|
||||
#define PXL8_TRANSPARENT 0
|
||||
#define PXL8_DYNAMIC_RANGE_START 144
|
||||
#define PXL8_DYNAMIC_RANGE_COUNT 16
|
||||
|
||||
typedef struct {
|
||||
u8 table[PXL8_COLORMAP_SIZE];
|
||||
} pxl8_colormap;
|
||||
|
||||
typedef struct {
|
||||
u8 dark_r, dark_g, dark_b;
|
||||
u8 tint_r, tint_g, tint_b;
|
||||
f32 tint_strength;
|
||||
} pxl8_level_tint;
|
||||
|
||||
void pxl8_colormap_generate(pxl8_colormap* cm, const u32* palette, const pxl8_level_tint* tint);
|
||||
|
||||
static inline u8 pxl8_colormap_lookup(const pxl8_colormap* cm, u8 pal_idx, u8 light) {
|
||||
u32 light_idx = light >> 2;
|
||||
return cm->table[light_idx * 256 + pal_idx];
|
||||
}
|
||||
|
||||
static inline u8 pxl8_colormap_lookup_dithered(const pxl8_colormap* cm, u8 pal_idx, u8 light, u32 x, u32 y) {
|
||||
u8 dithered = pxl8_dither_light(light, x, y);
|
||||
u32 light_idx = dithered >> 2;
|
||||
return cm->table[light_idx * 256 + pal_idx];
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
1490
src/gfx/pxl8_cpu.c
Normal file
1490
src/gfx/pxl8_cpu.c
Normal file
File diff suppressed because it is too large
Load diff
97
src/gfx/pxl8_cpu.h
Normal file
97
src/gfx/pxl8_cpu.h
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_atlas.h"
|
||||
#include "pxl8_blend.h"
|
||||
#include "pxl8_colormap.h"
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_gfx3d.h"
|
||||
#include "pxl8_math.h"
|
||||
#include "pxl8_mesh.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct pxl8_cpu_backend pxl8_cpu_backend;
|
||||
typedef struct pxl8_cpu_render_target pxl8_cpu_render_target;
|
||||
|
||||
typedef struct pxl8_cpu_render_target_desc {
|
||||
u32 width;
|
||||
u32 height;
|
||||
bool with_depth;
|
||||
bool with_lighting;
|
||||
} pxl8_cpu_render_target_desc;
|
||||
|
||||
pxl8_cpu_backend* pxl8_cpu_create(u32 width, u32 height);
|
||||
void pxl8_cpu_destroy(pxl8_cpu_backend* cpu);
|
||||
|
||||
void pxl8_cpu_begin_frame(pxl8_cpu_backend* cpu, const pxl8_3d_frame* frame);
|
||||
void pxl8_cpu_end_frame(pxl8_cpu_backend* cpu);
|
||||
|
||||
void pxl8_cpu_clear(pxl8_cpu_backend* cpu, u8 color);
|
||||
void pxl8_cpu_clear_depth(pxl8_cpu_backend* cpu);
|
||||
|
||||
void pxl8_cpu_set_colormap(pxl8_cpu_backend* cpu, const pxl8_colormap* cm);
|
||||
void pxl8_cpu_set_palette(pxl8_cpu_backend* cpu, const u32* palette);
|
||||
|
||||
void pxl8_cpu_draw_pixel(pxl8_cpu_backend* cpu, i32 x, i32 y, u8 color);
|
||||
u8 pxl8_cpu_get_pixel(pxl8_cpu_backend* cpu, i32 x, i32 y);
|
||||
void pxl8_cpu_draw_line_2d(pxl8_cpu_backend* cpu, i32 x0, i32 y0, i32 x1, i32 y1, u8 color);
|
||||
void pxl8_cpu_draw_rect(pxl8_cpu_backend* cpu, i32 x, i32 y, i32 w, i32 h, u8 color);
|
||||
void pxl8_cpu_draw_rect_fill(pxl8_cpu_backend* cpu, i32 x, i32 y, i32 w, i32 h, u8 color);
|
||||
void pxl8_cpu_draw_circle(pxl8_cpu_backend* cpu, i32 cx, i32 cy, i32 radius, u8 color);
|
||||
void pxl8_cpu_draw_circle_fill(pxl8_cpu_backend* cpu, i32 cx, i32 cy, i32 radius, u8 color);
|
||||
void pxl8_cpu_draw_line_3d(pxl8_cpu_backend* cpu, pxl8_vec3 v0, pxl8_vec3 v1, u8 color);
|
||||
|
||||
void pxl8_cpu_draw_mesh(
|
||||
pxl8_cpu_backend* cpu,
|
||||
const pxl8_mesh* mesh,
|
||||
const pxl8_mat4* model,
|
||||
const pxl8_material* material,
|
||||
const pxl8_atlas* textures
|
||||
);
|
||||
|
||||
void pxl8_cpu_draw_mesh_wireframe(
|
||||
pxl8_cpu_backend* cpu,
|
||||
const pxl8_mesh* mesh,
|
||||
pxl8_mat4 model,
|
||||
u8 color
|
||||
);
|
||||
|
||||
u8* pxl8_cpu_get_framebuffer(pxl8_cpu_backend* cpu);
|
||||
u32* pxl8_cpu_get_output(pxl8_cpu_backend* cpu);
|
||||
u32 pxl8_cpu_get_height(const pxl8_cpu_backend* cpu);
|
||||
u32 pxl8_cpu_get_width(const pxl8_cpu_backend* cpu);
|
||||
|
||||
void pxl8_cpu_render_glows(
|
||||
pxl8_cpu_backend* cpu,
|
||||
const pxl8_glow_source* glows,
|
||||
u32 glow_count,
|
||||
const pxl8_additive_table* additive,
|
||||
const pxl8_palette_cube* palette_cube,
|
||||
const pxl8_overbright_table* overbright
|
||||
);
|
||||
|
||||
void pxl8_cpu_resolve(pxl8_cpu_backend* cpu);
|
||||
|
||||
pxl8_cpu_render_target* pxl8_cpu_create_render_target(const pxl8_cpu_render_target_desc* desc);
|
||||
void pxl8_cpu_destroy_render_target(pxl8_cpu_render_target* target);
|
||||
|
||||
pxl8_cpu_render_target* pxl8_cpu_get_target(pxl8_cpu_backend* cpu);
|
||||
void pxl8_cpu_set_target(pxl8_cpu_backend* cpu, pxl8_cpu_render_target* target);
|
||||
|
||||
void pxl8_cpu_blit(pxl8_cpu_backend* cpu, pxl8_cpu_render_target* src, i32 x, i32 y, u8 transparent_idx);
|
||||
|
||||
bool pxl8_cpu_push_target(pxl8_cpu_backend* cpu);
|
||||
void pxl8_cpu_pop_target(pxl8_cpu_backend* cpu);
|
||||
u32 pxl8_cpu_get_target_depth(const pxl8_cpu_backend* cpu);
|
||||
|
||||
u8* pxl8_cpu_render_target_get_framebuffer(pxl8_cpu_render_target* target);
|
||||
u32 pxl8_cpu_render_target_get_height(const pxl8_cpu_render_target* target);
|
||||
u32 pxl8_cpu_render_target_get_width(const pxl8_cpu_render_target* target);
|
||||
u32* pxl8_cpu_render_target_get_light_accum(pxl8_cpu_render_target* target);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
8
src/gfx/pxl8_dither.c
Normal file
8
src/gfx/pxl8_dither.c
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#include "pxl8_dither.h"
|
||||
|
||||
const u8 PXL8_BAYER_4X4[16] = {
|
||||
0, 8, 2, 10,
|
||||
12, 4, 14, 6,
|
||||
3, 11, 1, 9,
|
||||
15, 7, 13, 5,
|
||||
};
|
||||
41
src/gfx/pxl8_dither.h
Normal file
41
src/gfx/pxl8_dither.h
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern const u8 PXL8_BAYER_4X4[16];
|
||||
|
||||
static inline i8 pxl8_bayer_offset(u32 x, u32 y) {
|
||||
return (i8)(PXL8_BAYER_4X4[(y & 3) * 4 + (x & 3)]) - 8;
|
||||
}
|
||||
|
||||
static inline u8 pxl8_ordered_dither(u8 value, u32 x, u32 y) {
|
||||
u8 bayer = PXL8_BAYER_4X4[(y & 3) * 4 + (x & 3)];
|
||||
u16 result = (u16)value + (bayer >> 1);
|
||||
return result > 255 ? 255 : (u8)result;
|
||||
}
|
||||
|
||||
static inline u8 pxl8_dither_light(u8 light, u32 x, u32 y) {
|
||||
i8 offset = pxl8_bayer_offset(x, y) >> 1;
|
||||
i16 result = (i16)light + offset;
|
||||
if (result < 0) return 0;
|
||||
if (result > 255) return 255;
|
||||
return (u8)result;
|
||||
}
|
||||
|
||||
static inline u8 pxl8_dither_float(f32 value, u32 x, u32 y) {
|
||||
u8 floor_val = (u8)value;
|
||||
f32 frac = value - (f32)floor_val;
|
||||
f32 threshold = (PXL8_BAYER_4X4[(y & 3) * 4 + (x & 3)] + 0.5f) * (1.0f / 16.0f);
|
||||
if (frac > threshold) {
|
||||
return floor_val < 255 ? floor_val + 1 : 255;
|
||||
}
|
||||
return floor_val;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
61
src/gfx/pxl8_font.c
Normal file
61
src/gfx/pxl8_font.c
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
#include "pxl8_font.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
pxl8_result pxl8_font_create_atlas(const pxl8_font* font, u8** atlas_data, i32* atlas_width, i32* atlas_height) {
|
||||
if (!font || !atlas_data || !atlas_width || !atlas_height) {
|
||||
return PXL8_ERROR_NULL_POINTER;
|
||||
}
|
||||
|
||||
i32 glyphs_per_row = 16;
|
||||
i32 rows_needed = (font->glyph_count + glyphs_per_row - 1) / glyphs_per_row;
|
||||
|
||||
*atlas_width = glyphs_per_row * font->default_width;
|
||||
*atlas_height = rows_needed * font->default_height;
|
||||
|
||||
i32 atlas_size = (*atlas_width) * (*atlas_height);
|
||||
*atlas_data = (u8*)malloc(atlas_size);
|
||||
if (!*atlas_data) {
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
memset(*atlas_data, 0, atlas_size);
|
||||
|
||||
for (u32 i = 0; i < font->glyph_count; i++) {
|
||||
const pxl8_glyph* glyph = &font->glyphs[i];
|
||||
|
||||
i32 atlas_x = (i % glyphs_per_row) * font->default_width;
|
||||
i32 atlas_y = (i / glyphs_per_row) * font->default_height;
|
||||
|
||||
for (i32 y = 0; y < glyph->height && y < font->default_height; y++) {
|
||||
for (i32 x = 0; x < glyph->width && x < font->default_width; x++) {
|
||||
i32 atlas_idx = (atlas_y + y) * (*atlas_width) + (atlas_x + x);
|
||||
|
||||
if (glyph->format == PXL8_FONT_FORMAT_INDEXED) {
|
||||
u8 pixel_byte = glyph->data.indexed[y];
|
||||
u8 pixel_bit = (pixel_byte >> x) & 1;
|
||||
(*atlas_data)[atlas_idx] = pixel_bit ? 255 : 0;
|
||||
} else {
|
||||
i32 glyph_idx = y * 8 + x;
|
||||
u32 rgba_pixel = glyph->data.rgba[glyph_idx];
|
||||
(*atlas_data)[atlas_idx] = (rgba_pixel >> 24) & 0xFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
const pxl8_glyph* pxl8_font_find_glyph(const pxl8_font* font, u32 codepoint) {
|
||||
if (!font || !font->glyphs) return NULL;
|
||||
|
||||
for (u32 i = 0; i < font->glyph_count; i++) {
|
||||
if (font->glyphs[i].codepoint == codepoint) {
|
||||
return &font->glyphs[i];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
130
src/gfx/pxl8_font.h
Normal file
130
src/gfx/pxl8_font.h
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#define PXL8_FONT_FORMAT_INDEXED 0
|
||||
#define PXL8_FONT_FORMAT_RGBA 1
|
||||
|
||||
typedef struct pxl8_glyph {
|
||||
u32 codepoint;
|
||||
u8 width;
|
||||
u8 height;
|
||||
u8 format;
|
||||
union {
|
||||
u8 indexed[64];
|
||||
u32 rgba[64];
|
||||
} data;
|
||||
} pxl8_glyph;
|
||||
|
||||
typedef struct pxl8_font {
|
||||
const pxl8_glyph* glyphs;
|
||||
u32 glyph_count;
|
||||
u8 default_width;
|
||||
u8 default_height;
|
||||
} pxl8_font;
|
||||
|
||||
static const pxl8_glyph pxl8_ascii_glyphs[] = {
|
||||
{ 32, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } },
|
||||
{ 33, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00 } } },
|
||||
{ 34, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } },
|
||||
{ 35, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00 } } },
|
||||
{ 36, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00 } } },
|
||||
{ 37, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00 } } },
|
||||
{ 38, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00 } } },
|
||||
{ 39, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 } } },
|
||||
{ 40, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00 } } },
|
||||
{ 41, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00 } } },
|
||||
{ 42, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00 } } },
|
||||
{ 43, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00 } } },
|
||||
{ 44, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x06, 0x00 } } },
|
||||
{ 45, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00 } } },
|
||||
{ 46, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00 } } },
|
||||
{ 47, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00 } } },
|
||||
{ 48, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00 } } },
|
||||
{ 49, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00 } } },
|
||||
{ 50, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00 } } },
|
||||
{ 51, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00 } } },
|
||||
{ 52, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00 } } },
|
||||
{ 53, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00 } } },
|
||||
{ 54, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00 } } },
|
||||
{ 55, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00 } } },
|
||||
{ 56, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00 } } },
|
||||
{ 57, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00 } } },
|
||||
{ 58, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00 } } },
|
||||
{ 59, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x06, 0x00 } } },
|
||||
{ 60, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00 } } },
|
||||
{ 61, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00 } } },
|
||||
{ 62, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00 } } },
|
||||
{ 63, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00 } } },
|
||||
{ 64, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00 } } },
|
||||
{ 65, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00 } } },
|
||||
{ 66, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00 } } },
|
||||
{ 67, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00 } } },
|
||||
{ 68, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00 } } },
|
||||
{ 69, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00 } } },
|
||||
{ 70, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00 } } },
|
||||
{ 71, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00 } } },
|
||||
{ 72, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00 } } },
|
||||
{ 73, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00 } } },
|
||||
{ 74, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00 } } },
|
||||
{ 75, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00 } } },
|
||||
{ 76, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00 } } },
|
||||
{ 77, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00 } } },
|
||||
{ 78, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00 } } },
|
||||
{ 79, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00 } } },
|
||||
{ 80, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00 } } },
|
||||
{ 81, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00 } } },
|
||||
{ 82, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00 } } },
|
||||
{ 83, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00 } } },
|
||||
{ 84, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00 } } },
|
||||
{ 85, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00 } } },
|
||||
{ 86, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00 } } },
|
||||
{ 87, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00 } } },
|
||||
{ 88, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00 } } },
|
||||
{ 89, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00 } } },
|
||||
{ 90, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00 } } },
|
||||
{ 97, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00 } } },
|
||||
{ 98, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00 } } },
|
||||
{ 99, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00 } } },
|
||||
{ 100, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x38, 0x30, 0x30, 0x3E, 0x33, 0x33, 0x6E, 0x00 } } },
|
||||
{ 101, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x1E, 0x33, 0x3F, 0x03, 0x1E, 0x00 } } },
|
||||
{ 102, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x1C, 0x36, 0x06, 0x0F, 0x06, 0x06, 0x0F, 0x00 } } },
|
||||
{ 103, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F } } },
|
||||
{ 104, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00 } } },
|
||||
{ 105, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00 } } },
|
||||
{ 106, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E } } },
|
||||
{ 107, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00 } } },
|
||||
{ 108, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00 } } },
|
||||
{ 109, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00 } } },
|
||||
{ 110, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00 } } },
|
||||
{ 111, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00 } } },
|
||||
{ 112, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F } } },
|
||||
{ 113, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78 } } },
|
||||
{ 114, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00 } } },
|
||||
{ 115, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00 } } },
|
||||
{ 116, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00 } } },
|
||||
{ 117, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00 } } },
|
||||
{ 118, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00 } } },
|
||||
{ 119, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00 } } },
|
||||
{ 120, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00 } } },
|
||||
{ 121, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F } } },
|
||||
{ 122, 8, 8, PXL8_FONT_FORMAT_INDEXED, { .indexed = { 0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00 } } }
|
||||
};
|
||||
|
||||
static const pxl8_font pxl8_default_font = {
|
||||
pxl8_ascii_glyphs,
|
||||
sizeof(pxl8_ascii_glyphs) / sizeof(pxl8_glyph),
|
||||
8,
|
||||
8
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_result pxl8_font_create_atlas(const pxl8_font* font, u8** atlas_data, i32* atlas_width, i32* atlas_height);
|
||||
const pxl8_glyph* pxl8_font_find_glyph(const pxl8_font* font, u32 codepoint);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
798
src/gfx/pxl8_gfx.c
Normal file
798
src/gfx/pxl8_gfx.c
Normal file
|
|
@ -0,0 +1,798 @@
|
|||
#include "pxl8_gfx.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_ase.h"
|
||||
#include "pxl8_atlas.h"
|
||||
#include "pxl8_backend.h"
|
||||
#include "pxl8_blend.h"
|
||||
#include "pxl8_blit.h"
|
||||
#include "pxl8_color.h"
|
||||
#include "pxl8_colormap.h"
|
||||
#include "pxl8_font.h"
|
||||
#include "pxl8_hal.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_macros.h"
|
||||
#include "pxl8_math.h"
|
||||
#include "pxl8_sys.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_sprite_cache_entry {
|
||||
char path[256];
|
||||
u32 sprite_id;
|
||||
bool active;
|
||||
} pxl8_sprite_cache_entry;
|
||||
|
||||
struct pxl8_gfx {
|
||||
pxl8_additive_table* additive_table;
|
||||
pxl8_atlas* atlas;
|
||||
pxl8_gfx_backend backend;
|
||||
pxl8_colormap* colormap;
|
||||
u8* framebuffer;
|
||||
i32 framebuffer_height;
|
||||
i32 framebuffer_width;
|
||||
pxl8_frustum frustum;
|
||||
const pxl8_hal* hal;
|
||||
bool initialized;
|
||||
pxl8_overbright_table* overbright_table;
|
||||
pxl8_palette* palette;
|
||||
pxl8_palette_cube* palette_cube;
|
||||
pxl8_pixel_mode pixel_mode;
|
||||
void* platform_data;
|
||||
pxl8_sprite_cache_entry* sprite_cache;
|
||||
u32 sprite_cache_capacity;
|
||||
u32 sprite_cache_count;
|
||||
pxl8_viewport viewport;
|
||||
};
|
||||
|
||||
pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx) {
|
||||
pxl8_bounds bounds = {0};
|
||||
if (!gfx) {
|
||||
return bounds;
|
||||
}
|
||||
bounds.w = gfx->framebuffer_width;
|
||||
bounds.h = gfx->framebuffer_height;
|
||||
return bounds;
|
||||
}
|
||||
|
||||
pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx) {
|
||||
return gfx ? gfx->pixel_mode : PXL8_PIXEL_INDEXED;
|
||||
}
|
||||
|
||||
u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx) {
|
||||
if (!gfx || gfx->pixel_mode == PXL8_PIXEL_HICOLOR) return NULL;
|
||||
return gfx->framebuffer;
|
||||
}
|
||||
|
||||
u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx) {
|
||||
if (!gfx || gfx->pixel_mode != PXL8_PIXEL_HICOLOR) return NULL;
|
||||
return (u16*)gfx->framebuffer;
|
||||
}
|
||||
|
||||
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_palette* pxl8_gfx_get_palette(pxl8_gfx* gfx) {
|
||||
return gfx ? gfx->palette : NULL;
|
||||
}
|
||||
|
||||
u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color) {
|
||||
if (!gfx || !gfx->palette) return 0;
|
||||
if (color <= 0xFFFFFF) color = (color << 8) | 0xFF;
|
||||
u8 r = (color >> 24) & 0xFF;
|
||||
u8 g = (color >> 16) & 0xFF;
|
||||
u8 b = (color >> 8) & 0xFF;
|
||||
return pxl8_palette_find_closest(gfx->palette, r, g, b);
|
||||
}
|
||||
|
||||
void pxl8_gfx_set_palette(pxl8_gfx* gfx, pxl8_palette* pal) {
|
||||
if (!gfx) return;
|
||||
if (gfx->palette) {
|
||||
pxl8_palette_destroy(gfx->palette);
|
||||
}
|
||||
gfx->palette = pal;
|
||||
|
||||
if (gfx->palette && gfx->pixel_mode != PXL8_PIXEL_HICOLOR) {
|
||||
u32* colors = pxl8_palette_colors(gfx->palette);
|
||||
if (gfx->colormap) {
|
||||
pxl8_colormap_generate(gfx->colormap, colors, NULL);
|
||||
}
|
||||
if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) {
|
||||
pxl8_cpu_set_palette(gfx->backend.cpu, colors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath) {
|
||||
if (!gfx || !filepath) return -1;
|
||||
pxl8_palette* pal = gfx->palette;
|
||||
if (!pal) return -1;
|
||||
pxl8_result result = pxl8_palette_load_ase(pal, filepath);
|
||||
if (result != PXL8_OK) return (i32)result;
|
||||
if (gfx->pixel_mode != PXL8_PIXEL_HICOLOR) {
|
||||
u32* colors = pxl8_palette_colors(pal);
|
||||
if (gfx->colormap) {
|
||||
pxl8_colormap_generate(gfx->colormap, colors, NULL);
|
||||
}
|
||||
if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) {
|
||||
pxl8_cpu_set_palette(gfx->backend.cpu, colors);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
pxl8_gfx* pxl8_gfx_create(
|
||||
const pxl8_hal* hal,
|
||||
void* platform_data,
|
||||
pxl8_pixel_mode mode,
|
||||
pxl8_resolution resolution
|
||||
) {
|
||||
pxl8_gfx* gfx = (pxl8_gfx*)calloc(1, sizeof(pxl8_gfx));
|
||||
if (!gfx) {
|
||||
pxl8_error("Failed to allocate graphics context");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gfx->hal = hal;
|
||||
gfx->platform_data = platform_data;
|
||||
|
||||
gfx->pixel_mode = mode;
|
||||
pxl8_size size = pxl8_get_resolution_dimensions(resolution);
|
||||
gfx->framebuffer_width = size.w;
|
||||
gfx->framebuffer_height = size.h;
|
||||
|
||||
if (!gfx->platform_data) {
|
||||
pxl8_error("Platform data cannot be NULL");
|
||||
free(gfx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (mode != PXL8_PIXEL_HICOLOR) {
|
||||
gfx->palette = pxl8_palette_create();
|
||||
}
|
||||
|
||||
gfx->backend.type = PXL8_GFX_BACKEND_CPU;
|
||||
gfx->backend.cpu = pxl8_cpu_create(gfx->framebuffer_width, gfx->framebuffer_height);
|
||||
if (!gfx->backend.cpu) {
|
||||
pxl8_error("Failed to create CPU backend");
|
||||
pxl8_gfx_destroy(gfx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
gfx->framebuffer = pxl8_cpu_get_framebuffer(gfx->backend.cpu);
|
||||
|
||||
if (mode != PXL8_PIXEL_HICOLOR) {
|
||||
gfx->colormap = calloc(1, sizeof(pxl8_colormap));
|
||||
if (gfx->colormap) {
|
||||
pxl8_cpu_set_colormap(gfx->backend.cpu, gfx->colormap);
|
||||
}
|
||||
}
|
||||
|
||||
gfx->viewport.offset_x = 0;
|
||||
gfx->viewport.offset_y = 0;
|
||||
gfx->viewport.scaled_width = gfx->framebuffer_width;
|
||||
gfx->viewport.scaled_height = gfx->framebuffer_height;
|
||||
gfx->viewport.scale = 1.0f;
|
||||
|
||||
gfx->initialized = true;
|
||||
return gfx;
|
||||
}
|
||||
|
||||
void pxl8_gfx_destroy(pxl8_gfx* gfx) {
|
||||
if (!gfx) return;
|
||||
|
||||
pxl8_additive_table_destroy(gfx->additive_table);
|
||||
pxl8_atlas_destroy(gfx->atlas);
|
||||
if (gfx->backend.type == PXL8_GFX_BACKEND_CPU) {
|
||||
pxl8_cpu_destroy(gfx->backend.cpu);
|
||||
}
|
||||
free(gfx->colormap);
|
||||
pxl8_overbright_table_destroy(gfx->overbright_table);
|
||||
pxl8_palette_cube_destroy(gfx->palette_cube);
|
||||
pxl8_palette_destroy(gfx->palette);
|
||||
free(gfx->sprite_cache);
|
||||
|
||||
free(gfx);
|
||||
}
|
||||
|
||||
static pxl8_result pxl8_gfx_ensure_atlas(pxl8_gfx* gfx) {
|
||||
if (gfx->atlas) return PXL8_OK;
|
||||
gfx->atlas = pxl8_atlas_create(PXL8_DEFAULT_ATLAS_SIZE, PXL8_DEFAULT_ATLAS_SIZE, gfx->pixel_mode);
|
||||
return gfx->atlas ? PXL8_OK : PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
void pxl8_gfx_clear_textures(pxl8_gfx* gfx) {
|
||||
if (!gfx) return;
|
||||
|
||||
if (gfx->atlas) {
|
||||
pxl8_atlas_clear(gfx->atlas, 0);
|
||||
}
|
||||
|
||||
if (gfx->sprite_cache) {
|
||||
gfx->sprite_cache_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
pxl8_result result = pxl8_gfx_ensure_atlas(gfx);
|
||||
if (result != PXL8_OK) return result;
|
||||
|
||||
u32 texture_id = pxl8_atlas_add_texture(gfx->atlas, pixels, width, height, gfx->pixel_mode);
|
||||
if (texture_id == UINT32_MAX) {
|
||||
pxl8_error("Texture doesn't fit in atlas");
|
||||
return PXL8_ERROR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
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->sprite_cache) {
|
||||
gfx->sprite_cache_capacity = PXL8_DEFAULT_SPRITE_CACHE_CAPACITY;
|
||||
gfx->sprite_cache = (pxl8_sprite_cache_entry*)calloc(
|
||||
gfx->sprite_cache_capacity, sizeof(pxl8_sprite_cache_entry)
|
||||
);
|
||||
if (!gfx->sprite_cache) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < gfx->sprite_cache_count; i++) {
|
||||
if (gfx->sprite_cache[i].active && strcmp(gfx->sprite_cache[i].path, path) == 0) {
|
||||
return gfx->sprite_cache[i].sprite_id;
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_result result = pxl8_gfx_ensure_atlas(gfx);
|
||||
if (result != PXL8_OK) return result;
|
||||
|
||||
pxl8_ase_file ase_file;
|
||||
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");
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
u32 sprite_id = pxl8_atlas_add_texture(
|
||||
gfx->atlas,
|
||||
ase_file.frames[0].pixels,
|
||||
ase_file.header.width,
|
||||
ase_file.header.height,
|
||||
gfx->pixel_mode
|
||||
);
|
||||
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
|
||||
if (sprite_id == UINT32_MAX) {
|
||||
pxl8_error("Sprite doesn't fit in atlas");
|
||||
return PXL8_ERROR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
if (gfx->sprite_cache_count >= gfx->sprite_cache_capacity) {
|
||||
u32 new_capacity = gfx->sprite_cache_capacity * 2;
|
||||
pxl8_sprite_cache_entry* new_cache = (pxl8_sprite_cache_entry*)realloc(
|
||||
gfx->sprite_cache,
|
||||
new_capacity * sizeof(pxl8_sprite_cache_entry)
|
||||
);
|
||||
if (!new_cache) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
gfx->sprite_cache = new_cache;
|
||||
gfx->sprite_cache_capacity = new_capacity;
|
||||
}
|
||||
|
||||
pxl8_sprite_cache_entry* entry = &gfx->sprite_cache[gfx->sprite_cache_count++];
|
||||
entry->active = true;
|
||||
entry->sprite_id = sprite_id;
|
||||
pxl8_strncpy(entry->path, path, sizeof(entry->path));
|
||||
|
||||
return sprite_id;
|
||||
}
|
||||
|
||||
pxl8_atlas* pxl8_gfx_get_atlas(pxl8_gfx* gfx) {
|
||||
if (!gfx || !gfx->initialized) return NULL;
|
||||
|
||||
if (pxl8_gfx_ensure_atlas(gfx) != PXL8_OK) return NULL;
|
||||
return gfx->atlas;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx) {
|
||||
(void)gfx;
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx) {
|
||||
if (!gfx || !gfx->initialized || !gfx->hal) return;
|
||||
|
||||
if (gfx->backend.type == PXL8_GFX_BACKEND_CPU && gfx->pixel_mode == PXL8_PIXEL_INDEXED) {
|
||||
pxl8_cpu_resolve(gfx->backend.cpu);
|
||||
u32* output = pxl8_cpu_get_output(gfx->backend.cpu);
|
||||
if (output) {
|
||||
gfx->hal->upload_texture(
|
||||
gfx->platform_data,
|
||||
output,
|
||||
gfx->framebuffer_width,
|
||||
gfx->framebuffer_height,
|
||||
PXL8_PIXEL_RGBA,
|
||||
NULL
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
u32* colors = gfx->palette ? pxl8_palette_colors(gfx->palette) : NULL;
|
||||
gfx->hal->upload_texture(
|
||||
gfx->platform_data,
|
||||
gfx->framebuffer,
|
||||
gfx->framebuffer_width,
|
||||
gfx->framebuffer_height,
|
||||
gfx->pixel_mode,
|
||||
colors
|
||||
);
|
||||
}
|
||||
|
||||
void pxl8_gfx_present(pxl8_gfx* gfx) {
|
||||
if (!gfx || !gfx->initialized || !gfx->hal) return;
|
||||
|
||||
gfx->hal->present(gfx->platform_data);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void pxl8_2d_clear(pxl8_gfx* gfx, u32 color) {
|
||||
if (!gfx) return;
|
||||
switch (gfx->backend.type) {
|
||||
case PXL8_GFX_BACKEND_CPU:
|
||||
pxl8_cpu_clear(gfx->backend.cpu, (u8)color);
|
||||
break;
|
||||
case PXL8_GFX_BACKEND_GPU:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_2d_pixel(pxl8_gfx* gfx, i32 x, i32 y, u32 color) {
|
||||
if (!gfx) return;
|
||||
switch (gfx->backend.type) {
|
||||
case PXL8_GFX_BACKEND_CPU:
|
||||
pxl8_cpu_draw_pixel(gfx->backend.cpu, x, y, (u8)color);
|
||||
break;
|
||||
case PXL8_GFX_BACKEND_GPU:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
u32 pxl8_2d_get_pixel(pxl8_gfx* gfx, i32 x, i32 y) {
|
||||
if (!gfx) return 0;
|
||||
switch (gfx->backend.type) {
|
||||
case PXL8_GFX_BACKEND_CPU:
|
||||
return pxl8_cpu_get_pixel(gfx->backend.cpu, x, y);
|
||||
case PXL8_GFX_BACKEND_GPU:
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void pxl8_2d_line(pxl8_gfx* gfx, i32 x0, i32 y0, i32 x1, i32 y1, u32 color) {
|
||||
if (!gfx) return;
|
||||
switch (gfx->backend.type) {
|
||||
case PXL8_GFX_BACKEND_CPU:
|
||||
pxl8_cpu_draw_line_2d(gfx->backend.cpu, x0, y0, x1, y1, (u8)color);
|
||||
break;
|
||||
case PXL8_GFX_BACKEND_GPU:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_2d_rect(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color) {
|
||||
if (!gfx) return;
|
||||
switch (gfx->backend.type) {
|
||||
case PXL8_GFX_BACKEND_CPU:
|
||||
pxl8_cpu_draw_rect(gfx->backend.cpu, x, y, w, h, (u8)color);
|
||||
break;
|
||||
case PXL8_GFX_BACKEND_GPU:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_2d_rect_fill(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color) {
|
||||
if (!gfx) return;
|
||||
switch (gfx->backend.type) {
|
||||
case PXL8_GFX_BACKEND_CPU:
|
||||
pxl8_cpu_draw_rect_fill(gfx->backend.cpu, x, y, w, h, (u8)color);
|
||||
break;
|
||||
case PXL8_GFX_BACKEND_GPU:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_2d_circle(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color) {
|
||||
if (!gfx) return;
|
||||
switch (gfx->backend.type) {
|
||||
case PXL8_GFX_BACKEND_CPU:
|
||||
pxl8_cpu_draw_circle(gfx->backend.cpu, cx, cy, radius, (u8)color);
|
||||
break;
|
||||
case PXL8_GFX_BACKEND_GPU:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_2d_circle_fill(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color) {
|
||||
if (!gfx) return;
|
||||
switch (gfx->backend.type) {
|
||||
case PXL8_GFX_BACKEND_CPU:
|
||||
pxl8_cpu_draw_circle_fill(gfx->backend.cpu, cx, cy, radius, (u8)color);
|
||||
break;
|
||||
case PXL8_GFX_BACKEND_GPU:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_2d_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color) {
|
||||
if (!gfx || !text) return;
|
||||
|
||||
u8* framebuffer = NULL;
|
||||
u32* light_accum = NULL;
|
||||
i32 fb_width = 0;
|
||||
i32 fb_height = 0;
|
||||
|
||||
switch (gfx->backend.type) {
|
||||
case PXL8_GFX_BACKEND_CPU: {
|
||||
pxl8_cpu_render_target* render_target = pxl8_cpu_get_target(gfx->backend.cpu);
|
||||
if (!render_target) return;
|
||||
framebuffer = pxl8_cpu_render_target_get_framebuffer(render_target);
|
||||
light_accum = pxl8_cpu_render_target_get_light_accum(render_target);
|
||||
fb_width = (i32)pxl8_cpu_render_target_get_width(render_target);
|
||||
fb_height = (i32)pxl8_cpu_render_target_get_height(render_target);
|
||||
break;
|
||||
}
|
||||
case PXL8_GFX_BACKEND_GPU:
|
||||
return;
|
||||
}
|
||||
|
||||
if (!framebuffer) return;
|
||||
|
||||
const pxl8_font* font = &pxl8_default_font;
|
||||
i32 cursor_x = x;
|
||||
i32 cursor_y = y;
|
||||
|
||||
for (const char* c = text; *c; c++) {
|
||||
const pxl8_glyph* glyph = pxl8_font_find_glyph(font, (u32)*c);
|
||||
if (!glyph) continue;
|
||||
|
||||
for (i32 gy = 0; gy < glyph->height; gy++) {
|
||||
for (i32 gx = 0; gx < glyph->width; gx++) {
|
||||
i32 px = cursor_x + gx;
|
||||
i32 py = cursor_y + gy;
|
||||
|
||||
if (px < 0 || px >= fb_width || py < 0 || py >= fb_height) continue;
|
||||
|
||||
u8 pixel_bit = 0;
|
||||
|
||||
if (glyph->format == PXL8_FONT_FORMAT_INDEXED) {
|
||||
u8 pixel_byte = glyph->data.indexed[gy];
|
||||
pixel_bit = (pixel_byte >> gx) & 1;
|
||||
} else {
|
||||
i32 glyph_idx = gy * 8 + gx;
|
||||
u32 rgba_pixel = glyph->data.rgba[glyph_idx];
|
||||
pixel_bit = ((rgba_pixel >> 24) & 0xFF) > 128 ? 1 : 0;
|
||||
}
|
||||
|
||||
if (pixel_bit) {
|
||||
i32 idx = py * fb_width + px;
|
||||
framebuffer[idx] = (u8)color;
|
||||
if (light_accum) light_accum[idx] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cursor_x += font->default_width;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bool flip_x, bool flip_y) {
|
||||
if (!gfx || !gfx->atlas) return;
|
||||
|
||||
u8* framebuffer = NULL;
|
||||
i32 fb_width = 0;
|
||||
i32 fb_height = 0;
|
||||
|
||||
switch (gfx->backend.type) {
|
||||
case PXL8_GFX_BACKEND_CPU: {
|
||||
pxl8_cpu_render_target* render_target = pxl8_cpu_get_target(gfx->backend.cpu);
|
||||
if (!render_target) return;
|
||||
framebuffer = pxl8_cpu_render_target_get_framebuffer(render_target);
|
||||
fb_width = (i32)pxl8_cpu_render_target_get_width(render_target);
|
||||
fb_height = (i32)pxl8_cpu_render_target_get_height(render_target);
|
||||
break;
|
||||
}
|
||||
case PXL8_GFX_BACKEND_GPU:
|
||||
return;
|
||||
}
|
||||
|
||||
if (!framebuffer) return;
|
||||
|
||||
const pxl8_atlas_entry* entry = pxl8_atlas_get_entry(gfx->atlas, sprite_id);
|
||||
if (!entry || !entry->active) return;
|
||||
|
||||
i32 clip_left = (x < 0) ? -x : 0;
|
||||
i32 clip_top = (y < 0) ? -y : 0;
|
||||
i32 clip_right = (x + w > fb_width) ? x + w - fb_width : 0;
|
||||
i32 clip_bottom = (y + h > fb_height) ? y + h - fb_height : 0;
|
||||
|
||||
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);
|
||||
bool is_flipped = flip_x || flip_y;
|
||||
|
||||
u32 atlas_width = pxl8_atlas_get_width(gfx->atlas);
|
||||
const u8* atlas_pixels = pxl8_atlas_get_pixels(gfx->atlas);
|
||||
|
||||
if (is_1to1_scale && is_unclipped && !is_flipped) {
|
||||
const u8* sprite_data = atlas_pixels + entry->y * atlas_width + entry->x;
|
||||
pxl8_blit_indexed(framebuffer, fb_width, sprite_data, atlas_width, x, y, w, h);
|
||||
} else {
|
||||
for (i32 py = 0; py < draw_height; py++) {
|
||||
for (i32 px = 0; px < draw_width; px++) {
|
||||
i32 local_x = (px + clip_left) * entry->w / w;
|
||||
i32 local_y = (py + clip_top) * entry->h / h;
|
||||
|
||||
i32 src_x = flip_x ? entry->x + entry->w - 1 - local_x : entry->x + local_x;
|
||||
i32 src_y = flip_y ? entry->y + entry->h - 1 - local_y : entry->y + local_y;
|
||||
|
||||
i32 src_idx = src_y * atlas_width + src_x;
|
||||
i32 dest_idx = (dest_y + py) * fb_width + (dest_x + px);
|
||||
|
||||
framebuffer[dest_idx] = pxl8_blend_indexed(atlas_pixels[src_idx], framebuffer[dest_idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_gfx_update(pxl8_gfx* gfx, f32 dt) {
|
||||
if (!gfx) return;
|
||||
|
||||
if (gfx->palette) {
|
||||
u16 delta_ticks = (u16)(dt * 1000.0f);
|
||||
pxl8_palette_tick(gfx->palette, delta_ticks);
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms) {
|
||||
pxl8_3d_frame frame = {0};
|
||||
if (!camera) return frame;
|
||||
|
||||
frame.view = pxl8_3d_camera_get_view(camera);
|
||||
frame.projection = pxl8_3d_camera_get_projection(camera);
|
||||
frame.camera_pos = pxl8_3d_camera_get_position(camera);
|
||||
frame.camera_dir = pxl8_3d_camera_get_forward(camera);
|
||||
frame.near_clip = 1.0f;
|
||||
frame.far_clip = 4096.0f;
|
||||
|
||||
if (uniforms) {
|
||||
frame.uniforms = *uniforms;
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms) {
|
||||
if (!gfx || !camera) return;
|
||||
|
||||
pxl8_3d_frame frame = pxl8_3d_frame_from_camera(camera, uniforms);
|
||||
|
||||
pxl8_mat4 vp = pxl8_mat4_mul(frame.projection, frame.view);
|
||||
gfx->frustum = pxl8_frustum_from_matrix(vp);
|
||||
|
||||
switch (gfx->backend.type) {
|
||||
case PXL8_GFX_BACKEND_CPU:
|
||||
pxl8_cpu_begin_frame(gfx->backend.cpu, &frame);
|
||||
break;
|
||||
case PXL8_GFX_BACKEND_GPU:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const pxl8_frustum* pxl8_3d_get_frustum(pxl8_gfx* gfx) {
|
||||
if (!gfx) return NULL;
|
||||
return &gfx->frustum;
|
||||
}
|
||||
|
||||
void pxl8_3d_clear(pxl8_gfx* gfx, u8 color) {
|
||||
if (!gfx) return;
|
||||
switch (gfx->backend.type) {
|
||||
case PXL8_GFX_BACKEND_CPU:
|
||||
pxl8_cpu_clear(gfx->backend.cpu, color);
|
||||
break;
|
||||
case PXL8_GFX_BACKEND_GPU:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_3d_clear_depth(pxl8_gfx* gfx) {
|
||||
if (!gfx) return;
|
||||
switch (gfx->backend.type) {
|
||||
case PXL8_GFX_BACKEND_CPU:
|
||||
pxl8_cpu_clear_depth(gfx->backend.cpu);
|
||||
break;
|
||||
case PXL8_GFX_BACKEND_GPU:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color) {
|
||||
if (!gfx) return;
|
||||
switch (gfx->backend.type) {
|
||||
case PXL8_GFX_BACKEND_CPU:
|
||||
pxl8_cpu_draw_line_3d(gfx->backend.cpu, v0, v1, color);
|
||||
break;
|
||||
case PXL8_GFX_BACKEND_GPU:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_material* material) {
|
||||
if (!gfx || !mesh || !model || !material) return;
|
||||
switch (gfx->backend.type) {
|
||||
case PXL8_GFX_BACKEND_CPU:
|
||||
pxl8_cpu_draw_mesh(gfx->backend.cpu, mesh, model, material, gfx->atlas);
|
||||
break;
|
||||
case PXL8_GFX_BACKEND_GPU:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_3d_draw_mesh_wireframe(pxl8_gfx* gfx, const pxl8_mesh* mesh, pxl8_mat4 model, u8 color) {
|
||||
if (!gfx || !mesh) return;
|
||||
switch (gfx->backend.type) {
|
||||
case PXL8_GFX_BACKEND_CPU:
|
||||
pxl8_cpu_draw_mesh_wireframe(gfx->backend.cpu, mesh, model, color);
|
||||
break;
|
||||
case PXL8_GFX_BACKEND_GPU:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_3d_end_frame(pxl8_gfx* gfx) {
|
||||
if (!gfx) return;
|
||||
switch (gfx->backend.type) {
|
||||
case PXL8_GFX_BACKEND_CPU:
|
||||
pxl8_cpu_end_frame(gfx->backend.cpu);
|
||||
break;
|
||||
case PXL8_GFX_BACKEND_GPU:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
u8* pxl8_3d_get_framebuffer(pxl8_gfx* gfx) {
|
||||
if (!gfx) return NULL;
|
||||
switch (gfx->backend.type) {
|
||||
case PXL8_GFX_BACKEND_CPU:
|
||||
return pxl8_cpu_get_framebuffer(gfx->backend.cpu);
|
||||
case PXL8_GFX_BACKEND_GPU:
|
||||
return NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool pxl8_gfx_push_target(pxl8_gfx* gfx) {
|
||||
if (!gfx) return false;
|
||||
switch (gfx->backend.type) {
|
||||
case PXL8_GFX_BACKEND_CPU:
|
||||
return pxl8_cpu_push_target(gfx->backend.cpu);
|
||||
case PXL8_GFX_BACKEND_GPU:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void pxl8_gfx_pop_target(pxl8_gfx* gfx) {
|
||||
if (!gfx) return;
|
||||
switch (gfx->backend.type) {
|
||||
case PXL8_GFX_BACKEND_CPU:
|
||||
pxl8_cpu_pop_target(gfx->backend.cpu);
|
||||
break;
|
||||
case PXL8_GFX_BACKEND_GPU:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void pxl8_gfx_ensure_blend_tables(pxl8_gfx* gfx) {
|
||||
if (!gfx || !gfx->palette) return;
|
||||
|
||||
if (!gfx->additive_table) {
|
||||
gfx->additive_table = pxl8_additive_table_create(gfx->palette);
|
||||
}
|
||||
if (!gfx->overbright_table) {
|
||||
gfx->overbright_table = pxl8_overbright_table_create(gfx->palette);
|
||||
}
|
||||
if (!gfx->palette_cube) {
|
||||
gfx->palette_cube = pxl8_palette_cube_create(gfx->palette);
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_gfx_blend_tables_update(pxl8_gfx* gfx) {
|
||||
if (!gfx || !gfx->palette) return;
|
||||
|
||||
if (gfx->additive_table) {
|
||||
pxl8_additive_table_rebuild(gfx->additive_table, gfx->palette);
|
||||
}
|
||||
if (gfx->overbright_table) {
|
||||
pxl8_overbright_table_rebuild(gfx->overbright_table, gfx->palette);
|
||||
}
|
||||
if (gfx->palette_cube) {
|
||||
pxl8_palette_cube_rebuild(gfx->palette_cube, gfx->palette);
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_gfx_colormap_update(pxl8_gfx* gfx) {
|
||||
if (!gfx || !gfx->palette || !gfx->colormap) return;
|
||||
u32* colors = pxl8_palette_colors(gfx->palette);
|
||||
if (colors) {
|
||||
pxl8_colormap_generate(gfx->colormap, colors, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_gfx_apply_effect(pxl8_gfx* gfx, pxl8_gfx_effect effect, const void* params, u32 count) {
|
||||
if (!gfx || !params || count == 0) return;
|
||||
|
||||
switch (effect) {
|
||||
case PXL8_GFX_EFFECT_GLOWS: {
|
||||
pxl8_gfx_ensure_blend_tables(gfx);
|
||||
if (!gfx->additive_table || !gfx->overbright_table || !gfx->palette_cube) return;
|
||||
|
||||
const pxl8_glow_source* glows = (const pxl8_glow_source*)params;
|
||||
switch (gfx->backend.type) {
|
||||
case PXL8_GFX_BACKEND_CPU:
|
||||
pxl8_cpu_render_glows(
|
||||
gfx->backend.cpu,
|
||||
glows,
|
||||
count,
|
||||
gfx->additive_table,
|
||||
gfx->palette_cube,
|
||||
gfx->overbright_table
|
||||
);
|
||||
break;
|
||||
case PXL8_GFX_BACKEND_GPU:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
103
src/gfx/pxl8_gfx.h
Normal file
103
src/gfx/pxl8_gfx.h
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_gfx2d.h"
|
||||
#include "pxl8_gfx3d.h"
|
||||
#include "pxl8_hal.h"
|
||||
#include "pxl8_palette.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_gfx pxl8_gfx;
|
||||
|
||||
typedef enum pxl8_gfx_effect {
|
||||
PXL8_GFX_EFFECT_GLOWS = 0,
|
||||
} pxl8_gfx_effect;
|
||||
|
||||
#define PXL8_MAX_GLOWS 256
|
||||
|
||||
typedef enum pxl8_glow_shape {
|
||||
PXL8_GLOW_CIRCLE = 0,
|
||||
PXL8_GLOW_DIAMOND = 1,
|
||||
PXL8_GLOW_SHAFT = 2,
|
||||
} pxl8_glow_shape;
|
||||
|
||||
typedef struct pxl8_glow_source {
|
||||
u8 color;
|
||||
u16 depth;
|
||||
u8 height;
|
||||
u16 intensity;
|
||||
u8 radius;
|
||||
pxl8_glow_shape shape;
|
||||
i16 x;
|
||||
i16 y;
|
||||
} pxl8_glow_source;
|
||||
|
||||
static inline pxl8_glow_source pxl8_glow_create(i32 x, i32 y, u8 radius, u16 intensity, u8 color) {
|
||||
return (pxl8_glow_source){
|
||||
.color = color,
|
||||
.depth = 0xFFFF,
|
||||
.height = 0,
|
||||
.intensity = intensity,
|
||||
.radius = radius,
|
||||
.shape = PXL8_GLOW_CIRCLE,
|
||||
.x = (i16)x,
|
||||
.y = (i16)y,
|
||||
};
|
||||
}
|
||||
|
||||
static inline pxl8_glow_source pxl8_glow_with_depth(pxl8_glow_source g, u16 depth) {
|
||||
g.depth = depth;
|
||||
return g;
|
||||
}
|
||||
|
||||
static inline pxl8_glow_source pxl8_glow_with_shape(pxl8_glow_source g, pxl8_glow_shape shape) {
|
||||
g.shape = shape;
|
||||
return g;
|
||||
}
|
||||
|
||||
static inline pxl8_glow_source pxl8_glow_with_height(pxl8_glow_source g, u8 height) {
|
||||
g.height = height;
|
||||
return g;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_gfx* pxl8_gfx_create(const pxl8_hal* hal, void* platform_data, pxl8_pixel_mode mode, pxl8_resolution resolution);
|
||||
void pxl8_gfx_destroy(pxl8_gfx* gfx);
|
||||
|
||||
void pxl8_gfx_present(pxl8_gfx* gfx);
|
||||
void pxl8_gfx_update(pxl8_gfx* gfx, f32 dt);
|
||||
void pxl8_gfx_upload_framebuffer(pxl8_gfx* gfx);
|
||||
|
||||
u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color);
|
||||
|
||||
pxl8_bounds pxl8_gfx_get_bounds(pxl8_gfx* gfx);
|
||||
u8* pxl8_gfx_get_framebuffer_indexed(pxl8_gfx* gfx);
|
||||
u16* pxl8_gfx_get_framebuffer_hicolor(pxl8_gfx* gfx);
|
||||
i32 pxl8_gfx_get_height(const pxl8_gfx* gfx);
|
||||
pxl8_palette* pxl8_gfx_get_palette(pxl8_gfx* gfx);
|
||||
pxl8_pixel_mode pxl8_gfx_get_pixel_mode(pxl8_gfx* gfx);
|
||||
i32 pxl8_gfx_get_width(const pxl8_gfx* gfx);
|
||||
|
||||
i32 pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* filepath);
|
||||
void pxl8_gfx_project(pxl8_gfx* gfx, f32 left, f32 right, f32 top, f32 bottom);
|
||||
void pxl8_gfx_set_palette(pxl8_gfx* gfx, pxl8_palette* pal);
|
||||
void pxl8_gfx_set_viewport(pxl8_gfx* gfx, pxl8_viewport vp);
|
||||
pxl8_viewport pxl8_gfx_viewport(pxl8_bounds bounds, i32 width, i32 height);
|
||||
|
||||
void pxl8_gfx_clear_textures(pxl8_gfx* gfx);
|
||||
pxl8_result pxl8_gfx_create_texture(pxl8_gfx* gfx, const u8* pixels, u32 width, u32 height);
|
||||
pxl8_result pxl8_gfx_load_font_atlas(pxl8_gfx* gfx);
|
||||
pxl8_result pxl8_gfx_load_sprite(pxl8_gfx* gfx, const char* path);
|
||||
|
||||
bool pxl8_gfx_push_target(pxl8_gfx* gfx);
|
||||
void pxl8_gfx_pop_target(pxl8_gfx* gfx);
|
||||
|
||||
void pxl8_gfx_apply_effect(pxl8_gfx* gfx, pxl8_gfx_effect effect, const void* params, u32 count);
|
||||
void pxl8_gfx_blend_tables_update(pxl8_gfx* gfx);
|
||||
void pxl8_gfx_colormap_update(pxl8_gfx* gfx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
24
src/gfx/pxl8_gfx2d.h
Normal file
24
src/gfx/pxl8_gfx2d.h
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_gfx pxl8_gfx;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void pxl8_2d_circle(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color);
|
||||
void pxl8_2d_circle_fill(pxl8_gfx* gfx, i32 cx, i32 cy, i32 radius, u32 color);
|
||||
void pxl8_2d_clear(pxl8_gfx* gfx, u32 color);
|
||||
u32 pxl8_2d_get_pixel(pxl8_gfx* gfx, i32 x, i32 y);
|
||||
void pxl8_2d_line(pxl8_gfx* gfx, i32 x0, i32 y0, i32 x1, i32 y1, u32 color);
|
||||
void pxl8_2d_pixel(pxl8_gfx* gfx, i32 x, i32 y, u32 color);
|
||||
void pxl8_2d_rect(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color);
|
||||
void pxl8_2d_rect_fill(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, u32 color);
|
||||
void pxl8_2d_sprite(pxl8_gfx* gfx, u32 sprite_id, i32 x, i32 y, i32 w, i32 h, bool flip_x, bool flip_y);
|
||||
void pxl8_2d_text(pxl8_gfx* gfx, const char* text, i32 x, i32 y, u32 color);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
72
src/gfx/pxl8_gfx3d.h
Normal file
72
src/gfx/pxl8_gfx3d.h
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_3d_camera.h"
|
||||
#include "pxl8_math.h"
|
||||
#include "pxl8_mesh.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_gfx pxl8_gfx;
|
||||
|
||||
#define PXL8_MAX_LIGHTS 16
|
||||
|
||||
typedef struct pxl8_light {
|
||||
pxl8_vec3 position;
|
||||
u8 r, g, b;
|
||||
u8 intensity;
|
||||
f32 radius;
|
||||
f32 radius_sq;
|
||||
f32 inv_radius_sq;
|
||||
} pxl8_light;
|
||||
|
||||
static inline pxl8_light pxl8_light_create(pxl8_vec3 pos, u8 r, u8 g, u8 b, u8 intensity, f32 radius) {
|
||||
f32 radius_sq = radius * radius;
|
||||
return (pxl8_light){
|
||||
.position = pos,
|
||||
.r = r, .g = g, .b = b,
|
||||
.intensity = intensity,
|
||||
.radius = radius,
|
||||
.radius_sq = radius_sq,
|
||||
.inv_radius_sq = radius_sq > 0.0f ? 1.0f / radius_sq : 0.0f,
|
||||
};
|
||||
}
|
||||
|
||||
typedef struct pxl8_3d_uniforms {
|
||||
u8 ambient;
|
||||
pxl8_vec3 celestial_dir;
|
||||
f32 celestial_intensity;
|
||||
u8 fog_color;
|
||||
f32 fog_density;
|
||||
pxl8_light lights[PXL8_MAX_LIGHTS];
|
||||
u32 num_lights;
|
||||
f32 time;
|
||||
} pxl8_3d_uniforms;
|
||||
|
||||
typedef struct pxl8_3d_frame {
|
||||
pxl8_3d_uniforms uniforms;
|
||||
pxl8_vec3 camera_dir;
|
||||
pxl8_vec3 camera_pos;
|
||||
f32 far_clip;
|
||||
f32 near_clip;
|
||||
pxl8_mat4 projection;
|
||||
pxl8_mat4 view;
|
||||
} pxl8_3d_frame;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms);
|
||||
void pxl8_3d_clear(pxl8_gfx* gfx, u8 color);
|
||||
void pxl8_3d_clear_depth(pxl8_gfx* gfx);
|
||||
void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 v0, pxl8_vec3 v1, u8 color);
|
||||
void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_material* material);
|
||||
void pxl8_3d_draw_mesh_wireframe(pxl8_gfx* gfx, const pxl8_mesh* mesh, pxl8_mat4 model, u8 color);
|
||||
void pxl8_3d_end_frame(pxl8_gfx* gfx);
|
||||
u8* pxl8_3d_get_framebuffer(pxl8_gfx* gfx);
|
||||
const pxl8_frustum* pxl8_3d_get_frustum(pxl8_gfx* gfx);
|
||||
|
||||
pxl8_3d_frame pxl8_3d_frame_from_camera(const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
122
src/gfx/pxl8_mesh.c
Normal file
122
src/gfx/pxl8_mesh.c
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
#include "pxl8_mesh.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
pxl8_mesh* pxl8_mesh_create(u32 vertex_capacity, u32 index_capacity) {
|
||||
if (vertex_capacity > PXL8_MESH_MAX_VERTICES) vertex_capacity = PXL8_MESH_MAX_VERTICES;
|
||||
if (index_capacity > PXL8_MESH_MAX_INDICES) index_capacity = PXL8_MESH_MAX_INDICES;
|
||||
|
||||
pxl8_mesh* mesh = calloc(1, sizeof(pxl8_mesh));
|
||||
if (!mesh) return NULL;
|
||||
|
||||
mesh->vertices = calloc(vertex_capacity, sizeof(pxl8_vertex));
|
||||
mesh->indices = calloc(index_capacity, sizeof(u16));
|
||||
|
||||
if (!mesh->vertices || !mesh->indices) {
|
||||
free(mesh->vertices);
|
||||
free(mesh->indices);
|
||||
free(mesh);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mesh->vertex_capacity = vertex_capacity;
|
||||
mesh->index_capacity = index_capacity;
|
||||
mesh->vertex_count = 0;
|
||||
mesh->index_count = 0;
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
void pxl8_mesh_destroy(pxl8_mesh* mesh) {
|
||||
if (!mesh) return;
|
||||
free(mesh->vertices);
|
||||
free(mesh->indices);
|
||||
free(mesh);
|
||||
}
|
||||
|
||||
void pxl8_mesh_clear(pxl8_mesh* mesh) {
|
||||
if (!mesh) return;
|
||||
mesh->vertex_count = 0;
|
||||
mesh->index_count = 0;
|
||||
}
|
||||
|
||||
u16 pxl8_mesh_push_vertex(pxl8_mesh* mesh, pxl8_vertex v) {
|
||||
if (!mesh || mesh->vertex_count >= mesh->vertex_capacity) return 0;
|
||||
u16 idx = (u16)mesh->vertex_count;
|
||||
mesh->vertices[mesh->vertex_count++] = v;
|
||||
return idx;
|
||||
}
|
||||
|
||||
void pxl8_mesh_push_triangle(pxl8_mesh* mesh, u16 i0, u16 i1, u16 i2) {
|
||||
if (!mesh || mesh->index_count + 3 > mesh->index_capacity) return;
|
||||
mesh->indices[mesh->index_count++] = i0;
|
||||
mesh->indices[mesh->index_count++] = i1;
|
||||
mesh->indices[mesh->index_count++] = i2;
|
||||
}
|
||||
|
||||
void pxl8_mesh_push_quad(pxl8_mesh* mesh, u16 i0, u16 i1, u16 i2, u16 i3) {
|
||||
pxl8_mesh_push_triangle(mesh, i0, i1, i2);
|
||||
pxl8_mesh_push_triangle(mesh, i0, i2, i3);
|
||||
}
|
||||
|
||||
void pxl8_mesh_push_box(pxl8_mesh* mesh, pxl8_vec3 center, pxl8_vec3 half, u8 color) {
|
||||
pxl8_mesh_push_box_uv(mesh, center, half, color, 1.0f, 1.0f);
|
||||
}
|
||||
|
||||
void pxl8_mesh_push_box_uv(pxl8_mesh* mesh, pxl8_vec3 center, pxl8_vec3 half, u8 color, f32 u_scale, f32 v_scale) {
|
||||
if (!mesh) return;
|
||||
|
||||
pxl8_vec3 corners[8] = {
|
||||
{center.x - half.x, center.y - half.y, center.z - half.z},
|
||||
{center.x + half.x, center.y - half.y, center.z - half.z},
|
||||
{center.x + half.x, center.y + half.y, center.z - half.z},
|
||||
{center.x - half.x, center.y + half.y, center.z - half.z},
|
||||
{center.x - half.x, center.y - half.y, center.z + half.z},
|
||||
{center.x + half.x, center.y - half.y, center.z + half.z},
|
||||
{center.x + half.x, center.y + half.y, center.z + half.z},
|
||||
{center.x - half.x, center.y + half.y, center.z + half.z},
|
||||
};
|
||||
|
||||
pxl8_vec3 normals[6] = {
|
||||
{ 0, 0, -1}, // front
|
||||
{ 0, 0, 1}, // back
|
||||
{-1, 0, 0}, // left
|
||||
{ 1, 0, 0}, // right
|
||||
{ 0, -1, 0}, // bottom
|
||||
{ 0, 1, 0}, // top
|
||||
};
|
||||
|
||||
i32 faces[6][4] = {
|
||||
{0, 1, 2, 3}, // front
|
||||
{5, 4, 7, 6}, // back
|
||||
{4, 0, 3, 7}, // left
|
||||
{1, 5, 6, 2}, // right
|
||||
{4, 5, 1, 0}, // bottom
|
||||
{3, 2, 6, 7}, // top
|
||||
};
|
||||
|
||||
f32 uvs[4][2] = {
|
||||
{0, v_scale},
|
||||
{u_scale, v_scale},
|
||||
{u_scale, 0},
|
||||
{0, 0},
|
||||
};
|
||||
|
||||
for (i32 face = 0; face < 6; face++) {
|
||||
u16 base = (u16)mesh->vertex_count;
|
||||
|
||||
for (i32 v = 0; v < 4; v++) {
|
||||
pxl8_vertex vert = {
|
||||
.position = corners[faces[face][v]],
|
||||
.normal = normals[face],
|
||||
.u = uvs[v][0],
|
||||
.v = uvs[v][1],
|
||||
.color = color,
|
||||
.light = 255,
|
||||
};
|
||||
pxl8_mesh_push_vertex(mesh, vert);
|
||||
}
|
||||
|
||||
pxl8_mesh_push_quad(mesh, base, base + 1, base + 2, base + 3);
|
||||
}
|
||||
}
|
||||
124
src/gfx/pxl8_mesh.h
Normal file
124
src/gfx/pxl8_mesh.h
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_math.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define PXL8_MESH_MAX_VERTICES 65536
|
||||
#define PXL8_MESH_MAX_INDICES 65536
|
||||
|
||||
typedef enum pxl8_blend_mode {
|
||||
PXL8_BLEND_OPAQUE = 0,
|
||||
PXL8_BLEND_ALPHA_TEST,
|
||||
PXL8_BLEND_ALPHA,
|
||||
PXL8_BLEND_ADDITIVE,
|
||||
} pxl8_blend_mode;
|
||||
|
||||
typedef struct pxl8_material {
|
||||
u32 texture_id;
|
||||
u8 alpha;
|
||||
u8 blend_mode;
|
||||
bool dither;
|
||||
bool double_sided;
|
||||
bool dynamic_lighting;
|
||||
bool per_pixel;
|
||||
bool vertex_color_passthrough;
|
||||
f32 emissive_intensity;
|
||||
} pxl8_material;
|
||||
|
||||
typedef struct pxl8_vertex {
|
||||
pxl8_vec3 position;
|
||||
pxl8_vec3 normal;
|
||||
f32 u, v;
|
||||
u8 color;
|
||||
u8 light;
|
||||
u8 _pad[2];
|
||||
} pxl8_vertex;
|
||||
|
||||
typedef struct pxl8_mesh {
|
||||
pxl8_vertex* vertices;
|
||||
u16* indices;
|
||||
u32 vertex_count;
|
||||
u32 index_count;
|
||||
u32 vertex_capacity;
|
||||
u32 index_capacity;
|
||||
} pxl8_mesh;
|
||||
|
||||
pxl8_mesh* pxl8_mesh_create(u32 vertex_capacity, u32 index_capacity);
|
||||
void pxl8_mesh_destroy(pxl8_mesh* mesh);
|
||||
void pxl8_mesh_clear(pxl8_mesh* mesh);
|
||||
|
||||
u16 pxl8_mesh_push_vertex(pxl8_mesh* mesh, pxl8_vertex v);
|
||||
void pxl8_mesh_push_triangle(pxl8_mesh* mesh, u16 i0, u16 i1, u16 i2);
|
||||
void pxl8_mesh_push_quad(pxl8_mesh* mesh, u16 i0, u16 i1, u16 i2, u16 i3);
|
||||
|
||||
void pxl8_mesh_push_box(pxl8_mesh* mesh, pxl8_vec3 center, pxl8_vec3 half_extents, u8 color);
|
||||
void pxl8_mesh_push_box_uv(pxl8_mesh* mesh, pxl8_vec3 center, pxl8_vec3 half_extents, u8 color, f32 u_scale, f32 v_scale);
|
||||
|
||||
static inline u32 pxl8_mesh_triangle_count(const pxl8_mesh* mesh) {
|
||||
return mesh->index_count / 3;
|
||||
}
|
||||
|
||||
static inline pxl8_material pxl8_material_create(u32 texture_id) {
|
||||
return (pxl8_material){
|
||||
.texture_id = texture_id,
|
||||
.alpha = 255,
|
||||
.blend_mode = PXL8_BLEND_OPAQUE,
|
||||
.dither = true,
|
||||
.double_sided = false,
|
||||
.dynamic_lighting = false,
|
||||
.per_pixel = false,
|
||||
.vertex_color_passthrough = false,
|
||||
.emissive_intensity = 0.0f,
|
||||
};
|
||||
}
|
||||
|
||||
static inline pxl8_material pxl8_material_with_alpha(pxl8_material m, u8 alpha) {
|
||||
m.alpha = alpha;
|
||||
if (alpha < 255 && m.blend_mode == PXL8_BLEND_OPAQUE) {
|
||||
m.blend_mode = PXL8_BLEND_ALPHA;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
static inline pxl8_material pxl8_material_with_blend(pxl8_material m, pxl8_blend_mode mode) {
|
||||
m.blend_mode = mode;
|
||||
return m;
|
||||
}
|
||||
|
||||
static inline pxl8_material pxl8_material_with_double_sided(pxl8_material m) {
|
||||
m.double_sided = true;
|
||||
return m;
|
||||
}
|
||||
|
||||
static inline pxl8_material pxl8_material_with_emissive(pxl8_material m, f32 intensity) {
|
||||
m.emissive_intensity = intensity;
|
||||
return m;
|
||||
}
|
||||
|
||||
static inline pxl8_material pxl8_material_with_lighting(pxl8_material m) {
|
||||
m.dynamic_lighting = true;
|
||||
return m;
|
||||
}
|
||||
|
||||
static inline pxl8_material pxl8_material_with_no_dither(pxl8_material m) {
|
||||
m.dither = false;
|
||||
return m;
|
||||
}
|
||||
|
||||
static inline pxl8_material pxl8_material_with_passthrough(pxl8_material m) {
|
||||
m.vertex_color_passthrough = true;
|
||||
return m;
|
||||
}
|
||||
|
||||
static inline pxl8_material pxl8_material_with_per_pixel(pxl8_material m) {
|
||||
m.per_pixel = true;
|
||||
return m;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
474
src/gfx/pxl8_palette.c
Normal file
474
src/gfx/pxl8_palette.c
Normal file
|
|
@ -0,0 +1,474 @@
|
|||
#include "pxl8_palette.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_ase.h"
|
||||
#include "pxl8_color.h"
|
||||
#include "pxl8_log.h"
|
||||
|
||||
#define PXL8_PALETTE_HASH_SIZE 512
|
||||
|
||||
typedef struct {
|
||||
u32 color;
|
||||
i16 index;
|
||||
} pxl8_palette_hash_entry;
|
||||
|
||||
struct pxl8_palette {
|
||||
u32 base_colors[PXL8_MAX_CYCLES][PXL8_MAX_CYCLE_LEN];
|
||||
u32 colors[PXL8_PALETTE_SIZE];
|
||||
u8 color_ramp[PXL8_PALETTE_SIZE];
|
||||
u16 color_count;
|
||||
pxl8_cycle_range cycles[PXL8_MAX_CYCLES];
|
||||
i8 directions[PXL8_MAX_CYCLES];
|
||||
f32 phases[PXL8_MAX_CYCLES];
|
||||
pxl8_palette_hash_entry hash[PXL8_PALETTE_HASH_SIZE];
|
||||
};
|
||||
|
||||
static inline u32 pxl8_palette_hash(u32 color) {
|
||||
color ^= color >> 16;
|
||||
color *= 0x85ebca6b;
|
||||
color ^= color >> 13;
|
||||
return color & (PXL8_PALETTE_HASH_SIZE - 1);
|
||||
}
|
||||
|
||||
static void pxl8_palette_rebuild_hash(pxl8_palette* pal) {
|
||||
for (u32 i = 0; i < PXL8_PALETTE_HASH_SIZE; i++) {
|
||||
pal->hash[i].index = -1;
|
||||
}
|
||||
for (u32 i = 0; i < PXL8_PALETTE_SIZE; i++) {
|
||||
u32 color = pal->colors[i];
|
||||
u32 slot = pxl8_palette_hash(color);
|
||||
while (pal->hash[slot].index >= 0) {
|
||||
slot = (slot + 1) & (PXL8_PALETTE_HASH_SIZE - 1);
|
||||
}
|
||||
pal->hash[slot].color = color;
|
||||
pal->hash[slot].index = (i16)i;
|
||||
}
|
||||
}
|
||||
|
||||
static inline u32 pack_rgb(u8 r, u8 g, u8 b) {
|
||||
return 0xFF000000 | ((u32)b << 16) | ((u32)g << 8) | r;
|
||||
}
|
||||
|
||||
static inline u32 pack_rgba(u8 r, u8 g, u8 b, u8 a) {
|
||||
return ((u32)a << 24) | ((u32)b << 16) | ((u32)g << 8) | r;
|
||||
}
|
||||
|
||||
static inline void unpack_rgba(u32 c, u8* r, u8* g, u8* b, u8* a) {
|
||||
*r = c & 0xFF;
|
||||
*g = (c >> 8) & 0xFF;
|
||||
*b = (c >> 16) & 0xFF;
|
||||
*a = (c >> 24) & 0xFF;
|
||||
}
|
||||
|
||||
static f32 apply_easing(pxl8_easing easing, f32 t) {
|
||||
switch (easing) {
|
||||
case PXL8_EASE_IN:
|
||||
return t * t;
|
||||
case PXL8_EASE_IN_OUT:
|
||||
return t < 0.5f ? 2.0f * t * t : 1.0f - (-2.0f * t + 2.0f) * (-2.0f * t + 2.0f) * 0.5f;
|
||||
case PXL8_EASE_LINEAR:
|
||||
return t;
|
||||
case PXL8_EASE_OUT:
|
||||
return 1.0f - (1.0f - t) * (1.0f - t);
|
||||
default:
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
static void rgb_to_hsl(u8 r, u8 g, u8 b, f32* h, f32* s, f32* l) {
|
||||
f32 rf = r / 255.0f;
|
||||
f32 gf = g / 255.0f;
|
||||
f32 bf = b / 255.0f;
|
||||
|
||||
f32 max = rf > gf ? (rf > bf ? rf : bf) : (gf > bf ? gf : bf);
|
||||
f32 min = rf < gf ? (rf < bf ? rf : bf) : (gf < bf ? gf : bf);
|
||||
f32 delta = max - min;
|
||||
|
||||
*l = (max + min) / 2.0f;
|
||||
|
||||
if (delta < 0.001f) {
|
||||
*h = 0.0f;
|
||||
*s = 0.0f;
|
||||
} else {
|
||||
*s = *l > 0.5f ? delta / (2.0f - max - min) : delta / (max + min);
|
||||
|
||||
if (max == rf) {
|
||||
*h = (gf - bf) / delta + (gf < bf ? 6.0f : 0.0f);
|
||||
} else if (max == gf) {
|
||||
*h = (bf - rf) / delta + 2.0f;
|
||||
} else {
|
||||
*h = (rf - gf) / delta + 4.0f;
|
||||
}
|
||||
*h /= 6.0f;
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
u8 index;
|
||||
f32 hue;
|
||||
f32 sat;
|
||||
f32 lum;
|
||||
} palette_sort_entry;
|
||||
|
||||
static int palette_sort_cmp(const void* a, const void* b) {
|
||||
const palette_sort_entry* entry_a = (const palette_sort_entry*)a;
|
||||
const palette_sort_entry* entry_b = (const palette_sort_entry*)b;
|
||||
|
||||
u8 a_gray = entry_a->sat < 0.1f;
|
||||
u8 b_gray = entry_b->sat < 0.1f;
|
||||
if (a_gray != b_gray) return a_gray - b_gray;
|
||||
|
||||
if (a_gray) {
|
||||
if (entry_a->lum < entry_b->lum) return -1;
|
||||
if (entry_a->lum > entry_b->lum) return 1;
|
||||
return (int)entry_a->index - (int)entry_b->index;
|
||||
}
|
||||
|
||||
if (entry_a->hue < entry_b->hue - 0.02f) return -1;
|
||||
if (entry_a->hue > entry_b->hue + 0.02f) return 1;
|
||||
|
||||
if (entry_a->lum < entry_b->lum) return -1;
|
||||
if (entry_a->lum > entry_b->lum) return 1;
|
||||
|
||||
return (int)entry_a->index - (int)entry_b->index;
|
||||
}
|
||||
|
||||
static void pxl8_palette_sort_colors(pxl8_palette* pal) {
|
||||
if (!pal || pal->color_count < 2) return;
|
||||
|
||||
pal->color_ramp[0] = 0;
|
||||
|
||||
u8 count = pal->color_count;
|
||||
palette_sort_entry entries[PXL8_PALETTE_SIZE - 1];
|
||||
for (u32 i = 1; i < count; i++) {
|
||||
u8 r, g, b, a;
|
||||
unpack_rgba(pal->colors[i], &r, &g, &b, &a);
|
||||
|
||||
entries[i - 1].index = (u8)i;
|
||||
rgb_to_hsl(r, g, b, &entries[i - 1].hue, &entries[i - 1].sat, &entries[i - 1].lum);
|
||||
}
|
||||
|
||||
qsort(entries, count - 1, sizeof(palette_sort_entry), palette_sort_cmp);
|
||||
|
||||
for (u32 i = 1; i < count; i++) {
|
||||
pal->color_ramp[i] = entries[i - 1].index;
|
||||
}
|
||||
}
|
||||
|
||||
static u32 lerp_color(u32 c0, u32 c1, f32 t) {
|
||||
u8 r0, g0, b0, a0, r1, g1, b1, a1;
|
||||
unpack_rgba(c0, &r0, &g0, &b0, &a0);
|
||||
unpack_rgba(c1, &r1, &g1, &b1, &a1);
|
||||
|
||||
u8 r = (u8)(r0 + (r1 - r0) * t);
|
||||
u8 g = (u8)(g0 + (g1 - g0) * t);
|
||||
u8 b = (u8)(b0 + (b1 - b0) * t);
|
||||
u8 a = (u8)(a0 + (a1 - a0) * t);
|
||||
|
||||
return pack_rgba(r, g, b, a);
|
||||
}
|
||||
|
||||
static void update_cycle_colors(pxl8_palette* pal, u8 slot) {
|
||||
pxl8_cycle_range* cycle = &pal->cycles[slot];
|
||||
f32 phase = pal->phases[slot];
|
||||
f32 eased = apply_easing(cycle->easing, phase);
|
||||
u8 len = cycle->len;
|
||||
u8 start = cycle->start;
|
||||
|
||||
if (cycle->interpolate) {
|
||||
f32 pos = eased * (len - 1);
|
||||
u8 idx0 = (u8)pos;
|
||||
if (idx0 >= len - 1) idx0 = len - 1;
|
||||
f32 frac = pos - idx0;
|
||||
|
||||
for (u8 i = 0; i < len; i++) {
|
||||
u8 src0 = (i + idx0) % len;
|
||||
u8 src1 = (i + idx0 + 1) % len;
|
||||
u32 c0 = pal->base_colors[slot][src0];
|
||||
u32 c1 = pal->base_colors[slot][src1];
|
||||
pal->colors[start + i] = lerp_color(c0, c1, frac);
|
||||
}
|
||||
} else {
|
||||
u8 offset = (u8)(eased * len) % len;
|
||||
for (u8 i = 0; i < len; i++) {
|
||||
u8 src = (i + offset) % len;
|
||||
pal->colors[start + i] = pal->base_colors[slot][src];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_palette* pxl8_palette_create(void) {
|
||||
pxl8_palette* pal = calloc(1, sizeof(pxl8_palette));
|
||||
if (!pal) return NULL;
|
||||
|
||||
pal->colors[0] = 0x00000000;
|
||||
pal->colors[1] = pack_rgb(0x00, 0x00, 0x00);
|
||||
pal->colors[2] = pack_rgb(0xFF, 0xFF, 0xFF);
|
||||
pal->color_count = 3;
|
||||
pal->color_ramp[0] = 0;
|
||||
pal->color_ramp[1] = 1;
|
||||
pal->color_ramp[2] = 2;
|
||||
|
||||
for (u8 i = 0; i < PXL8_MAX_CYCLES; i++) {
|
||||
pal->cycles[i] = pxl8_cycle_range_disabled();
|
||||
pal->phases[i] = 0.0f;
|
||||
pal->directions[i] = 1;
|
||||
}
|
||||
|
||||
return pal;
|
||||
}
|
||||
|
||||
void pxl8_palette_destroy(pxl8_palette* pal) {
|
||||
free(pal);
|
||||
}
|
||||
|
||||
pxl8_result pxl8_palette_load_ase(pxl8_palette* pal, const char* path) {
|
||||
if (!pal || !path) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
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 || !ase_file.palette.colors) {
|
||||
pxl8_error("No palette data in ASE file");
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
u32 copy_count = ase_file.palette.entry_count < PXL8_PALETTE_SIZE
|
||||
? ase_file.palette.entry_count
|
||||
: PXL8_PALETTE_SIZE;
|
||||
|
||||
memcpy(pal->colors, ase_file.palette.colors, copy_count * sizeof(u32));
|
||||
pal->color_count = (u16)copy_count;
|
||||
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
pxl8_palette_sort_colors(pal);
|
||||
pxl8_palette_rebuild_hash(pal);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
u32* pxl8_palette_colors(pxl8_palette* pal) {
|
||||
return pal ? pal->colors : NULL;
|
||||
}
|
||||
|
||||
u8* pxl8_palette_color_ramp(pxl8_palette* pal) {
|
||||
return pal ? pal->color_ramp : NULL;
|
||||
}
|
||||
|
||||
u16 pxl8_palette_color_count(const pxl8_palette* pal) {
|
||||
return pal ? pal->color_count : 0;
|
||||
}
|
||||
|
||||
u8 pxl8_palette_ramp_index(const pxl8_palette* pal, u8 position) {
|
||||
if (!pal || position >= pal->color_count) return 0;
|
||||
return pal->color_ramp[position];
|
||||
}
|
||||
|
||||
u8 pxl8_palette_ramp_position(const pxl8_palette* pal, u8 index) {
|
||||
if (!pal) return 0;
|
||||
for (u32 i = 0; i < pal->color_count; i++) {
|
||||
if (pal->color_ramp[i] == index) return (u8)i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
u8 pxl8_palette_find_closest(const pxl8_palette* pal, u8 r, u8 g, u8 b) {
|
||||
if (!pal || pal->color_count < 2) return 1;
|
||||
|
||||
u8 best_idx = 1;
|
||||
u32 best_dist = 0xFFFFFFFF;
|
||||
|
||||
for (u32 i = 1; i < pal->color_count; i++) {
|
||||
u8 pr, pg, pb, pa;
|
||||
unpack_rgba(pal->colors[i], &pr, &pg, &pb, &pa);
|
||||
|
||||
i32 dr = (i32)r - (i32)pr;
|
||||
i32 dg = (i32)g - (i32)pg;
|
||||
i32 db = (i32)b - (i32)pb;
|
||||
u32 dist = (u32)(dr * dr + dg * dg + db * db);
|
||||
|
||||
if (dist < best_dist) {
|
||||
best_dist = dist;
|
||||
best_idx = (u8)i;
|
||||
if (dist == 0) break;
|
||||
}
|
||||
}
|
||||
|
||||
return best_idx;
|
||||
}
|
||||
|
||||
u32 pxl8_palette_color(const pxl8_palette* pal, u8 idx) {
|
||||
if (!pal) return 0;
|
||||
return pxl8_color_to_rgba(pal->colors[idx]);
|
||||
}
|
||||
|
||||
i32 pxl8_palette_index(const pxl8_palette* pal, u32 rgba) {
|
||||
if (!pal) return -1;
|
||||
if (rgba <= 0xFFFFFF) rgba = (rgba << 8) | 0xFF;
|
||||
u32 abgr = pxl8_color_from_rgba(rgba);
|
||||
u32 slot = pxl8_palette_hash(abgr);
|
||||
for (u32 i = 0; i < PXL8_PALETTE_HASH_SIZE; i++) {
|
||||
u32 idx = (slot + i) & (PXL8_PALETTE_HASH_SIZE - 1);
|
||||
if (pal->hash[idx].index < 0) return -1;
|
||||
if (pal->hash[idx].color == abgr) return pal->hash[idx].index;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void pxl8_palette_get_rgb(const pxl8_palette* pal, u8 idx, u8* r, u8* g, u8* b) {
|
||||
if (!pal || !r || !g || !b) return;
|
||||
u8 a;
|
||||
unpack_rgba(pal->colors[idx], r, g, b, &a);
|
||||
}
|
||||
|
||||
void pxl8_palette_get_rgba(const pxl8_palette* pal, u8 idx, u8* r, u8* g, u8* b, u8* a) {
|
||||
if (!pal || !r || !g || !b || !a) return;
|
||||
unpack_rgba(pal->colors[idx], r, g, b, a);
|
||||
}
|
||||
|
||||
void pxl8_palette_set(pxl8_palette* pal, u8 idx, u32 color) {
|
||||
if (pal) pal->colors[idx] = color;
|
||||
}
|
||||
|
||||
void pxl8_palette_set_rgb(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b) {
|
||||
if (pal) pal->colors[idx] = pack_rgb(r, g, b);
|
||||
}
|
||||
|
||||
void pxl8_palette_set_rgba(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b, u8 a) {
|
||||
if (pal) pal->colors[idx] = pack_rgba(r, g, b, a);
|
||||
}
|
||||
|
||||
void pxl8_palette_fill_gradient(pxl8_palette* pal, u8 start, u8 count, u32 from, u32 to) {
|
||||
if (!pal || count == 0) return;
|
||||
|
||||
u8 r0, g0, b0, a0, r1, g1, b1, a1;
|
||||
unpack_rgba(from, &r0, &g0, &b0, &a0);
|
||||
unpack_rgba(to, &r1, &g1, &b1, &a1);
|
||||
|
||||
for (u8 i = 0; i < count; i++) {
|
||||
f32 t = (count > 1) ? (f32)i / (f32)(count - 1) : 0.0f;
|
||||
u8 r = (u8)(r0 + (r1 - r0) * t);
|
||||
u8 g = (u8)(g0 + (g1 - g0) * t);
|
||||
u8 b = (u8)(b0 + (b1 - b0) * t);
|
||||
u8 a = (u8)(a0 + (a1 - a0) * t);
|
||||
pal->colors[start + i] = pack_rgba(r, g, b, a);
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_palette_fill_gradient_rgb(pxl8_palette* pal, u8 start, u8 count, u8 r0, u8 g0, u8 b0, u8 r1, u8 g1, u8 b1) {
|
||||
pxl8_palette_fill_gradient(pal, start, count, pack_rgb(r0, g0, b0), pack_rgb(r1, g1, b1));
|
||||
}
|
||||
|
||||
void pxl8_palette_reset_cycle(pxl8_palette* pal, u8 slot) {
|
||||
if (!pal || slot >= PXL8_MAX_CYCLES) return;
|
||||
pal->phases[slot] = 0.0f;
|
||||
pal->directions[slot] = 1;
|
||||
}
|
||||
|
||||
void pxl8_palette_set_cycle(pxl8_palette* pal, u8 slot, pxl8_cycle_range range) {
|
||||
if (!pal || slot >= PXL8_MAX_CYCLES) return;
|
||||
|
||||
pal->cycles[slot] = range;
|
||||
pal->phases[slot] = 0.0f;
|
||||
pal->directions[slot] = 1;
|
||||
|
||||
u8 start = range.start;
|
||||
u8 len = range.len;
|
||||
if (len > PXL8_MAX_CYCLE_LEN) len = PXL8_MAX_CYCLE_LEN;
|
||||
|
||||
for (u8 i = 0; i < len; i++) {
|
||||
pal->base_colors[slot][i] = pal->colors[start + i];
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_palette_set_cycle_colors(pxl8_palette* pal, u8 slot, const u32* colors, u8 count) {
|
||||
if (!pal || !colors || slot >= PXL8_MAX_CYCLES) return;
|
||||
|
||||
pxl8_cycle_range* cycle = &pal->cycles[slot];
|
||||
u8 start = cycle->start;
|
||||
u8 len = cycle->len;
|
||||
if (len > count) len = count;
|
||||
if (len > PXL8_MAX_CYCLE_LEN) len = PXL8_MAX_CYCLE_LEN;
|
||||
|
||||
for (u8 i = 0; i < len; i++) {
|
||||
pal->base_colors[slot][i] = colors[i];
|
||||
pal->colors[start + i] = colors[i];
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_palette_set_cycle_phase(pxl8_palette* pal, u8 slot, f32 phase) {
|
||||
if (!pal || slot >= PXL8_MAX_CYCLES) return;
|
||||
|
||||
if (phase < 0.0f) phase = 0.0f;
|
||||
if (phase > 1.0f) phase = 1.0f;
|
||||
|
||||
pal->phases[slot] = phase;
|
||||
update_cycle_colors(pal, slot);
|
||||
}
|
||||
|
||||
void pxl8_palette_tick(pxl8_palette* pal, u16 delta_ticks) {
|
||||
if (!pal) return;
|
||||
|
||||
for (u8 slot = 0; slot < PXL8_MAX_CYCLES; slot++) {
|
||||
pxl8_cycle_range* cycle = &pal->cycles[slot];
|
||||
if (cycle->period == 0 || cycle->len < 2) continue;
|
||||
|
||||
f32 delta = (f32)delta_ticks / (f32)cycle->period;
|
||||
f32 dir = (f32)pal->directions[slot];
|
||||
pal->phases[slot] += delta * dir;
|
||||
|
||||
switch (cycle->mode) {
|
||||
case PXL8_CYCLE_LOOP:
|
||||
while (pal->phases[slot] >= 1.0f) pal->phases[slot] -= 1.0f;
|
||||
while (pal->phases[slot] < 0.0f) pal->phases[slot] += 1.0f;
|
||||
break;
|
||||
|
||||
case PXL8_CYCLE_PINGPONG:
|
||||
if (pal->phases[slot] >= 1.0f) {
|
||||
pal->phases[slot] = 1.0f - (pal->phases[slot] - 1.0f);
|
||||
pal->directions[slot] = -1;
|
||||
} else if (pal->phases[slot] <= 0.0f) {
|
||||
pal->phases[slot] = -pal->phases[slot];
|
||||
pal->directions[slot] = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case PXL8_CYCLE_ONCE:
|
||||
if (pal->phases[slot] < 0.0f) pal->phases[slot] = 0.0f;
|
||||
if (pal->phases[slot] > 1.0f) pal->phases[slot] = 1.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
update_cycle_colors(pal, slot);
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_cycle_range pxl8_cycle_range_create(u8 start, u8 len, u16 period) {
|
||||
pxl8_cycle_range range = {
|
||||
.easing = PXL8_EASE_LINEAR,
|
||||
.interpolate = true,
|
||||
.len = len,
|
||||
.mode = PXL8_CYCLE_LOOP,
|
||||
.period = period,
|
||||
.start = start,
|
||||
};
|
||||
return range;
|
||||
}
|
||||
|
||||
pxl8_cycle_range pxl8_cycle_range_disabled(void) {
|
||||
pxl8_cycle_range range = {
|
||||
.easing = PXL8_EASE_LINEAR,
|
||||
.interpolate = false,
|
||||
.len = 0,
|
||||
.mode = PXL8_CYCLE_LOOP,
|
||||
.period = 0,
|
||||
.start = 0,
|
||||
};
|
||||
return range;
|
||||
}
|
||||
70
src/gfx/pxl8_palette.h
Normal file
70
src/gfx/pxl8_palette.h
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#define PXL8_PALETTE_SIZE 256
|
||||
#define PXL8_MAX_CYCLES 8
|
||||
#define PXL8_MAX_CYCLE_LEN 16
|
||||
|
||||
typedef struct pxl8_palette pxl8_palette;
|
||||
|
||||
typedef enum pxl8_cycle_mode {
|
||||
PXL8_CYCLE_LOOP,
|
||||
PXL8_CYCLE_ONCE,
|
||||
PXL8_CYCLE_PINGPONG,
|
||||
} pxl8_cycle_mode;
|
||||
|
||||
typedef enum pxl8_easing {
|
||||
PXL8_EASE_LINEAR,
|
||||
PXL8_EASE_IN,
|
||||
PXL8_EASE_IN_OUT,
|
||||
PXL8_EASE_OUT,
|
||||
} pxl8_easing;
|
||||
|
||||
typedef struct pxl8_cycle_range {
|
||||
pxl8_easing easing;
|
||||
bool interpolate;
|
||||
u8 len;
|
||||
pxl8_cycle_mode mode;
|
||||
u16 period;
|
||||
u8 start;
|
||||
} pxl8_cycle_range;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_palette* pxl8_palette_create(void);
|
||||
void pxl8_palette_destroy(pxl8_palette* pal);
|
||||
|
||||
pxl8_result pxl8_palette_load_ase(pxl8_palette* pal, const char* path);
|
||||
|
||||
u16 pxl8_palette_color_count(const pxl8_palette* pal);
|
||||
u8* pxl8_palette_color_ramp(pxl8_palette* pal);
|
||||
u32* pxl8_palette_colors(pxl8_palette* pal);
|
||||
u8 pxl8_palette_ramp_index(const pxl8_palette* pal, u8 position);
|
||||
u8 pxl8_palette_ramp_position(const pxl8_palette* pal, u8 index);
|
||||
u8 pxl8_palette_find_closest(const pxl8_palette* pal, u8 r, u8 g, u8 b);
|
||||
u32 pxl8_palette_color(const pxl8_palette* pal, u8 idx);
|
||||
i32 pxl8_palette_index(const pxl8_palette* pal, u32 color);
|
||||
void pxl8_palette_get_rgb(const pxl8_palette* pal, u8 idx, u8* r, u8* g, u8* b);
|
||||
void pxl8_palette_get_rgba(const pxl8_palette* pal, u8 idx, u8* r, u8* g, u8* b, u8* a);
|
||||
void pxl8_palette_set(pxl8_palette* pal, u8 idx, u32 color);
|
||||
void pxl8_palette_set_rgb(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b);
|
||||
void pxl8_palette_set_rgba(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b, u8 a);
|
||||
|
||||
void pxl8_palette_fill_gradient(pxl8_palette* pal, u8 start, u8 count, u32 from, u32 to);
|
||||
void pxl8_palette_fill_gradient_rgb(pxl8_palette* pal, u8 start, u8 count, u8 r0, u8 g0, u8 b0, u8 r1, u8 g1, u8 b1);
|
||||
|
||||
void pxl8_palette_reset_cycle(pxl8_palette* pal, u8 slot);
|
||||
void pxl8_palette_set_cycle(pxl8_palette* pal, u8 slot, pxl8_cycle_range range);
|
||||
void pxl8_palette_set_cycle_colors(pxl8_palette* pal, u8 slot, const u32* colors, u8 count);
|
||||
void pxl8_palette_set_cycle_phase(pxl8_palette* pal, u8 slot, f32 phase);
|
||||
void pxl8_palette_tick(pxl8_palette* pal, u16 delta_ticks);
|
||||
|
||||
pxl8_cycle_range pxl8_cycle_range_create(u8 start, u8 len, u16 period);
|
||||
pxl8_cycle_range pxl8_cycle_range_disabled(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
322
src/gfx/pxl8_particles.c
Normal file
322
src/gfx/pxl8_particles.c
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
#include "pxl8_particles.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_gfx2d.h"
|
||||
#include "pxl8_palette.h"
|
||||
#include "pxl8_rng.h"
|
||||
|
||||
struct pxl8_particles {
|
||||
pxl8_particle* particles;
|
||||
pxl8_palette* palette;
|
||||
pxl8_rng* rng;
|
||||
u32 alive_count;
|
||||
u32 count;
|
||||
u32 max_count;
|
||||
|
||||
f32 x, y;
|
||||
f32 spread_x, spread_y;
|
||||
|
||||
f32 drag;
|
||||
f32 gravity_x, gravity_y;
|
||||
f32 turbulence;
|
||||
|
||||
f32 spawn_rate;
|
||||
f32 spawn_timer;
|
||||
|
||||
u8 color_min, color_max;
|
||||
f32 life_min, life_max;
|
||||
f32 size_min, size_max;
|
||||
f32 vx_min, vx_max, vy_min, vy_max;
|
||||
|
||||
pxl8_particle_render_fn render_fn;
|
||||
pxl8_particle_spawn_fn spawn_fn;
|
||||
pxl8_particle_update_fn update_fn;
|
||||
void* userdata;
|
||||
};
|
||||
|
||||
pxl8_particles* pxl8_particles_create(u32 max_count, pxl8_rng* rng) {
|
||||
pxl8_particles* ps = calloc(1, sizeof(pxl8_particles));
|
||||
if (!ps) return NULL;
|
||||
|
||||
ps->particles = calloc(max_count, sizeof(pxl8_particle));
|
||||
if (!ps->particles) {
|
||||
free(ps);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ps->rng = rng;
|
||||
ps->max_count = max_count;
|
||||
ps->drag = 0.98f;
|
||||
ps->gravity_y = 100.0f;
|
||||
ps->spawn_rate = 10.0f;
|
||||
|
||||
ps->color_min = ps->color_max = 15;
|
||||
ps->life_min = ps->life_max = 1.0f;
|
||||
ps->size_min = ps->size_max = 1.0f;
|
||||
|
||||
return ps;
|
||||
}
|
||||
|
||||
void pxl8_particles_destroy(pxl8_particles* ps) {
|
||||
if (!ps) return;
|
||||
free(ps->particles);
|
||||
free(ps);
|
||||
}
|
||||
|
||||
void pxl8_particles_clear(pxl8_particles* ps) {
|
||||
if (!ps || !ps->particles) return;
|
||||
|
||||
for (u32 i = 0; i < ps->max_count; i++) {
|
||||
ps->particles[i].life = 0;
|
||||
ps->particles[i].flags = 0;
|
||||
}
|
||||
ps->alive_count = 0;
|
||||
ps->spawn_timer = 0;
|
||||
}
|
||||
|
||||
void pxl8_particles_emit(pxl8_particles* ps, u32 count) {
|
||||
if (!ps || !ps->particles) return;
|
||||
|
||||
for (u32 i = 0; i < count && ps->alive_count < ps->max_count; i++) {
|
||||
for (u32 j = 0; j < ps->max_count; j++) {
|
||||
if (ps->particles[j].life <= 0) {
|
||||
pxl8_particle* p = &ps->particles[j];
|
||||
|
||||
f32 life = ps->life_min + pxl8_rng_f32(ps->rng) * (ps->life_max - ps->life_min);
|
||||
p->life = life;
|
||||
p->max_life = life;
|
||||
|
||||
p->x = ps->x + (pxl8_rng_f32(ps->rng) - 0.5f) * ps->spread_x;
|
||||
p->y = ps->y + (pxl8_rng_f32(ps->rng) - 0.5f) * ps->spread_y;
|
||||
p->z = 0;
|
||||
|
||||
p->vx = ps->vx_min + pxl8_rng_f32(ps->rng) * (ps->vx_max - ps->vx_min);
|
||||
p->vy = ps->vy_min + pxl8_rng_f32(ps->rng) * (ps->vy_max - ps->vy_min);
|
||||
p->vz = 0;
|
||||
|
||||
p->ax = ps->gravity_x;
|
||||
p->ay = ps->gravity_y;
|
||||
p->az = 0;
|
||||
|
||||
u8 ramp_range = ps->color_max - ps->color_min + 1;
|
||||
u8 ramp_pos = ps->color_min + (pxl8_rng_next(ps->rng) % ramp_range);
|
||||
u8 color = ps->palette
|
||||
? pxl8_palette_ramp_index(ps->palette, ramp_pos)
|
||||
: ramp_pos;
|
||||
p->color = p->start_color = p->end_color = color;
|
||||
|
||||
p->size = ps->size_min + pxl8_rng_f32(ps->rng) * (ps->size_max - ps->size_min);
|
||||
p->angle = 0;
|
||||
p->spin = 0;
|
||||
p->flags = 1;
|
||||
|
||||
if (ps->spawn_fn) {
|
||||
ps->spawn_fn(ps, p);
|
||||
}
|
||||
|
||||
ps->alive_count++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_particles_render(pxl8_particles* ps, pxl8_gfx* gfx) {
|
||||
if (!ps || !ps->particles || !gfx) return;
|
||||
|
||||
for (u32 i = 0; i < ps->max_count; i++) {
|
||||
pxl8_particle* p = &ps->particles[i];
|
||||
if (p->life > 0 && p->flags) {
|
||||
if (ps->render_fn) {
|
||||
ps->render_fn(gfx, p, ps->userdata);
|
||||
} else {
|
||||
i32 x = (i32)p->x;
|
||||
i32 y = (i32)p->y;
|
||||
if (x >= 0 && x < pxl8_gfx_get_width(gfx) && y >= 0 && y < pxl8_gfx_get_height(gfx)) {
|
||||
pxl8_2d_pixel(gfx, x, y, p->color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_particles_update(pxl8_particles* ps, f32 dt) {
|
||||
if (!ps || !ps->particles) return;
|
||||
|
||||
if (ps->spawn_rate > 0.0f) {
|
||||
ps->spawn_timer += dt;
|
||||
f32 spawn_interval = 1.0f / ps->spawn_rate;
|
||||
u32 max_spawns_per_frame = ps->max_count / 10;
|
||||
if (max_spawns_per_frame < 1) max_spawns_per_frame = 1;
|
||||
u32 spawn_count = 0;
|
||||
while (ps->spawn_timer >= spawn_interval && spawn_count < max_spawns_per_frame) {
|
||||
pxl8_particles_emit(ps, 1);
|
||||
ps->spawn_timer -= spawn_interval;
|
||||
spawn_count++;
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < ps->max_count; i++) {
|
||||
pxl8_particle* p = &ps->particles[i];
|
||||
if (p->life > 0) {
|
||||
if (ps->update_fn) {
|
||||
ps->update_fn(p, dt, ps->userdata);
|
||||
} else {
|
||||
p->vx += p->ax * dt;
|
||||
p->vy += p->ay * dt;
|
||||
p->vz += p->az * dt;
|
||||
|
||||
p->vx *= ps->drag;
|
||||
p->vy *= ps->drag;
|
||||
p->vz *= ps->drag;
|
||||
|
||||
p->x += p->vx * dt;
|
||||
p->y += p->vy * dt;
|
||||
p->z += p->vz * dt;
|
||||
|
||||
p->angle += p->spin * dt;
|
||||
}
|
||||
|
||||
p->life -= dt / p->max_life;
|
||||
if (p->life <= 0) {
|
||||
p->flags = 0;
|
||||
ps->alive_count--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u32 pxl8_particles_count(const pxl8_particles* ps) {
|
||||
return ps ? ps->alive_count : 0;
|
||||
}
|
||||
|
||||
u32 pxl8_particles_max_count(const pxl8_particles* ps) {
|
||||
return ps ? ps->max_count : 0;
|
||||
}
|
||||
|
||||
pxl8_particle* pxl8_particles_get(pxl8_particles* ps, u32 index) {
|
||||
if (!ps || index >= ps->max_count) return NULL;
|
||||
return &ps->particles[index];
|
||||
}
|
||||
|
||||
pxl8_rng* pxl8_particles_rng(pxl8_particles* ps) {
|
||||
return ps ? ps->rng : NULL;
|
||||
}
|
||||
|
||||
f32 pxl8_particles_get_drag(const pxl8_particles* ps) {
|
||||
return ps ? ps->drag : 0.0f;
|
||||
}
|
||||
|
||||
f32 pxl8_particles_get_gravity_x(const pxl8_particles* ps) {
|
||||
return ps ? ps->gravity_x : 0.0f;
|
||||
}
|
||||
|
||||
f32 pxl8_particles_get_gravity_y(const pxl8_particles* ps) {
|
||||
return ps ? ps->gravity_y : 0.0f;
|
||||
}
|
||||
|
||||
f32 pxl8_particles_get_spawn_rate(const pxl8_particles* ps) {
|
||||
return ps ? ps->spawn_rate : 0.0f;
|
||||
}
|
||||
|
||||
f32 pxl8_particles_get_spread_x(const pxl8_particles* ps) {
|
||||
return ps ? ps->spread_x : 0.0f;
|
||||
}
|
||||
|
||||
f32 pxl8_particles_get_spread_y(const pxl8_particles* ps) {
|
||||
return ps ? ps->spread_y : 0.0f;
|
||||
}
|
||||
|
||||
f32 pxl8_particles_get_turbulence(const pxl8_particles* ps) {
|
||||
return ps ? ps->turbulence : 0.0f;
|
||||
}
|
||||
|
||||
void* pxl8_particles_get_userdata(const pxl8_particles* ps) {
|
||||
return ps ? ps->userdata : NULL;
|
||||
}
|
||||
|
||||
f32 pxl8_particles_get_x(const pxl8_particles* ps) {
|
||||
return ps ? ps->x : 0.0f;
|
||||
}
|
||||
|
||||
f32 pxl8_particles_get_y(const pxl8_particles* ps) {
|
||||
return ps ? ps->y : 0.0f;
|
||||
}
|
||||
|
||||
void pxl8_particles_set_colors(pxl8_particles* ps, u8 color_min, u8 color_max) {
|
||||
if (!ps) return;
|
||||
ps->color_min = color_min;
|
||||
ps->color_max = color_max;
|
||||
}
|
||||
|
||||
void pxl8_particles_set_drag(pxl8_particles* ps, f32 drag) {
|
||||
if (ps) ps->drag = drag;
|
||||
}
|
||||
|
||||
void pxl8_particles_set_gravity(pxl8_particles* ps, f32 gx, f32 gy) {
|
||||
if (!ps) return;
|
||||
ps->gravity_x = gx;
|
||||
ps->gravity_y = gy;
|
||||
}
|
||||
|
||||
void pxl8_particles_set_life(pxl8_particles* ps, f32 life_min, f32 life_max) {
|
||||
if (!ps) return;
|
||||
ps->life_min = life_min;
|
||||
ps->life_max = life_max;
|
||||
}
|
||||
|
||||
void pxl8_particles_set_palette(pxl8_particles* ps, pxl8_palette* palette) {
|
||||
if (ps) ps->palette = palette;
|
||||
}
|
||||
|
||||
void pxl8_particles_set_position(pxl8_particles* ps, f32 x, f32 y) {
|
||||
if (!ps) return;
|
||||
ps->x = x;
|
||||
ps->y = y;
|
||||
}
|
||||
|
||||
void pxl8_particles_set_render_fn(pxl8_particles* ps, pxl8_particle_render_fn fn) {
|
||||
if (ps) ps->render_fn = fn;
|
||||
}
|
||||
|
||||
void pxl8_particles_set_size(pxl8_particles* ps, f32 size_min, f32 size_max) {
|
||||
if (!ps) return;
|
||||
ps->size_min = size_min;
|
||||
ps->size_max = size_max;
|
||||
}
|
||||
|
||||
void pxl8_particles_set_spawn_fn(pxl8_particles* ps, pxl8_particle_spawn_fn fn) {
|
||||
if (ps) ps->spawn_fn = fn;
|
||||
}
|
||||
|
||||
void pxl8_particles_set_spawn_rate(pxl8_particles* ps, f32 rate) {
|
||||
if (ps) ps->spawn_rate = rate;
|
||||
}
|
||||
|
||||
void pxl8_particles_set_spread(pxl8_particles* ps, f32 spread_x, f32 spread_y) {
|
||||
if (!ps) return;
|
||||
ps->spread_x = spread_x;
|
||||
ps->spread_y = spread_y;
|
||||
}
|
||||
|
||||
void pxl8_particles_set_turbulence(pxl8_particles* ps, f32 turbulence) {
|
||||
if (ps) ps->turbulence = turbulence;
|
||||
}
|
||||
|
||||
void pxl8_particles_set_update_fn(pxl8_particles* ps, pxl8_particle_update_fn fn) {
|
||||
if (ps) ps->update_fn = fn;
|
||||
}
|
||||
|
||||
void pxl8_particles_set_userdata(pxl8_particles* ps, void* userdata) {
|
||||
if (ps) ps->userdata = userdata;
|
||||
}
|
||||
|
||||
void pxl8_particles_set_velocity(pxl8_particles* ps, f32 vx_min, f32 vx_max, f32 vy_min, f32 vy_max) {
|
||||
if (!ps) return;
|
||||
ps->vx_min = vx_min;
|
||||
ps->vx_max = vx_max;
|
||||
ps->vy_min = vy_min;
|
||||
ps->vy_max = vy_max;
|
||||
}
|
||||
75
src/gfx/pxl8_particles.h
Normal file
75
src/gfx/pxl8_particles.h
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_gfx pxl8_gfx;
|
||||
typedef struct pxl8_palette pxl8_palette;
|
||||
typedef struct pxl8_particles pxl8_particles;
|
||||
typedef struct pxl8_rng pxl8_rng;
|
||||
|
||||
typedef struct pxl8_particle {
|
||||
f32 angle;
|
||||
f32 ax, ay, az;
|
||||
u32 color;
|
||||
u32 end_color;
|
||||
u8 flags;
|
||||
f32 life;
|
||||
f32 max_life;
|
||||
f32 size;
|
||||
f32 spin;
|
||||
u32 start_color;
|
||||
f32 vx, vy, vz;
|
||||
f32 x, y, z;
|
||||
} pxl8_particle;
|
||||
|
||||
typedef void (*pxl8_particle_render_fn)(pxl8_gfx* gfx, pxl8_particle* p, void* userdata);
|
||||
typedef void (*pxl8_particle_spawn_fn)(pxl8_particles* ps, pxl8_particle* p);
|
||||
typedef void (*pxl8_particle_update_fn)(pxl8_particle* p, f32 dt, void* userdata);
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_particles* pxl8_particles_create(u32 max_count, pxl8_rng* rng);
|
||||
void pxl8_particles_destroy(pxl8_particles* ps);
|
||||
|
||||
void pxl8_particles_clear(pxl8_particles* ps);
|
||||
void pxl8_particles_emit(pxl8_particles* ps, u32 count);
|
||||
void pxl8_particles_render(pxl8_particles* ps, pxl8_gfx* gfx);
|
||||
void pxl8_particles_update(pxl8_particles* ps, f32 dt);
|
||||
|
||||
u32 pxl8_particles_count(const pxl8_particles* ps);
|
||||
u32 pxl8_particles_max_count(const pxl8_particles* ps);
|
||||
pxl8_particle* pxl8_particles_get(pxl8_particles* ps, u32 index);
|
||||
pxl8_rng* pxl8_particles_rng(pxl8_particles* ps);
|
||||
|
||||
f32 pxl8_particles_get_drag(const pxl8_particles* ps);
|
||||
f32 pxl8_particles_get_gravity_x(const pxl8_particles* ps);
|
||||
f32 pxl8_particles_get_gravity_y(const pxl8_particles* ps);
|
||||
f32 pxl8_particles_get_spawn_rate(const pxl8_particles* ps);
|
||||
f32 pxl8_particles_get_spread_x(const pxl8_particles* ps);
|
||||
f32 pxl8_particles_get_spread_y(const pxl8_particles* ps);
|
||||
f32 pxl8_particles_get_turbulence(const pxl8_particles* ps);
|
||||
void* pxl8_particles_get_userdata(const pxl8_particles* ps);
|
||||
f32 pxl8_particles_get_x(const pxl8_particles* ps);
|
||||
f32 pxl8_particles_get_y(const pxl8_particles* ps);
|
||||
|
||||
void pxl8_particles_set_colors(pxl8_particles* ps, u8 color_min, u8 color_max);
|
||||
void pxl8_particles_set_drag(pxl8_particles* ps, f32 drag);
|
||||
void pxl8_particles_set_gravity(pxl8_particles* ps, f32 gx, f32 gy);
|
||||
void pxl8_particles_set_life(pxl8_particles* ps, f32 life_min, f32 life_max);
|
||||
void pxl8_particles_set_palette(pxl8_particles* ps, pxl8_palette* palette);
|
||||
void pxl8_particles_set_position(pxl8_particles* ps, f32 x, f32 y);
|
||||
void pxl8_particles_set_render_fn(pxl8_particles* ps, pxl8_particle_render_fn fn);
|
||||
void pxl8_particles_set_size(pxl8_particles* ps, f32 size_min, f32 size_max);
|
||||
void pxl8_particles_set_spawn_fn(pxl8_particles* ps, pxl8_particle_spawn_fn fn);
|
||||
void pxl8_particles_set_spawn_rate(pxl8_particles* ps, f32 rate);
|
||||
void pxl8_particles_set_spread(pxl8_particles* ps, f32 spread_x, f32 spread_y);
|
||||
void pxl8_particles_set_turbulence(pxl8_particles* ps, f32 turbulence);
|
||||
void pxl8_particles_set_update_fn(pxl8_particles* ps, pxl8_particle_update_fn fn);
|
||||
void pxl8_particles_set_userdata(pxl8_particles* ps, void* userdata);
|
||||
void pxl8_particles_set_velocity(pxl8_particles* ps, f32 vx_min, f32 vx_max, f32 vy_min, f32 vy_max);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
614
src/gfx/pxl8_tilemap.c
Normal file
614
src/gfx/pxl8_tilemap.c
Normal file
|
|
@ -0,0 +1,614 @@
|
|||
#include "pxl8_tilemap.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_ase.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_macros.h"
|
||||
#include "pxl8_tilesheet.h"
|
||||
|
||||
struct pxl8_tilesheet {
|
||||
u8* data;
|
||||
bool* tile_valid;
|
||||
u32 height;
|
||||
u32 tile_size;
|
||||
u32 tiles_per_row;
|
||||
u32 total_tiles;
|
||||
u32 width;
|
||||
pxl8_pixel_mode pixel_mode;
|
||||
u32 ref_count;
|
||||
pxl8_tile_animation* animations;
|
||||
u32 animation_count;
|
||||
pxl8_tile_properties* properties;
|
||||
};
|
||||
|
||||
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;
|
||||
u32 tile_user_data_capacity;
|
||||
void** tile_user_data;
|
||||
u32 width;
|
||||
};
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
pxl8_tilemap* pxl8_tilemap_create(u32 width, u32 height, u32 tile_size) {
|
||||
if (width > PXL8_MAX_TILEMAP_WIDTH || height > PXL8_MAX_TILEMAP_HEIGHT) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pxl8_tilemap* tilemap = calloc(1, sizeof(pxl8_tilemap));
|
||||
if (!tilemap) return NULL;
|
||||
|
||||
tilemap->width = width;
|
||||
tilemap->height = height;
|
||||
tilemap->tile_size = tile_size ? tile_size : PXL8_TILE_SIZE;
|
||||
tilemap->active_layers = 1;
|
||||
|
||||
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;
|
||||
|
||||
u32 chunks_wide = (width + PXL8_CHUNK_SIZE - 1) / PXL8_CHUNK_SIZE;
|
||||
u32 chunks_high = (height + PXL8_CHUNK_SIZE - 1) / PXL8_CHUNK_SIZE;
|
||||
|
||||
for (u32 i = 0; i < PXL8_MAX_TILE_LAYERS; i++) {
|
||||
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) {
|
||||
for (u32 j = 0; j < i; j++) {
|
||||
free(tilemap->layers[j].chunks);
|
||||
}
|
||||
if (tilemap->tilesheet) pxl8_tilesheet_destroy(tilemap->tilesheet);
|
||||
free(tilemap);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return tilemap;
|
||||
}
|
||||
|
||||
void pxl8_tilemap_destroy(pxl8_tilemap* tilemap) {
|
||||
if (!tilemap) return;
|
||||
|
||||
for (u32 i = 0; i < PXL8_MAX_TILE_LAYERS; i++) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (tilemap->tilesheet) pxl8_tilesheet_unref(tilemap->tilesheet);
|
||||
if (tilemap->tile_user_data) free(tilemap->tile_user_data);
|
||||
|
||||
free(tilemap);
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
pxl8_result pxl8_tilemap_set_tilesheet(pxl8_tilemap* tilemap, pxl8_tilesheet* tilesheet) {
|
||||
if (!tilemap || !tilesheet) return PXL8_ERROR_NULL_POINTER;
|
||||
|
||||
if (tilemap->tilesheet) {
|
||||
pxl8_tilesheet_unref(tilemap->tilesheet);
|
||||
}
|
||||
|
||||
tilemap->tilesheet = tilesheet;
|
||||
pxl8_tilesheet_ref(tilesheet);
|
||||
|
||||
u32 tilesheet_size = pxl8_tilesheet_get_tile_size(tilesheet);
|
||||
if (tilesheet_size != tilemap->tile_size) {
|
||||
pxl8_warn("Tilesheet tile size (%d) differs from tilemap tile size (%d)",
|
||||
tilesheet_size, tilemap->tile_size);
|
||||
tilemap->tile_size = tilesheet_size;
|
||||
}
|
||||
|
||||
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];
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
return chunk->tiles[idx];
|
||||
}
|
||||
|
||||
void pxl8_tilemap_set_camera(pxl8_tilemap* tilemap, i32 x, i32 y) {
|
||||
if (!tilemap) return;
|
||||
tilemap->camera_x = x;
|
||||
tilemap->camera_y = y;
|
||||
}
|
||||
|
||||
void pxl8_tilemap_get_view(const pxl8_tilemap* tilemap, const pxl8_gfx* gfx, pxl8_tilemap_view* view) {
|
||||
if (!tilemap || !gfx || !view) return;
|
||||
|
||||
view->x = -tilemap->camera_x;
|
||||
view->y = -tilemap->camera_y;
|
||||
view->width = pxl8_gfx_get_width(gfx);
|
||||
view->height = pxl8_gfx_get_height(gfx);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void pxl8_tilemap_render_tile(const pxl8_tilemap* tilemap, pxl8_gfx* gfx, u16 tile_id, i32 x, i32 y, u8 flags) {
|
||||
if (!tilemap || !gfx || !tilemap->tilesheet) return;
|
||||
pxl8_tilesheet_render_tile(tilemap->tilesheet, gfx, tile_id, x, y, flags);
|
||||
}
|
||||
|
||||
void pxl8_tilemap_render_layer(const pxl8_tilemap* tilemap, pxl8_gfx* gfx, u32 layer) {
|
||||
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;
|
||||
|
||||
i32 view_left = tilemap->camera_x / tilemap->tile_size;
|
||||
i32 view_top = tilemap->camera_y / tilemap->tile_size;
|
||||
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;
|
||||
|
||||
u32 chunk_left = pxl8_max(0, view_left >> 4);
|
||||
u32 chunk_top = pxl8_max(0, view_top >> 4);
|
||||
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));
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_tilemap_render(const pxl8_tilemap* tilemap, pxl8_gfx* gfx) {
|
||||
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);
|
||||
if (pxl8_tile_get_flags(tile) & PXL8_TILE_SOLID) return true;
|
||||
}
|
||||
|
||||
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);
|
||||
if (pxl8_tile_get_flags(tile) & PXL8_TILE_SOLID) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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->pixel_mode = PXL8_PIXEL_INDEXED;
|
||||
|
||||
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;
|
||||
}
|
||||
123
src/gfx/pxl8_tilemap.h
Normal file
123
src/gfx/pxl8_tilemap.h
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_tilesheet.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#define PXL8_TILE_SIZE 8
|
||||
#define PXL8_MAX_TILEMAP_WIDTH 256
|
||||
#define PXL8_MAX_TILEMAP_HEIGHT 256
|
||||
#define PXL8_MAX_TILE_LAYERS 4
|
||||
#define PXL8_CHUNK_SIZE 16
|
||||
#define PXL8_CHUNK_MASK 15
|
||||
|
||||
typedef enum pxl8_tile_flags {
|
||||
PXL8_TILE_FLIP_X = 1 << 0,
|
||||
PXL8_TILE_FLIP_Y = 1 << 1,
|
||||
PXL8_TILE_SOLID = 1 << 2,
|
||||
PXL8_TILE_TRIGGER = 1 << 3,
|
||||
PXL8_TILE_ANIMATED = 1 << 4,
|
||||
PXL8_TILE_AUTOTILE = 1 << 5,
|
||||
} pxl8_tile_flags;
|
||||
|
||||
#define PXL8_TILE_ID_MASK 0x0000FFFF
|
||||
#define PXL8_TILE_FLAGS_MASK 0x00FF0000
|
||||
#define PXL8_TILE_PAL_MASK 0xFF000000
|
||||
#define PXL8_TILE_ID_SHIFT 0
|
||||
#define PXL8_TILE_FLAGS_SHIFT 16
|
||||
#define PXL8_TILE_PAL_SHIFT 24
|
||||
|
||||
typedef u32 pxl8_tile;
|
||||
|
||||
static inline pxl8_tile pxl8_tile_pack(u16 id, u8 flags, u8 palette_offset) {
|
||||
return (u32)id | ((u32)flags << 16) | ((u32)palette_offset << 24);
|
||||
}
|
||||
|
||||
static inline u16 pxl8_tile_get_id(pxl8_tile tile) {
|
||||
return tile & PXL8_TILE_ID_MASK;
|
||||
}
|
||||
|
||||
static inline u8 pxl8_tile_get_flags(pxl8_tile tile) {
|
||||
return (tile & PXL8_TILE_FLAGS_MASK) >> PXL8_TILE_FLAGS_SHIFT;
|
||||
}
|
||||
|
||||
static inline u8 pxl8_tile_get_palette(pxl8_tile tile) {
|
||||
return (tile & PXL8_TILE_PAL_MASK) >> PXL8_TILE_PAL_SHIFT;
|
||||
}
|
||||
|
||||
typedef struct pxl8_tile_animation {
|
||||
u16 current_frame;
|
||||
f32 frame_duration;
|
||||
u16 frame_count;
|
||||
u16* frames;
|
||||
f32 time_accumulator;
|
||||
} pxl8_tile_animation;
|
||||
|
||||
typedef struct pxl8_tile_properties {
|
||||
i16 collision_offset_x;
|
||||
i16 collision_offset_y;
|
||||
u16 collision_height;
|
||||
u16 collision_width;
|
||||
u32 property_flags;
|
||||
void* user_data;
|
||||
} pxl8_tile_properties;
|
||||
|
||||
typedef struct pxl8_autotile_rule {
|
||||
u8 neighbor_mask;
|
||||
u16 tile_id;
|
||||
} pxl8_autotile_rule;
|
||||
|
||||
typedef struct pxl8_tile_chunk {
|
||||
u32 chunk_x;
|
||||
u32 chunk_y;
|
||||
bool empty;
|
||||
pxl8_tile tiles[PXL8_CHUNK_SIZE * PXL8_CHUNK_SIZE];
|
||||
} pxl8_tile_chunk;
|
||||
|
||||
typedef struct pxl8_tilemap_layer pxl8_tilemap_layer;
|
||||
typedef struct pxl8_tilemap pxl8_tilemap;
|
||||
|
||||
typedef struct pxl8_tilemap_view {
|
||||
i32 height;
|
||||
i32 tile_end_x, tile_end_y;
|
||||
i32 tile_start_x, tile_start_y;
|
||||
i32 width;
|
||||
i32 x, y;
|
||||
} pxl8_tilemap_view;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_tilemap* pxl8_tilemap_create(u32 width, u32 height, u32 tile_size);
|
||||
void pxl8_tilemap_destroy(pxl8_tilemap* tilemap);
|
||||
|
||||
u32 pxl8_tilemap_get_height(const pxl8_tilemap* tilemap);
|
||||
u32 pxl8_tilemap_get_memory_usage(const pxl8_tilemap* tilemap);
|
||||
pxl8_tile pxl8_tilemap_get_tile(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y);
|
||||
u16 pxl8_tilemap_get_tile_id(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y);
|
||||
u32 pxl8_tilemap_get_tile_size(const pxl8_tilemap* tilemap);
|
||||
void* pxl8_tilemap_get_tile_user_data(const pxl8_tilemap* tilemap, u16 tile_id);
|
||||
void pxl8_tilemap_get_view(const pxl8_tilemap* tilemap, const pxl8_gfx* gfx, pxl8_tilemap_view* view);
|
||||
u32 pxl8_tilemap_get_width(const pxl8_tilemap* tilemap);
|
||||
|
||||
void pxl8_tilemap_set_camera(pxl8_tilemap* tilemap, i32 x, i32 y);
|
||||
pxl8_result pxl8_tilemap_set_tile(pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y, u16 tile_id, u8 flags);
|
||||
pxl8_result pxl8_tilemap_set_tile_auto(pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y, u16 base_tile_id, u8 flags);
|
||||
void pxl8_tilemap_set_tile_user_data(pxl8_tilemap* tilemap, u16 tile_id, void* user_data);
|
||||
pxl8_result pxl8_tilemap_set_tilesheet(pxl8_tilemap* tilemap, pxl8_tilesheet* tilesheet);
|
||||
|
||||
pxl8_result pxl8_tilemap_load_ase(pxl8_tilemap* tilemap, const char* filepath, u32 layer);
|
||||
|
||||
bool pxl8_tilemap_check_collision(const pxl8_tilemap* tilemap, i32 x, i32 y, i32 w, i32 h);
|
||||
void pxl8_tilemap_compress(pxl8_tilemap* tilemap);
|
||||
bool pxl8_tilemap_is_solid(const pxl8_tilemap* tilemap, u32 x, u32 y);
|
||||
void pxl8_tilemap_render(const pxl8_tilemap* tilemap, pxl8_gfx* gfx);
|
||||
void pxl8_tilemap_render_layer(const pxl8_tilemap* tilemap, pxl8_gfx* gfx, u32 layer);
|
||||
void pxl8_tilemap_render_tile(const pxl8_tilemap* tilemap, pxl8_gfx* gfx, u16 tile_id, i32 x, i32 y, u8 flags);
|
||||
void pxl8_tilemap_update(pxl8_tilemap* tilemap, f32 delta_time);
|
||||
void pxl8_tilemap_update_autotiles(pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y, u32 w, u32 h);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
405
src/gfx/pxl8_tilesheet.c
Normal file
405
src/gfx/pxl8_tilesheet.c
Normal file
|
|
@ -0,0 +1,405 @@
|
|||
#include "pxl8_tilesheet.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_ase.h"
|
||||
#include "pxl8_color.h"
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_tilemap.h"
|
||||
|
||||
struct pxl8_tilesheet {
|
||||
u8* data;
|
||||
bool* tile_valid;
|
||||
|
||||
u32 height;
|
||||
u32 tile_size;
|
||||
u32 tiles_per_row;
|
||||
u32 total_tiles;
|
||||
u32 width;
|
||||
|
||||
pxl8_pixel_mode pixel_mode;
|
||||
u32 ref_count;
|
||||
|
||||
pxl8_tile_animation* animations;
|
||||
u32 animation_count;
|
||||
|
||||
pxl8_tile_properties* properties;
|
||||
|
||||
pxl8_autotile_rule** autotile_rules;
|
||||
u32* autotile_rule_counts;
|
||||
};
|
||||
|
||||
pxl8_tilesheet* pxl8_tilesheet_create(u32 tile_size) {
|
||||
pxl8_tilesheet* tilesheet = calloc(1, sizeof(pxl8_tilesheet));
|
||||
if (!tilesheet) return NULL;
|
||||
|
||||
tilesheet->tile_size = tile_size;
|
||||
tilesheet->ref_count = 1;
|
||||
|
||||
return tilesheet;
|
||||
}
|
||||
|
||||
void pxl8_tilesheet_destroy(pxl8_tilesheet* tilesheet) {
|
||||
if (!tilesheet) return;
|
||||
|
||||
if (tilesheet->data) {
|
||||
free(tilesheet->data);
|
||||
}
|
||||
|
||||
if (tilesheet->tile_valid) {
|
||||
free(tilesheet->tile_valid);
|
||||
}
|
||||
|
||||
if (tilesheet->animations) {
|
||||
for (u32 i = 0; i < tilesheet->animation_count; i++) {
|
||||
if (tilesheet->animations[i].frames) {
|
||||
free(tilesheet->animations[i].frames);
|
||||
}
|
||||
}
|
||||
free(tilesheet->animations);
|
||||
}
|
||||
|
||||
if (tilesheet->properties) {
|
||||
free(tilesheet->properties);
|
||||
}
|
||||
|
||||
if (tilesheet->autotile_rules) {
|
||||
for (u32 i = 0; i <= tilesheet->total_tiles; i++) {
|
||||
if (tilesheet->autotile_rules[i]) {
|
||||
free(tilesheet->autotile_rules[i]);
|
||||
}
|
||||
}
|
||||
free(tilesheet->autotile_rules);
|
||||
free(tilesheet->autotile_rule_counts);
|
||||
}
|
||||
|
||||
free(tilesheet);
|
||||
}
|
||||
|
||||
pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, pxl8_gfx* 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->pixel_mode = pxl8_gfx_get_pixel_mode(gfx);
|
||||
|
||||
u32 pixel_count = width * height;
|
||||
u16 ase_depth = ase_file.header.color_depth;
|
||||
bool gfx_hicolor = (tilesheet->pixel_mode == PXL8_PIXEL_HICOLOR);
|
||||
|
||||
size_t data_size = pixel_count * pxl8_bytes_per_pixel(tilesheet->pixel_mode);
|
||||
tilesheet->data = malloc(data_size);
|
||||
if (!tilesheet->data) {
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
if (ase_file.frame_count > 0 && ase_file.frames[0].pixels) {
|
||||
const u8* src = ase_file.frames[0].pixels;
|
||||
|
||||
if (ase_depth == 8 && !gfx_hicolor) {
|
||||
memcpy(tilesheet->data, src, pixel_count);
|
||||
} else if (ase_depth == 32 && gfx_hicolor) {
|
||||
u16* dst = (u16*)tilesheet->data;
|
||||
const u32* rgba = (const u32*)src;
|
||||
for (u32 i = 0; i < pixel_count; i++) {
|
||||
u32 c = rgba[i];
|
||||
u8 a = (c >> 24) & 0xFF;
|
||||
if (a == 0) {
|
||||
dst[i] = 0;
|
||||
} else {
|
||||
dst[i] = pxl8_rgba32_to_rgb565(c);
|
||||
}
|
||||
}
|
||||
} else if (ase_depth == 8 && gfx_hicolor) {
|
||||
pxl8_warn("Indexed ASE with hicolor gfx - storing as indexed");
|
||||
tilesheet->pixel_mode = PXL8_PIXEL_INDEXED;
|
||||
u8* new_data = realloc(tilesheet->data, pixel_count);
|
||||
if (!new_data) {
|
||||
free(tilesheet->data);
|
||||
tilesheet->data = NULL;
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
tilesheet->data = new_data;
|
||||
memcpy(tilesheet->data, src, pixel_count);
|
||||
} else {
|
||||
pxl8_error("Unsupported ASE color depth %d for gfx mode", ase_depth);
|
||||
free(tilesheet->data);
|
||||
tilesheet->data = NULL;
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
}
|
||||
|
||||
tilesheet->tile_valid = calloc(tilesheet->total_tiles + 1, sizeof(bool));
|
||||
if (!tilesheet->tile_valid) {
|
||||
free(tilesheet->data);
|
||||
tilesheet->data = NULL;
|
||||
pxl8_ase_destroy(&ase_file);
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
u32 valid_tiles = 0;
|
||||
bool is_hicolor = (tilesheet->pixel_mode == PXL8_PIXEL_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++) {
|
||||
u32 idx = (tile_y + py) * width + (tile_x + px);
|
||||
if (is_hicolor) {
|
||||
if (((u16*)tilesheet->data)[idx] != 0) {
|
||||
has_content = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
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_destroy(&ase_file);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
void pxl8_tilesheet_render_tile(const pxl8_tilesheet* tilesheet, pxl8_gfx* 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 < pxl8_gfx_get_width(gfx) &&
|
||||
screen_y >= 0 && screen_y < pxl8_gfx_get_height(gfx)) {
|
||||
pxl8_2d_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;
|
||||
}
|
||||
|
||||
void pxl8_tilesheet_ref(pxl8_tilesheet* tilesheet) {
|
||||
if (!tilesheet) return;
|
||||
tilesheet->ref_count++;
|
||||
}
|
||||
|
||||
void pxl8_tilesheet_unref(pxl8_tilesheet* tilesheet) {
|
||||
if (!tilesheet) return;
|
||||
|
||||
if (--tilesheet->ref_count == 0) {
|
||||
pxl8_tilesheet_destroy(tilesheet);
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_result pxl8_tilesheet_add_animation(pxl8_tilesheet* tilesheet, u16 base_tile_id,
|
||||
const u16* frames, u16 frame_count, f32 frame_duration) {
|
||||
if (!tilesheet || !frames || frame_count == 0) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
if (base_tile_id == 0 || base_tile_id > tilesheet->total_tiles) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
if (!tilesheet->animations) {
|
||||
tilesheet->animations = calloc(tilesheet->total_tiles + 1, sizeof(pxl8_tile_animation));
|
||||
if (!tilesheet->animations) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
pxl8_tile_animation* anim = &tilesheet->animations[base_tile_id];
|
||||
if (anim->frames) free(anim->frames);
|
||||
|
||||
anim->frames = malloc(frame_count * sizeof(u16));
|
||||
if (!anim->frames) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
memcpy(anim->frames, frames, frame_count * sizeof(u16));
|
||||
anim->frame_count = frame_count;
|
||||
anim->current_frame = 0;
|
||||
anim->frame_duration = frame_duration;
|
||||
anim->time_accumulator = 0;
|
||||
|
||||
tilesheet->animation_count++;
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
void pxl8_tilesheet_update_animations(pxl8_tilesheet* tilesheet, f32 delta_time) {
|
||||
if (!tilesheet || !tilesheet->animations) return;
|
||||
|
||||
for (u32 i = 1; i <= tilesheet->total_tiles; i++) {
|
||||
pxl8_tile_animation* anim = &tilesheet->animations[i];
|
||||
if (!anim->frames || anim->frame_count == 0) continue;
|
||||
|
||||
anim->time_accumulator += delta_time;
|
||||
while (anim->time_accumulator >= anim->frame_duration) {
|
||||
anim->time_accumulator -= anim->frame_duration;
|
||||
anim->current_frame = (anim->current_frame + 1) % anim->frame_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u16 pxl8_tilesheet_get_animated_frame(const pxl8_tilesheet* tilesheet, u16 tile_id) {
|
||||
if (!tilesheet || !tilesheet->animations || tile_id == 0 || tile_id > tilesheet->total_tiles) {
|
||||
return tile_id;
|
||||
}
|
||||
|
||||
const pxl8_tile_animation* anim = &tilesheet->animations[tile_id];
|
||||
if (!anim->frames || anim->frame_count == 0) return tile_id;
|
||||
|
||||
return anim->frames[anim->current_frame];
|
||||
}
|
||||
|
||||
pxl8_result pxl8_tilesheet_set_tile_pixels(pxl8_tilesheet* tilesheet, u16 tile_id, const u8* pixels) {
|
||||
if (!tilesheet || !pixels || tile_id == 0 || tile_id > tilesheet->total_tiles) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
if (!tilesheet->data) return PXL8_ERROR_NULL_POINTER;
|
||||
|
||||
u32 tile_x = (tile_id - 1) % tilesheet->tiles_per_row;
|
||||
u32 tile_y = (tile_id - 1) / tilesheet->tiles_per_row;
|
||||
u32 bytes_per_pixel = pxl8_bytes_per_pixel(tilesheet->pixel_mode);
|
||||
|
||||
for (u32 py = 0; py < tilesheet->tile_size; py++) {
|
||||
for (u32 px = 0; px < tilesheet->tile_size; px++) {
|
||||
u32 src_idx = py * tilesheet->tile_size + px;
|
||||
u32 dst_x = tile_x * tilesheet->tile_size + px;
|
||||
u32 dst_y = tile_y * tilesheet->tile_size + py;
|
||||
u32 dst_idx = dst_y * tilesheet->width + dst_x;
|
||||
|
||||
if (bytes_per_pixel == 2) {
|
||||
((u16*)tilesheet->data)[dst_idx] = ((const u16*)pixels)[src_idx];
|
||||
} else {
|
||||
tilesheet->data[dst_idx] = pixels[src_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tilesheet->tile_valid) {
|
||||
tilesheet->tile_valid[tile_id] = true;
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
void pxl8_tilesheet_set_tile_property(pxl8_tilesheet* tilesheet, u16 tile_id,
|
||||
const pxl8_tile_properties* props) {
|
||||
if (!tilesheet || !props || tile_id == 0 || tile_id > tilesheet->total_tiles) return;
|
||||
|
||||
if (!tilesheet->properties) {
|
||||
tilesheet->properties = calloc(tilesheet->total_tiles + 1, sizeof(pxl8_tile_properties));
|
||||
if (!tilesheet->properties) return;
|
||||
}
|
||||
|
||||
tilesheet->properties[tile_id] = *props;
|
||||
}
|
||||
|
||||
const pxl8_tile_properties* pxl8_tilesheet_get_tile_property(const pxl8_tilesheet* tilesheet, u16 tile_id) {
|
||||
if (!tilesheet || !tilesheet->properties || tile_id == 0 || tile_id > tilesheet->total_tiles) {
|
||||
return NULL;
|
||||
}
|
||||
return &tilesheet->properties[tile_id];
|
||||
}
|
||||
|
||||
u32 pxl8_tilesheet_get_tile_size(const pxl8_tilesheet* tilesheet) {
|
||||
return tilesheet ? tilesheet->tile_size : 0;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_tilesheet_add_autotile_rule(pxl8_tilesheet* tilesheet, u16 base_tile_id,
|
||||
u8 neighbor_mask, u16 result_tile_id) {
|
||||
if (!tilesheet || base_tile_id == 0 || base_tile_id > tilesheet->total_tiles) {
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (!tilesheet->autotile_rules) {
|
||||
tilesheet->autotile_rules = calloc(tilesheet->total_tiles + 1, sizeof(pxl8_autotile_rule*));
|
||||
tilesheet->autotile_rule_counts = calloc(tilesheet->total_tiles + 1, sizeof(u32));
|
||||
if (!tilesheet->autotile_rules || !tilesheet->autotile_rule_counts) {
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
}
|
||||
|
||||
u32 count = tilesheet->autotile_rule_counts[base_tile_id];
|
||||
pxl8_autotile_rule* new_rules = realloc(tilesheet->autotile_rules[base_tile_id],
|
||||
(count + 1) * sizeof(pxl8_autotile_rule));
|
||||
if (!new_rules) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
new_rules[count].neighbor_mask = neighbor_mask;
|
||||
new_rules[count].tile_id = result_tile_id;
|
||||
|
||||
tilesheet->autotile_rules[base_tile_id] = new_rules;
|
||||
tilesheet->autotile_rule_counts[base_tile_id]++;
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
u16 pxl8_tilesheet_apply_autotile(const pxl8_tilesheet* tilesheet, u16 base_tile_id, u8 neighbors) {
|
||||
if (!tilesheet || !tilesheet->autotile_rules || base_tile_id == 0 ||
|
||||
base_tile_id > tilesheet->total_tiles) {
|
||||
return base_tile_id;
|
||||
}
|
||||
|
||||
pxl8_autotile_rule* rules = tilesheet->autotile_rules[base_tile_id];
|
||||
u32 rule_count = tilesheet->autotile_rule_counts[base_tile_id];
|
||||
|
||||
for (u32 i = 0; i < rule_count; i++) {
|
||||
if (rules[i].neighbor_mask == neighbors) {
|
||||
return rules[i].tile_id;
|
||||
}
|
||||
}
|
||||
|
||||
return base_tile_id;
|
||||
}
|
||||
38
src/gfx/pxl8_tilesheet.h
Normal file
38
src/gfx/pxl8_tilesheet.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_autotile_rule pxl8_autotile_rule;
|
||||
typedef struct pxl8_tile_animation pxl8_tile_animation;
|
||||
typedef struct pxl8_tile_properties pxl8_tile_properties;
|
||||
typedef struct pxl8_tilesheet pxl8_tilesheet;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_tilesheet* pxl8_tilesheet_create(u32 tile_size);
|
||||
void pxl8_tilesheet_destroy(pxl8_tilesheet* tilesheet);
|
||||
|
||||
u16 pxl8_tilesheet_get_animated_frame(const pxl8_tilesheet* tilesheet, u16 tile_id);
|
||||
const pxl8_tile_properties* pxl8_tilesheet_get_tile_property(const pxl8_tilesheet* tilesheet, u16 tile_id);
|
||||
u32 pxl8_tilesheet_get_tile_size(const pxl8_tilesheet* tilesheet);
|
||||
bool pxl8_tilesheet_is_tile_valid(const pxl8_tilesheet* tilesheet, u16 tile_id);
|
||||
|
||||
pxl8_result pxl8_tilesheet_set_tile_pixels(pxl8_tilesheet* tilesheet, u16 tile_id, const u8* pixels);
|
||||
void pxl8_tilesheet_set_tile_property(pxl8_tilesheet* tilesheet, u16 tile_id, const pxl8_tile_properties* props);
|
||||
|
||||
pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, pxl8_gfx* gfx);
|
||||
void pxl8_tilesheet_ref(pxl8_tilesheet* tilesheet);
|
||||
void pxl8_tilesheet_unref(pxl8_tilesheet* tilesheet);
|
||||
|
||||
pxl8_result pxl8_tilesheet_add_animation(pxl8_tilesheet* tilesheet, u16 base_tile_id, const u16* frames, u16 frame_count, f32 frame_duration);
|
||||
pxl8_result pxl8_tilesheet_add_autotile_rule(pxl8_tilesheet* tilesheet, u16 base_tile_id, u8 neighbor_mask, u16 result_tile_id);
|
||||
u16 pxl8_tilesheet_apply_autotile(const pxl8_tilesheet* tilesheet, u16 base_tile_id, u8 neighbors);
|
||||
void pxl8_tilesheet_render_tile(const pxl8_tilesheet* tilesheet, pxl8_gfx* gfx, u16 tile_id, i32 x, i32 y, u8 flags);
|
||||
void pxl8_tilesheet_update_animations(pxl8_tilesheet* tilesheet, f32 delta_time);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
252
src/gfx/pxl8_transition.c
Normal file
252
src/gfx/pxl8_transition.c
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
#include "pxl8_transition.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_math.h"
|
||||
|
||||
pxl8_transition* pxl8_transition_create(pxl8_transition_type type, f32 duration) {
|
||||
if (duration <= 0.0f) {
|
||||
pxl8_error("Invalid transition duration: %f", duration);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pxl8_transition* transition = (pxl8_transition*)calloc(1, sizeof(pxl8_transition));
|
||||
if (!transition) {
|
||||
pxl8_error("Failed to allocate transition");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
transition->type = type;
|
||||
transition->duration = duration;
|
||||
transition->time = 0.0f;
|
||||
transition->active = false;
|
||||
transition->reverse = false;
|
||||
transition->color = 0xFF000000;
|
||||
transition->on_complete = NULL;
|
||||
transition->userdata = NULL;
|
||||
|
||||
return transition;
|
||||
}
|
||||
|
||||
void pxl8_transition_destroy(pxl8_transition* transition) {
|
||||
if (!transition) return;
|
||||
free(transition);
|
||||
}
|
||||
|
||||
f32 pxl8_transition_get_progress(const pxl8_transition* transition) {
|
||||
if (!transition) return 0.0f;
|
||||
|
||||
f32 t = transition->time / transition->duration;
|
||||
if (t < 0.0f) t = 0.0f;
|
||||
if (t > 1.0f) t = 1.0f;
|
||||
|
||||
return transition->reverse ? 1.0f - t : t;
|
||||
}
|
||||
|
||||
bool pxl8_transition_is_active(const pxl8_transition* transition) {
|
||||
return transition && transition->active;
|
||||
}
|
||||
|
||||
bool pxl8_transition_is_complete(const pxl8_transition* transition) {
|
||||
if (!transition) return true;
|
||||
return transition->time >= transition->duration;
|
||||
}
|
||||
|
||||
void pxl8_transition_render(const pxl8_transition* transition, pxl8_gfx* gfx) {
|
||||
if (!transition || !gfx || !transition->active) return;
|
||||
|
||||
f32 progress = pxl8_transition_get_progress(transition);
|
||||
i32 width = pxl8_gfx_get_width(gfx);
|
||||
i32 height = pxl8_gfx_get_height(gfx);
|
||||
|
||||
switch (transition->type) {
|
||||
case PXL8_TRANSITION_FADE: {
|
||||
u8 alpha = (u8)(progress * 255.0f);
|
||||
u32 fade_color = (transition->color & 0x00FFFFFF) | (alpha << 24);
|
||||
|
||||
for (i32 y = 0; y < height; y++) {
|
||||
for (i32 x = 0; x < width; x++) {
|
||||
u32 bg = pxl8_2d_get_pixel(gfx, x, y);
|
||||
u32 r_bg = (bg >> 16) & 0xFF;
|
||||
u32 g_bg = (bg >> 8) & 0xFF;
|
||||
u32 b_bg = bg & 0xFF;
|
||||
|
||||
u32 r_fg = (fade_color >> 16) & 0xFF;
|
||||
u32 g_fg = (fade_color >> 8) & 0xFF;
|
||||
u32 b_fg = fade_color & 0xFF;
|
||||
|
||||
u32 r = (r_bg * (255 - alpha) + r_fg * alpha) / 255;
|
||||
u32 g = (g_bg * (255 - alpha) + g_fg * alpha) / 255;
|
||||
u32 b = (b_bg * (255 - alpha) + b_fg * alpha) / 255;
|
||||
|
||||
pxl8_2d_pixel(gfx, x, y, 0xFF000000 | (r << 16) | (g << 8) | b);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PXL8_TRANSITION_WIPE_LEFT: {
|
||||
i32 wipe_x = (i32)(width * progress);
|
||||
pxl8_2d_rect_fill(gfx, 0, 0, wipe_x, height, transition->color);
|
||||
break;
|
||||
}
|
||||
|
||||
case PXL8_TRANSITION_WIPE_RIGHT: {
|
||||
i32 wipe_x = (i32)(width * (1.0f - progress));
|
||||
pxl8_2d_rect_fill(gfx, wipe_x, 0, width - wipe_x, height, transition->color);
|
||||
break;
|
||||
}
|
||||
|
||||
case PXL8_TRANSITION_WIPE_UP: {
|
||||
i32 wipe_y = (i32)(height * progress);
|
||||
pxl8_2d_rect_fill(gfx, 0, 0, width, wipe_y, transition->color);
|
||||
break;
|
||||
}
|
||||
|
||||
case PXL8_TRANSITION_WIPE_DOWN: {
|
||||
i32 wipe_y = (i32)(height * (1.0f - progress));
|
||||
pxl8_2d_rect_fill(gfx, 0, wipe_y, width, height - wipe_y, transition->color);
|
||||
break;
|
||||
}
|
||||
|
||||
case PXL8_TRANSITION_CIRCLE_CLOSE: {
|
||||
i32 center_x = width / 2;
|
||||
i32 center_y = height / 2;
|
||||
i32 max_radius = (i32)sqrtf((f32)(center_x * center_x + center_y * center_y));
|
||||
i32 radius = (i32)(max_radius * (1.0f - progress));
|
||||
|
||||
for (i32 y = 0; y < height; y++) {
|
||||
for (i32 x = 0; x < width; x++) {
|
||||
i32 dx = x - center_x;
|
||||
i32 dy = y - center_y;
|
||||
i32 dist = (i32)sqrtf((f32)(dx * dx + dy * dy));
|
||||
if (dist > radius) {
|
||||
pxl8_2d_pixel(gfx, x, y, transition->color);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PXL8_TRANSITION_CIRCLE_OPEN: {
|
||||
i32 center_x = width / 2;
|
||||
i32 center_y = height / 2;
|
||||
i32 max_radius = (i32)sqrtf((f32)(center_x * center_x + center_y * center_y));
|
||||
i32 radius = (i32)(max_radius * progress);
|
||||
|
||||
for (i32 y = 0; y < height; y++) {
|
||||
for (i32 x = 0; x < width; x++) {
|
||||
i32 dx = x - center_x;
|
||||
i32 dy = y - center_y;
|
||||
i32 dist = (i32)sqrtf((f32)(dx * dx + dy * dy));
|
||||
if (dist < radius) {
|
||||
pxl8_2d_pixel(gfx, x, y, transition->color);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PXL8_TRANSITION_DISSOLVE: {
|
||||
u32 seed = 12345;
|
||||
for (i32 y = 0; y < height; y++) {
|
||||
for (i32 x = 0; x < width; x++) {
|
||||
seed = seed * 1103515245 + 12345;
|
||||
f32 noise = (f32)((seed / 65536) % 1000) / 1000.0f;
|
||||
if (noise < progress) {
|
||||
pxl8_2d_pixel(gfx, x, y, transition->color);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case PXL8_TRANSITION_PIXELATE: {
|
||||
i32 max_block_size = 32;
|
||||
i32 block_size = (i32)(max_block_size * progress);
|
||||
if (block_size < 1) block_size = 1;
|
||||
|
||||
pxl8_pixel_mode mode = pxl8_gfx_get_pixel_mode(gfx);
|
||||
bool has_fb = (mode == PXL8_PIXEL_HICOLOR)
|
||||
? (pxl8_gfx_get_framebuffer_hicolor(gfx) != NULL)
|
||||
: (pxl8_gfx_get_framebuffer_indexed(gfx) != NULL);
|
||||
if (!has_fb) break;
|
||||
|
||||
for (i32 y = 0; y < height; y += block_size) {
|
||||
for (i32 x = 0; x < width; x += block_size) {
|
||||
u32 color_sum_r = 0, color_sum_g = 0, color_sum_b = 0;
|
||||
i32 count = 0;
|
||||
|
||||
for (i32 by = 0; by < block_size && y + by < height; by++) {
|
||||
for (i32 bx = 0; bx < block_size && x + bx < width; bx++) {
|
||||
u32 color = pxl8_2d_get_pixel(gfx, x + bx, y + by);
|
||||
color_sum_r += (color >> 16) & 0xFF;
|
||||
color_sum_g += (color >> 8) & 0xFF;
|
||||
color_sum_b += color & 0xFF;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
u32 avg_color = 0xFF000000 |
|
||||
((color_sum_r / count) << 16) |
|
||||
((color_sum_g / count) << 8) |
|
||||
(color_sum_b / count);
|
||||
|
||||
for (i32 by = 0; by < block_size && y + by < height; by++) {
|
||||
for (i32 bx = 0; bx < block_size && x + bx < width; bx++) {
|
||||
pxl8_2d_pixel(gfx, x + bx, y + by, avg_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_transition_reset(pxl8_transition* transition) {
|
||||
if (!transition) return;
|
||||
transition->time = 0.0f;
|
||||
transition->active = false;
|
||||
}
|
||||
|
||||
void pxl8_transition_set_color(pxl8_transition* transition, u32 color) {
|
||||
if (!transition) return;
|
||||
transition->color = color;
|
||||
}
|
||||
|
||||
void pxl8_transition_set_reverse(pxl8_transition* transition, bool reverse) {
|
||||
if (!transition) return;
|
||||
transition->reverse = reverse;
|
||||
}
|
||||
|
||||
void pxl8_transition_start(pxl8_transition* transition) {
|
||||
if (!transition) return;
|
||||
transition->active = true;
|
||||
transition->time = 0.0f;
|
||||
}
|
||||
|
||||
void pxl8_transition_stop(pxl8_transition* transition) {
|
||||
if (!transition) return;
|
||||
transition->active = false;
|
||||
}
|
||||
|
||||
void pxl8_transition_update(pxl8_transition* transition, f32 dt) {
|
||||
if (!transition || !transition->active) return;
|
||||
|
||||
transition->time += dt;
|
||||
|
||||
if (transition->time >= transition->duration) {
|
||||
transition->time = transition->duration;
|
||||
transition->active = false;
|
||||
|
||||
if (transition->on_complete) {
|
||||
transition->on_complete(transition->userdata);
|
||||
}
|
||||
}
|
||||
}
|
||||
53
src/gfx/pxl8_transition.h
Normal file
53
src/gfx/pxl8_transition.h
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef enum pxl8_transition_type {
|
||||
PXL8_TRANSITION_FADE,
|
||||
PXL8_TRANSITION_WIPE_LEFT,
|
||||
PXL8_TRANSITION_WIPE_RIGHT,
|
||||
PXL8_TRANSITION_WIPE_UP,
|
||||
PXL8_TRANSITION_WIPE_DOWN,
|
||||
PXL8_TRANSITION_CIRCLE_OPEN,
|
||||
PXL8_TRANSITION_CIRCLE_CLOSE,
|
||||
PXL8_TRANSITION_DISSOLVE,
|
||||
PXL8_TRANSITION_PIXELATE
|
||||
} pxl8_transition_type;
|
||||
|
||||
typedef struct pxl8_transition {
|
||||
pxl8_transition_type type;
|
||||
f32 duration;
|
||||
f32 time;
|
||||
bool active;
|
||||
bool reverse;
|
||||
|
||||
u32 color;
|
||||
|
||||
void (*on_complete)(void* userdata);
|
||||
void* userdata;
|
||||
} pxl8_transition;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_transition* pxl8_transition_create(pxl8_transition_type type, f32 duration);
|
||||
void pxl8_transition_destroy(pxl8_transition* transition);
|
||||
|
||||
f32 pxl8_transition_get_progress(const pxl8_transition* transition);
|
||||
bool pxl8_transition_is_active(const pxl8_transition* transition);
|
||||
bool pxl8_transition_is_complete(const pxl8_transition* transition);
|
||||
|
||||
void pxl8_transition_set_color(pxl8_transition* transition, u32 color);
|
||||
void pxl8_transition_set_reverse(pxl8_transition* transition, bool reverse);
|
||||
|
||||
void pxl8_transition_render(const pxl8_transition* transition, pxl8_gfx* gfx);
|
||||
void pxl8_transition_reset(pxl8_transition* transition);
|
||||
void pxl8_transition_start(pxl8_transition* transition);
|
||||
void pxl8_transition_stop(pxl8_transition* transition);
|
||||
void pxl8_transition_update(pxl8_transition* transition, f32 dt);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
23
src/hal/pxl8_hal.h
Normal file
23
src/hal/pxl8_hal.h
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_hal {
|
||||
void* (*create)(i32 render_w, i32 render_h,
|
||||
const char* title, i32 win_w, i32 win_h);
|
||||
void (*destroy)(void* platform_data);
|
||||
u64 (*get_ticks)(void);
|
||||
void (*center_cursor)(void* platform_data);
|
||||
void (*present)(void* platform_data);
|
||||
void (*set_cursor)(void* platform_data, u32 cursor);
|
||||
void (*set_relative_mouse_mode)(void* platform_data, bool enabled);
|
||||
void (*upload_texture)(void* platform_data, const void* pixels, u32 w, u32 h,
|
||||
u32 bpp, const u32* palette);
|
||||
|
||||
void* (*audio_create)(i32 sample_rate, i32 channels);
|
||||
void (*audio_destroy)(void* audio_handle);
|
||||
void (*audio_start)(void* audio_handle);
|
||||
void (*audio_stop)(void* audio_handle);
|
||||
bool (*upload_audio)(void* audio_handle, const f32* stereo_samples, i32 sample_count);
|
||||
i32 (*audio_queued)(void* audio_handle);
|
||||
} pxl8_hal;
|
||||
410
src/hal/pxl8_sdl3.c
Normal file
410
src/hal/pxl8_sdl3.c
Normal file
|
|
@ -0,0 +1,410 @@
|
|||
#include "pxl8_sdl3.h"
|
||||
|
||||
#define SDL_MAIN_USE_CALLBACKS
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_main.h>
|
||||
|
||||
#include "pxl8_color.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_sys.h"
|
||||
|
||||
typedef struct pxl8_sdl3_context {
|
||||
SDL_Texture* framebuffer;
|
||||
SDL_Renderer* renderer;
|
||||
SDL_Window* window;
|
||||
|
||||
u32* rgba_buffer;
|
||||
size_t rgba_buffer_size;
|
||||
} pxl8_sdl3_context;
|
||||
|
||||
static void* sdl3_create(i32 render_w, i32 render_h,
|
||||
const char* title, i32 win_w, i32 win_h) {
|
||||
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)SDL_calloc(1, sizeof(pxl8_sdl3_context));
|
||||
if (!ctx) {
|
||||
pxl8_error("Failed to allocate SDL3 context");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ctx->window = SDL_CreateWindow(title, win_w, win_h, SDL_WINDOW_RESIZABLE);
|
||||
if (!ctx->window) {
|
||||
pxl8_error("Failed to create window: %s", SDL_GetError());
|
||||
SDL_free(ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ctx->renderer = SDL_CreateRenderer(ctx->window, NULL);
|
||||
if (!ctx->renderer) {
|
||||
pxl8_error("Failed to create renderer: %s", SDL_GetError());
|
||||
SDL_DestroyWindow(ctx->window);
|
||||
SDL_free(ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!SDL_SetRenderVSync(ctx->renderer, 1)) {
|
||||
pxl8_error("Failed to set vsync: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
ctx->framebuffer = SDL_CreateTexture(ctx->renderer,
|
||||
SDL_PIXELFORMAT_RGBA32,
|
||||
SDL_TEXTUREACCESS_STREAMING,
|
||||
render_w, render_h);
|
||||
if (!ctx->framebuffer) {
|
||||
pxl8_error("Failed to create framebuffer texture: %s", SDL_GetError());
|
||||
SDL_DestroyRenderer(ctx->renderer);
|
||||
SDL_DestroyWindow(ctx->window);
|
||||
SDL_free(ctx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SDL_SetTextureScaleMode(ctx->framebuffer, SDL_SCALEMODE_NEAREST);
|
||||
|
||||
ctx->rgba_buffer = NULL;
|
||||
ctx->rgba_buffer_size = 0;
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
static void sdl3_destroy(void* platform_data) {
|
||||
if (!platform_data) return;
|
||||
|
||||
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
|
||||
|
||||
if (ctx->rgba_buffer) SDL_free(ctx->rgba_buffer);
|
||||
if (ctx->framebuffer) SDL_DestroyTexture(ctx->framebuffer);
|
||||
if (ctx->renderer) SDL_DestroyRenderer(ctx->renderer);
|
||||
if (ctx->window) SDL_DestroyWindow(ctx->window);
|
||||
|
||||
SDL_free(ctx);
|
||||
}
|
||||
|
||||
static u64 sdl3_get_ticks(void) {
|
||||
return SDL_GetTicksNS();
|
||||
}
|
||||
|
||||
static void sdl3_present(void* platform_data) {
|
||||
if (!platform_data) return;
|
||||
|
||||
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
|
||||
|
||||
SDL_SetRenderDrawColor(ctx->renderer, 0, 0, 0, 255);
|
||||
SDL_RenderClear(ctx->renderer);
|
||||
|
||||
if (ctx->framebuffer) {
|
||||
SDL_RenderTexture(ctx->renderer, ctx->framebuffer, NULL, NULL);
|
||||
}
|
||||
|
||||
SDL_RenderPresent(ctx->renderer);
|
||||
}
|
||||
|
||||
static void sdl3_upload_texture(void* platform_data, const void* pixels, u32 w, u32 h,
|
||||
u32 bpp, const u32* palette) {
|
||||
if (!platform_data || !pixels) return;
|
||||
|
||||
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
|
||||
size_t pixel_count = w * h;
|
||||
|
||||
if (bpp == 4) {
|
||||
SDL_UpdateTexture(ctx->framebuffer, NULL, pixels, w * 4);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx->rgba_buffer_size < pixel_count) {
|
||||
u32* new_buffer = (u32*)SDL_realloc(ctx->rgba_buffer, pixel_count * 4);
|
||||
if (!new_buffer) return;
|
||||
ctx->rgba_buffer = new_buffer;
|
||||
ctx->rgba_buffer_size = pixel_count;
|
||||
}
|
||||
|
||||
if (bpp == 2) {
|
||||
const u16* pixels16 = (const u16*)pixels;
|
||||
for (u32 i = 0; i < pixel_count; i++) {
|
||||
ctx->rgba_buffer[i] = pxl8_rgb565_to_rgba32(pixels16[i]);
|
||||
}
|
||||
} else {
|
||||
const u8* pixels8 = (const u8*)pixels;
|
||||
for (u32 i = 0; i < pixel_count; i++) {
|
||||
ctx->rgba_buffer[i] = palette[pixels8[i]];
|
||||
}
|
||||
}
|
||||
|
||||
SDL_UpdateTexture(ctx->framebuffer, NULL, ctx->rgba_buffer, w * 4);
|
||||
}
|
||||
|
||||
SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {
|
||||
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_AUDIO)) {
|
||||
pxl8_error("SDL_Init failed: %s", SDL_GetError());
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
pxl8* sys = pxl8_create(&pxl8_hal_sdl3);
|
||||
if (!sys) {
|
||||
pxl8_error("Failed to create pxl8 system");
|
||||
SDL_Quit();
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
pxl8_result result = pxl8_init(sys, argc, argv);
|
||||
if (result != PXL8_OK) {
|
||||
pxl8_destroy(sys);
|
||||
SDL_Quit();
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
*appstate = sys;
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
SDL_AppResult SDL_AppIterate(void* appstate) {
|
||||
pxl8* sys = (pxl8*)appstate;
|
||||
if (!sys) {
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
pxl8_result update_result = pxl8_update(sys);
|
||||
if (update_result != PXL8_OK) {
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
pxl8_result frame_result = pxl8_frame(sys);
|
||||
if (frame_result != PXL8_OK) {
|
||||
return SDL_APP_FAILURE;
|
||||
}
|
||||
|
||||
return pxl8_is_running(sys) ? SDL_APP_CONTINUE : SDL_APP_SUCCESS;
|
||||
}
|
||||
|
||||
SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) {
|
||||
pxl8* sys = (pxl8*)appstate;
|
||||
if (!sys) {
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
pxl8_input_state* input = pxl8_get_input(sys);
|
||||
if (!input) {
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
switch (event->type) {
|
||||
case SDL_EVENT_QUIT:
|
||||
pxl8_set_running(sys, false);
|
||||
break;
|
||||
|
||||
case SDL_EVENT_KEY_DOWN: {
|
||||
SDL_Scancode scancode = event->key.scancode;
|
||||
if (scancode < PXL8_MAX_KEYS) {
|
||||
if (!input->keys_down[scancode]) {
|
||||
input->keys_pressed[scancode] = true;
|
||||
}
|
||||
input->keys_down[scancode] = true;
|
||||
input->keys_released[scancode] = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_EVENT_KEY_UP: {
|
||||
SDL_Scancode scancode = event->key.scancode;
|
||||
if (scancode < PXL8_MAX_KEYS) {
|
||||
input->keys_down[scancode] = false;
|
||||
input->keys_pressed[scancode] = false;
|
||||
input->keys_released[scancode] = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN: {
|
||||
u8 button = event->button.button - 1;
|
||||
if (button < 3) {
|
||||
if (!input->mouse_buttons_down[button]) {
|
||||
input->mouse_buttons_pressed[button] = true;
|
||||
}
|
||||
input->mouse_buttons_down[button] = true;
|
||||
input->mouse_buttons_released[button] = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP: {
|
||||
u8 button = event->button.button - 1;
|
||||
if (button < 3) {
|
||||
input->mouse_buttons_down[button] = false;
|
||||
input->mouse_buttons_pressed[button] = false;
|
||||
input->mouse_buttons_released[button] = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_EVENT_MOUSE_MOTION: {
|
||||
pxl8_gfx* gfx = pxl8_get_gfx(sys);
|
||||
if (!gfx) break;
|
||||
|
||||
input->mouse_dx = (i32)event->motion.xrel;
|
||||
input->mouse_dy = (i32)event->motion.yrel;
|
||||
|
||||
i32 window_mouse_x = (i32)event->motion.x;
|
||||
i32 window_mouse_y = (i32)event->motion.y;
|
||||
|
||||
SDL_Window* window = SDL_GetWindowFromID(event->motion.windowID);
|
||||
if (!window) break;
|
||||
|
||||
i32 window_width, window_height;
|
||||
SDL_GetWindowSize(window, &window_width, &window_height);
|
||||
|
||||
i32 render_w = pxl8_gfx_get_width(gfx);
|
||||
i32 render_h = pxl8_gfx_get_height(gfx);
|
||||
|
||||
pxl8_bounds window_bounds = {0, 0, window_width, window_height};
|
||||
pxl8_viewport vp = pxl8_gfx_viewport(window_bounds, render_w, render_h);
|
||||
|
||||
input->mouse_x = (i32)((window_mouse_x - vp.offset_x) / vp.scale);
|
||||
input->mouse_y = (i32)((window_mouse_y - vp.offset_y) / vp.scale);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_EVENT_MOUSE_WHEEL: {
|
||||
input->mouse_wheel_x = (i32)event->wheel.x;
|
||||
input->mouse_wheel_y = (i32)event->wheel.y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
|
||||
void SDL_AppQuit(void* appstate, SDL_AppResult result) {
|
||||
(void)result;
|
||||
|
||||
pxl8* sys = (pxl8*)appstate;
|
||||
if (sys) {
|
||||
pxl8_quit(sys);
|
||||
pxl8_destroy(sys);
|
||||
}
|
||||
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
static void sdl3_set_relative_mouse_mode(void* platform_data, bool enabled) {
|
||||
if (!platform_data) return;
|
||||
|
||||
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
|
||||
if (!SDL_SetWindowRelativeMouseMode(ctx->window, enabled)) {
|
||||
pxl8_error("Failed to set relative mouse mode: %s", SDL_GetError());
|
||||
}
|
||||
}
|
||||
|
||||
static void sdl3_set_cursor(void* platform_data, u32 cursor) {
|
||||
if (!platform_data) return;
|
||||
|
||||
SDL_SystemCursor sdl_cursor;
|
||||
switch (cursor) {
|
||||
case PXL8_CURSOR_ARROW:
|
||||
sdl_cursor = SDL_SYSTEM_CURSOR_DEFAULT;
|
||||
break;
|
||||
case PXL8_CURSOR_HAND:
|
||||
sdl_cursor = SDL_SYSTEM_CURSOR_POINTER;
|
||||
break;
|
||||
default:
|
||||
sdl_cursor = SDL_SYSTEM_CURSOR_DEFAULT;
|
||||
break;
|
||||
}
|
||||
|
||||
SDL_Cursor* cursor_obj = SDL_CreateSystemCursor(sdl_cursor);
|
||||
if (cursor_obj) {
|
||||
SDL_SetCursor(cursor_obj);
|
||||
}
|
||||
}
|
||||
|
||||
static void sdl3_center_cursor(void* platform_data) {
|
||||
if (!platform_data) return;
|
||||
|
||||
pxl8_sdl3_context* ctx = (pxl8_sdl3_context*)platform_data;
|
||||
i32 w, h;
|
||||
if (SDL_GetWindowSize(ctx->window, &w, &h)) {
|
||||
SDL_WarpMouseInWindow(ctx->window, w / 2, h / 2);
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct pxl8_sdl3_audio {
|
||||
SDL_AudioStream* stream;
|
||||
i32 sample_rate;
|
||||
i32 channels;
|
||||
} pxl8_sdl3_audio;
|
||||
|
||||
static void* sdl3_audio_create(i32 sample_rate, i32 channels) {
|
||||
pxl8_sdl3_audio* audio = (pxl8_sdl3_audio*)SDL_calloc(1, sizeof(pxl8_sdl3_audio));
|
||||
if (!audio) return NULL;
|
||||
|
||||
SDL_AudioSpec spec = {
|
||||
.freq = sample_rate,
|
||||
.channels = channels,
|
||||
.format = SDL_AUDIO_F32
|
||||
};
|
||||
|
||||
audio->stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, NULL, NULL);
|
||||
if (!audio->stream) {
|
||||
pxl8_error("Failed to open audio device: %s", SDL_GetError());
|
||||
SDL_free(audio);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
audio->sample_rate = sample_rate;
|
||||
audio->channels = channels;
|
||||
|
||||
return audio;
|
||||
}
|
||||
|
||||
static void sdl3_audio_destroy(void* audio_handle) {
|
||||
if (!audio_handle) return;
|
||||
|
||||
pxl8_sdl3_audio* audio = (pxl8_sdl3_audio*)audio_handle;
|
||||
if (audio->stream) {
|
||||
SDL_DestroyAudioStream(audio->stream);
|
||||
}
|
||||
SDL_free(audio);
|
||||
}
|
||||
|
||||
static void sdl3_audio_start(void* audio_handle) {
|
||||
if (!audio_handle) return;
|
||||
|
||||
pxl8_sdl3_audio* audio = (pxl8_sdl3_audio*)audio_handle;
|
||||
SDL_ResumeAudioStreamDevice(audio->stream);
|
||||
}
|
||||
|
||||
static void sdl3_audio_stop(void* audio_handle) {
|
||||
if (!audio_handle) return;
|
||||
|
||||
pxl8_sdl3_audio* audio = (pxl8_sdl3_audio*)audio_handle;
|
||||
SDL_PauseAudioStreamDevice(audio->stream);
|
||||
}
|
||||
|
||||
static bool sdl3_upload_audio(void* audio_handle, const f32* stereo_samples, i32 sample_count) {
|
||||
if (!audio_handle || !stereo_samples) return false;
|
||||
|
||||
pxl8_sdl3_audio* audio = (pxl8_sdl3_audio*)audio_handle;
|
||||
return SDL_PutAudioStreamData(audio->stream, stereo_samples,
|
||||
sample_count * audio->channels * sizeof(f32));
|
||||
}
|
||||
|
||||
static i32 sdl3_audio_queued(void* audio_handle) {
|
||||
if (!audio_handle) return 0;
|
||||
|
||||
pxl8_sdl3_audio* audio = (pxl8_sdl3_audio*)audio_handle;
|
||||
i32 bytes = SDL_GetAudioStreamQueued(audio->stream);
|
||||
return bytes / (audio->channels * sizeof(f32));
|
||||
}
|
||||
|
||||
const pxl8_hal pxl8_hal_sdl3 = {
|
||||
.create = sdl3_create,
|
||||
.destroy = sdl3_destroy,
|
||||
.get_ticks = sdl3_get_ticks,
|
||||
.center_cursor = sdl3_center_cursor,
|
||||
.present = sdl3_present,
|
||||
.set_cursor = sdl3_set_cursor,
|
||||
.set_relative_mouse_mode = sdl3_set_relative_mouse_mode,
|
||||
.upload_texture = sdl3_upload_texture,
|
||||
.audio_create = sdl3_audio_create,
|
||||
.audio_destroy = sdl3_audio_destroy,
|
||||
.audio_start = sdl3_audio_start,
|
||||
.audio_stop = sdl3_audio_stop,
|
||||
.upload_audio = sdl3_upload_audio,
|
||||
.audio_queued = sdl3_audio_queued,
|
||||
};
|
||||
5
src/hal/pxl8_sdl3.h
Normal file
5
src/hal/pxl8_sdl3.h
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_hal.h"
|
||||
|
||||
extern const pxl8_hal pxl8_hal_sdl3;
|
||||
208
src/lua/pxl8.lua
Normal file
208
src/lua/pxl8.lua
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
local anim = require("pxl8.anim")
|
||||
local bytes = require("pxl8.bytes")
|
||||
local core = require("pxl8.core")
|
||||
local gfx2d = require("pxl8.gfx2d")
|
||||
local gfx3d = require("pxl8.gfx3d")
|
||||
local gui = require("pxl8.gui")
|
||||
local input = require("pxl8.input")
|
||||
local math3d = require("pxl8.math")
|
||||
local net = require("pxl8.net")
|
||||
local particles = require("pxl8.particles")
|
||||
local sfx = require("pxl8.sfx")
|
||||
local tilemap = require("pxl8.tilemap")
|
||||
local transition = require("pxl8.transition")
|
||||
local world = require("pxl8.world")
|
||||
local pxl8 = {}
|
||||
|
||||
core.init(pxl8_gfx, pxl8_input, pxl8_rng, pxl8_sfx, pxl8_sys)
|
||||
|
||||
pxl8.get_fps = core.get_fps
|
||||
pxl8.get_height = core.get_height
|
||||
pxl8.get_title = core.get_title
|
||||
pxl8.get_width = core.get_width
|
||||
pxl8.info = core.info
|
||||
pxl8.warn = core.warn
|
||||
pxl8.error = core.error
|
||||
pxl8.debug = core.debug
|
||||
pxl8.trace = core.trace
|
||||
pxl8.quit = core.quit
|
||||
|
||||
pxl8.rng_seed = core.rng_seed
|
||||
pxl8.rng_next = core.rng_next
|
||||
pxl8.rng_f32 = core.rng_f32
|
||||
pxl8.rng_range = core.rng_range
|
||||
|
||||
pxl8.find_color = core.find_color
|
||||
pxl8.palette_color = core.palette_color
|
||||
pxl8.palette_index = core.palette_index
|
||||
pxl8.ramp_index = core.ramp_index
|
||||
pxl8.set_palette_rgb = core.set_palette_rgb
|
||||
|
||||
pxl8.clear = gfx2d.clear
|
||||
pxl8.pixel = gfx2d.pixel
|
||||
pxl8.line = gfx2d.line
|
||||
pxl8.rect = gfx2d.rect
|
||||
pxl8.rect_fill = gfx2d.rect_fill
|
||||
pxl8.circle = gfx2d.circle
|
||||
pxl8.circle_fill = gfx2d.circle_fill
|
||||
pxl8.text = gfx2d.text
|
||||
pxl8.sprite = gfx2d.sprite
|
||||
pxl8.load_palette = gfx2d.load_palette
|
||||
pxl8.load_sprite = gfx2d.load_sprite
|
||||
pxl8.create_texture = gfx2d.create_texture
|
||||
pxl8.gfx_color_ramp = gfx2d.color_ramp
|
||||
pxl8.gfx_fade_palette = gfx2d.fade_palette
|
||||
pxl8.gfx_cycle_palette = gfx2d.cycle_palette
|
||||
pxl8.add_palette_cycle = gfx2d.add_palette_cycle
|
||||
pxl8.remove_palette_cycle = gfx2d.remove_palette_cycle
|
||||
pxl8.set_palette_cycle_speed = gfx2d.set_palette_cycle_speed
|
||||
pxl8.clear_palette_cycles = gfx2d.clear_palette_cycles
|
||||
|
||||
pxl8.key_down = input.key_down
|
||||
pxl8.key_pressed = input.key_pressed
|
||||
pxl8.key_released = input.key_released
|
||||
pxl8.mouse_dx = input.mouse_dx
|
||||
pxl8.mouse_dy = input.mouse_dy
|
||||
pxl8.mouse_wheel_x = input.mouse_wheel_x
|
||||
pxl8.mouse_wheel_y = input.mouse_wheel_y
|
||||
pxl8.mouse_x = input.mouse_x
|
||||
pxl8.mouse_y = input.mouse_y
|
||||
pxl8.get_mouse_pos = input.get_mouse_pos
|
||||
pxl8.mouse_pressed = input.mouse_pressed
|
||||
pxl8.mouse_released = input.mouse_released
|
||||
pxl8.center_cursor = input.center_cursor
|
||||
pxl8.set_cursor = input.set_cursor
|
||||
pxl8.set_relative_mouse_mode = input.set_relative_mouse_mode
|
||||
|
||||
pxl8.Anim = anim.Anim
|
||||
pxl8.create_anim = anim.Anim.new
|
||||
pxl8.create_anim_from_ase = anim.Anim.from_ase
|
||||
|
||||
pxl8.bounds = math3d.bounds
|
||||
|
||||
pxl8.Camera3D = gfx3d.Camera3D
|
||||
pxl8.create_camera_3d = gfx3d.Camera3D.new
|
||||
pxl8.begin_frame_3d = gfx3d.begin_frame
|
||||
pxl8.clear_3d = gfx3d.clear
|
||||
pxl8.clear_depth = gfx3d.clear_depth
|
||||
pxl8.draw_line_3d = gfx3d.draw_line
|
||||
pxl8.draw_mesh = gfx3d.draw_mesh
|
||||
pxl8.end_frame_3d = gfx3d.end_frame
|
||||
pxl8.Mesh = gfx3d.Mesh
|
||||
pxl8.create_mesh = gfx3d.Mesh.new
|
||||
|
||||
pxl8.Compressor = sfx.Compressor
|
||||
pxl8.create_compressor = sfx.Compressor.new
|
||||
pxl8.Delay = sfx.Delay
|
||||
pxl8.create_delay = sfx.Delay.new
|
||||
pxl8.Reverb = sfx.Reverb
|
||||
pxl8.create_reverb = sfx.Reverb.new
|
||||
|
||||
pxl8.Gui = gui.Gui
|
||||
pxl8.create_gui = gui.Gui.new
|
||||
pxl8.gui_label = gui.label
|
||||
pxl8.gui_window = gui.window
|
||||
|
||||
pxl8.mat4_identity = math3d.mat4_identity
|
||||
pxl8.mat4_lookat = math3d.mat4_lookat
|
||||
pxl8.mat4_multiply = math3d.mat4_multiply
|
||||
pxl8.mat4_ortho = math3d.mat4_ortho
|
||||
pxl8.mat4_perspective = math3d.mat4_perspective
|
||||
pxl8.mat4_rotate_x = math3d.mat4_rotate_x
|
||||
pxl8.mat4_rotate_y = math3d.mat4_rotate_y
|
||||
pxl8.mat4_rotate_z = math3d.mat4_rotate_z
|
||||
pxl8.mat4_scale = math3d.mat4_scale
|
||||
pxl8.mat4_translate = math3d.mat4_translate
|
||||
|
||||
pxl8.Net = net.Net
|
||||
pxl8.create_net = net.Net.new
|
||||
pxl8.NET_MODE_LOCAL = net.MODE_LOCAL
|
||||
pxl8.NET_MODE_REMOTE = net.MODE_REMOTE
|
||||
|
||||
pxl8.pack_f32_be = bytes.pack_f32_be
|
||||
pxl8.pack_f32_le = bytes.pack_f32_le
|
||||
pxl8.pack_f64_be = bytes.pack_f64_be
|
||||
pxl8.pack_f64_le = bytes.pack_f64_le
|
||||
pxl8.pack_i8 = bytes.pack_i8
|
||||
pxl8.pack_i16_be = bytes.pack_i16_be
|
||||
pxl8.pack_i16_le = bytes.pack_i16_le
|
||||
pxl8.pack_i32_be = bytes.pack_i32_be
|
||||
pxl8.pack_i32_le = bytes.pack_i32_le
|
||||
pxl8.pack_i64_be = bytes.pack_i64_be
|
||||
pxl8.pack_i64_le = bytes.pack_i64_le
|
||||
pxl8.pack_u8 = bytes.pack_u8
|
||||
pxl8.pack_u16_be = bytes.pack_u16_be
|
||||
pxl8.pack_u16_le = bytes.pack_u16_le
|
||||
pxl8.pack_u32_be = bytes.pack_u32_be
|
||||
pxl8.pack_u32_le = bytes.pack_u32_le
|
||||
pxl8.pack_u64_be = bytes.pack_u64_be
|
||||
pxl8.pack_u64_le = bytes.pack_u64_le
|
||||
|
||||
pxl8.Particles = particles.Particles
|
||||
pxl8.create_particles = particles.Particles.new
|
||||
|
||||
pxl8.PROCGEN_ROOMS = world.PROCGEN_ROOMS
|
||||
pxl8.PROCGEN_TERRAIN = world.PROCGEN_TERRAIN
|
||||
pxl8.procgen_tex = world.procgen_tex
|
||||
|
||||
pxl8.SfxContext = sfx.SfxContext
|
||||
pxl8.SfxNode = sfx.SfxNode
|
||||
pxl8.create_sfx_context = sfx.SfxContext.new
|
||||
pxl8.sfx_get_master_volume = sfx.get_master_volume
|
||||
pxl8.sfx_note_to_freq = sfx.note_to_freq
|
||||
pxl8.sfx_set_master_volume = sfx.set_master_volume
|
||||
pxl8.sfx_voice_params = sfx.voice_params
|
||||
|
||||
pxl8.SFX_FILTER_BANDPASS = sfx.FILTER_BANDPASS
|
||||
pxl8.SFX_FILTER_HIGHPASS = sfx.FILTER_HIGHPASS
|
||||
pxl8.SFX_FILTER_LOWPASS = sfx.FILTER_LOWPASS
|
||||
pxl8.SFX_FILTER_NONE = sfx.FILTER_NONE
|
||||
pxl8.SFX_LFO_AMPLITUDE = sfx.LFO_AMPLITUDE
|
||||
pxl8.SFX_LFO_FILTER = sfx.LFO_FILTER
|
||||
pxl8.SFX_LFO_PITCH = sfx.LFO_PITCH
|
||||
pxl8.SFX_NODE_COMPRESSOR = sfx.NODE_COMPRESSOR
|
||||
pxl8.SFX_NODE_DELAY = sfx.NODE_DELAY
|
||||
pxl8.SFX_NODE_REVERB = sfx.NODE_REVERB
|
||||
pxl8.SFX_WAVE_NOISE = sfx.WAVE_NOISE
|
||||
pxl8.SFX_WAVE_PULSE = sfx.WAVE_PULSE
|
||||
pxl8.SFX_WAVE_SAW = sfx.WAVE_SAW
|
||||
pxl8.SFX_WAVE_SINE = sfx.WAVE_SINE
|
||||
pxl8.SFX_WAVE_SQUARE = sfx.WAVE_SQUARE
|
||||
pxl8.SFX_WAVE_TRIANGLE = sfx.WAVE_TRIANGLE
|
||||
|
||||
pxl8.TILE_FLIP_X = tilemap.TILE_FLIP_X
|
||||
pxl8.TILE_FLIP_Y = tilemap.TILE_FLIP_Y
|
||||
pxl8.TILE_SOLID = tilemap.TILE_SOLID
|
||||
pxl8.TILE_TRIGGER = tilemap.TILE_TRIGGER
|
||||
pxl8.Tilemap = tilemap.Tilemap
|
||||
pxl8.create_tilemap = tilemap.Tilemap.new
|
||||
pxl8.Tilesheet = tilemap.Tilesheet
|
||||
pxl8.create_tilesheet = tilemap.Tilesheet.new
|
||||
|
||||
pxl8.Transition = transition.Transition
|
||||
pxl8.create_transition = transition.Transition.new
|
||||
pxl8.TRANSITION_TYPES = transition.TYPES
|
||||
|
||||
pxl8.unpack_f32_be = bytes.unpack_f32_be
|
||||
pxl8.unpack_f32_le = bytes.unpack_f32_le
|
||||
pxl8.unpack_f64_be = bytes.unpack_f64_be
|
||||
pxl8.unpack_f64_le = bytes.unpack_f64_le
|
||||
pxl8.unpack_i8 = bytes.unpack_i8
|
||||
pxl8.unpack_i16_be = bytes.unpack_i16_be
|
||||
pxl8.unpack_i16_le = bytes.unpack_i16_le
|
||||
pxl8.unpack_i32_be = bytes.unpack_i32_be
|
||||
pxl8.unpack_i32_le = bytes.unpack_i32_le
|
||||
pxl8.unpack_i64_be = bytes.unpack_i64_be
|
||||
pxl8.unpack_i64_le = bytes.unpack_i64_le
|
||||
pxl8.unpack_u8 = bytes.unpack_u8
|
||||
pxl8.unpack_u16_be = bytes.unpack_u16_be
|
||||
pxl8.unpack_u16_le = bytes.unpack_u16_le
|
||||
pxl8.unpack_u32_be = bytes.unpack_u32_be
|
||||
pxl8.unpack_u32_le = bytes.unpack_u32_le
|
||||
pxl8.unpack_u64_be = bytes.unpack_u64_be
|
||||
pxl8.unpack_u64_le = bytes.unpack_u64_le
|
||||
|
||||
pxl8.World = world.World
|
||||
pxl8.create_world = world.World.new
|
||||
|
||||
return pxl8
|
||||
126
src/lua/pxl8/anim.lua
Normal file
126
src/lua/pxl8/anim.lua
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
local core = require("pxl8.core")
|
||||
|
||||
local anim = {}
|
||||
|
||||
local Anim = {}
|
||||
Anim.__index = Anim
|
||||
|
||||
function Anim.new(frame_ids, frame_durations)
|
||||
local frame_count = #frame_ids
|
||||
local c_frame_ids = ffi.new("u32[?]", frame_count)
|
||||
local c_frame_durations = nil
|
||||
|
||||
for i = 1, frame_count do
|
||||
c_frame_ids[i-1] = frame_ids[i]
|
||||
end
|
||||
|
||||
if frame_durations then
|
||||
c_frame_durations = ffi.new("u16[?]", frame_count)
|
||||
for i = 1, frame_count do
|
||||
c_frame_durations[i-1] = frame_durations[i]
|
||||
end
|
||||
end
|
||||
|
||||
local a = C.pxl8_anim_create(c_frame_ids, c_frame_durations, frame_count)
|
||||
if a == nil then
|
||||
return nil
|
||||
end
|
||||
return setmetatable({ _ptr = a }, Anim)
|
||||
end
|
||||
|
||||
function Anim.from_ase(filepath)
|
||||
local a = C.pxl8_anim_create_from_ase(core.gfx, filepath)
|
||||
if a == nil then
|
||||
return nil
|
||||
end
|
||||
return setmetatable({ _ptr = a }, Anim)
|
||||
end
|
||||
|
||||
function Anim:add_state(name, state_anim)
|
||||
return C.pxl8_anim_add_state(self._ptr, name, state_anim._ptr)
|
||||
end
|
||||
|
||||
function Anim:destroy()
|
||||
if self._ptr then
|
||||
C.pxl8_anim_destroy(self._ptr)
|
||||
self._ptr = nil
|
||||
end
|
||||
end
|
||||
|
||||
function Anim:get_current_frame()
|
||||
return C.pxl8_anim_get_current_frame(self._ptr)
|
||||
end
|
||||
|
||||
function Anim:get_current_frame_id()
|
||||
return C.pxl8_anim_get_current_frame_id(self._ptr)
|
||||
end
|
||||
|
||||
function Anim:get_state()
|
||||
local state_name = C.pxl8_anim_get_state(self._ptr)
|
||||
if state_name ~= nil then
|
||||
return ffi.string(state_name)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function Anim:has_state_machine()
|
||||
return C.pxl8_anim_has_state_machine(self._ptr)
|
||||
end
|
||||
|
||||
function Anim:is_complete()
|
||||
return C.pxl8_anim_is_complete(self._ptr)
|
||||
end
|
||||
|
||||
function Anim:is_playing()
|
||||
return C.pxl8_anim_is_playing(self._ptr)
|
||||
end
|
||||
|
||||
function Anim:pause()
|
||||
C.pxl8_anim_pause(self._ptr)
|
||||
end
|
||||
|
||||
function Anim:play()
|
||||
C.pxl8_anim_play(self._ptr)
|
||||
end
|
||||
|
||||
function Anim:render(x, y, w, h, flip_x, flip_y)
|
||||
C.pxl8_anim_render_sprite(self._ptr, core.gfx, x, y, w, h, flip_x or false, flip_y or false)
|
||||
end
|
||||
|
||||
function Anim:reset()
|
||||
C.pxl8_anim_reset(self._ptr)
|
||||
end
|
||||
|
||||
function Anim:set_frame(frame)
|
||||
C.pxl8_anim_set_frame(self._ptr, frame)
|
||||
end
|
||||
|
||||
function Anim:set_loop(loop)
|
||||
C.pxl8_anim_set_loop(self._ptr, loop)
|
||||
end
|
||||
|
||||
function Anim:set_reverse(reverse)
|
||||
C.pxl8_anim_set_reverse(self._ptr, reverse)
|
||||
end
|
||||
|
||||
function Anim:set_speed(speed)
|
||||
C.pxl8_anim_set_speed(self._ptr, speed)
|
||||
end
|
||||
|
||||
function Anim:set_state(name)
|
||||
return C.pxl8_anim_set_state(self._ptr, name)
|
||||
end
|
||||
|
||||
function Anim:stop()
|
||||
C.pxl8_anim_stop(self._ptr)
|
||||
end
|
||||
|
||||
function Anim:update(dt)
|
||||
C.pxl8_anim_update(self._ptr, dt)
|
||||
end
|
||||
|
||||
anim.Anim = Anim
|
||||
|
||||
return anim
|
||||
44
src/lua/pxl8/bytes.lua
Normal file
44
src/lua/pxl8/bytes.lua
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
|
||||
local bytes = {}
|
||||
|
||||
bytes.pack_f32_be = C.pxl8_pack_f32_be
|
||||
bytes.pack_f32_le = C.pxl8_pack_f32_le
|
||||
bytes.pack_f64_be = C.pxl8_pack_f64_be
|
||||
bytes.pack_f64_le = C.pxl8_pack_f64_le
|
||||
bytes.pack_i8 = C.pxl8_pack_i8
|
||||
bytes.pack_i16_be = C.pxl8_pack_i16_be
|
||||
bytes.pack_i16_le = C.pxl8_pack_i16_le
|
||||
bytes.pack_i32_be = C.pxl8_pack_i32_be
|
||||
bytes.pack_i32_le = C.pxl8_pack_i32_le
|
||||
bytes.pack_i64_be = C.pxl8_pack_i64_be
|
||||
bytes.pack_i64_le = C.pxl8_pack_i64_le
|
||||
bytes.pack_u8 = C.pxl8_pack_u8
|
||||
bytes.pack_u16_be = C.pxl8_pack_u16_be
|
||||
bytes.pack_u16_le = C.pxl8_pack_u16_le
|
||||
bytes.pack_u32_be = C.pxl8_pack_u32_be
|
||||
bytes.pack_u32_le = C.pxl8_pack_u32_le
|
||||
bytes.pack_u64_be = C.pxl8_pack_u64_be
|
||||
bytes.pack_u64_le = C.pxl8_pack_u64_le
|
||||
|
||||
bytes.unpack_f32_be = C.pxl8_unpack_f32_be
|
||||
bytes.unpack_f32_le = C.pxl8_unpack_f32_le
|
||||
bytes.unpack_f64_be = C.pxl8_unpack_f64_be
|
||||
bytes.unpack_f64_le = C.pxl8_unpack_f64_le
|
||||
bytes.unpack_i8 = C.pxl8_unpack_i8
|
||||
bytes.unpack_i16_be = C.pxl8_unpack_i16_be
|
||||
bytes.unpack_i16_le = C.pxl8_unpack_i16_le
|
||||
bytes.unpack_i32_be = C.pxl8_unpack_i32_be
|
||||
bytes.unpack_i32_le = C.pxl8_unpack_i32_le
|
||||
bytes.unpack_i64_be = C.pxl8_unpack_i64_be
|
||||
bytes.unpack_i64_le = C.pxl8_unpack_i64_le
|
||||
bytes.unpack_u8 = C.pxl8_unpack_u8
|
||||
bytes.unpack_u16_be = C.pxl8_unpack_u16_be
|
||||
bytes.unpack_u16_le = C.pxl8_unpack_u16_le
|
||||
bytes.unpack_u32_be = C.pxl8_unpack_u32_be
|
||||
bytes.unpack_u32_le = C.pxl8_unpack_u32_le
|
||||
bytes.unpack_u64_be = C.pxl8_unpack_u64_be
|
||||
bytes.unpack_u64_le = C.pxl8_unpack_u64_le
|
||||
|
||||
return bytes
|
||||
122
src/lua/pxl8/core.lua
Normal file
122
src/lua/pxl8/core.lua
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
|
||||
local core = {}
|
||||
|
||||
function core.init(gfx, input, rng, sfx, sys)
|
||||
core.gfx = gfx
|
||||
core.input = input
|
||||
core.rng = rng
|
||||
core.sfx = sfx
|
||||
core.sys = sys
|
||||
end
|
||||
|
||||
function core.find_color(color)
|
||||
return C.pxl8_gfx_find_color(core.gfx, color)
|
||||
end
|
||||
|
||||
function core.get_fps()
|
||||
return C.pxl8_get_fps(core.sys)
|
||||
end
|
||||
|
||||
function core.palette_color(index)
|
||||
local pal = C.pxl8_gfx_get_palette(core.gfx)
|
||||
if pal == nil then return 0 end
|
||||
return C.pxl8_palette_color(pal, index)
|
||||
end
|
||||
|
||||
function core.palette_index(color)
|
||||
local pal = C.pxl8_gfx_get_palette(core.gfx)
|
||||
if pal == nil then return -1 end
|
||||
return C.pxl8_palette_index(pal, color)
|
||||
end
|
||||
|
||||
function core.set_palette_rgb(index, r, g, b)
|
||||
local pal = C.pxl8_gfx_get_palette(core.gfx)
|
||||
if pal == nil then return end
|
||||
C.pxl8_palette_set_rgb(pal, index, r, g, b)
|
||||
C.pxl8_gfx_blend_tables_update(core.gfx)
|
||||
C.pxl8_gfx_colormap_update(core.gfx)
|
||||
end
|
||||
|
||||
function core.ramp_index(position)
|
||||
local pal = C.pxl8_gfx_get_palette(core.gfx)
|
||||
if pal == nil then return 0 end
|
||||
return C.pxl8_palette_ramp_index(pal, position)
|
||||
end
|
||||
|
||||
function core.get_width()
|
||||
return C.pxl8_gfx_get_width(core.gfx)
|
||||
end
|
||||
|
||||
function core.get_height()
|
||||
return C.pxl8_gfx_get_height(core.gfx)
|
||||
end
|
||||
|
||||
function core.get_title()
|
||||
local cart = C.pxl8_get_cart()
|
||||
if cart ~= nil then
|
||||
local title = C.pxl8_cart_get_title(cart)
|
||||
if title ~= nil then
|
||||
return ffi.string(title)
|
||||
end
|
||||
end
|
||||
return "pxl8"
|
||||
end
|
||||
|
||||
local function is_user_script(info)
|
||||
local src = info and info.short_src
|
||||
return src and (src:match("%.fnl$") or src:match("%.lua$"))
|
||||
end
|
||||
|
||||
local function get_caller_info()
|
||||
for level = 2, 10 do
|
||||
local info = debug.getinfo(level, "Sl")
|
||||
if not info then break end
|
||||
if is_user_script(info) then
|
||||
return info.short_src, info.currentline or 0
|
||||
end
|
||||
end
|
||||
return "?", 0
|
||||
end
|
||||
|
||||
core.LOG_TRACE = 0
|
||||
core.LOG_DEBUG = 1
|
||||
core.LOG_INFO = 2
|
||||
core.LOG_WARN = 3
|
||||
core.LOG_ERROR = 4
|
||||
|
||||
local function make_logger(level)
|
||||
return function(msg)
|
||||
local src, line = get_caller_info()
|
||||
C.pxl8_lua_log(level, src, line, msg)
|
||||
end
|
||||
end
|
||||
|
||||
core.trace = make_logger(core.LOG_TRACE)
|
||||
core.debug = make_logger(core.LOG_DEBUG)
|
||||
core.info = make_logger(core.LOG_INFO)
|
||||
core.warn = make_logger(core.LOG_WARN)
|
||||
core.error = make_logger(core.LOG_ERROR)
|
||||
|
||||
function core.quit()
|
||||
C.pxl8_set_running(core.sys, false)
|
||||
end
|
||||
|
||||
function core.rng_seed(seed)
|
||||
C.pxl8_rng_seed(core.rng, seed)
|
||||
end
|
||||
|
||||
function core.rng_next()
|
||||
return C.pxl8_rng_next(core.rng)
|
||||
end
|
||||
|
||||
function core.rng_f32()
|
||||
return C.pxl8_rng_f32(core.rng)
|
||||
end
|
||||
|
||||
function core.rng_range(min, max)
|
||||
return C.pxl8_rng_range(core.rng, min, max)
|
||||
end
|
||||
|
||||
return core
|
||||
32
src/lua/pxl8/effects.lua
Normal file
32
src/lua/pxl8/effects.lua
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
local core = require("pxl8.core")
|
||||
|
||||
local effects = {}
|
||||
|
||||
effects.GLOW_CIRCLE = 0
|
||||
effects.GLOW_DIAMOND = 1
|
||||
effects.GLOW_SHAFT = 2
|
||||
|
||||
function effects.glows(glows)
|
||||
if not glows or #glows == 0 then return end
|
||||
|
||||
local count = #glows
|
||||
local glow_array = ffi.new("pxl8_glow_source[?]", count)
|
||||
|
||||
for i, g in ipairs(glows) do
|
||||
local idx = i - 1
|
||||
glow_array[idx].x = g.x or 0
|
||||
glow_array[idx].y = g.y or 0
|
||||
glow_array[idx].radius = g.radius or 8
|
||||
glow_array[idx].intensity = g.intensity or 255
|
||||
glow_array[idx].color = g.color or 15
|
||||
glow_array[idx].depth = g.depth or 0xFFFF
|
||||
glow_array[idx].height = g.height or 0
|
||||
glow_array[idx].shape = g.shape or 0
|
||||
end
|
||||
|
||||
C.pxl8_gfx_apply_effect(core.gfx, C.PXL8_GFX_EFFECT_GLOWS, glow_array, count)
|
||||
end
|
||||
|
||||
return effects
|
||||
109
src/lua/pxl8/gfx2d.lua
Normal file
109
src/lua/pxl8/gfx2d.lua
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
local core = require("pxl8.core")
|
||||
|
||||
local graphics = {}
|
||||
|
||||
function graphics.clear(color)
|
||||
C.pxl8_2d_clear(core.gfx, color or 0)
|
||||
end
|
||||
|
||||
function graphics.pixel(x, y, color)
|
||||
if color then
|
||||
C.pxl8_2d_pixel(core.gfx, x, y, color)
|
||||
else
|
||||
return C.pxl8_2d_get_pixel(core.gfx, x, y)
|
||||
end
|
||||
end
|
||||
|
||||
function graphics.line(x0, y0, x1, y1, color)
|
||||
C.pxl8_2d_line(core.gfx, x0, y0, x1, y1, color)
|
||||
end
|
||||
|
||||
function graphics.rect(x, y, w, h, color)
|
||||
C.pxl8_2d_rect(core.gfx, x, y, w, h, color)
|
||||
end
|
||||
|
||||
function graphics.rect_fill(x, y, w, h, color)
|
||||
C.pxl8_2d_rect_fill(core.gfx, x, y, w, h, color)
|
||||
end
|
||||
|
||||
function graphics.circle(x, y, r, color)
|
||||
C.pxl8_2d_circle(core.gfx, x, y, r, color)
|
||||
end
|
||||
|
||||
function graphics.circle_fill(x, y, r, color)
|
||||
C.pxl8_2d_circle_fill(core.gfx, x, y, r, color)
|
||||
end
|
||||
|
||||
function graphics.text(str, x, y, color)
|
||||
C.pxl8_2d_text(core.gfx, str, x or 0, y or 0, color or 15)
|
||||
end
|
||||
|
||||
function graphics.sprite(id, x, y, w, h, flip_x, flip_y)
|
||||
C.pxl8_2d_sprite(core.gfx, id or 0, x or 0, y or 0, w or 16, h or 16, flip_x or false, flip_y or false)
|
||||
end
|
||||
|
||||
function graphics.load_palette(filepath)
|
||||
return C.pxl8_gfx_load_palette(core.gfx, filepath)
|
||||
end
|
||||
|
||||
function graphics.load_sprite(filepath)
|
||||
local sprite_id = ffi.new("unsigned int[1]")
|
||||
local result = C.pxl8_gfx_load_sprite(core.gfx, filepath, sprite_id)
|
||||
if result == 0 then
|
||||
return sprite_id[0]
|
||||
else
|
||||
return nil, result
|
||||
end
|
||||
end
|
||||
|
||||
function graphics.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(core.gfx, pixel_data, width, height)
|
||||
if result < 0 then
|
||||
return nil
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function graphics.color_ramp(start, count, from_color, to_color)
|
||||
C.pxl8_gfx_color_ramp(core.gfx, start, count, from_color, to_color)
|
||||
end
|
||||
|
||||
function graphics.fade_palette(start, count, amount, target_color)
|
||||
C.pxl8_gfx_fade_palette(core.gfx, start, count, amount, target_color)
|
||||
end
|
||||
|
||||
function graphics.cycle_palette(start, count, step)
|
||||
C.pxl8_gfx_cycle_palette(core.gfx, start, count, step or 1)
|
||||
end
|
||||
|
||||
function graphics.add_palette_cycle(start_index, end_index, speed)
|
||||
return C.pxl8_gfx_add_palette_cycle(core.gfx, start_index, end_index, speed or 1.0)
|
||||
end
|
||||
|
||||
function graphics.remove_palette_cycle(cycle_id)
|
||||
C.pxl8_gfx_remove_palette_cycle(core.gfx, cycle_id)
|
||||
end
|
||||
|
||||
function graphics.set_palette_cycle_speed(cycle_id, speed)
|
||||
C.pxl8_gfx_set_palette_cycle_speed(core.gfx, cycle_id, speed)
|
||||
end
|
||||
|
||||
function graphics.clear_palette_cycles()
|
||||
C.pxl8_gfx_clear_palette_cycles(core.gfx)
|
||||
end
|
||||
|
||||
function graphics.push_target()
|
||||
return C.pxl8_gfx_push_target(core.gfx)
|
||||
end
|
||||
|
||||
function graphics.pop_target()
|
||||
C.pxl8_gfx_pop_target(core.gfx)
|
||||
end
|
||||
|
||||
return graphics
|
||||
208
src/lua/pxl8/gfx3d.lua
Normal file
208
src/lua/pxl8/gfx3d.lua
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
local core = require("pxl8.core")
|
||||
|
||||
local gfx3d = {}
|
||||
|
||||
local Camera3D = {}
|
||||
Camera3D.__index = Camera3D
|
||||
|
||||
function Camera3D.new()
|
||||
local cam = C.pxl8_3d_camera_create()
|
||||
if cam == nil then
|
||||
return nil
|
||||
end
|
||||
return setmetatable({ _ptr = cam }, Camera3D)
|
||||
end
|
||||
|
||||
function Camera3D:destroy()
|
||||
if self._ptr then
|
||||
C.pxl8_3d_camera_destroy(self._ptr)
|
||||
self._ptr = nil
|
||||
end
|
||||
end
|
||||
|
||||
function Camera3D:get_forward()
|
||||
local v = C.pxl8_3d_camera_get_forward(self._ptr)
|
||||
return {v.x, v.y, v.z}
|
||||
end
|
||||
|
||||
function Camera3D:get_position()
|
||||
local v = C.pxl8_3d_camera_get_position(self._ptr)
|
||||
return {v.x, v.y, v.z}
|
||||
end
|
||||
|
||||
function Camera3D:get_right()
|
||||
local v = C.pxl8_3d_camera_get_right(self._ptr)
|
||||
return {v.x, v.y, v.z}
|
||||
end
|
||||
|
||||
function Camera3D:get_up()
|
||||
local v = C.pxl8_3d_camera_get_up(self._ptr)
|
||||
return {v.x, v.y, v.z}
|
||||
end
|
||||
|
||||
function Camera3D:get_view()
|
||||
return C.pxl8_3d_camera_get_view(self._ptr)
|
||||
end
|
||||
|
||||
function Camera3D:get_projection()
|
||||
return C.pxl8_3d_camera_get_projection(self._ptr)
|
||||
end
|
||||
|
||||
function Camera3D:lookat(eye, target, up)
|
||||
up = up or {0, 1, 0}
|
||||
local eye_vec = ffi.new("pxl8_vec3", {x = eye[1], y = eye[2], z = eye[3]})
|
||||
local target_vec = ffi.new("pxl8_vec3", {x = target[1], y = target[2], z = target[3]})
|
||||
local up_vec = ffi.new("pxl8_vec3", {x = up[1], y = up[2], z = up[3]})
|
||||
C.pxl8_3d_camera_lookat(self._ptr, eye_vec, target_vec, up_vec)
|
||||
end
|
||||
|
||||
function Camera3D:set_perspective(fov, aspect, near, far)
|
||||
C.pxl8_3d_camera_set_perspective(self._ptr, fov, aspect, near, far)
|
||||
end
|
||||
|
||||
function Camera3D:set_position(x, y, z)
|
||||
local pos = ffi.new("pxl8_vec3", {x = x, y = y, z = z})
|
||||
C.pxl8_3d_camera_set_position(self._ptr, pos)
|
||||
end
|
||||
|
||||
function Camera3D:set_rotation(pitch, yaw, roll)
|
||||
C.pxl8_3d_camera_set_rotation(self._ptr, pitch, yaw or 0, roll or 0)
|
||||
end
|
||||
|
||||
function Camera3D:update(dt)
|
||||
C.pxl8_3d_camera_update(self._ptr, dt)
|
||||
end
|
||||
|
||||
gfx3d.Camera3D = Camera3D
|
||||
|
||||
local Mesh = {}
|
||||
Mesh.__index = Mesh
|
||||
|
||||
function Mesh.new(vertices, indices)
|
||||
local vertex_count = #vertices
|
||||
local index_count = #indices
|
||||
local mesh = C.pxl8_mesh_create(vertex_count, index_count)
|
||||
if mesh == nil then
|
||||
return nil
|
||||
end
|
||||
local self = setmetatable({ _ptr = mesh }, Mesh)
|
||||
for _, v in ipairs(vertices) do
|
||||
local vert = ffi.new("pxl8_vertex", {
|
||||
position = {x = v.x or 0, y = v.y or 0, z = v.z or 0},
|
||||
normal = {x = v.nx or 0, y = v.ny or 0, z = v.nz or 0},
|
||||
u = v.u or 0,
|
||||
v = v.v or 0,
|
||||
color = v.color or 0,
|
||||
light = v.light or 255,
|
||||
})
|
||||
C.pxl8_mesh_push_vertex(mesh, vert)
|
||||
end
|
||||
for i = 1, #indices, 3 do
|
||||
C.pxl8_mesh_push_triangle(mesh, indices[i], indices[i+1], indices[i+2])
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function Mesh:destroy()
|
||||
if self._ptr then
|
||||
C.pxl8_mesh_destroy(self._ptr)
|
||||
self._ptr = nil
|
||||
end
|
||||
end
|
||||
|
||||
function Mesh:clear()
|
||||
if self._ptr then
|
||||
C.pxl8_mesh_clear(self._ptr)
|
||||
end
|
||||
end
|
||||
|
||||
gfx3d.Mesh = Mesh
|
||||
|
||||
function gfx3d.draw_mesh(mesh, opts)
|
||||
opts = opts or {}
|
||||
local model = ffi.new("pxl8_mat4")
|
||||
model.m[0] = 1
|
||||
model.m[5] = 1
|
||||
model.m[10] = 1
|
||||
model.m[15] = 1
|
||||
if opts.x then model.m[12] = opts.x end
|
||||
if opts.y then model.m[13] = opts.y end
|
||||
if opts.z then model.m[14] = opts.z end
|
||||
local material = ffi.new("pxl8_material", {
|
||||
texture_id = opts.texture or 0,
|
||||
alpha = opts.alpha or 255,
|
||||
blend_mode = opts.blend_mode or 0,
|
||||
dither = opts.dither ~= false,
|
||||
double_sided = opts.double_sided or false,
|
||||
dynamic_lighting = opts.lighting or false,
|
||||
per_pixel = opts.per_pixel or false,
|
||||
vertex_color_passthrough = opts.passthrough or false,
|
||||
emissive_intensity = opts.emissive or 0.0,
|
||||
})
|
||||
C.pxl8_3d_draw_mesh(core.gfx, mesh._ptr, model, material)
|
||||
end
|
||||
|
||||
function gfx3d.begin_frame(camera, uniforms)
|
||||
uniforms = uniforms or {}
|
||||
local u = ffi.new("pxl8_3d_uniforms")
|
||||
|
||||
u.ambient = uniforms.ambient or 0
|
||||
u.fog_color = uniforms.fog_color or 0
|
||||
u.fog_density = uniforms.fog_density or 0.0
|
||||
u.time = uniforms.time or 0.0
|
||||
|
||||
if uniforms.celestial_dir then
|
||||
u.celestial_dir.x = uniforms.celestial_dir[1] or 0
|
||||
u.celestial_dir.y = uniforms.celestial_dir[2] or -1
|
||||
u.celestial_dir.z = uniforms.celestial_dir[3] or 0
|
||||
else
|
||||
u.celestial_dir.x = 0
|
||||
u.celestial_dir.y = -1
|
||||
u.celestial_dir.z = 0
|
||||
end
|
||||
u.celestial_intensity = uniforms.celestial_intensity or 0.0
|
||||
|
||||
u.num_lights = 0
|
||||
if uniforms.lights then
|
||||
for i, light in ipairs(uniforms.lights) do
|
||||
if i > 16 then break end
|
||||
local idx = i - 1
|
||||
u.lights[idx].position.x = light.x or 0
|
||||
u.lights[idx].position.y = light.y or 0
|
||||
u.lights[idx].position.z = light.z or 0
|
||||
u.lights[idx].r = light.r or 255
|
||||
u.lights[idx].g = light.g or 255
|
||||
u.lights[idx].b = light.b or 255
|
||||
u.lights[idx].intensity = light.intensity or 255
|
||||
u.lights[idx].radius = light.radius or 100
|
||||
local radius_sq = u.lights[idx].radius * u.lights[idx].radius
|
||||
u.lights[idx].radius_sq = radius_sq
|
||||
u.lights[idx].inv_radius_sq = radius_sq > 0 and (1.0 / radius_sq) or 0
|
||||
u.num_lights = i
|
||||
end
|
||||
end
|
||||
|
||||
C.pxl8_3d_begin_frame(core.gfx, camera._ptr, u)
|
||||
end
|
||||
|
||||
function gfx3d.clear(color)
|
||||
C.pxl8_3d_clear(core.gfx, color or 0)
|
||||
end
|
||||
|
||||
function gfx3d.clear_depth()
|
||||
C.pxl8_3d_clear_depth(core.gfx)
|
||||
end
|
||||
|
||||
function gfx3d.draw_line(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]})
|
||||
C.pxl8_3d_draw_line(core.gfx, vec0, vec1, color)
|
||||
end
|
||||
|
||||
function gfx3d.end_frame()
|
||||
C.pxl8_3d_end_frame(core.gfx)
|
||||
end
|
||||
|
||||
return gfx3d
|
||||
70
src/lua/pxl8/gui.lua
Normal file
70
src/lua/pxl8/gui.lua
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
local core = require("pxl8.core")
|
||||
|
||||
local gui = {}
|
||||
|
||||
local Gui = {}
|
||||
Gui.__index = Gui
|
||||
|
||||
function Gui.new()
|
||||
local s = C.pxl8_gui_state_create()
|
||||
if s == nil then
|
||||
return nil
|
||||
end
|
||||
return setmetatable({ _ptr = s }, Gui)
|
||||
end
|
||||
|
||||
function Gui:begin_frame()
|
||||
C.pxl8_gui_begin_frame(self._ptr)
|
||||
end
|
||||
|
||||
function Gui:button(id, x, y, w, h, label)
|
||||
return C.pxl8_gui_button(self._ptr, core.gfx, id, x, y, w, h, label)
|
||||
end
|
||||
|
||||
function Gui:cursor_down()
|
||||
C.pxl8_gui_cursor_down(self._ptr)
|
||||
end
|
||||
|
||||
function Gui:cursor_move(x, y)
|
||||
C.pxl8_gui_cursor_move(self._ptr, x, y)
|
||||
end
|
||||
|
||||
function Gui:cursor_up()
|
||||
C.pxl8_gui_cursor_up(self._ptr)
|
||||
end
|
||||
|
||||
function Gui:destroy()
|
||||
if self._ptr then
|
||||
C.pxl8_gui_state_destroy(self._ptr)
|
||||
self._ptr = nil
|
||||
end
|
||||
end
|
||||
|
||||
function Gui:end_frame()
|
||||
C.pxl8_gui_end_frame(self._ptr)
|
||||
end
|
||||
|
||||
function Gui:get_cursor_pos()
|
||||
local x = ffi.new("i32[1]")
|
||||
local y = ffi.new("i32[1]")
|
||||
C.pxl8_gui_get_cursor_pos(self._ptr, x, y)
|
||||
return x[0], y[0]
|
||||
end
|
||||
|
||||
function Gui:is_hovering()
|
||||
return C.pxl8_gui_is_hovering(self._ptr)
|
||||
end
|
||||
|
||||
gui.Gui = Gui
|
||||
|
||||
function gui.label(x, y, text, color)
|
||||
C.pxl8_gui_label(core.gfx, x, y, text, color)
|
||||
end
|
||||
|
||||
function gui.window(x, y, w, h, title)
|
||||
C.pxl8_gui_window(core.gfx, x, y, w, h, title)
|
||||
end
|
||||
|
||||
return gui
|
||||
75
src/lua/pxl8/input.lua
Normal file
75
src/lua/pxl8/input.lua
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
local core = require("pxl8.core")
|
||||
|
||||
local input = {}
|
||||
|
||||
function input.key_down(key)
|
||||
return C.pxl8_key_down(core.input, key)
|
||||
end
|
||||
|
||||
function input.key_pressed(key)
|
||||
return C.pxl8_key_pressed(core.input, key)
|
||||
end
|
||||
|
||||
function input.key_released(key)
|
||||
return C.pxl8_key_released(core.input, key)
|
||||
end
|
||||
|
||||
function input.mouse_wheel_x()
|
||||
return C.pxl8_mouse_wheel_x(core.input)
|
||||
end
|
||||
|
||||
function input.mouse_wheel_y()
|
||||
return C.pxl8_mouse_wheel_y(core.input)
|
||||
end
|
||||
|
||||
function input.mouse_x()
|
||||
return C.pxl8_mouse_x(core.input)
|
||||
end
|
||||
|
||||
function input.mouse_y()
|
||||
return C.pxl8_mouse_y(core.input)
|
||||
end
|
||||
|
||||
function input.mouse_dx()
|
||||
return C.pxl8_mouse_dx(core.input)
|
||||
end
|
||||
|
||||
function input.mouse_dy()
|
||||
return C.pxl8_mouse_dy(core.input)
|
||||
end
|
||||
|
||||
function input.get_mouse_pos()
|
||||
return C.pxl8_mouse_x(core.input), C.pxl8_mouse_y(core.input)
|
||||
end
|
||||
|
||||
function input.mouse_pressed(button)
|
||||
return C.pxl8_mouse_pressed(core.input, button)
|
||||
end
|
||||
|
||||
function input.mouse_released(button)
|
||||
return C.pxl8_mouse_released(core.input, button)
|
||||
end
|
||||
|
||||
function input.set_relative_mouse_mode(enabled)
|
||||
C.pxl8_set_relative_mouse_mode(core.sys, enabled)
|
||||
end
|
||||
|
||||
function input.center_cursor()
|
||||
C.pxl8_center_cursor(core.sys)
|
||||
end
|
||||
|
||||
function input.set_cursor(cursor_type)
|
||||
local cursor_enum
|
||||
if cursor_type == "arrow" then
|
||||
cursor_enum = C.PXL8_CURSOR_ARROW
|
||||
elseif cursor_type == "hand" then
|
||||
cursor_enum = C.PXL8_CURSOR_HAND
|
||||
else
|
||||
cursor_enum = C.PXL8_CURSOR_ARROW
|
||||
end
|
||||
C.pxl8_set_cursor(core.sys, cursor_enum)
|
||||
end
|
||||
|
||||
return input
|
||||
53
src/lua/pxl8/math.lua
Normal file
53
src/lua/pxl8/math.lua
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
|
||||
local math3d = {}
|
||||
|
||||
function math3d.mat4_identity()
|
||||
return C.pxl8_mat4_identity()
|
||||
end
|
||||
|
||||
function math3d.mat4_mul(a, b)
|
||||
return C.pxl8_mat4_mul(a, b)
|
||||
end
|
||||
|
||||
function math3d.mat4_translate(x, y, z)
|
||||
return C.pxl8_mat4_translate(x, y, z)
|
||||
end
|
||||
|
||||
function math3d.mat4_rotate_x(angle)
|
||||
return C.pxl8_mat4_rotate_x(angle)
|
||||
end
|
||||
|
||||
function math3d.mat4_rotate_y(angle)
|
||||
return C.pxl8_mat4_rotate_y(angle)
|
||||
end
|
||||
|
||||
function math3d.mat4_rotate_z(angle)
|
||||
return C.pxl8_mat4_rotate_z(angle)
|
||||
end
|
||||
|
||||
function math3d.mat4_scale(x, y, z)
|
||||
return C.pxl8_mat4_scale(x, y, z)
|
||||
end
|
||||
|
||||
function math3d.mat4_ortho(left, right, bottom, top, near, far)
|
||||
return C.pxl8_mat4_ortho(left, right, bottom, top, near, far)
|
||||
end
|
||||
|
||||
function math3d.mat4_perspective(fov, aspect, near, far)
|
||||
return C.pxl8_mat4_perspective(fov, aspect, near, far)
|
||||
end
|
||||
|
||||
function math3d.mat4_lookat(eye, center, up)
|
||||
local eye_vec = ffi.new("pxl8_vec3", {x = eye[1], y = eye[2], z = eye[3]})
|
||||
local center_vec = ffi.new("pxl8_vec3", {x = center[1], y = center[2], z = center[3]})
|
||||
local up_vec = ffi.new("pxl8_vec3", {x = up[1], y = up[2], z = up[3]})
|
||||
return C.pxl8_mat4_lookat(eye_vec, center_vec, up_vec)
|
||||
end
|
||||
|
||||
function math3d.bounds(x, y, w, h)
|
||||
return ffi.new("pxl8_bounds", {x = x, y = y, w = w, h = h})
|
||||
end
|
||||
|
||||
return math3d
|
||||
182
src/lua/pxl8/net.lua
Normal file
182
src/lua/pxl8/net.lua
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
|
||||
local net = {}
|
||||
|
||||
local Net = {}
|
||||
Net.__index = Net
|
||||
|
||||
net.MODE_LOCAL = C.PXL8_NET_LOCAL
|
||||
net.MODE_REMOTE = C.PXL8_NET_REMOTE
|
||||
|
||||
function Net.new(config)
|
||||
config = config or {}
|
||||
local cfg = ffi.new("pxl8_net_config")
|
||||
cfg.address = config.address or "127.0.0.1"
|
||||
cfg.mode = config.mode or C.PXL8_NET_REMOTE
|
||||
cfg.port = config.port or 7777
|
||||
|
||||
local n = C.pxl8_net_create(cfg)
|
||||
if n == nil then
|
||||
return nil
|
||||
end
|
||||
return setmetatable({ _ptr = n }, Net)
|
||||
end
|
||||
|
||||
function Net:connect()
|
||||
return C.pxl8_net_connect(self._ptr) == 0
|
||||
end
|
||||
|
||||
function Net:connected()
|
||||
return C.pxl8_net_connected(self._ptr)
|
||||
end
|
||||
|
||||
function Net:destroy()
|
||||
if self._ptr then
|
||||
C.pxl8_net_destroy(self._ptr)
|
||||
self._ptr = nil
|
||||
end
|
||||
end
|
||||
|
||||
function Net:disconnect()
|
||||
C.pxl8_net_disconnect(self._ptr)
|
||||
end
|
||||
|
||||
function Net:entities()
|
||||
local snap = C.pxl8_net_snapshot(self._ptr)
|
||||
if snap == nil then
|
||||
return {}
|
||||
end
|
||||
local ents = C.pxl8_net_entities(self._ptr)
|
||||
if ents == nil then
|
||||
return {}
|
||||
end
|
||||
local result = {}
|
||||
for i = 0, snap.entity_count - 1 do
|
||||
result[i + 1] = {
|
||||
entity_id = tonumber(ents[i].entity_id),
|
||||
userdata = ents[i].userdata
|
||||
}
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
function Net:entity_prev_userdata(entity_id)
|
||||
return C.pxl8_net_entity_prev_userdata(self._ptr, entity_id)
|
||||
end
|
||||
|
||||
function Net:entity_userdata(entity_id)
|
||||
return C.pxl8_net_entity_userdata(self._ptr, entity_id)
|
||||
end
|
||||
|
||||
function Net:input_at(tick)
|
||||
local input = C.pxl8_net_input_at(self._ptr, tick)
|
||||
if input == nil then return nil end
|
||||
return {
|
||||
buttons = input.buttons,
|
||||
look_dx = input.look_dx,
|
||||
look_dy = input.look_dy,
|
||||
move_x = input.move_x,
|
||||
move_y = input.move_y,
|
||||
yaw = input.yaw,
|
||||
tick = tonumber(input.tick),
|
||||
timestamp = tonumber(input.timestamp)
|
||||
}
|
||||
end
|
||||
|
||||
function Net:input_oldest_tick()
|
||||
return tonumber(C.pxl8_net_input_oldest_tick(self._ptr))
|
||||
end
|
||||
|
||||
function Net:input_push(input)
|
||||
local msg = ffi.new("pxl8_input_msg")
|
||||
msg.buttons = input.buttons or 0
|
||||
msg.look_dx = input.look_dx or 0
|
||||
msg.look_dy = input.look_dy or 0
|
||||
msg.move_x = input.move_x or 0
|
||||
msg.move_y = input.move_y or 0
|
||||
msg.yaw = input.yaw or 0
|
||||
msg.tick = input.tick or 0
|
||||
msg.timestamp = input.timestamp or 0
|
||||
C.pxl8_net_input_push(self._ptr, msg)
|
||||
end
|
||||
|
||||
function Net:lerp_alpha()
|
||||
return C.pxl8_net_lerp_alpha(self._ptr)
|
||||
end
|
||||
|
||||
function Net:needs_correction()
|
||||
return C.pxl8_net_needs_correction(self._ptr)
|
||||
end
|
||||
|
||||
function Net:player_id()
|
||||
return tonumber(C.pxl8_net_player_id(self._ptr))
|
||||
end
|
||||
|
||||
function Net:poll()
|
||||
return C.pxl8_net_poll(self._ptr)
|
||||
end
|
||||
|
||||
function Net:predicted_state()
|
||||
return C.pxl8_net_predicted_state(self._ptr)
|
||||
end
|
||||
|
||||
function Net:predicted_tick_set(tick)
|
||||
C.pxl8_net_predicted_tick_set(self._ptr, tick)
|
||||
end
|
||||
|
||||
function Net:send_command(cmd)
|
||||
return C.pxl8_net_send_command(self._ptr, cmd) == 0
|
||||
end
|
||||
|
||||
function Net:send_input(input)
|
||||
local msg = ffi.new("pxl8_input_msg")
|
||||
msg.buttons = input.buttons or 0
|
||||
msg.look_dx = input.look_dx or 0
|
||||
msg.look_dy = input.look_dy or 0
|
||||
msg.move_x = input.move_x or 0
|
||||
msg.move_y = input.move_y or 0
|
||||
msg.yaw = input.yaw or 0
|
||||
msg.tick = input.tick or 0
|
||||
msg.timestamp = input.timestamp or 0
|
||||
return C.pxl8_net_send_input(self._ptr, msg) == 0
|
||||
end
|
||||
|
||||
function Net:snapshot()
|
||||
local snap = C.pxl8_net_snapshot(self._ptr)
|
||||
if snap == nil then
|
||||
return nil
|
||||
end
|
||||
return {
|
||||
entity_count = snap.entity_count,
|
||||
event_count = snap.event_count,
|
||||
player_id = tonumber(snap.player_id),
|
||||
tick = tonumber(snap.tick),
|
||||
time = snap.time
|
||||
}
|
||||
end
|
||||
|
||||
function Net:spawn(x, y, z, yaw, pitch)
|
||||
local cmd = ffi.new("pxl8_command_msg")
|
||||
cmd.cmd_type = C.PXL8_CMD_SPAWN_ENTITY
|
||||
C.pxl8_pack_f32_be(cmd.payload, 0, x or 0)
|
||||
C.pxl8_pack_f32_be(cmd.payload, 4, y or 0)
|
||||
C.pxl8_pack_f32_be(cmd.payload, 8, z or 0)
|
||||
C.pxl8_pack_f32_be(cmd.payload, 12, yaw or 0)
|
||||
C.pxl8_pack_f32_be(cmd.payload, 16, pitch or 0)
|
||||
cmd.payload_size = 20
|
||||
cmd.tick = 0
|
||||
return self:send_command(cmd)
|
||||
end
|
||||
|
||||
function Net:tick()
|
||||
return tonumber(C.pxl8_net_tick(self._ptr))
|
||||
end
|
||||
|
||||
function Net:update(dt)
|
||||
C.pxl8_net_update(self._ptr, dt)
|
||||
end
|
||||
|
||||
net.Net = Net
|
||||
|
||||
return net
|
||||
103
src/lua/pxl8/particles.lua
Normal file
103
src/lua/pxl8/particles.lua
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
local core = require("pxl8.core")
|
||||
|
||||
local particles = {}
|
||||
|
||||
local Particles = {}
|
||||
Particles.__index = Particles
|
||||
|
||||
function Particles.new(max_count)
|
||||
local ps = C.pxl8_particles_create(max_count or 1000, core.rng)
|
||||
if ps == nil then
|
||||
return nil
|
||||
end
|
||||
local pal = C.pxl8_gfx_get_palette(core.gfx)
|
||||
if pal ~= nil then
|
||||
C.pxl8_particles_set_palette(ps, pal)
|
||||
end
|
||||
return setmetatable({ _ptr = ps }, Particles)
|
||||
end
|
||||
|
||||
function Particles:clear()
|
||||
C.pxl8_particles_clear(self._ptr)
|
||||
end
|
||||
|
||||
function Particles:destroy()
|
||||
if self._ptr then
|
||||
C.pxl8_particles_destroy(self._ptr)
|
||||
self._ptr = nil
|
||||
end
|
||||
end
|
||||
|
||||
function Particles:emit(count)
|
||||
C.pxl8_particles_emit(self._ptr, count or 1)
|
||||
end
|
||||
|
||||
function Particles:render()
|
||||
C.pxl8_particles_render(self._ptr, core.gfx)
|
||||
end
|
||||
|
||||
function Particles:update(dt)
|
||||
C.pxl8_particles_update(self._ptr, dt)
|
||||
end
|
||||
|
||||
function Particles:count()
|
||||
return C.pxl8_particles_count(self._ptr)
|
||||
end
|
||||
|
||||
function Particles:get(index)
|
||||
return C.pxl8_particles_get(self._ptr, index)
|
||||
end
|
||||
|
||||
function Particles:set_position(x, y)
|
||||
C.pxl8_particles_set_position(self._ptr, x, y)
|
||||
end
|
||||
|
||||
function Particles:set_gravity(gx, gy)
|
||||
C.pxl8_particles_set_gravity(self._ptr, gx, gy)
|
||||
end
|
||||
|
||||
function Particles:set_spread(sx, sy)
|
||||
C.pxl8_particles_set_spread(self._ptr, sx, sy)
|
||||
end
|
||||
|
||||
function Particles:set_drag(drag)
|
||||
C.pxl8_particles_set_drag(self._ptr, drag)
|
||||
end
|
||||
|
||||
function Particles:set_turbulence(turbulence)
|
||||
C.pxl8_particles_set_turbulence(self._ptr, turbulence)
|
||||
end
|
||||
|
||||
function Particles:set_spawn_rate(rate)
|
||||
C.pxl8_particles_set_spawn_rate(self._ptr, rate)
|
||||
end
|
||||
|
||||
function Particles:set_colors(ramp_min, ramp_max)
|
||||
ramp_max = ramp_max or ramp_min
|
||||
if ramp_min > ramp_max then
|
||||
ramp_min, ramp_max = ramp_max, ramp_min
|
||||
end
|
||||
C.pxl8_particles_set_colors(self._ptr, ramp_min, ramp_max)
|
||||
end
|
||||
|
||||
function Particles:set_palette(palette)
|
||||
C.pxl8_particles_set_palette(self._ptr, palette)
|
||||
end
|
||||
|
||||
function Particles:set_life(life_min, life_max)
|
||||
C.pxl8_particles_set_life(self._ptr, life_min, life_max or life_min)
|
||||
end
|
||||
|
||||
function Particles:set_size(size_min, size_max)
|
||||
C.pxl8_particles_set_size(self._ptr, size_min, size_max or size_min)
|
||||
end
|
||||
|
||||
function Particles:set_velocity(vx_min, vx_max, vy_min, vy_max)
|
||||
C.pxl8_particles_set_velocity(self._ptr, vx_min, vx_max, vy_min, vy_max)
|
||||
end
|
||||
|
||||
particles.Particles = Particles
|
||||
|
||||
return particles
|
||||
239
src/lua/pxl8/sfx.lua
Normal file
239
src/lua/pxl8/sfx.lua
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
local core = require("pxl8.core")
|
||||
|
||||
local sfx = {}
|
||||
|
||||
sfx.FILTER_BANDPASS = C.PXL8_SFX_FILTER_BANDPASS
|
||||
sfx.FILTER_HIGHPASS = C.PXL8_SFX_FILTER_HIGHPASS
|
||||
sfx.FILTER_LOWPASS = C.PXL8_SFX_FILTER_LOWPASS
|
||||
sfx.FILTER_NONE = C.PXL8_SFX_FILTER_NONE
|
||||
|
||||
sfx.LFO_AMPLITUDE = C.PXL8_SFX_LFO_AMPLITUDE
|
||||
sfx.LFO_FILTER = C.PXL8_SFX_LFO_FILTER
|
||||
sfx.LFO_PITCH = C.PXL8_SFX_LFO_PITCH
|
||||
|
||||
sfx.NODE_COMPRESSOR = C.PXL8_SFX_NODE_COMPRESSOR
|
||||
sfx.NODE_DELAY = C.PXL8_SFX_NODE_DELAY
|
||||
sfx.NODE_REVERB = C.PXL8_SFX_NODE_REVERB
|
||||
|
||||
sfx.WAVE_NOISE = C.PXL8_SFX_WAVE_NOISE
|
||||
sfx.WAVE_PULSE = C.PXL8_SFX_WAVE_PULSE
|
||||
sfx.WAVE_SAW = C.PXL8_SFX_WAVE_SAW
|
||||
sfx.WAVE_SINE = C.PXL8_SFX_WAVE_SINE
|
||||
sfx.WAVE_SQUARE = C.PXL8_SFX_WAVE_SQUARE
|
||||
sfx.WAVE_TRIANGLE = C.PXL8_SFX_WAVE_TRIANGLE
|
||||
|
||||
local SfxNode = {}
|
||||
SfxNode.__index = SfxNode
|
||||
|
||||
function SfxNode:destroy()
|
||||
if self._ptr then
|
||||
C.pxl8_sfx_node_destroy(self._ptr)
|
||||
self._ptr = nil
|
||||
end
|
||||
end
|
||||
|
||||
sfx.SfxNode = SfxNode
|
||||
|
||||
local SfxContext = {}
|
||||
SfxContext.__index = SfxContext
|
||||
|
||||
function SfxContext.new()
|
||||
local ctx = C.pxl8_sfx_context_create()
|
||||
if ctx == nil then
|
||||
return nil
|
||||
end
|
||||
return setmetatable({ _ptr = ctx }, SfxContext)
|
||||
end
|
||||
|
||||
function SfxContext:append_node(node)
|
||||
C.pxl8_sfx_context_append_node(self._ptr, node._ptr)
|
||||
end
|
||||
|
||||
function SfxContext:attach()
|
||||
C.pxl8_sfx_mixer_attach(core.sfx, self._ptr)
|
||||
end
|
||||
|
||||
function SfxContext:destroy()
|
||||
if self._ptr then
|
||||
C.pxl8_sfx_context_destroy(self._ptr)
|
||||
self._ptr = nil
|
||||
end
|
||||
end
|
||||
|
||||
function SfxContext:detach()
|
||||
C.pxl8_sfx_mixer_detach(core.sfx, self._ptr)
|
||||
end
|
||||
|
||||
function SfxContext:get_head()
|
||||
local ptr = C.pxl8_sfx_context_get_head(self._ptr)
|
||||
if ptr == nil then return nil end
|
||||
return setmetatable({ _ptr = ptr }, SfxNode)
|
||||
end
|
||||
|
||||
function SfxContext:get_volume()
|
||||
return C.pxl8_sfx_context_get_volume(self._ptr)
|
||||
end
|
||||
|
||||
function SfxContext:insert_node(after, node)
|
||||
C.pxl8_sfx_context_insert_node(self._ptr, after._ptr, node._ptr)
|
||||
end
|
||||
|
||||
function SfxContext:play_note(note, params, volume, duration)
|
||||
return C.pxl8_sfx_play_note(self._ptr, note, params, volume or 0.8, duration or 0)
|
||||
end
|
||||
|
||||
function SfxContext:release_voice(voice_id)
|
||||
C.pxl8_sfx_release_voice(self._ptr, voice_id)
|
||||
end
|
||||
|
||||
function SfxContext:remove_node(node)
|
||||
C.pxl8_sfx_context_remove_node(self._ptr, node._ptr)
|
||||
end
|
||||
|
||||
function SfxContext:set_volume(volume)
|
||||
C.pxl8_sfx_context_set_volume(self._ptr, volume)
|
||||
end
|
||||
|
||||
function SfxContext:stop_all()
|
||||
C.pxl8_sfx_stop_all(self._ptr)
|
||||
end
|
||||
|
||||
function SfxContext:stop_voice(voice_id)
|
||||
C.pxl8_sfx_stop_voice(self._ptr, voice_id)
|
||||
end
|
||||
|
||||
sfx.SfxContext = SfxContext
|
||||
|
||||
local Compressor = setmetatable({}, { __index = SfxNode })
|
||||
Compressor.__index = Compressor
|
||||
|
||||
function Compressor.new(opts)
|
||||
opts = opts or {}
|
||||
local cfg = ffi.new("pxl8_sfx_compressor_config")
|
||||
cfg.attack = opts.attack or 10
|
||||
cfg.ratio = opts.ratio or 4
|
||||
cfg.release = opts.release or 100
|
||||
cfg.threshold = opts.threshold or -12
|
||||
local ptr = C.pxl8_sfx_compressor_create(cfg)
|
||||
if ptr == nil then return nil end
|
||||
return setmetatable({ _ptr = ptr }, Compressor)
|
||||
end
|
||||
|
||||
function Compressor:set_attack(attack)
|
||||
C.pxl8_sfx_compressor_set_attack(self._ptr, attack)
|
||||
end
|
||||
|
||||
function Compressor:set_ratio(ratio)
|
||||
C.pxl8_sfx_compressor_set_ratio(self._ptr, ratio)
|
||||
end
|
||||
|
||||
function Compressor:set_release(release)
|
||||
C.pxl8_sfx_compressor_set_release(self._ptr, release)
|
||||
end
|
||||
|
||||
function Compressor:set_threshold(threshold)
|
||||
C.pxl8_sfx_compressor_set_threshold(self._ptr, threshold)
|
||||
end
|
||||
|
||||
sfx.Compressor = Compressor
|
||||
|
||||
local Delay = setmetatable({}, { __index = SfxNode })
|
||||
Delay.__index = Delay
|
||||
|
||||
function Delay.new(opts)
|
||||
opts = opts or {}
|
||||
local cfg = ffi.new("pxl8_sfx_delay_config")
|
||||
cfg.feedback = opts.feedback or 0.4
|
||||
cfg.mix = opts.mix or 0.25
|
||||
cfg.time_l = opts.time_l or 350
|
||||
cfg.time_r = opts.time_r or 500
|
||||
local ptr = C.pxl8_sfx_delay_create(cfg)
|
||||
if ptr == nil then return nil end
|
||||
return setmetatable({ _ptr = ptr }, Delay)
|
||||
end
|
||||
|
||||
function Delay:set_feedback(feedback)
|
||||
C.pxl8_sfx_delay_set_feedback(self._ptr, feedback)
|
||||
end
|
||||
|
||||
function Delay:set_mix(mix)
|
||||
C.pxl8_sfx_delay_set_mix(self._ptr, mix)
|
||||
end
|
||||
|
||||
function Delay:set_time(time_l, time_r)
|
||||
C.pxl8_sfx_delay_set_time(self._ptr, time_l, time_r)
|
||||
end
|
||||
|
||||
sfx.Delay = Delay
|
||||
|
||||
local Reverb = setmetatable({}, { __index = SfxNode })
|
||||
Reverb.__index = Reverb
|
||||
|
||||
function Reverb.new(opts)
|
||||
opts = opts or {}
|
||||
local cfg = ffi.new("pxl8_sfx_reverb_config")
|
||||
cfg.damping = opts.damping or 0.5
|
||||
cfg.mix = opts.mix or 0.3
|
||||
cfg.room = opts.room or 0.5
|
||||
local ptr = C.pxl8_sfx_reverb_create(cfg)
|
||||
if ptr == nil then return nil end
|
||||
return setmetatable({ _ptr = ptr }, Reverb)
|
||||
end
|
||||
|
||||
function Reverb:set_damping(damping)
|
||||
C.pxl8_sfx_reverb_set_damping(self._ptr, damping)
|
||||
end
|
||||
|
||||
function Reverb:set_mix(mix)
|
||||
C.pxl8_sfx_reverb_set_mix(self._ptr, mix)
|
||||
end
|
||||
|
||||
function Reverb:set_room(room)
|
||||
C.pxl8_sfx_reverb_set_room(self._ptr, room)
|
||||
end
|
||||
|
||||
sfx.Reverb = Reverb
|
||||
|
||||
function sfx.get_master_volume()
|
||||
return C.pxl8_sfx_mixer_get_master_volume(core.sfx)
|
||||
end
|
||||
|
||||
sfx.note_to_freq = C.pxl8_sfx_note_to_freq
|
||||
|
||||
function sfx.set_master_volume(volume)
|
||||
C.pxl8_sfx_mixer_set_master_volume(core.sfx, volume)
|
||||
end
|
||||
|
||||
function sfx.voice_params(opts)
|
||||
opts = opts or {}
|
||||
local params = ffi.new("pxl8_sfx_voice_params")
|
||||
|
||||
params.amp_env.attack = opts.attack or 0.01
|
||||
params.amp_env.decay = opts.decay or 0.1
|
||||
params.amp_env.sustain = opts.sustain or 0.5
|
||||
params.amp_env.release = opts.release or 0.2
|
||||
|
||||
params.filter_env.attack = opts.filter_attack or 0.01
|
||||
params.filter_env.decay = opts.filter_decay or 0.1
|
||||
params.filter_env.sustain = opts.filter_sustain or 0.3
|
||||
params.filter_env.release = opts.filter_release or 0.1
|
||||
|
||||
params.filter_type = opts.filter_type or C.PXL8_SFX_FILTER_NONE
|
||||
params.lfo_target = opts.lfo_target or C.PXL8_SFX_LFO_PITCH
|
||||
params.lfo_waveform = opts.lfo_waveform or C.PXL8_SFX_WAVE_SINE
|
||||
params.waveform = opts.waveform or C.PXL8_SFX_WAVE_SINE
|
||||
|
||||
params.filter_cutoff = opts.filter_cutoff or 4000
|
||||
params.filter_env_depth = opts.filter_env_depth or 0
|
||||
params.filter_resonance = opts.filter_resonance or 0
|
||||
params.fx_send = opts.fx_send or 0
|
||||
params.lfo_depth = opts.lfo_depth or 0
|
||||
params.lfo_rate = opts.lfo_rate or 0
|
||||
params.pulse_width = opts.pulse_width or 0.5
|
||||
|
||||
return params
|
||||
end
|
||||
|
||||
return sfx
|
||||
106
src/lua/pxl8/tilemap.lua
Normal file
106
src/lua/pxl8/tilemap.lua
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
local core = require("pxl8.core")
|
||||
|
||||
local tilemap = {}
|
||||
|
||||
tilemap.TILE_FLIP_X = 1
|
||||
tilemap.TILE_FLIP_Y = 2
|
||||
tilemap.TILE_SOLID = 4
|
||||
tilemap.TILE_TRIGGER = 8
|
||||
|
||||
local Tilesheet = {}
|
||||
Tilesheet.__index = Tilesheet
|
||||
|
||||
function Tilesheet.new(tile_size)
|
||||
local ts = C.pxl8_tilesheet_create(tile_size or 16)
|
||||
if ts == nil then
|
||||
return nil
|
||||
end
|
||||
return setmetatable({ _ptr = ts }, Tilesheet)
|
||||
end
|
||||
|
||||
function Tilesheet:destroy()
|
||||
if self._ptr then
|
||||
C.pxl8_tilesheet_destroy(self._ptr)
|
||||
self._ptr = nil
|
||||
end
|
||||
end
|
||||
|
||||
function Tilesheet:load(filepath)
|
||||
return C.pxl8_tilesheet_load(self._ptr, filepath, core.gfx)
|
||||
end
|
||||
|
||||
tilemap.Tilesheet = Tilesheet
|
||||
|
||||
local Tilemap = {}
|
||||
Tilemap.__index = Tilemap
|
||||
|
||||
local tile_data = setmetatable({}, {__mode = "k"})
|
||||
|
||||
function Tilemap.new(width, height, tile_size)
|
||||
local tm = C.pxl8_tilemap_create(width, height, tile_size or 16)
|
||||
if tm == nil then
|
||||
return nil
|
||||
end
|
||||
return setmetatable({ _ptr = tm }, Tilemap)
|
||||
end
|
||||
|
||||
function Tilemap:check_collision(x, y, w, h)
|
||||
return C.pxl8_tilemap_check_collision(self._ptr, x, y, w, h)
|
||||
end
|
||||
|
||||
function Tilemap:destroy()
|
||||
if self._ptr then
|
||||
C.pxl8_tilemap_destroy(self._ptr)
|
||||
self._ptr = nil
|
||||
end
|
||||
end
|
||||
|
||||
function Tilemap:get_tile_data(tile_id)
|
||||
if tile_id == 0 then return nil end
|
||||
if not tile_data[self] then return nil end
|
||||
return tile_data[self][tile_id]
|
||||
end
|
||||
|
||||
function Tilemap:get_tile_id(layer, x, y)
|
||||
return C.pxl8_tilemap_get_tile_id(self._ptr, layer or 0, x, y)
|
||||
end
|
||||
|
||||
function Tilemap:is_solid(x, y)
|
||||
return C.pxl8_tilemap_is_solid(self._ptr, x, y)
|
||||
end
|
||||
|
||||
function Tilemap:load_ase(filepath, layer)
|
||||
return C.pxl8_tilemap_load_ase(self._ptr, filepath, layer or 0)
|
||||
end
|
||||
|
||||
function Tilemap:render()
|
||||
C.pxl8_tilemap_render(self._ptr, core.gfx)
|
||||
end
|
||||
|
||||
function Tilemap:render_layer(layer)
|
||||
C.pxl8_tilemap_render_layer(self._ptr, core.gfx, layer)
|
||||
end
|
||||
|
||||
function Tilemap:set_camera(x, y)
|
||||
C.pxl8_tilemap_set_camera(self._ptr, x, y)
|
||||
end
|
||||
|
||||
function Tilemap:set_tile(layer, x, y, tile_id, flags)
|
||||
C.pxl8_tilemap_set_tile(self._ptr, layer or 0, x, y, tile_id or 0, flags or 0)
|
||||
end
|
||||
|
||||
function Tilemap:set_tile_data(tile_id, data)
|
||||
if tile_id == 0 then return end
|
||||
if not tile_data[self] then tile_data[self] = {} end
|
||||
tile_data[self][tile_id] = data
|
||||
end
|
||||
|
||||
function Tilemap:set_tilesheet(tilesheet)
|
||||
return C.pxl8_tilemap_set_tilesheet(self._ptr, tilesheet._ptr)
|
||||
end
|
||||
|
||||
tilemap.Tilemap = Tilemap
|
||||
|
||||
return tilemap
|
||||
82
src/lua/pxl8/transition.lua
Normal file
82
src/lua/pxl8/transition.lua
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
local core = require("pxl8.core")
|
||||
|
||||
local transition = {}
|
||||
|
||||
local TYPES = {
|
||||
fade = 0,
|
||||
wipe_left = 1,
|
||||
wipe_right = 2,
|
||||
wipe_up = 3,
|
||||
wipe_down = 4,
|
||||
circle_open = 5,
|
||||
circle_close = 6,
|
||||
dissolve = 7,
|
||||
pixelate = 8
|
||||
}
|
||||
|
||||
transition.TYPES = TYPES
|
||||
|
||||
local Transition = {}
|
||||
Transition.__index = Transition
|
||||
|
||||
function Transition.new(type_name, duration)
|
||||
local type_id = TYPES[type_name] or 0
|
||||
local t = C.pxl8_transition_create(type_id, duration or 1.0)
|
||||
if t == nil then
|
||||
return nil
|
||||
end
|
||||
return setmetatable({ _ptr = t }, Transition)
|
||||
end
|
||||
|
||||
function Transition:destroy()
|
||||
if self._ptr then
|
||||
C.pxl8_transition_destroy(self._ptr)
|
||||
self._ptr = nil
|
||||
end
|
||||
end
|
||||
|
||||
function Transition:get_progress()
|
||||
return C.pxl8_transition_get_progress(self._ptr)
|
||||
end
|
||||
|
||||
function Transition:is_active()
|
||||
return C.pxl8_transition_is_active(self._ptr)
|
||||
end
|
||||
|
||||
function Transition:is_complete()
|
||||
return C.pxl8_transition_is_complete(self._ptr)
|
||||
end
|
||||
|
||||
function Transition:render()
|
||||
C.pxl8_transition_render(self._ptr, core.gfx)
|
||||
end
|
||||
|
||||
function Transition:reset()
|
||||
C.pxl8_transition_reset(self._ptr)
|
||||
end
|
||||
|
||||
function Transition:set_color(color)
|
||||
C.pxl8_transition_set_color(self._ptr, color)
|
||||
end
|
||||
|
||||
function Transition:set_reverse(reverse)
|
||||
C.pxl8_transition_set_reverse(self._ptr, reverse)
|
||||
end
|
||||
|
||||
function Transition:start()
|
||||
C.pxl8_transition_start(self._ptr)
|
||||
end
|
||||
|
||||
function Transition:stop()
|
||||
C.pxl8_transition_stop(self._ptr)
|
||||
end
|
||||
|
||||
function Transition:update(dt)
|
||||
C.pxl8_transition_update(self._ptr, dt)
|
||||
end
|
||||
|
||||
transition.Transition = Transition
|
||||
|
||||
return transition
|
||||
120
src/lua/pxl8/world.lua
Normal file
120
src/lua/pxl8/world.lua
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
local core = require("pxl8.core")
|
||||
|
||||
local world = {}
|
||||
|
||||
world.PROCGEN_ROOMS = C.PXL8_PROCGEN_ROOMS
|
||||
world.PROCGEN_TERRAIN = C.PXL8_PROCGEN_TERRAIN
|
||||
|
||||
local World = {}
|
||||
World.__index = World
|
||||
|
||||
function World.new()
|
||||
local w = C.pxl8_world_create()
|
||||
if w == nil then
|
||||
return nil
|
||||
end
|
||||
return setmetatable({ _ptr = w }, World)
|
||||
end
|
||||
|
||||
function World:apply_textures(texture_defs)
|
||||
local count = #texture_defs
|
||||
local textures = ffi.new("pxl8_world_texture[?]", count)
|
||||
|
||||
for i, def in ipairs(texture_defs) do
|
||||
local idx = i - 1
|
||||
ffi.copy(textures[idx].name, def.name or "", math.min(#(def.name or ""), 15))
|
||||
textures[idx].texture_id = def.texture_id or 0
|
||||
|
||||
if def.rule then
|
||||
textures[idx].rule = ffi.cast("bool (*)(const pxl8_vec3*, const pxl8_bsp_face*, const pxl8_bsp*)",
|
||||
function(normal, face, bsp)
|
||||
return def.rule(normal[0], face, bsp)
|
||||
end)
|
||||
else
|
||||
textures[idx].rule = nil
|
||||
end
|
||||
end
|
||||
|
||||
return C.pxl8_world_apply_textures(self._ptr, textures, count)
|
||||
end
|
||||
|
||||
function World:check_collision(x, y, z, radius)
|
||||
local pos = ffi.new("pxl8_vec3", {x = x, y = y, z = z})
|
||||
return C.pxl8_world_check_collision(self._ptr, pos, radius)
|
||||
end
|
||||
|
||||
function World:destroy()
|
||||
if self._ptr then
|
||||
C.pxl8_world_destroy(self._ptr)
|
||||
self._ptr = nil
|
||||
end
|
||||
end
|
||||
|
||||
function World:generate(params)
|
||||
local c_params = ffi.new("pxl8_procgen_params")
|
||||
c_params.type = params.type or C.PXL8_PROCGEN_ROOMS
|
||||
c_params.width = params.width or 32
|
||||
c_params.height = params.height or 32
|
||||
c_params.depth = params.depth or 0
|
||||
c_params.seed = params.seed or 0
|
||||
c_params.min_room_size = params.min_room_size or 5
|
||||
c_params.max_room_size = params.max_room_size or 10
|
||||
c_params.num_rooms = params.num_rooms or 8
|
||||
return C.pxl8_world_generate(self._ptr, core.gfx, c_params)
|
||||
end
|
||||
|
||||
function World:is_loaded()
|
||||
return C.pxl8_world_is_loaded(self._ptr)
|
||||
end
|
||||
|
||||
function World:load(filepath)
|
||||
return C.pxl8_world_load(self._ptr, filepath)
|
||||
end
|
||||
|
||||
function World:render(camera_pos)
|
||||
local vec = ffi.new("pxl8_vec3", {x = camera_pos[1], y = camera_pos[2], z = camera_pos[3]})
|
||||
C.pxl8_world_render(self._ptr, core.gfx, vec)
|
||||
end
|
||||
|
||||
function World:resolve_collision(from_x, from_y, from_z, to_x, to_y, to_z, radius)
|
||||
local from = ffi.new("pxl8_vec3", {x = from_x, y = from_y, z = from_z})
|
||||
local to = ffi.new("pxl8_vec3", {x = to_x, y = to_y, z = to_z})
|
||||
local result = C.pxl8_world_resolve_collision(self._ptr, from, to, radius)
|
||||
return result.x, result.y, result.z
|
||||
end
|
||||
|
||||
function World:unload()
|
||||
C.pxl8_world_unload(self._ptr)
|
||||
end
|
||||
|
||||
world.World = World
|
||||
|
||||
function world.procgen_tex(params)
|
||||
local width = params.width or 64
|
||||
local height = params.height or 64
|
||||
local buffer = ffi.new("u8[?]", width * height)
|
||||
local tex_params = ffi.new("pxl8_procgen_tex_params")
|
||||
|
||||
local name = params.name or ""
|
||||
ffi.copy(tex_params.name, name, math.min(#name, 15))
|
||||
|
||||
tex_params.seed = params.seed or 0
|
||||
tex_params.width = width
|
||||
tex_params.height = height
|
||||
tex_params.scale = params.scale or 1.0
|
||||
tex_params.roughness = params.roughness or 0.0
|
||||
tex_params.base_color = params.base_color or 0
|
||||
tex_params.variation = params.variation or 0
|
||||
|
||||
C.pxl8_procgen_tex(buffer, tex_params)
|
||||
|
||||
local tex_id = C.pxl8_gfx_create_texture(core.gfx, buffer, width, height)
|
||||
if tex_id < 0 then
|
||||
return nil
|
||||
end
|
||||
return tex_id
|
||||
end
|
||||
|
||||
return world
|
||||
310
src/math/pxl8_math.c
Normal file
310
src/math/pxl8_math.c
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
#include "pxl8_math.h"
|
||||
|
||||
pxl8_vec2 pxl8_vec2_add(pxl8_vec2 a, pxl8_vec2 b) {
|
||||
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){
|
||||
.x = a.x - b.x,
|
||||
.y = a.y - b.y,
|
||||
};
|
||||
}
|
||||
|
||||
pxl8_vec2 pxl8_vec2_scale(pxl8_vec2 v, f32 s) {
|
||||
return (pxl8_vec2){
|
||||
.x = v.x * s,
|
||||
.y = v.y * s,
|
||||
};
|
||||
}
|
||||
|
||||
f32 pxl8_vec2_dot(pxl8_vec2 a, pxl8_vec2 b) {
|
||||
return a.x * b.x + a.y * b.y;
|
||||
}
|
||||
|
||||
f32 pxl8_vec2_length(pxl8_vec2 v) {
|
||||
return sqrtf(v.x * v.x + v.y * v.y);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
pxl8_vec3 pxl8_vec3_add(pxl8_vec3 a, pxl8_vec3 b) {
|
||||
return (pxl8_vec3){
|
||||
.x = a.x + b.x,
|
||||
.y = a.y + b.y,
|
||||
.z = a.z + b.z,
|
||||
};
|
||||
}
|
||||
|
||||
pxl8_vec3 pxl8_vec3_sub(pxl8_vec3 a, pxl8_vec3 b) {
|
||||
return (pxl8_vec3){
|
||||
.x = a.x - b.x,
|
||||
.y = a.y - b.y,
|
||||
.z = a.z - b.z,
|
||||
};
|
||||
}
|
||||
|
||||
pxl8_vec3 pxl8_vec3_scale(pxl8_vec3 v, f32 s) {
|
||||
return (pxl8_vec3){
|
||||
.x = v.x * s,
|
||||
.y = v.y * s,
|
||||
.z = v.z * s,
|
||||
};
|
||||
}
|
||||
|
||||
f32 pxl8_vec3_dot(pxl8_vec3 a, pxl8_vec3 b) {
|
||||
return a.x * b.x + a.y * b.y + a.z * b.z;
|
||||
}
|
||||
|
||||
pxl8_vec3 pxl8_vec3_cross(pxl8_vec3 a, pxl8_vec3 b) {
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
f32 pxl8_vec3_length(pxl8_vec3 v) {
|
||||
return sqrtf(pxl8_vec3_dot(v, v));
|
||||
}
|
||||
|
||||
pxl8_vec3 pxl8_vec3_lerp(pxl8_vec3 a, pxl8_vec3 b, f32 t) {
|
||||
return (pxl8_vec3){
|
||||
a.x + (b.x - a.x) * t,
|
||||
a.y + (b.y - a.y) * t,
|
||||
a.z + (b.z - a.z) * t
|
||||
};
|
||||
}
|
||||
|
||||
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_mul(pxl8_mat4 a, pxl8_mat4 b) {
|
||||
pxl8_mat4 mat = {0};
|
||||
|
||||
for (i32 col = 0; col < 4; col++) {
|
||||
for (i32 row = 0; row < 4; row++) {
|
||||
mat.m[col * 4 + row] =
|
||||
a.m[0 * 4 + row] * b.m[col * 4 + 0] +
|
||||
a.m[1 * 4 + row] * b.m[col * 4 + 1] +
|
||||
a.m[2 * 4 + row] * b.m[col * 4 + 2] +
|
||||
a.m[3 * 4 + row] * b.m[col * 4 + 3];
|
||||
}
|
||||
}
|
||||
|
||||
return mat;
|
||||
}
|
||||
|
||||
pxl8_vec4 pxl8_mat4_mul_vec4(pxl8_mat4 m, pxl8_vec4 v) {
|
||||
return (pxl8_vec4){
|
||||
.x = m.m[0] * v.x + m.m[4] * v.y + m.m[8] * v.z + m.m[12] * v.w,
|
||||
.y = m.m[1] * v.x + m.m[5] * v.y + m.m[9] * v.z + m.m[13] * v.w,
|
||||
.z = m.m[2] * v.x + m.m[6] * v.y + m.m[10] * v.z + m.m[14] * v.w,
|
||||
.w = m.m[3] * v.x + m.m[7] * v.y + m.m[11] * v.z + m.m[15] * v.w,
|
||||
};
|
||||
}
|
||||
|
||||
pxl8_vec3 pxl8_mat4_mul_vec3(pxl8_mat4 m, pxl8_vec3 v) {
|
||||
return (pxl8_vec3){
|
||||
.x = m.m[0] * v.x + m.m[4] * v.y + m.m[8] * v.z,
|
||||
.y = m.m[1] * v.x + m.m[5] * v.y + m.m[9] * v.z,
|
||||
.z = m.m[2] * v.x + m.m[6] * v.y + m.m[10] * v.z,
|
||||
};
|
||||
}
|
||||
|
||||
pxl8_mat4 pxl8_mat4_translate(f32 x, f32 y, f32 z) {
|
||||
pxl8_mat4 mat = pxl8_mat4_identity();
|
||||
|
||||
mat.m[12] = x;
|
||||
mat.m[13] = y;
|
||||
mat.m[14] = z;
|
||||
|
||||
return mat;
|
||||
}
|
||||
|
||||
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[9] = -s;
|
||||
mat.m[6] = s;
|
||||
mat.m[10] = c;
|
||||
|
||||
return mat;
|
||||
}
|
||||
|
||||
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[8] = s;
|
||||
mat.m[2] = -s;
|
||||
mat.m[10] = c;
|
||||
|
||||
return mat;
|
||||
}
|
||||
|
||||
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[4] = -s;
|
||||
mat.m[1] = 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;
|
||||
}
|
||||
|
||||
pxl8_mat4 pxl8_mat4_ortho(f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far) {
|
||||
pxl8_mat4 mat = {0};
|
||||
|
||||
mat.m[0] = 2.0f / (right - left);
|
||||
mat.m[5] = 2.0f / (top - bottom);
|
||||
mat.m[10] = -2.0f / (far - near);
|
||||
mat.m[12] = -(right + left) / (right - left);
|
||||
mat.m[13] = -(top + bottom) / (top - bottom);
|
||||
mat.m[14] = -(far + near) / (far - near);
|
||||
mat.m[15] = 1.0f;
|
||||
|
||||
return mat;
|
||||
}
|
||||
|
||||
pxl8_mat4 pxl8_mat4_perspective(f32 fov, f32 aspect, f32 near, f32 far) {
|
||||
pxl8_mat4 mat = {0};
|
||||
f32 tan_half_fov = tanf(fov / 2.0f);
|
||||
|
||||
mat.m[0] = 1.0f / (aspect * tan_half_fov);
|
||||
mat.m[5] = 1.0f / tan_half_fov;
|
||||
mat.m[10] = -(far + near) / (far - near);
|
||||
mat.m[14] = -(2.0f * far * near) / (far - near);
|
||||
mat.m[11] = -1.0f;
|
||||
|
||||
return mat;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
mat.m[0] = s.x;
|
||||
mat.m[4] = s.y;
|
||||
mat.m[8] = s.z;
|
||||
mat.m[1] = u.x;
|
||||
mat.m[5] = u.y;
|
||||
mat.m[9] = u.z;
|
||||
mat.m[2] = -f.x;
|
||||
mat.m[6] = -f.y;
|
||||
mat.m[10] = -f.z;
|
||||
mat.m[12] = -pxl8_vec3_dot(s, eye);
|
||||
mat.m[13] = -pxl8_vec3_dot(u, eye);
|
||||
mat.m[14] = pxl8_vec3_dot(f, eye);
|
||||
|
||||
return mat;
|
||||
}
|
||||
|
||||
pxl8_frustum pxl8_frustum_from_matrix(pxl8_mat4 vp) {
|
||||
pxl8_frustum frustum;
|
||||
const f32* m = vp.m;
|
||||
|
||||
frustum.planes[0].normal.x = m[3] - m[0];
|
||||
frustum.planes[0].normal.y = m[7] - m[4];
|
||||
frustum.planes[0].normal.z = m[11] - m[8];
|
||||
frustum.planes[0].distance = m[15] - m[12];
|
||||
|
||||
frustum.planes[1].normal.x = m[3] + m[0];
|
||||
frustum.planes[1].normal.y = m[7] + m[4];
|
||||
frustum.planes[1].normal.z = m[11] + m[8];
|
||||
frustum.planes[1].distance = m[15] + m[12];
|
||||
|
||||
frustum.planes[2].normal.x = m[3] + m[1];
|
||||
frustum.planes[2].normal.y = m[7] + m[5];
|
||||
frustum.planes[2].normal.z = m[11] + m[9];
|
||||
frustum.planes[2].distance = m[15] + m[13];
|
||||
|
||||
frustum.planes[3].normal.x = m[3] - m[1];
|
||||
frustum.planes[3].normal.y = m[7] - m[5];
|
||||
frustum.planes[3].normal.z = m[11] - m[9];
|
||||
frustum.planes[3].distance = m[15] - m[13];
|
||||
|
||||
frustum.planes[4].normal.x = m[3] - m[2];
|
||||
frustum.planes[4].normal.y = m[7] - m[6];
|
||||
frustum.planes[4].normal.z = m[11] - m[10];
|
||||
frustum.planes[4].distance = m[15] - m[14];
|
||||
|
||||
frustum.planes[5].normal.x = m[3] + m[2];
|
||||
frustum.planes[5].normal.y = m[7] + m[6];
|
||||
frustum.planes[5].normal.z = m[11] + m[10];
|
||||
frustum.planes[5].distance = m[15] + m[14];
|
||||
|
||||
for (i32 i = 0; i < 6; i++) {
|
||||
f32 len = pxl8_vec3_length(frustum.planes[i].normal);
|
||||
if (len > 1e-6f) {
|
||||
f32 inv_len = 1.0f / len;
|
||||
frustum.planes[i].normal = pxl8_vec3_scale(frustum.planes[i].normal, inv_len);
|
||||
frustum.planes[i].distance *= inv_len;
|
||||
}
|
||||
}
|
||||
|
||||
return frustum;
|
||||
}
|
||||
|
||||
bool pxl8_frustum_test_aabb(const pxl8_frustum* frustum, pxl8_vec3 min, pxl8_vec3 max) {
|
||||
for (i32 i = 0; i < 6; i++) {
|
||||
pxl8_vec3 normal = frustum->planes[i].normal;
|
||||
f32 d = frustum->planes[i].distance;
|
||||
|
||||
pxl8_vec3 p_vertex = {
|
||||
(normal.x >= 0.0f) ? max.x : min.x,
|
||||
(normal.y >= 0.0f) ? max.y : min.y,
|
||||
(normal.z >= 0.0f) ? max.z : min.z
|
||||
};
|
||||
|
||||
f32 p_dist = pxl8_vec3_dot(normal, p_vertex) + d;
|
||||
|
||||
if (p_dist < 0.0f) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
82
src/math/pxl8_math.h
Normal file
82
src/math/pxl8_math.h
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
#pragma once
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#define PXL8_PI 3.14159265358979323846f
|
||||
#define PXL8_TAU (PXL8_PI * 2.0f)
|
||||
|
||||
static inline f32 pxl8_fast_inv_sqrt(f32 x) {
|
||||
f32 half = 0.5f * x;
|
||||
i32 i = *(i32*)&x;
|
||||
i = 0x5f3759df - (i >> 1);
|
||||
x = *(f32*)&i;
|
||||
x = x * (1.5f - half * x * x);
|
||||
return x;
|
||||
}
|
||||
|
||||
typedef struct pxl8_vec2 {
|
||||
f32 x, y;
|
||||
} pxl8_vec2;
|
||||
|
||||
typedef struct pxl8_vec3 {
|
||||
f32 x, y, z;
|
||||
} pxl8_vec3;
|
||||
|
||||
typedef struct pxl8_vec4 {
|
||||
f32 x, y, z, w;
|
||||
} pxl8_vec4;
|
||||
|
||||
typedef struct pxl8_mat4 {
|
||||
f32 m[16];
|
||||
} pxl8_mat4;
|
||||
|
||||
typedef struct pxl8_plane {
|
||||
pxl8_vec3 normal;
|
||||
f32 distance;
|
||||
} pxl8_plane;
|
||||
|
||||
typedef struct pxl8_frustum {
|
||||
pxl8_plane planes[6];
|
||||
} pxl8_frustum;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_vec2 pxl8_vec2_add(pxl8_vec2 a, pxl8_vec2 b);
|
||||
f32 pxl8_vec2_dot(pxl8_vec2 a, pxl8_vec2 b);
|
||||
f32 pxl8_vec2_length(pxl8_vec2 v);
|
||||
pxl8_vec2 pxl8_vec2_normalize(pxl8_vec2 v);
|
||||
pxl8_vec2 pxl8_vec2_scale(pxl8_vec2 v, f32 s);
|
||||
pxl8_vec2 pxl8_vec2_sub(pxl8_vec2 a, pxl8_vec2 b);
|
||||
|
||||
pxl8_vec3 pxl8_vec3_add(pxl8_vec3 a, pxl8_vec3 b);
|
||||
pxl8_vec3 pxl8_vec3_cross(pxl8_vec3 a, pxl8_vec3 b);
|
||||
f32 pxl8_vec3_dot(pxl8_vec3 a, pxl8_vec3 b);
|
||||
f32 pxl8_vec3_length(pxl8_vec3 v);
|
||||
pxl8_vec3 pxl8_vec3_lerp(pxl8_vec3 a, pxl8_vec3 b, f32 t);
|
||||
pxl8_vec3 pxl8_vec3_normalize(pxl8_vec3 v);
|
||||
pxl8_vec3 pxl8_vec3_scale(pxl8_vec3 v, f32 s);
|
||||
pxl8_vec3 pxl8_vec3_sub(pxl8_vec3 a, pxl8_vec3 b);
|
||||
|
||||
pxl8_mat4 pxl8_mat4_identity(void);
|
||||
pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up);
|
||||
pxl8_mat4 pxl8_mat4_mul(pxl8_mat4 a, pxl8_mat4 b);
|
||||
pxl8_vec3 pxl8_mat4_mul_vec3(pxl8_mat4 m, pxl8_vec3 v);
|
||||
pxl8_vec4 pxl8_mat4_mul_vec4(pxl8_mat4 m, pxl8_vec4 v);
|
||||
pxl8_mat4 pxl8_mat4_ortho(f32 left, f32 right, f32 bottom, f32 top, f32 near, f32 far);
|
||||
pxl8_mat4 pxl8_mat4_perspective(f32 fov, f32 aspect, f32 near, f32 far);
|
||||
pxl8_mat4 pxl8_mat4_rotate_x(f32 angle);
|
||||
pxl8_mat4 pxl8_mat4_rotate_y(f32 angle);
|
||||
pxl8_mat4 pxl8_mat4_rotate_z(f32 angle);
|
||||
pxl8_mat4 pxl8_mat4_scale(f32 x, f32 y, f32 z);
|
||||
pxl8_mat4 pxl8_mat4_translate(f32 x, f32 y, f32 z);
|
||||
|
||||
pxl8_frustum pxl8_frustum_from_matrix(pxl8_mat4 vp);
|
||||
bool pxl8_frustum_test_aabb(const pxl8_frustum* frustum, pxl8_vec3 min, pxl8_vec3 max);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
291
src/math/pxl8_simd.h
Normal file
291
src/math/pxl8_simd.h
Normal file
|
|
@ -0,0 +1,291 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#if defined(__x86_64__) || defined(_M_X64)
|
||||
#define PXL8_SIMD_SSE2 1
|
||||
#include <emmintrin.h>
|
||||
#elif defined(__aarch64__) || defined(_M_ARM64)
|
||||
#define PXL8_SIMD_NEON 1
|
||||
#include <arm_neon.h>
|
||||
#else
|
||||
#define PXL8_SIMD_SCALAR 1
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if defined(PXL8_SIMD_SSE2)
|
||||
|
||||
typedef struct { __m128 v; } pxl8_f32x4;
|
||||
typedef struct { __m128i v; } pxl8_i32x4;
|
||||
typedef struct { __m128i v; } pxl8_u16x8;
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_splat(f32 x) {
|
||||
return (pxl8_f32x4){ _mm_set1_ps(x) };
|
||||
}
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_new(f32 a, f32 b, f32 c, f32 d) {
|
||||
return (pxl8_f32x4){ _mm_set_ps(d, c, b, a) };
|
||||
}
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_add(pxl8_f32x4 a, pxl8_f32x4 b) {
|
||||
return (pxl8_f32x4){ _mm_add_ps(a.v, b.v) };
|
||||
}
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_sub(pxl8_f32x4 a, pxl8_f32x4 b) {
|
||||
return (pxl8_f32x4){ _mm_sub_ps(a.v, b.v) };
|
||||
}
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_mul(pxl8_f32x4 a, pxl8_f32x4 b) {
|
||||
return (pxl8_f32x4){ _mm_mul_ps(a.v, b.v) };
|
||||
}
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_div(pxl8_f32x4 a, pxl8_f32x4 b) {
|
||||
return (pxl8_f32x4){ _mm_div_ps(a.v, b.v) };
|
||||
}
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_min(pxl8_f32x4 a, pxl8_f32x4 b) {
|
||||
return (pxl8_f32x4){ _mm_min_ps(a.v, b.v) };
|
||||
}
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_max(pxl8_f32x4 a, pxl8_f32x4 b) {
|
||||
return (pxl8_f32x4){ _mm_max_ps(a.v, b.v) };
|
||||
}
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_cmplt(pxl8_f32x4 a, pxl8_f32x4 b) {
|
||||
return (pxl8_f32x4){ _mm_cmplt_ps(a.v, b.v) };
|
||||
}
|
||||
|
||||
static inline i32 pxl8_f32x4_movemask(pxl8_f32x4 a) {
|
||||
return _mm_movemask_ps(a.v);
|
||||
}
|
||||
|
||||
static inline pxl8_i32x4 pxl8_f32x4_to_i32x4(pxl8_f32x4 a) {
|
||||
return (pxl8_i32x4){ _mm_cvttps_epi32(a.v) };
|
||||
}
|
||||
|
||||
static inline void pxl8_f32x4_store(pxl8_f32x4 a, f32* out) {
|
||||
_mm_storeu_ps(out, a.v);
|
||||
}
|
||||
|
||||
static inline pxl8_i32x4 pxl8_i32x4_splat(i32 x) {
|
||||
return (pxl8_i32x4){ _mm_set1_epi32(x) };
|
||||
}
|
||||
|
||||
static inline pxl8_i32x4 pxl8_i32x4_slli(pxl8_i32x4 a, i32 n) {
|
||||
return (pxl8_i32x4){ _mm_slli_epi32(a.v, n) };
|
||||
}
|
||||
|
||||
static inline pxl8_i32x4 pxl8_i32x4_srai(pxl8_i32x4 a, i32 n) {
|
||||
return (pxl8_i32x4){ _mm_srai_epi32(a.v, n) };
|
||||
}
|
||||
|
||||
static inline pxl8_i32x4 pxl8_i32x4_and(pxl8_i32x4 a, pxl8_i32x4 b) {
|
||||
return (pxl8_i32x4){ _mm_and_si128(a.v, b.v) };
|
||||
}
|
||||
|
||||
static inline pxl8_i32x4 pxl8_i32x4_or(pxl8_i32x4 a, pxl8_i32x4 b) {
|
||||
return (pxl8_i32x4){ _mm_or_si128(a.v, b.v) };
|
||||
}
|
||||
|
||||
static inline void pxl8_i32x4_store(pxl8_i32x4 a, i32* out) {
|
||||
_mm_storeu_si128((__m128i*)out, a.v);
|
||||
}
|
||||
|
||||
static inline pxl8_u16x8 pxl8_u16x8_cmplt(pxl8_u16x8 a, pxl8_u16x8 b) {
|
||||
return (pxl8_u16x8){ _mm_cmplt_epi16(a.v, b.v) };
|
||||
}
|
||||
|
||||
static inline pxl8_u16x8 pxl8_u16x8_blend(pxl8_u16x8 a, pxl8_u16x8 b, pxl8_u16x8 mask) {
|
||||
__m128i not_mask = _mm_andnot_si128(mask.v, a.v);
|
||||
__m128i and_mask = _mm_and_si128(mask.v, b.v);
|
||||
return (pxl8_u16x8){ _mm_or_si128(not_mask, and_mask) };
|
||||
}
|
||||
|
||||
static inline i32 pxl8_u16x8_movemask(pxl8_u16x8 a) {
|
||||
return _mm_movemask_epi8(a.v);
|
||||
}
|
||||
|
||||
#elif defined(PXL8_SIMD_NEON)
|
||||
|
||||
typedef struct { float32x4_t v; } pxl8_f32x4;
|
||||
typedef struct { int32x4_t v; } pxl8_i32x4;
|
||||
typedef struct { uint16x8_t v; } pxl8_u16x8;
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_splat(f32 x) {
|
||||
return (pxl8_f32x4){ vdupq_n_f32(x) };
|
||||
}
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_new(f32 a, f32 b, f32 c, f32 d) {
|
||||
f32 arr[4] = {a, b, c, d};
|
||||
return (pxl8_f32x4){ vld1q_f32(arr) };
|
||||
}
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_add(pxl8_f32x4 a, pxl8_f32x4 b) {
|
||||
return (pxl8_f32x4){ vaddq_f32(a.v, b.v) };
|
||||
}
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_sub(pxl8_f32x4 a, pxl8_f32x4 b) {
|
||||
return (pxl8_f32x4){ vsubq_f32(a.v, b.v) };
|
||||
}
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_mul(pxl8_f32x4 a, pxl8_f32x4 b) {
|
||||
return (pxl8_f32x4){ vmulq_f32(a.v, b.v) };
|
||||
}
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_div(pxl8_f32x4 a, pxl8_f32x4 b) {
|
||||
return (pxl8_f32x4){ vdivq_f32(a.v, b.v) };
|
||||
}
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_min(pxl8_f32x4 a, pxl8_f32x4 b) {
|
||||
return (pxl8_f32x4){ vminq_f32(a.v, b.v) };
|
||||
}
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_max(pxl8_f32x4 a, pxl8_f32x4 b) {
|
||||
return (pxl8_f32x4){ vmaxq_f32(a.v, b.v) };
|
||||
}
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_cmplt(pxl8_f32x4 a, pxl8_f32x4 b) {
|
||||
uint32x4_t cmp = vcltq_f32(a.v, b.v);
|
||||
return (pxl8_f32x4){ vreinterpretq_f32_u32(cmp) };
|
||||
}
|
||||
|
||||
static inline i32 pxl8_f32x4_movemask(pxl8_f32x4 a) {
|
||||
uint32x4_t input = vreinterpretq_u32_f32(a.v);
|
||||
static const i32 shifts[4] = {0, 1, 2, 3};
|
||||
uint32x4_t shifted = vshrq_n_u32(input, 31);
|
||||
return vgetq_lane_u32(shifted, 0) | (vgetq_lane_u32(shifted, 1) << 1) |
|
||||
(vgetq_lane_u32(shifted, 2) << 2) | (vgetq_lane_u32(shifted, 3) << 3);
|
||||
}
|
||||
|
||||
static inline pxl8_i32x4 pxl8_f32x4_to_i32x4(pxl8_f32x4 a) {
|
||||
return (pxl8_i32x4){ vcvtq_s32_f32(a.v) };
|
||||
}
|
||||
|
||||
static inline void pxl8_f32x4_store(pxl8_f32x4 a, f32* out) {
|
||||
vst1q_f32(out, a.v);
|
||||
}
|
||||
|
||||
static inline pxl8_i32x4 pxl8_i32x4_splat(i32 x) {
|
||||
return (pxl8_i32x4){ vdupq_n_s32(x) };
|
||||
}
|
||||
|
||||
static inline pxl8_i32x4 pxl8_i32x4_slli(pxl8_i32x4 a, i32 n) {
|
||||
return (pxl8_i32x4){ vshlq_s32(a.v, vdupq_n_s32(n)) };
|
||||
}
|
||||
|
||||
static inline pxl8_i32x4 pxl8_i32x4_srai(pxl8_i32x4 a, i32 n) {
|
||||
return (pxl8_i32x4){ vshlq_s32(a.v, vdupq_n_s32(-n)) };
|
||||
}
|
||||
|
||||
static inline pxl8_i32x4 pxl8_i32x4_and(pxl8_i32x4 a, pxl8_i32x4 b) {
|
||||
return (pxl8_i32x4){ vandq_s32(a.v, b.v) };
|
||||
}
|
||||
|
||||
static inline pxl8_i32x4 pxl8_i32x4_or(pxl8_i32x4 a, pxl8_i32x4 b) {
|
||||
return (pxl8_i32x4){ vorrq_s32(a.v, b.v) };
|
||||
}
|
||||
|
||||
static inline void pxl8_i32x4_store(pxl8_i32x4 a, i32* out) {
|
||||
vst1q_s32(out, a.v);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
typedef struct { f32 v[4]; } pxl8_f32x4;
|
||||
typedef struct { i32 v[4]; } pxl8_i32x4;
|
||||
typedef struct { u16 v[8]; } pxl8_u16x8;
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_splat(f32 x) {
|
||||
return (pxl8_f32x4){{ x, x, x, x }};
|
||||
}
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_new(f32 a, f32 b, f32 c, f32 d) {
|
||||
return (pxl8_f32x4){{ a, b, c, d }};
|
||||
}
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_add(pxl8_f32x4 a, pxl8_f32x4 b) {
|
||||
return (pxl8_f32x4){{ a.v[0]+b.v[0], a.v[1]+b.v[1], a.v[2]+b.v[2], a.v[3]+b.v[3] }};
|
||||
}
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_sub(pxl8_f32x4 a, pxl8_f32x4 b) {
|
||||
return (pxl8_f32x4){{ a.v[0]-b.v[0], a.v[1]-b.v[1], a.v[2]-b.v[2], a.v[3]-b.v[3] }};
|
||||
}
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_mul(pxl8_f32x4 a, pxl8_f32x4 b) {
|
||||
return (pxl8_f32x4){{ a.v[0]*b.v[0], a.v[1]*b.v[1], a.v[2]*b.v[2], a.v[3]*b.v[3] }};
|
||||
}
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_div(pxl8_f32x4 a, pxl8_f32x4 b) {
|
||||
return (pxl8_f32x4){{ a.v[0]/b.v[0], a.v[1]/b.v[1], a.v[2]/b.v[2], a.v[3]/b.v[3] }};
|
||||
}
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_min(pxl8_f32x4 a, pxl8_f32x4 b) {
|
||||
return (pxl8_f32x4){{
|
||||
a.v[0]<b.v[0]?a.v[0]:b.v[0], a.v[1]<b.v[1]?a.v[1]:b.v[1],
|
||||
a.v[2]<b.v[2]?a.v[2]:b.v[2], a.v[3]<b.v[3]?a.v[3]:b.v[3]
|
||||
}};
|
||||
}
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_max(pxl8_f32x4 a, pxl8_f32x4 b) {
|
||||
return (pxl8_f32x4){{
|
||||
a.v[0]>b.v[0]?a.v[0]:b.v[0], a.v[1]>b.v[1]?a.v[1]:b.v[1],
|
||||
a.v[2]>b.v[2]?a.v[2]:b.v[2], a.v[3]>b.v[3]?a.v[3]:b.v[3]
|
||||
}};
|
||||
}
|
||||
|
||||
static inline pxl8_f32x4 pxl8_f32x4_cmplt(pxl8_f32x4 a, pxl8_f32x4 b) {
|
||||
pxl8_f32x4 r;
|
||||
u32* rv = (u32*)r.v;
|
||||
rv[0] = a.v[0] < b.v[0] ? 0xFFFFFFFF : 0;
|
||||
rv[1] = a.v[1] < b.v[1] ? 0xFFFFFFFF : 0;
|
||||
rv[2] = a.v[2] < b.v[2] ? 0xFFFFFFFF : 0;
|
||||
rv[3] = a.v[3] < b.v[3] ? 0xFFFFFFFF : 0;
|
||||
return r;
|
||||
}
|
||||
|
||||
static inline i32 pxl8_f32x4_movemask(pxl8_f32x4 a) {
|
||||
u32* av = (u32*)a.v;
|
||||
return ((av[0] >> 31) & 1) | ((av[1] >> 31) & 1) << 1 |
|
||||
((av[2] >> 31) & 1) << 2 | ((av[3] >> 31) & 1) << 3;
|
||||
}
|
||||
|
||||
static inline pxl8_i32x4 pxl8_f32x4_to_i32x4(pxl8_f32x4 a) {
|
||||
return (pxl8_i32x4){{ (i32)a.v[0], (i32)a.v[1], (i32)a.v[2], (i32)a.v[3] }};
|
||||
}
|
||||
|
||||
static inline void pxl8_f32x4_store(pxl8_f32x4 a, f32* out) {
|
||||
out[0] = a.v[0]; out[1] = a.v[1]; out[2] = a.v[2]; out[3] = a.v[3];
|
||||
}
|
||||
|
||||
static inline pxl8_i32x4 pxl8_i32x4_splat(i32 x) {
|
||||
return (pxl8_i32x4){{ x, x, x, x }};
|
||||
}
|
||||
|
||||
static inline pxl8_i32x4 pxl8_i32x4_slli(pxl8_i32x4 a, i32 n) {
|
||||
return (pxl8_i32x4){{ a.v[0]<<n, a.v[1]<<n, a.v[2]<<n, a.v[3]<<n }};
|
||||
}
|
||||
|
||||
static inline pxl8_i32x4 pxl8_i32x4_srai(pxl8_i32x4 a, i32 n) {
|
||||
return (pxl8_i32x4){{ a.v[0]>>n, a.v[1]>>n, a.v[2]>>n, a.v[3]>>n }};
|
||||
}
|
||||
|
||||
static inline pxl8_i32x4 pxl8_i32x4_and(pxl8_i32x4 a, pxl8_i32x4 b) {
|
||||
return (pxl8_i32x4){{ a.v[0]&b.v[0], a.v[1]&b.v[1], a.v[2]&b.v[2], a.v[3]&b.v[3] }};
|
||||
}
|
||||
|
||||
static inline pxl8_i32x4 pxl8_i32x4_or(pxl8_i32x4 a, pxl8_i32x4 b) {
|
||||
return (pxl8_i32x4){{ a.v[0]|b.v[0], a.v[1]|b.v[1], a.v[2]|b.v[2], a.v[3]|b.v[3] }};
|
||||
}
|
||||
|
||||
static inline void pxl8_i32x4_store(pxl8_i32x4 a, i32* out) {
|
||||
out[0] = a.v[0]; out[1] = a.v[1]; out[2] = a.v[2]; out[3] = a.v[3];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
312
src/net/pxl8_net.c
Normal file
312
src/net/pxl8_net.c
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
#include "pxl8_net.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
typedef SOCKET socket_t;
|
||||
#define INVALID_SOCK INVALID_SOCKET
|
||||
#define close_socket closesocket
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
typedef int socket_t;
|
||||
#define INVALID_SOCK -1
|
||||
#define close_socket close
|
||||
#endif
|
||||
|
||||
#define PXL8_NET_DEFAULT_PORT 7777
|
||||
#define PXL8_NET_TICK_RATE 30.0f
|
||||
|
||||
struct pxl8_net {
|
||||
char address[256];
|
||||
bool connected;
|
||||
pxl8_entity_state entities[PXL8_MAX_SNAPSHOT_ENTITIES];
|
||||
pxl8_event_msg events[PXL8_MAX_SNAPSHOT_EVENTS];
|
||||
u64 highest_tick;
|
||||
pxl8_input_msg input_history[PXL8_NET_INPUT_HISTORY_SIZE];
|
||||
u64 input_head;
|
||||
u64 input_oldest_tick;
|
||||
f32 interp_time;
|
||||
pxl8_net_mode mode;
|
||||
u16 port;
|
||||
u8 predicted_state[PXL8_NET_USERDATA_SIZE];
|
||||
u64 predicted_tick;
|
||||
pxl8_entity_state prev_entities[PXL8_MAX_SNAPSHOT_ENTITIES];
|
||||
pxl8_snapshot_header prev_snapshot;
|
||||
u8 recv_buf[4096];
|
||||
u8 send_buf[4096];
|
||||
u32 sequence;
|
||||
struct sockaddr_in server_addr;
|
||||
pxl8_snapshot_header snapshot;
|
||||
socket_t sock;
|
||||
};
|
||||
|
||||
static const pxl8_entity_state* find_entity(const pxl8_entity_state* entities, u16 count, u64 id) {
|
||||
for (u16 i = 0; i < count; i++) {
|
||||
if (entities[i].entity_id == id) return &entities[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_net_connect(pxl8_net* net) {
|
||||
if (!net) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
if (net->connected) return PXL8_OK;
|
||||
|
||||
#ifdef _WIN32
|
||||
WSADATA wsa;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
#endif
|
||||
|
||||
net->sock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (net->sock == INVALID_SOCK) {
|
||||
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
u_long nonblocking = 1;
|
||||
ioctlsocket(net->sock, FIONBIO, &nonblocking);
|
||||
#else
|
||||
int flags = fcntl(net->sock, F_GETFL, 0);
|
||||
fcntl(net->sock, F_SETFL, flags | O_NONBLOCK);
|
||||
#endif
|
||||
|
||||
memset(&net->server_addr, 0, sizeof(net->server_addr));
|
||||
net->server_addr.sin_family = AF_INET;
|
||||
net->server_addr.sin_port = htons(net->port);
|
||||
inet_pton(AF_INET, net->address, &net->server_addr.sin_addr);
|
||||
|
||||
net->connected = true;
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
bool pxl8_net_connected(const pxl8_net* net) {
|
||||
return net && net->connected;
|
||||
}
|
||||
|
||||
pxl8_net* pxl8_net_create(const pxl8_net_config* config) {
|
||||
pxl8_net* net = calloc(1, sizeof(pxl8_net));
|
||||
if (!net) return NULL;
|
||||
|
||||
net->mode = config->mode;
|
||||
net->port = config->port ? config->port : PXL8_NET_DEFAULT_PORT;
|
||||
net->sock = INVALID_SOCK;
|
||||
net->connected = false;
|
||||
net->sequence = 0;
|
||||
net->highest_tick = 0;
|
||||
|
||||
if (config->address) {
|
||||
strncpy(net->address, config->address, sizeof(net->address) - 1);
|
||||
} else {
|
||||
strncpy(net->address, "127.0.0.1", sizeof(net->address) - 1);
|
||||
}
|
||||
|
||||
return net;
|
||||
}
|
||||
|
||||
void pxl8_net_destroy(pxl8_net* net) {
|
||||
if (!net) return;
|
||||
pxl8_net_disconnect(net);
|
||||
free(net);
|
||||
}
|
||||
|
||||
void pxl8_net_disconnect(pxl8_net* net) {
|
||||
if (!net) return;
|
||||
if (net->sock != INVALID_SOCK) {
|
||||
close_socket(net->sock);
|
||||
net->sock = INVALID_SOCK;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
WSACleanup();
|
||||
#endif
|
||||
net->connected = false;
|
||||
}
|
||||
|
||||
const pxl8_entity_state* pxl8_net_entities(const pxl8_net* net) {
|
||||
if (!net) return NULL;
|
||||
return net->entities;
|
||||
}
|
||||
|
||||
const u8* pxl8_net_entity_prev_userdata(const pxl8_net* net, u64 entity_id) {
|
||||
if (!net) return NULL;
|
||||
const pxl8_entity_state* e = find_entity(net->prev_entities, net->prev_snapshot.entity_count, entity_id);
|
||||
return e ? e->userdata : NULL;
|
||||
}
|
||||
|
||||
const u8* pxl8_net_entity_userdata(const pxl8_net* net, u64 entity_id) {
|
||||
if (!net) return NULL;
|
||||
const pxl8_entity_state* e = find_entity(net->entities, net->snapshot.entity_count, entity_id);
|
||||
return e ? e->userdata : NULL;
|
||||
}
|
||||
|
||||
const pxl8_event_msg* pxl8_net_events(const pxl8_net* net) {
|
||||
if (!net) return NULL;
|
||||
return net->events;
|
||||
}
|
||||
|
||||
const pxl8_input_msg* pxl8_net_input_at(const pxl8_net* net, u64 tick) {
|
||||
if (!net) return NULL;
|
||||
for (u64 i = 0; i < PXL8_NET_INPUT_HISTORY_SIZE; i++) {
|
||||
if (net->input_history[i].tick == tick) {
|
||||
return &net->input_history[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
u64 pxl8_net_input_oldest_tick(const pxl8_net* net) {
|
||||
if (!net) return 0;
|
||||
return net->input_oldest_tick;
|
||||
}
|
||||
|
||||
void pxl8_net_input_push(pxl8_net* net, const pxl8_input_msg* input) {
|
||||
if (!net || !input) return;
|
||||
u64 idx = net->input_head % PXL8_NET_INPUT_HISTORY_SIZE;
|
||||
net->input_history[idx] = *input;
|
||||
net->input_head++;
|
||||
if (net->input_oldest_tick == 0 || input->tick < net->input_oldest_tick) {
|
||||
net->input_oldest_tick = input->tick;
|
||||
}
|
||||
}
|
||||
|
||||
f32 pxl8_net_lerp_alpha(const pxl8_net* net) {
|
||||
if (!net) return 1.0f;
|
||||
f32 tick_duration = 1.0f / PXL8_NET_TICK_RATE;
|
||||
f32 alpha = net->interp_time / tick_duration;
|
||||
return alpha > 1.0f ? 1.0f : alpha;
|
||||
}
|
||||
|
||||
bool pxl8_net_needs_correction(const pxl8_net* net) {
|
||||
if (!net) return false;
|
||||
if (net->snapshot.tick == 0) return false;
|
||||
if (net->predicted_tick == 0) return false;
|
||||
if (net->snapshot.tick > net->predicted_tick) return true;
|
||||
const u8* server = pxl8_net_entity_userdata(net, net->snapshot.player_id);
|
||||
if (!server) return false;
|
||||
return memcmp(server, net->predicted_state, PXL8_NET_USERDATA_SIZE) != 0;
|
||||
}
|
||||
|
||||
u64 pxl8_net_player_id(const pxl8_net* net) {
|
||||
if (!net) return 0;
|
||||
return net->snapshot.player_id;
|
||||
}
|
||||
|
||||
bool pxl8_net_poll(pxl8_net* net) {
|
||||
if (!net || !net->connected) return false;
|
||||
|
||||
size_t len = pxl8_net_recv(net, net->recv_buf, sizeof(net->recv_buf));
|
||||
if (len < sizeof(pxl8_msg_header)) return false;
|
||||
|
||||
pxl8_msg_header hdr;
|
||||
size_t offset = pxl8_protocol_deserialize_header(net->recv_buf, len, &hdr);
|
||||
if (hdr.type != PXL8_MSG_SNAPSHOT) return false;
|
||||
|
||||
pxl8_snapshot_header snap;
|
||||
offset += pxl8_protocol_deserialize_snapshot_header(net->recv_buf + offset, len - offset, &snap);
|
||||
|
||||
if (snap.tick <= net->highest_tick) return false;
|
||||
|
||||
memcpy(net->prev_entities, net->entities, sizeof(net->entities));
|
||||
net->prev_snapshot = net->snapshot;
|
||||
|
||||
net->highest_tick = snap.tick;
|
||||
net->snapshot = snap;
|
||||
net->interp_time = 0.0f;
|
||||
|
||||
u16 count = snap.entity_count;
|
||||
if (count > PXL8_MAX_SNAPSHOT_ENTITIES) count = PXL8_MAX_SNAPSHOT_ENTITIES;
|
||||
|
||||
for (u16 i = 0; i < count; i++) {
|
||||
offset += pxl8_protocol_deserialize_entity_state(
|
||||
net->recv_buf + offset, len - offset, &net->entities[i]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
u8* pxl8_net_predicted_state(pxl8_net* net) {
|
||||
if (!net) return NULL;
|
||||
return net->predicted_state;
|
||||
}
|
||||
|
||||
void pxl8_net_predicted_tick_set(pxl8_net* net, u64 tick) {
|
||||
if (!net) return;
|
||||
net->predicted_tick = tick;
|
||||
}
|
||||
|
||||
size_t pxl8_net_recv(pxl8_net* net, u8* buf, size_t len) {
|
||||
if (!net || !net->connected) return 0;
|
||||
|
||||
struct sockaddr_in from;
|
||||
socklen_t from_len = sizeof(from);
|
||||
ssize_t received = recvfrom(net->sock, (char*)buf, len, 0,
|
||||
(struct sockaddr*)&from, &from_len);
|
||||
return (received > 0) ? (size_t)received : 0;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_net_send(pxl8_net* net, const u8* data, size_t len) {
|
||||
if (!net || !net->connected) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
ssize_t sent = sendto(net->sock, (const char*)data, len, 0,
|
||||
(struct sockaddr*)&net->server_addr,
|
||||
sizeof(net->server_addr));
|
||||
return (sent > 0) ? PXL8_OK : PXL8_ERROR_SYSTEM_FAILURE;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_net_send_command(pxl8_net* net, const pxl8_command_msg* cmd) {
|
||||
if (!net || !net->connected) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
u8 buf[sizeof(pxl8_msg_header) + sizeof(pxl8_command_msg)];
|
||||
pxl8_msg_header hdr = {
|
||||
.type = PXL8_MSG_COMMAND,
|
||||
.version = PXL8_PROTOCOL_VERSION,
|
||||
.size = sizeof(pxl8_command_msg),
|
||||
.sequence = 0
|
||||
};
|
||||
|
||||
size_t offset = pxl8_protocol_serialize_header(&hdr, buf, sizeof(buf));
|
||||
offset += pxl8_protocol_serialize_command(cmd, buf + offset, sizeof(buf) - offset);
|
||||
|
||||
return pxl8_net_send(net, buf, offset);
|
||||
}
|
||||
|
||||
pxl8_result pxl8_net_send_input(pxl8_net* net, const pxl8_input_msg* input) {
|
||||
if (!net || !net->connected) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
u8 buf[sizeof(pxl8_msg_header) + sizeof(pxl8_input_msg)];
|
||||
pxl8_msg_header hdr = {
|
||||
.type = PXL8_MSG_INPUT,
|
||||
.version = PXL8_PROTOCOL_VERSION,
|
||||
.size = sizeof(pxl8_input_msg),
|
||||
.sequence = 0
|
||||
};
|
||||
|
||||
size_t offset = pxl8_protocol_serialize_header(&hdr, buf, sizeof(buf));
|
||||
offset += pxl8_protocol_serialize_input(input, buf + offset, sizeof(buf) - offset);
|
||||
|
||||
return pxl8_net_send(net, buf, offset);
|
||||
}
|
||||
|
||||
const pxl8_snapshot_header* pxl8_net_snapshot(const pxl8_net* net) {
|
||||
if (!net) return NULL;
|
||||
return &net->snapshot;
|
||||
}
|
||||
|
||||
u64 pxl8_net_tick(const pxl8_net* net) {
|
||||
if (!net) return 0;
|
||||
return net->snapshot.tick;
|
||||
}
|
||||
|
||||
void pxl8_net_update(pxl8_net* net, f32 dt) {
|
||||
if (!net) return;
|
||||
net->interp_time += dt;
|
||||
}
|
||||
54
src/net/pxl8_net.h
Normal file
54
src/net/pxl8_net.h
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_protocol.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define PXL8_NET_INPUT_HISTORY_SIZE 64
|
||||
#define PXL8_NET_USERDATA_SIZE 56
|
||||
|
||||
typedef struct pxl8_net pxl8_net;
|
||||
|
||||
typedef enum pxl8_net_mode {
|
||||
PXL8_NET_LOCAL = 0,
|
||||
PXL8_NET_REMOTE
|
||||
} pxl8_net_mode;
|
||||
|
||||
typedef struct pxl8_net_config {
|
||||
const char* address;
|
||||
pxl8_net_mode mode;
|
||||
u16 port;
|
||||
} pxl8_net_config;
|
||||
|
||||
pxl8_result pxl8_net_connect(pxl8_net* net);
|
||||
bool pxl8_net_connected(const pxl8_net* net);
|
||||
pxl8_net* pxl8_net_create(const pxl8_net_config* config);
|
||||
void pxl8_net_destroy(pxl8_net* net);
|
||||
void pxl8_net_disconnect(pxl8_net* net);
|
||||
const pxl8_entity_state* pxl8_net_entities(const pxl8_net* net);
|
||||
const u8* pxl8_net_entity_prev_userdata(const pxl8_net* net, u64 entity_id);
|
||||
const u8* pxl8_net_entity_userdata(const pxl8_net* net, u64 entity_id);
|
||||
const pxl8_event_msg* pxl8_net_events(const pxl8_net* net);
|
||||
const pxl8_input_msg* pxl8_net_input_at(const pxl8_net* net, u64 tick);
|
||||
u64 pxl8_net_input_oldest_tick(const pxl8_net* net);
|
||||
void pxl8_net_input_push(pxl8_net* net, const pxl8_input_msg* input);
|
||||
f32 pxl8_net_lerp_alpha(const pxl8_net* net);
|
||||
bool pxl8_net_needs_correction(const pxl8_net* net);
|
||||
u64 pxl8_net_player_id(const pxl8_net* net);
|
||||
bool pxl8_net_poll(pxl8_net* net);
|
||||
u8* pxl8_net_predicted_state(pxl8_net* net);
|
||||
void pxl8_net_predicted_tick_set(pxl8_net* net, u64 tick);
|
||||
size_t pxl8_net_recv(pxl8_net* net, u8* buf, size_t len);
|
||||
pxl8_result pxl8_net_send(pxl8_net* net, const u8* data, size_t len);
|
||||
pxl8_result pxl8_net_send_command(pxl8_net* net, const pxl8_command_msg* cmd);
|
||||
pxl8_result pxl8_net_send_input(pxl8_net* net, const pxl8_input_msg* input);
|
||||
const pxl8_snapshot_header* pxl8_net_snapshot(const pxl8_net* net);
|
||||
u64 pxl8_net_tick(const pxl8_net* net);
|
||||
void pxl8_net_update(pxl8_net* net, f32 dt);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
124
src/net/pxl8_protocol.c
Normal file
124
src/net/pxl8_protocol.c
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
#include "pxl8_protocol.h"
|
||||
#include "pxl8_bytes.h"
|
||||
|
||||
size_t pxl8_protocol_serialize_header(const pxl8_msg_header* msg, u8* buf, size_t len) {
|
||||
if (len < sizeof(pxl8_msg_header)) return 0;
|
||||
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
|
||||
pxl8_write_u32_be(&s, msg->sequence);
|
||||
pxl8_write_u16_be(&s, msg->size);
|
||||
pxl8_write_u8(&s, msg->type);
|
||||
pxl8_write_u8(&s, msg->version);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
size_t pxl8_protocol_deserialize_header(const u8* buf, size_t len, pxl8_msg_header* msg) {
|
||||
if (len < sizeof(pxl8_msg_header)) return 0;
|
||||
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
|
||||
msg->sequence = pxl8_read_u32_be(&s);
|
||||
msg->size = pxl8_read_u16_be(&s);
|
||||
msg->type = pxl8_read_u8(&s);
|
||||
msg->version = pxl8_read_u8(&s);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
size_t pxl8_protocol_serialize_input(const pxl8_input_msg* msg, u8* buf, size_t len) {
|
||||
if (len < sizeof(pxl8_input_msg)) return 0;
|
||||
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
|
||||
pxl8_write_u32_be(&s, msg->buttons);
|
||||
pxl8_write_f32_be(&s, msg->look_dx);
|
||||
pxl8_write_f32_be(&s, msg->look_dy);
|
||||
pxl8_write_f32_be(&s, msg->move_x);
|
||||
pxl8_write_f32_be(&s, msg->move_y);
|
||||
pxl8_write_f32_be(&s, msg->yaw);
|
||||
pxl8_write_u64_be(&s, msg->tick);
|
||||
pxl8_write_u64_be(&s, msg->timestamp);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
size_t pxl8_protocol_deserialize_input(const u8* buf, size_t len, pxl8_input_msg* msg) {
|
||||
if (len < sizeof(pxl8_input_msg)) return 0;
|
||||
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
|
||||
msg->buttons = pxl8_read_u32_be(&s);
|
||||
msg->look_dx = pxl8_read_f32_be(&s);
|
||||
msg->look_dy = pxl8_read_f32_be(&s);
|
||||
msg->move_x = pxl8_read_f32_be(&s);
|
||||
msg->move_y = pxl8_read_f32_be(&s);
|
||||
msg->yaw = pxl8_read_f32_be(&s);
|
||||
msg->tick = pxl8_read_u64_be(&s);
|
||||
msg->timestamp = pxl8_read_u64_be(&s);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
size_t pxl8_protocol_serialize_command(const pxl8_command_msg* msg, u8* buf, size_t len) {
|
||||
if (len < sizeof(pxl8_command_msg)) return 0;
|
||||
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
|
||||
pxl8_write_u16_be(&s, msg->cmd_type);
|
||||
pxl8_write_bytes(&s, msg->payload, PXL8_COMMAND_PAYLOAD_SIZE);
|
||||
pxl8_write_u16_be(&s, msg->payload_size);
|
||||
pxl8_write_u64_be(&s, msg->tick);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
size_t pxl8_protocol_deserialize_command(const u8* buf, size_t len, pxl8_command_msg* msg) {
|
||||
if (len < sizeof(pxl8_command_msg)) return 0;
|
||||
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
|
||||
msg->cmd_type = pxl8_read_u16_be(&s);
|
||||
pxl8_read_bytes(&s, msg->payload, PXL8_COMMAND_PAYLOAD_SIZE);
|
||||
msg->payload_size = pxl8_read_u16_be(&s);
|
||||
msg->tick = pxl8_read_u64_be(&s);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
size_t pxl8_protocol_serialize_entity_state(const pxl8_entity_state* state, u8* buf, size_t len) {
|
||||
if (len < sizeof(pxl8_entity_state)) return 0;
|
||||
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
|
||||
pxl8_write_u64_be(&s, state->entity_id);
|
||||
pxl8_write_bytes(&s, state->userdata, 56);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
size_t pxl8_protocol_deserialize_entity_state(const u8* buf, size_t len, pxl8_entity_state* state) {
|
||||
if (len < sizeof(pxl8_entity_state)) return 0;
|
||||
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
|
||||
state->entity_id = pxl8_read_u64_be(&s);
|
||||
pxl8_read_bytes(&s, state->userdata, 56);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
size_t pxl8_protocol_serialize_event(const pxl8_event_msg* msg, u8* buf, size_t len) {
|
||||
if (len < sizeof(pxl8_event_msg)) return 0;
|
||||
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
|
||||
pxl8_write_u8(&s, msg->event_type);
|
||||
pxl8_write_bytes(&s, msg->payload, PXL8_EVENT_PAYLOAD_SIZE);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
size_t pxl8_protocol_deserialize_event(const u8* buf, size_t len, pxl8_event_msg* msg) {
|
||||
if (len < sizeof(pxl8_event_msg)) return 0;
|
||||
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
|
||||
msg->event_type = pxl8_read_u8(&s);
|
||||
pxl8_read_bytes(&s, msg->payload, PXL8_EVENT_PAYLOAD_SIZE);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
size_t pxl8_protocol_serialize_snapshot_header(const pxl8_snapshot_header* hdr, u8* buf, size_t len) {
|
||||
if (len < sizeof(pxl8_snapshot_header)) return 0;
|
||||
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
|
||||
pxl8_write_u16_be(&s, hdr->entity_count);
|
||||
pxl8_write_u16_be(&s, hdr->event_count);
|
||||
pxl8_write_u64_be(&s, hdr->player_id);
|
||||
pxl8_write_u64_be(&s, hdr->tick);
|
||||
pxl8_write_f32_be(&s, hdr->time);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
size_t pxl8_protocol_deserialize_snapshot_header(const u8* buf, size_t len, pxl8_snapshot_header* hdr) {
|
||||
if (len < sizeof(pxl8_snapshot_header)) return 0;
|
||||
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
|
||||
hdr->entity_count = pxl8_read_u16_be(&s);
|
||||
hdr->event_count = pxl8_read_u16_be(&s);
|
||||
hdr->player_id = pxl8_read_u64_be(&s);
|
||||
hdr->tick = pxl8_read_u64_be(&s);
|
||||
hdr->time = pxl8_read_f32_be(&s);
|
||||
return s.offset;
|
||||
}
|
||||
93
src/net/pxl8_protocol.h
Normal file
93
src/net/pxl8_protocol.h
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define PXL8_PROTOCOL_VERSION 1
|
||||
#define PXL8_MAX_SNAPSHOT_ENTITIES 256
|
||||
#define PXL8_MAX_SNAPSHOT_EVENTS 32
|
||||
#define PXL8_COMMAND_PAYLOAD_SIZE 64
|
||||
#define PXL8_EVENT_PAYLOAD_SIZE 15
|
||||
|
||||
typedef enum pxl8_msg_type {
|
||||
PXL8_MSG_NONE = 0,
|
||||
PXL8_MSG_CONNECT,
|
||||
PXL8_MSG_DISCONNECT,
|
||||
PXL8_MSG_INPUT,
|
||||
PXL8_MSG_COMMAND,
|
||||
PXL8_MSG_SNAPSHOT,
|
||||
PXL8_MSG_EVENT
|
||||
} pxl8_msg_type;
|
||||
|
||||
typedef struct pxl8_msg_header {
|
||||
u32 sequence;
|
||||
u16 size;
|
||||
u8 type;
|
||||
u8 version;
|
||||
} pxl8_msg_header;
|
||||
|
||||
typedef enum pxl8_cmd_type {
|
||||
PXL8_CMD_NONE = 0,
|
||||
PXL8_CMD_SPAWN_ENTITY,
|
||||
} pxl8_cmd_type;
|
||||
|
||||
typedef struct pxl8_input_msg {
|
||||
u32 buttons;
|
||||
f32 look_dx;
|
||||
f32 look_dy;
|
||||
f32 move_x;
|
||||
f32 move_y;
|
||||
f32 yaw;
|
||||
u64 tick;
|
||||
u64 timestamp;
|
||||
} pxl8_input_msg;
|
||||
|
||||
typedef struct pxl8_command_msg {
|
||||
u16 cmd_type;
|
||||
u8 payload[PXL8_COMMAND_PAYLOAD_SIZE];
|
||||
u16 payload_size;
|
||||
u64 tick;
|
||||
} pxl8_command_msg;
|
||||
|
||||
typedef struct pxl8_entity_state {
|
||||
u64 entity_id;
|
||||
u8 userdata[56];
|
||||
} pxl8_entity_state;
|
||||
|
||||
typedef struct pxl8_event_msg {
|
||||
u8 event_type;
|
||||
u8 payload[PXL8_EVENT_PAYLOAD_SIZE];
|
||||
} pxl8_event_msg;
|
||||
|
||||
typedef struct pxl8_snapshot_header {
|
||||
u16 entity_count;
|
||||
u16 event_count;
|
||||
u64 player_id;
|
||||
u64 tick;
|
||||
f32 time;
|
||||
} pxl8_snapshot_header;
|
||||
|
||||
size_t pxl8_protocol_serialize_header(const pxl8_msg_header* msg, u8* buf, size_t len);
|
||||
size_t pxl8_protocol_deserialize_header(const u8* buf, size_t len, pxl8_msg_header* msg);
|
||||
|
||||
size_t pxl8_protocol_serialize_input(const pxl8_input_msg* msg, u8* buf, size_t len);
|
||||
size_t pxl8_protocol_deserialize_input(const u8* buf, size_t len, pxl8_input_msg* msg);
|
||||
|
||||
size_t pxl8_protocol_serialize_command(const pxl8_command_msg* msg, u8* buf, size_t len);
|
||||
size_t pxl8_protocol_deserialize_command(const u8* buf, size_t len, pxl8_command_msg* msg);
|
||||
|
||||
size_t pxl8_protocol_serialize_entity_state(const pxl8_entity_state* state, u8* buf, size_t len);
|
||||
size_t pxl8_protocol_deserialize_entity_state(const u8* buf, size_t len, pxl8_entity_state* state);
|
||||
|
||||
size_t pxl8_protocol_serialize_event(const pxl8_event_msg* msg, u8* buf, size_t len);
|
||||
size_t pxl8_protocol_deserialize_event(const u8* buf, size_t len, pxl8_event_msg* msg);
|
||||
|
||||
size_t pxl8_protocol_serialize_snapshot_header(const pxl8_snapshot_header* hdr, u8* buf, size_t len);
|
||||
size_t pxl8_protocol_deserialize_snapshot_header(const u8* buf, size_t len, pxl8_snapshot_header* hdr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
316
src/script/pxl8_repl.c
Normal file
316
src/script/pxl8_repl.c
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
#include "pxl8_repl.h"
|
||||
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <linenoise.h>
|
||||
|
||||
#define PXL8_MAX_REPL_COMMAND_SIZE 4096
|
||||
#define PXL8_REPL_QUEUE_SIZE 8
|
||||
|
||||
struct pxl8_repl_command {
|
||||
char buffer[PXL8_MAX_REPL_COMMAND_SIZE];
|
||||
};
|
||||
|
||||
struct pxl8_repl {
|
||||
char commands[PXL8_REPL_QUEUE_SIZE][PXL8_MAX_REPL_COMMAND_SIZE];
|
||||
atomic_uint cmd_write_idx;
|
||||
atomic_uint cmd_read_idx;
|
||||
atomic_bool cmd_complete;
|
||||
|
||||
char logs[PXL8_REPL_QUEUE_SIZE][PXL8_MAX_REPL_COMMAND_SIZE];
|
||||
atomic_uint log_write_idx;
|
||||
atomic_uint log_read_idx;
|
||||
|
||||
atomic_bool should_quit;
|
||||
pthread_t thread;
|
||||
char accumulator[PXL8_MAX_REPL_COMMAND_SIZE];
|
||||
pxl8_repl_command command;
|
||||
};
|
||||
|
||||
static pxl8_repl* g_repl = NULL;
|
||||
|
||||
static void pxl8_repl_completion(const char* buf, linenoiseCompletions* lc) {
|
||||
const char* fennel_keywords[] = {
|
||||
"fn", "let", "var", "set", "global", "local",
|
||||
"if", "when", "do", "while", "for", "each",
|
||||
"lambda", "λ", "partial", "macro", "macros",
|
||||
"require", "include", "import-macros",
|
||||
"values", "select", "table", "length",
|
||||
".", "..", ":", "->", "->>", "-?>", "-?>>",
|
||||
"doto", "match", "case", "pick-values",
|
||||
"collect", "icollect", "accumulate"
|
||||
};
|
||||
|
||||
const char* pxl8_functions[] = {
|
||||
"pxl8.clr", "pxl8.pixel", "pxl8.get_pixel",
|
||||
"pxl8.line", "pxl8.rect", "pxl8.rect_fill",
|
||||
"pxl8.circle", "pxl8.circle_fill", "pxl8.text",
|
||||
"pxl8.get_screen", "pxl8.info", "pxl8.warn",
|
||||
"pxl8.error", "pxl8.debug", "pxl8.trace"
|
||||
};
|
||||
|
||||
size_t buf_len = strlen(buf);
|
||||
|
||||
for (size_t i = 0; i < sizeof(fennel_keywords) / sizeof(fennel_keywords[0]); i++) {
|
||||
if (strncmp(buf, fennel_keywords[i], buf_len) == 0) {
|
||||
linenoiseAddCompletion(lc, fennel_keywords[i]);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < sizeof(pxl8_functions) / sizeof(pxl8_functions[0]); i++) {
|
||||
if (strncmp(buf, pxl8_functions[i], buf_len) == 0) {
|
||||
linenoiseAddCompletion(lc, pxl8_functions[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static char* pxl8_repl_hints(const char* buf, int* color, int* bold) {
|
||||
if (strncmp(buf, "pxl8.", 5) == 0 && strlen(buf) == 5) {
|
||||
*color = 35;
|
||||
*bold = 0;
|
||||
return "clr|pixel|line|rect|circle|text|get_screen";
|
||||
}
|
||||
|
||||
if (strcmp(buf, "(fn") == 0) {
|
||||
*color = 36;
|
||||
*bold = 0;
|
||||
return " [args] body)";
|
||||
}
|
||||
|
||||
if (strcmp(buf, "(let") == 0) {
|
||||
*color = 36;
|
||||
*bold = 0;
|
||||
return " [bindings] body)";
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void pxl8_repl_flush_logs(pxl8_repl* repl) {
|
||||
u32 log_read_idx = atomic_load(&repl->log_read_idx);
|
||||
u32 log_write_idx = atomic_load(&repl->log_write_idx);
|
||||
while (log_read_idx != log_write_idx) {
|
||||
printf("%s", repl->logs[log_read_idx]);
|
||||
atomic_store(&repl->log_read_idx, (log_read_idx + 1) % PXL8_REPL_QUEUE_SIZE);
|
||||
log_read_idx = atomic_load(&repl->log_read_idx);
|
||||
log_write_idx = atomic_load(&repl->log_write_idx);
|
||||
}
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
static void* pxl8_repl_thread(void* arg) {
|
||||
pxl8_repl* repl = (pxl8_repl*)arg;
|
||||
|
||||
printf("[pxl8 REPL] Fennel 1.6.0 - Tab for completion, Ctrl-D to exit\n");
|
||||
fflush(stdout);
|
||||
|
||||
struct linenoiseState ls;
|
||||
char input_buf[PXL8_MAX_REPL_COMMAND_SIZE];
|
||||
bool editing = false;
|
||||
|
||||
struct pollfd pfd = {
|
||||
.fd = STDIN_FILENO,
|
||||
.events = POLLIN
|
||||
};
|
||||
|
||||
while (!atomic_load(&repl->should_quit)) {
|
||||
u32 log_read_idx = atomic_load(&repl->log_read_idx);
|
||||
u32 log_write_idx = atomic_load(&repl->log_write_idx);
|
||||
|
||||
if (log_read_idx != log_write_idx) {
|
||||
printf("\r\033[K");
|
||||
if (editing) {
|
||||
linenoiseEditStop(&ls);
|
||||
editing = false;
|
||||
printf("\033[A\r\033[K");
|
||||
}
|
||||
while (log_read_idx != log_write_idx) {
|
||||
printf("%s", repl->logs[log_read_idx]);
|
||||
atomic_store(&repl->log_read_idx, (log_read_idx + 1) % PXL8_REPL_QUEUE_SIZE);
|
||||
log_read_idx = atomic_load(&repl->log_read_idx);
|
||||
log_write_idx = atomic_load(&repl->log_write_idx);
|
||||
}
|
||||
fflush(stdout);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!editing && !atomic_load(&repl->should_quit)) {
|
||||
const char* prompt = (repl->accumulator[0] != '\0') ? ".. " : ">> ";
|
||||
if (linenoiseEditStart(&ls, STDIN_FILENO, STDOUT_FILENO, input_buf, sizeof(input_buf), prompt) == -1) {
|
||||
atomic_store(&repl->should_quit, true);
|
||||
break;
|
||||
}
|
||||
editing = true;
|
||||
}
|
||||
|
||||
if (poll(&pfd, 1, 1) <= 0) continue;
|
||||
|
||||
char* line = linenoiseEditFeed(&ls);
|
||||
|
||||
if (line == NULL) {
|
||||
atomic_store(&repl->should_quit, true);
|
||||
break;
|
||||
}
|
||||
|
||||
if (line == linenoiseEditMore) continue;
|
||||
|
||||
linenoiseEditStop(&ls);
|
||||
editing = false;
|
||||
|
||||
bool in_multiline = (repl->accumulator[0] != '\0');
|
||||
|
||||
if (strlen(line) > 0 || in_multiline) {
|
||||
if (!in_multiline) {
|
||||
linenoiseHistoryAdd(line);
|
||||
linenoiseHistorySave(".pxl8_history");
|
||||
}
|
||||
|
||||
if (repl->accumulator[0] != '\0') {
|
||||
strncat(repl->accumulator, "\n",
|
||||
PXL8_MAX_REPL_COMMAND_SIZE - strlen(repl->accumulator) - 1);
|
||||
}
|
||||
strncat(repl->accumulator, line,
|
||||
PXL8_MAX_REPL_COMMAND_SIZE - strlen(repl->accumulator) - 1);
|
||||
|
||||
u32 write_idx = atomic_load(&repl->cmd_write_idx);
|
||||
u32 read_idx = atomic_load(&repl->cmd_read_idx);
|
||||
u32 next_write = (write_idx + 1) % PXL8_REPL_QUEUE_SIZE;
|
||||
|
||||
if (next_write != read_idx) {
|
||||
strncpy(repl->commands[write_idx], repl->accumulator, PXL8_MAX_REPL_COMMAND_SIZE - 1);
|
||||
repl->commands[write_idx][PXL8_MAX_REPL_COMMAND_SIZE - 1] = '\0';
|
||||
atomic_store(&repl->cmd_write_idx, next_write);
|
||||
}
|
||||
}
|
||||
|
||||
linenoiseFree(line);
|
||||
|
||||
while (!atomic_load(&repl->should_quit) &&
|
||||
(atomic_load(&repl->cmd_write_idx) != atomic_load(&repl->cmd_read_idx) ||
|
||||
!atomic_load(&repl->cmd_complete))) {
|
||||
u32 lr = atomic_load(&repl->log_read_idx);
|
||||
u32 lw = atomic_load(&repl->log_write_idx);
|
||||
while (lr != lw) {
|
||||
printf("%s", repl->logs[lr]);
|
||||
atomic_store(&repl->log_read_idx, (lr + 1) % PXL8_REPL_QUEUE_SIZE);
|
||||
lr = atomic_load(&repl->log_read_idx);
|
||||
lw = atomic_load(&repl->log_write_idx);
|
||||
}
|
||||
fflush(stdout);
|
||||
struct timespec ts = {.tv_sec = 0, .tv_nsec = 1000000};
|
||||
nanosleep(&ts, NULL);
|
||||
}
|
||||
atomic_store(&repl->cmd_complete, false);
|
||||
}
|
||||
|
||||
if (editing) linenoiseEditStop(&ls);
|
||||
|
||||
pxl8_repl_flush_logs(repl);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pxl8_repl* pxl8_repl_create(void) {
|
||||
pxl8_repl* repl = (pxl8_repl*)calloc(1, sizeof(pxl8_repl));
|
||||
if (!repl) return NULL;
|
||||
|
||||
repl->accumulator[0] = '\0';
|
||||
atomic_store(&repl->cmd_write_idx, 0);
|
||||
atomic_store(&repl->cmd_read_idx, 0);
|
||||
atomic_store(&repl->cmd_complete, true);
|
||||
atomic_store(&repl->log_write_idx, 0);
|
||||
atomic_store(&repl->log_read_idx, 0);
|
||||
atomic_store(&repl->should_quit, false);
|
||||
|
||||
linenoiseHistorySetMaxLen(100);
|
||||
linenoiseSetMultiLine(1);
|
||||
linenoiseSetCompletionCallback(pxl8_repl_completion);
|
||||
linenoiseSetHintsCallback(pxl8_repl_hints);
|
||||
linenoiseHistoryLoad(".pxl8_history");
|
||||
|
||||
g_repl = repl;
|
||||
|
||||
if (pthread_create(&repl->thread, NULL, pxl8_repl_thread, repl) != 0) {
|
||||
free(repl);
|
||||
g_repl = NULL;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return repl;
|
||||
}
|
||||
|
||||
void pxl8_repl_destroy(pxl8_repl* repl) {
|
||||
if (!repl) return;
|
||||
|
||||
atomic_store(&repl->should_quit, true);
|
||||
|
||||
struct timespec ts = {.tv_sec = 0, .tv_nsec = 2000000};
|
||||
nanosleep(&ts, NULL);
|
||||
|
||||
printf("\r\033[K");
|
||||
fflush(stdout);
|
||||
|
||||
pthread_join(repl->thread, NULL);
|
||||
pxl8_repl_flush_logs(repl);
|
||||
|
||||
g_repl = NULL;
|
||||
|
||||
system("stty sane 2>/dev/null");
|
||||
free(repl);
|
||||
}
|
||||
|
||||
pxl8_repl_command* pxl8_repl_pop_command(pxl8_repl* repl) {
|
||||
if (!repl) return NULL;
|
||||
|
||||
u32 read_idx = atomic_load(&repl->cmd_read_idx);
|
||||
u32 write_idx = atomic_load(&repl->cmd_write_idx);
|
||||
|
||||
if (read_idx == write_idx) return NULL;
|
||||
|
||||
strncpy(repl->command.buffer, repl->commands[read_idx], PXL8_MAX_REPL_COMMAND_SIZE - 1);
|
||||
repl->command.buffer[PXL8_MAX_REPL_COMMAND_SIZE - 1] = '\0';
|
||||
|
||||
atomic_store(&repl->cmd_read_idx, (read_idx + 1) % PXL8_REPL_QUEUE_SIZE);
|
||||
|
||||
return &repl->command;
|
||||
}
|
||||
|
||||
const char* pxl8_repl_command_buffer(pxl8_repl_command* cmd) {
|
||||
return cmd ? cmd->buffer : NULL;
|
||||
}
|
||||
|
||||
bool pxl8_repl_should_quit(pxl8_repl* repl) {
|
||||
return repl ? atomic_load(&repl->should_quit) : false;
|
||||
}
|
||||
|
||||
bool pxl8_repl_push_log(const char* message) {
|
||||
if (!g_repl || !message) return false;
|
||||
|
||||
u32 write_idx = atomic_load(&g_repl->log_write_idx);
|
||||
u32 read_idx = atomic_load(&g_repl->log_read_idx);
|
||||
u32 next_write = (write_idx + 1) % PXL8_REPL_QUEUE_SIZE;
|
||||
|
||||
if (next_write != read_idx) {
|
||||
strncpy(g_repl->logs[write_idx], message, PXL8_MAX_REPL_COMMAND_SIZE - 1);
|
||||
g_repl->logs[write_idx][PXL8_MAX_REPL_COMMAND_SIZE - 1] = '\0';
|
||||
atomic_store(&g_repl->log_write_idx, next_write);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void pxl8_repl_clear_accumulator(pxl8_repl* repl) {
|
||||
if (!repl) return;
|
||||
repl->accumulator[0] = '\0';
|
||||
}
|
||||
|
||||
void pxl8_repl_signal_complete(pxl8_repl* repl) {
|
||||
if (!repl) return;
|
||||
atomic_store(&repl->cmd_complete, true);
|
||||
}
|
||||
24
src/script/pxl8_repl.h
Normal file
24
src/script/pxl8_repl.h
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_repl pxl8_repl;
|
||||
typedef struct pxl8_repl_command pxl8_repl_command;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_repl* pxl8_repl_create(void);
|
||||
void pxl8_repl_destroy(pxl8_repl* repl);
|
||||
|
||||
pxl8_repl_command* pxl8_repl_pop_command(pxl8_repl* repl);
|
||||
const char* pxl8_repl_command_buffer(pxl8_repl_command* cmd);
|
||||
bool pxl8_repl_push_log(const char* message);
|
||||
void pxl8_repl_clear_accumulator(pxl8_repl* repl);
|
||||
bool pxl8_repl_should_quit(pxl8_repl* repl);
|
||||
void pxl8_repl_signal_complete(pxl8_repl* repl);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
1285
src/script/pxl8_script.c
Normal file
1285
src/script/pxl8_script.c
Normal file
File diff suppressed because it is too large
Load diff
44
src/script/pxl8_script.h
Normal file
44
src/script/pxl8_script.h
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_cart.h"
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_sfx.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_script pxl8_script;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_script* pxl8_script_create(bool repl_mode);
|
||||
void pxl8_script_destroy(pxl8_script* script);
|
||||
|
||||
const char* pxl8_script_get_last_error(pxl8_script* script);
|
||||
bool pxl8_script_is_incomplete_input(pxl8_script* script);
|
||||
|
||||
void pxl8_script_set_cart_path(pxl8_script* script, const char* cart_path, const char* original_cwd);
|
||||
void pxl8_script_set_gfx(pxl8_script* script, pxl8_gfx* gfx);
|
||||
void pxl8_script_set_input(pxl8_script* script, pxl8_input_state* input);
|
||||
void pxl8_script_set_rng(pxl8_script* script, void* rng);
|
||||
void pxl8_script_set_sfx(pxl8_script* script, pxl8_sfx_mixer* mixer);
|
||||
void pxl8_script_set_sys(pxl8_script* script, void* sys);
|
||||
|
||||
pxl8_result pxl8_script_call_function(pxl8_script* script, const char* name);
|
||||
pxl8_result pxl8_script_call_function_f32(pxl8_script* script, const char* name, f32 arg);
|
||||
bool pxl8_script_check_reload(pxl8_script* script);
|
||||
pxl8_result pxl8_script_eval(pxl8_script* script, const char* code);
|
||||
pxl8_result pxl8_script_eval_repl(pxl8_script* script, const char* code);
|
||||
pxl8_result pxl8_script_load_cart_manifest(pxl8_script* script, pxl8_cart* cart);
|
||||
pxl8_result pxl8_script_load_main(pxl8_script* script, const char* path);
|
||||
pxl8_result pxl8_script_load_module(pxl8_script* script, const char* module_name);
|
||||
pxl8_result pxl8_script_run_fennel_file(pxl8_script* script, const char* filename);
|
||||
pxl8_result pxl8_script_run_file(pxl8_script* script, const char* filename);
|
||||
|
||||
u32 pxl8_script_serialize_globals(pxl8_script* script, u8** out_data);
|
||||
void pxl8_script_deserialize_globals(pxl8_script* script, const u8* data, u32 size);
|
||||
void pxl8_script_free_serialized(u8* data);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
525
src/script/pxl8_script_ffi.h
Normal file
525
src/script/pxl8_script_ffi.h
Normal file
|
|
@ -0,0 +1,525 @@
|
|||
#pragma once
|
||||
|
||||
static const char* pxl8_ffi_cdefs =
|
||||
"typedef uint8_t u8;\n"
|
||||
"typedef uint16_t u16;\n"
|
||||
"typedef uint32_t u32;\n"
|
||||
"typedef uint64_t u64;\n"
|
||||
"typedef int8_t i8;\n"
|
||||
"typedef int16_t i16;\n"
|
||||
"typedef int32_t i32;\n"
|
||||
"typedef int64_t i64;\n"
|
||||
"typedef float f32;\n"
|
||||
"typedef double f64;\n"
|
||||
"typedef struct pxl8 pxl8;\n"
|
||||
"typedef struct pxl8_gfx pxl8_gfx;\n"
|
||||
"typedef struct { int x, y, w, h; } pxl8_bounds;\n"
|
||||
"typedef struct { int x, y; } pxl8_point;\n"
|
||||
"typedef struct pxl8_rng { u32 state; } pxl8_rng;\n"
|
||||
"\n"
|
||||
"void pxl8_rng_seed(pxl8_rng* rng, u32 seed);\n"
|
||||
"u32 pxl8_rng_next(pxl8_rng* rng);\n"
|
||||
"f32 pxl8_rng_f32(pxl8_rng* rng);\n"
|
||||
"i32 pxl8_rng_range(pxl8_rng* rng, i32 min, i32 max);\n"
|
||||
"\n"
|
||||
"f32 pxl8_get_fps(const pxl8* sys);\n"
|
||||
"\n"
|
||||
"u8 pxl8_gfx_find_color(pxl8_gfx* gfx, u32 color);\n"
|
||||
"i32 pxl8_gfx_get_height(pxl8_gfx* ctx);\n"
|
||||
"typedef struct pxl8_palette pxl8_palette;\n"
|
||||
"pxl8_palette* pxl8_gfx_get_palette(pxl8_gfx* gfx);\n"
|
||||
"u32 pxl8_palette_color(const pxl8_palette* pal, u8 idx);\n"
|
||||
"void pxl8_palette_set_rgb(pxl8_palette* pal, u8 idx, u8 r, u8 g, u8 b);\n"
|
||||
"i32 pxl8_palette_index(const pxl8_palette* pal, u32 color);\n"
|
||||
"u8 pxl8_palette_ramp_index(const pxl8_palette* pal, u8 position);\n"
|
||||
"i32 pxl8_gfx_get_width(pxl8_gfx* ctx);\n"
|
||||
"void pxl8_2d_circle(pxl8_gfx* ctx, i32 x, i32 y, i32 r, u32 color);\n"
|
||||
"void pxl8_2d_circle_fill(pxl8_gfx* ctx, i32 x, i32 y, i32 r, u32 color);\n"
|
||||
"void pxl8_2d_clear(pxl8_gfx* ctx, u32 color);\n"
|
||||
"u32 pxl8_2d_get_pixel(pxl8_gfx* ctx, i32 x, i32 y);\n"
|
||||
"void pxl8_2d_line(pxl8_gfx* ctx, i32 x0, i32 y0, i32 x1, i32 y1, u32 color);\n"
|
||||
"void pxl8_2d_pixel(pxl8_gfx* ctx, i32 x, i32 y, u32 color);\n"
|
||||
"void pxl8_2d_rect(pxl8_gfx* ctx, i32 x, i32 y, i32 w, i32 h, u32 color);\n"
|
||||
"void pxl8_2d_rect_fill(pxl8_gfx* ctx, i32 x, i32 y, i32 w, i32 h, u32 color);\n"
|
||||
"void pxl8_2d_sprite(pxl8_gfx* ctx, u32 id, i32 x, i32 y, i32 w, i32 h, bool flip_x, bool flip_y);\n"
|
||||
"void pxl8_2d_text(pxl8_gfx* ctx, const char* str, i32 x, i32 y, u32 color);\n"
|
||||
"void pxl8_gfx_color_ramp(pxl8_gfx* ctx, u8 start, u8 count, u32 from_color, u32 to_color);\n"
|
||||
"void pxl8_gfx_cycle_palette(pxl8_gfx* ctx, u8 start, u8 count, i32 step);\n"
|
||||
"void pxl8_gfx_fade_palette(pxl8_gfx* ctx, u8 start, u8 count, f32 amount, u32 target_color);\n"
|
||||
"i32 pxl8_gfx_add_palette_cycle(pxl8_gfx* ctx, u8 start_index, u8 end_index, f32 speed);\n"
|
||||
"void pxl8_gfx_remove_palette_cycle(pxl8_gfx* ctx, i32 cycle_id);\n"
|
||||
"void pxl8_gfx_set_palette_cycle_speed(pxl8_gfx* ctx, i32 cycle_id, f32 speed);\n"
|
||||
"void pxl8_gfx_clear_palette_cycles(pxl8_gfx* ctx);\n"
|
||||
"void pxl8_gfx_update(pxl8_gfx* ctx, f32 dt);\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"
|
||||
"bool pxl8_gfx_push_target(pxl8_gfx* ctx);\n"
|
||||
"void pxl8_gfx_pop_target(pxl8_gfx* ctx);\n"
|
||||
"typedef struct pxl8_input_state pxl8_input_state;\n"
|
||||
"bool pxl8_key_down(const pxl8_input_state* input, const char* key_name);\n"
|
||||
"bool pxl8_key_pressed(const pxl8_input_state* input, const char* key_name);\n"
|
||||
"bool pxl8_key_released(const pxl8_input_state* input, const char* key_name);\n"
|
||||
"bool pxl8_mouse_pressed(const pxl8_input_state* input, i32 button);\n"
|
||||
"bool pxl8_mouse_released(const pxl8_input_state* input, i32 button);\n"
|
||||
"int pxl8_mouse_wheel_x(const pxl8_input_state* input);\n"
|
||||
"int pxl8_mouse_wheel_y(const pxl8_input_state* input);\n"
|
||||
"int pxl8_mouse_x(const pxl8_input_state* input);\n"
|
||||
"int pxl8_mouse_y(const pxl8_input_state* input);\n"
|
||||
"int pxl8_mouse_dx(const pxl8_input_state* input);\n"
|
||||
"int pxl8_mouse_dy(const pxl8_input_state* input);\n"
|
||||
"typedef enum { PXL8_CURSOR_ARROW = 0, PXL8_CURSOR_HAND = 1 } pxl8_cursor;\n"
|
||||
"void pxl8_center_cursor(pxl8* sys);\n"
|
||||
"void pxl8_set_cursor(pxl8* sys, pxl8_cursor cursor);\n"
|
||||
"void pxl8_set_relative_mouse_mode(pxl8* sys, bool enabled);\n"
|
||||
"void pxl8_set_running(pxl8* sys, bool running);\n"
|
||||
"void pxl8_lua_log(int level, const char* file, int line, const char* msg);\n"
|
||||
"typedef struct pxl8_cart pxl8_cart;\n"
|
||||
"pxl8_cart* pxl8_get_cart(void);\n"
|
||||
"const char* pxl8_cart_get_title(const pxl8_cart* cart);\n"
|
||||
"typedef u32 pxl8_tile;\n"
|
||||
"typedef struct pxl8_tilemap pxl8_tilemap;\n"
|
||||
"typedef struct pxl8_tilesheet pxl8_tilesheet;\n"
|
||||
"pxl8_tilemap* pxl8_tilemap_create(u32 width, u32 height, u32 tile_size);\n"
|
||||
"void pxl8_tilemap_destroy(pxl8_tilemap* tilemap);\n"
|
||||
"u32 pxl8_tilemap_get_height(const pxl8_tilemap* tilemap);\n"
|
||||
"pxl8_tile pxl8_tilemap_get_tile(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y);\n"
|
||||
"u32 pxl8_tilemap_get_tile_size(const pxl8_tilemap* tilemap);\n"
|
||||
"void* pxl8_tilemap_get_tile_user_data(const pxl8_tilemap* tilemap, u16 tile_id);\n"
|
||||
"u32 pxl8_tilemap_get_width(const pxl8_tilemap* tilemap);\n"
|
||||
"void pxl8_tilemap_set_tile_user_data(pxl8_tilemap* tilemap, u16 tile_id, void* user_data);\n"
|
||||
"bool pxl8_tilemap_check_collision(const pxl8_tilemap* tilemap, i32 x, i32 y, i32 w, i32 h);\n"
|
||||
"u16 pxl8_tilemap_get_tile_id(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y);\n"
|
||||
"bool pxl8_tilemap_is_solid(const pxl8_tilemap* tilemap, u32 x, u32 y);\n"
|
||||
"void pxl8_tilemap_render(const pxl8_tilemap* tilemap, pxl8_gfx* gfx);\n"
|
||||
"void pxl8_tilemap_render_layer(const pxl8_tilemap* tilemap, pxl8_gfx* gfx, u32 layer);\n"
|
||||
"void pxl8_tilemap_set_camera(pxl8_tilemap* tilemap, i32 x, i32 y);\n"
|
||||
"void pxl8_tilemap_set_tile(pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y, u16 tile_id, u8 flags);\n"
|
||||
"i32 pxl8_tilemap_set_tilesheet(pxl8_tilemap* tilemap, pxl8_tilesheet* tilesheet);\n"
|
||||
"i32 pxl8_tilemap_load_ase(pxl8_tilemap* tilemap, const char* filepath, u32 layer);\n"
|
||||
"pxl8_tilesheet* pxl8_tilesheet_create(u32 tile_size);\n"
|
||||
"void pxl8_tilesheet_destroy(pxl8_tilesheet* tilesheet);\n"
|
||||
"i32 pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, pxl8_gfx* gfx);\n"
|
||||
"\n"
|
||||
"typedef struct {\n"
|
||||
" float angle;\n"
|
||||
" float ax, ay, az;\n"
|
||||
" unsigned int color;\n"
|
||||
" unsigned int end_color;\n"
|
||||
" unsigned char flags;\n"
|
||||
" float life;\n"
|
||||
" float max_life;\n"
|
||||
" float size;\n"
|
||||
" float spin;\n"
|
||||
" unsigned int start_color;\n"
|
||||
" float vx, vy, vz;\n"
|
||||
" float x, y, z;\n"
|
||||
"} pxl8_particle;\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_particles pxl8_particles;\n"
|
||||
"pxl8_particles* pxl8_particles_create(u32 max_count, pxl8_rng* rng);\n"
|
||||
"void pxl8_particles_destroy(pxl8_particles* ps);\n"
|
||||
"void pxl8_particles_clear(pxl8_particles* ps);\n"
|
||||
"void pxl8_particles_emit(pxl8_particles* ps, u32 count);\n"
|
||||
"void pxl8_particles_render(pxl8_particles* ps, pxl8_gfx* gfx);\n"
|
||||
"void pxl8_particles_update(pxl8_particles* ps, float dt);\n"
|
||||
"u32 pxl8_particles_count(const pxl8_particles* ps);\n"
|
||||
"u32 pxl8_particles_max_count(const pxl8_particles* ps);\n"
|
||||
"pxl8_particle* pxl8_particles_get(pxl8_particles* ps, u32 index);\n"
|
||||
"pxl8_rng* pxl8_particles_rng(pxl8_particles* ps);\n"
|
||||
"f32 pxl8_particles_get_drag(const pxl8_particles* ps);\n"
|
||||
"f32 pxl8_particles_get_gravity_x(const pxl8_particles* ps);\n"
|
||||
"f32 pxl8_particles_get_gravity_y(const pxl8_particles* ps);\n"
|
||||
"f32 pxl8_particles_get_spawn_rate(const pxl8_particles* ps);\n"
|
||||
"f32 pxl8_particles_get_spread_x(const pxl8_particles* ps);\n"
|
||||
"f32 pxl8_particles_get_spread_y(const pxl8_particles* ps);\n"
|
||||
"f32 pxl8_particles_get_turbulence(const pxl8_particles* ps);\n"
|
||||
"f32 pxl8_particles_get_x(const pxl8_particles* ps);\n"
|
||||
"f32 pxl8_particles_get_y(const pxl8_particles* ps);\n"
|
||||
"void pxl8_particles_set_colors(pxl8_particles* ps, u8 color_min, u8 color_max);\n"
|
||||
"void pxl8_particles_set_drag(pxl8_particles* ps, f32 drag);\n"
|
||||
"void pxl8_particles_set_gravity(pxl8_particles* ps, f32 gx, f32 gy);\n"
|
||||
"void pxl8_particles_set_life(pxl8_particles* ps, f32 life_min, f32 life_max);\n"
|
||||
"void pxl8_particles_set_palette(pxl8_particles* ps, pxl8_palette* palette);\n"
|
||||
"void pxl8_particles_set_position(pxl8_particles* ps, f32 x, f32 y);\n"
|
||||
"void pxl8_particles_set_size(pxl8_particles* ps, f32 size_min, f32 size_max);\n"
|
||||
"void pxl8_particles_set_spawn_rate(pxl8_particles* ps, f32 rate);\n"
|
||||
"void pxl8_particles_set_spread(pxl8_particles* ps, f32 spread_x, f32 spread_y);\n"
|
||||
"void pxl8_particles_set_turbulence(pxl8_particles* ps, f32 turbulence);\n"
|
||||
"void pxl8_particles_set_velocity(pxl8_particles* ps, f32 vx_min, f32 vx_max, f32 vy_min, f32 vy_max);\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_transition pxl8_transition;\n"
|
||||
"typedef struct pxl8_anim pxl8_anim;\n"
|
||||
"\n"
|
||||
"pxl8_transition* pxl8_transition_create(i32 type, f32 duration);\n"
|
||||
"void pxl8_transition_destroy(pxl8_transition* transition);\n"
|
||||
"f32 pxl8_transition_get_progress(const pxl8_transition* transition);\n"
|
||||
"bool pxl8_transition_is_active(const pxl8_transition* transition);\n"
|
||||
"bool pxl8_transition_is_complete(const pxl8_transition* transition);\n"
|
||||
"void pxl8_transition_render(const pxl8_transition* transition, pxl8_gfx* gfx);\n"
|
||||
"void pxl8_transition_reset(pxl8_transition* transition);\n"
|
||||
"void pxl8_transition_set_color(pxl8_transition* transition, u32 color);\n"
|
||||
"void pxl8_transition_set_reverse(pxl8_transition* transition, bool reverse);\n"
|
||||
"void pxl8_transition_start(pxl8_transition* transition);\n"
|
||||
"void pxl8_transition_stop(pxl8_transition* transition);\n"
|
||||
"void pxl8_transition_update(pxl8_transition* transition, f32 dt);\n"
|
||||
"\n"
|
||||
"pxl8_anim* pxl8_anim_create(const u32* frame_ids, const u16* frame_durations, u16 frame_count);\n"
|
||||
"pxl8_anim* pxl8_anim_create_from_ase(pxl8_gfx* gfx, const char* path);\n"
|
||||
"void pxl8_anim_destroy(pxl8_anim* anim);\n"
|
||||
"i32 pxl8_anim_add_state(pxl8_anim* anim, const char* name, pxl8_anim* state_anim);\n"
|
||||
"u16 pxl8_anim_get_current_frame(const pxl8_anim* anim);\n"
|
||||
"u32 pxl8_anim_get_current_frame_id(const pxl8_anim* anim);\n"
|
||||
"const char* pxl8_anim_get_state(const pxl8_anim* anim);\n"
|
||||
"bool pxl8_anim_has_state_machine(const pxl8_anim* anim);\n"
|
||||
"bool pxl8_anim_is_complete(const pxl8_anim* anim);\n"
|
||||
"bool pxl8_anim_is_playing(const pxl8_anim* anim);\n"
|
||||
"void pxl8_anim_pause(pxl8_anim* anim);\n"
|
||||
"void pxl8_anim_play(pxl8_anim* anim);\n"
|
||||
"void pxl8_anim_render_sprite(const pxl8_anim* anim, pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, bool flip_x, bool flip_y);\n"
|
||||
"void pxl8_anim_reset(pxl8_anim* anim);\n"
|
||||
"void pxl8_anim_set_frame(pxl8_anim* anim, u16 frame);\n"
|
||||
"void pxl8_anim_set_loop(pxl8_anim* anim, bool loop);\n"
|
||||
"void pxl8_anim_set_reverse(pxl8_anim* anim, bool reverse);\n"
|
||||
"void pxl8_anim_set_speed(pxl8_anim* anim, f32 speed);\n"
|
||||
"i32 pxl8_anim_set_state(pxl8_anim* anim, const char* name);\n"
|
||||
"void pxl8_anim_stop(pxl8_anim* anim);\n"
|
||||
"void pxl8_anim_update(pxl8_anim* anim, f32 dt);\n"
|
||||
"\n"
|
||||
"typedef struct { float x, y, z; } pxl8_vec3;\n"
|
||||
"typedef struct { float x, y, z, w; } pxl8_vec4;\n"
|
||||
"typedef struct { float m[16]; } pxl8_mat4;\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_light {\n"
|
||||
" pxl8_vec3 position;\n"
|
||||
" u8 r, g, b;\n"
|
||||
" u8 intensity;\n"
|
||||
" f32 radius;\n"
|
||||
" f32 radius_sq;\n"
|
||||
" f32 inv_radius_sq;\n"
|
||||
"} pxl8_light;\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_3d_uniforms {\n"
|
||||
" u8 ambient;\n"
|
||||
" pxl8_vec3 celestial_dir;\n"
|
||||
" f32 celestial_intensity;\n"
|
||||
" u8 fog_color;\n"
|
||||
" f32 fog_density;\n"
|
||||
" pxl8_light lights[16];\n"
|
||||
" u32 num_lights;\n"
|
||||
" f32 time;\n"
|
||||
"} pxl8_3d_uniforms;\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_3d_camera pxl8_3d_camera;\n"
|
||||
"pxl8_3d_camera* pxl8_3d_camera_create(void);\n"
|
||||
"void pxl8_3d_camera_destroy(pxl8_3d_camera* cam);\n"
|
||||
"void pxl8_3d_camera_set_perspective(pxl8_3d_camera* cam, f32 fov, f32 aspect, f32 near, f32 far);\n"
|
||||
"void pxl8_3d_camera_set_position(pxl8_3d_camera* cam, pxl8_vec3 pos);\n"
|
||||
"void pxl8_3d_camera_set_rotation(pxl8_3d_camera* cam, f32 pitch, f32 yaw, f32 roll);\n"
|
||||
"void pxl8_3d_camera_lookat(pxl8_3d_camera* cam, pxl8_vec3 eye, pxl8_vec3 target, pxl8_vec3 up);\n"
|
||||
"pxl8_vec3 pxl8_3d_camera_get_forward(const pxl8_3d_camera* cam);\n"
|
||||
"pxl8_vec3 pxl8_3d_camera_get_position(const pxl8_3d_camera* cam);\n"
|
||||
"pxl8_vec3 pxl8_3d_camera_get_right(const pxl8_3d_camera* cam);\n"
|
||||
"pxl8_vec3 pxl8_3d_camera_get_up(const pxl8_3d_camera* cam);\n"
|
||||
"pxl8_mat4 pxl8_3d_camera_get_view(const pxl8_3d_camera* cam);\n"
|
||||
"pxl8_mat4 pxl8_3d_camera_get_projection(const pxl8_3d_camera* cam);\n"
|
||||
"void pxl8_3d_camera_update(pxl8_3d_camera* cam, f32 dt);\n"
|
||||
"\n"
|
||||
"typedef enum pxl8_gfx_effect { PXL8_GFX_EFFECT_GLOWS = 0 } pxl8_gfx_effect;\n"
|
||||
"\n"
|
||||
"typedef enum pxl8_glow_shape { PXL8_GLOW_CIRCLE = 0, PXL8_GLOW_DIAMOND = 1, PXL8_GLOW_SHAFT = 2 } pxl8_glow_shape;\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_glow_source {\n"
|
||||
" u8 color;\n"
|
||||
" u16 depth;\n"
|
||||
" u8 height;\n"
|
||||
" u16 intensity;\n"
|
||||
" u8 radius;\n"
|
||||
" pxl8_glow_shape shape;\n"
|
||||
" i16 x;\n"
|
||||
" i16 y;\n"
|
||||
"} pxl8_glow_source;\n"
|
||||
"\n"
|
||||
"void pxl8_gfx_apply_effect(pxl8_gfx* gfx, pxl8_gfx_effect effect, const void* params, u32 count);\n"
|
||||
"void pxl8_gfx_blend_tables_update(pxl8_gfx* gfx);\n"
|
||||
"void pxl8_gfx_colormap_update(pxl8_gfx* gfx);\n"
|
||||
"\n"
|
||||
"void pxl8_3d_begin_frame(pxl8_gfx* gfx, const pxl8_3d_camera* camera, const pxl8_3d_uniforms* uniforms);\n"
|
||||
"void pxl8_3d_clear(pxl8_gfx* gfx, u8 color);\n"
|
||||
"void pxl8_3d_clear_depth(pxl8_gfx* gfx);\n"
|
||||
"void pxl8_3d_draw_line(pxl8_gfx* gfx, pxl8_vec3 p0, pxl8_vec3 p1, u8 color);\n"
|
||||
"void pxl8_3d_end_frame(pxl8_gfx* gfx);\n"
|
||||
"\n"
|
||||
"typedef enum pxl8_blend_mode { PXL8_BLEND_OPAQUE = 0, PXL8_BLEND_ALPHA_TEST, PXL8_BLEND_ALPHA, PXL8_BLEND_ADDITIVE } pxl8_blend_mode;\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_material {\n"
|
||||
" u32 texture_id;\n"
|
||||
" u8 alpha;\n"
|
||||
" u8 blend_mode;\n"
|
||||
" bool dither;\n"
|
||||
" bool double_sided;\n"
|
||||
" bool dynamic_lighting;\n"
|
||||
" bool per_pixel;\n"
|
||||
" bool vertex_color_passthrough;\n"
|
||||
" f32 emissive_intensity;\n"
|
||||
"} pxl8_material;\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_vertex {\n"
|
||||
" pxl8_vec3 position;\n"
|
||||
" pxl8_vec3 normal;\n"
|
||||
" f32 u, v;\n"
|
||||
" u8 color;\n"
|
||||
" u8 light;\n"
|
||||
" u8 _pad[2];\n"
|
||||
"} pxl8_vertex;\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_mesh {\n"
|
||||
" pxl8_vertex* vertices;\n"
|
||||
" u16* indices;\n"
|
||||
" u32 vertex_count;\n"
|
||||
" u32 index_count;\n"
|
||||
" u32 vertex_capacity;\n"
|
||||
" u32 index_capacity;\n"
|
||||
"} pxl8_mesh;\n"
|
||||
"\n"
|
||||
"pxl8_mesh* pxl8_mesh_create(u32 vertex_capacity, u32 index_capacity);\n"
|
||||
"void pxl8_mesh_destroy(pxl8_mesh* mesh);\n"
|
||||
"void pxl8_mesh_clear(pxl8_mesh* mesh);\n"
|
||||
"u16 pxl8_mesh_push_vertex(pxl8_mesh* mesh, pxl8_vertex v);\n"
|
||||
"void pxl8_mesh_push_triangle(pxl8_mesh* mesh, u16 i0, u16 i1, u16 i2);\n"
|
||||
"void pxl8_3d_draw_mesh(pxl8_gfx* gfx, const pxl8_mesh* mesh, const pxl8_mat4* model, const pxl8_material* material);\n"
|
||||
"\n"
|
||||
"pxl8_mat4 pxl8_mat4_identity(void);\n"
|
||||
"pxl8_mat4 pxl8_mat4_lookat(pxl8_vec3 eye, pxl8_vec3 center, pxl8_vec3 up);\n"
|
||||
"pxl8_mat4 pxl8_mat4_mul(pxl8_mat4 a, pxl8_mat4 b);\n"
|
||||
"pxl8_mat4 pxl8_mat4_ortho(float left, float right, float bottom, float top, float near, float far);\n"
|
||||
"pxl8_mat4 pxl8_mat4_perspective(float fov, float aspect, float near, float far);\n"
|
||||
"pxl8_mat4 pxl8_mat4_rotate_x(float angle);\n"
|
||||
"pxl8_mat4 pxl8_mat4_rotate_y(float angle);\n"
|
||||
"pxl8_mat4 pxl8_mat4_rotate_z(float angle);\n"
|
||||
"pxl8_mat4 pxl8_mat4_scale(float x, float y, float z);\n"
|
||||
"pxl8_mat4 pxl8_mat4_translate(float x, float y, float z);\n"
|
||||
"\n"
|
||||
"typedef enum pxl8_procgen_type {\n"
|
||||
" PXL8_PROCGEN_ROOMS = 0,\n"
|
||||
" PXL8_PROCGEN_TERRAIN = 1\n"
|
||||
"} pxl8_procgen_type;\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_procgen_params {\n"
|
||||
" pxl8_procgen_type type;\n"
|
||||
" int width;\n"
|
||||
" int height;\n"
|
||||
" int depth;\n"
|
||||
" unsigned int seed;\n"
|
||||
" int min_room_size;\n"
|
||||
" int max_room_size;\n"
|
||||
" int num_rooms;\n"
|
||||
"} pxl8_procgen_params;\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_procgen_tex_params {\n"
|
||||
" char name[16];\n"
|
||||
" unsigned int seed;\n"
|
||||
" int width;\n"
|
||||
" int height;\n"
|
||||
" float scale;\n"
|
||||
" float roughness;\n"
|
||||
" unsigned char base_color;\n"
|
||||
" unsigned char variation;\n"
|
||||
"} pxl8_procgen_tex_params;\n"
|
||||
"\n"
|
||||
"void pxl8_procgen_tex(u8* buffer, const pxl8_procgen_tex_params* params);\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_bsp pxl8_bsp;\n"
|
||||
"typedef struct pxl8_bsp_face pxl8_bsp_face;\n"
|
||||
"typedef bool (*pxl8_texture_rule)(const pxl8_vec3* normal, const pxl8_bsp_face* face, const pxl8_bsp* bsp);\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_world_texture {\n"
|
||||
" char name[16];\n"
|
||||
" unsigned int texture_id;\n"
|
||||
" pxl8_texture_rule rule;\n"
|
||||
"} pxl8_world_texture;\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_world pxl8_world;\n"
|
||||
"pxl8_world* pxl8_world_create(void);\n"
|
||||
"void pxl8_world_destroy(pxl8_world* world);\n"
|
||||
"int pxl8_world_generate(pxl8_world* world, pxl8_gfx* gfx, const pxl8_procgen_params* params);\n"
|
||||
"int pxl8_world_load(pxl8_world* world, const char* path);\n"
|
||||
"void pxl8_world_unload(pxl8_world* world);\n"
|
||||
"int pxl8_world_apply_textures(pxl8_world* world, const pxl8_world_texture* textures, unsigned int count);\n"
|
||||
"bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, float radius);\n"
|
||||
"bool pxl8_world_is_loaded(const pxl8_world* world);\n"
|
||||
"void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);\n"
|
||||
"pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, float radius);\n"
|
||||
"\n"
|
||||
"typedef struct { i32 cursor_x; i32 cursor_y; bool cursor_down; bool cursor_clicked; u32 hot_id; u32 active_id; } pxl8_gui_state;\n"
|
||||
"pxl8_gui_state* pxl8_gui_state_create(void);\n"
|
||||
"void pxl8_gui_state_destroy(pxl8_gui_state* state);\n"
|
||||
"void pxl8_gui_begin_frame(pxl8_gui_state* state);\n"
|
||||
"void pxl8_gui_end_frame(pxl8_gui_state* state);\n"
|
||||
"void pxl8_gui_cursor_move(pxl8_gui_state* state, i32 x, i32 y);\n"
|
||||
"void pxl8_gui_cursor_down(pxl8_gui_state* state);\n"
|
||||
"void pxl8_gui_cursor_up(pxl8_gui_state* state);\n"
|
||||
"bool pxl8_gui_button(pxl8_gui_state* state, pxl8_gfx* gfx, u32 id, i32 x, i32 y, i32 w, i32 h, const char* label);\n"
|
||||
"void pxl8_gui_window(pxl8_gfx* gfx, i32 x, i32 y, i32 w, i32 h, const char* title);\n"
|
||||
"void pxl8_gui_label(pxl8_gfx* gfx, i32 x, i32 y, const char* text, u8 color);\n"
|
||||
"bool pxl8_gui_is_hovering(const pxl8_gui_state* state);\n"
|
||||
"void pxl8_gui_get_cursor_pos(const pxl8_gui_state* state, i32* x, i32* y);\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_save pxl8_save;\n"
|
||||
"pxl8_save* pxl8_save_create(const char* game_name, u32 magic, u32 version);\n"
|
||||
"void pxl8_save_destroy(pxl8_save* save);\n"
|
||||
"i32 pxl8_save_write(pxl8_save* save, u8 slot, const u8* data, u32 size);\n"
|
||||
"i32 pxl8_save_read(pxl8_save* save, u8 slot, u8** data_out, u32* size_out);\n"
|
||||
"void pxl8_save_free(u8* data);\n"
|
||||
"bool pxl8_save_exists(pxl8_save* save, u8 slot);\n"
|
||||
"i32 pxl8_save_delete(pxl8_save* save, u8 slot);\n"
|
||||
"const char* pxl8_save_get_directory(pxl8_save* save);\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_sfx_context pxl8_sfx_context;\n"
|
||||
"typedef struct pxl8_sfx_mixer pxl8_sfx_mixer;\n"
|
||||
"typedef struct pxl8_sfx_node pxl8_sfx_node;\n"
|
||||
"typedef enum pxl8_sfx_filter_type { PXL8_SFX_FILTER_BANDPASS = 0, PXL8_SFX_FILTER_HIGHPASS, PXL8_SFX_FILTER_LOWPASS, PXL8_SFX_FILTER_NONE } pxl8_sfx_filter_type;\n"
|
||||
"typedef enum pxl8_sfx_lfo_target { PXL8_SFX_LFO_AMPLITUDE = 0, PXL8_SFX_LFO_FILTER, PXL8_SFX_LFO_PITCH } pxl8_sfx_lfo_target;\n"
|
||||
"typedef enum pxl8_sfx_node_type { PXL8_SFX_NODE_COMPRESSOR, PXL8_SFX_NODE_DELAY, PXL8_SFX_NODE_REVERB } pxl8_sfx_node_type;\n"
|
||||
"typedef enum pxl8_sfx_waveform { PXL8_SFX_WAVE_NOISE = 0, PXL8_SFX_WAVE_PULSE, PXL8_SFX_WAVE_SAW, PXL8_SFX_WAVE_SINE, PXL8_SFX_WAVE_SQUARE, PXL8_SFX_WAVE_TRIANGLE } pxl8_sfx_waveform;\n"
|
||||
"typedef struct pxl8_sfx_adsr { f32 attack; f32 decay; f32 sustain; f32 release; } pxl8_sfx_adsr;\n"
|
||||
"typedef struct pxl8_sfx_compressor_config { f32 attack; f32 ratio; f32 release; f32 threshold; } pxl8_sfx_compressor_config;\n"
|
||||
"typedef struct pxl8_sfx_delay_config { f32 feedback; f32 mix; u32 time_l; u32 time_r; } pxl8_sfx_delay_config;\n"
|
||||
"typedef struct pxl8_sfx_reverb_config { f32 damping; f32 mix; f32 room; } pxl8_sfx_reverb_config;\n"
|
||||
"typedef struct pxl8_sfx_voice_params { pxl8_sfx_adsr amp_env; pxl8_sfx_adsr filter_env; pxl8_sfx_filter_type filter_type; pxl8_sfx_lfo_target lfo_target; pxl8_sfx_waveform lfo_waveform; pxl8_sfx_waveform waveform; f32 filter_cutoff; f32 filter_env_depth; f32 filter_resonance; f32 fx_send; f32 lfo_depth; f32 lfo_rate; f32 pulse_width; } pxl8_sfx_voice_params;\n"
|
||||
"pxl8_sfx_node* pxl8_sfx_compressor_create(pxl8_sfx_compressor_config cfg);\n"
|
||||
"void pxl8_sfx_compressor_set_attack(pxl8_sfx_node* node, f32 attack);\n"
|
||||
"void pxl8_sfx_compressor_set_ratio(pxl8_sfx_node* node, f32 ratio);\n"
|
||||
"void pxl8_sfx_compressor_set_release(pxl8_sfx_node* node, f32 release);\n"
|
||||
"void pxl8_sfx_compressor_set_threshold(pxl8_sfx_node* node, f32 threshold);\n"
|
||||
"void pxl8_sfx_context_append_node(pxl8_sfx_context* ctx, pxl8_sfx_node* node);\n"
|
||||
"pxl8_sfx_context* pxl8_sfx_context_create(void);\n"
|
||||
"void pxl8_sfx_context_destroy(pxl8_sfx_context* ctx);\n"
|
||||
"pxl8_sfx_node* pxl8_sfx_context_get_head(pxl8_sfx_context* ctx);\n"
|
||||
"f32 pxl8_sfx_context_get_volume(const pxl8_sfx_context* ctx);\n"
|
||||
"void pxl8_sfx_context_insert_node(pxl8_sfx_context* ctx, pxl8_sfx_node* after, pxl8_sfx_node* node);\n"
|
||||
"void pxl8_sfx_context_remove_node(pxl8_sfx_context* ctx, pxl8_sfx_node* node);\n"
|
||||
"void pxl8_sfx_context_set_volume(pxl8_sfx_context* ctx, f32 volume);\n"
|
||||
"pxl8_sfx_node* pxl8_sfx_delay_create(pxl8_sfx_delay_config cfg);\n"
|
||||
"void pxl8_sfx_delay_set_feedback(pxl8_sfx_node* node, f32 feedback);\n"
|
||||
"void pxl8_sfx_delay_set_mix(pxl8_sfx_node* node, f32 mix);\n"
|
||||
"void pxl8_sfx_delay_set_time(pxl8_sfx_node* node, u32 time_l, u32 time_r);\n"
|
||||
"void pxl8_sfx_mixer_attach(pxl8_sfx_mixer* mixer, pxl8_sfx_context* ctx);\n"
|
||||
"pxl8_sfx_mixer* pxl8_sfx_mixer_create(void);\n"
|
||||
"void pxl8_sfx_mixer_destroy(pxl8_sfx_mixer* mixer);\n"
|
||||
"void pxl8_sfx_mixer_detach(pxl8_sfx_mixer* mixer, pxl8_sfx_context* ctx);\n"
|
||||
"f32 pxl8_sfx_mixer_get_master_volume(const pxl8_sfx_mixer* mixer);\n"
|
||||
"void pxl8_sfx_mixer_set_master_volume(pxl8_sfx_mixer* mixer, f32 volume);\n"
|
||||
"void pxl8_sfx_node_destroy(pxl8_sfx_node* node);\n"
|
||||
"f32 pxl8_sfx_note_to_freq(u8 note);\n"
|
||||
"u16 pxl8_sfx_play_note(pxl8_sfx_context* ctx, u8 note, const pxl8_sfx_voice_params* params, f32 volume, f32 duration);\n"
|
||||
"void pxl8_sfx_release_voice(pxl8_sfx_context* ctx, u16 voice_id);\n"
|
||||
"pxl8_sfx_node* pxl8_sfx_reverb_create(pxl8_sfx_reverb_config cfg);\n"
|
||||
"void pxl8_sfx_reverb_set_damping(pxl8_sfx_node* node, f32 damping);\n"
|
||||
"void pxl8_sfx_reverb_set_mix(pxl8_sfx_node* node, f32 mix);\n"
|
||||
"void pxl8_sfx_reverb_set_room(pxl8_sfx_node* node, f32 room);\n"
|
||||
"void pxl8_sfx_stop_all(pxl8_sfx_context* ctx);\n"
|
||||
"void pxl8_sfx_stop_voice(pxl8_sfx_context* ctx, u16 voice_id);\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_net pxl8_net;\n"
|
||||
"typedef enum pxl8_net_mode { PXL8_NET_LOCAL = 0, PXL8_NET_REMOTE } pxl8_net_mode;\n"
|
||||
"typedef struct pxl8_net_config { const char* address; pxl8_net_mode mode; u16 port; } pxl8_net_config;\n"
|
||||
"typedef enum pxl8_cmd_type { PXL8_CMD_NONE = 0, PXL8_CMD_SPAWN_ENTITY } pxl8_cmd_type;\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_command_msg {\n"
|
||||
" u16 cmd_type;\n"
|
||||
" u8 payload[64];\n"
|
||||
" u16 payload_size;\n"
|
||||
" u64 tick;\n"
|
||||
"} pxl8_command_msg;\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_input_msg {\n"
|
||||
" u32 buttons;\n"
|
||||
" f32 look_dx;\n"
|
||||
" f32 look_dy;\n"
|
||||
" f32 move_x;\n"
|
||||
" f32 move_y;\n"
|
||||
" f32 yaw;\n"
|
||||
" u64 tick;\n"
|
||||
" u64 timestamp;\n"
|
||||
"} pxl8_input_msg;\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_entity_state {\n"
|
||||
" u64 entity_id;\n"
|
||||
" u8 userdata[56];\n"
|
||||
"} pxl8_entity_state;\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_snapshot_header {\n"
|
||||
" u16 entity_count;\n"
|
||||
" u16 event_count;\n"
|
||||
" u64 player_id;\n"
|
||||
" u64 tick;\n"
|
||||
" f32 time;\n"
|
||||
"} pxl8_snapshot_header;\n"
|
||||
"\n"
|
||||
"i32 pxl8_net_connect(pxl8_net* net);\n"
|
||||
"bool pxl8_net_connected(const pxl8_net* net);\n"
|
||||
"pxl8_net* pxl8_net_create(const pxl8_net_config* config);\n"
|
||||
"void pxl8_net_destroy(pxl8_net* net);\n"
|
||||
"void pxl8_net_disconnect(pxl8_net* net);\n"
|
||||
"const pxl8_entity_state* pxl8_net_entities(const pxl8_net* net);\n"
|
||||
"const u8* pxl8_net_entity_prev_userdata(const pxl8_net* net, u64 entity_id);\n"
|
||||
"const u8* pxl8_net_entity_userdata(const pxl8_net* net, u64 entity_id);\n"
|
||||
"const pxl8_input_msg* pxl8_net_input_at(const pxl8_net* net, u64 tick);\n"
|
||||
"u64 pxl8_net_input_oldest_tick(const pxl8_net* net);\n"
|
||||
"void pxl8_net_input_push(pxl8_net* net, const pxl8_input_msg* input);\n"
|
||||
"f32 pxl8_net_lerp_alpha(const pxl8_net* net);\n"
|
||||
"bool pxl8_net_needs_correction(const pxl8_net* net);\n"
|
||||
"u64 pxl8_net_player_id(const pxl8_net* net);\n"
|
||||
"bool pxl8_net_poll(pxl8_net* net);\n"
|
||||
"u8* pxl8_net_predicted_state(pxl8_net* net);\n"
|
||||
"void pxl8_net_predicted_tick_set(pxl8_net* net, u64 tick);\n"
|
||||
"i32 pxl8_net_send_command(pxl8_net* net, const pxl8_command_msg* cmd);\n"
|
||||
"i32 pxl8_net_send_input(pxl8_net* net, const pxl8_input_msg* input);\n"
|
||||
"const pxl8_snapshot_header* pxl8_net_snapshot(const pxl8_net* net);\n"
|
||||
"u64 pxl8_net_tick(const pxl8_net* net);\n"
|
||||
"void pxl8_net_update(pxl8_net* net, f32 dt);\n"
|
||||
"\n"
|
||||
"void pxl8_bit_clear(u32* val, u8 bit);\n"
|
||||
"u32 pxl8_bit_count(u32 val);\n"
|
||||
"void pxl8_bit_set(u32* val, u8 bit);\n"
|
||||
"bool pxl8_bit_test(u32 val, u8 bit);\n"
|
||||
"void pxl8_bit_toggle(u32* val, u8 bit);\n"
|
||||
"\n"
|
||||
"void pxl8_pack_u8(u8* buf, size_t offset, u8 val);\n"
|
||||
"void pxl8_pack_u16_be(u8* buf, size_t offset, u16 val);\n"
|
||||
"void pxl8_pack_u16_le(u8* buf, size_t offset, u16 val);\n"
|
||||
"void pxl8_pack_u32_be(u8* buf, size_t offset, u32 val);\n"
|
||||
"void pxl8_pack_u32_le(u8* buf, size_t offset, u32 val);\n"
|
||||
"void pxl8_pack_u64_be(u8* buf, size_t offset, u64 val);\n"
|
||||
"void pxl8_pack_u64_le(u8* buf, size_t offset, u64 val);\n"
|
||||
"void pxl8_pack_i8(u8* buf, size_t offset, i8 val);\n"
|
||||
"void pxl8_pack_i16_be(u8* buf, size_t offset, i16 val);\n"
|
||||
"void pxl8_pack_i16_le(u8* buf, size_t offset, i16 val);\n"
|
||||
"void pxl8_pack_i32_be(u8* buf, size_t offset, i32 val);\n"
|
||||
"void pxl8_pack_i32_le(u8* buf, size_t offset, i32 val);\n"
|
||||
"void pxl8_pack_i64_be(u8* buf, size_t offset, i64 val);\n"
|
||||
"void pxl8_pack_i64_le(u8* buf, size_t offset, i64 val);\n"
|
||||
"void pxl8_pack_f32_be(u8* buf, size_t offset, f32 val);\n"
|
||||
"void pxl8_pack_f32_le(u8* buf, size_t offset, f32 val);\n"
|
||||
"void pxl8_pack_f64_be(u8* buf, size_t offset, f64 val);\n"
|
||||
"void pxl8_pack_f64_le(u8* buf, size_t offset, f64 val);\n"
|
||||
"\n"
|
||||
"u8 pxl8_unpack_u8(const u8* buf, size_t offset);\n"
|
||||
"u16 pxl8_unpack_u16_be(const u8* buf, size_t offset);\n"
|
||||
"u16 pxl8_unpack_u16_le(const u8* buf, size_t offset);\n"
|
||||
"u32 pxl8_unpack_u32_be(const u8* buf, size_t offset);\n"
|
||||
"u32 pxl8_unpack_u32_le(const u8* buf, size_t offset);\n"
|
||||
"u64 pxl8_unpack_u64_be(const u8* buf, size_t offset);\n"
|
||||
"u64 pxl8_unpack_u64_le(const u8* buf, size_t offset);\n"
|
||||
"i8 pxl8_unpack_i8(const u8* buf, size_t offset);\n"
|
||||
"i16 pxl8_unpack_i16_be(const u8* buf, size_t offset);\n"
|
||||
"i16 pxl8_unpack_i16_le(const u8* buf, size_t offset);\n"
|
||||
"i32 pxl8_unpack_i32_be(const u8* buf, size_t offset);\n"
|
||||
"i32 pxl8_unpack_i32_le(const u8* buf, size_t offset);\n"
|
||||
"i64 pxl8_unpack_i64_be(const u8* buf, size_t offset);\n"
|
||||
"i64 pxl8_unpack_i64_le(const u8* buf, size_t offset);\n"
|
||||
"f32 pxl8_unpack_f32_be(const u8* buf, size_t offset);\n"
|
||||
"f32 pxl8_unpack_f32_le(const u8* buf, size_t offset);\n"
|
||||
"f64 pxl8_unpack_f64_be(const u8* buf, size_t offset);\n"
|
||||
"f64 pxl8_unpack_f64_le(const u8* buf, size_t offset);\n";
|
||||
1121
src/sfx/pxl8_sfx.c
Normal file
1121
src/sfx/pxl8_sfx.c
Normal file
File diff suppressed because it is too large
Load diff
141
src/sfx/pxl8_sfx.h
Normal file
141
src/sfx/pxl8_sfx.h
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_hal.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#define PXL8_SFX_BUFFER_SIZE 1024
|
||||
#define PXL8_SFX_MAX_CONTEXTS 8
|
||||
#define PXL8_SFX_MAX_DELAY_SAMPLES 48000
|
||||
#define PXL8_SFX_MAX_VOICES 16
|
||||
#define PXL8_SFX_SAMPLE_RATE 48000
|
||||
|
||||
typedef struct pxl8_sfx_context pxl8_sfx_context;
|
||||
typedef struct pxl8_sfx_mixer pxl8_sfx_mixer;
|
||||
typedef struct pxl8_sfx_node pxl8_sfx_node;
|
||||
|
||||
typedef enum pxl8_sfx_filter_type {
|
||||
PXL8_SFX_FILTER_BANDPASS = 0,
|
||||
PXL8_SFX_FILTER_HIGHPASS,
|
||||
PXL8_SFX_FILTER_LOWPASS,
|
||||
PXL8_SFX_FILTER_NONE
|
||||
} pxl8_sfx_filter_type;
|
||||
|
||||
typedef enum pxl8_sfx_lfo_target {
|
||||
PXL8_SFX_LFO_AMPLITUDE = 0,
|
||||
PXL8_SFX_LFO_FILTER,
|
||||
PXL8_SFX_LFO_PITCH
|
||||
} pxl8_sfx_lfo_target;
|
||||
|
||||
typedef enum pxl8_sfx_node_type {
|
||||
PXL8_SFX_NODE_COMPRESSOR,
|
||||
PXL8_SFX_NODE_DELAY,
|
||||
PXL8_SFX_NODE_REVERB
|
||||
} pxl8_sfx_node_type;
|
||||
|
||||
typedef enum pxl8_sfx_waveform {
|
||||
PXL8_SFX_WAVE_NOISE = 0,
|
||||
PXL8_SFX_WAVE_PULSE,
|
||||
PXL8_SFX_WAVE_SAW,
|
||||
PXL8_SFX_WAVE_SINE,
|
||||
PXL8_SFX_WAVE_SQUARE,
|
||||
PXL8_SFX_WAVE_TRIANGLE
|
||||
} pxl8_sfx_waveform;
|
||||
|
||||
typedef struct pxl8_sfx_adsr {
|
||||
f32 attack;
|
||||
f32 decay;
|
||||
f32 sustain;
|
||||
f32 release;
|
||||
} pxl8_sfx_adsr;
|
||||
|
||||
typedef struct pxl8_sfx_compressor_config {
|
||||
f32 attack;
|
||||
f32 ratio;
|
||||
f32 release;
|
||||
f32 threshold;
|
||||
} pxl8_sfx_compressor_config;
|
||||
|
||||
typedef struct pxl8_sfx_delay_config {
|
||||
f32 feedback;
|
||||
f32 mix;
|
||||
u32 time_l;
|
||||
u32 time_r;
|
||||
} pxl8_sfx_delay_config;
|
||||
|
||||
typedef struct pxl8_sfx_reverb_config {
|
||||
f32 damping;
|
||||
f32 mix;
|
||||
f32 room;
|
||||
} pxl8_sfx_reverb_config;
|
||||
|
||||
typedef struct pxl8_sfx_voice_params {
|
||||
pxl8_sfx_adsr amp_env;
|
||||
pxl8_sfx_adsr filter_env;
|
||||
pxl8_sfx_filter_type filter_type;
|
||||
pxl8_sfx_lfo_target lfo_target;
|
||||
pxl8_sfx_waveform lfo_waveform;
|
||||
pxl8_sfx_waveform waveform;
|
||||
f32 filter_cutoff;
|
||||
f32 filter_env_depth;
|
||||
f32 filter_resonance;
|
||||
f32 fx_send;
|
||||
f32 lfo_depth;
|
||||
f32 lfo_rate;
|
||||
f32 pulse_width;
|
||||
} pxl8_sfx_voice_params;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_sfx_node* pxl8_sfx_compressor_create(pxl8_sfx_compressor_config cfg);
|
||||
void pxl8_sfx_compressor_set_attack(pxl8_sfx_node* node, f32 attack);
|
||||
void pxl8_sfx_compressor_set_ratio(pxl8_sfx_node* node, f32 ratio);
|
||||
void pxl8_sfx_compressor_set_release(pxl8_sfx_node* node, f32 release);
|
||||
void pxl8_sfx_compressor_set_threshold(pxl8_sfx_node* node, f32 threshold);
|
||||
|
||||
void pxl8_sfx_context_append_node(pxl8_sfx_context* ctx, pxl8_sfx_node* node);
|
||||
pxl8_sfx_context* pxl8_sfx_context_create(void);
|
||||
void pxl8_sfx_context_destroy(pxl8_sfx_context* ctx);
|
||||
pxl8_sfx_node* pxl8_sfx_context_get_head(pxl8_sfx_context* ctx);
|
||||
f32 pxl8_sfx_context_get_volume(const pxl8_sfx_context* ctx);
|
||||
void pxl8_sfx_context_insert_node(pxl8_sfx_context* ctx, pxl8_sfx_node* after, pxl8_sfx_node* node);
|
||||
void pxl8_sfx_context_remove_node(pxl8_sfx_context* ctx, pxl8_sfx_node* node);
|
||||
void pxl8_sfx_context_set_volume(pxl8_sfx_context* ctx, f32 volume);
|
||||
|
||||
pxl8_sfx_node* pxl8_sfx_delay_create(pxl8_sfx_delay_config cfg);
|
||||
void pxl8_sfx_delay_set_feedback(pxl8_sfx_node* node, f32 feedback);
|
||||
void pxl8_sfx_delay_set_mix(pxl8_sfx_node* node, f32 mix);
|
||||
void pxl8_sfx_delay_set_time(pxl8_sfx_node* node, u32 time_l, u32 time_r);
|
||||
|
||||
#define PXL8_SFX_EVENT_NOTE_ON 1
|
||||
#define PXL8_SFX_EVENT_NOTE_OFF 2
|
||||
|
||||
typedef void (*pxl8_sfx_event_callback)(u8 event_type, u8 context_id, u8 note, f32 volume, void* userdata);
|
||||
|
||||
void pxl8_sfx_mixer_attach(pxl8_sfx_mixer* mixer, pxl8_sfx_context* ctx);
|
||||
void pxl8_sfx_mixer_clear(pxl8_sfx_mixer* mixer);
|
||||
pxl8_sfx_mixer* pxl8_sfx_mixer_create(const pxl8_hal* hal);
|
||||
void pxl8_sfx_mixer_destroy(pxl8_sfx_mixer* mixer);
|
||||
void pxl8_sfx_mixer_detach(pxl8_sfx_mixer* mixer, pxl8_sfx_context* ctx);
|
||||
f32 pxl8_sfx_mixer_get_master_volume(const pxl8_sfx_mixer* mixer);
|
||||
void pxl8_sfx_mixer_process(pxl8_sfx_mixer* mixer);
|
||||
void pxl8_sfx_mixer_set_event_callback(pxl8_sfx_mixer* mixer, pxl8_sfx_event_callback cb, void* userdata);
|
||||
void pxl8_sfx_mixer_set_master_volume(pxl8_sfx_mixer* mixer, f32 volume);
|
||||
|
||||
void pxl8_sfx_node_destroy(pxl8_sfx_node* node);
|
||||
f32 pxl8_sfx_note_to_freq(u8 note);
|
||||
u16 pxl8_sfx_play_note(pxl8_sfx_context* ctx, u8 note, const pxl8_sfx_voice_params* params, f32 volume, f32 duration);
|
||||
void pxl8_sfx_release_voice(pxl8_sfx_context* ctx, u16 voice_id);
|
||||
|
||||
pxl8_sfx_node* pxl8_sfx_reverb_create(pxl8_sfx_reverb_config cfg);
|
||||
void pxl8_sfx_reverb_set_damping(pxl8_sfx_node* node, f32 damping);
|
||||
void pxl8_sfx_reverb_set_mix(pxl8_sfx_node* node, f32 mix);
|
||||
void pxl8_sfx_reverb_set_room(pxl8_sfx_node* node, f32 room);
|
||||
|
||||
void pxl8_sfx_stop_all(pxl8_sfx_context* ctx);
|
||||
void pxl8_sfx_stop_voice(pxl8_sfx_context* ctx, u16 voice_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
756
src/world/pxl8_bsp.c
Normal file
756
src/world/pxl8_bsp.c
Normal file
|
|
@ -0,0 +1,756 @@
|
|||
#include "pxl8_bsp.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_color.h"
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_io.h"
|
||||
#include "pxl8_log.h"
|
||||
|
||||
#define BSP_VERSION 29
|
||||
|
||||
typedef enum {
|
||||
CHUNK_ENTITIES = 0,
|
||||
CHUNK_PLANES = 1,
|
||||
CHUNK_TEXTURES = 2,
|
||||
CHUNK_VERTICES = 3,
|
||||
CHUNK_VISIBILITY = 4,
|
||||
CHUNK_NODES = 5,
|
||||
CHUNK_TEXINFO = 6,
|
||||
CHUNK_FACES = 7,
|
||||
CHUNK_LIGHTING = 8,
|
||||
CHUNK_CLIPNODES = 9,
|
||||
CHUNK_LEAFS = 10,
|
||||
CHUNK_MARKSURFACES = 11,
|
||||
CHUNK_EDGES = 12,
|
||||
CHUNK_SURFEDGES = 13,
|
||||
CHUNK_MODELS = 14,
|
||||
CHUNK_COUNT = 15
|
||||
} pxl8_bsp_chunk_type;
|
||||
|
||||
typedef struct {
|
||||
u32 offset;
|
||||
u32 size;
|
||||
} pxl8_bsp_chunk;
|
||||
|
||||
typedef struct {
|
||||
u32 version;
|
||||
pxl8_bsp_chunk chunks[CHUNK_COUNT];
|
||||
} pxl8_bsp_header;
|
||||
|
||||
static inline pxl8_vec3 read_vec3(pxl8_stream* stream) {
|
||||
pxl8_vec3 v;
|
||||
v.x = pxl8_read_f32(stream);
|
||||
v.y = pxl8_read_f32(stream);
|
||||
v.z = pxl8_read_f32(stream);
|
||||
return v;
|
||||
}
|
||||
|
||||
static bool validate_chunk(const pxl8_bsp_chunk* chunk, u32 element_size, size_t file_size) {
|
||||
if (chunk->size == 0) return true;
|
||||
if (chunk->offset >= file_size) return false;
|
||||
if (chunk->offset + chunk->size > file_size) return false;
|
||||
if (chunk->size % element_size != 0) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool pxl8_bsp_get_edge_vertex(const pxl8_bsp* bsp, i32 surfedge_idx, u32* out_vert_idx) {
|
||||
if (surfedge_idx >= (i32)bsp->num_surfedges) return false;
|
||||
|
||||
i32 edge_idx = bsp->surfedges[surfedge_idx];
|
||||
u32 vertex_index;
|
||||
|
||||
if (edge_idx >= 0) {
|
||||
if ((u32)edge_idx >= bsp->num_edges) return false;
|
||||
vertex_index = 0;
|
||||
} else {
|
||||
edge_idx = -edge_idx;
|
||||
if ((u32)edge_idx >= bsp->num_edges) return false;
|
||||
vertex_index = 1;
|
||||
}
|
||||
|
||||
*out_vert_idx = bsp->edges[edge_idx].vertex[vertex_index];
|
||||
return *out_vert_idx < bsp->num_vertices;
|
||||
}
|
||||
|
||||
static inline bool pxl8_bsp_get_edge_vertices(const pxl8_bsp* bsp, i32 surfedge_idx, u32* out_v0_idx, u32* out_v1_idx) {
|
||||
if (surfedge_idx >= (i32)bsp->num_surfedges) return false;
|
||||
|
||||
i32 edge_idx = bsp->surfedges[surfedge_idx];
|
||||
|
||||
if (edge_idx >= 0) {
|
||||
if ((u32)edge_idx >= bsp->num_edges) return false;
|
||||
*out_v0_idx = bsp->edges[edge_idx].vertex[0];
|
||||
*out_v1_idx = bsp->edges[edge_idx].vertex[1];
|
||||
} else {
|
||||
edge_idx = -edge_idx;
|
||||
if ((u32)edge_idx >= bsp->num_edges) return false;
|
||||
*out_v0_idx = bsp->edges[edge_idx].vertex[1];
|
||||
*out_v1_idx = bsp->edges[edge_idx].vertex[0];
|
||||
}
|
||||
|
||||
return *out_v0_idx < bsp->num_vertices && *out_v1_idx < bsp->num_vertices;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp) {
|
||||
if (!path || !bsp) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
memset(bsp, 0, sizeof(*bsp));
|
||||
|
||||
u8* file_data = NULL;
|
||||
size_t file_size = 0;
|
||||
pxl8_result result = pxl8_io_read_binary_file(path, &file_data, &file_size);
|
||||
if (result != PXL8_OK) {
|
||||
pxl8_error("Failed to load BSP file: %s", path);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (file_size < sizeof(pxl8_bsp_header)) {
|
||||
pxl8_error("BSP file too small: %s", path);
|
||||
free(file_data);
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
pxl8_stream stream = pxl8_stream_create(file_data, (u32)file_size);
|
||||
|
||||
pxl8_bsp_header header;
|
||||
header.version = pxl8_read_u32(&stream);
|
||||
|
||||
if (header.version != BSP_VERSION) {
|
||||
pxl8_error("Invalid BSP version: %u (expected %d)", header.version, BSP_VERSION);
|
||||
free(file_data);
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
for (i32 i = 0; i < CHUNK_COUNT; i++) {
|
||||
header.chunks[i].offset = pxl8_read_u32(&stream);
|
||||
header.chunks[i].size = pxl8_read_u32(&stream);
|
||||
}
|
||||
|
||||
pxl8_bsp_chunk* chunk = &header.chunks[CHUNK_VERTICES];
|
||||
if (!validate_chunk(chunk, 12, file_size)) goto error_cleanup;
|
||||
bsp->num_vertices = chunk->size / 12;
|
||||
if (bsp->num_vertices > 0) {
|
||||
bsp->vertices = calloc(bsp->num_vertices, sizeof(pxl8_bsp_vertex));
|
||||
if (!bsp->vertices) goto error_cleanup;
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_vertices; i++) {
|
||||
bsp->vertices[i].position = read_vec3(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_EDGES];
|
||||
if (!validate_chunk(chunk, 4, file_size)) goto error_cleanup;
|
||||
bsp->num_edges = chunk->size / 4;
|
||||
if (bsp->num_edges > 0) {
|
||||
bsp->edges = calloc(bsp->num_edges, sizeof(pxl8_bsp_edge));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_edges; i++) {
|
||||
bsp->edges[i].vertex[0] = pxl8_read_u16(&stream);
|
||||
bsp->edges[i].vertex[1] = pxl8_read_u16(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_SURFEDGES];
|
||||
if (!validate_chunk(chunk, 4, file_size)) goto error_cleanup;
|
||||
bsp->num_surfedges = chunk->size / 4;
|
||||
if (bsp->num_surfedges > 0) {
|
||||
bsp->surfedges = calloc(bsp->num_surfedges, sizeof(i32));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_surfedges; i++) {
|
||||
bsp->surfedges[i] = pxl8_read_i32(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_PLANES];
|
||||
if (!validate_chunk(chunk, 20, file_size)) goto error_cleanup;
|
||||
bsp->num_planes = chunk->size / 20;
|
||||
if (bsp->num_planes > 0) {
|
||||
bsp->planes = calloc(bsp->num_planes, sizeof(pxl8_bsp_plane));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_planes; i++) {
|
||||
bsp->planes[i].normal = read_vec3(&stream);
|
||||
bsp->planes[i].dist = pxl8_read_f32(&stream);
|
||||
bsp->planes[i].type = pxl8_read_i32(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_TEXINFO];
|
||||
if (!validate_chunk(chunk, 40, file_size)) goto error_cleanup;
|
||||
bsp->num_texinfo = chunk->size / 40;
|
||||
if (bsp->num_texinfo > 0) {
|
||||
bsp->texinfo = calloc(bsp->num_texinfo, sizeof(pxl8_bsp_texinfo));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_texinfo; i++) {
|
||||
bsp->texinfo[i].u_axis = read_vec3(&stream);
|
||||
bsp->texinfo[i].u_offset = pxl8_read_f32(&stream);
|
||||
bsp->texinfo[i].v_axis = read_vec3(&stream);
|
||||
bsp->texinfo[i].v_offset = pxl8_read_f32(&stream);
|
||||
bsp->texinfo[i].miptex = pxl8_read_u32(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_FACES];
|
||||
if (!validate_chunk(chunk, 20, file_size)) goto error_cleanup;
|
||||
bsp->num_faces = chunk->size / 20;
|
||||
if (bsp->num_faces > 0) {
|
||||
bsp->faces = calloc(bsp->num_faces, sizeof(pxl8_bsp_face));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_faces; i++) {
|
||||
bsp->faces[i].plane_id = pxl8_read_u16(&stream);
|
||||
bsp->faces[i].side = pxl8_read_u16(&stream);
|
||||
bsp->faces[i].first_edge = pxl8_read_u32(&stream);
|
||||
bsp->faces[i].num_edges = pxl8_read_u16(&stream);
|
||||
bsp->faces[i].texinfo_id = pxl8_read_u16(&stream);
|
||||
bsp->faces[i].styles[0] = pxl8_read_u8(&stream);
|
||||
bsp->faces[i].styles[1] = pxl8_read_u8(&stream);
|
||||
bsp->faces[i].styles[2] = pxl8_read_u8(&stream);
|
||||
bsp->faces[i].styles[3] = pxl8_read_u8(&stream);
|
||||
bsp->faces[i].lightmap_offset = pxl8_read_u32(&stream);
|
||||
|
||||
bsp->faces[i].aabb_min = (pxl8_vec3){1e30f, 1e30f, 1e30f};
|
||||
bsp->faces[i].aabb_max = (pxl8_vec3){-1e30f, -1e30f, -1e30f};
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_NODES];
|
||||
if (!validate_chunk(chunk, 24, file_size)) goto error_cleanup;
|
||||
bsp->num_nodes = chunk->size / 24;
|
||||
if (bsp->num_nodes > 0) {
|
||||
bsp->nodes = calloc(bsp->num_nodes, sizeof(pxl8_bsp_node));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_nodes; i++) {
|
||||
bsp->nodes[i].plane_id = pxl8_read_u32(&stream);
|
||||
bsp->nodes[i].children[0] = pxl8_read_i16(&stream);
|
||||
bsp->nodes[i].children[1] = pxl8_read_i16(&stream);
|
||||
for (u32 j = 0; j < 3; j++) bsp->nodes[i].mins[j] = pxl8_read_i16(&stream);
|
||||
for (u32 j = 0; j < 3; j++) bsp->nodes[i].maxs[j] = pxl8_read_i16(&stream);
|
||||
bsp->nodes[i].first_face = pxl8_read_u16(&stream);
|
||||
bsp->nodes[i].num_faces = pxl8_read_u16(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_LEAFS];
|
||||
if (!validate_chunk(chunk, 28, file_size)) goto error_cleanup;
|
||||
bsp->num_leafs = chunk->size / 28;
|
||||
if (bsp->num_leafs > 0) {
|
||||
bsp->leafs = calloc(bsp->num_leafs, sizeof(pxl8_bsp_leaf));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_leafs; i++) {
|
||||
bsp->leafs[i].contents = pxl8_read_i32(&stream);
|
||||
bsp->leafs[i].visofs = pxl8_read_i32(&stream);
|
||||
for (u32 j = 0; j < 3; j++) bsp->leafs[i].mins[j] = pxl8_read_i16(&stream);
|
||||
for (u32 j = 0; j < 3; j++) bsp->leafs[i].maxs[j] = pxl8_read_i16(&stream);
|
||||
bsp->leafs[i].first_marksurface = pxl8_read_u16(&stream);
|
||||
bsp->leafs[i].num_marksurfaces = pxl8_read_u16(&stream);
|
||||
for (u32 j = 0; j < 4; j++) bsp->leafs[i].ambient_level[j] = pxl8_read_u8(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_MARKSURFACES];
|
||||
if (!validate_chunk(chunk, 2, file_size)) goto error_cleanup;
|
||||
bsp->num_marksurfaces = chunk->size / 2;
|
||||
if (bsp->num_marksurfaces > 0) {
|
||||
bsp->marksurfaces = calloc(bsp->num_marksurfaces, sizeof(u16));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_marksurfaces; i++) {
|
||||
bsp->marksurfaces[i] = pxl8_read_u16(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_MODELS];
|
||||
if (!validate_chunk(chunk, 64, file_size)) goto error_cleanup;
|
||||
bsp->num_models = chunk->size / 64;
|
||||
if (bsp->num_models > 0) {
|
||||
bsp->models = calloc(bsp->num_models, sizeof(pxl8_bsp_model));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_models; i++) {
|
||||
for (u32 j = 0; j < 3; j++) bsp->models[i].mins[j] = pxl8_read_f32(&stream);
|
||||
for (u32 j = 0; j < 3; j++) bsp->models[i].maxs[j] = pxl8_read_f32(&stream);
|
||||
bsp->models[i].origin = read_vec3(&stream);
|
||||
for (u32 j = 0; j < 4; j++) bsp->models[i].headnode[j] = pxl8_read_i32(&stream);
|
||||
bsp->models[i].visleafs = pxl8_read_i32(&stream);
|
||||
bsp->models[i].first_face = pxl8_read_i32(&stream);
|
||||
bsp->models[i].num_faces = pxl8_read_i32(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_VISIBILITY];
|
||||
if (!validate_chunk(chunk, 1, file_size)) goto error_cleanup;
|
||||
bsp->visdata_size = chunk->size;
|
||||
if (bsp->visdata_size > 0) {
|
||||
bsp->visdata = malloc(bsp->visdata_size);
|
||||
memcpy(bsp->visdata, file_data + chunk->offset, bsp->visdata_size);
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_LIGHTING];
|
||||
if (!validate_chunk(chunk, 1, file_size)) goto error_cleanup;
|
||||
bsp->lightdata_size = chunk->size;
|
||||
if (bsp->lightdata_size > 0) {
|
||||
bsp->lightdata = malloc(bsp->lightdata_size);
|
||||
memcpy(bsp->lightdata, file_data + chunk->offset, bsp->lightdata_size);
|
||||
}
|
||||
|
||||
free(file_data);
|
||||
|
||||
for (u32 i = 0; i < bsp->num_faces; i++) {
|
||||
pxl8_bsp_face* face = &bsp->faces[i];
|
||||
f32 min_x = 1e30f, min_y = 1e30f, min_z = 1e30f;
|
||||
f32 max_x = -1e30f, max_y = -1e30f, max_z = -1e30f;
|
||||
|
||||
for (u32 j = 0; j < face->num_edges; j++) {
|
||||
i32 surfedge_idx = face->first_edge + j;
|
||||
u32 vert_idx;
|
||||
|
||||
if (!pxl8_bsp_get_edge_vertex(bsp, surfedge_idx, &vert_idx)) continue;
|
||||
|
||||
pxl8_vec3 v = bsp->vertices[vert_idx].position;
|
||||
|
||||
if (v.x < min_x) min_x = v.x;
|
||||
if (v.x > max_x) max_x = v.x;
|
||||
if (v.y < min_y) min_y = v.y;
|
||||
if (v.y > max_y) max_y = v.y;
|
||||
if (v.z < min_z) min_z = v.z;
|
||||
if (v.z > max_z) max_z = v.z;
|
||||
}
|
||||
|
||||
face->aabb_min = (pxl8_vec3){min_x, min_y, min_z};
|
||||
face->aabb_max = (pxl8_vec3){max_x, max_y, max_z};
|
||||
}
|
||||
|
||||
pxl8_debug("Loaded BSP: %u verts, %u faces, %u nodes, %u leafs",
|
||||
bsp->num_vertices, bsp->num_faces, bsp->num_nodes, bsp->num_leafs);
|
||||
|
||||
return PXL8_OK;
|
||||
|
||||
error_cleanup:
|
||||
pxl8_error("BSP chunk validation failed: %s", path);
|
||||
free(file_data);
|
||||
pxl8_bsp_destroy(bsp);
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
void pxl8_bsp_destroy(pxl8_bsp* bsp) {
|
||||
if (!bsp) return;
|
||||
|
||||
free(bsp->edges);
|
||||
free(bsp->faces);
|
||||
free(bsp->leafs);
|
||||
free(bsp->lightdata);
|
||||
free(bsp->marksurfaces);
|
||||
free(bsp->models);
|
||||
free(bsp->nodes);
|
||||
free(bsp->planes);
|
||||
free(bsp->surfedges);
|
||||
free(bsp->texinfo);
|
||||
free(bsp->vertices);
|
||||
free(bsp->visdata);
|
||||
|
||||
memset(bsp, 0, sizeof(*bsp));
|
||||
}
|
||||
|
||||
i32 pxl8_bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos) {
|
||||
if (!bsp || bsp->num_nodes == 0) return -1;
|
||||
|
||||
i32 node_id = 0;
|
||||
|
||||
while (node_id >= 0) {
|
||||
const pxl8_bsp_node* node = &bsp->nodes[node_id];
|
||||
const pxl8_bsp_plane* plane = &bsp->planes[node->plane_id];
|
||||
|
||||
f32 dist = pxl8_vec3_dot(pos, plane->normal) - plane->dist;
|
||||
node_id = node->children[dist < 0 ? 1 : 0];
|
||||
}
|
||||
|
||||
return -(node_id + 1);
|
||||
}
|
||||
|
||||
bool pxl8_bsp_is_leaf_visible(const pxl8_bsp* bsp, i32 leaf_from, i32 leaf_to) {
|
||||
if (!bsp || !bsp->visdata || bsp->visdata_size == 0) return true;
|
||||
if (leaf_from < 0 || leaf_to < 0) return true;
|
||||
if ((u32)leaf_from >= bsp->num_leafs || (u32)leaf_to >= bsp->num_leafs) return true;
|
||||
|
||||
i32 visofs = bsp->leafs[leaf_from].visofs;
|
||||
if (visofs < 0) return true;
|
||||
|
||||
u32 target_byte = leaf_to >> 3;
|
||||
u32 target_bit = leaf_to & 7;
|
||||
u32 pvs_size = (bsp->num_leafs + 7) / 8;
|
||||
|
||||
u32 pos = (u32)visofs;
|
||||
u32 current_byte = 0;
|
||||
|
||||
while (current_byte < pvs_size && pos < bsp->visdata_size) {
|
||||
u8 b = bsp->visdata[pos++];
|
||||
|
||||
if (b != 0) {
|
||||
if (current_byte == target_byte) {
|
||||
return (b & (1 << target_bit)) != 0;
|
||||
}
|
||||
current_byte++;
|
||||
} else {
|
||||
if (pos >= bsp->visdata_size) return false;
|
||||
u32 count = bsp->visdata[pos++];
|
||||
if (target_byte < current_byte + count) {
|
||||
return false;
|
||||
}
|
||||
current_byte += count;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
pxl8_bsp_pvs pxl8_bsp_decompress_pvs(const pxl8_bsp* bsp, i32 leaf) {
|
||||
pxl8_bsp_pvs pvs = {0};
|
||||
|
||||
u32 pvs_size = (bsp->num_leafs + 7) / 8;
|
||||
pvs.data = calloc(pvs_size, 1);
|
||||
pvs.size = pvs_size;
|
||||
|
||||
if (!pvs.data) return pvs;
|
||||
|
||||
if (!bsp || leaf < 0 || (u32)leaf >= bsp->num_leafs) {
|
||||
memset(pvs.data, 0xFF, pvs_size);
|
||||
return pvs;
|
||||
}
|
||||
|
||||
i32 visofs = bsp->leafs[leaf].visofs;
|
||||
if (visofs < 0 || !bsp->visdata || bsp->visdata_size == 0) {
|
||||
memset(pvs.data, 0xFF, pvs_size);
|
||||
return pvs;
|
||||
}
|
||||
|
||||
u32 pos = (u32)visofs;
|
||||
u32 out = 0;
|
||||
|
||||
while (out < pvs_size && pos < bsp->visdata_size) {
|
||||
u8 b = bsp->visdata[pos++];
|
||||
|
||||
if (b != 0) {
|
||||
pvs.data[out++] = b;
|
||||
} else {
|
||||
if (pos >= bsp->visdata_size) break;
|
||||
u32 count = bsp->visdata[pos++];
|
||||
out += count;
|
||||
}
|
||||
}
|
||||
|
||||
return pvs;
|
||||
}
|
||||
|
||||
void pxl8_bsp_pvs_destroy(pxl8_bsp_pvs* pvs) {
|
||||
if (pvs) {
|
||||
free(pvs->data);
|
||||
pvs->data = NULL;
|
||||
pvs->size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool pxl8_bsp_pvs_is_visible(const pxl8_bsp_pvs* pvs, i32 leaf) {
|
||||
if (!pvs || !pvs->data || leaf < 0) return false;
|
||||
u32 byte_idx = leaf >> 3;
|
||||
u32 bit_idx = leaf & 7;
|
||||
if (byte_idx >= pvs->size) return false;
|
||||
return (pvs->data[byte_idx] & (1 << bit_idx)) != 0;
|
||||
}
|
||||
|
||||
pxl8_bsp_lightmap pxl8_bsp_lightmap_uniform(u8 r, u8 g, u8 b) {
|
||||
return (pxl8_bsp_lightmap){
|
||||
.color = {r, g, b},
|
||||
.height = 0,
|
||||
.offset = 0,
|
||||
.width = 0,
|
||||
};
|
||||
}
|
||||
|
||||
pxl8_bsp_lightmap pxl8_bsp_lightmap_mapped(u8 width, u8 height, u32 offset) {
|
||||
return (pxl8_bsp_lightmap){
|
||||
.color = {0, 0, 0},
|
||||
.height = height,
|
||||
.offset = offset,
|
||||
.width = width,
|
||||
};
|
||||
}
|
||||
|
||||
pxl8_bsp_lightmap_sample pxl8_bsp_sample_lightmap(const pxl8_bsp* bsp, u32 face_idx, f32 u, f32 v) {
|
||||
pxl8_bsp_lightmap_sample white = {255, 255, 255};
|
||||
|
||||
if (!bsp || !bsp->lightmaps || face_idx >= bsp->num_lightmaps) {
|
||||
return white;
|
||||
}
|
||||
|
||||
const pxl8_bsp_lightmap* lm = &bsp->lightmaps[face_idx];
|
||||
|
||||
if (lm->width == 0) {
|
||||
return (pxl8_bsp_lightmap_sample){lm->color[2], lm->color[1], lm->color[0]};
|
||||
}
|
||||
|
||||
if (!bsp->lightdata || bsp->lightdata_size == 0) {
|
||||
return white;
|
||||
}
|
||||
|
||||
f32 w = (f32)lm->width;
|
||||
f32 h = (f32)lm->height;
|
||||
f32 fx = u * w;
|
||||
f32 fy = v * h;
|
||||
|
||||
if (fx < 0) fx = 0;
|
||||
if (fx > w - 1.001f) fx = w - 1.001f;
|
||||
if (fy < 0) fy = 0;
|
||||
if (fy > h - 1.001f) fy = h - 1.001f;
|
||||
|
||||
u32 x0 = (u32)fx;
|
||||
u32 y0 = (u32)fy;
|
||||
u32 x1 = x0 + 1;
|
||||
u32 y1 = y0 + 1;
|
||||
if (x1 >= lm->width) x1 = lm->width - 1;
|
||||
if (y1 >= lm->height) y1 = lm->height - 1;
|
||||
|
||||
f32 frac_x = fx - (f32)x0;
|
||||
f32 frac_y = fy - (f32)y0;
|
||||
|
||||
u32 stride = lm->width;
|
||||
u32 base = lm->offset;
|
||||
|
||||
u32 idx00 = base + y0 * stride + x0;
|
||||
u32 idx10 = base + y0 * stride + x1;
|
||||
u32 idx01 = base + y1 * stride + x0;
|
||||
u32 idx11 = base + y1 * stride + x1;
|
||||
|
||||
u8 r00, g00, b00, r10, g10, b10, r01, g01, b01, r11, g11, b11;
|
||||
|
||||
if (idx00 < bsp->lightdata_size) pxl8_rgb332_unpack(bsp->lightdata[idx00], &r00, &g00, &b00);
|
||||
else { r00 = g00 = b00 = 255; }
|
||||
if (idx10 < bsp->lightdata_size) pxl8_rgb332_unpack(bsp->lightdata[idx10], &r10, &g10, &b10);
|
||||
else { r10 = g10 = b10 = 255; }
|
||||
if (idx01 < bsp->lightdata_size) pxl8_rgb332_unpack(bsp->lightdata[idx01], &r01, &g01, &b01);
|
||||
else { r01 = g01 = b01 = 255; }
|
||||
if (idx11 < bsp->lightdata_size) pxl8_rgb332_unpack(bsp->lightdata[idx11], &r11, &g11, &b11);
|
||||
else { r11 = g11 = b11 = 255; }
|
||||
|
||||
f32 inv_x = 1.0f - frac_x;
|
||||
f32 inv_y = 1.0f - frac_y;
|
||||
|
||||
u8 r = (u8)(r00 * inv_x * inv_y + r10 * frac_x * inv_y + r01 * inv_x * frac_y + r11 * frac_x * frac_y);
|
||||
u8 g = (u8)(g00 * inv_x * inv_y + g10 * frac_x * inv_y + g01 * inv_x * frac_y + g11 * frac_x * frac_y);
|
||||
u8 b = (u8)(b00 * inv_x * inv_y + b10 * frac_x * inv_y + b01 * inv_x * frac_y + b11 * frac_x * frac_y);
|
||||
|
||||
return (pxl8_bsp_lightmap_sample){b, g, r};
|
||||
}
|
||||
|
||||
static inline bool face_in_frustum(const pxl8_bsp* bsp, u32 face_id, const pxl8_frustum* frustum) {
|
||||
const pxl8_bsp_face* face = &bsp->faces[face_id];
|
||||
return pxl8_frustum_test_aabb(frustum, face->aabb_min, face->aabb_max);
|
||||
}
|
||||
|
||||
static void collect_face_to_mesh(
|
||||
const pxl8_bsp* bsp,
|
||||
u32 face_id,
|
||||
pxl8_mesh* mesh
|
||||
) {
|
||||
const pxl8_bsp_face* face = &bsp->faces[face_id];
|
||||
if (face->num_edges < 3) return;
|
||||
|
||||
pxl8_vec3 normal = {0, 1, 0};
|
||||
if (face->plane_id < bsp->num_planes) {
|
||||
normal = bsp->planes[face->plane_id].normal;
|
||||
if (face->side) {
|
||||
normal.x = -normal.x;
|
||||
normal.y = -normal.y;
|
||||
normal.z = -normal.z;
|
||||
}
|
||||
}
|
||||
|
||||
const pxl8_bsp_texinfo* texinfo = NULL;
|
||||
f32 tex_scale = 64.0f;
|
||||
if (face->texinfo_id < bsp->num_texinfo) {
|
||||
texinfo = &bsp->texinfo[face->texinfo_id];
|
||||
}
|
||||
|
||||
u16 base_idx = (u16)mesh->vertex_count;
|
||||
u32 num_verts = 0;
|
||||
|
||||
for (u32 i = 0; i < face->num_edges && num_verts < 64; i++) {
|
||||
i32 surfedge_idx = face->first_edge + i;
|
||||
u32 vert_idx;
|
||||
|
||||
if (!pxl8_bsp_get_edge_vertex(bsp, surfedge_idx, &vert_idx)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pxl8_vec3 pos = bsp->vertices[vert_idx].position;
|
||||
|
||||
f32 u = 0.0f, v = 0.0f;
|
||||
if (texinfo) {
|
||||
u = (pxl8_vec3_dot(pos, texinfo->u_axis) + texinfo->u_offset) / tex_scale;
|
||||
v = (pxl8_vec3_dot(pos, texinfo->v_axis) + texinfo->v_offset) / tex_scale;
|
||||
}
|
||||
|
||||
u8 light = 255;
|
||||
if (bsp->vertex_lights && vert_idx < bsp->num_vertex_lights) {
|
||||
light = (bsp->vertex_lights[vert_idx] >> 24) & 0xFF;
|
||||
}
|
||||
|
||||
pxl8_vertex vtx = {
|
||||
.position = pos,
|
||||
.normal = normal,
|
||||
.u = u,
|
||||
.v = v,
|
||||
.color = 15,
|
||||
.light = light,
|
||||
};
|
||||
pxl8_mesh_push_vertex(mesh, vtx);
|
||||
num_verts++;
|
||||
}
|
||||
|
||||
if (num_verts < 3) return;
|
||||
|
||||
for (u32 i = 1; i < num_verts - 1; i++) {
|
||||
pxl8_mesh_push_triangle(mesh, base_idx, base_idx + i, base_idx + i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 texture_id) {
|
||||
if (!gfx || !bsp || face_id >= bsp->num_faces) return;
|
||||
|
||||
pxl8_mesh* mesh = pxl8_mesh_create(64, 192);
|
||||
if (!mesh) return;
|
||||
|
||||
collect_face_to_mesh(bsp, face_id, mesh);
|
||||
|
||||
if (mesh->index_count > 0) {
|
||||
pxl8_mat4 identity = pxl8_mat4_identity();
|
||||
pxl8_material mat = pxl8_material_create(texture_id);
|
||||
pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat);
|
||||
}
|
||||
|
||||
pxl8_mesh_destroy(mesh);
|
||||
}
|
||||
|
||||
void pxl8_bsp_render_textured(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos) {
|
||||
static int call_count = 0;
|
||||
if (!gfx || !bsp || bsp->num_faces == 0) {
|
||||
if (call_count++ < 5) {
|
||||
pxl8_debug("bsp_render_textured: early return - gfx=%p, bsp=%p, num_faces=%u",
|
||||
(void*)gfx, (void*)bsp, bsp ? bsp->num_faces : 0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx);
|
||||
if (!frustum) {
|
||||
if (call_count++ < 5) {
|
||||
pxl8_debug("bsp_render_textured: frustum is NULL!");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
|
||||
|
||||
static u8* rendered_faces = NULL;
|
||||
static u32 rendered_faces_capacity = 0;
|
||||
|
||||
if (rendered_faces_capacity < bsp->num_faces) {
|
||||
u8* new_buffer = realloc(rendered_faces, bsp->num_faces);
|
||||
if (!new_buffer) return;
|
||||
rendered_faces = new_buffer;
|
||||
rendered_faces_capacity = bsp->num_faces;
|
||||
}
|
||||
|
||||
memset(rendered_faces, 0, bsp->num_faces);
|
||||
|
||||
pxl8_mesh* mesh = pxl8_mesh_create(8192, 16384);
|
||||
if (!mesh) return;
|
||||
|
||||
u32 current_texture = 0xFFFFFFFF;
|
||||
|
||||
for (u32 leaf_id = 0; leaf_id < bsp->num_leafs; leaf_id++) {
|
||||
if (camera_leaf >= 0 && !pxl8_bsp_is_leaf_visible(bsp, camera_leaf, leaf_id)) continue;
|
||||
|
||||
const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id];
|
||||
|
||||
for (u32 i = 0; i < leaf->num_marksurfaces; i++) {
|
||||
u32 surf_idx = leaf->first_marksurface + i;
|
||||
if (surf_idx >= bsp->num_marksurfaces) continue;
|
||||
|
||||
u32 face_id = bsp->marksurfaces[surf_idx];
|
||||
if (face_id >= bsp->num_faces) continue;
|
||||
|
||||
if (rendered_faces[face_id]) continue;
|
||||
rendered_faces[face_id] = 1;
|
||||
|
||||
if (!face_in_frustum(bsp, face_id, frustum)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const pxl8_bsp_face* face = &bsp->faces[face_id];
|
||||
u32 texture_id = 0;
|
||||
if (face->texinfo_id < bsp->num_texinfo) {
|
||||
texture_id = bsp->texinfo[face->texinfo_id].miptex;
|
||||
}
|
||||
|
||||
if (texture_id != current_texture && mesh->index_count > 0) {
|
||||
pxl8_mat4 identity = pxl8_mat4_identity();
|
||||
pxl8_material mat = pxl8_material_with_lighting(pxl8_material_with_double_sided(pxl8_material_create(current_texture)));
|
||||
pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat);
|
||||
pxl8_mesh_clear(mesh);
|
||||
}
|
||||
|
||||
current_texture = texture_id;
|
||||
collect_face_to_mesh(bsp, face_id, mesh);
|
||||
}
|
||||
}
|
||||
|
||||
if (mesh->index_count > 0) {
|
||||
pxl8_mat4 identity = pxl8_mat4_identity();
|
||||
pxl8_material mat = pxl8_material_with_lighting(pxl8_material_with_double_sided(pxl8_material_create(current_texture)));
|
||||
pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat);
|
||||
}
|
||||
|
||||
pxl8_mesh_destroy(mesh);
|
||||
}
|
||||
|
||||
void pxl8_bsp_render_wireframe(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos, u32 color) {
|
||||
if (!gfx || !bsp) return;
|
||||
|
||||
const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx);
|
||||
if (!frustum) return;
|
||||
|
||||
i32 camera_leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
|
||||
u8 line_color = (u8)(color & 0xFF);
|
||||
|
||||
for (u32 leaf_id = 0; leaf_id < bsp->num_leafs; leaf_id++) {
|
||||
if (camera_leaf >= 0 && !pxl8_bsp_is_leaf_visible(bsp, camera_leaf, leaf_id)) continue;
|
||||
|
||||
const pxl8_bsp_leaf* leaf = &bsp->leafs[leaf_id];
|
||||
|
||||
for (u32 i = 0; i < leaf->num_marksurfaces; i++) {
|
||||
u32 surf_idx = leaf->first_marksurface + i;
|
||||
if (surf_idx >= bsp->num_marksurfaces) continue;
|
||||
|
||||
u32 face_id = bsp->marksurfaces[surf_idx];
|
||||
if (face_id >= bsp->num_faces) continue;
|
||||
|
||||
if (!face_in_frustum(bsp, face_id, frustum)) continue;
|
||||
|
||||
const pxl8_bsp_face* face = &bsp->faces[face_id];
|
||||
|
||||
for (u32 e = 0; e < face->num_edges; e++) {
|
||||
i32 surfedge_idx = face->first_edge + e;
|
||||
|
||||
if (surfedge_idx >= (i32)bsp->num_surfedges) continue;
|
||||
|
||||
u32 v0_idx, v1_idx;
|
||||
if (!pxl8_bsp_get_edge_vertices(bsp, surfedge_idx, &v0_idx, &v1_idx)) continue;
|
||||
|
||||
pxl8_vec3 p0 = bsp->vertices[v0_idx].position;
|
||||
pxl8_vec3 p1 = bsp->vertices[v1_idx].position;
|
||||
|
||||
pxl8_3d_draw_line(gfx, p0, p1, line_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
164
src/world/pxl8_bsp.h
Normal file
164
src/world/pxl8_bsp.h
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_math.h"
|
||||
#include "pxl8_mesh.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_bsp_edge {
|
||||
u16 vertex[2];
|
||||
} pxl8_bsp_edge;
|
||||
|
||||
typedef struct pxl8_bsp_face {
|
||||
u32 first_edge;
|
||||
u32 lightmap_offset;
|
||||
u16 num_edges;
|
||||
u16 plane_id;
|
||||
u16 side;
|
||||
|
||||
u8 styles[4];
|
||||
u16 texinfo_id;
|
||||
|
||||
pxl8_vec3 aabb_min;
|
||||
pxl8_vec3 aabb_max;
|
||||
} pxl8_bsp_face;
|
||||
|
||||
typedef struct pxl8_bsp_leaf {
|
||||
u8 ambient_level[4];
|
||||
i32 contents;
|
||||
|
||||
u16 first_marksurface;
|
||||
i16 maxs[3];
|
||||
i16 mins[3];
|
||||
u16 num_marksurfaces;
|
||||
|
||||
i32 visofs;
|
||||
} pxl8_bsp_leaf;
|
||||
|
||||
typedef struct pxl8_bsp_model {
|
||||
i32 first_face;
|
||||
i32 headnode[4];
|
||||
f32 maxs[3];
|
||||
f32 mins[3];
|
||||
i32 num_faces;
|
||||
|
||||
pxl8_vec3 origin;
|
||||
i32 visleafs;
|
||||
} pxl8_bsp_model;
|
||||
|
||||
typedef struct pxl8_bsp_node {
|
||||
i32 children[2];
|
||||
|
||||
u16 first_face;
|
||||
i16 maxs[3];
|
||||
i16 mins[3];
|
||||
u16 num_faces;
|
||||
|
||||
u32 plane_id;
|
||||
} pxl8_bsp_node;
|
||||
|
||||
typedef struct pxl8_bsp_plane {
|
||||
f32 dist;
|
||||
pxl8_vec3 normal;
|
||||
i32 type;
|
||||
} pxl8_bsp_plane;
|
||||
|
||||
typedef struct pxl8_bsp_texinfo {
|
||||
u32 miptex;
|
||||
char name[16];
|
||||
|
||||
f32 u_offset;
|
||||
pxl8_vec3 u_axis;
|
||||
|
||||
f32 v_offset;
|
||||
pxl8_vec3 v_axis;
|
||||
} pxl8_bsp_texinfo;
|
||||
|
||||
typedef struct pxl8_bsp_vertex {
|
||||
pxl8_vec3 position;
|
||||
} pxl8_bsp_vertex;
|
||||
|
||||
typedef struct pxl8_bsp_lightmap {
|
||||
u8 color[3];
|
||||
u8 height;
|
||||
u32 offset;
|
||||
u8 width;
|
||||
} pxl8_bsp_lightmap;
|
||||
|
||||
typedef struct pxl8_bsp_lightmap_sample {
|
||||
u8 b;
|
||||
u8 g;
|
||||
u8 r;
|
||||
} pxl8_bsp_lightmap_sample;
|
||||
|
||||
typedef struct pxl8_bsp_material_batch {
|
||||
u16* face_indices;
|
||||
u32 face_count;
|
||||
u8 material_id;
|
||||
pxl8_mesh* mesh;
|
||||
} pxl8_bsp_material_batch;
|
||||
|
||||
typedef struct pxl8_bsp_pvs {
|
||||
u8* data;
|
||||
u32 size;
|
||||
} pxl8_bsp_pvs;
|
||||
|
||||
typedef struct pxl8_bsp {
|
||||
pxl8_bsp_edge* edges;
|
||||
pxl8_bsp_face* faces;
|
||||
pxl8_bsp_leaf* leafs;
|
||||
u8* lightdata;
|
||||
pxl8_bsp_lightmap* lightmaps;
|
||||
u16* marksurfaces;
|
||||
pxl8_bsp_material_batch* material_batches;
|
||||
pxl8_bsp_model* models;
|
||||
pxl8_bsp_node* nodes;
|
||||
pxl8_bsp_plane* planes;
|
||||
i32* surfedges;
|
||||
pxl8_bsp_texinfo* texinfo;
|
||||
u32* vertex_lights;
|
||||
pxl8_bsp_vertex* vertices;
|
||||
u8* visdata;
|
||||
|
||||
u32 lightdata_size;
|
||||
u32 num_edges;
|
||||
u32 num_faces;
|
||||
u32 num_leafs;
|
||||
u32 num_lightmaps;
|
||||
u32 num_marksurfaces;
|
||||
u32 num_material_batches;
|
||||
u32 num_models;
|
||||
u32 num_nodes;
|
||||
u32 num_planes;
|
||||
u32 num_surfedges;
|
||||
u32 num_texinfo;
|
||||
u32 num_vertex_lights;
|
||||
u32 num_vertices;
|
||||
u32 visdata_size;
|
||||
} pxl8_bsp;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_result pxl8_bsp_load(const char* path, pxl8_bsp* bsp);
|
||||
void pxl8_bsp_destroy(pxl8_bsp* bsp);
|
||||
|
||||
i32 pxl8_bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos);
|
||||
bool pxl8_bsp_is_leaf_visible(const pxl8_bsp* bsp, i32 leaf_from, i32 leaf_to);
|
||||
|
||||
pxl8_bsp_pvs pxl8_bsp_decompress_pvs(const pxl8_bsp* bsp, i32 leaf);
|
||||
void pxl8_bsp_pvs_destroy(pxl8_bsp_pvs* pvs);
|
||||
bool pxl8_bsp_pvs_is_visible(const pxl8_bsp_pvs* pvs, i32 leaf);
|
||||
|
||||
pxl8_bsp_lightmap pxl8_bsp_lightmap_uniform(u8 r, u8 g, u8 b);
|
||||
pxl8_bsp_lightmap pxl8_bsp_lightmap_mapped(u8 width, u8 height, u32 offset);
|
||||
pxl8_bsp_lightmap_sample pxl8_bsp_sample_lightmap(const pxl8_bsp* bsp, u32 face_idx, f32 u, f32 v);
|
||||
|
||||
void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 texture_id);
|
||||
void pxl8_bsp_render_textured(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos);
|
||||
void pxl8_bsp_render_wireframe(pxl8_gfx* gfx, const pxl8_bsp* bsp, pxl8_vec3 camera_pos, u32 color);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
504
src/world/pxl8_gen.c
Normal file
504
src/world/pxl8_gen.c
Normal file
|
|
@ -0,0 +1,504 @@
|
|||
#include "pxl8_gen.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_rng.h"
|
||||
|
||||
typedef struct room_grid {
|
||||
u8* cells;
|
||||
i32 width;
|
||||
i32 height;
|
||||
} room_grid;
|
||||
|
||||
static bool room_grid_init(room_grid* grid, i32 width, i32 height) {
|
||||
grid->width = width;
|
||||
grid->height = height;
|
||||
grid->cells = calloc(width * height, sizeof(u8));
|
||||
|
||||
return grid->cells != NULL;
|
||||
}
|
||||
|
||||
static u8 room_grid_get(const room_grid* grid, i32 x, i32 y) {
|
||||
if (x < 0 || x >= grid->width || y < 0 || y >= grid->height) {
|
||||
return 1;
|
||||
}
|
||||
return grid->cells[y * grid->width + x];
|
||||
}
|
||||
|
||||
static void room_grid_set(room_grid* grid, i32 x, i32 y, u8 value) {
|
||||
if (x < 0 || x >= grid->width || y < 0 || y >= grid->height) {
|
||||
return;
|
||||
}
|
||||
grid->cells[y * grid->width + x] = value;
|
||||
}
|
||||
|
||||
static inline void compute_face_aabb(pxl8_bsp_face* face, const pxl8_bsp_vertex* verts, u32 vert_idx) {
|
||||
face->aabb_min = (pxl8_vec3){1e30f, 1e30f, 1e30f};
|
||||
face->aabb_max = (pxl8_vec3){-1e30f, -1e30f, -1e30f};
|
||||
|
||||
for (u32 i = 0; i < 4; i++) {
|
||||
pxl8_vec3 v = verts[vert_idx + i].position;
|
||||
if (v.x < face->aabb_min.x) face->aabb_min.x = v.x;
|
||||
if (v.x > face->aabb_max.x) face->aabb_max.x = v.x;
|
||||
if (v.y < face->aabb_min.y) face->aabb_min.y = v.y;
|
||||
if (v.y > face->aabb_max.y) face->aabb_max.y = v.y;
|
||||
if (v.z < face->aabb_min.z) face->aabb_min.z = v.z;
|
||||
if (v.z > face->aabb_max.z) face->aabb_max.z = v.z;
|
||||
}
|
||||
}
|
||||
|
||||
static void room_grid_fill(room_grid* grid, u8 value) {
|
||||
for (i32 y = 0; y < grid->height; y++) {
|
||||
for (i32 x = 0; x < grid->width; x++) {
|
||||
room_grid_set(grid, x, y, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static pxl8_result grid_to_bsp(pxl8_bsp* bsp, const room_grid* grid) {
|
||||
i32 vertex_count = 0;
|
||||
i32 face_count = 0;
|
||||
i32 floor_ceiling_count = 0;
|
||||
|
||||
for (i32 y = 0; y < grid->height; y++) {
|
||||
for (i32 x = 0; x < grid->width; x++) {
|
||||
if (room_grid_get(grid, x, y) == 0) {
|
||||
if (room_grid_get(grid, x - 1, y) == 1) face_count++;
|
||||
if (room_grid_get(grid, x + 1, y) == 1) face_count++;
|
||||
if (room_grid_get(grid, x, y - 1) == 1) face_count++;
|
||||
if (room_grid_get(grid, x, y + 1) == 1) face_count++;
|
||||
floor_ceiling_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
face_count += floor_ceiling_count * 2;
|
||||
vertex_count = face_count * 4;
|
||||
|
||||
pxl8_debug("Cave generation: %dx%d grid -> %d faces, %d vertices",
|
||||
grid->width, grid->height, face_count, vertex_count);
|
||||
|
||||
bsp->vertices = calloc(vertex_count, sizeof(pxl8_bsp_vertex));
|
||||
bsp->faces = calloc(face_count, sizeof(pxl8_bsp_face));
|
||||
bsp->planes = calloc(face_count, sizeof(pxl8_bsp_plane));
|
||||
bsp->edges = calloc(vertex_count, sizeof(pxl8_bsp_edge));
|
||||
bsp->surfedges = calloc(vertex_count, sizeof(i32));
|
||||
|
||||
if (!bsp->vertices || !bsp->faces || !bsp->planes || !bsp->edges || !bsp->surfedges) {
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
bsp->texinfo = NULL;
|
||||
bsp->num_texinfo = 0;
|
||||
|
||||
i32 vert_idx = 0;
|
||||
i32 face_idx = 0;
|
||||
i32 edge_idx = 0;
|
||||
|
||||
const f32 cell_size = 64.0f;
|
||||
const f32 wall_height = 128.0f;
|
||||
|
||||
for (i32 y = 0; y < grid->height; y++) {
|
||||
for (i32 x = 0; x < grid->width; x++) {
|
||||
if (room_grid_get(grid, x, y) == 0) {
|
||||
f32 fx = (f32)x * cell_size;
|
||||
f32 fy = (f32)y * cell_size;
|
||||
|
||||
if (room_grid_get(grid, x - 1, y) == 1) {
|
||||
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy};
|
||||
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx, wall_height, fy};
|
||||
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx, wall_height, fy + cell_size};
|
||||
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx, 0, fy + cell_size};
|
||||
|
||||
bsp->planes[face_idx].normal = (pxl8_vec3){-1, 0, 0};
|
||||
bsp->planes[face_idx].dist = -fx;
|
||||
|
||||
bsp->faces[face_idx].plane_id = face_idx;
|
||||
bsp->faces[face_idx].num_edges = 4;
|
||||
bsp->faces[face_idx].first_edge = edge_idx;
|
||||
bsp->faces[face_idx].texinfo_id = 0;
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
|
||||
bsp->edges[edge_idx + i].vertex[1] = vert_idx + ((i + 1) % 4);
|
||||
bsp->surfedges[edge_idx + i] = edge_idx + i;
|
||||
}
|
||||
|
||||
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
|
||||
|
||||
vert_idx += 4;
|
||||
edge_idx += 4;
|
||||
face_idx++;
|
||||
}
|
||||
|
||||
if (room_grid_get(grid, x + 1, y) == 1) {
|
||||
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx + cell_size, 0, fy};
|
||||
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx + cell_size, 0, fy + cell_size};
|
||||
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy + cell_size};
|
||||
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx + cell_size, wall_height, fy};
|
||||
|
||||
bsp->planes[face_idx].normal = (pxl8_vec3){1, 0, 0};
|
||||
bsp->planes[face_idx].dist = fx + cell_size;
|
||||
|
||||
bsp->faces[face_idx].plane_id = face_idx;
|
||||
bsp->faces[face_idx].num_edges = 4;
|
||||
bsp->faces[face_idx].first_edge = edge_idx;
|
||||
bsp->faces[face_idx].texinfo_id = 0;
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
|
||||
bsp->edges[edge_idx + i].vertex[1] = vert_idx + ((i + 1) % 4);
|
||||
bsp->surfedges[edge_idx + i] = edge_idx + i;
|
||||
}
|
||||
|
||||
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
|
||||
|
||||
vert_idx += 4;
|
||||
edge_idx += 4;
|
||||
face_idx++;
|
||||
}
|
||||
|
||||
if (room_grid_get(grid, x, y - 1) == 1) {
|
||||
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy};
|
||||
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx + cell_size, 0, fy};
|
||||
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy};
|
||||
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx, wall_height, fy};
|
||||
|
||||
bsp->planes[face_idx].normal = (pxl8_vec3){0, 0, -1};
|
||||
bsp->planes[face_idx].dist = -fy;
|
||||
|
||||
bsp->faces[face_idx].plane_id = face_idx;
|
||||
bsp->faces[face_idx].num_edges = 4;
|
||||
bsp->faces[face_idx].first_edge = edge_idx;
|
||||
bsp->faces[face_idx].texinfo_id = 0;
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
|
||||
bsp->edges[edge_idx + i].vertex[1] = vert_idx + ((i + 1) % 4);
|
||||
bsp->surfedges[edge_idx + i] = edge_idx + i;
|
||||
}
|
||||
|
||||
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
|
||||
|
||||
vert_idx += 4;
|
||||
edge_idx += 4;
|
||||
face_idx++;
|
||||
}
|
||||
|
||||
if (room_grid_get(grid, x, y + 1) == 1) {
|
||||
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy + cell_size};
|
||||
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx, wall_height, fy + cell_size};
|
||||
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy + cell_size};
|
||||
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx + cell_size, 0, fy + cell_size};
|
||||
|
||||
bsp->planes[face_idx].normal = (pxl8_vec3){0, 0, 1};
|
||||
bsp->planes[face_idx].dist = fy + cell_size;
|
||||
|
||||
bsp->faces[face_idx].plane_id = face_idx;
|
||||
bsp->faces[face_idx].num_edges = 4;
|
||||
bsp->faces[face_idx].first_edge = edge_idx;
|
||||
bsp->faces[face_idx].texinfo_id = 0;
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
|
||||
bsp->edges[edge_idx + i].vertex[1] = vert_idx + ((i + 1) % 4);
|
||||
bsp->surfedges[edge_idx + i] = edge_idx + i;
|
||||
}
|
||||
|
||||
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
|
||||
|
||||
vert_idx += 4;
|
||||
edge_idx += 4;
|
||||
face_idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i32 y = 0; y < grid->height; y++) {
|
||||
for (i32 x = 0; x < grid->width; x++) {
|
||||
if (room_grid_get(grid, x, y) == 0) {
|
||||
f32 fx = (f32)x * cell_size;
|
||||
f32 fy = (f32)y * cell_size;
|
||||
|
||||
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, 0, fy};
|
||||
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx, 0, fy + cell_size};
|
||||
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, 0, fy + cell_size};
|
||||
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx + cell_size, 0, fy};
|
||||
|
||||
bsp->planes[face_idx].normal = (pxl8_vec3){0, 1, 0};
|
||||
bsp->planes[face_idx].dist = 0;
|
||||
|
||||
bsp->faces[face_idx].plane_id = face_idx;
|
||||
bsp->faces[face_idx].num_edges = 4;
|
||||
bsp->faces[face_idx].first_edge = edge_idx;
|
||||
bsp->faces[face_idx].texinfo_id = 0;
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
|
||||
bsp->edges[edge_idx + i].vertex[1] = vert_idx + ((i + 1) % 4);
|
||||
bsp->surfedges[edge_idx + i] = edge_idx + i;
|
||||
}
|
||||
|
||||
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
|
||||
|
||||
vert_idx += 4;
|
||||
edge_idx += 4;
|
||||
face_idx++;
|
||||
|
||||
bsp->vertices[vert_idx + 0].position = (pxl8_vec3){fx, wall_height, fy};
|
||||
bsp->vertices[vert_idx + 1].position = (pxl8_vec3){fx + cell_size, wall_height, fy};
|
||||
bsp->vertices[vert_idx + 2].position = (pxl8_vec3){fx + cell_size, wall_height, fy + cell_size};
|
||||
bsp->vertices[vert_idx + 3].position = (pxl8_vec3){fx, wall_height, fy + cell_size};
|
||||
|
||||
bsp->planes[face_idx].normal = (pxl8_vec3){0, -1, 0};
|
||||
bsp->planes[face_idx].dist = -wall_height;
|
||||
|
||||
bsp->faces[face_idx].plane_id = face_idx;
|
||||
bsp->faces[face_idx].num_edges = 4;
|
||||
bsp->faces[face_idx].first_edge = edge_idx;
|
||||
bsp->faces[face_idx].texinfo_id = 0;
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
bsp->edges[edge_idx + i].vertex[0] = vert_idx + i;
|
||||
bsp->edges[edge_idx + i].vertex[1] = vert_idx + ((i + 1) % 4);
|
||||
bsp->surfedges[edge_idx + i] = edge_idx + i;
|
||||
}
|
||||
|
||||
compute_face_aabb(&bsp->faces[face_idx], bsp->vertices, vert_idx);
|
||||
|
||||
vert_idx += 4;
|
||||
edge_idx += 4;
|
||||
face_idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bsp->num_vertices = vertex_count;
|
||||
bsp->num_faces = face_count;
|
||||
bsp->num_planes = face_count;
|
||||
bsp->num_edges = vertex_count;
|
||||
bsp->num_surfedges = vertex_count;
|
||||
|
||||
bsp->leafs = calloc(1, sizeof(pxl8_bsp_leaf));
|
||||
bsp->marksurfaces = calloc(face_count, sizeof(u16));
|
||||
|
||||
if (!bsp->leafs || !bsp->marksurfaces) {
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
bsp->num_leafs = 1;
|
||||
bsp->num_marksurfaces = face_count;
|
||||
|
||||
bsp->leafs[0].first_marksurface = 0;
|
||||
bsp->leafs[0].num_marksurfaces = face_count;
|
||||
bsp->leafs[0].contents = -2;
|
||||
|
||||
for (i32 i = 0; i < face_count; i++) {
|
||||
bsp->marksurfaces[i] = i;
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static bool bounds_intersects(const pxl8_bounds* a, const pxl8_bounds* b) {
|
||||
return !(a->x + a->w <= b->x || b->x + b->w <= a->x ||
|
||||
a->y + a->h <= b->y || b->y + b->h <= a->y);
|
||||
}
|
||||
|
||||
static void carve_corridor_h(room_grid* grid, i32 x1, i32 x2, i32 y) {
|
||||
i32 start = (x1 < x2) ? x1 : x2;
|
||||
i32 end = (x1 > x2) ? x1 : x2;
|
||||
for (i32 x = start; x <= end; x++) {
|
||||
room_grid_set(grid, x, y, 0);
|
||||
room_grid_set(grid, x, y - 1, 0);
|
||||
room_grid_set(grid, x, y + 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void carve_corridor_v(room_grid* grid, i32 y1, i32 y2, i32 x) {
|
||||
i32 start = (y1 < y2) ? y1 : y2;
|
||||
i32 end = (y1 > y2) ? y1 : y2;
|
||||
for (i32 y = start; y <= end; y++) {
|
||||
room_grid_set(grid, x, y, 0);
|
||||
room_grid_set(grid, x - 1, y, 0);
|
||||
room_grid_set(grid, x + 1, y, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static pxl8_result procgen_rooms(pxl8_bsp* bsp, const pxl8_procgen_params* params) {
|
||||
pxl8_debug("procgen_rooms called: %dx%d, seed=%u, min=%d, max=%d, num=%d",
|
||||
params->width, params->height, params->seed,
|
||||
params->min_room_size, params->max_room_size, params->num_rooms);
|
||||
|
||||
pxl8_rng rng;
|
||||
pxl8_rng_seed(&rng, params->seed);
|
||||
|
||||
room_grid grid;
|
||||
if (!room_grid_init(&grid, params->width, params->height)) {
|
||||
pxl8_error("Failed to allocate room grid");
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
room_grid_fill(&grid, 1);
|
||||
|
||||
pxl8_bounds rooms[256];
|
||||
i32 room_count = 0;
|
||||
i32 max_attempts = params->num_rooms * 10;
|
||||
|
||||
for (i32 attempt = 0; attempt < max_attempts && room_count < params->num_rooms && room_count < 256; attempt++) {
|
||||
i32 w = params->min_room_size + (pxl8_rng_next(&rng) % (params->max_room_size - params->min_room_size + 1));
|
||||
i32 h = params->min_room_size + (pxl8_rng_next(&rng) % (params->max_room_size - params->min_room_size + 1));
|
||||
i32 x = 1 + (pxl8_rng_next(&rng) % (params->width - w - 2));
|
||||
i32 y = 1 + (pxl8_rng_next(&rng) % (params->height - h - 2));
|
||||
|
||||
pxl8_bounds new_room = {x, y, w, h};
|
||||
|
||||
bool overlaps = false;
|
||||
for (i32 i = 0; i < room_count; i++) {
|
||||
if (bounds_intersects(&new_room, &rooms[i])) {
|
||||
overlaps = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!overlaps) {
|
||||
for (i32 ry = y; ry < y + h; ry++) {
|
||||
for (i32 rx = x; rx < x + w; rx++) {
|
||||
room_grid_set(&grid, rx, ry, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (room_count > 0) {
|
||||
i32 new_cx = x + w / 2;
|
||||
i32 new_cy = y + h / 2;
|
||||
i32 prev_cx = rooms[room_count - 1].x + rooms[room_count - 1].w / 2;
|
||||
i32 prev_cy = rooms[room_count - 1].y + rooms[room_count - 1].h / 2;
|
||||
|
||||
if (pxl8_rng_next(&rng) % 2 == 0) {
|
||||
carve_corridor_h(&grid, prev_cx, new_cx, prev_cy);
|
||||
carve_corridor_v(&grid, prev_cy, new_cy, new_cx);
|
||||
} else {
|
||||
carve_corridor_v(&grid, prev_cy, new_cy, prev_cx);
|
||||
carve_corridor_h(&grid, prev_cx, new_cx, new_cy);
|
||||
}
|
||||
}
|
||||
|
||||
rooms[room_count++] = new_room;
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_debug("Room generation: %dx%d grid -> %d rooms created",
|
||||
params->width, params->height, room_count);
|
||||
|
||||
pxl8_result result = grid_to_bsp(bsp, &grid);
|
||||
free(grid.cells);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_procgen(pxl8_bsp* bsp, const pxl8_procgen_params* params) {
|
||||
if (!bsp || !params) {
|
||||
return PXL8_ERROR_NULL_POINTER;
|
||||
}
|
||||
|
||||
switch (params->type) {
|
||||
case PXL8_PROCGEN_ROOMS:
|
||||
return procgen_rooms(bsp, params);
|
||||
|
||||
case PXL8_PROCGEN_TERRAIN:
|
||||
pxl8_error("Terrain generation not yet implemented");
|
||||
return PXL8_ERROR_NOT_INITIALIZED;
|
||||
|
||||
default:
|
||||
pxl8_error("Unknown procgen type: %d", params->type);
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
}
|
||||
|
||||
static u32 hash2d(i32 x, i32 y) {
|
||||
u32 h = ((u32)x * 374761393u) + ((u32)y * 668265263u);
|
||||
h ^= h >> 13;
|
||||
h ^= h << 17;
|
||||
h ^= h >> 5;
|
||||
return h;
|
||||
}
|
||||
|
||||
void pxl8_procgen_tex(u8* buffer, const pxl8_procgen_tex_params* params) {
|
||||
if (!buffer || !params) return;
|
||||
|
||||
for (i32 y = 0; y < params->height; y++) {
|
||||
for (i32 x = 0; x < params->width; x++) {
|
||||
f32 u = (f32)x / (f32)params->width;
|
||||
f32 v = (f32)y / (f32)params->height;
|
||||
|
||||
u8 color = params->base_color;
|
||||
|
||||
// Tile-based pattern (floor style)
|
||||
if (params->seed == 11111) {
|
||||
i32 tile_x = (i32)floorf(u * 8.0f);
|
||||
i32 tile_y = (i32)floorf(v * 8.0f);
|
||||
u32 h = hash2d(tile_x, tile_y);
|
||||
|
||||
f32 pattern = (f32)(h & 0xFF) / 255.0f;
|
||||
i32 quantized = (pattern < 0.3f) ? 0 : (pattern < 0.7f) ? 1 : 2;
|
||||
|
||||
color = params->base_color + quantized;
|
||||
|
||||
// Checkerboard dither
|
||||
if (((tile_x + tile_y) & 1) == 0 && (h & 0x100)) {
|
||||
color = (color < 255) ? color + 1 : color;
|
||||
}
|
||||
}
|
||||
// Large tile pattern (ceiling style)
|
||||
else if (params->seed == 22222) {
|
||||
i32 coarse_x = (i32)floorf(u * 2.0f);
|
||||
i32 coarse_y = (i32)floorf(v * 2.0f);
|
||||
u32 coarse_h = hash2d(coarse_x, coarse_y);
|
||||
|
||||
i32 subdivision = (coarse_h >> 8) & 0x3;
|
||||
i32 tile_x, tile_y;
|
||||
|
||||
switch (subdivision) {
|
||||
case 0: tile_x = (i32)floorf(u * 3.0f); tile_y = (i32)floorf(v * 3.0f); break;
|
||||
case 1: tile_x = (i32)floorf(u * 5.0f); tile_y = (i32)floorf(v * 5.0f); break;
|
||||
case 2: tile_x = (i32)floorf(u * 2.0f); tile_y = (i32)floorf(v * 4.0f); break;
|
||||
default: tile_x = (i32)floorf(u * 4.0f); tile_y = (i32)floorf(v * 2.0f); break;
|
||||
}
|
||||
|
||||
u32 h = hash2d(tile_x, tile_y);
|
||||
f32 pattern = (f32)(h & 0xFF) / 255.0f;
|
||||
|
||||
if (pattern < 0.25f) color = params->base_color;
|
||||
else if (pattern < 0.50f) color = params->base_color + 1;
|
||||
else if (pattern < 0.75f) color = params->base_color + 2;
|
||||
else color = params->base_color + 3;
|
||||
}
|
||||
// Brick pattern (wall style)
|
||||
else {
|
||||
f32 brick_y = floorf(v * 4.0f);
|
||||
f32 offset = ((i32)brick_y & 1) ? 0.5f : 0.0f;
|
||||
i32 brick_x = (i32)floorf(u * 4.0f + offset);
|
||||
brick_y = (i32)brick_y;
|
||||
|
||||
f32 brick_u = fabsf((u * 4.0f + offset) - floorf(u * 4.0f + offset) - 0.5f);
|
||||
f32 brick_v = fabsf((v * 4.0f) - floorf(v * 4.0f) - 0.5f);
|
||||
|
||||
u32 h = hash2d(brick_x, (i32)brick_y);
|
||||
f32 noise = (f32)(h & 0xFF) / 255.0f;
|
||||
|
||||
// Mortar lines
|
||||
if (brick_u > 0.47f || brick_v > 0.47f) {
|
||||
color = params->base_color - 2;
|
||||
} else {
|
||||
i32 shade = (i32)(noise * 3.0f);
|
||||
color = params->base_color + shade;
|
||||
}
|
||||
}
|
||||
|
||||
buffer[y * params->width + x] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
45
src/world/pxl8_gen.h
Normal file
45
src/world/pxl8_gen.h
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_bsp.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef enum pxl8_procgen_type {
|
||||
PXL8_PROCGEN_ROOMS,
|
||||
PXL8_PROCGEN_TERRAIN
|
||||
} pxl8_procgen_type;
|
||||
|
||||
typedef struct pxl8_procgen_params {
|
||||
pxl8_procgen_type type;
|
||||
|
||||
i32 width;
|
||||
i32 height;
|
||||
i32 depth;
|
||||
u32 seed;
|
||||
|
||||
i32 min_room_size;
|
||||
i32 max_room_size;
|
||||
i32 num_rooms;
|
||||
} pxl8_procgen_params;
|
||||
|
||||
typedef struct pxl8_procgen_tex_params {
|
||||
char name[16];
|
||||
u32 seed;
|
||||
i32 width;
|
||||
i32 height;
|
||||
f32 scale;
|
||||
f32 roughness;
|
||||
u8 base_color;
|
||||
u8 variation;
|
||||
u8 max_color;
|
||||
} pxl8_procgen_tex_params;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_result pxl8_procgen(pxl8_bsp* bsp, const pxl8_procgen_params* params);
|
||||
void pxl8_procgen_tex(u8* buffer, const pxl8_procgen_tex_params* params);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
406
src/world/pxl8_world.c
Normal file
406
src/world/pxl8_world.c
Normal file
|
|
@ -0,0 +1,406 @@
|
|||
#include "pxl8_world.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_bsp.h"
|
||||
#include "pxl8_gen.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_math.h"
|
||||
|
||||
struct pxl8_world {
|
||||
pxl8_bsp bsp;
|
||||
bool loaded;
|
||||
bool wireframe;
|
||||
u32 wireframe_color;
|
||||
};
|
||||
|
||||
pxl8_world* pxl8_world_create(void) {
|
||||
pxl8_world* world = (pxl8_world*)calloc(1, sizeof(pxl8_world));
|
||||
if (!world) {
|
||||
pxl8_error("Failed to allocate world");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
world->loaded = false;
|
||||
world->wireframe_color = 15;
|
||||
|
||||
return world;
|
||||
}
|
||||
|
||||
void pxl8_world_destroy(pxl8_world* world) {
|
||||
if (!world) return;
|
||||
|
||||
if (world->loaded) {
|
||||
pxl8_bsp_destroy(&world->bsp);
|
||||
}
|
||||
|
||||
free(world);
|
||||
}
|
||||
|
||||
pxl8_result pxl8_world_generate(pxl8_world* world, pxl8_gfx* gfx, const pxl8_procgen_params* params) {
|
||||
if (!world || !gfx || !params) {
|
||||
pxl8_error("Invalid arguments to pxl8_world_generate");
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (world->loaded) {
|
||||
pxl8_bsp_destroy(&world->bsp);
|
||||
world->loaded = false;
|
||||
}
|
||||
|
||||
memset(&world->bsp, 0, sizeof(pxl8_bsp));
|
||||
|
||||
pxl8_result result = pxl8_procgen(&world->bsp, params);
|
||||
if (result != PXL8_OK) {
|
||||
pxl8_error("Failed to generate world: %d", result);
|
||||
pxl8_bsp_destroy(&world->bsp);
|
||||
return result;
|
||||
}
|
||||
|
||||
world->loaded = true;
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_world_load(pxl8_world* world, const char* path) {
|
||||
if (!world || !path) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
if (world->loaded) {
|
||||
pxl8_bsp_destroy(&world->bsp);
|
||||
world->loaded = false;
|
||||
}
|
||||
|
||||
memset(&world->bsp, 0, sizeof(pxl8_bsp));
|
||||
|
||||
pxl8_result result = pxl8_bsp_load(path, &world->bsp);
|
||||
if (result != PXL8_OK) {
|
||||
pxl8_error("Failed to load world: %s", path);
|
||||
return result;
|
||||
}
|
||||
|
||||
world->loaded = true;
|
||||
pxl8_info("Loaded world: %s", path);
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
void pxl8_world_unload(pxl8_world* world) {
|
||||
if (!world || !world->loaded) return;
|
||||
|
||||
pxl8_bsp_destroy(&world->bsp);
|
||||
world->loaded = false;
|
||||
}
|
||||
|
||||
pxl8_result pxl8_world_apply_textures(pxl8_world* world, const pxl8_world_texture* textures, u32 count) {
|
||||
if (!world || !world->loaded || !textures || count == 0) {
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
pxl8_bsp* bsp = &world->bsp;
|
||||
|
||||
u32 max_texinfo = count * 6;
|
||||
bsp->texinfo = calloc(max_texinfo, sizeof(pxl8_bsp_texinfo));
|
||||
if (!bsp->texinfo) {
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
bsp->num_texinfo = 0;
|
||||
|
||||
for (u32 face_idx = 0; face_idx < bsp->num_faces; face_idx++) {
|
||||
pxl8_bsp_face* face = &bsp->faces[face_idx];
|
||||
pxl8_vec3 normal = bsp->planes[face->plane_id].normal;
|
||||
|
||||
u32 matched_texture_idx = count;
|
||||
for (u32 tex_idx = 0; tex_idx < count; tex_idx++) {
|
||||
if (textures[tex_idx].rule && textures[tex_idx].rule(&normal, face, bsp)) {
|
||||
matched_texture_idx = tex_idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matched_texture_idx >= count) {
|
||||
pxl8_warn("No texture rule matched for face %u", face_idx);
|
||||
continue;
|
||||
}
|
||||
|
||||
const pxl8_world_texture* matched = &textures[matched_texture_idx];
|
||||
|
||||
pxl8_vec3 u_axis, v_axis;
|
||||
if (fabsf(normal.y) > 0.9f) {
|
||||
u_axis = (pxl8_vec3){1.0f, 0.0f, 0.0f};
|
||||
v_axis = (pxl8_vec3){0.0f, 0.0f, 1.0f};
|
||||
} else if (fabsf(normal.x) > 0.7f) {
|
||||
u_axis = (pxl8_vec3){0.0f, 1.0f, 0.0f};
|
||||
v_axis = (pxl8_vec3){0.0f, 0.0f, 1.0f};
|
||||
} else {
|
||||
u_axis = (pxl8_vec3){1.0f, 0.0f, 0.0f};
|
||||
v_axis = (pxl8_vec3){0.0f, 1.0f, 0.0f};
|
||||
}
|
||||
|
||||
u32 texinfo_idx = bsp->num_texinfo;
|
||||
bool found_existing = false;
|
||||
for (u32 i = 0; i < bsp->num_texinfo; i++) {
|
||||
if (strcmp(bsp->texinfo[i].name, matched->name) == 0 &&
|
||||
bsp->texinfo[i].miptex == matched->texture_id &&
|
||||
bsp->texinfo[i].u_axis.x == u_axis.x &&
|
||||
bsp->texinfo[i].u_axis.y == u_axis.y &&
|
||||
bsp->texinfo[i].u_axis.z == u_axis.z &&
|
||||
bsp->texinfo[i].v_axis.x == v_axis.x &&
|
||||
bsp->texinfo[i].v_axis.y == v_axis.y &&
|
||||
bsp->texinfo[i].v_axis.z == v_axis.z) {
|
||||
texinfo_idx = i;
|
||||
found_existing = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_existing) {
|
||||
if (bsp->num_texinfo >= max_texinfo) {
|
||||
pxl8_error("Too many unique texinfo entries");
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
memcpy(bsp->texinfo[texinfo_idx].name, matched->name, sizeof(bsp->texinfo[texinfo_idx].name));
|
||||
bsp->texinfo[texinfo_idx].name[sizeof(bsp->texinfo[texinfo_idx].name) - 1] = '\0';
|
||||
bsp->texinfo[texinfo_idx].miptex = matched->texture_id;
|
||||
bsp->texinfo[texinfo_idx].u_offset = 0.0f;
|
||||
bsp->texinfo[texinfo_idx].v_offset = 0.0f;
|
||||
bsp->texinfo[texinfo_idx].u_axis = u_axis;
|
||||
bsp->texinfo[texinfo_idx].v_axis = v_axis;
|
||||
|
||||
bsp->num_texinfo++;
|
||||
}
|
||||
|
||||
face->texinfo_id = texinfo_idx;
|
||||
}
|
||||
|
||||
pxl8_info("Applied %u textures to %u faces, created %u texinfo entries",
|
||||
count, bsp->num_faces, bsp->num_texinfo);
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, f32 radius) {
|
||||
if (!world || !world->loaded) return false;
|
||||
|
||||
const pxl8_bsp* bsp = &world->bsp;
|
||||
|
||||
for (u32 i = 0; i < bsp->num_faces; i++) {
|
||||
const pxl8_bsp_face* face = &bsp->faces[i];
|
||||
const pxl8_bsp_plane* plane = &bsp->planes[face->plane_id];
|
||||
|
||||
if (fabsf(plane->normal.y) > 0.7f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
f32 dist = plane->normal.x * pos.x +
|
||||
plane->normal.y * pos.y +
|
||||
plane->normal.z * pos.z - plane->dist;
|
||||
|
||||
if (fabsf(dist) > radius) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pxl8_vec3 closest_point = {
|
||||
pos.x - plane->normal.x * dist,
|
||||
pos.y - plane->normal.y * dist,
|
||||
pos.z - plane->normal.z * dist
|
||||
};
|
||||
|
||||
if (closest_point.x < face->aabb_min.x - radius || closest_point.x > face->aabb_max.x + radius ||
|
||||
closest_point.y < face->aabb_min.y - radius || closest_point.y > face->aabb_max.y + radius ||
|
||||
closest_point.z < face->aabb_min.z - radius || closest_point.z > face->aabb_max.z + radius) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool pxl8_world_is_loaded(const pxl8_world* world) {
|
||||
return world && world->loaded;
|
||||
}
|
||||
|
||||
pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius) {
|
||||
if (!world || !world->loaded) return to;
|
||||
|
||||
const pxl8_bsp* bsp = &world->bsp;
|
||||
pxl8_vec3 pos = to;
|
||||
pxl8_vec3 original_velocity = {to.x - from.x, to.y - from.y, to.z - from.z};
|
||||
|
||||
pxl8_vec3 clip_planes[5];
|
||||
u32 num_planes = 0;
|
||||
|
||||
const f32 edge_epsilon = 1.2f;
|
||||
const f32 radius_min = -radius + edge_epsilon;
|
||||
const f32 radius_max = radius - edge_epsilon;
|
||||
|
||||
for (i32 iteration = 0; iteration < 4; iteration++) {
|
||||
bool collided = false;
|
||||
|
||||
for (u32 i = 0; i < bsp->num_faces; i++) {
|
||||
const pxl8_bsp_face* face = &bsp->faces[i];
|
||||
const pxl8_bsp_plane* plane = &bsp->planes[face->plane_id];
|
||||
|
||||
if (fabsf(plane->normal.y) > 0.7f) continue;
|
||||
|
||||
f32 dist = plane->normal.x * pos.x +
|
||||
plane->normal.y * pos.y +
|
||||
plane->normal.z * pos.z - plane->dist;
|
||||
|
||||
f32 abs_dist = fabsf(dist);
|
||||
if (abs_dist > radius) continue;
|
||||
|
||||
pxl8_vec3 closest_point = {
|
||||
pos.x - plane->normal.x * dist,
|
||||
pos.y - plane->normal.y * dist,
|
||||
pos.z - plane->normal.z * dist
|
||||
};
|
||||
|
||||
if (closest_point.x < face->aabb_min.x + radius_min || closest_point.x > face->aabb_max.x + radius_max ||
|
||||
closest_point.y < face->aabb_min.y + radius_min || closest_point.y > face->aabb_max.y + radius_max ||
|
||||
closest_point.z < face->aabb_min.z + radius_min || closest_point.z > face->aabb_max.z + radius_max) {
|
||||
continue;
|
||||
}
|
||||
|
||||
f32 penetration = radius - abs_dist;
|
||||
if (penetration > 0.01f) {
|
||||
pxl8_vec3 push_dir;
|
||||
if (dist < 0) {
|
||||
push_dir.x = -plane->normal.x;
|
||||
push_dir.y = -plane->normal.y;
|
||||
push_dir.z = -plane->normal.z;
|
||||
} else {
|
||||
push_dir.x = plane->normal.x;
|
||||
push_dir.y = plane->normal.y;
|
||||
push_dir.z = plane->normal.z;
|
||||
}
|
||||
|
||||
bool is_new_plane = true;
|
||||
for (u32 p = 0; p < num_planes; p++) {
|
||||
if (pxl8_vec3_dot(push_dir, clip_planes[p]) > 0.99f) {
|
||||
is_new_plane = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_new_plane && num_planes < 5) {
|
||||
clip_planes[num_planes++] = push_dir;
|
||||
}
|
||||
|
||||
pos.x += push_dir.x * penetration;
|
||||
pos.y += push_dir.y * penetration;
|
||||
pos.z += push_dir.z * penetration;
|
||||
|
||||
collided = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!collided) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (num_planes >= 3) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (num_planes == 2) {
|
||||
f32 orig_vel_len_sq = original_velocity.x * original_velocity.x +
|
||||
original_velocity.y * original_velocity.y +
|
||||
original_velocity.z * original_velocity.z;
|
||||
|
||||
if (orig_vel_len_sq > 0.000001f) {
|
||||
f32 vdot0 = pxl8_vec3_dot(original_velocity, clip_planes[0]);
|
||||
f32 vdot1 = pxl8_vec3_dot(original_velocity, clip_planes[1]);
|
||||
f32 dot0 = fabsf(vdot0);
|
||||
f32 dot1 = fabsf(vdot1);
|
||||
|
||||
pxl8_vec3 slide_vel;
|
||||
if (dot0 < dot1) {
|
||||
slide_vel.x = original_velocity.x - clip_planes[0].x * vdot0;
|
||||
slide_vel.y = original_velocity.y - clip_planes[0].y * vdot0;
|
||||
slide_vel.z = original_velocity.z - clip_planes[0].z * vdot0;
|
||||
} else {
|
||||
slide_vel.x = original_velocity.x - clip_planes[1].x * vdot1;
|
||||
slide_vel.y = original_velocity.y - clip_planes[1].y * vdot1;
|
||||
slide_vel.z = original_velocity.z - clip_planes[1].z * vdot1;
|
||||
}
|
||||
|
||||
f32 slide_len = sqrtf(slide_vel.x * slide_vel.x + slide_vel.y * slide_vel.y + slide_vel.z * slide_vel.z);
|
||||
|
||||
if (slide_len > 0.01f) {
|
||||
pos.x += slide_vel.x;
|
||||
pos.y += slide_vel.y;
|
||||
pos.z += slide_vel.z;
|
||||
|
||||
pxl8_vec3 crease_dir = pxl8_vec3_cross(clip_planes[0], clip_planes[1]);
|
||||
f32 crease_len = pxl8_vec3_length(crease_dir);
|
||||
if (crease_len > 0.01f && fabsf(crease_dir.y / crease_len) > 0.9f) {
|
||||
f32 bias0 = pxl8_vec3_dot(original_velocity, clip_planes[0]);
|
||||
f32 bias1 = pxl8_vec3_dot(original_velocity, clip_planes[1]);
|
||||
|
||||
if (bias0 < 0 && bias1 < 0) {
|
||||
const f32 corner_push = 0.1f;
|
||||
pxl8_vec3 push_away = {
|
||||
clip_planes[0].x + clip_planes[1].x,
|
||||
clip_planes[0].y + clip_planes[1].y,
|
||||
clip_planes[0].z + clip_planes[1].z
|
||||
};
|
||||
f32 push_len_sq = push_away.x * push_away.x + push_away.y * push_away.y + push_away.z * push_away.z;
|
||||
if (push_len_sq > 0.000001f) {
|
||||
f32 inv_push_len = corner_push / sqrtf(push_len_sq);
|
||||
pos.x += push_away.x * inv_push_len;
|
||||
pos.y += push_away.y * inv_push_len;
|
||||
pos.z += push_away.z * inv_push_len;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
f32 horizontal_movement_sq = (pos.x - from.x) * (pos.x - from.x) +
|
||||
(pos.z - from.z) * (pos.z - from.z);
|
||||
f32 desired_horizontal_sq = (to.x - from.x) * (to.x - from.x) +
|
||||
(to.z - from.z) * (to.z - from.z);
|
||||
|
||||
if (desired_horizontal_sq > 0.01f && horizontal_movement_sq < desired_horizontal_sq * 0.25f) {
|
||||
const f32 max_step_height = 0.4f;
|
||||
|
||||
pxl8_vec3 step_up = pos;
|
||||
step_up.y += max_step_height;
|
||||
|
||||
if (!pxl8_world_check_collision(world, step_up, radius)) {
|
||||
pxl8_vec3 step_forward = {
|
||||
step_up.x + (to.x - pos.x),
|
||||
step_up.y,
|
||||
step_up.z + (to.z - pos.z)
|
||||
};
|
||||
|
||||
if (!pxl8_world_check_collision(world, step_forward, radius)) {
|
||||
pos = step_forward;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) {
|
||||
if (!world || !gfx || !world->loaded) {
|
||||
static int count = 0;
|
||||
if (count++ < 10) {
|
||||
pxl8_debug("world_render: early return - world=%p, gfx=%p, loaded=%d",
|
||||
(void*)world, (void*)gfx, world ? world->loaded : -1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (world->wireframe) {
|
||||
pxl8_bsp_render_wireframe(gfx, &world->bsp, camera_pos, world->wireframe_color);
|
||||
} else {
|
||||
pxl8_bsp_render_textured(gfx, &world->bsp, camera_pos);
|
||||
}
|
||||
}
|
||||
38
src/world/pxl8_world.h
Normal file
38
src/world/pxl8_world.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_bsp.h"
|
||||
#include "pxl8_gen.h"
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_math.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct pxl8_world pxl8_world;
|
||||
|
||||
typedef bool (*pxl8_texture_rule)(const pxl8_vec3* normal, const pxl8_bsp_face* face, const pxl8_bsp* bsp);
|
||||
|
||||
typedef struct pxl8_world_texture {
|
||||
char name[16];
|
||||
u32 texture_id;
|
||||
pxl8_texture_rule rule;
|
||||
} pxl8_world_texture;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
pxl8_world* pxl8_world_create(void);
|
||||
void pxl8_world_destroy(pxl8_world* world);
|
||||
|
||||
pxl8_result pxl8_world_generate(pxl8_world* world, pxl8_gfx* gfx, const pxl8_procgen_params* params);
|
||||
pxl8_result pxl8_world_load(pxl8_world* world, const char* path);
|
||||
void pxl8_world_unload(pxl8_world* world);
|
||||
|
||||
pxl8_result pxl8_world_apply_textures(pxl8_world* world, const pxl8_world_texture* textures, u32 count);
|
||||
bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, f32 radius);
|
||||
bool pxl8_world_is_loaded(const pxl8_world* world);
|
||||
void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);
|
||||
pxl8_vec3 pxl8_world_resolve_collision(const pxl8_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue