Lagrange
Loading...
Searching...
No Matches
compute_rectangle_packing.h
1/*
2 * Copyright 2020 Adobe. All rights reserved.
3 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may obtain a copy
5 * of the License at http://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software distributed under
8 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 * OF ANY KIND, either express or implied. See the License for the specific language
10 * governing permissions and limitations under the License.
11 */
12#pragma once
13
14// Third party rectangle bin pack
15// clang-format off
16#include <lagrange/utils/warnoff.h>
17#include <RectangleBinPack/GuillotineBinPack.h>
18#include <RectangleBinPack/Rect.h>
19#include <lagrange/utils/warnon.h>
20// clang-format on
21
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>
32
33namespace lagrange {
34namespace packing {
35LAGRANGE_LEGACY_INLINE
36namespace legacy {
37
38namespace internal {
39
40template <typename T>
41LA_NOSANITIZE_SIGNED_INTEGER_OVERFLOW bool product_will_overflow(volatile T x, volatile T y)
42{
43 // Note that volatile is needed to avoid compiler optimization.
44 if (x == 0) return false;
45 return ((x * y) / x) != y;
46}
47} // namespace internal
48
49class PackingFailure : public std::runtime_error
50{
51public:
52 PackingFailure(const std::string& what)
53 : std::runtime_error(what)
54 {}
55};
56
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,
61 bool allow_flip,
62 int margin = 2)
63{
64#ifdef RECTANGLE_BIN_PACK_OSS
65 using Int = int;
66#else
67 using Int = ::rbp::Int;
68#endif
69
70 la_runtime_assert(bbox_mins.rows() == bbox_maxs.rows());
71 const auto num_boxes = bbox_mins.rows();
72 if (num_boxes == 0) {
73 return std::make_tuple(
74 Eigen::Matrix<Scalar, Eigen::Dynamic, 2, Eigen::RowMajor>(0, 2),
75 std::vector<bool>());
76 }
77
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;
83 const Scalar scale =
84 max_box_length > safe_cast<Scalar>(1e-12) ? max_box_length / max_canvas_size : 1;
85 la_runtime_assert(std::isfinite(scale));
86 assert(internal::product_will_overflow<Int>(2, MAX_AREA));
87
88 std::vector<::rbp::RectSize> boxes;
89 boxes.reserve(num_boxes);
90 for (auto i : range(num_boxes)) {
91 boxes.emplace_back();
92 auto& box = boxes.back();
93 box.width = safe_cast<Int>(std::ceil((bbox_maxs(i, 0) - bbox_mins(i, 0)) / scale)) + margin;
94 box.height =
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));
97 }
98
99 Eigen::Matrix<Scalar, Eigen::Dynamic, 2, Eigen::RowMajor> centers(num_boxes, 2);
100 std::vector<bool> flipped(num_boxes);
101
102 auto pack = [&](int L, bool trial) -> bool {
103 assert(!internal::product_will_overflow<Int>(L, L));
104#ifdef RECTANGLE_BIN_PACK_OSS
105 if (!allow_flip) {
106 logger().warn(
107 "Disabling rotation is not supported with this version of RectangleBinPack!");
108 }
109 rbp::GuillotineBinPack packer(L, L);
110#else
111 rbp::GuillotineBinPack packer(L, L, allow_flip);
112#endif
113 for (auto i : range(num_boxes)) {
114 auto rect = packer.Insert(
115 boxes[i].width,
116 boxes[i].height,
117 false, // Perform empty space merging for defragmentation.
118 rbp::GuillotineBinPack::FreeRectChoiceHeuristic::RectBestAreaFit,
119 rbp::GuillotineBinPack::GuillotineSplitHeuristic::SplitMinimizeArea);
120 if (rect.width == 0 || rect.height == 0) {
121 return false;
122 }
123 }
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;
127
128 if (!trial) {
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;
135 }
136 }
137 return true;
138 };
139
140 // Find max_canvas_size large enough to fit all boxes.
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) {
145 // Ops, run out of bound.
146 throw PackingFailure("Cannot pack even with canvas at max area!");
147 }
148 }
149 la_runtime_assert(max_canvas_size > 0);
150 // Binary search for the smallest max_canvas_size that fits.
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) {
154 break;
155 }
156 if (pack(l, true)) {
157 max_canvas_size = l;
158 } else {
159 min_canvas_size = l;
160 }
161 }
162 la_runtime_assert(max_canvas_size > 0);
163 bool r = pack(max_canvas_size, false);
165
166 return std::make_tuple(centers, flipped);
167}
168
173{
174#ifndef RECTANGLE_BIN_PACK_OSS
175 bool allow_flip = true;
176#endif
177 bool normalize = true;
178 int margin = 2;
179};
180
191template <typename MeshType>
192void compute_rectangle_packing(MeshType& mesh, const PackingOptions& options)
193{
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;
197
198 VerboseTimer timer;
199 timer.tick();
200
201 auto uv_mesh = mesh.get_uv_mesh();
202 auto uvs = uv_mesh->get_vertices(); // Copied intentionally.
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();
207
208 DisjointSets<Index> components(num_vertices);
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));
212 }
213 }
214 std::vector<Index> per_vertex_comp_ids;
215 auto num_comps = components.extract_disjoint_set_indices(per_vertex_comp_ids);
216
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));
228 }
229
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;
234#else
235 bool allow_flip = options.allow_flip;
236#endif
237 std::tie(centers, flipped) = pack_boxes(bbox_mins, bbox_maxs, allow_flip, options.margin);
238
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];
243
244 if (comp_id == static_cast<Index>(num_comps)) {
245 // Poor isolated vertices.
246 uvs.row(i).setZero();
247 continue;
248 }
249
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);
254 } else {
255 uvs.row(i) = (uvs.row(i) - comp_center) * rot90 + centers.row(comp_id);
256 }
257 }
258
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;
262
263 uvs = (uvs.rowwise() - all_bbox_min) / s;
264 mesh.initialize_uv(uvs, facets);
265 timer.tock("Packing uv");
266}
267
268template <typename MeshType>
269void compute_rectangle_packing(MeshType& mesh)
270{
271 const PackingOptions opt;
272 return compute_rectangle_packing(mesh, opt);
273}
274
285template <typename MeshTypePtr>
286void compute_rectangle_packing(std::vector<MeshTypePtr>& meshes_2d, const PackingOptions& options)
287{
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");
292
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());
298
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();
303 }
304
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;
309#else
310 bool allow_flip = options.allow_flip;
311#endif
312 std::tie(centers, flipped) = pack_boxes(bbox_mins, bbox_maxs, allow_flip, options.margin);
313
314 Eigen::Matrix<Scalar, 2, 2> rot90;
315 rot90 << 0, -1, 1, 0;
316
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());
321
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);
326
327 if (!flipped[i]) {
328 vertices.rowwise() += centers.row(i) - comp_center;
329 } else {
330 vertices =
331 ((vertices.rowwise() - comp_center).transpose() * rot90).transpose().rowwise() +
332 centers.row(i);
333 }
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);
337 }
338
339 const Scalar s = options.normalize ? (all_bbox_max - all_bbox_min).maxCoeff() : 1;
340
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);
346 }
347}
348
349
350} // namespace legacy
351} // namespace packing
352} // namespace lagrange
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