#include "pxl8_world.h" #include #include #include "pxl8_bsp.h" #include "pxl8_macros.h" #include "pxl8_procgen.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; } static inline f32 vec3_dot(pxl8_vec3 a, pxl8_vec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; } static inline pxl8_vec3 vec3_cross(pxl8_vec3 a, pxl8_vec3 b) { return (pxl8_vec3){ a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x }; } static inline f32 vec3_length(pxl8_vec3 v) { return sqrtf(v.x * v.x + v.y * v.y + v.z * v.z); } 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 (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 = vec3_dot(original_velocity, clip_planes[0]); f32 vdot1 = 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 = vec3_cross(clip_planes[0], clip_planes[1]); f32 crease_len = vec3_length(crease_dir); if (crease_len > 0.01f && fabsf(crease_dir.y / crease_len) > 0.9f) { f32 bias0 = vec3_dot(original_velocity, clip_planes[0]); f32 bias1 = 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) 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); } }