Subtract an axis-aligned box from existing geometry. This is a simplified CSG operation that clips triangles against a box. For Phase 2 MVP, we use a simple approach: remove triangles fully inside the box and clip intersecting ones. This handles the common case of door/window cutouts in walls. - `min`: box minimum corner [x, y, z] - `max`: box maximum corner [x, y, z]
(
geo: &GeometryData,
min: [f32; 3],
max: [f32; 3],
)
| 187 | /// - `min`: box minimum corner [x, y, z] |
| 188 | /// - `max`: box maximum corner [x, y, z] |
| 189 | pub fn subtract_box( |
| 190 | geo: &GeometryData, |
| 191 | min: [f32; 3], |
| 192 | max: [f32; 3], |
| 193 | ) -> GeometryData { |
| 194 | let mut out_vertices = Vec::new(); |
| 195 | let mut out_indices = Vec::new(); |
| 196 | |
| 197 | // Process each triangle |
| 198 | for tri in geo.indices.chunks(3) { |
| 199 | if tri.len() < 3 { continue; } |
| 200 | let v0 = &geo.vertices[tri[0] as usize]; |
| 201 | let v1 = &geo.vertices[tri[1] as usize]; |
| 202 | let v2 = &geo.vertices[tri[2] as usize]; |
| 203 | |
| 204 | // Check if any vertex is inside the box |
| 205 | let in0 = point_in_box(&v0.position, &min, &max); |
| 206 | let in1 = point_in_box(&v1.position, &min, &max); |
| 207 | let in2 = point_in_box(&v2.position, &min, &max); |
| 208 | |
| 209 | if in0 && in1 && in2 { |
| 210 | // Triangle fully inside box — discard |
| 211 | continue; |
| 212 | } |
| 213 | |
| 214 | // Triangle fully outside or partially intersecting — keep for now |
| 215 | // (Full CSG clipping would split partial triangles, but for MVP |
| 216 | // removing fully-interior triangles handles most cutout cases) |
| 217 | let base = out_vertices.len() as u32; |
| 218 | out_vertices.push(*v0); |
| 219 | out_vertices.push(*v1); |
| 220 | out_vertices.push(*v2); |
| 221 | out_indices.push(base); |
| 222 | out_indices.push(base + 1); |
| 223 | out_indices.push(base + 2); |
| 224 | } |
| 225 | |
| 226 | GeometryData { |
| 227 | vertices: out_vertices, |
| 228 | indices: out_indices, |
| 229 | } |
| 230 | } |
| 231 | |
| 232 | fn point_in_box(p: &[f32; 3], min: &[f32; 3], max: &[f32; 3]) -> bool { |
| 233 | p[0] >= min[0] && p[0] <= max[0] |
no test coverage detected