use super::model::{Placement2D, Sheet2D};
pub(crate) fn kerf_area_for_layout(sheet: &Sheet2D, placements: &[Placement2D]) -> u64 {
let kerf = sheet.kerf;
if kerf == 0 || placements.len() < 2 {
return 0;
}
let kerf_u64 = u64::from(kerf);
let mut h_bands: std::collections::BTreeMap<u32, (u32, u32, u32, u32)> =
std::collections::BTreeMap::new();
let mut v_bands: std::collections::BTreeMap<u32, (u32, u32, u32, u32)> =
std::collections::BTreeMap::new();
for (i, a) in placements.iter().enumerate() {
let a_right = a.x.saturating_add(a.width);
let a_bottom = a.y.saturating_add(a.height);
for b in &placements[i + 1..] {
let b_right = b.x.saturating_add(b.width);
let b_bottom = b.y.saturating_add(b.height);
if let Some((gap_x, a_is_left)) = gap_start(a.x, a_right, b.x, b_right, kerf) {
let (left_y_lo, left_y_hi, right_y_lo, right_y_hi) = if a_is_left {
(a.y, a_bottom, b.y, b_bottom)
} else {
(b.y, b_bottom, a.y, a_bottom)
};
v_bands
.entry(gap_x)
.and_modify(|(alo, ahi, blo, bhi)| {
*alo = (*alo).min(left_y_lo);
*ahi = (*ahi).max(left_y_hi);
*blo = (*blo).min(right_y_lo);
*bhi = (*bhi).max(right_y_hi);
})
.or_insert((left_y_lo, left_y_hi, right_y_lo, right_y_hi));
}
if let Some((gap_y, a_is_top)) = gap_start(a.y, a_bottom, b.y, b_bottom, kerf) {
let (top_x_lo, top_x_hi, bot_x_lo, bot_x_hi) = if a_is_top {
(a.x, a_right, b.x, b_right)
} else {
(b.x, b_right, a.x, a_right)
};
h_bands
.entry(gap_y)
.and_modify(|(alo, ahi, blo, bhi)| {
*alo = (*alo).min(top_x_lo);
*ahi = (*ahi).max(top_x_hi);
*blo = (*blo).min(bot_x_lo);
*bhi = (*bhi).max(bot_x_hi);
})
.or_insert((top_x_lo, top_x_hi, bot_x_lo, bot_x_hi));
}
}
}
let mut total: u64 = 0;
for (a_lo, a_hi, b_lo, b_hi) in h_bands.values().chain(v_bands.values()) {
let covered = hull_intersection(*a_lo, *a_hi, *b_lo, *b_hi);
total = total.saturating_add(kerf_u64.saturating_mul(covered));
}
let sheet_area = u64::from(sheet.width).saturating_mul(u64::from(sheet.height));
total.min(sheet_area)
}
fn gap_start(a_lo: u32, a_hi: u32, b_lo: u32, b_hi: u32, kerf: u32) -> Option<(u32, bool)> {
if a_hi <= b_lo && b_lo.saturating_sub(a_hi) == kerf {
return Some((a_hi, true));
}
if b_hi <= a_lo && a_lo.saturating_sub(b_hi) == kerf {
return Some((b_hi, false));
}
None
}
fn hull_intersection(a_lo: u32, a_hi: u32, b_lo: u32, b_hi: u32) -> u64 {
let lo = a_lo.max(b_lo);
let hi = a_hi.min(b_hi);
if hi > lo { u64::from(hi - lo) } else { 0 }
}
#[cfg(test)]
mod tests {
use super::*;
fn sheet(width: u32, height: u32, kerf: u32) -> Sheet2D {
Sheet2D {
name: "s".to_string(),
width,
height,
cost: 1.0,
quantity: None,
kerf,
edge_kerf_relief: false,
}
}
fn place(x: u32, y: u32, width: u32, height: u32) -> Placement2D {
Placement2D { name: "p".to_string(), x, y, width, height, rotated: false }
}
#[test]
fn zero_kerf_has_zero_area() {
let s = sheet(10, 10, 0);
let placements = vec![place(0, 0, 5, 10), place(5, 0, 5, 10)];
assert_eq!(kerf_area_for_layout(&s, &placements), 0);
}
#[test]
fn single_placement_has_zero_kerf() {
let s = sheet(10, 10, 2);
let placements = vec![place(0, 0, 10, 10)];
assert_eq!(kerf_area_for_layout(&s, &placements), 0);
}
#[test]
fn horizontally_adjacent_pair_consumes_one_kerf_line() {
let s = sheet(10, 10, 2);
let placements = vec![place(0, 0, 4, 10), place(6, 0, 4, 10)];
assert_eq!(kerf_area_for_layout(&s, &placements), 20);
}
#[test]
fn vertically_adjacent_pair_consumes_one_kerf_line() {
let s = sheet(10, 10, 1);
let placements = vec![place(0, 0, 10, 4), place(0, 5, 10, 4)];
assert_eq!(kerf_area_for_layout(&s, &placements), 10);
}
#[test]
fn partial_overlap_uses_overlap_length_not_full_edge() {
let s = sheet(10, 10, 2);
let placements = vec![place(0, 0, 4, 10), place(6, 2, 4, 6)];
assert_eq!(kerf_area_for_layout(&s, &placements), 12);
}
#[test]
fn non_adjacent_pair_contributes_nothing() {
let s = sheet(20, 10, 2);
let placements = vec![place(0, 0, 4, 10), place(10, 0, 4, 10)];
assert_eq!(kerf_area_for_layout(&s, &placements), 0);
}
#[test]
fn boundary_flush_placement_does_not_count_against_sheet_edge() {
let s = sheet(10, 10, 2);
let placements = vec![place(0, 0, 4, 10)];
assert_eq!(kerf_area_for_layout(&s, &placements), 0);
}
#[test]
fn three_placements_share_two_kerf_lines() {
let s = sheet(10, 10, 2);
let placements = vec![place(0, 0, 2, 10), place(4, 0, 2, 10), place(8, 0, 2, 10)];
assert_eq!(kerf_area_for_layout(&s, &placements), 40);
}
#[test]
fn t_intersection_counts_each_shared_edge_once() {
let s = sheet(10, 10, 1);
let placements = vec![place(0, 0, 10, 4), place(0, 5, 4, 5), place(5, 5, 5, 5)];
assert_eq!(kerf_area_for_layout(&s, &placements), 15);
}
}