diff --git a/demo/main.fnl b/demo/main.fnl index 51543c1..7c025af 100644 --- a/demo/main.fnl +++ b/demo/main.fnl @@ -26,11 +26,11 @@ (pxl8.transition_start transition))) (global init (fn [] - (cube3d.init) - (worldgen.init) (pxl8.load_palette "res/sprites/pxl8_logo.ase") (set logo-sprite (pxl8.load_sprite "res/sprites/pxl8_logo.ase")) - (set particles (pxl8.particles_new 1000)))) + (set particles (pxl8.particles_new 1000)) + (cube3d.init) + (worldgen.init))) (global update (fn [dt] (set time (+ time dt)) diff --git a/demo/mod/worldgen.fnl b/demo/mod/worldgen.fnl index a99f0c3..b05d013 100644 --- a/demo/mod/worldgen.fnl +++ b/demo/mod/worldgen.fnl @@ -10,7 +10,7 @@ (var cam-z 1000) (local cell-size 64) (local gravity -800) -(local grid-size 32) +(local grid-size 64) (var grounded? true) (local ground-y 64) (local jump-force 175) @@ -24,18 +24,20 @@ (local turn-speed 2.0) (var velocity-y 0) (var world nil) +(var auto-run? false) (fn init [] (set world (pxl8.world_new)) (let [result (pxl8.world_generate world { - :type pxl8.PROCGEN_CAVE - :width 32 - :height 32 + :type pxl8.PROCGEN_ROOMS + :width 64 + :height 64 :seed 42 - :density 0.45 - :iterations 4})] + :min_room_size 5 + :max_room_size 10 + :num_rooms 20})] (if (< result 0) - (pxl8.error (.. "Failed to generate cave - result: " result)) + (pxl8.error (.. "Failed to generate rooms - result: " result)) (let [floor-tex (pxl8.procgen_tex {:name "floor" :seed 11111 :width 64 @@ -73,6 +75,9 @@ (when (pxl8.key_pressed "escape") (set mouse-look? (not mouse-look?))) + (when (pxl8.key_pressed "`") + (set auto-run? (not auto-run?))) + (when (pxl8.world_is_loaded world) (let [forward-x (- (math.sin cam-yaw)) forward-z (- (math.cos cam-yaw)) @@ -85,7 +90,7 @@ (var move-forward 0) (var move-right 0) - (when (pxl8.key_down "w") + (when (or (pxl8.key_down "w") auto-run?) (set move-forward (+ move-forward 1))) (when (pxl8.key_down "s") diff --git a/src/lua/pxl8.lua b/src/lua/pxl8.lua index 3e6a97b..c1174a2 100644 --- a/src/lua/pxl8.lua +++ b/src/lua/pxl8.lua @@ -135,8 +135,7 @@ pxl8.world_is_loaded = world.is_loaded pxl8.world_generate = world.generate pxl8.world_apply_textures = world.apply_textures pxl8.procgen_tex = world.procgen_tex -pxl8.PROCGEN_CAVE = world.PROCGEN_CAVE -pxl8.PROCGEN_DUNGEON = world.PROCGEN_DUNGEON +pxl8.PROCGEN_ROOMS = world.PROCGEN_ROOMS pxl8.PROCGEN_TERRAIN = world.PROCGEN_TERRAIN pxl8.transition_create = transition.create diff --git a/src/lua/pxl8/world.lua b/src/lua/pxl8/world.lua index c0b7296..f86e4f3 100644 --- a/src/lua/pxl8/world.lua +++ b/src/lua/pxl8/world.lua @@ -4,8 +4,7 @@ local core = require("pxl8.core") local world = {} -world.PROCGEN_CAVE = C.PXL8_PROCGEN_CAVE -world.PROCGEN_DUNGEON = C.PXL8_PROCGEN_DUNGEON +world.PROCGEN_ROOMS = C.PXL8_PROCGEN_ROOMS world.PROCGEN_TERRAIN = C.PXL8_PROCGEN_TERRAIN function world.new() @@ -35,14 +34,14 @@ end function world.generate(w, params) local c_params = ffi.new("pxl8_procgen_params") - c_params.type = params.type or C.PXL8_PROCGEN_CAVE + 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.density = params.density or 0.45 - c_params.iterations = params.iterations or 4 - c_params.type_params = nil + 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(w, core.gfx, c_params) end diff --git a/src/pxl8_io.c b/src/pxl8_io.c index 7d63133..75d515c 100644 --- a/src/pxl8_io.c +++ b/src/pxl8_io.c @@ -116,7 +116,7 @@ static i32 pxl8_key_code(const char* key_name) { {"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}, + {"-", 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}, diff --git a/src/pxl8_procgen.c b/src/pxl8_procgen.c index 1c6ce2a..96f5e01 100644 --- a/src/pxl8_procgen.c +++ b/src/pxl8_procgen.c @@ -5,11 +5,11 @@ #include "pxl8_macros.h" -typedef struct cave_grid { +typedef struct room_grid { u8* cells; i32 width; i32 height; -} cave_grid; +} room_grid; static u32 prng_state = 0; @@ -24,11 +24,7 @@ static u32 prng_next(void) { return prng_state; } -static f32 prng_float(void) { - return (f32)prng_next() / (f32)0xFFFFFFFF; -} - -static bool cave_grid_init(cave_grid* grid, i32 width, i32 height) { +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)); @@ -36,33 +32,20 @@ static bool cave_grid_init(cave_grid* grid, i32 width, i32 height) { return grid->cells != NULL; } -static u8 cave_grid_get(const cave_grid* grid, i32 x, i32 y) { +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 cave_grid_set(cave_grid* grid, i32 x, i32 y, u8 value) { +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 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}; @@ -78,43 +61,26 @@ static inline void compute_face_aabb(pxl8_bsp_face* face, const pxl8_bsp_vertex* } } -static void cave_grid_initialize(cave_grid* grid, f32 density) { +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++) { - u8 value = (prng_float() < density) ? 1 : 0; - cave_grid_set(grid, x, y, value); + room_grid_set(grid, x, y, value); } } } -static void cave_grid_smooth(cave_grid* grid) { - cave_grid temp; - if (!cave_grid_init(&temp, grid->width, grid->height)) 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); - free(temp.cells); -} - -static pxl8_result cave_to_bsp(pxl8_bsp* bsp, const cave_grid* grid) { +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 (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++; + 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++; } } @@ -148,11 +114,11 @@ static pxl8_result cave_to_bsp(pxl8_bsp* bsp, const cave_grid* grid) { 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 (room_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) { + 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}; @@ -179,7 +145,7 @@ static pxl8_result cave_to_bsp(pxl8_bsp* bsp, const cave_grid* grid) { face_idx++; } - if (cave_grid_get(grid, x + 1, y) == 1) { + 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}; @@ -206,7 +172,7 @@ static pxl8_result cave_to_bsp(pxl8_bsp* bsp, const cave_grid* grid) { face_idx++; } - if (cave_grid_get(grid, x, y - 1) == 1) { + 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}; @@ -233,7 +199,7 @@ static pxl8_result cave_to_bsp(pxl8_bsp* bsp, const cave_grid* grid) { face_idx++; } - if (cave_grid_get(grid, x, y + 1) == 1) { + 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}; @@ -265,7 +231,7 @@ static pxl8_result cave_to_bsp(pxl8_bsp* bsp, const cave_grid* grid) { 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 (room_grid_get(grid, x, y) == 0) { f32 fx = (f32)x * cell_size; f32 fy = (f32)y * cell_size; @@ -349,21 +315,96 @@ static pxl8_result cave_to_bsp(pxl8_bsp* bsp, const cave_grid* grid) { return PXL8_OK; } -static pxl8_result procgen_cave(pxl8_bsp* bsp, const pxl8_procgen_params* params) { +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); + prng_seed(params->seed); - cave_grid grid; - if (!cave_grid_init(&grid, params->width, params->height)) { + 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; } - cave_grid_initialize(&grid, params->density); + room_grid_fill(&grid, 1); - for (i32 i = 0; i < params->iterations; i++) { - cave_grid_smooth(&grid); + 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 + (prng_next() % (params->max_room_size - params->min_room_size + 1)); + i32 h = params->min_room_size + (prng_next() % (params->max_room_size - params->min_room_size + 1)); + i32 x = 1 + (prng_next() % (params->width - w - 2)); + i32 y = 1 + (prng_next() % (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 (prng_next() % 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_result result = cave_to_bsp(bsp, &grid); + 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; @@ -375,12 +416,8 @@ pxl8_result pxl8_procgen(pxl8_bsp* bsp, const pxl8_procgen_params* params) { } 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_ROOMS: + return procgen_rooms(bsp, params); case PXL8_PROCGEN_TERRAIN: pxl8_error("Terrain generation not yet implemented"); diff --git a/src/pxl8_procgen.h b/src/pxl8_procgen.h index 01fc15a..a887eb1 100644 --- a/src/pxl8_procgen.h +++ b/src/pxl8_procgen.h @@ -4,8 +4,7 @@ #include "pxl8_types.h" typedef enum pxl8_procgen_type { - PXL8_PROCGEN_CAVE, - PXL8_PROCGEN_DUNGEON, + PXL8_PROCGEN_ROOMS, PXL8_PROCGEN_TERRAIN } pxl8_procgen_type; @@ -17,22 +16,10 @@ typedef struct pxl8_procgen_params { i32 depth; u32 seed; - f32 density; - i32 iterations; - - void* type_params; -} pxl8_procgen_params; - -typedef struct pxl8_procgen_cave_params { - i32 min_cave_size; -} pxl8_procgen_cave_params; - -typedef struct pxl8_procgen_dungeon_params { - i32 room_count; i32 min_room_size; i32 max_room_size; - i32 corridor_width; -} pxl8_procgen_dungeon_params; + i32 num_rooms; +} pxl8_procgen_params; typedef struct pxl8_procgen_tex_params { char name[16]; diff --git a/src/pxl8_script.c b/src/pxl8_script.c index 36d56c1..260a268 100644 --- a/src/pxl8_script.c +++ b/src/pxl8_script.c @@ -255,9 +255,8 @@ static const char* pxl8_ffi_cdefs = "pxl8_mat4 pxl8_mat4_translate(float x, float y, float z);\n" "\n" "typedef enum pxl8_procgen_type {\n" -" PXL8_PROCGEN_CAVE = 0,\n" -" PXL8_PROCGEN_DUNGEON = 1,\n" -" PXL8_PROCGEN_TERRAIN = 2\n" +" PXL8_PROCGEN_ROOMS = 0,\n" +" PXL8_PROCGEN_TERRAIN = 1\n" "} pxl8_procgen_type;\n" "\n" "typedef struct pxl8_procgen_params {\n" @@ -266,9 +265,9 @@ static const char* pxl8_ffi_cdefs = " int height;\n" " int depth;\n" " unsigned int seed;\n" -" float density;\n" -" int iterations;\n" -" void* type_params;\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" diff --git a/src/pxl8_world.c b/src/pxl8_world.c index 23a610f..38047f2 100644 --- a/src/pxl8_world.c +++ b/src/pxl8_world.c @@ -38,7 +38,12 @@ void pxl8_world_destroy(pxl8_world* world) { } pxl8_result pxl8_world_generate(pxl8_world* world, pxl8_gfx* gfx, const pxl8_procgen_params* params) { - if (!world || !gfx || !params) return PXL8_ERROR_INVALID_ARGUMENT; + pxl8_debug("pxl8_world_generate called"); + + 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); @@ -49,11 +54,12 @@ pxl8_result pxl8_world_generate(pxl8_world* world, pxl8_gfx* gfx, const pxl8_pro pxl8_result result = pxl8_procgen(&world->bsp, params); if (result != PXL8_OK) { - pxl8_error("Failed to generate world"); + pxl8_error("Failed to generate world: %d", result); pxl8_bsp_destroy(&world->bsp); return result; } + pxl8_debug("World generation succeeded, setting loaded=true"); world->loaded = true; return PXL8_OK; }