Initial commit.
Signed-off-by: Andreas Widen <aw@luflow.net>
This commit is contained in:
commit
d13d74df42
10 changed files with 1278 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
CHANGELOG.md
|
||||
.vscode
|
||||
/target
|
||||
9
AUTHORS
Normal file
9
AUTHORS
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
flow-rbp: A library for packing rectangles into two-dimensional finite bins.
|
||||
Maintainer: Andreas Widen <aw@luflow.net>
|
||||
License: zlib
|
||||
URL: https://www.luflow.net
|
||||
|
||||
Authors
|
||||
=======
|
||||
|
||||
Andreas Widen <aw@luflow.net>
|
||||
7
Cargo.lock
generated
Normal file
7
Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "flow-rbp"
|
||||
version = "0.1.0"
|
||||
14
Cargo.toml
Normal file
14
Cargo.toml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "flow-rbp"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
authors = ["Andreas Widen <aw@luflow.net>"]
|
||||
description = "flow-rbp is a library for packing rectangles into two-dimensional finite bins."
|
||||
license = "Zlib"
|
||||
repository = "https://luflow.net/git/hfsoulz/flow-rbp.git"
|
||||
readme = "README.md"
|
||||
documentation = "https://luflow.net/git/hfsoulz/flow-rbp.git"
|
||||
keywords = ["rectangles", "rect", "bin", "packing"]
|
||||
exclude = [".github", "/ci/*"]
|
||||
|
||||
[dependencies]
|
||||
18
LICENSE
Normal file
18
LICENSE
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
flow-rbp: A library for packing rectangles into two-dimensional finite bins.
|
||||
Copyright (C) 2026-2026 Andreas Widen <aw@luflow.net>
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
51
README.md
Normal file
51
README.md
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# flow-rbp
|
||||
|
||||
`flow-rbp` is a library for packing rectangles into two-dimensional finite bins
|
||||
using different heuristic methods for placement.
|
||||
|
||||
The two-dimensional rectangle bin packing is a classical problem in
|
||||
combinatorial optimization. In this problem, one is given a sequence of
|
||||
rectangles `(R1, R2, ... Rn), Ri = (wi, hi)` and the task is to find a packing
|
||||
of these items into a minimum number of bins of size `(W, H)`. No two
|
||||
rectangles may intersect or be contained inside one another. This library uses
|
||||
an algorithm sometimes referred as `The Maximal Rectangles ALgorithm`. This
|
||||
algorithm stores a list of free rectangles that represents the free area of the
|
||||
bin.
|
||||
|
||||
## Usage
|
||||
|
||||
Add this to your `Cargo.toml`:
|
||||
|
||||
```
|
||||
[dependencies]
|
||||
flow-rbp = { git = "https://luflow.net/git/hfsoulz/flow-rbp.git", tag = "v0.1.0" }
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```rust
|
||||
use flow_rbp::FreeRectHeuristic;
|
||||
use flow_rbp::RectsBinPack;
|
||||
|
||||
// create a new bin of size 32x32 which allows rotation:
|
||||
let mut rbp = RectsBinPack::new(32, 32, true).unwrap();
|
||||
|
||||
// make sure occupancy is zero:
|
||||
assert_eq!(rbp.get_occupancy(), 0.0);
|
||||
|
||||
// add a few rects that should fit:
|
||||
assert_eq!(rbp.insert(16, 16, FreeRectHeuristic::BottomLeft).is_some(), true);
|
||||
assert_eq!(rbp.insert(16, 16, FreeRectHeuristic::BottomLeft).is_some(), true);
|
||||
assert_eq!(rbp.get_occupancy(), 0.5);
|
||||
assert_eq!(rbp.insert(16, 16, FreeRectHeuristic::BottomLeft).is_some(), true);
|
||||
assert_eq!(rbp.insert(16, 16, FreeRectHeuristic::BottomLeft).is_some(), true);
|
||||
assert_eq!(rbp.get_occupancy(), 1.0);
|
||||
|
||||
// this rect will not fit and therefore returns None:
|
||||
assert_eq!(rbp.insert(1, 1, FreeRectHeuristic::BottomLeft).is_none(), true);
|
||||
|
||||
```
|
||||
|
||||
## LICENSE
|
||||
|
||||
See the file 'LICENSE' for license information.
|
||||
81
cliff.toml
Normal file
81
cliff.toml
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
# git-cliff ~ configuration file
|
||||
# https://git-cliff.org/docs/configuration
|
||||
|
||||
[changelog]
|
||||
# A Tera template to be rendered as the changelog's header.
|
||||
# See https://keats.github.io/tera/docs/#introduction
|
||||
header = """
|
||||
# Changelog\n
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n
|
||||
"""
|
||||
# A Tera template to be rendered for each release in the changelog.
|
||||
# See https://keats.github.io/tera/docs/#introduction
|
||||
body = """
|
||||
{% if version -%}
|
||||
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||
{% else -%}
|
||||
## [Unreleased]
|
||||
{% endif -%}
|
||||
{% for group, commits in commits | group_by(attribute="group") %}
|
||||
### {{ group | upper_first }}
|
||||
{% for commit in commits %}
|
||||
- {{ commit.message | split(pat="\n") | first | upper_first | trim }}\
|
||||
{% endfor %}
|
||||
{% endfor %}\n
|
||||
"""
|
||||
# A Tera template to be rendered as the changelog's footer.
|
||||
# See https://keats.github.io/tera/docs/#introduction
|
||||
footer = """
|
||||
{% for release in releases -%}
|
||||
{% if release.version -%}
|
||||
{% if release.previous.version -%}
|
||||
[{{ release.version | trim_start_matches(pat="v") }}]: \
|
||||
https://luflow.net/git/hfsoulz/flow-rbp.git\
|
||||
/compare/{{ release.previous.version }}..{{ release.version }}
|
||||
{% else -%}
|
||||
[{{ release.version | trim_start_matches(pat="v") }}]: \
|
||||
https://luflow.net/git/hfsoulz/flow-rbp.git\
|
||||
/releases/tag/{{ release.version }}
|
||||
{% endif -%}
|
||||
{% else -%}
|
||||
[unreleased]: https://luflow.net/git/hfsoulz/flow-rbp.git\
|
||||
/compare/{{ release.previous.version }}..HEAD
|
||||
{% endif -%}
|
||||
{% endfor %}
|
||||
<!-- generated by git-cliff -->
|
||||
"""
|
||||
# Remove leading and trailing whitespaces from the changelog's body.
|
||||
trim = true
|
||||
|
||||
[git]
|
||||
# Parse commits according to the conventional commits specification.
|
||||
# See https://www.conventionalcommits.org
|
||||
conventional_commits = true
|
||||
# Exclude commits that do not match the conventional commits specification.
|
||||
filter_unconventional = false
|
||||
# An array of regex based parsers for extracting data from the commit message.
|
||||
# Assigns commits to groups.
|
||||
# Optionally sets the commit's scope and can decide to exclude commits from further processing.
|
||||
commit_parsers = [
|
||||
{ message = "^[a|A]dd", group = "Added" },
|
||||
{ message = "^[s|S]upport", group = "Added" },
|
||||
{ message = "^[r|R]emove", group = "Removed" },
|
||||
{ message = "^.*: add", group = "Added" },
|
||||
{ message = "^.*: support", group = "Added" },
|
||||
{ message = "^.*: remove", group = "Removed" },
|
||||
{ message = "^.*: delete", group = "Removed" },
|
||||
{ message = "^test", group = "Fixed" },
|
||||
{ message = "^fix", group = "Fixed" },
|
||||
{ message = "^.*: fix", group = "Fixed" },
|
||||
{ message = "^.*", group = "Changed" },
|
||||
]
|
||||
# Prevent commits that are breaking from being excluded by commit parsers.
|
||||
filter_commits = false
|
||||
# Order releases topologically instead of chronologically.
|
||||
topo_order = false
|
||||
# Order of commits in each group/release within the changelog.
|
||||
# Allowed values: newest, oldest
|
||||
sort_commits = "oldest"
|
||||
55
src/lib.rs
Normal file
55
src/lib.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
// flow-rbp: A library for packing rectangles into two-dimensional finite bins.
|
||||
// zlib License (see LICENSE)
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
//! This crates provides a library for packing rectangles into two-dimensional finite bins using
|
||||
//! different heuristic methods for placement.
|
||||
//!
|
||||
//! The two-dimensional rectangle bin packing is a classical problem in combinatorial optimization.
|
||||
//! In this problem, one is given a sequence of rectangles `(R1, R2, ... Rn), Ri = (wi, hi)` and
|
||||
//! the task is to find a packing of these items into a minimum number of bins of size `(W, H)`. No two
|
||||
//! rectangles may intersect or be contained inside one another. This library uses an algorithm
|
||||
//! sometimes referred as `The Maximal Rectangles ALgorithm`. This algorithm stores a list of free
|
||||
//! rectangles that represents the free area of the bin.
|
||||
//!
|
||||
//! Placement can be tweaked by using different heuristic methods such as
|
||||
//! [`ShortSideFit`](crate::rbp::FreeRectHeuristic::ShortSideFit),
|
||||
//! [`LongSideFit`](crate::rbp::FreeRectHeuristic::LongSideFit),
|
||||
//! [`AreaFit`](crate::rbp::FreeRectHeuristic::AreaFit),
|
||||
//! [`BottomLeft`](crate::rbp::FreeRectHeuristic::BottomLeft) and
|
||||
//! [`ContactPoint`](crate::rbp::FreeRectHeuristic::ContactPoint).
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```
|
||||
//! use flow_rbp::FreeRectHeuristic;
|
||||
//! use flow_rbp::RectsBinPack;
|
||||
//!
|
||||
//! // create a new bin of size 32x32 which allows rotation:
|
||||
//! let mut rbp = RectsBinPack::new(32, 32, true).unwrap();
|
||||
//!
|
||||
//! // make sure occupancy is zero:
|
||||
//! assert_eq!(rbp.get_occupancy(), 0.0);
|
||||
//!
|
||||
//! // add a few rects that should fit:
|
||||
//! assert_eq!(rbp.insert(16, 16, FreeRectHeuristic::BottomLeft).is_some(), true);
|
||||
//! assert_eq!(rbp.insert(16, 16, FreeRectHeuristic::BottomLeft).is_some(), true);
|
||||
//! assert_eq!(rbp.get_occupancy(), 0.5);
|
||||
//! assert_eq!(rbp.insert(16, 16, FreeRectHeuristic::BottomLeft).is_some(), true);
|
||||
//! assert_eq!(rbp.insert(16, 16, FreeRectHeuristic::BottomLeft).is_some(), true);
|
||||
//! assert_eq!(rbp.get_occupancy(), 1.0);
|
||||
//!
|
||||
//! // this rect will not fit and therefore returns None:
|
||||
//! assert_eq!(rbp.insert(1, 1, FreeRectHeuristic::BottomLeft).is_none(), true);
|
||||
//!
|
||||
//! ```
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod rbp;
|
||||
|
||||
// re-export types:
|
||||
pub use crate::rbp::FreeRectHeuristic;
|
||||
pub use crate::rbp::Rect2D;
|
||||
pub use crate::rbp::RectsBinPack;
|
||||
pub use crate::rbp::RectsBinPackError;
|
||||
786
src/rbp.rs
Normal file
786
src/rbp.rs
Normal file
|
|
@ -0,0 +1,786 @@
|
|||
// flow-rbp: A library for packing rectangles into two-dimensional finite bins.
|
||||
// zlib License (see LICENSE)
|
||||
|
||||
/// Specifies the different heuristic rules that can be used when deciding where to place a new
|
||||
/// rectangle.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum FreeRectHeuristic {
|
||||
/// Choose to pack `R` into such `Fi` that `min(wf - w, hf - h)` is the smallest. In other words, we
|
||||
/// minimize the length of the shorter leftover side.
|
||||
ShortSideFit,
|
||||
|
||||
/// Pack `R` into an `Fi` such that `max(wf - w, hf - h)` is the smallest. That is, we minimize
|
||||
/// the length of the longer leftover side.
|
||||
LongSideFit,
|
||||
|
||||
/// Pick the `Fi ∈ F` that is smallest in area to place the next rectangle `R` into. If there is a
|
||||
/// tie, we use the [`ShortSideFit`](crate::rbp::FreeRectHeuristic::ShortSideFit) rule to break it.
|
||||
AreaFit,
|
||||
|
||||
/// Orient and place each rectangle to the position where the y-coordinate of the top side of the
|
||||
/// rectangle is the smallest and if there are several such valid positions, pick the one that has
|
||||
/// the smallest x-coordinate value.
|
||||
BottomLeft,
|
||||
|
||||
/// Place `R` into a position where the length of the perimeter of `R` that is touched by the bin
|
||||
/// edge or by a previously packed rectangle is maximized.
|
||||
ContactPoint,
|
||||
}
|
||||
|
||||
/// Specifies the different error types that can occur.
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub enum RectsBinPackError {
|
||||
/// Invalid argument
|
||||
InvalidArg,
|
||||
}
|
||||
|
||||
/// Specifies the properties of a 2D rectangle.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Rect2D {
|
||||
/// is the x offset
|
||||
pub x: i32,
|
||||
|
||||
/// is the y offset
|
||||
pub y: i32,
|
||||
|
||||
/// is the width
|
||||
pub width: i32,
|
||||
|
||||
/// is the height
|
||||
pub height: i32,
|
||||
}
|
||||
|
||||
impl Rect2D {
|
||||
/// Instantiates a 2D rectangle of size (0, 0, 0, 0).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use flow_rbp::Rect2D;
|
||||
///
|
||||
/// let rect = Rect2D::new();
|
||||
/// assert_eq!(rect.x, 0);
|
||||
/// assert_eq!(rect.y, 0);
|
||||
/// assert_eq!(rect.width, 0);
|
||||
/// assert_eq!(rect.height, 0);
|
||||
/// ```
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Instantiates a 2D rectangle with given size properties.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `x` - is the x offset.
|
||||
/// * `y` - is the y offset.
|
||||
/// * `width` - is the width.
|
||||
/// * `height` - is the height.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// [`InvalidArg`](crate::rbp::RectsBinPackError::InvalidArg)
|
||||
/// is returned if `x < 0 || y < 0 || width <= 0 || height <= 0`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use flow_rbp::Rect2D;
|
||||
///
|
||||
/// let rect = Rect2D::with_details(0, 0, 32, 16).unwrap();
|
||||
/// assert_eq!(rect.x, 0);
|
||||
/// assert_eq!(rect.y, 0);
|
||||
/// assert_eq!(rect.width, 32);
|
||||
/// assert_eq!(rect.height, 16);
|
||||
///
|
||||
/// // this should fail:
|
||||
/// assert_eq!(Rect2D::with_details(0, 0, 0, 0).is_err(), true);
|
||||
/// ```
|
||||
pub fn with_details(
|
||||
x: i32,
|
||||
y: i32,
|
||||
width: i32,
|
||||
height: i32,
|
||||
) -> Result<Self, RectsBinPackError> {
|
||||
if x >= 0 && y >= 0 && width > 0 && height > 0 {
|
||||
Ok(Self {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
})
|
||||
} else {
|
||||
Err(RectsBinPackError::InvalidArg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Specifies the properties of a rectangle bin.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RectsBinPack {
|
||||
/// is the width of the bin
|
||||
width: i32,
|
||||
|
||||
/// is the height of the bin
|
||||
height: i32,
|
||||
|
||||
/// is the flag indicating whether rotation is allowed or not
|
||||
allow_flip: bool,
|
||||
|
||||
/// is the vector holding the used rects
|
||||
used_rects: Vec<Rect2D>,
|
||||
|
||||
/// is the vector holding the free rects
|
||||
free_rects: Vec<Rect2D>,
|
||||
}
|
||||
|
||||
impl RectsBinPack {
|
||||
/// Instantiates a empty bin of given size.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `width` - is the width of the bin
|
||||
/// * `height` - is the height of the bin
|
||||
/// * `allow_flip` - is the flag indicating whether the packing algorithm is allowed to rotate
|
||||
/// the input rectangle 90 degrees clockwise to consider a better placement.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// [`RectsBinPackError::InvalidArg`](crate::rbp::RectsBinPackError)
|
||||
/// is returned if `width <= 0 || height <= 0`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use flow_rbp::RectsBinPack;
|
||||
/// use flow_rbp::FreeRectHeuristic;
|
||||
///
|
||||
/// let mut rbp = RectsBinPack::new(32, 32, false).unwrap();
|
||||
/// assert_eq!(rbp.insert(16, 16, FreeRectHeuristic::BottomLeft).is_some(), true);
|
||||
/// assert_eq!(rbp.insert(33, 33, FreeRectHeuristic::BottomLeft).is_none(), true);
|
||||
/// ```
|
||||
pub fn new(width: i32, height: i32, allow_flip: bool) -> Result<Self, RectsBinPackError> {
|
||||
if width > 0 && height > 0 {
|
||||
Ok(Self {
|
||||
width,
|
||||
height,
|
||||
allow_flip,
|
||||
used_rects: Vec::new(),
|
||||
free_rects: vec![Rect2D::with_details(0, 0, width, height).unwrap()],
|
||||
})
|
||||
} else {
|
||||
Err(RectsBinPackError::InvalidArg)
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a single rectangle into the bin, possibly rotated.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `width` - is the rectangle width
|
||||
/// * `height` - is the rectangle height
|
||||
/// * `heuristic` - is the heuristic method to use when packing
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use flow_rbp::RectsBinPack;
|
||||
/// use flow_rbp::FreeRectHeuristic;
|
||||
///
|
||||
/// let mut rbp = RectsBinPack::new(32, 32, false).unwrap();
|
||||
/// assert_eq!(rbp.insert(16, 16, FreeRectHeuristic::BottomLeft).is_some(), true);
|
||||
/// assert_eq!(rbp.insert(33, 33, FreeRectHeuristic::BottomLeft).is_none(), true);
|
||||
/// ```
|
||||
pub fn insert(
|
||||
&mut self,
|
||||
width: i32,
|
||||
height: i32,
|
||||
heuristic: FreeRectHeuristic,
|
||||
) -> Option<Rect2D> {
|
||||
let output = match heuristic {
|
||||
FreeRectHeuristic::ShortSideFit => self.get_rect_for_best_short_side_fit(width, height),
|
||||
FreeRectHeuristic::BottomLeft => self.get_rect_for_bottom_left(width, height),
|
||||
FreeRectHeuristic::ContactPoint => self.get_rect_for_contact_point(width, height),
|
||||
FreeRectHeuristic::LongSideFit => self.get_rect_for_best_long_side_fit(width, height),
|
||||
FreeRectHeuristic::AreaFit => self.get_rect_for_best_area_fit(width, height),
|
||||
};
|
||||
|
||||
if let Some(new_rect) = output {
|
||||
let mut i: usize = 0;
|
||||
while i < self.free_rects.len() {
|
||||
if let Some(free_rect) = self.free_rects.get(i) {
|
||||
if self.is_split_free_node(&free_rect.clone(), &new_rect) {
|
||||
self.free_rects.remove(i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
self.prune_free_list();
|
||||
self.used_rects.push(new_rect.clone());
|
||||
|
||||
return Some(new_rect);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the ratio of used surface area to the total bin area.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use flow_rbp::RectsBinPack;
|
||||
/// use flow_rbp::FreeRectHeuristic;
|
||||
///
|
||||
/// let mut rbp = RectsBinPack::new(32, 32, false).unwrap();
|
||||
///
|
||||
/// // occupancy should be 0.0 initially:
|
||||
/// assert_eq!(rbp.get_occupancy(), 0.0);
|
||||
///
|
||||
/// assert_eq!(rbp.insert(16, 16, FreeRectHeuristic::BottomLeft).is_some(), true);
|
||||
/// assert_eq!(rbp.get_occupancy(), 0.25);
|
||||
/// assert_eq!(rbp.insert(16, 16, FreeRectHeuristic::BottomLeft).is_some(), true);
|
||||
/// assert_eq!(rbp.get_occupancy(), 0.5);
|
||||
/// assert_eq!(rbp.insert(16, 16, FreeRectHeuristic::BottomLeft).is_some(), true);
|
||||
/// assert_eq!(rbp.get_occupancy(), 0.75);
|
||||
/// assert_eq!(rbp.insert(16, 16, FreeRectHeuristic::BottomLeft).is_some(), true);
|
||||
///
|
||||
/// // occupancy should now be full as in 1.0:
|
||||
/// assert_eq!(rbp.get_occupancy(), 1.0);
|
||||
/// ```
|
||||
pub fn get_occupancy(&self) -> f32 {
|
||||
let mut used_surface_area: i32 = 0;
|
||||
|
||||
for i in 0..self.used_rects.len() {
|
||||
if let Some(rect) = self.used_rects.get(i) {
|
||||
used_surface_area += rect.width * rect.height;
|
||||
}
|
||||
}
|
||||
|
||||
// return occupancy:
|
||||
return used_surface_area as f32 / (self.width * self.height) as f32;
|
||||
}
|
||||
|
||||
/// Computes the placement score for the contact point variant.
|
||||
fn get_score_for_contact_point(&self, x: i32, y: i32, width: i32, height: i32) -> i32 {
|
||||
let mut score: i32 = 0;
|
||||
|
||||
if x == 0 || x + width == self.width {
|
||||
score += height;
|
||||
}
|
||||
|
||||
if y == 0 || y + height == self.height {
|
||||
score += width;
|
||||
}
|
||||
|
||||
for i in 0..self.used_rects.len() {
|
||||
if let Some(used_rect) = self.used_rects.get(i) {
|
||||
if used_rect.x == x + width || used_rect.x + used_rect.width == x {
|
||||
score += self.get_common_interval_len(
|
||||
used_rect.y,
|
||||
used_rect.y + used_rect.height,
|
||||
y,
|
||||
y + height,
|
||||
);
|
||||
}
|
||||
|
||||
if used_rect.y == y + height || used_rect.y + used_rect.height == y {
|
||||
score += self.get_common_interval_len(
|
||||
used_rect.x,
|
||||
used_rect.x + used_rect.width,
|
||||
x,
|
||||
x + width,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
/// Computes the rect for bottom left placement variant.
|
||||
fn get_rect_for_bottom_left(&self, width: i32, height: i32) -> Option<Rect2D> {
|
||||
let mut new_rect = Rect2D::new();
|
||||
|
||||
let mut best_x = std::i32::MAX;
|
||||
let mut best_y = std::i32::MAX;
|
||||
|
||||
for i in 0..self.free_rects.len() {
|
||||
if let Some(free_rect) = self.free_rects.get(i) {
|
||||
// true to place the rect in upright (non-flipped) orientation:
|
||||
if free_rect.width >= width && free_rect.height >= height {
|
||||
let top_side_y = free_rect.y + height;
|
||||
|
||||
if top_side_y < best_y || (top_side_y == best_y && free_rect.x < best_x) {
|
||||
new_rect.x = free_rect.x;
|
||||
new_rect.y = free_rect.y;
|
||||
new_rect.width = width;
|
||||
new_rect.height = height;
|
||||
best_x = free_rect.x;
|
||||
best_y = top_side_y;
|
||||
}
|
||||
}
|
||||
|
||||
if self.allow_flip && free_rect.width >= height && free_rect.height >= width {
|
||||
let top_side_y = free_rect.y + width;
|
||||
|
||||
if top_side_y < best_y || (top_side_y == best_y && free_rect.x < best_x) {
|
||||
new_rect.x = free_rect.x;
|
||||
new_rect.y = free_rect.y;
|
||||
new_rect.width = height;
|
||||
new_rect.height = width;
|
||||
best_x = free_rect.x;
|
||||
best_y = top_side_y;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
if new_rect.height == 0 || new_rect.width == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
return Some(new_rect);
|
||||
}
|
||||
|
||||
/// Computes the rect for short side fit variant.
|
||||
fn get_rect_for_best_short_side_fit(&self, width: i32, height: i32) -> Option<Rect2D> {
|
||||
let mut new_rect = Rect2D::new();
|
||||
|
||||
let mut best_short_side_fit = std::i32::MAX;
|
||||
let mut best_long_side_fit = std::i32::MAX;
|
||||
|
||||
for i in 0..self.free_rects.len() {
|
||||
if let Some(free_rect) = self.free_rects.get(i) {
|
||||
// try to place the rect in upright (non-flipped) orientation:
|
||||
if free_rect.width >= width && free_rect.height >= height {
|
||||
let left_over_horiz = free_rect.width - width;
|
||||
let left_over_vert = free_rect.height - height;
|
||||
let short_side_fit = std::cmp::min(left_over_horiz, left_over_vert);
|
||||
let long_side_fit = std::cmp::max(left_over_horiz, left_over_vert);
|
||||
|
||||
if short_side_fit < best_short_side_fit
|
||||
|| (short_side_fit == best_short_side_fit
|
||||
&& long_side_fit < best_long_side_fit)
|
||||
{
|
||||
new_rect.x = free_rect.x;
|
||||
new_rect.y = free_rect.y;
|
||||
new_rect.width = width;
|
||||
new_rect.height = height;
|
||||
best_short_side_fit = short_side_fit;
|
||||
best_long_side_fit = long_side_fit;
|
||||
}
|
||||
}
|
||||
|
||||
if self.allow_flip && free_rect.width >= height && free_rect.height >= width {
|
||||
let flipped_left_over_horiz = free_rect.width - height;
|
||||
let flipped_left_over_vert = free_rect.height - width;
|
||||
let flipped_short_side_fit =
|
||||
std::cmp::min(flipped_left_over_horiz, flipped_left_over_vert);
|
||||
let flipped_long_side_fit =
|
||||
std::cmp::max(flipped_left_over_horiz, flipped_left_over_vert);
|
||||
|
||||
if flipped_short_side_fit < best_short_side_fit
|
||||
|| (flipped_short_side_fit == best_short_side_fit
|
||||
&& flipped_long_side_fit < best_long_side_fit)
|
||||
{
|
||||
new_rect.x = free_rect.x;
|
||||
new_rect.y = free_rect.y;
|
||||
new_rect.width = height;
|
||||
new_rect.height = width;
|
||||
best_short_side_fit = flipped_short_side_fit;
|
||||
best_long_side_fit = flipped_long_side_fit;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
if new_rect.height == 0 || new_rect.width == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
return Some(new_rect);
|
||||
}
|
||||
|
||||
/// Computes the rect for long side fit variant.
|
||||
fn get_rect_for_best_long_side_fit(&self, width: i32, height: i32) -> Option<Rect2D> {
|
||||
let mut new_rect = Rect2D::new();
|
||||
|
||||
let mut best_short_side_fit = std::i32::MAX;
|
||||
let mut best_long_side_fit = std::i32::MAX;
|
||||
|
||||
for i in 0..self.free_rects.len() {
|
||||
if let Some(free_rect) = self.free_rects.get(i) {
|
||||
// try to place the rect in upright (non-flipped) orientation:
|
||||
if free_rect.width >= width && free_rect.height >= height {
|
||||
let left_over_horiz = free_rect.width - width;
|
||||
let left_over_vert = free_rect.height - height;
|
||||
let short_side_fit = std::cmp::min(left_over_horiz, left_over_vert);
|
||||
let long_side_fit = std::cmp::max(left_over_horiz, left_over_vert);
|
||||
|
||||
if long_side_fit < best_long_side_fit
|
||||
|| (long_side_fit == best_long_side_fit
|
||||
&& short_side_fit < best_short_side_fit)
|
||||
{
|
||||
new_rect.x = free_rect.x;
|
||||
new_rect.y = free_rect.y;
|
||||
new_rect.width = width;
|
||||
new_rect.height = height;
|
||||
best_short_side_fit = short_side_fit;
|
||||
best_long_side_fit = long_side_fit;
|
||||
}
|
||||
}
|
||||
|
||||
if self.allow_flip && free_rect.width >= height && free_rect.height >= width {
|
||||
let left_over_horiz = free_rect.width - height;
|
||||
let left_over_vert = free_rect.height - width;
|
||||
let short_side_fit = std::cmp::min(left_over_horiz, left_over_vert);
|
||||
let long_side_fit = std::cmp::max(left_over_horiz, left_over_vert);
|
||||
|
||||
if long_side_fit < best_long_side_fit
|
||||
|| (long_side_fit == best_long_side_fit
|
||||
&& short_side_fit < best_short_side_fit)
|
||||
{
|
||||
new_rect.x = free_rect.x;
|
||||
new_rect.y = free_rect.y;
|
||||
new_rect.width = height;
|
||||
new_rect.height = width;
|
||||
best_short_side_fit = short_side_fit;
|
||||
best_long_side_fit = long_side_fit;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
if new_rect.height == 0 || new_rect.width == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
return Some(new_rect);
|
||||
}
|
||||
|
||||
/// Computes the rect for best area fit variant.
|
||||
fn get_rect_for_best_area_fit(&self, width: i32, height: i32) -> Option<Rect2D> {
|
||||
let mut new_rect = Rect2D::new();
|
||||
|
||||
let mut best_area_fit = std::i32::MAX;
|
||||
let mut best_short_side_fit = std::i32::MAX;
|
||||
|
||||
for i in 0..self.free_rects.len() {
|
||||
if let Some(free_rect) = self.free_rects.get(i) {
|
||||
let area_fit = free_rect.width * free_rect.height - width * height;
|
||||
|
||||
// try to place rect in upright (non-flipped) orientation:
|
||||
if free_rect.width >= width && free_rect.height >= height {
|
||||
let left_over_horiz = free_rect.width - width;
|
||||
let left_over_vert = free_rect.height - height;
|
||||
let short_side_fit = std::cmp::min(left_over_horiz, left_over_vert);
|
||||
|
||||
if area_fit < best_area_fit
|
||||
|| (area_fit == best_area_fit && short_side_fit < best_short_side_fit)
|
||||
{
|
||||
new_rect.x = free_rect.x;
|
||||
new_rect.y = free_rect.y;
|
||||
new_rect.width = width;
|
||||
new_rect.height = height;
|
||||
best_short_side_fit = short_side_fit;
|
||||
best_area_fit = area_fit;
|
||||
}
|
||||
}
|
||||
|
||||
if self.allow_flip && free_rect.width >= height && free_rect.height >= width {
|
||||
let left_over_horiz = free_rect.width - height;
|
||||
let left_over_vert = free_rect.height - width;
|
||||
let short_side_fit = std::cmp::min(left_over_horiz, left_over_vert);
|
||||
|
||||
if area_fit < best_area_fit
|
||||
|| (area_fit == best_area_fit && short_side_fit < best_short_side_fit)
|
||||
{
|
||||
new_rect.x = free_rect.x;
|
||||
new_rect.y = free_rect.y;
|
||||
new_rect.width = height;
|
||||
new_rect.height = width;
|
||||
best_short_side_fit = short_side_fit;
|
||||
best_area_fit = area_fit;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
if new_rect.height == 0 || new_rect.width == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
return Some(new_rect);
|
||||
}
|
||||
|
||||
/// Computes the rect for contact point variant.
|
||||
fn get_rect_for_contact_point(&self, width: i32, height: i32) -> Option<Rect2D> {
|
||||
let mut new_rect = Rect2D::new();
|
||||
let mut best_contact_score = -1;
|
||||
|
||||
for i in 0..self.free_rects.len() {
|
||||
if let Some(free_rect) = self.free_rects.get(i) {
|
||||
// try to place the rect in upright (non-flipped) orientation:
|
||||
if free_rect.width >= width && free_rect.height >= height {
|
||||
let contact_score =
|
||||
self.get_score_for_contact_point(free_rect.x, free_rect.y, width, height);
|
||||
|
||||
if contact_score > best_contact_score {
|
||||
new_rect.x = free_rect.x;
|
||||
new_rect.y = free_rect.y;
|
||||
new_rect.width = width;
|
||||
new_rect.height = height;
|
||||
best_contact_score = contact_score;
|
||||
}
|
||||
}
|
||||
|
||||
if self.allow_flip && free_rect.width >= height && free_rect.height >= width {
|
||||
let contact_score =
|
||||
self.get_score_for_contact_point(free_rect.x, free_rect.y, height, width);
|
||||
|
||||
if contact_score > best_contact_score {
|
||||
new_rect.x = free_rect.x;
|
||||
new_rect.y = free_rect.y;
|
||||
new_rect.width = height;
|
||||
new_rect.height = width;
|
||||
best_contact_score = contact_score;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
if new_rect.height == 0 || new_rect.width == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
return Some(new_rect);
|
||||
}
|
||||
|
||||
/// returns true if the free rect was split
|
||||
fn is_split_free_node(&mut self, free_rect: &Rect2D, used_rect: &Rect2D) -> bool {
|
||||
// test with SAT if the rects even intersect:
|
||||
if used_rect.x >= free_rect.x + free_rect.width
|
||||
|| used_rect.x + used_rect.width <= free_rect.x
|
||||
|| used_rect.y >= free_rect.y + free_rect.height
|
||||
|| used_rect.y + used_rect.height <= free_rect.y
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if used_rect.x < free_rect.x + free_rect.width
|
||||
&& used_rect.x + used_rect.width > free_rect.x
|
||||
{
|
||||
// new node at the top side of the used node:
|
||||
if used_rect.y > free_rect.y && used_rect.y < free_rect.y + free_rect.height {
|
||||
let mut new_rect = free_rect.clone();
|
||||
new_rect.height = used_rect.y - new_rect.y;
|
||||
self.free_rects.push(new_rect);
|
||||
}
|
||||
|
||||
// new node at the bottom side of the used node:
|
||||
if used_rect.y + used_rect.height < free_rect.y + free_rect.height {
|
||||
let mut new_rect = free_rect.clone();
|
||||
new_rect.y = used_rect.y + used_rect.height;
|
||||
new_rect.height = free_rect.y + free_rect.height - (used_rect.y + used_rect.height);
|
||||
self.free_rects.push(new_rect);
|
||||
}
|
||||
}
|
||||
|
||||
if used_rect.y < free_rect.y + free_rect.height
|
||||
&& used_rect.y + used_rect.height > free_rect.y
|
||||
{
|
||||
// new node at the left side of the used node:
|
||||
if used_rect.x > free_rect.x && used_rect.x < free_rect.x + free_rect.width {
|
||||
let mut new_rect = free_rect.clone();
|
||||
new_rect.width = used_rect.x - new_rect.x;
|
||||
self.free_rects.push(new_rect);
|
||||
}
|
||||
|
||||
// new node at the right side of the used node:
|
||||
if used_rect.x + used_rect.width < free_rect.x + free_rect.width {
|
||||
let mut new_rect = free_rect.clone();
|
||||
new_rect.x = used_rect.x + used_rect.width;
|
||||
new_rect.width = free_rect.x + free_rect.width - (used_rect.x + used_rect.width);
|
||||
self.free_rects.push(new_rect);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// goes through the free rect list and removes any redundant entries
|
||||
fn prune_free_list(&mut self) {
|
||||
// go through each pair and remove any rects that are redundant:
|
||||
let mut keep: Vec<bool> = vec![true; self.free_rects.len()];
|
||||
for i in 0..self.free_rects.len() {
|
||||
for j in i + 1..self.free_rects.len() {
|
||||
if let Some(free_rect_i) = self.free_rects.get(i)
|
||||
&& let Some(free_rect_j) = self.free_rects.get(j)
|
||||
{
|
||||
if self.is_contained_on(free_rect_i, free_rect_j) {
|
||||
if let Some(value) = keep.get_mut(i) {
|
||||
*value = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if self.is_contained_on(free_rect_j, free_rect_i) {
|
||||
if let Some(value) = keep.get_mut(j) {
|
||||
*value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove all items marked false:
|
||||
let mut iter = keep.iter();
|
||||
self.free_rects.retain(|_| *iter.next().unwrap());
|
||||
}
|
||||
|
||||
/// determine whether rect A is contained on rect B
|
||||
fn is_contained_on(&self, a: &Rect2D, b: &Rect2D) -> bool {
|
||||
return a.x >= b.x
|
||||
&& a.y >= b.y
|
||||
&& a.x + a.width <= b.x + b.width
|
||||
&& a.y + a.height <= b.y + b.height;
|
||||
}
|
||||
|
||||
/// returns 0 if the two intervals i1 and i2 are disjoint, or the length of their
|
||||
/// overlap otherwise.
|
||||
fn get_common_interval_len(
|
||||
&self,
|
||||
i1_start: i32,
|
||||
i1_end: i32,
|
||||
i2_start: i32,
|
||||
i2_end: i32,
|
||||
) -> i32 {
|
||||
if i1_end < i2_start || i2_end < i1_start {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return std::cmp::min(i1_end, i2_end) - std::cmp::max(i1_start, i2_start);
|
||||
}
|
||||
} // impl RectsBinPack
|
||||
|
||||
// unit tests:
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn rect2d_basics() {
|
||||
let rect = Rect2D::new();
|
||||
|
||||
assert_eq!(rect.x, 0);
|
||||
assert_eq!(rect.y, 0);
|
||||
assert_eq!(rect.width, 0);
|
||||
assert_eq!(rect.height, 0);
|
||||
|
||||
let rect = Rect2D::with_details(2, 4, 16, 32).unwrap();
|
||||
|
||||
assert_eq!(rect.x, 2);
|
||||
assert_eq!(rect.y, 4);
|
||||
assert_eq!(rect.width, 16);
|
||||
assert_eq!(rect.height, 32);
|
||||
|
||||
assert_eq!(
|
||||
Rect2D::with_details(-1, 4, 16, 32).unwrap_err(),
|
||||
RectsBinPackError::InvalidArg
|
||||
);
|
||||
assert_eq!(
|
||||
Rect2D::with_details(0, -1, 16, 32).unwrap_err(),
|
||||
RectsBinPackError::InvalidArg
|
||||
);
|
||||
assert_eq!(
|
||||
Rect2D::with_details(0, 0, 0, 32).unwrap_err(),
|
||||
RectsBinPackError::InvalidArg
|
||||
);
|
||||
assert_eq!(
|
||||
Rect2D::with_details(2, 4, 16, 0).unwrap_err(),
|
||||
RectsBinPackError::InvalidArg
|
||||
);
|
||||
assert_eq!(
|
||||
Rect2D::with_details(-1, -1, 0, 0).unwrap_err(),
|
||||
RectsBinPackError::InvalidArg
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rbp_invalid_arg() {
|
||||
assert_eq!(
|
||||
RectsBinPack::new(0, 0, false).unwrap_err(),
|
||||
RectsBinPackError::InvalidArg
|
||||
);
|
||||
assert_eq!(
|
||||
RectsBinPack::new(32, 0, false).unwrap_err(),
|
||||
RectsBinPackError::InvalidArg
|
||||
);
|
||||
assert_eq!(
|
||||
RectsBinPack::new(0, 32, false).unwrap_err(),
|
||||
RectsBinPackError::InvalidArg
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
RectsBinPack::new(0, 0, true).unwrap_err(),
|
||||
RectsBinPackError::InvalidArg
|
||||
);
|
||||
assert_eq!(
|
||||
RectsBinPack::new(32, 0, true).unwrap_err(),
|
||||
RectsBinPackError::InvalidArg
|
||||
);
|
||||
assert_eq!(
|
||||
RectsBinPack::new(0, 32, true).unwrap_err(),
|
||||
RectsBinPackError::InvalidArg
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rbp_basics() {
|
||||
let mut rbp = RectsBinPack::new(32, 32, false).unwrap();
|
||||
assert_eq!(rbp.get_occupancy(), 0.0);
|
||||
|
||||
assert_eq!(
|
||||
rbp.insert(16, 16, FreeRectHeuristic::ShortSideFit)
|
||||
.is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
rbp.insert(16, 16, FreeRectHeuristic::LongSideFit).is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
rbp.insert(16, 16, FreeRectHeuristic::AreaFit).is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
rbp.insert(16, 16, FreeRectHeuristic::BottomLeft).is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rbp.get_occupancy(), 1.0);
|
||||
|
||||
assert_eq!(
|
||||
rbp.insert(1, 1, FreeRectHeuristic::ContactPoint).is_none(),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
254
tests/insert.rs
Normal file
254
tests/insert.rs
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
// flow-rbp: A library for packing rectangles into two-dimensional finite bins.
|
||||
// public domain License
|
||||
|
||||
use flow_rbp::FreeRectHeuristic;
|
||||
use flow_rbp::RectsBinPack;
|
||||
|
||||
#[test]
|
||||
fn insert_short_side_fit() {
|
||||
let mut rbp = RectsBinPack::new(32, 32, false).unwrap();
|
||||
assert_eq!(rbp.get_occupancy(), 0.0);
|
||||
|
||||
assert_eq!(
|
||||
rbp.insert(16, 16, FreeRectHeuristic::ShortSideFit)
|
||||
.is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rbp.get_occupancy(), 0.25);
|
||||
assert_eq!(
|
||||
rbp.insert(16, 16, FreeRectHeuristic::ShortSideFit)
|
||||
.is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rbp.get_occupancy(), 0.5);
|
||||
assert_eq!(
|
||||
rbp.insert(16, 16, FreeRectHeuristic::ShortSideFit)
|
||||
.is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rbp.get_occupancy(), 0.75);
|
||||
assert_eq!(
|
||||
rbp.insert(16, 16, FreeRectHeuristic::ShortSideFit)
|
||||
.is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rbp.get_occupancy(), 1.0);
|
||||
|
||||
assert_eq!(
|
||||
rbp.insert(1, 1, FreeRectHeuristic::ShortSideFit).is_none(),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_short_side_fit_rotated() {
|
||||
let mut rbp = RectsBinPack::new(32, 16, true).unwrap();
|
||||
assert_eq!(rbp.get_occupancy(), 0.0);
|
||||
|
||||
assert_eq!(
|
||||
rbp.insert(16, 32, FreeRectHeuristic::ShortSideFit)
|
||||
.is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rbp.get_occupancy(), 1.0);
|
||||
|
||||
assert_eq!(
|
||||
rbp.insert(1, 1, FreeRectHeuristic::ShortSideFit).is_none(),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_long_side_fit() {
|
||||
let mut rbp = RectsBinPack::new(32, 32, false).unwrap();
|
||||
assert_eq!(rbp.get_occupancy(), 0.0);
|
||||
|
||||
assert_eq!(
|
||||
rbp.insert(16, 16, FreeRectHeuristic::LongSideFit).is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rbp.get_occupancy(), 0.25);
|
||||
assert_eq!(
|
||||
rbp.insert(16, 16, FreeRectHeuristic::LongSideFit).is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rbp.get_occupancy(), 0.5);
|
||||
assert_eq!(
|
||||
rbp.insert(16, 16, FreeRectHeuristic::LongSideFit).is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rbp.get_occupancy(), 0.75);
|
||||
assert_eq!(
|
||||
rbp.insert(16, 16, FreeRectHeuristic::LongSideFit).is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rbp.get_occupancy(), 1.0);
|
||||
|
||||
assert_eq!(
|
||||
rbp.insert(1, 1, FreeRectHeuristic::LongSideFit).is_none(),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_long_side_fit_rotated() {
|
||||
let mut rbp = RectsBinPack::new(32, 16, true).unwrap();
|
||||
assert_eq!(rbp.get_occupancy(), 0.0);
|
||||
|
||||
assert_eq!(
|
||||
rbp.insert(16, 32, FreeRectHeuristic::LongSideFit).is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rbp.get_occupancy(), 1.0);
|
||||
|
||||
assert_eq!(
|
||||
rbp.insert(1, 1, FreeRectHeuristic::LongSideFit).is_none(),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_area_fit() {
|
||||
let mut rbp = RectsBinPack::new(32, 32, false).unwrap();
|
||||
assert_eq!(rbp.get_occupancy(), 0.0);
|
||||
|
||||
assert_eq!(
|
||||
rbp.insert(16, 16, FreeRectHeuristic::AreaFit).is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rbp.get_occupancy(), 0.25);
|
||||
assert_eq!(
|
||||
rbp.insert(16, 16, FreeRectHeuristic::AreaFit).is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rbp.get_occupancy(), 0.5);
|
||||
assert_eq!(
|
||||
rbp.insert(16, 16, FreeRectHeuristic::AreaFit).is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rbp.get_occupancy(), 0.75);
|
||||
assert_eq!(
|
||||
rbp.insert(16, 16, FreeRectHeuristic::AreaFit).is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rbp.get_occupancy(), 1.0);
|
||||
|
||||
assert_eq!(rbp.insert(1, 1, FreeRectHeuristic::AreaFit).is_none(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_area_fit_rotated() {
|
||||
let mut rbp = RectsBinPack::new(32, 16, true).unwrap();
|
||||
assert_eq!(rbp.get_occupancy(), 0.0);
|
||||
|
||||
assert_eq!(
|
||||
rbp.insert(16, 32, FreeRectHeuristic::AreaFit).is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rbp.get_occupancy(), 1.0);
|
||||
|
||||
assert_eq!(rbp.insert(1, 1, FreeRectHeuristic::AreaFit).is_none(), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_bottom_left() {
|
||||
let mut rbp = RectsBinPack::new(32, 32, false).unwrap();
|
||||
assert_eq!(rbp.get_occupancy(), 0.0);
|
||||
|
||||
assert_eq!(
|
||||
rbp.insert(16, 16, FreeRectHeuristic::BottomLeft).is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rbp.get_occupancy(), 0.25);
|
||||
assert_eq!(
|
||||
rbp.insert(16, 16, FreeRectHeuristic::BottomLeft).is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rbp.get_occupancy(), 0.5);
|
||||
assert_eq!(
|
||||
rbp.insert(16, 16, FreeRectHeuristic::BottomLeft).is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rbp.get_occupancy(), 0.75);
|
||||
assert_eq!(
|
||||
rbp.insert(16, 16, FreeRectHeuristic::BottomLeft).is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rbp.get_occupancy(), 1.0);
|
||||
|
||||
assert_eq!(
|
||||
rbp.insert(1, 1, FreeRectHeuristic::BottomLeft).is_none(),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_bottom_left_rotated() {
|
||||
let mut rbp = RectsBinPack::new(32, 16, true).unwrap();
|
||||
assert_eq!(rbp.get_occupancy(), 0.0);
|
||||
|
||||
assert_eq!(
|
||||
rbp.insert(16, 32, FreeRectHeuristic::BottomLeft).is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rbp.get_occupancy(), 1.0);
|
||||
|
||||
assert_eq!(
|
||||
rbp.insert(1, 1, FreeRectHeuristic::BottomLeft).is_none(),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_contact_point() {
|
||||
let mut rbp = RectsBinPack::new(32, 32, false).unwrap();
|
||||
assert_eq!(rbp.get_occupancy(), 0.0);
|
||||
|
||||
assert_eq!(
|
||||
rbp.insert(16, 16, FreeRectHeuristic::ContactPoint)
|
||||
.is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rbp.get_occupancy(), 0.25);
|
||||
assert_eq!(
|
||||
rbp.insert(16, 16, FreeRectHeuristic::ContactPoint)
|
||||
.is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rbp.get_occupancy(), 0.5);
|
||||
assert_eq!(
|
||||
rbp.insert(16, 16, FreeRectHeuristic::ContactPoint)
|
||||
.is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rbp.get_occupancy(), 0.75);
|
||||
assert_eq!(
|
||||
rbp.insert(16, 16, FreeRectHeuristic::ContactPoint)
|
||||
.is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rbp.get_occupancy(), 1.0);
|
||||
|
||||
assert_eq!(
|
||||
rbp.insert(1, 1, FreeRectHeuristic::ContactPoint).is_none(),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_contact_point_rotated() {
|
||||
let mut rbp = RectsBinPack::new(32, 16, true).unwrap();
|
||||
assert_eq!(rbp.get_occupancy(), 0.0);
|
||||
|
||||
assert_eq!(
|
||||
rbp.insert(16, 32, FreeRectHeuristic::ContactPoint)
|
||||
.is_some(),
|
||||
true
|
||||
);
|
||||
assert_eq!(rbp.get_occupancy(), 1.0);
|
||||
|
||||
assert_eq!(
|
||||
rbp.insert(1, 1, FreeRectHeuristic::ContactPoint).is_none(),
|
||||
true
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue