package phys import "core:log" import "core:math" import "core:math/linalg" import hm "core:container/handle_map" Vec2 :: [2]f32 Rect :: struct { start: Vec2, size: Vec2, } Layer :: enum (u16) { Hard, // hard collisions; don't let bodies intersect at all. default Soft, // soft collisions; push away other bodies with a force Enemy, // enemy hitboxes Player, // player hitboxes Enemy_Projectile, Player_Projectile, } Layer_Set :: bit_set[Layer;u16] Collision_Type :: enum (u8) { Up, Down, Right, Left, Horizontal, Vertical, } Raycast :: struct { start: Vec2, end: Vec2, mask: Layer_Set, } Raycast_Collision :: struct { body: Body_Handle, enter: Vec2, exit: Vec2, } Body :: struct { handle: Body_Handle, udata: any, bin_idx: i32, rect: Rect, active: bool, pos: Vec2, vel: Vec2, collisions: bit_set[Collision_Type;u8], layers: Layer_Set, mask: Layer_Set, } @(require_results) make_body :: proc( rect: Rect, layers := Layer_Set{.Hard}, mask := Layer_Set{.Hard}, ) -> Body_Handle { b := Body { rect = rect, layers = layers, mask = mask, active = true, } return add_body(b) } @(require_results) make_raycast :: #force_inline proc( start: Vec2, end: Vec2, mask := Layer_Set{.Hard}, ) -> Raycast { return Raycast{ start, end, mask, } } @(require_results) aabb_hori :: proc(a: Rect, b: Rect) -> bool { return a.start.x < b.start.x + b.size.x && b.start.x < a.start.x + a.size.x } @(require_results) aabb_vert :: proc(a: Rect, b: Rect) -> bool { return a.start.y < b.start.y + b.size.y && b.start.y < a.start.y + a.size.y } @(require_results) aabb :: proc(a: Rect, b: Rect) -> bool { return aabb_hori(a, b) && aabb_vert(a, b) } @(require_results) point_aabb_hori :: proc(r: Rect, p: Vec2) -> bool { return r.start.x < p.x && r.start.x + r.size.x > p.x } @(require_results) point_aabb_vert :: proc(r: Rect, p: Vec2) -> bool { return r.start.y < p.y && r.start.y + r.size.y > p.y } @(require_results) point_aabb :: proc(r: Rect, p: Vec2) -> bool { return point_aabb_hori(r, p) && point_aabb_vert(r, p) } @(require_results) raycast_to_aabb :: proc( rc: Raycast, body: Body, ) -> (collision := Raycast_Collision{}, collided := false) { body_min := body.pos + body.rect.start body_max := body_min + body.rect.size rc_dir_to_body := (body_min + body_max) * 0.5 - rc.start rc_dir := linalg.normalize0(rc.end - rc.start) // Don't consider bodies behind the ray if linalg.dot(rc_dir_to_body, rc_dir) < 0 { return } near := -math.INF_F32 far := math.INF_F32 dir_inv := 1.0 / rc_dir tx_near := (body_min.x - rc.start.x) * dir_inv.x tx_far := (body_max.x - rc.start.x) * dir_inv.x near = max(near, min(tx_far, tx_near)) far = min(far, max(tx_far, tx_near)) ty_near := (body_min.y - rc.start.y) * dir_inv.y ty_far := (body_max.y - rc.start.y) * dir_inv.y near = max(near, min(ty_far, ty_near)) far = min(far, max(ty_far, ty_near)) collision.body = body.handle collision.enter = rc.start + rc_dir * near collision.exit = rc.start + rc_dir * far rc_len := linalg.length2(rc.end - rc.start) rc_dist_to_body := linalg.length2(rc.start - collision.enter) collided = far >= near && rc_dist_to_body <= rc_len return } @(require_results) get_velocity :: proc(h: Body_Handle) -> Vec2 { return hm.get(&world.bodies, h).vel } set_velocity :: proc(h: Body_Handle, vel: Vec2) { hm.get(&world.bodies, h).vel = vel } @(require_results) get_position :: proc(h: Body_Handle) -> Vec2 { return hm.get(&world.bodies, h).pos } set_position :: proc(h: Body_Handle, new_pos: Vec2) { b := hm.get(&world.bodies, h) prev_bin := _hash_bin(_world_to_bin(b.pos + b.rect.start)) res_bin := _hash_bin(_world_to_bin(new_pos + b.rect.start)) if prev_bin != res_bin { _remove_from_bins(b^) b.pos = new_pos _add_to_bins(b) } else { b.pos = new_pos } } @(require_results) get_collisions :: proc(h: Body_Handle) -> bit_set[Collision_Type;u8] { return hm.get(&world.bodies, h).collisions } @(require_results) get_rect :: proc(h: Body_Handle) -> Rect { return hm.get(&world.bodies, h).rect } @(require_results) get_layers :: proc(h: Body_Handle) -> Layer_Set { return hm.get(&world.bodies, h).layers } @(require_results) get_mask :: proc(h: Body_Handle) -> Layer_Set { return hm.get(&world.bodies, h).mask } @(require_results) get_udata :: proc(h: Body_Handle) -> any { return hm.get(&world.bodies, h).udata } set_udata :: proc(h: Body_Handle, udata: any) { hm.get(&world.bodies, h).udata = udata }