diff --git a/demo/mod/worldgen.fnl b/demo/mod/worldgen.fnl index ba3d29b..1884eef 100644 --- a/demo/mod/worldgen.fnl +++ b/demo/mod/worldgen.fnl @@ -18,6 +18,9 @@ (local turn-speed 2.0) (local bob-speed 8.0) (local bob-amount 4.0) +(local max-pitch 1.5) +(local cell-size 64) +(local grid-size 32) (fn init [] (set world (pxl8.world_new)) @@ -30,7 +33,37 @@ :iterations 4})] (if (< result 0) (pxl8.error (.. "Failed to generate cave - result: " result)) - (pxl8.info "Generated procedural cave")))) + (do + (let [floor-tex (pxl8.procgen_tex {:name "floor" + :seed 11111 + :width 64 + :height 64 + :base_color 20}) + ceiling-tex (pxl8.procgen_tex {:name "ceiling" + :seed 22222 + :width 64 + :height 64 + :base_color 0}) + wall-tex (pxl8.procgen_tex {:name "wall" + :seed 12345 + :width 64 + :height 64 + :base_color 4})] + + (pxl8.upload_atlas) + + (let [result (pxl8.world_apply_textures world [ + {:name "floor" + :texture_id floor-tex + :rule (fn [normal] (> normal.y 0.7))} + {:name "ceiling" + :texture_id ceiling-tex + :rule (fn [normal] (< normal.y -0.7))} + {:name "wall" + :texture_id wall-tex + :rule (fn [normal] (and (<= normal.y 0.7) (>= normal.y -0.7)))}])] + (when (< result 0) + (pxl8.error (.. "Failed to apply textures - result: " result))))))))) (fn update [dt] (set fps-accumulator (+ fps-accumulator dt)) @@ -50,36 +83,35 @@ forward-z (- (math.cos cam-yaw)) right-x (math.cos cam-yaw) right-z (- (math.sin cam-yaw)) - cell-size 64 - grid-min 0 - grid-max (* 32 cell-size)] + grid-max (* grid-size cell-size) + move-delta (* move-speed dt)] (var moving false) (var new-x cam-x) (var new-z cam-z) (when (pxl8.key_down "w") - (set new-x (+ new-x (* forward-x move-speed dt))) - (set new-z (+ new-z (* forward-z move-speed dt))) + (set new-x (+ new-x (* forward-x move-delta))) + (set new-z (+ new-z (* forward-z move-delta))) (set moving true)) (when (pxl8.key_down "s") - (set new-x (- new-x (* forward-x move-speed dt))) - (set new-z (- new-z (* forward-z move-speed dt))) + (set new-x (- new-x (* forward-x move-delta))) + (set new-z (- new-z (* forward-z move-delta))) (set moving true)) (when (pxl8.key_down "q") - (set new-x (- new-x (* right-x move-speed dt))) - (set new-z (- new-z (* right-z move-speed dt))) + (set new-x (- new-x (* right-x move-delta))) + (set new-z (- new-z (* right-z move-delta))) (set moving true)) (when (pxl8.key_down "e") - (set new-x (+ new-x (* right-x move-speed dt))) - (set new-z (+ new-z (* right-z move-speed dt))) + (set new-x (+ new-x (* right-x move-delta))) + (set new-z (+ new-z (* right-z move-delta))) (set moving true)) - (when (and (>= new-x grid-min) (<= new-x grid-max) - (>= new-z grid-min) (<= new-z grid-max)) + (when (and (>= new-x 0) (<= new-x grid-max) + (>= new-z 0) (<= new-z grid-max)) (set cam-x new-x) (set cam-z new-z)) @@ -90,12 +122,10 @@ (set cam-yaw (- cam-yaw (* turn-speed dt)))) (when (pxl8.key_down "up") - (set cam-pitch (+ cam-pitch (* turn-speed dt))) - (when (> cam-pitch 1.5) (set cam-pitch 1.5))) + (set cam-pitch (math.min max-pitch (+ cam-pitch (* turn-speed dt))))) (when (pxl8.key_down "down") - (set cam-pitch (- cam-pitch (* turn-speed dt))) - (when (< cam-pitch -1.5) (set cam-pitch -1.5))) + (set cam-pitch (math.max (- max-pitch) (- cam-pitch (* turn-speed dt))))) (if moving (set bob-time (+ bob-time (* dt bob-speed))) @@ -134,17 +164,17 @@ (pxl8.text (.. "Pos: " (string.format "%.0f" cam-x) "," (string.format "%.0f" cam-y) "," - (string.format "%.0f" cam-z)) 10 25 12)) + (string.format "%.0f" cam-z)) 10 25 12) - (let [new-state (debug-ui.render {:show-debug-ui show-debug-ui - :fps fps - :wireframe false - :auto-rotate false - :orthographic false - :use-texture true - :affine affine})] - (when (not= new-state.show-debug-ui nil) (set show-debug-ui new-state.show-debug-ui)) - (when (not= new-state.affine nil) (set affine new-state.affine))))) + (let [new-state (debug-ui.render {:show-debug-ui show-debug-ui + :fps fps + :wireframe false + :auto-rotate false + :orthographic false + :use-texture true + :affine affine})] + (when (not= new-state.show-debug-ui nil) (set show-debug-ui new-state.show-debug-ui)) + (when (not= new-state.affine nil) (set affine new-state.affine)))))) {:init init :update update diff --git a/src/lua/pxl8.lua b/src/lua/pxl8.lua index 65fddfe..5c39078 100644 --- a/src/lua/pxl8.lua +++ b/src/lua/pxl8.lua @@ -469,11 +469,64 @@ function pxl8.world_generate(world, params) c_params.density = params.density or 0.45 c_params.iterations = params.iterations or 4 c_params.type_params = nil - return C.pxl8_world_generate(world, c_params) + return C.pxl8_world_generate(world, gfx, c_params) end pxl8.PROCGEN_CAVE = C.PXL8_PROCGEN_CAVE pxl8.PROCGEN_DUNGEON = C.PXL8_PROCGEN_DUNGEON pxl8.PROCGEN_TERRAIN = C.PXL8_PROCGEN_TERRAIN +function pxl8.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(gfx, buffer, width, height) + if tex_id < 0 then + return nil + end + return tex_id +end + +function pxl8.world_apply_textures(world, texture_defs) + local count = #texture_defs + local textures = ffi.new("pxl8_world_texture[?]", count) + local callbacks = {} + + 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 + local cb = 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) + textures[idx].rule = cb + callbacks[idx + 1] = cb + else + textures[idx].rule = nil + end + end + + local result = C.pxl8_world_apply_textures(world, textures, count) + + return result +end + return pxl8 diff --git a/src/pxl8_bsp.c b/src/pxl8_bsp.c index e5284fa..2e249a9 100644 --- a/src/pxl8_bsp.c +++ b/src/pxl8_bsp.c @@ -386,17 +386,8 @@ void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 t pxl8_vec3 verts[64]; u32 num_verts = 0; - const pxl8_bsp_plane* plane = &bsp->planes[face->plane_id]; - - u32 color; - bool use_texture = false; - - if (fabsf(plane->normal.y) > 0.7f) { - color = (plane->normal.y > 0) ? 4 : 3; - } else { - color = 15; - use_texture = (texture_id > 0); - } + u32 color = 15; + bool use_texture = (texture_id > 0); for (u32 i = 0; i < face->num_edges && num_verts < 64; i++) { @@ -446,11 +437,10 @@ void pxl8_bsp_render_face(pxl8_gfx* gfx, const pxl8_bsp* bsp, u32 face_id, u32 t } } -void pxl8_bsp_render_solid( +void pxl8_bsp_render_textured( pxl8_gfx* gfx, const pxl8_bsp* bsp, - pxl8_vec3 camera_pos, - u32 texture_id + pxl8_vec3 camera_pos ) { if (!gfx || !bsp || bsp->num_faces == 0) return; @@ -488,10 +478,22 @@ void pxl8_bsp_render_solid( 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; + } else { + static bool warned = false; + if (!warned) { + pxl8_warn("Face %u has invalid texinfo_id %u (num_texinfo=%u)", + face_id, face->texinfo_id, bsp->num_texinfo); + warned = true; + } + } + pxl8_bsp_render_face(gfx, bsp, face_id, texture_id); } } - } void pxl8_bsp_render_wireframe( diff --git a/src/pxl8_bsp.h b/src/pxl8_bsp.h index 363d12b..1ed2801 100644 --- a/src/pxl8_bsp.h +++ b/src/pxl8_bsp.h @@ -20,6 +20,7 @@ typedef struct pxl8_bsp_plane { typedef struct pxl8_bsp_texinfo { u32 miptex; + char name[16]; f32 u_offset; pxl8_vec3 u_axis; @@ -121,11 +122,10 @@ void pxl8_bsp_render_face( u32 texture_id ); -void pxl8_bsp_render_solid( +void pxl8_bsp_render_textured( pxl8_gfx* gfx, const pxl8_bsp* bsp, - pxl8_vec3 camera_pos, - u32 texture_id + pxl8_vec3 camera_pos ); void pxl8_bsp_render_wireframe( diff --git a/src/pxl8_gfx.c b/src/pxl8_gfx.c index 8a8aa13..4695fdd 100644 --- a/src/pxl8_gfx.c +++ b/src/pxl8_gfx.c @@ -352,7 +352,7 @@ pxl8_result pxl8_gfx_load_palette(pxl8_gfx* gfx, const char* path) { pxl8_ase_destroy(&ase_file); pxl8_debug("Loaded palette with %u colors", copy_size); - + return PXL8_OK; } @@ -992,7 +992,15 @@ static inline u32 pxl8_sample_texture(pxl8_gfx* gfx, u32 texture_id, f32 u, f32 if (!gfx->atlas) return 0; const pxl8_atlas_entry* entry = pxl8_atlas_get_entry(gfx->atlas, texture_id); - if (!entry || !entry->active) return 0; + if (!entry || !entry->active) { + static bool warned = false; + if (!warned) { + pxl8_warn("Texture sampling failed: texture_id=%u entry=%p active=%d", + texture_id, (void*)entry, entry ? entry->active : 0); + warned = true; + } + return 0; + } u = u - floorf(u); v = v - floorf(v); diff --git a/src/pxl8_gfx.h b/src/pxl8_gfx.h index 933d108..ac8cfb4 100644 --- a/src/pxl8_gfx.h +++ b/src/pxl8_gfx.h @@ -14,14 +14,6 @@ typedef enum pxl8_blend_mode { PXL8_BLEND_NONE } pxl8_blend_mode; -typedef struct pxl8_mode7_params { - bool active; - f32 horizon; - f32 offset_x, offset_y; - f32 rotation; - f32 scale_x, scale_y; -} pxl8_mode7_params; - typedef struct pxl8_palette_cycle { bool active; u8 end_index; diff --git a/src/pxl8_procgen.c b/src/pxl8_procgen.c index b91f9cf..0b848bf 100644 --- a/src/pxl8_procgen.c +++ b/src/pxl8_procgen.c @@ -78,19 +78,6 @@ static i32 cave_grid_count_neighbors(const cave_grid* grid, i32 x, i32 y) { return count; } -static void calculate_texture_axes(const pxl8_vec3 normal, pxl8_vec3* u_axis, pxl8_vec3* 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}; - } -} - 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}; @@ -159,12 +146,14 @@ static pxl8_result cave_to_bsp(pxl8_bsp* bsp, const cave_grid* grid) { 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)); - bsp->texinfo = calloc(face_count, sizeof(pxl8_bsp_texinfo)); - if (!bsp->vertices || !bsp->faces || !bsp->planes || !bsp->edges || !bsp->surfedges || !bsp->texinfo) { + 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; @@ -190,13 +179,7 @@ static pxl8_result cave_to_bsp(pxl8_bsp* bsp, const cave_grid* grid) { 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 = face_idx; - - calculate_texture_axes(bsp->planes[face_idx].normal, - &bsp->texinfo[face_idx].u_axis, - &bsp->texinfo[face_idx].v_axis); - bsp->texinfo[face_idx].u_offset = 0.0f; - bsp->texinfo[face_idx].v_offset = 0.0f; + bsp->faces[face_idx].texinfo_id = 0; for (i32 i = 0; i < 4; i++) { bsp->edges[edge_idx + i].vertex[0] = vert_idx + i; @@ -223,13 +206,7 @@ static pxl8_result cave_to_bsp(pxl8_bsp* bsp, const cave_grid* grid) { 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 = face_idx; - - calculate_texture_axes(bsp->planes[face_idx].normal, - &bsp->texinfo[face_idx].u_axis, - &bsp->texinfo[face_idx].v_axis); - bsp->texinfo[face_idx].u_offset = 0.0f; - bsp->texinfo[face_idx].v_offset = 0.0f; + bsp->faces[face_idx].texinfo_id = 0; for (i32 i = 0; i < 4; i++) { bsp->edges[edge_idx + i].vertex[0] = vert_idx + i; @@ -256,13 +233,7 @@ static pxl8_result cave_to_bsp(pxl8_bsp* bsp, const cave_grid* grid) { 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 = face_idx; - - calculate_texture_axes(bsp->planes[face_idx].normal, - &bsp->texinfo[face_idx].u_axis, - &bsp->texinfo[face_idx].v_axis); - bsp->texinfo[face_idx].u_offset = 0.0f; - bsp->texinfo[face_idx].v_offset = 0.0f; + bsp->faces[face_idx].texinfo_id = 0; for (i32 i = 0; i < 4; i++) { bsp->edges[edge_idx + i].vertex[0] = vert_idx + i; @@ -289,13 +260,7 @@ static pxl8_result cave_to_bsp(pxl8_bsp* bsp, const cave_grid* grid) { 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 = face_idx; - - calculate_texture_axes(bsp->planes[face_idx].normal, - &bsp->texinfo[face_idx].u_axis, - &bsp->texinfo[face_idx].v_axis); - bsp->texinfo[face_idx].u_offset = 0.0f; - bsp->texinfo[face_idx].v_offset = 0.0f; + bsp->faces[face_idx].texinfo_id = 0; for (i32 i = 0; i < 4; i++) { bsp->edges[edge_idx + i].vertex[0] = vert_idx + i; @@ -330,13 +295,7 @@ static pxl8_result cave_to_bsp(pxl8_bsp* bsp, const cave_grid* grid) { 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 = face_idx; - - calculate_texture_axes(bsp->planes[face_idx].normal, - &bsp->texinfo[face_idx].u_axis, - &bsp->texinfo[face_idx].v_axis); - bsp->texinfo[face_idx].u_offset = 0.0f; - bsp->texinfo[face_idx].v_offset = 0.0f; + bsp->faces[face_idx].texinfo_id = 0; for (i32 i = 0; i < 4; i++) { bsp->edges[edge_idx + i].vertex[0] = vert_idx + i; @@ -361,13 +320,7 @@ static pxl8_result cave_to_bsp(pxl8_bsp* bsp, const cave_grid* grid) { 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 = face_idx; - - calculate_texture_axes(bsp->planes[face_idx].normal, - &bsp->texinfo[face_idx].u_axis, - &bsp->texinfo[face_idx].v_axis); - bsp->texinfo[face_idx].u_offset = 0.0f; - bsp->texinfo[face_idx].v_offset = 0.0f; + bsp->faces[face_idx].texinfo_id = 0; for (i32 i = 0; i < 4; i++) { bsp->edges[edge_idx + i].vertex[0] = vert_idx + i; @@ -389,7 +342,6 @@ static pxl8_result cave_to_bsp(pxl8_bsp* bsp, const cave_grid* grid) { bsp->num_planes = face_count; bsp->num_edges = vertex_count; bsp->num_surfedges = vertex_count; - bsp->num_texinfo = face_count; bsp->leafs = calloc(1, sizeof(pxl8_bsp_leaf)); bsp->marksurfaces = calloc(face_count, sizeof(u16)); @@ -455,45 +407,99 @@ pxl8_result pxl8_procgen(pxl8_bsp* bsp, const pxl8_procgen_params* params) { } } +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); - u8 min_val = 255, max_val = 0; - for (i32 y = 0; y < params->height; y++) { for (i32 x = 0; x < params->width; x++) { - i32 ix = (i32)((f32)x * params->scale); - i32 iy = (i32)((f32)y * params->scale); + f32 u = (f32)x / (f32)params->width; + f32 v = (f32)y / (f32)params->height; - u32 block_hash = ((u32)ix * 374761393u + (u32)iy * 668265263u) ^ params->seed; - block_hash ^= block_hash >> 13; - block_hash ^= block_hash << 17; - block_hash ^= block_hash >> 5; + u8 color = params->base_color; - u32 pixel_hash = ((u32)x * 1597334677u + (u32)y * 3812015801u) ^ params->seed; - pixel_hash ^= pixel_hash >> 13; - pixel_hash ^= pixel_hash << 17; - pixel_hash ^= pixel_hash >> 5; + // 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); - u32 combined = (block_hash * 3 + pixel_hash) / 4; + f32 pattern = (f32)(h & 0xFF) / 255.0f; + i32 quantized = (pattern < 0.3f) ? 0 : (pattern < 0.7f) ? 1 : 2; - u32 value_range = params->variation + 1; - i32 value = params->base_color + (combined % value_range); + color = params->base_color + quantized; - if (value < 0) value = 0; - if (value > 15) value = 15; + // Checkerboard dither + if (((tile_x + tile_y) & 1) == 0 && (h & 0x100)) { + color = (color < 15) ? 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); - u8 final_value = (u8)value; - buffer[y * params->width + x] = final_value; + i32 subdivision = (coarse_h >> 8) & 0x3; + i32 tile_x, tile_y; - if (final_value < min_val) min_val = final_value; - if (final_value > max_val) max_val = final_value; + 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; + } + } + + if (color > 31) color = 31; + buffer[y * params->width + x] = color; } } - pxl8_debug("Generated texture %dx%d: values range %u-%u (base=%u, variation=%u)", - params->width, params->height, min_val, max_val, - params->base_color, params->variation); + u8 min_color = 255, max_color = 0; + u32 color_counts[256] = {0}; + for (i32 i = 0; i < params->width * params->height; i++) { + if (buffer[i] < min_color) min_color = buffer[i]; + if (buffer[i] > max_color) max_color = buffer[i]; + color_counts[buffer[i]]++; + } + } diff --git a/src/pxl8_procgen.h b/src/pxl8_procgen.h index 9ee1515..01fc15a 100644 --- a/src/pxl8_procgen.h +++ b/src/pxl8_procgen.h @@ -35,6 +35,7 @@ typedef struct pxl8_procgen_dungeon_params { } pxl8_procgen_dungeon_params; typedef struct pxl8_procgen_tex_params { + char name[16]; u32 seed; i32 width; i32 height; @@ -42,6 +43,7 @@ typedef struct pxl8_procgen_tex_params { f32 roughness; u8 base_color; u8 variation; + u8 max_color; } pxl8_procgen_tex_params; #ifdef __cplusplus diff --git a/src/pxl8_script.c b/src/pxl8_script.c index df3de4a..a391f8e 100644 --- a/src/pxl8_script.c +++ b/src/pxl8_script.c @@ -225,10 +225,34 @@ static const char* pxl8_ffi_cdefs = " void* type_params;\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, const pxl8_procgen_params* params);\n" +"int pxl8_world_generate(pxl8_world* world, pxl8_gfx* gfx, const pxl8_procgen_params* params);\n" +"int pxl8_world_apply_textures(pxl8_world* world, const pxl8_world_texture* textures, unsigned int count);\n" "bool pxl8_world_is_loaded(const pxl8_world* world);\n" "int pxl8_world_load(pxl8_world* world, const char* path);\n" "void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);\n" @@ -692,7 +716,7 @@ pxl8_result pxl8_script_load_main(pxl8_script* script, const char* path) { if (result == PXL8_OK) { pxl8_info("Loaded script: %s", path); } else { - pxl8_warn("Failed to load script: %s", script->last_error); + pxl8_error("Failed to load script: %s", script->last_error); } return result; diff --git a/src/pxl8_world.c b/src/pxl8_world.c index dd0ca41..942974a 100644 --- a/src/pxl8_world.c +++ b/src/pxl8_world.c @@ -1,3 +1,4 @@ +#include #include #include @@ -36,8 +37,8 @@ void pxl8_world_destroy(pxl8_world* world) { free(world); } -pxl8_result pxl8_world_generate(pxl8_world* world, const pxl8_procgen_params* params) { - if (!world || !params) return PXL8_ERROR_INVALID_ARGUMENT; +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; if (world->loaded) { pxl8_bsp_destroy(&world->bsp); @@ -54,7 +55,93 @@ pxl8_result pxl8_world_generate(pxl8_world* world, const pxl8_procgen_params* pa } world->loaded = true; - pxl8_info("Generated world"); + return PXL8_OK; +} + +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; } @@ -84,38 +171,10 @@ pxl8_result pxl8_world_load(pxl8_world* world, const char* path) { void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) { if (!world || !gfx || !world->loaded) return; - static bool texture_generated = false; - static u32 wall_texture_id = 0; - if (!texture_generated) { - u8 texture_data[64 * 64]; - - pxl8_procgen_tex_params tex_params = { - .seed = 12345, - .width = 64, - .height = 64, - .scale = 0.25f, - .roughness = 0.5f, - .base_color = 1, - .variation = 3 - }; - - pxl8_procgen_tex(texture_data, &tex_params); - - pxl8_result result = pxl8_gfx_create_texture(gfx, texture_data, 64, 64); - if (result >= 0) { - wall_texture_id = (u32)result; - pxl8_gfx_upload_atlas(gfx); - texture_generated = true; - pxl8_info("Generated stone texture with ID: %u", wall_texture_id); - } else { - pxl8_error("Failed to create wall texture: %d", result); - } - } - if (world->wireframe) { pxl8_bsp_render_wireframe(gfx, &world->bsp, camera_pos, world->wireframe_color); } else { - pxl8_bsp_render_solid(gfx, &world->bsp, camera_pos, wall_texture_id); + pxl8_bsp_render_textured(gfx, &world->bsp, camera_pos); } } diff --git a/src/pxl8_world.h b/src/pxl8_world.h index 15289c1..0f5d2f0 100644 --- a/src/pxl8_world.h +++ b/src/pxl8_world.h @@ -7,6 +7,14 @@ 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 @@ -14,7 +22,8 @@ extern "C" { pxl8_world* pxl8_world_create(void); void pxl8_world_destroy(pxl8_world* world); -pxl8_result pxl8_world_generate(pxl8_world* world, const pxl8_procgen_params* params); +pxl8_result pxl8_world_generate(pxl8_world* world, pxl8_gfx* gfx, const pxl8_procgen_params* params); +pxl8_result pxl8_world_apply_textures(pxl8_world* world, const pxl8_world_texture* textures, u32 count); bool pxl8_world_is_loaded(const pxl8_world* world); pxl8_result pxl8_world_load(pxl8_world* world, const char* path); void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);