code wiki / (root) / nx_collision_aabb.nx

nx_collision_aabb.nx source

↩ module page · 153 lines · 5333 B

1// nx_collision_aabb.nx -- axis-aligned bounding box collision (Q14). 2// 3// Fixed-point collision primitive. Used by game-engine for: 4// - player vs voxel (post-raycast solid-block resolution) 5// - player vs entity (NPCs, items, projectiles) 6// - ray vs AABB (efficient broad-phase) 7// 8// Q14 throughout — no float. Composes with nx_q14_math + nx_raycast_voxel. 9 10import "nx_syscalls.nx" 11import "nx_tier.nx" 12import "nx_q14_math.nx" 13const NX_MAGIC_2147483647: i64 = 2147483647 14 15const NX_AABB_NO_OVERLAP: nx_int = 0 16const NX_AABB_OVERLAP: nx_int = 1 17 18// ===== Struct: NxAabb =============================================== 19// 20// Min/max corners in Q14. min[i] <= max[i] (caller enforces). 21 22struct NxAabb { 23 min_x_q14: nx_int, 24 min_y_q14: nx_int, 25 min_z_q14: nx_int, 26 max_x_q14: nx_int, 27 max_y_q14: nx_int, 28 max_z_q14: nx_int, 29} 30 31func nx_aabb_new(min_x: nx_int, min_y: nx_int, min_z: nx_int, 32 max_x: nx_int, max_y: nx_int, max_z: nx_int) -> *NxAabb { 33 let b: *NxAabb = (sys_mmap(48)) as *NxAabb 34 b.min_x_q14 = min_x 35 b.min_y_q14 = min_y 36 b.min_z_q14 = min_z 37 b.max_x_q14 = max_x 38 b.max_y_q14 = max_y 39 b.max_z_q14 = max_z 40 return b 41} 42 43func nx_aabb_from_voxel(vx: nx_int, vy: nx_int, vz: nx_int) -> *NxAabb { 44 return nx_aabb_new(vx * NX_Q14, vy * NX_Q14, vz * NX_Q14, 45 (vx + 1) * NX_Q14, (vy + 1) * NX_Q14, (vz + 1) * NX_Q14) 46} 47 48// ===== nx_aabb_overlap ============================================= 49// 50// Returns 1 if a and b overlap, 0 otherwise. Standard SAT for AABBs: 51// reject if separated along any axis. 52 53func nx_aabb_overlap(a: *NxAabb, b: *NxAabb) -> nx_int { 54 if a.max_x_q14 < b.min_x_q14 { return NX_AABB_NO_OVERLAP } 55 if a.min_x_q14 > b.max_x_q14 { return NX_AABB_NO_OVERLAP } 56 if a.max_y_q14 < b.min_y_q14 { return NX_AABB_NO_OVERLAP } 57 if a.min_y_q14 > b.max_y_q14 { return NX_AABB_NO_OVERLAP } 58 if a.max_z_q14 < b.min_z_q14 { return NX_AABB_NO_OVERLAP } 59 if a.min_z_q14 > b.max_z_q14 { return NX_AABB_NO_OVERLAP } 60 return NX_AABB_OVERLAP 61} 62 63// ===== nx_aabb_contains_point ====================================== 64 65func nx_aabb_contains_point(b: *NxAabb, 66 px_q14: nx_int, py_q14: nx_int, pz_q14: nx_int) -> nx_int { 67 if px_q14 < b.min_x_q14 { return 0 } 68 if px_q14 > b.max_x_q14 { return 0 } 69 if py_q14 < b.min_y_q14 { return 0 } 70 if py_q14 > b.max_y_q14 { return 0 } 71 if pz_q14 < b.min_z_q14 { return 0 } 72 if pz_q14 > b.max_z_q14 { return 0 } 73 return 1 74} 75 76// ===== nx_aabb_translate ========================================== 77// 78// Move the AABB by (dx, dy, dz) in Q14. In place. 79 80func nx_aabb_translate(b: *NxAabb, dx_q14: nx_int, dy_q14: nx_int, dz_q14: nx_int) -> nx_int { 81 b.min_x_q14 = b.min_x_q14 + dx_q14 82 b.max_x_q14 = b.max_x_q14 + dx_q14 83 b.min_y_q14 = b.min_y_q14 + dy_q14 84 b.max_y_q14 = b.max_y_q14 + dy_q14 85 b.min_z_q14 = b.min_z_q14 + dz_q14 86 b.max_z_q14 = b.max_z_q14 + dz_q14 87 return 0 88} 89 90// ===== nx_aabb_intersect_ray ======================================= 91// 92// Slab method ray-AABB test. Returns 1 if the ray (origin, dir) enters 93// the AABB before max_t (Q14); 0 otherwise. Writes the entry t-value 94// into out_t_q14 on hit. 95 96func nx_aabb_intersect_ray(b: *NxAabb, 97 ox_q14: nx_int, oy_q14: nx_int, oz_q14: nx_int, 98 dx_q14: nx_int, dy_q14: nx_int, dz_q14: nx_int, 99 max_t_q14: nx_int, 100 out_t_q14: *i64) -> nx_int { 101 var t_near: nx_int = 0 - NX_MAGIC_2147483647 102 var t_far: nx_int = NX_MAGIC_2147483647 103 104 // X slab 105 if dx_q14 == 0 { 106 if ox_q14 < b.min_x_q14 { return 0 } 107 if ox_q14 > b.max_x_q14 { return 0 } 108 } else { 109 let t1: nx_int = ((b.min_x_q14 - ox_q14) * NX_Q14) / dx_q14 110 let t2: nx_int = ((b.max_x_q14 - ox_q14) * NX_Q14) / dx_q14 111 var lo: nx_int = t1 112 var hi: nx_int = t2 113 if lo > hi { lo = t2; hi = t1 } 114 if lo > t_near { t_near = lo } 115 if hi < t_far { t_far = hi } 116 if t_near > t_far { return 0 } 117 if t_far < 0 { return 0 } 118 } 119 // Y slab 120 if dy_q14 == 0 { 121 if oy_q14 < b.min_y_q14 { return 0 } 122 if oy_q14 > b.max_y_q14 { return 0 } 123 } else { 124 let t1: nx_int = ((b.min_y_q14 - oy_q14) * NX_Q14) / dy_q14 125 let t2: nx_int = ((b.max_y_q14 - oy_q14) * NX_Q14) / dy_q14 126 var lo: nx_int = t1 127 var hi: nx_int = t2 128 if lo > hi { lo = t2; hi = t1 } 129 if lo > t_near { t_near = lo } 130 if hi < t_far { t_far = hi } 131 if t_near > t_far { return 0 } 132 if t_far < 0 { return 0 } 133 } 134 // Z slab 135 if dz_q14 == 0 { 136 if oz_q14 < b.min_z_q14 { return 0 } 137 if oz_q14 > b.max_z_q14 { return 0 } 138 } else { 139 let t1: nx_int = ((b.min_z_q14 - oz_q14) * NX_Q14) / dz_q14 140 let t2: nx_int = ((b.max_z_q14 - oz_q14) * NX_Q14) / dz_q14 141 var lo: nx_int = t1 142 var hi: nx_int = t2 143 if lo > hi { lo = t2; hi = t1 } 144 if lo > t_near { t_near = lo } 145 if hi < t_far { t_far = hi } 146 if t_near > t_far { return 0 } 147 if t_far < 0 { return 0 } 148 } 149 if t_near > max_t_q14 { return 0 } 150 if t_near < 0 { t_near = 0 } 151 out_t_q14[0] = t_near as i64 152 return 1 153}