use crate::partition_element_records::PartitionElementRecord;
use std::collections::{BTreeMap, BTreeSet};
pub const COLUMN_BODY_SOURCE_FIELD: &str = "m_column_body_source";
pub const COLUMN_BODY_JOIN_CUT: &str = "partition_element_record_join_cut";
pub const COLUMN_CUT_WALL_COUNT_FIELD: &str = "m_column_cut_walls";
pub const CUT_EPS_FEET: f64 = 1e-9;
pub const MAX_CUTTERS: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ColumnCut {
pub bbox_feet: [f64; 6],
pub wall_count: usize,
}
fn overlaps(a: &[f64; 6], b: &[f64; 6]) -> bool {
(0..3).all(|axis| a[axis + 3].min(b[axis + 3]) - a[axis].max(b[axis]) > CUT_EPS_FEET)
}
fn contains(box_feet: &[f64; 6], point: [f64; 3]) -> bool {
(0..3).all(|axis| {
point[axis] >= box_feet[axis] - CUT_EPS_FEET
&& point[axis] <= box_feet[axis + 3] + CUT_EPS_FEET
})
}
fn grid(prism: &[f64; 6], cutters: &[[f64; 6]], axis: usize) -> Vec<f64> {
let mut values = vec![prism[axis], prism[axis + 3]];
for cutter in cutters {
for value in [cutter[axis], cutter[axis + 3]] {
if value > prism[axis] + CUT_EPS_FEET && value < prism[axis + 3] - CUT_EPS_FEET {
values.push(value);
}
}
}
values.sort_by(|a, b| a.partial_cmp(b).expect("finite"));
values.dedup_by(|a, b| (*a - *b).abs() <= CUT_EPS_FEET);
values
}
pub fn difference_box(prism: &[f64; 6], cutters: &[[f64; 6]]) -> Option<[f64; 6]> {
let cutters: Vec<[f64; 6]> = cutters
.iter()
.filter(|cutter| overlaps(prism, cutter))
.copied()
.collect();
if cutters.is_empty() || cutters.len() > MAX_CUTTERS {
return None;
}
let grids = [
grid(prism, &cutters, 0),
grid(prism, &cutters, 1),
grid(prism, &cutters, 2),
];
let mut low = [f64::INFINITY; 3];
let mut high = [f64::NEG_INFINITY; 3];
let mut volume = 0.0f64;
for xi in 0..grids[0].len() - 1 {
for yi in 0..grids[1].len() - 1 {
for zi in 0..grids[2].len() - 1 {
let cell = [
grids[0][xi],
grids[1][yi],
grids[2][zi],
grids[0][xi + 1],
grids[1][yi + 1],
grids[2][zi + 1],
];
let centre = [
(cell[0] + cell[3]) * 0.5,
(cell[1] + cell[4]) * 0.5,
(cell[2] + cell[5]) * 0.5,
];
if cutters.iter().any(|cutter| contains(cutter, centre)) {
continue;
}
volume += (cell[3] - cell[0]) * (cell[4] - cell[1]) * (cell[5] - cell[2]);
for axis in 0..3 {
low[axis] = low[axis].min(cell[axis]);
high[axis] = high[axis].max(cell[axis + 3]);
}
}
}
}
if !low[0].is_finite() {
return None;
}
let hull = [low[0], low[1], low[2], high[0], high[1], high[2]];
let hull_volume = (0..3)
.map(|axis| hull[axis + 3] - hull[axis])
.product::<f64>();
if (hull_volume - volume).abs() > CUT_EPS_FEET {
return None;
}
if (0..6).all(|slot| (hull[slot] - prism[slot]).abs() <= CUT_EPS_FEET) {
return None;
}
Some(hull)
}
pub fn column_cut_boxes(
columns: &[PartitionElementRecord],
walls: &[PartitionElementRecord],
) -> BTreeMap<u32, ColumnCut> {
let wall_boxes: BTreeMap<u32, [f64; 6]> = walls
.iter()
.map(|record| (record.element_id, record.bbox_feet))
.collect();
let wall_ids: BTreeSet<u32> = wall_boxes.keys().copied().collect();
let mut out = BTreeMap::new();
for column in columns {
let cutters: Vec<[f64; 6]> = column
.references
.iter()
.filter(|slot| **slot <= u64::from(u32::MAX))
.map(|slot| *slot as u32)
.filter(|id| wall_ids.contains(id))
.filter_map(|id| wall_boxes.get(&id).copied())
.filter(|cutter| overlaps(&column.bbox_feet, cutter))
.collect();
if let Some(bbox_feet) = difference_box(&column.bbox_feet, &cutters) {
out.insert(
column.element_id,
ColumnCut {
bbox_feet,
wall_count: cutters.len(),
},
);
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::partition_element_records as per;
fn record(
element_id: u32,
category: i64,
bbox_feet: [f64; 6],
refs: &[u32],
) -> PartitionElementRecord {
let mut references: Vec<u64> = vec![3, 5755, 20307];
references.extend(refs.iter().map(|id| u64::from(*id)));
references.push(u64::from(element_id));
references.sort_unstable();
PartitionElementRecord {
stream: "Partitions/59".into(),
offset: element_id as usize,
element_id,
flags: 0x0121,
builtin_category: category,
container: per::CONTAINER_NONE,
placement_kind: per::PLACEMENT_KIND_INSTANCE,
class_tag: 0,
bbox_feet,
preceding_reference: None,
owner_reference: None,
references,
id_from_enclosing_record: false,
design_option: None,
}
}
fn column(element_id: u32, bbox_feet: [f64; 6], refs: &[u32]) -> PartitionElementRecord {
record(element_id, per::OST_COLUMNS, bbox_feet, refs)
}
fn wall(element_id: u32, bbox_feet: [f64; 6]) -> PartitionElementRecord {
record(element_id, per::OST_WALLS, bbox_feet, &[])
}
#[test]
fn a_wall_that_overruns_one_face_insets_the_column() {
let columns = vec![column(
22807,
[20.0, 112.0, -40.0, 22.0, 114.0, 0.0],
&[80743],
)];
let walls = vec![wall(80743, [20.0, 112.5, -40.0, 166.25, 114.0, 0.0])];
let cut = column_cut_boxes(&columns, &walls)[&22807];
assert_eq!(cut.wall_count, 1);
assert!((cut.bbox_feet[4] - 112.5).abs() < 1e-9, "y max cut back");
assert!((cut.bbox_feet[1] - 112.0).abs() < 1e-9, "y min unchanged");
assert!((cut.bbox_feet[0] - 20.0).abs() < 1e-9);
assert!((cut.bbox_feet[3] - 22.0).abs() < 1e-9);
}
#[test]
fn an_interior_slot_leaves_the_prism_alone() {
let columns = vec![column(
20376,
[48.0, 109.0, 76.0, 50.0, 111.0, 90.33333],
&[20811],
)];
let walls = vec![wall(20811, [49.0, 100.0, 76.0, 49.5, 120.0, 84.0])];
assert!(column_cut_boxes(&columns, &walls).is_empty());
}
#[test]
fn a_column_that_names_no_wall_is_not_cut() {
let columns = vec![column(63298, [0.0, 0.0, 0.0, 2.0, 2.0, 9.0], &[])];
let walls = vec![wall(90000, [0.0, 0.0, 0.0, 0.5, 2.0, 9.0])];
assert!(column_cut_boxes(&columns, &walls).is_empty());
}
#[test]
fn a_named_wall_that_does_not_overlap_cuts_nothing() {
let columns = vec![column(63298, [0.0, 0.0, 0.0, 2.0, 2.0, 9.0], &[90000])];
let walls = vec![wall(90000, [10.0, 0.0, 0.0, 10.5, 2.0, 9.0])];
assert!(column_cut_boxes(&columns, &walls).is_empty());
}
#[test]
fn a_cut_that_leaves_an_l_is_declined() {
let columns = vec![column(
63299,
[0.0, 0.0, 0.0, 2.0, 2.0, 9.0],
&[90000, 90001],
)];
let walls = vec![
wall(90000, [0.0, 0.0, 0.0, 0.5, 2.0, 4.0]),
wall(90001, [0.0, 0.0, 4.0, 2.0, 0.5, 9.0]),
];
assert!(column_cut_boxes(&columns, &walls).is_empty());
}
#[test]
fn opposite_faces_leave_a_narrower_box() {
let columns = vec![column(
63300,
[0.0, 0.0, 0.0, 2.0, 2.0, 9.0],
&[90000, 90001],
)];
let walls = vec![
wall(90000, [-5.0, 0.0, 0.0, 0.33333, 2.0, 9.0]),
wall(90001, [1.75, 0.0, 0.0, 7.0, 2.0, 9.0]),
];
let cut = column_cut_boxes(&columns, &walls)[&63300];
assert_eq!(cut.wall_count, 2);
assert!((cut.bbox_feet[0] - 0.33333).abs() < 1e-9);
assert!((cut.bbox_feet[3] - 1.75).abs() < 1e-9);
assert!((cut.bbox_feet[3] - cut.bbox_feet[0] - 1.41667).abs() < 1e-4);
}
#[test]
fn a_wall_that_swallows_the_column_leaves_nothing_and_declines() {
let columns = vec![column(63301, [0.0, 0.0, 0.0, 2.0, 2.0, 9.0], &[90000])];
let walls = vec![wall(90000, [-1.0, -1.0, -1.0, 3.0, 3.0, 10.0])];
assert!(column_cut_boxes(&columns, &walls).is_empty());
}
#[test]
fn difference_box_is_none_without_cutters() {
assert!(difference_box(&[0.0, 0.0, 0.0, 2.0, 2.0, 9.0], &[]).is_none());
}
}