rectangle_pack/target_bin/
push_available_bin_section.rs

1//! Methods for adding a BinSection back into a TargetBin.
2//!
3//! Useful in an application that needs to be able to remove packed rectangles from bins.
4//! After which the [`TargetBin.coalesce`] method can be used to combine smaller adjacent sections
5//! into larger sections.
6
7#![allow(missing_docs)]
8
9use crate::bin_section::BinSection;
10use crate::TargetBin;
11use core::fmt::{Display, Formatter, Result as FmtResult};
12
13impl TargetBin {
14    /// Push a [`BinSection`] to the list of remaining [`BinSection`]'s that rectangles can be
15    /// placed in.
16    ///
17    /// ## Performance
18    ///
19    /// This checks that your [`BinSection`] does not overlap any other bin sections. In many
20    /// cases this will be negligible, however it is important to note that this has a worst case
21    /// time complexity of `O(Width * Height * Depth)`, where the worst case is tht you have a bin
22    /// full of `1x1x1` rectangles.
23    ///
24    /// To skip the validity checks use [`TargetBin.push_available_bin_section_unchecked`].
25    ///
26    /// [`TargetBin.push_available_bin_section_unchecked`]: #method.push_available_bin_section_unchecked
27    pub fn push_available_bin_section(
28        &mut self,
29        bin_section: BinSection,
30    ) -> Result<(), PushBinSectionError> {
31        if bin_section.x >= self.max_width
32            || bin_section.y >= self.max_height
33            || bin_section.z >= self.max_depth
34        {
35            return Err(PushBinSectionError::OutOfBounds(bin_section));
36        }
37
38        for available in self.available_bin_sections.iter() {
39            if available.overlaps(&bin_section) {
40                return Err(PushBinSectionError::Overlaps {
41                    remaining_section: *available,
42                    new_section: bin_section,
43                });
44            }
45        }
46
47        self.push_available_bin_section_unchecked(bin_section);
48
49        Ok(())
50    }
51
52    /// Push a [`BinSection`] to the list of remaining [`BinSection`]'s that rectangles can be
53    /// placed in, without checking whether or not it is valid.
54    ///
55    /// Use [`TargetBin.push_available_bin_section`] if you want to check that the new bin section
56    /// does not overlap any existing bin sections nad that it is within the [`TargetBin`]'s bounds.
57    ///
58    /// [`TargetBin.push_available_bin_section`]: #method.push_available_bin_section
59    pub fn push_available_bin_section_unchecked(&mut self, bin_section: BinSection) {
60        self.available_bin_sections.push(bin_section);
61    }
62}
63
64/// An error while attempting to push a [`BinSection`] into the remaining bin sections of a
65/// [`TargetBin`].
66#[derive(Debug)]
67pub enum PushBinSectionError {
68    /// Attempted to push a [`BinSection`] that is not fully contained by the bin.
69    OutOfBounds(BinSection),
70    /// Attempted to push a [`BinSection`] that overlaps another empty bin section.
71    Overlaps {
72        /// The section that is already stored as empty within the [`TargetBin`];
73        remaining_section: BinSection,
74        /// The section that you were trying to add to the [`TargetBin`];
75        new_section: BinSection,
76    },
77}
78
79impl Display for PushBinSectionError {
80    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
81        match self {
82            PushBinSectionError::OutOfBounds(oob) => {
83                f.debug_tuple("BinSection").field(oob).finish()
84            }
85            PushBinSectionError::Overlaps {
86                remaining_section,
87                new_section,
88            } => f
89                .debug_struct("Overlaps")
90                .field("remaining_section", remaining_section)
91                .field("new_section", new_section)
92                .finish(),
93        }
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100    use crate::width_height_depth::WidthHeightDepth;
101
102    /// Verify that if the bin section that we are pushing is outside of the TargetBin's bounds we
103    /// return an error.
104    #[test]
105    fn error_if_bin_section_out_of_bounds() {
106        let mut bin = empty_bin();
107
108        let out_of_bounds = BinSection::new(101, 0, 0, WidthHeightDepth::new(1, 1, 1));
109
110        match bin.push_available_bin_section(out_of_bounds).err().unwrap() {
111            PushBinSectionError::OutOfBounds(err_bin_section) => {
112                assert_eq!(err_bin_section, out_of_bounds)
113            }
114            _ => panic!(),
115        };
116    }
117
118    /// Verify that if the bin section that we are pushing overlaps another bin section we return
119    /// an error.
120    #[test]
121    fn error_if_bin_section_overlaps_another_remaining_section() {
122        let mut bin = empty_bin();
123
124        let overlaps = BinSection::new(0, 0, 0, WidthHeightDepth::new(1, 1, 1));
125
126        match bin.push_available_bin_section(overlaps).err().unwrap() {
127            PushBinSectionError::Overlaps {
128                remaining_section: err_remaining_section,
129                new_section: err_new_section,
130            } => {
131                assert_eq!(err_new_section, overlaps);
132                assert_eq!(
133                    err_remaining_section,
134                    BinSection::new(0, 0, 0, WidthHeightDepth::new(100, 100, 1))
135                );
136            }
137            _ => panic!(),
138        }
139    }
140
141    /// Verify that we can push a valid bin section.
142    #[test]
143    fn push_bin_section() {
144        let mut bin = full_bin();
145
146        let valid_section = BinSection::new(1, 2, 0, WidthHeightDepth::new(1, 1, 1));
147
148        assert_eq!(bin.available_bin_sections.len(), 0);
149        bin.push_available_bin_section(valid_section).unwrap();
150        assert_eq!(bin.available_bin_sections.len(), 1);
151
152        assert_eq!(bin.available_bin_sections[0], valid_section);
153    }
154
155    fn empty_bin() -> TargetBin {
156        TargetBin::new(100, 100, 1)
157    }
158
159    fn full_bin() -> TargetBin {
160        let mut bin = TargetBin::new(100, 100, 1);
161
162        bin.available_bin_sections.clear();
163
164        bin
165    }
166}