Crates.io | rectangle-pack |
lib.rs | rectangle-pack |
version | 0.4.2 |
source | src |
created_at | 2020-03-09 01:22:27.481424 |
updated_at | 2021-05-03 07:09:26.891615 |
description | A general purpose, deterministic bin packer designed to conform to any two or three dimensional use case. |
homepage | |
repository | https://github.com/chinedufn/rectangle-pack |
max_upload_size | |
id | 216723 |
size | 99,975 |
A general purpose, deterministic bin packer designed to conform to any two or three dimensional use case.
rectangle-pack
is a library focused on laying out any number of smaller rectangles (both 2d rectangles and 3d rectangular prisms) inside any number of larger rectangles.
rectangle-pack
exposes an API that gives the consumer control over how rectangles are packed - allowing them to tailor
the packing to their specific use case.
While rectangle-pack
was originally designed with texture atlas related use cases in mind - the library itself has no notions of images and can be used
in any rectangle packing context.
# In your Cargo.toml
rectangle-pack = "0.4"
//! A basic example of packing rectangles into target bins
use rectangle_pack::{
GroupedRectsToPlace,
RectToInsert,
pack_rects,
TargetBin,
volume_heuristic,
contains_smallest_box
};
use std::collections::BTreeMap;
// A rectangle ID just needs to meet these trait bounds (ideally also Copy).
// So you could use a String, PathBuf, or any other type that meets these
// trat bounds. You do not have to use a custom enum.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Ord, PartialOrd)]
enum MyCustomRectId {
RectOne,
RectTwo,
RectThree,
}
// A target bin ID just needs to meet these trait bounds (ideally also Copy)
// So you could use a u32, &str, or any other type that meets these
// trat bounds. You do not have to use a custom enum.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Ord, PartialOrd)]
enum MyCustomBinId {
DestinationBinOne,
DestinationBinTwo,
}
// A placement group just needs to meet these trait bounds (ideally also Copy).
//
// Groups allow you to ensure that a set of rectangles will be placed
// into the same bin. If this isn't possible an error is returned.
//
// Groups are optional.
//
// You could use an i32, &'static str, or any other type that meets these
// trat bounds. You do not have to use a custom enum.
#[derive(Debug, Hash, PartialEq, Eq, Clone, Ord, PartialOrd)]
enum MyCustomGroupId {
GroupIdOne
}
let mut rects_to_place = GroupedRectsToPlace::new();
rects_to_place.push_rect(
MyCustomRectId::RectOne,
Some(vec![MyCustomGroupId::GroupIdOne]),
RectToInsert::new(10, 20, 255)
);
rects_to_place.push_rect(
MyCustomRectId::RectTwo,
Some(vec![MyCustomGroupId::GroupIdOne]),
RectToInsert::new(5, 50, 255)
);
rects_to_place.push_rect(
MyCustomRectId::RectThree,
None,
RectToInsert::new(30, 30, 255)
);
let mut target_bins = BTreeMap::new();
target_bins.insert(MyCustomBinId::DestinationBinOne, TargetBin::new(2048, 2048, 255));
target_bins.insert(MyCustomBinId::DestinationBinTwo, TargetBin::new(4096, 4096, 1020));
// Information about where each `MyCustomRectId` was placed
let rectangle_placements = pack_rects(
&rects_to_place,
&mut target_bins,
&volume_heuristic,
&contains_smallest_box
).unwrap();
In my application I've switched to dynamically placing textures into atlases at runtime instead of in how I previously used an asset compilation step, so some of the problems explained here are now moot.
I still use rectangle-pack to power my runtime texture allocation, though, along with a handful of other strategies depending on the nature of the textures that need to be placed into the atlas.
rectangle-pack knows nothing about textures, so you can use it for any form of bin packing, whether at runtime, during an offline step or any other time you like.
I'm working on a game with some of the following texture atlas requirements (as of March 2020):
I need to be able to guarantee that certain textures are available in the same atlas.
I want to have control over which channels are used when I'm packing my atlases.
I need to be able to ensure that uncommon textures aren't taking up space in commonly used atlases
I need to be able to pack individual bits within a channel. For example - if I have a texture mask that encodes whether or not a fragment is metallic I want to be able to pack that into a single bit, perhaps within my alpha channel.
I need to be able to allow one texture to be present in multiple atlases.
These requirements are the initial guiding pillars to design the rectangle-pack API.
The API shouldn't know about the specifics of any of these requirements - it should just provide the bare minimum required to make them possible. We're trying to push as much into user-land as possible and leave
rectangle-pack
s responsibility to not much more than answering:
Given these rectangles that need to be placed, the maximum sizes of the target bins to place them in and some criteria about how to place and how not to place them, where can I put all of these rectangles?
rectangle-pack supports no_std
by disabling the std
feature.
rectangle-pack = {version = "0.4", default-features = false}
Disabling the std
feature does the following.
BTreeMap
s are used internally in places where HashMap
s would have been used.Place any number of 2d / 3d rectangles into any number of 2d / 3d target bins.
Generic API that pushes as much as possible into user-land for maximum flexibility.
Group rectangles using generic group id's when you need to ensure that certain rectangles will always end up sharing a bin with each other.
Supports two dimensional rectangles (depth = 1).
User provided heuristics to grant full control over the packing algorithm.
Zero dependencies, making it easier to embed it inside of a more use case specific library without introducing bloat.
Deterministic packing.
Ability to remove placed rectangles and coalesce neighboring free space.
The first version of rectangle-pack
was designed to meet my own needs.
As such there is functionality that could be useful that was not explored since I did not need it.
Here are some things that could be useful in the future.
When attempting to place a Rectangle into the smallest available bin section we might want to rotate the rectangle in order to see which orientation produces the best fit.
This could be accomplished by:
The API exposes three booleans for every incoming rectangles, allow_global_x_axis_rotation
, allow_global_y_axis_rotation
, allow_global_z_axis_rotation
.
Let's say all three are enabled. When attempting to place the rectangle/box we should attempt it in all 6 possible orientations and then select the best placement (based on the ComparePotentialContainersFn
heuristic).
Return information to the caller about which axis ended up being rotated.
An example of this is the ability to ensure that certain rectqngle groups are not placed in the same bins.
Perhaps you have two plates (bins) and two groups of cheese (rectangles), one for Alice and one for Bob.
When packing you want to ensure that these groups of cheese each end up in a different bin since Alice and Bob don't like to share.
Things such as the amount of wasted space - or anything else that would allow the caller to compare the results of different combinations of target bin sizes and heuristics to see which packed the most efficiently.
If you have a use case that isn't supported, go right ahead and open an issue or submit a pull request.
We started with the algorithm described in rectpack2D and then made some adjustments in order to support our goal of flexibly supporting all use cases.
The heuristic is provided by the caller instead of having rectangle-pack
decide on a user provided heuristic.
When splitting an available section of a bin into two new sections of a bin - we do not decide on how the split should occur arbitrarily.
Instead, we base it on the user provided more_suitable_containers
heuristic function.
There is a third dimension.
Here are some known production users of rectangle-pack
.
Akigi uses rectangle-pack
to power parts of its runtime texture allocation strategy.
Bevy uses rectangle-pack
to create texture atlases.
If you have a use case that isn't supported, a question, a patch, or anything else, go right ahead and open an issue or submit a pull request.
To run the test suite.
# Clone the repository
git clone git@github.com:chinedufn/rectangle-pack.git
cd rectangle-pack
# Run tests
cargo test