oxiphysics_collision/recast/
compact.rs1use super::RecastConfig;
7use super::rasterize::Heightfield;
8
9pub(crate) const DIRS: [(i32, i32); 4] = [(1, 0), (0, 1), (-1, 0), (0, -1)];
12
13#[derive(Debug, Clone, Default)]
14pub struct CompactCell {
15 pub index: u32,
17 pub count: u16,
19}
20
21#[derive(Debug, Clone, Default)]
22pub struct CompactSpan {
23 pub y: u16,
25 pub reg: u16,
27 pub con: u32,
31 pub h: u16,
33}
34
35pub struct CompactHeightfield {
36 pub width: usize,
37 pub height: usize, pub span_count: usize,
39 pub walkable_height: u16,
40 pub walkable_climb: u16,
41 pub bmin: [f64; 3],
42 pub bmax: [f64; 3],
43 pub cs: f64,
44 pub ch: f64,
45 pub cells: Vec<CompactCell>, pub spans: Vec<CompactSpan>, pub dist: Vec<u16>, pub max_dist: u16,
49}
50
51pub fn build_compact_heightfield(hf: &Heightfield, cfg: &RecastConfig) -> CompactHeightfield {
52 let walkable_height = (cfg.agent_height / cfg.cell_height).ceil() as u16;
53 let walkable_climb = (cfg.agent_max_climb / cfg.cell_height) as u16;
54 let w = hf.width;
55 let h = hf.height;
56 let n = w * h;
57
58 let mut cells = vec![CompactCell::default(); n];
59 let mut spans: Vec<CompactSpan> = Vec::new();
60
61 for iz in 0..h {
63 for ix in 0..w {
64 let col = &hf.spans[hf.col_idx(ix, iz)];
65 let cell_idx = iz * w + ix;
66 cells[cell_idx].index = spans.len() as u32;
67 let start = spans.len();
68 for (si, s) in col.iter().enumerate() {
69 if !s.walkable {
70 continue;
71 }
72 let y = s.smax;
73 let clearance = if si + 1 < col.len() {
74 col[si + 1].smin.saturating_sub(s.smax)
75 } else {
76 u16::MAX
77 };
78 spans.push(CompactSpan {
79 y,
80 reg: 0,
81 con: 0xFFFFFFFF, h: clearance.min(walkable_height),
83 });
84 }
85 cells[cell_idx].count = (spans.len() - start) as u16;
86 }
87 }
88
89 let span_count = spans.len();
90
91 for iz in 0..h {
93 for ix in 0..w {
94 let cell_idx = iz * w + ix;
95 let ci = cells[cell_idx].index as usize;
96 let cc = cells[cell_idx].count as usize;
97 for i in 0..cc {
98 let si = ci + i;
99 let s_y = spans[si].y as i32;
100 let mut con = 0xFFFFFFFFu32;
101 for (dir, &(dx, dz)) in DIRS.iter().enumerate() {
102 let nx = ix as i32 + dx;
103 let nz = iz as i32 + dz;
104 if nx < 0 || nz < 0 || nx >= w as i32 || nz >= h as i32 {
105 continue;
107 }
108 let ncell_idx = nz as usize * w + nx as usize;
109 let nci = cells[ncell_idx].index as usize;
110 let ncc = cells[ncell_idx].count as usize;
111 let mut best = 0xFFu32;
113 for ni in 0..ncc {
114 let n_y = spans[nci + ni].y as i32;
115 if (n_y - s_y).abs() <= walkable_climb as i32 {
116 best = ni as u32;
117 break;
118 }
119 }
120 con &= !(0xFFu32 << (dir * 8));
122 con |= (best & 0xFF) << (dir * 8);
123 }
124 spans[si].con = con;
125 }
126 }
127 }
128
129 let mut dist = vec![u16::MAX; span_count];
131
132 for si in 0..span_count {
134 let con = spans[si].con;
135 let is_border = (0..4).any(|dir| ((con >> (dir * 8)) & 0xFF) == 0xFF);
136 if is_border {
137 dist[si] = 0;
138 }
139 }
140
141 for iz in 0..h {
143 for ix in 0..w {
144 let cell_idx = iz * w + ix;
145 let ci = cells[cell_idx].index as usize;
146 let cc = cells[cell_idx].count as usize;
147 for i in 0..cc {
148 let si = ci + i;
149 for (dir, &(dx, dz)) in DIRS.iter().enumerate() {
150 let nc_raw = (spans[si].con >> (dir * 8)) & 0xFF;
151 if nc_raw == 0xFF {
152 continue;
153 }
154 let nx = ix as i32 + dx;
155 let nz = iz as i32 + dz;
156 if nx < 0 || nz < 0 || nx >= w as i32 || nz >= h as i32 {
157 continue;
158 }
159 let ncell_idx = nz as usize * w + nx as usize;
160 let nci = cells[ncell_idx].index as usize;
161 let nsi = nci + nc_raw as usize;
162 if dist[nsi] != u16::MAX {
163 let new_dist = dist[nsi].saturating_add(2);
164 if new_dist < dist[si] {
165 dist[si] = new_dist;
166 }
167 }
168 }
169 }
170 }
171 }
172
173 for iz in (0..h).rev() {
175 for ix in (0..w).rev() {
176 let cell_idx = iz * w + ix;
177 let ci = cells[cell_idx].index as usize;
178 let cc = cells[cell_idx].count as usize;
179 for i in 0..cc {
180 let si = ci + i;
181 for (dir, &(dx, dz)) in DIRS.iter().enumerate() {
182 let nc_raw = (spans[si].con >> (dir * 8)) & 0xFF;
183 if nc_raw == 0xFF {
184 continue;
185 }
186 let nx = ix as i32 + dx;
187 let nz = iz as i32 + dz;
188 if nx < 0 || nz < 0 || nx >= w as i32 || nz >= h as i32 {
189 continue;
190 }
191 let ncell_idx = nz as usize * w + nx as usize;
192 let nci = cells[ncell_idx].index as usize;
193 let nsi = nci + nc_raw as usize;
194 if dist[nsi] != u16::MAX {
195 let new_dist = dist[nsi].saturating_add(2);
196 if new_dist < dist[si] {
197 dist[si] = new_dist;
198 }
199 }
200 }
201 }
202 }
203 }
204
205 let max_dist = dist
206 .iter()
207 .cloned()
208 .filter(|&d| d != u16::MAX)
209 .max()
210 .unwrap_or(0);
211
212 CompactHeightfield {
213 width: w,
214 height: h,
215 span_count,
216 walkable_height,
217 walkable_climb,
218 bmin: hf.bmin,
219 bmax: hf.bmax,
220 cs: hf.cs,
221 ch: hf.ch,
222 cells,
223 spans,
224 dist,
225 max_dist,
226 }
227}