13 个不稳定版本 (3 个破坏性更新)
0.4.2 | 2021年5月3日 |
---|---|
0.4.1 | 2021年4月28日 |
0.3.0 | 2021年3月23日 |
0.2.2 | 2021年3月23日 |
0.1.5 | 2020年3月10日 |
#148 in 算法
52,067 每月下载量
用于 397 个 crate (13 直接使用)
88KB
1.5K SLoC
rectangle-pack

一个通用、确定性的箱装程序,旨在适应任何二维或三维用例。
rectangle-pack
是一个库,专注于将任意数量的较小矩形(包括二维和三维矩形棱柱)排列在任意数量的较大矩形内。
rectangle-pack
提供了一个 API,允许消费者控制矩形的装箱方式,从而可以根据其特定用例定制装箱。
虽然 rectangle-pack
最初是为纹理图集相关用例设计的 - 但库本身没有图像的概念,可以在任何矩形装箱场景中使用。
快速入门
# 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();
背景/初始动机
点击显示库的初始动机。在我的应用程序中,我已经将纹理动态放置到图集中,而不是像以前在资产编译步骤中那样,因此这里解释的一些问题现在已经无关紧要。
尽管如此,我仍然使用 rectangle-pack 来支持我的运行时纹理分配,同时结合一些其他策略,具体取决于需要放入图集的纹理的性质。
rectangle-pack 对纹理一无所知,因此您可以在运行时、离线步骤或其他任何您喜欢的时间使用它进行任何形式的箱装。
我正在制作一个游戏,该游戏具有以下纹理图集需求(截至 2020 年 3 月)
-
我需要确保某些纹理可以在同一个图集中使用。
- 例如 - 如果我正在使用混合图来渲染地形,该混合图将每个通道映射到颜色/金属-粗糙度/法线纹理,我想要所有这些纹理都在同一个图集中可用。否则在最坏的情况下,我可能需要十几个纹理统一体才能渲染单个地形块。
-
我希望在打包我的图集时能够控制使用哪些通道。
- 例如 - 我需要能够轻松地将我的金属和粗糙度纹理打包到每个通道中,而将颜色和法线通道打包到三个通道中。
- 这意味着我的矩形打包器需要公开配置,以显示目标框中可用的层数/通道数。
- 例如 - 我需要能够轻松地将我的金属和粗糙度纹理打包到每个通道中,而将颜色和法线通道打包到三个通道中。
-
我需要确保不常见的纹理不会占用常用图集的空间。
- 例如 - 如果一组纹理只用于游戏中的一个特定区域,那么它们就不应该在包含用于非常常见游戏元素的纹理的图集中占用空间。
- 这意味着打包器需要考虑某种组或优先级的概念,以便不常见的纹理可以与常见的纹理分开放置。
- 这使我们能够最大限度地减少在任何时候GPU内存中的纹理数量,因为在不使用一段时间后,包含不常见纹理的图集可以移除。
- 如果不能满足这一要求,一个大的纹理可能会无限期地占用GPU空间,因为它与永远不会被移除的非常常见的纹理共享图集。
- 请注意,我们可能无法在API级别实现这一点。这可以通过消费者多次调用库来实现,使用他们确定的任何输入矩形,只要这些矩形被认为是具有相似优先级的。
- 或者其他解决方案。
- 例如 - 如果一组纹理只用于游戏中的一个特定区域,那么它们就不应该在包含用于非常常见游戏元素的纹理的图集中占用空间。
-
我需要能够打包通道内的单个位。例如 - 如果我有一个纹理遮罩,它编码了一个片段是否是金属的,我希望能够将其打包到一个位中,可能在我的alpha通道中。
- 这意味着我们的层概念需要支持多维需求。层中的层。
- 例如 - 在颜色空间中,人们可能会考虑RGBA通道/层,或者在Alpha通道内考虑有255个不同的子层。或者更少的可变大小子层。我们的API需要使这种表示和打包变得简单。
- 我们内部不一定需要以这种方式建模,甚至不需要在API中公开多层概念 - 我们只需要启用这些用例 - 即使我们在API级别仍然将它们视为单维层。
- 实际上 .. 在我打字的时候 .. API级别的单维层听起来既简单又一致。让消费者考虑一个通道是否被视为一个层(即alpha)或255个层(即alpha通道中的每个位)。
- 这意味着我们的层概念需要支持多维需求。层中的层。
-
我需要能够允许一个纹理出现在多个图集中。
- 例如 - 假设有一个草纹理,它在游戏中的每个草地区域都使用。假设每个区域都有一些只在该区域使用的纹理,因此被分配到自己的图集。我们希望确保我们的草纹理被复制到每个这些纹理中,以便一个纹理可以满足该区域的需求,而不是两个。
这些要求是设计矩形打包API的初始指导原则。
API不应该知道这些要求的任何具体细节 - 它应该只提供实现这些要求的最低限度的功能。我们试图将尽可能多的功能推到用户空间,并让rectangle-pack
的责任仅限于回答
给定需要放置的这些矩形,放置它们的目标框的最大尺寸以及一些关于如何放置和如何不放置的准则,我可以在哪里放置所有这些矩形?
no_std
矩形打包支持 no_std
,通过禁用 std
功能来实现。
rectangle-pack = {version = "0.4", default-features = false}
禁用 std
功能具有以下作用。
BTreeMap
在原本使用HashMap
的地方被内部使用。
功能
-
将任意数量的2D/3D矩形放入任意数量的2D/3D目标箱子中。
- 通过基于宽度+高度+深度的API支持三维矩形。
-
通用的API,尽可能将功能推入用户空间,以实现最大的灵活性。
-
当需要确保某些矩形总是共享同一个箱子时,可以使用通用的组ID来分组矩形。
-
支持二维矩形(深度=1)。
-
用户提供的启发式算法,可以对打包算法进行完全控制。
-
零依赖性,使其更容易嵌入到更特定用例的库中,而不会引入冗余。
-
确定性打包。
- 使用相同的启发式算法和相同大小的目标箱子对相同的输入进行打包,将始终导致相同的布局。
- 这在需要可重复构建的地方很有用,例如生成基于内容哈希进行缓存的纹理图集。
- 使用相同的启发式算法和相同大小的目标箱子对相同的输入进行打包,将始终导致相同的布局。
-
能够移除放置的矩形并合并相邻的空闲空间。
未来工作
rectangle-pack
的第一个版本是为了满足我的个人需求而设计的。
因此,有一些可能很有用的功能,由于我没有需要,所以没有探索。
以下是一些未来可能有用的功能。
三维输入矩形旋转
当尝试将矩形放入最小的可用箱子部分时,我们可能希望旋转矩形以查看哪种方向最适合。
这可以通过以下方式实现:
-
API为每个传入的矩形公开了三个布尔值,分别是
allow_global_x_axis_rotation
、allow_global_y_axis_rotation
、allow_global_z_axis_rotation
。 -
假设这三个都启用了。在尝试放置矩形/箱子时,我们应该尝试所有6种可能的方向,然后选择最佳放置(基于
ComparePotentialContainersFn
启发式)。 -
向调用者返回有关哪个轴被旋转的信息。
互斥组
例如,确保某些矩形组不放入同一箱子。
也许你有两个盘子(箱子)和两组奶酪(矩形),一组给Alice,一组给Bob。
在打包时,你希望确保这些奶酪组各自进入不同的盘子,因为Alice和Bob不喜欢共享。
关于箱子打包的统计信息
例如,浪费的空间量等,这允许调用者比较不同目标箱子大小和启发式算法组合的结果,以查看哪个打包得最有效。
如果你有不受支持的用例,请直接提交问题或发送拉取请求。
打包算法
我们从 rectpack2D 中描述的算法开始,然后进行了一些调整,以支持我们灵活支持所有用例的目标。
-
启发式算法由调用者提供,而不是由
rectangle-pack
决定用户提供的启发式算法。 -
当将一个bin的可用部分分割成两个新的bin部分时,我们不会任意决定分割方式。相反,我们依据用户提供的
more_suitable_containers
启发式函数来决定。 -
这里有一个第三维度。
在野外
以下是一些已知的rectangle-pack
生产用户。
贡献
如果您有一个不受支持的用例、一个问题、一个补丁或其他任何东西,请直接打开一个问题或提交一个拉取请求。
测试
运行测试套件。
# Clone the repository
git clone [email protected]:chinedufn/rectangle-pack.git
cd rectangle-pack
# Run tests
cargo test
参见
- rectpack2D
- 我们的初始实现受到的启发部分