add collision

This commit is contained in:
asrael 2025-11-21 16:44:42 -06:00
parent 8baf5f06ea
commit 2427e3a504
7 changed files with 302 additions and 52 deletions

View file

@ -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))

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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;
}
memset(&world->bsp, 0, sizeof(pxl8_bsp));
f32 dist = plane->normal.x * pos.x +
plane->normal.y * pos.y +
plane->normal.z * pos.z - plane->dist;
pxl8_result result = pxl8_bsp_load(path, &world->bsp);
if (result != PXL8_OK) {
pxl8_error("Failed to load world: %s", path);
return result;
if (fabsf(dist) > radius) {
continue;
}
world->loaded = true;
pxl8_info("Loaded world: %s", path);
pxl8_vec3 closest_point = {
pos.x - plane->normal.x * dist,
pos.y - plane->normal.y * dist,
pos.z - plane->normal.z * dist
};
return PXL8_OK;
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) {
@ -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;
}

View file

@ -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