16#include <lagrange/utils/warnoff.h>
17#include <RectangleBinPack/GuillotineBinPack.h>
18#include <RectangleBinPack/Rect.h>
19#include <lagrange/utils/warnon.h>
22#include <lagrange/Logger.h>
23#include <lagrange/MeshTrait.h>
24#include <lagrange/common.h>
25#include <lagrange/legacy/inline.h>
26#include <lagrange/utils/DisjointSets.h>
27#include <lagrange/utils/assert.h>
28#include <lagrange/utils/range.h>
29#include <lagrange/utils/safe_cast.h>
30#include <lagrange/utils/timing.h>
31#include <lagrange/utils/warning.h>
41LA_NOSANITIZE_SIGNED_INTEGER_OVERFLOW
bool product_will_overflow(
volatile T x,
volatile T y)
44 if (x == 0)
return false;
45 return ((x * y) / x) != y;
49class PackingFailure :
public std::runtime_error
52 PackingFailure(
const std::string& what)
53 : std::runtime_error(what)
57template <
typename Scalar>
58std::tuple<Eigen::Matrix<Scalar, Eigen::Dynamic, 2, Eigen::RowMajor>, std::vector<bool>> pack_boxes(
59 const Eigen::Matrix<Scalar, Eigen::Dynamic, 2, Eigen::RowMajor>& bbox_mins,
60 const Eigen::Matrix<Scalar, Eigen::Dynamic, 2, Eigen::RowMajor>& bbox_maxs,
64#ifdef RECTANGLE_BIN_PACK_OSS
67 using Int = ::rbp::Int;
71 const auto num_boxes = bbox_mins.rows();
73 return std::make_tuple(
74 Eigen::Matrix<Scalar, Eigen::Dynamic, 2, Eigen::RowMajor>(0, 2),
78 const Scalar max_box_length = (bbox_maxs - bbox_mins).maxCoeff();
79 constexpr Int MAX_AREA = std::numeric_limits<Int>::max();
80 constexpr Int RESOLUTION = 1 << 12;
81 Int max_canvas_size = RESOLUTION;
82 Int min_canvas_size = RESOLUTION;
86 assert(internal::product_will_overflow<Int>(2, MAX_AREA));
88 std::vector<::rbp::RectSize> boxes;
89 boxes.reserve(num_boxes);
90 for (
auto i :
range(num_boxes)) {
92 auto& box = boxes.back();
93 box.width =
safe_cast<Int>(std::ceil((bbox_maxs(i, 0) - bbox_mins(i, 0)) / scale)) + margin;
95 safe_cast<Int>(std::ceil((bbox_maxs(i, 1) - bbox_mins(i, 1)) / scale)) + margin;
96 assert(!internal::product_will_overflow<Int>(box.width, box.height));
99 Eigen::Matrix<Scalar, Eigen::Dynamic, 2, Eigen::RowMajor> centers(num_boxes, 2);
100 std::vector<bool> flipped(num_boxes);
102 auto pack = [&](
int L,
bool trial) ->
bool {
103 assert(!internal::product_will_overflow<Int>(L, L));
104#ifdef RECTANGLE_BIN_PACK_OSS
107 "Disabling rotation is not supported with this version of RectangleBinPack!");
109 rbp::GuillotineBinPack packer(L, L);
111 rbp::GuillotineBinPack packer(L, L, allow_flip);
113 for (
auto i :
range(num_boxes)) {
114 auto rect = packer.Insert(
118 rbp::GuillotineBinPack::FreeRectChoiceHeuristic::RectBestAreaFit,
119 rbp::GuillotineBinPack::GuillotineSplitHeuristic::SplitMinimizeArea);
120 if (rect.width == 0 || rect.height == 0) {
124 const auto& packed_rect = packer.GetUsedRectangles();
125 logger().debug(
"num packed rectangles {}, expecting {}", packed_rect.size(), num_boxes);
126 if (
static_cast<decltype(num_boxes)
>(packed_rect.size()) != num_boxes)
return false;
129 for (
auto i :
range(num_boxes)) {
130 const auto& r = packed_rect[i];
131 flipped[i] = r.width != boxes[i].width;
132 assert(allow_flip || !flipped[i]);
133 centers(i, 0) = (r.x + r.width * 0.5f) * scale;
134 centers(i, 1) = (r.y + r.height * 0.5f) * scale;
141 while (!pack(max_canvas_size,
true)) {
142 min_canvas_size = max_canvas_size;
143 max_canvas_size *= 2;
144 if (MAX_AREA / max_canvas_size <= max_canvas_size) {
146 throw PackingFailure(
"Cannot pack even with canvas at max area!");
151 while (min_canvas_size < max_canvas_size) {
152 const auto l = (min_canvas_size + max_canvas_size) / 2;
153 if (l == min_canvas_size) {
163 bool r = pack(max_canvas_size,
false);
166 return std::make_tuple(centers, flipped);
174#ifndef RECTANGLE_BIN_PACK_OSS
191template <
typename MeshType>
192void compute_rectangle_packing(MeshType& mesh,
const PackingOptions& options)
194 static_assert(MeshTrait<MeshType>::is_mesh(),
"Input is not a Mesh object");
195 using Scalar =
typename MeshType::Scalar;
196 using Index =
typename MeshType::Index;
201 auto uv_mesh = mesh.get_uv_mesh();
202 auto uvs = uv_mesh->get_vertices();
203 const auto& facets = uv_mesh->get_facets();
204 const auto num_vertices = uv_mesh->get_num_vertices();
205 const auto num_facets = uv_mesh->get_num_facets();
206 const auto vertex_per_facet = uv_mesh->get_vertex_per_facet();
209 for (
auto i :
range(num_facets)) {
210 for (
auto j :
range(vertex_per_facet)) {
211 components.
merge(facets(i, j), facets(i, (j + 1) % vertex_per_facet));
214 std::vector<Index> per_vertex_comp_ids;
217 Eigen::Matrix<Scalar, Eigen::Dynamic, 2, Eigen::RowMajor> bbox_mins(num_comps, 2);
218 Eigen::Matrix<Scalar, Eigen::Dynamic, 2, Eigen::RowMajor> bbox_maxs(num_comps, 2);
219 bbox_mins.setConstant(std::numeric_limits<Scalar>::max());
220 bbox_maxs.setConstant(std::numeric_limits<Scalar>::lowest());
221 for (
auto i :
range(num_vertices)) {
222 const auto comp_id = per_vertex_comp_ids[i];
223 if (comp_id ==
static_cast<Index
>(num_comps))
continue;
224 bbox_mins(comp_id, 0) = std::min(bbox_mins(comp_id, 0), uvs(i, 0));
225 bbox_maxs(comp_id, 0) = std::max(bbox_maxs(comp_id, 0), uvs(i, 0));
226 bbox_mins(comp_id, 1) = std::min(bbox_mins(comp_id, 1), uvs(i, 1));
227 bbox_maxs(comp_id, 1) = std::max(bbox_maxs(comp_id, 1), uvs(i, 1));
230 Eigen::Matrix<Scalar, Eigen::Dynamic, 2, Eigen::RowMajor> centers(num_comps, 2);
231 std::vector<bool> flipped(num_comps);
232#ifdef RECTANGLE_BIN_PACK_OSS
233 bool allow_flip =
true;
237 std::tie(centers, flipped) = pack_boxes(bbox_mins, bbox_maxs, allow_flip, options.
margin);
239 Eigen::Matrix<Scalar, 2, 2> rot90;
240 rot90 << 0, -1, 1, 0;
241 for (
auto i :
range(num_vertices)) {
242 const auto comp_id = per_vertex_comp_ids[i];
244 if (comp_id ==
static_cast<Index
>(num_comps)) {
246 uvs.row(i).setZero();
250 const Eigen::Matrix<Scalar, 1, 2> comp_center =
251 (bbox_mins.row(comp_id) + bbox_maxs.row(comp_id)) * 0.5;
252 if (!flipped[comp_id]) {
253 uvs.row(i) = (uvs.row(i) - comp_center) + centers.row(comp_id);
255 uvs.row(i) = (uvs.row(i) - comp_center) * rot90 + centers.row(comp_id);
259 const auto all_bbox_min = uvs.colwise().minCoeff().eval();
260 const auto all_bbox_max = uvs.colwise().maxCoeff().eval();
261 const Scalar s = options.
normalize ? (all_bbox_max - all_bbox_min).maxCoeff() : 1;
263 uvs = (uvs.rowwise() - all_bbox_min) / s;
264 mesh.initialize_uv(uvs, facets);
265 timer.
tock(
"Packing uv");
268template <
typename MeshType>
269void compute_rectangle_packing(MeshType& mesh)
272 return compute_rectangle_packing(mesh, opt);
285template <
typename MeshTypePtr>
286void compute_rectangle_packing(std::vector<MeshTypePtr>& meshes_2d,
const PackingOptions& options)
288 using MeshType =
typename std::pointer_traits<MeshTypePtr>::element_type;
289 using Scalar =
typename MeshType::Scalar;
290 using VertexArray =
typename MeshType::VertexArray;
291 static_assert(MeshTrait<MeshType>::is_mesh(),
"Input is not a Mesh object");
293 const auto num_meshes = meshes_2d.size();
294 Eigen::Matrix<Scalar, Eigen::Dynamic, 2, Eigen::RowMajor> bbox_mins(num_meshes, 2);
295 Eigen::Matrix<Scalar, Eigen::Dynamic, 2, Eigen::RowMajor> bbox_maxs(num_meshes, 2);
296 bbox_mins.setConstant(std::numeric_limits<Scalar>::max());
297 bbox_maxs.setConstant(std::numeric_limits<Scalar>::lowest());
299 for (
auto i :
range(num_meshes)) {
300 const auto& vertices = meshes_2d[i]->get_vertices();
301 bbox_mins.row(i) = vertices.colwise().minCoeff();
302 bbox_maxs.row(i) = vertices.colwise().maxCoeff();
305 Eigen::Matrix<Scalar, Eigen::Dynamic, 2, Eigen::RowMajor> centers(num_meshes, 2);
306 std::vector<bool> flipped(num_meshes);
307#ifdef RECTANGLE_BIN_PACK_OSS
308 bool allow_flip =
false;
310 bool allow_flip = options.allow_flip;
312 std::tie(centers, flipped) = pack_boxes(bbox_mins, bbox_maxs, allow_flip, options.margin);
314 Eigen::Matrix<Scalar, 2, 2> rot90;
315 rot90 << 0, -1, 1, 0;
317 Eigen::Matrix<Scalar, 1, 2> all_bbox_min;
318 Eigen::Matrix<Scalar, 1, 2> all_bbox_max;
319 all_bbox_min.setConstant(std::numeric_limits<Scalar>::max());
320 all_bbox_max.setConstant(std::numeric_limits<Scalar>::lowest());
322 for (
auto i :
range(num_meshes)) {
323 const auto comp_center = (bbox_mins.row(i) + bbox_maxs.row(i)) * 0.5;
324 VertexArray vertices;
325 meshes_2d[i]->export_vertices(vertices);
328 vertices.rowwise() += centers.row(i) - comp_center;
331 ((vertices.rowwise() - comp_center).transpose() * rot90).transpose().rowwise() +
334 all_bbox_min = all_bbox_min.array().min(vertices.colwise().minCoeff().array());
335 all_bbox_max = all_bbox_max.array().max(vertices.colwise().maxCoeff().array());
336 meshes_2d[i]->import_vertices(vertices);
339 const Scalar s = options.normalize ? (all_bbox_max - all_bbox_min).maxCoeff() : 1;
341 for (
auto i :
range(num_meshes)) {
342 VertexArray vertices;
343 meshes_2d[i]->export_vertices(vertices);
344 vertices = vertices / s;
345 meshes_2d[i]->import_vertices(vertices);
Disjoint sets computation.
Definition DisjointSets.h:32
IndexType merge(IndexType i, IndexType j)
Merge the disjoint set containing entry i and the disjoint set containing entry j.
Definition DisjointSets.h:80
size_t extract_disjoint_set_indices(std::vector< IndexType > &index_map)
Assign all elements their disjoint set index.
Definition DisjointSets.cpp:64
Creates a verbose timer that prints after tock().
Definition timing.h:49
void tick()
Starts the timer.
Definition timing.h:70
double tock(const std::string name="")
Stops the timer.
Definition timing.h:79
Definition compute_rectangle_packing.h:50
LA_CORE_API spdlog::logger & logger()
Retrieves the current logger.
Definition Logger.cpp:40
@ Scalar
Mesh attribute must have exactly 1 channel.
Definition AttributeFwd.h:56
#define la_runtime_assert(...)
Runtime assertion check.
Definition assert.h:174
internal::Range< Index > range(Index end)
Returns an iterable object representing the range [0, end).
Definition range.h:176
constexpr auto safe_cast(SourceType value) -> std::enable_if_t<!std::is_same< SourceType, TargetType >::value, TargetType >
Perform safe cast from SourceType to TargetType, where "safe" means:
Definition safe_cast.h:50
Main namespace for Lagrange.
PackingOptions allows one to customize packing options.
Definition compute_rectangle_packing.h:173
int margin
Minimum allowed distance (kind of) between two boxes.
Definition compute_rectangle_packing.h:178
bool allow_flip
Whether to allow box to rotate by 90 degree when packing.
Definition compute_rectangle_packing.h:175
bool normalize
Should the output be normalized to fit into a unit box.
Definition compute_rectangle_packing.h:177