diff --git a/demo/main.fnl b/demo/main.fnl index 282a22a..0eda6af 100644 --- a/demo/main.fnl +++ b/demo/main.fnl @@ -46,6 +46,8 @@ (when transition-pending (when (and (= active-demo :worldgen) (not= transition-pending :worldgen)) (pxl8.set_relative_mouse_mode false)) + (when (and (not= active-demo :worldgen) (= transition-pending :worldgen)) + (pxl8.set_relative_mouse_mode true)) (set active-demo transition-pending) (set transition-pending nil) (when (= active-demo :fire) (set fire-init? false)) diff --git a/demo/mod/worldgen.fnl b/demo/mod/worldgen.fnl index 43a447a..170b58f 100644 --- a/demo/mod/worldgen.fnl +++ b/demo/mod/worldgen.fnl @@ -1,14 +1,24 @@ (local pxl8 (require :pxl8)) -(local bob-amount 4.0) -(local bob-speed 8.0) -(var bob-time 0) +(var world nil) + +(var auto-run? false) (var cam-pitch 0) (var cam-x 1000) (var cam-y 64) (var cam-yaw 0) (var cam-z 1000) +(var cursor-look? true) + +(var smooth-cam-x 1000) +(var smooth-cam-z 1000) +(local cam-smoothing 0.25) + +(local bob-amount 4.0) +(local bob-speed 8.0) +(var bob-time 0) (local cell-size 64) +(local cursor-sensitivity 0.008) (local gravity -800) (local grid-size 64) (var grounded? true) @@ -18,13 +28,9 @@ (local land-squash-amount -4) (var land-squash 0) (local max-pitch 1.5) -(var mouse-look? true) -(local mouse-sensitivity 0.008) (local move-speed 200) (local turn-speed 2.0) (var velocity-y 0) -(var world nil) -(var auto-run? false) (fn init [] (set world (pxl8.world_new)) @@ -111,27 +117,31 @@ (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)) + (let [(resolved-x resolved-y resolved-z) (pxl8.world_resolve_collision world cam-x cam-y cam-z new-x cam-y new-z 5)] + (set cam-x resolved-x) + (set cam-z resolved-z))) - (when mouse-look? + (set smooth-cam-x (+ (* smooth-cam-x (- 1 cam-smoothing)) (* cam-x cam-smoothing))) + (set smooth-cam-z (+ (* smooth-cam-z (- 1 cam-smoothing)) (* cam-z cam-smoothing))) + + (when cursor-look? (let [dx (pxl8.mouse_dx) dy (pxl8.mouse_dy)] - (set cam-yaw (- cam-yaw (* dx mouse-sensitivity))) + (set cam-yaw (- cam-yaw (* dx cursor-sensitivity))) (set cam-pitch (math.max (- max-pitch) (math.min max-pitch - (- cam-pitch (* dy mouse-sensitivity))))))) + (- cam-pitch (* dy cursor-sensitivity))))))) - (when (and (not mouse-look?) (pxl8.key_down "left")) + (when (and (not cursor-look?) (pxl8.key_down "left")) (set cam-yaw (+ cam-yaw (* turn-speed dt)))) - (when (and (not mouse-look?) (pxl8.key_down "right")) + (when (and (not cursor-look?) (pxl8.key_down "right")) (set cam-yaw (- cam-yaw (* turn-speed dt)))) - (when (and (not mouse-look?) (pxl8.key_down "up")) + (when (and (not cursor-look?) (pxl8.key_down "up")) (set cam-pitch (math.min max-pitch (+ cam-pitch (* turn-speed dt))))) - (when (and (not mouse-look?) (pxl8.key_down "down")) + (when (and (not cursor-look?) (pxl8.key_down "down")) (set cam-pitch (math.max (- max-pitch) (- cam-pitch (* turn-speed dt))))) (when (and (pxl8.key_pressed "space") grounded?) @@ -164,9 +174,9 @@ eye-y (+ cam-y bob-offset land-squash) forward-x (- (math.sin cam-yaw)) forward-z (- (math.cos cam-yaw)) - target-x (+ cam-x forward-x) + target-x (+ smooth-cam-x forward-x) target-y (+ eye-y (math.sin cam-pitch)) - target-z (+ cam-z forward-z)] + target-z (+ smooth-cam-z forward-z)] (pxl8.clear_zbuffer) (pxl8.set_backface_culling true) @@ -177,13 +187,13 @@ (pxl8.set_projection (pxl8.mat4_perspective fov aspect 1.0 4096.0))) (pxl8.set_view (pxl8.mat4_lookat - [cam-x eye-y cam-z] + [smooth-cam-x eye-y smooth-cam-z] [target-x target-y target-z] [0 1 0])) (pxl8.set_model (pxl8.mat4_identity)) - (pxl8.world_render world [cam-x eye-y cam-z]) + (pxl8.world_render world [smooth-cam-x eye-y smooth-cam-z]) (let [cx (/ (pxl8.get_width) 2) cy (/ (pxl8.get_height) 2) diff --git a/src/lua/pxl8.lua b/src/lua/pxl8.lua index e2fa1f8..2927384 100644 --- a/src/lua/pxl8.lua +++ b/src/lua/pxl8.lua @@ -143,6 +143,8 @@ pxl8.world_unload = world.unload pxl8.world_is_loaded = world.is_loaded pxl8.world_generate = world.generate pxl8.world_apply_textures = world.apply_textures +pxl8.world_check_collision = world.check_collision +pxl8.world_resolve_collision = world.resolve_collision pxl8.procgen_tex = world.procgen_tex pxl8.PROCGEN_ROOMS = world.PROCGEN_ROOMS pxl8.PROCGEN_TERRAIN = world.PROCGEN_TERRAIN diff --git a/src/lua/pxl8/world.lua b/src/lua/pxl8/world.lua index f86e4f3..c6c9ad9 100644 --- a/src/lua/pxl8/world.lua +++ b/src/lua/pxl8/world.lua @@ -95,4 +95,16 @@ function world.apply_textures(w, texture_defs) return result end +function world.check_collision(w, x, y, z, radius) + local pos = ffi.new("pxl8_vec3", {x = x, y = y, z = z}) + return C.pxl8_world_check_collision(w, pos, radius) +end + +function world.resolve_collision(w, 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(w, from, to, radius) + return result.x, result.y, result.z +end + return world diff --git a/src/pxl8_script.c b/src/pxl8_script.c index 158c01a..a5b83d2 100644 --- a/src/pxl8_script.c +++ b/src/pxl8_script.c @@ -302,11 +302,13 @@ static const char* pxl8_ffi_cdefs = "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_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" "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" diff --git a/src/pxl8_world.c b/src/pxl8_world.c index d34d081..87e6689 100644 --- a/src/pxl8_world.c +++ b/src/pxl8_world.c @@ -38,8 +38,6 @@ void pxl8_world_destroy(pxl8_world* world) { } pxl8_result pxl8_world_generate(pxl8_world* world, pxl8_gfx* gfx, const pxl8_procgen_params* params) { - pxl8_debug("pxl8_world_generate called"); - if (!world || !gfx || !params) { pxl8_error("Invalid arguments to pxl8_world_generate"); return PXL8_ERROR_INVALID_ARGUMENT; @@ -63,6 +61,35 @@ pxl8_result pxl8_world_generate(pxl8_world* world, pxl8_gfx* gfx, const pxl8_pro 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; @@ -151,26 +178,229 @@ pxl8_result pxl8_world_apply_textures(pxl8_world* world, const pxl8_world_textur return PXL8_OK; } -pxl8_result pxl8_world_load(pxl8_world* world, const char* path) { - if (!world || !path) return PXL8_ERROR_INVALID_ARGUMENT; +bool pxl8_world_check_collision(const pxl8_world* world, pxl8_vec3 pos, f32 radius) { + if (!world || !world->loaded) return false; - if (world->loaded) { - pxl8_bsp_destroy(&world->bsp); - world->loaded = 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; } - memset(&world->bsp, 0, sizeof(pxl8_bsp)); + return false; +} - pxl8_result result = pxl8_bsp_load(path, &world->bsp); - if (result != PXL8_OK) { - pxl8_error("Failed to load world: %s", path); - return result; +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; + } } - world->loaded = true; - pxl8_info("Loaded world: %s", path); + 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; - return PXL8_OK; + 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) { @@ -182,14 +412,3 @@ void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) { pxl8_bsp_render_textured(gfx, &world->bsp, camera_pos); } } - -void pxl8_world_unload(pxl8_world* world) { - if (!world || !world->loaded) return; - - pxl8_bsp_destroy(&world->bsp); - world->loaded = false; -} - -bool pxl8_world_is_loaded(const pxl8_world* world) { - return world && world->loaded; -} diff --git a/src/pxl8_world.h b/src/pxl8_world.h index 03158af..129d5ec 100644 --- a/src/pxl8_world.h +++ b/src/pxl8_world.h @@ -24,12 +24,15 @@ 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_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); 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