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}