#include "pxl8_procgen.h" #include "pxl8_macros.h" #include #include #include typedef struct cave_grid { u8* cells; i32 width; i32 height; } cave_grid; static u32 prng_state = 0; static void prng_seed(u32 seed) { prng_state = seed; } static u32 prng_next(void) { prng_state ^= prng_state << 13; prng_state ^= prng_state >> 17; prng_state ^= prng_state << 5; return prng_state; } static f32 prng_float(void) { return (f32)prng_next() / (f32)0xFFFFFFFF; } static cave_grid* cave_grid_create(i32 width, i32 height) { cave_grid* grid = malloc(sizeof(cave_grid)); if (!grid) return NULL; grid->width = width; grid->height = height; grid->cells = calloc(width * height, sizeof(u8)); if (!grid->cells) { free(grid); return NULL; } return grid; } static void cave_grid_destroy(cave_grid* grid) { if (!grid) return; free(grid->cells); free(grid); } static u8 cave_grid_get(const cave_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 cave_grid_set(cave_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 i32 cave_grid_count_neighbors(const cave_grid* grid, i32 x, i32 y) { i32 count = 0; for (i32 dy = -1; dy <= 1; dy++) { for (i32 dx = -1; dx <= 1; dx++) { if (dx == 0 && dy == 0) continue; if (cave_grid_get(grid, x + dx, y + dy)) { count++; } } } return count; } 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 cave_grid_initialize(cave_grid* grid, f32 density) { for (i32 y = 0; y < grid->height; y++) { for (i32 x = 0; x < grid->width; x++) { u8 value = (prng_float() < density) ? 1 : 0; cave_grid_set(grid, x, y, value); } } } static void cave_grid_smooth(cave_grid* grid) { cave_grid* temp = cave_grid_create(grid->width, grid->height); if (!temp) return; for (i32 y = 0; y < grid->height; y++) { for (i32 x = 0; x < grid->width; x++) { i32 neighbors = cave_grid_count_neighbors(grid, x, y); u8 value = (neighbors > 4) ? 1 : 0; cave_grid_set(temp, x, y, value); } } memcpy(grid->cells, temp->cells, grid->width * grid->height); cave_grid_destroy(temp); } static pxl8_result cave_to_bsp(pxl8_bsp* bsp, const cave_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 (cave_grid_get(grid, x, y) == 0) { if (cave_grid_get(grid, x - 1, y) == 1) face_count++; if (cave_grid_get(grid, x + 1, y) == 1) face_count++; if (cave_grid_get(grid, x, y - 1) == 1) face_count++; if (cave_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 (cave_grid_get(grid, x, y) == 0) { f32 fx = (f32)x * cell_size; f32 fy = (f32)y * cell_size; if (cave_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 (cave_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 (cave_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 (cave_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 (cave_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 pxl8_result procgen_cave(pxl8_bsp* bsp, const pxl8_procgen_params* params) { prng_seed(params->seed); cave_grid* grid = cave_grid_create(params->width, params->height); if (!grid) { return PXL8_ERROR_OUT_OF_MEMORY; } cave_grid_initialize(grid, params->density); for (i32 i = 0; i < params->iterations; i++) { cave_grid_smooth(grid); } pxl8_result result = cave_to_bsp(bsp, grid); cave_grid_destroy(grid); 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_CAVE: return procgen_cave(bsp, params); case PXL8_PROCGEN_DUNGEON: pxl8_error("Dungeon generation not yet implemented"); return PXL8_ERROR_NOT_INITIALIZED; 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; prng_seed(params->seed); 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 + 1; } // 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; } } }