Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
2d_rectangle_presolve.cc
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <array>
18#include <limits>
19#include <memory>
20#include <optional>
21#include <tuple>
22#include <utility>
23#include <vector>
24
25#include "absl/algorithm/container.h"
26#include "absl/base/log_severity.h"
27#include "absl/container/btree_map.h"
28#include "absl/container/flat_hash_map.h"
29#include "absl/container/flat_hash_set.h"
30#include "absl/log/check.h"
31#include "absl/strings/str_cat.h"
32#include "absl/types/span.h"
39
40namespace operations_research {
41namespace sat {
42
43namespace {
44std::vector<Rectangle> FindSpacesThatCannotBeOccupied(
45 absl::Span<const Rectangle> fixed_boxes,
46 absl::Span<const RectangleInRange> non_fixed_boxes,
47 const Rectangle& bounding_box, IntegerValue min_x_size,
48 IntegerValue min_y_size) {
49 std::vector<Rectangle> optional_boxes = {fixed_boxes.begin(),
50 fixed_boxes.end()};
51
52 if (bounding_box.x_min > std::numeric_limits<IntegerValue>::min() &&
53 bounding_box.y_min > std::numeric_limits<IntegerValue>::min() &&
54 bounding_box.x_max < std::numeric_limits<IntegerValue>::max() &&
55 bounding_box.y_max < std::numeric_limits<IntegerValue>::max()) {
56 // Add fake rectangles to build a frame around the bounding box. This allows
57 // to find more areas that must be empty. The frame is as follows:
58 // +************
59 // +...........+
60 // +...........+
61 // +...........+
62 // ************+
63 optional_boxes.push_back({.x_min = bounding_box.x_min - 1,
64 .x_max = bounding_box.x_max,
65 .y_min = bounding_box.y_min - 1,
66 .y_max = bounding_box.y_min});
67 optional_boxes.push_back({.x_min = bounding_box.x_max,
68 .x_max = bounding_box.x_max + 1,
69 .y_min = bounding_box.y_min - 1,
70 .y_max = bounding_box.y_max});
71 optional_boxes.push_back({.x_min = bounding_box.x_min,
72 .x_max = bounding_box.x_max + 1,
73 .y_min = bounding_box.y_max,
74 .y_max = bounding_box.y_max + 1});
75 optional_boxes.push_back({.x_min = bounding_box.x_min - 1,
76 .x_max = bounding_box.x_min,
77 .y_min = bounding_box.y_min,
78 .y_max = bounding_box.y_max + 1});
79 }
80
81 // All items we added to `optional_boxes` at this point are only to be used by
82 // the "gap between items" logic below. They are not actual optional boxes and
83 // should be removed right after the logic is applied.
84 const int num_optional_boxes_to_remove = optional_boxes.size();
85
86 // Add a rectangle to `optional_boxes` but respecting that rectangles must
87 // remain disjoint.
88 const auto add_box = [&optional_boxes](Rectangle new_box) {
89 std::vector<Rectangle> to_add = {std::move(new_box)};
90 for (int i = 0; i < to_add.size(); ++i) {
91 Rectangle new_box = to_add[i];
92 bool is_disjoint = true;
93 for (const Rectangle& existing_box : optional_boxes) {
94 if (!new_box.IsDisjoint(existing_box)) {
95 is_disjoint = false;
96 for (const Rectangle& disjoint_box :
97 new_box.RegionDifference(existing_box)) {
98 to_add.push_back(disjoint_box);
99 }
100 break;
101 }
102 }
103 if (is_disjoint) {
104 optional_boxes.push_back(std::move(new_box));
105 }
106 }
107 };
108
109 // Now check if there is any space that cannot be occupied by any non-fixed
110 // item.
111 // TODO(user): remove the limit of 1000 and reimplement FindEmptySpaces()
112 // using a sweep line algorithm.
113 if (non_fixed_boxes.size() < 1000) {
114 std::vector<Rectangle> bounding_boxes;
115 bounding_boxes.reserve(non_fixed_boxes.size());
116 for (const RectangleInRange& box : non_fixed_boxes) {
117 bounding_boxes.push_back(box.bounding_area);
118 }
119 std::vector<Rectangle> empty_spaces =
120 FindEmptySpaces(bounding_box, std::move(bounding_boxes));
121 for (const Rectangle& r : empty_spaces) {
122 add_box(r);
123 }
124 }
125
126 // Now look for gaps between objects that are too small to place anything.
127 for (int i = 1; i < optional_boxes.size(); ++i) {
128 const Rectangle cur_box = optional_boxes[i];
129 for (int j = 0; j < i; ++j) {
130 const Rectangle& other_box = optional_boxes[j];
131 const IntegerValue lower_top = std::min(cur_box.y_max, other_box.y_max);
132 const IntegerValue higher_bottom =
133 std::max(other_box.y_min, cur_box.y_min);
134 const IntegerValue rightmost_left_edge =
135 std::max(other_box.x_min, cur_box.x_min);
136 const IntegerValue leftmost_right_edge =
137 std::min(other_box.x_max, cur_box.x_max);
138 if (rightmost_left_edge < leftmost_right_edge) {
139 if (lower_top < higher_bottom &&
140 higher_bottom - lower_top < min_y_size) {
141 add_box({.x_min = rightmost_left_edge,
142 .x_max = leftmost_right_edge,
143 .y_min = lower_top,
144 .y_max = higher_bottom});
145 }
146 }
147 if (higher_bottom < lower_top) {
148 if (leftmost_right_edge < rightmost_left_edge &&
149 rightmost_left_edge - leftmost_right_edge < min_x_size) {
150 add_box({.x_min = leftmost_right_edge,
151 .x_max = rightmost_left_edge,
152 .y_min = higher_bottom,
153 .y_max = lower_top});
154 }
155 }
156 }
157 }
158 optional_boxes.erase(optional_boxes.begin(),
159 optional_boxes.begin() + num_optional_boxes_to_remove);
160 return optional_boxes;
161}
162
163} // namespace
164
166 absl::Span<const RectangleInRange> non_fixed_boxes,
167 std::vector<Rectangle>* fixed_boxes) {
168 // This implementation compiles a set of areas that cannot be occupied by any
169 // item, then calls ReduceNumberofBoxes() to use these areas to minimize
170 // `fixed_boxes`.
171 bool changed = false;
172
173 DCHECK(FindPartialRectangleIntersections(*fixed_boxes).empty());
174 IntegerValue original_area = 0;
175 std::vector<Rectangle> fixed_boxes_copy;
176 if (VLOG_IS_ON(1)) {
177 for (const Rectangle& r : *fixed_boxes) {
178 original_area += r.Area();
179 }
180 }
181 if (VLOG_IS_ON(2)) {
182 fixed_boxes_copy = *fixed_boxes;
183 }
184
185 const int original_num_boxes = fixed_boxes->size();
186
187 // The greedy algorithm is really fast. Run it first since it might greatly
188 // reduce the size of large trivial instances.
189 std::vector<Rectangle> empty_vec;
190 if (ReduceNumberofBoxesGreedy(fixed_boxes, &empty_vec)) {
191 changed = true;
192 }
193
194 IntegerValue min_x_size = std::numeric_limits<IntegerValue>::max();
195 IntegerValue min_y_size = std::numeric_limits<IntegerValue>::max();
196
197 CHECK(!non_fixed_boxes.empty());
198 Rectangle bounding_box = non_fixed_boxes[0].bounding_area;
199
200 for (const RectangleInRange& box : non_fixed_boxes) {
201 bounding_box.GrowToInclude(box.bounding_area);
202 min_x_size = std::min(min_x_size, box.x_size);
203 min_y_size = std::min(min_y_size, box.y_size);
204 }
205 DCHECK_GT(min_x_size, 0);
206 DCHECK_GT(min_y_size, 0);
207
208 // Fixed items are only useful to constraint where the non-fixed items can be
209 // placed. This means in particular that any part of a fixed item outside the
210 // bounding box of the non-fixed items is useless. Clip them.
211 int new_size = 0;
212 while (new_size < fixed_boxes->size()) {
213 Rectangle& rectangle = (*fixed_boxes)[new_size];
214 DCHECK_GT(rectangle.SizeX(), 0);
215 DCHECK_GT(rectangle.SizeY(), 0);
216 if (rectangle.x_min < bounding_box.x_min) {
217 rectangle.x_min = bounding_box.x_min;
218 changed = true;
219 }
220 if (rectangle.x_max > bounding_box.x_max) {
221 rectangle.x_max = bounding_box.x_max;
222 changed = true;
223 }
224 if (rectangle.y_min < bounding_box.y_min) {
225 rectangle.y_min = bounding_box.y_min;
226 changed = true;
227 }
228 if (rectangle.y_max > bounding_box.y_max) {
229 rectangle.y_max = bounding_box.y_max;
230 changed = true;
231 }
232 if (rectangle.SizeX() <= 0 || rectangle.SizeY() <= 0) {
233 // The whole rectangle was outside of the domain, remove it.
234 std::swap(rectangle, (*fixed_boxes)[fixed_boxes->size() - 1]);
235 fixed_boxes->resize(fixed_boxes->size() - 1);
236 changed = true;
237 continue;
238 } else {
239 new_size++;
240 }
241 }
242
243 std::vector<Rectangle> optional_boxes = FindSpacesThatCannotBeOccupied(
244 *fixed_boxes, non_fixed_boxes, bounding_box, min_x_size, min_y_size);
245
246 // TODO(user): instead of doing the greedy algorithm first with optional
247 // boxes, and then the one that is exact for mandatory boxes but weak for
248 // optional ones, refactor the second algorithm. One possible way of doing
249 // that would be to follow the shape boundary of optional+mandatory boxes and
250 // look whether we can shave off some turns. For example, if we have a shape
251 // like below, with the "+" representing area covered by optional boxes, we
252 // can replace the turns by a straight line.
253 //
254 // -->
255 // ^ ++++
256 // . ++++ .
257 // . ++++ . =>
258 // ++++ \/
259 // --> ++++ --> -->
260 // *********** ***********
261 // *********** ***********
262 //
263 // Since less turns means less edges, this should be a good way to reduce the
264 // number of boxes.
265 if (ReduceNumberofBoxesGreedy(fixed_boxes, &optional_boxes)) {
266 changed = true;
267 }
268 const int num_after_first_pass = fixed_boxes->size();
269 if (ReduceNumberOfBoxesExactMandatory(fixed_boxes, &optional_boxes)) {
270 changed = true;
271 }
272 if (changed && VLOG_IS_ON(1)) {
273 IntegerValue area = 0;
274 for (const Rectangle& r : *fixed_boxes) {
275 area += r.Area();
276 }
277 VLOG_EVERY_N_SEC(1, 1) << "Presolved " << original_num_boxes
278 << " fixed rectangles (area=" << original_area
279 << ") into " << num_after_first_pass << " then "
280 << fixed_boxes->size() << " (area=" << area << ")";
281
282 VLOG_EVERY_N_SEC(2, 2) << "Presolved rectangles:\n"
283 << RenderDot(bounding_box, fixed_boxes_copy)
284 << "Into:\n"
285 << RenderDot(bounding_box, *fixed_boxes)
286 << (optional_boxes.empty()
287 ? ""
288 : absl::StrCat("Unused optional rects:\n",
289 RenderDot(bounding_box,
290 optional_boxes)));
291 }
292 return changed;
293}
294
295namespace {
296struct Edge {
297 IntegerValue x_start;
298 IntegerValue y_start;
299 IntegerValue size;
300
301 static Edge GetEdge(const Rectangle& rectangle, EdgePosition pos) {
302 switch (pos) {
304 return {.x_start = rectangle.x_min,
305 .y_start = rectangle.y_max,
306 .size = rectangle.SizeX()};
308 return {.x_start = rectangle.x_min,
309 .y_start = rectangle.y_min,
310 .size = rectangle.SizeX()};
312 return {.x_start = rectangle.x_min,
313 .y_start = rectangle.y_min,
314 .size = rectangle.SizeY()};
316 return {.x_start = rectangle.x_max,
317 .y_start = rectangle.y_min,
318 .size = rectangle.SizeY()};
319 }
320 }
321
322 template <typename H>
323 friend H AbslHashValue(H h, const Edge& e) {
324 return H::combine(std::move(h), e.x_start, e.y_start, e.size);
325 }
326
327 bool operator==(const Edge& other) const {
328 return x_start == other.x_start && y_start == other.y_start &&
329 size == other.size;
330 }
331
332 static bool CompareXThenY(const Edge& a, const Edge& b) {
333 return std::tie(a.x_start, a.y_start, a.size) <
334 std::tie(b.x_start, b.y_start, b.size);
335 }
336 static bool CompareYThenX(const Edge& a, const Edge& b) {
337 return std::tie(a.y_start, a.x_start, a.size) <
338 std::tie(b.y_start, b.x_start, b.size);
339 }
340};
341} // namespace
342
343bool ReduceNumberofBoxesGreedy(std::vector<Rectangle>* mandatory_rectangles,
344 std::vector<Rectangle>* optional_rectangles) {
345 // The current implementation just greedly merge rectangles that shares an
346 // edge.
347 std::vector<std::unique_ptr<Rectangle>> rectangle_storage;
348 enum class OptionalEnum { OPTIONAL, MANDATORY };
349 // bool for is_optional
350 std::vector<std::pair<const Rectangle*, OptionalEnum>> rectangles_ptr;
351 absl::flat_hash_map<Edge, int> top_edges_to_rectangle;
352 absl::flat_hash_map<Edge, int> bottom_edges_to_rectangle;
353 absl::flat_hash_map<Edge, int> left_edges_to_rectangle;
354 absl::flat_hash_map<Edge, int> right_edges_to_rectangle;
355
356 bool changed_optional = false;
357 bool changed_mandatory = false;
358
359 auto add_rectangle = [&](const Rectangle* rectangle_ptr,
360 OptionalEnum optional) {
361 const int index = rectangles_ptr.size();
362 rectangles_ptr.push_back({rectangle_ptr, optional});
363 const Rectangle& rectangle = *rectangles_ptr[index].first;
364 top_edges_to_rectangle[Edge::GetEdge(rectangle, EdgePosition::TOP)] = index;
365 bottom_edges_to_rectangle[Edge::GetEdge(rectangle, EdgePosition::BOTTOM)] =
366 index;
367 left_edges_to_rectangle[Edge::GetEdge(rectangle, EdgePosition::LEFT)] =
368 index;
369 right_edges_to_rectangle[Edge::GetEdge(rectangle, EdgePosition::RIGHT)] =
370 index;
371 };
372 for (const Rectangle& rectangle : *mandatory_rectangles) {
373 add_rectangle(&rectangle, OptionalEnum::MANDATORY);
374 }
375 for (const Rectangle& rectangle : *optional_rectangles) {
376 add_rectangle(&rectangle, OptionalEnum::OPTIONAL);
377 }
378
379 auto remove_rectangle = [&](const int index) {
380 const Rectangle& rectangle = *rectangles_ptr[index].first;
381 const Edge top_edge = Edge::GetEdge(rectangle, EdgePosition::TOP);
382 const Edge bottom_edge = Edge::GetEdge(rectangle, EdgePosition::BOTTOM);
383 const Edge left_edge = Edge::GetEdge(rectangle, EdgePosition::LEFT);
384 const Edge right_edge = Edge::GetEdge(rectangle, EdgePosition::RIGHT);
385 top_edges_to_rectangle.erase(top_edge);
386 bottom_edges_to_rectangle.erase(bottom_edge);
387 left_edges_to_rectangle.erase(left_edge);
388 right_edges_to_rectangle.erase(right_edge);
389 rectangles_ptr[index].first = nullptr;
390 };
391
392 bool iteration_did_merge = true;
393 while (iteration_did_merge) {
394 iteration_did_merge = false;
395 for (int i = 0; i < rectangles_ptr.size(); ++i) {
396 if (!rectangles_ptr[i].first) {
397 continue;
398 }
399 const Rectangle& rectangle = *rectangles_ptr[i].first;
400
401 const Edge top_edge = Edge::GetEdge(rectangle, EdgePosition::TOP);
402 const Edge bottom_edge = Edge::GetEdge(rectangle, EdgePosition::BOTTOM);
403 const Edge left_edge = Edge::GetEdge(rectangle, EdgePosition::LEFT);
404 const Edge right_edge = Edge::GetEdge(rectangle, EdgePosition::RIGHT);
405
406 int index = -1;
407 if (const auto it = right_edges_to_rectangle.find(left_edge);
408 it != right_edges_to_rectangle.end()) {
409 index = it->second;
410 } else if (const auto it = left_edges_to_rectangle.find(right_edge);
411 it != left_edges_to_rectangle.end()) {
412 index = it->second;
413 } else if (const auto it = bottom_edges_to_rectangle.find(top_edge);
414 it != bottom_edges_to_rectangle.end()) {
415 index = it->second;
416 } else if (const auto it = top_edges_to_rectangle.find(bottom_edge);
417 it != top_edges_to_rectangle.end()) {
418 index = it->second;
419 }
420 if (index == -1) {
421 continue;
422 }
423 iteration_did_merge = true;
424
425 // Merge two rectangles!
426 const OptionalEnum new_optional =
427 (rectangles_ptr[i].second == OptionalEnum::MANDATORY ||
428 rectangles_ptr[index].second == OptionalEnum::MANDATORY)
429 ? OptionalEnum::MANDATORY
430 : OptionalEnum::OPTIONAL;
431 changed_mandatory =
432 changed_mandatory || (new_optional == OptionalEnum::MANDATORY);
433 changed_optional =
434 changed_optional ||
435 (rectangles_ptr[i].second == OptionalEnum::OPTIONAL ||
436 rectangles_ptr[index].second == OptionalEnum::OPTIONAL);
437 rectangle_storage.push_back(std::make_unique<Rectangle>(rectangle));
438 Rectangle& new_rectangle = *rectangle_storage.back();
439 new_rectangle.GrowToInclude(*rectangles_ptr[index].first);
440 remove_rectangle(i);
441 remove_rectangle(index);
442 add_rectangle(&new_rectangle, new_optional);
443 }
444 }
445
446 if (changed_mandatory) {
447 std::vector<Rectangle> new_rectangles;
448 for (auto [rectangle, optional] : rectangles_ptr) {
449 if (rectangle && optional == OptionalEnum::MANDATORY) {
450 new_rectangles.push_back(*rectangle);
451 }
452 }
453 *mandatory_rectangles = std::move(new_rectangles);
454 }
455 if (changed_optional) {
456 std::vector<Rectangle> new_rectangles;
457 for (auto [rectangle, optional] : rectangles_ptr) {
458 if (rectangle && optional == OptionalEnum::OPTIONAL) {
459 new_rectangles.push_back(*rectangle);
460 }
461 }
462 *optional_rectangles = std::move(new_rectangles);
463 }
464 return changed_mandatory;
465}
466
467Neighbours BuildNeighboursGraph(absl::Span<const Rectangle> rectangles) {
468 // To build a graph of neighbours, we build a sorted vector for each one of
469 // the edges (top, bottom, etc) of the rectangles. Then we merge the bottom
470 // and top vectors and iterate on it. Due to the sorting order, segments where
471 // the bottom of a rectangle touches the top of another one must consecutive.
472 std::vector<std::pair<Edge, int>> edges_to_rectangle[4];
473 std::vector<std::tuple<int, EdgePosition, int>> neighbours;
474 neighbours.reserve(2 * rectangles.size());
475 for (int edge_int = 0; edge_int < 4; ++edge_int) {
476 const EdgePosition edge_position = static_cast<EdgePosition>(edge_int);
477 edges_to_rectangle[edge_position].reserve(rectangles.size());
478 }
479
480 for (int i = 0; i < rectangles.size(); ++i) {
481 const Rectangle& rectangle = rectangles[i];
482 for (int edge_int = 0; edge_int < 4; ++edge_int) {
483 const EdgePosition edge_position = static_cast<EdgePosition>(edge_int);
484 const Edge edge = Edge::GetEdge(rectangle, edge_position);
485 edges_to_rectangle[edge_position].push_back({edge, i});
486 }
487 }
488 for (int edge_int = 0; edge_int < 4; ++edge_int) {
489 const EdgePosition edge_position = static_cast<EdgePosition>(edge_int);
490 const bool sort_x_then_y = edge_position == EdgePosition::LEFT ||
491 edge_position == EdgePosition::RIGHT;
492 const auto cmp =
493 sort_x_then_y
494 ? [](const std::pair<Edge, int>& a,
495 const std::pair<Edge, int>&
496 b) { return Edge::CompareXThenY(a.first, b.first); }
497 : [](const std::pair<Edge, int>& a, const std::pair<Edge, int>& b) {
498 return Edge::CompareYThenX(a.first, b.first);
499 };
500 absl::c_sort(edges_to_rectangle[edge_position], cmp);
501 }
502
503 constexpr struct EdgeData {
504 EdgePosition edge;
505 EdgePosition opposite_edge;
506 bool (*cmp)(const Edge&, const Edge&);
507 } edge_data[4] = {{.edge = EdgePosition::BOTTOM,
508 .opposite_edge = EdgePosition::TOP,
509 .cmp = &Edge::CompareYThenX},
510 {.edge = EdgePosition::TOP,
511 .opposite_edge = EdgePosition::BOTTOM,
512 .cmp = &Edge::CompareYThenX},
513 {.edge = EdgePosition::LEFT,
514 .opposite_edge = EdgePosition::RIGHT,
515 .cmp = &Edge::CompareXThenY},
516 {.edge = EdgePosition::RIGHT,
517 .opposite_edge = EdgePosition::LEFT,
518 .cmp = &Edge::CompareXThenY}};
519
520 for (int edge_int = 0; edge_int < 4; ++edge_int) {
521 const EdgePosition edge_position = edge_data[edge_int].edge;
522 const EdgePosition opposite_edge_position =
523 edge_data[edge_int].opposite_edge;
524 auto it = edges_to_rectangle[edge_position].begin();
525 for (const auto& [edge, index] :
526 edges_to_rectangle[opposite_edge_position]) {
527 while (it != edges_to_rectangle[edge_position].end() &&
528 edge_data[edge_int].cmp(it->first, edge)) {
529 ++it;
530 }
531 if (it == edges_to_rectangle[edge_position].end()) {
532 break;
533 }
534 if (edge_position == EdgePosition::BOTTOM ||
535 edge_position == EdgePosition::TOP) {
536 while (it != edges_to_rectangle[edge_position].end() &&
537 it->first.y_start == edge.y_start &&
538 it->first.x_start < edge.x_start + edge.size) {
539 neighbours.push_back({index, opposite_edge_position, it->second});
540 neighbours.push_back({it->second, edge_position, index});
541 ++it;
542 }
543 } else {
544 while (it != edges_to_rectangle[edge_position].end() &&
545 it->first.x_start == edge.x_start &&
546 it->first.y_start < edge.y_start + edge.size) {
547 neighbours.push_back({index, opposite_edge_position, it->second});
548 neighbours.push_back({it->second, edge_position, index});
549 ++it;
550 }
551 }
552 }
553 }
554
556 return Neighbours(rectangles, neighbours);
557}
558
559std::vector<std::vector<int>> SplitInConnectedComponents(
560 const Neighbours& neighbours) {
561 class GraphView {
562 public:
563 explicit GraphView(const Neighbours& neighbours)
564 : neighbours_(neighbours) {}
565 absl::Span<const int> operator[](int node) const {
566 temp_.clear();
567 for (int edge = 0; edge < 4; ++edge) {
568 const auto edge_neighbors = neighbours_.GetSortedNeighbors(
569 node, static_cast<EdgePosition>(edge));
570 for (int neighbor : edge_neighbors) {
571 temp_.push_back(neighbor);
572 }
573 }
574 return temp_;
575 }
576
577 private:
578 const Neighbours& neighbours_;
579 mutable std::vector<int> temp_;
580 };
581
582 std::vector<std::vector<int>> components;
584 GraphView(neighbours), &components);
585 return components;
586}
587
588namespace {
589IntegerValue GetClockwiseStart(EdgePosition edge, const Rectangle& rectangle) {
590 switch (edge) {
592 return rectangle.y_min;
594 return rectangle.y_max;
596 return rectangle.x_max;
598 return rectangle.x_min;
599 }
600}
601
602IntegerValue GetClockwiseEnd(EdgePosition edge, const Rectangle& rectangle) {
603 switch (edge) {
605 return rectangle.y_max;
607 return rectangle.y_min;
609 return rectangle.x_min;
611 return rectangle.x_max;
612 }
613}
614
615// Given a list of rectangles and their neighbours graph, find the list of
616// vertical and horizontal segments that touches a single rectangle edge. Or,
617// view in another way, the pieces of an edge that is touching the empty space.
618// For example, this corresponds to the "0" segments in the example below:
619//
620// 000000
621// 0****0 000000
622// 0****0 0****0
623// 0****0 0****0
624// 00******00000****00000
625// 0********************0
626// 0********************0
627// 0000000000000000000000
628void GetAllSegmentsTouchingVoid(
629 absl::Span<const Rectangle> rectangles, const Neighbours& neighbours,
630 std::vector<std::pair<Edge, int>>& vertical_edges_on_boundary,
631 std::vector<std::pair<Edge, int>>& horizontal_edges_on_boundary) {
632 for (int i = 0; i < rectangles.size(); ++i) {
633 const Rectangle& rectangle = rectangles[i];
634 for (int edge_int = 0; edge_int < 4; ++edge_int) {
635 const EdgePosition edge = static_cast<EdgePosition>(edge_int);
636 const auto box_neighbors = neighbours.GetSortedNeighbors(i, edge);
637 if (box_neighbors.empty()) {
638 if (edge == EdgePosition::LEFT || edge == EdgePosition::RIGHT) {
639 vertical_edges_on_boundary.push_back(
640 {Edge::GetEdge(rectangle, edge), i});
641 } else {
642 horizontal_edges_on_boundary.push_back(
643 {Edge::GetEdge(rectangle, edge), i});
644 }
645 continue;
646 }
647 IntegerValue previous_pos = GetClockwiseStart(edge, rectangle);
648 for (int n = 0; n <= box_neighbors.size(); ++n) {
649 IntegerValue neighbor_start;
650 const Rectangle* neighbor;
651 if (n == box_neighbors.size()) {
652 // On the last iteration we consider instead of the next neighbor the
653 // end of the current box.
654 neighbor_start = GetClockwiseEnd(edge, rectangle);
655 } else {
656 const int neighbor_idx = box_neighbors[n];
657 neighbor = &rectangles[neighbor_idx];
658 neighbor_start = GetClockwiseStart(edge, *neighbor);
659 }
660 switch (edge) {
662 if (neighbor_start > previous_pos) {
663 vertical_edges_on_boundary.push_back(
664 {Edge{.x_start = rectangle.x_min,
665 .y_start = previous_pos,
666 .size = neighbor_start - previous_pos},
667 i});
668 }
669 break;
671 if (neighbor_start < previous_pos) {
672 vertical_edges_on_boundary.push_back(
673 {Edge{.x_start = rectangle.x_max,
674 .y_start = neighbor_start,
675 .size = previous_pos - neighbor_start},
676 i});
677 }
678 break;
680 if (neighbor_start < previous_pos) {
681 horizontal_edges_on_boundary.push_back(
682 {Edge{.x_start = neighbor_start,
683 .y_start = rectangle.y_min,
684 .size = previous_pos - neighbor_start},
685 i});
686 }
687 break;
689 if (neighbor_start > previous_pos) {
690 horizontal_edges_on_boundary.push_back(
691 {Edge{.x_start = previous_pos,
692 .y_start = rectangle.y_max,
693 .size = neighbor_start - previous_pos},
694 i});
695 }
696 break;
697 }
698 if (n != box_neighbors.size()) {
699 previous_pos = GetClockwiseEnd(edge, *neighbor);
700 }
701 }
702 }
703 }
704}
705
706// Trace a boundary (interior or exterior) that contains the edge described by
707// starting_edge_position and starting_step_point. This method removes the edges
708// that were added to the boundary from `segments_to_follow`.
709ShapePath TraceBoundary(
710 const EdgePosition& starting_edge_position,
711 std::pair<IntegerValue, IntegerValue> starting_step_point,
712 std::array<absl::btree_map<std::pair<IntegerValue, IntegerValue>,
713 std::pair<IntegerValue, int>>,
714 4>& segments_to_follow) {
715 // The boundary is composed of edges on the `segments_to_follow` map. So all
716 // we need is to find and glue them together on the right order.
717 ShapePath path;
718
719 auto extracted =
720 segments_to_follow[starting_edge_position].extract(starting_step_point);
721 CHECK(!extracted.empty());
722 const int first_index = extracted.mapped().second;
723
724 std::pair<IntegerValue, IntegerValue> cur = starting_step_point;
725 int cur_index = first_index;
726 // Now we navigate from one edge to the next. To avoid going back, we remove
727 // used edges from the hash map.
728 while (true) {
729 path.step_points.push_back(cur);
730
731 bool can_go[4] = {false, false, false, false};
732 EdgePosition direction_to_take = EdgePosition::LEFT;
733 for (int edge_int = 0; edge_int < 4; ++edge_int) {
734 const EdgePosition edge = static_cast<EdgePosition>(edge_int);
735 if (segments_to_follow[edge].contains(cur)) {
736 can_go[edge] = true;
737 direction_to_take = edge;
738 }
739 }
740
741 if (can_go == absl::Span<const bool>{false, false, false, false}) {
742 // Cannot move anywhere, we closed the loop.
743 break;
744 }
745
746 // Handle one pathological case.
747 if (can_go[EdgePosition::LEFT] && can_go[EdgePosition::RIGHT]) {
748 // Corner case (literally):
749 // ********
750 // ********
751 // ********
752 // ********
753 // ^ +++++++++
754 // | +++++++++
755 // | +++++++++
756 // +++++++++
757 //
758 // In this case we keep following the same box.
759 auto it_x = segments_to_follow[EdgePosition::LEFT].find(cur);
760 if (cur_index == it_x->second.second) {
761 direction_to_take = EdgePosition::LEFT;
762 } else {
763 direction_to_take = EdgePosition::RIGHT;
764 }
765 } else if (can_go[EdgePosition::TOP] && can_go[EdgePosition::BOTTOM]) {
766 auto it_y = segments_to_follow[EdgePosition::TOP].find(cur);
767 if (cur_index == it_y->second.second) {
768 direction_to_take = EdgePosition::TOP;
769 } else {
770 direction_to_take = EdgePosition::BOTTOM;
771 }
772 }
773
774 auto extracted = segments_to_follow[direction_to_take].extract(cur);
775 cur_index = extracted.mapped().second;
776 switch (direction_to_take) {
778 cur.first -= extracted.mapped().first;
779 segments_to_follow[EdgePosition::RIGHT].erase(
780 cur); // Forbid going back
781 break;
783 cur.first += extracted.mapped().first;
784 segments_to_follow[EdgePosition::LEFT].erase(cur); // Forbid going back
785 break;
787 cur.second += extracted.mapped().first;
788 segments_to_follow[EdgePosition::BOTTOM].erase(
789 cur); // Forbid going back
790 break;
792 cur.second -= extracted.mapped().first;
793 segments_to_follow[EdgePosition::TOP].erase(cur); // Forbid going back
794 break;
795 }
796 path.touching_box_index.push_back(cur_index);
797 }
798 path.touching_box_index.push_back(cur_index);
799
800 return path;
801}
802} // namespace
803
804std::vector<SingleShape> BoxesToShapes(absl::Span<const Rectangle> rectangles,
805 const Neighbours& neighbours) {
806 std::vector<std::pair<Edge, int>> vertical_edges_on_boundary;
807 std::vector<std::pair<Edge, int>> horizontal_edges_on_boundary;
808 GetAllSegmentsTouchingVoid(rectangles, neighbours, vertical_edges_on_boundary,
809 horizontal_edges_on_boundary);
810
811 std::array<absl::btree_map<std::pair<IntegerValue, IntegerValue>,
812 std::pair<IntegerValue, int>>,
813 4>
814 segments_to_follow;
815
816 for (const auto& [edge, box_index] : vertical_edges_on_boundary) {
817 segments_to_follow[EdgePosition::TOP][{edge.x_start, edge.y_start}] = {
818 edge.size, box_index};
819 segments_to_follow[EdgePosition::BOTTOM][{
820 edge.x_start, edge.y_start + edge.size}] = {edge.size, box_index};
821 }
822 for (const auto& [edge, box_index] : horizontal_edges_on_boundary) {
823 segments_to_follow[EdgePosition::RIGHT][{edge.x_start, edge.y_start}] = {
824 edge.size, box_index};
825 segments_to_follow[EdgePosition::LEFT][{
826 edge.x_start + edge.size, edge.y_start}] = {edge.size, box_index};
827 }
828
829 const auto components = SplitInConnectedComponents(neighbours);
830 std::vector<SingleShape> result(components.size());
831 std::vector<int> box_to_component(rectangles.size());
832 for (int i = 0; i < components.size(); ++i) {
833 for (const int box_index : components[i]) {
834 box_to_component[box_index] = i;
835 }
836 }
837 while (!segments_to_follow[EdgePosition::LEFT].empty()) {
838 // Get edge most to the bottom left
839 const int box_index =
840 segments_to_follow[EdgePosition::RIGHT].begin()->second.second;
841 const std::pair<IntegerValue, IntegerValue> starting_step_point =
842 segments_to_follow[EdgePosition::RIGHT].begin()->first;
843 const int component_index = box_to_component[box_index];
844
845 // The left-most vertical edge of the connected component must be of its
846 // exterior boundary. So we must always see the exterior boundary before
847 // seeing any holes.
848 const bool is_hole = !result[component_index].boundary.step_points.empty();
849 ShapePath& path = is_hole ? result[component_index].holes.emplace_back()
850 : result[component_index].boundary;
851 path = TraceBoundary(EdgePosition::RIGHT, starting_step_point,
852 segments_to_follow);
853 if (is_hole) {
854 // Follow the usual convention that holes are in the inverse orientation
855 // of the external boundary.
856 absl::c_reverse(path.step_points);
857 absl::c_reverse(path.touching_box_index);
858 }
859 }
860 return result;
861}
862
863namespace {
864struct PolygonCut {
865 std::pair<IntegerValue, IntegerValue> start;
866 std::pair<IntegerValue, IntegerValue> end;
867 int start_index;
868 int end_index;
869
870 struct CmpByStartY {
871 bool operator()(const PolygonCut& a, const PolygonCut& b) const {
872 return std::tie(a.start.second, a.start.first) <
873 std::tie(b.start.second, b.start.first);
874 }
875 };
876
877 struct CmpByEndY {
878 bool operator()(const PolygonCut& a, const PolygonCut& b) const {
879 return std::tie(a.end.second, a.end.first) <
880 std::tie(b.end.second, b.end.first);
881 }
882 };
883
884 struct CmpByStartX {
885 bool operator()(const PolygonCut& a, const PolygonCut& b) const {
886 return a.start < b.start;
887 }
888 };
889
890 struct CmpByEndX {
891 bool operator()(const PolygonCut& a, const PolygonCut& b) const {
892 return a.end < b.end;
893 }
894 };
895
896 template <typename Sink>
897 friend void AbslStringify(Sink& sink, const PolygonCut& diagonal) {
898 absl::Format(&sink, "(%v,%v)-(%v,%v)", diagonal.start.first,
899 diagonal.start.second, diagonal.end.first,
900 diagonal.end.second);
901 }
902};
903
904// A different representation of a shape. The two vectors must have the same
905// size. The first one contains the points of the shape and the second one
906// contains the index of the next point in the shape.
907//
908// Note that we code in this file is only correct for shapes with points
909// connected only by horizontal or vertical lines.
910struct FlatShape {
911 std::vector<std::pair<IntegerValue, IntegerValue>> points;
912 std::vector<int> next;
913};
914
915EdgePosition GetSegmentDirection(
916 const std::pair<IntegerValue, IntegerValue>& curr_segment,
917 const std::pair<IntegerValue, IntegerValue>& next_segment) {
918 if (curr_segment.first == next_segment.first) {
919 return next_segment.second > curr_segment.second ? EdgePosition::TOP
921 } else {
922 return next_segment.first > curr_segment.first ? EdgePosition::RIGHT
924 }
925}
926
927// Given a polygon, this function returns all line segments that start on a
928// concave vertex and follow horizontally or vertically until it reaches the
929// border of the polygon. This function returns all such segments grouped on the
930// direction the line takes after starting in the concave vertex. Some of those
931// segments start and end on a convex vertex, so they will appear twice in the
932// output. This function modifies the shape by splitting some of the path
933// segments in two. This is needed to make sure that `PolygonCut.start_index`
934// and `PolygonCut.end_index` always corresponds to points in the FlatShape,
935// even if they are not edges.
936std::array<std::vector<PolygonCut>, 4> GetPotentialPolygonCuts(
937 FlatShape& shape) {
938 std::array<std::vector<PolygonCut>, 4> cuts;
939
940 // First, for each concave vertex we create a cut that starts at it and
941 // crosses the polygon until infinite (in practice, int_max/int_min).
942 for (int i = 0; i < shape.points.size(); i++) {
943 const auto& it = &shape.points[shape.next[i]];
944 const auto& previous = &shape.points[i];
945 const auto& next_segment = &shape.points[shape.next[shape.next[i]]];
946 const EdgePosition previous_dir = GetSegmentDirection(*previous, *it);
947 const EdgePosition next_dir = GetSegmentDirection(*it, *next_segment);
948
949 if ((previous_dir == EdgePosition::TOP && next_dir == EdgePosition::LEFT) ||
950 (previous_dir == EdgePosition::RIGHT &&
951 next_dir == EdgePosition::TOP)) {
952 cuts[EdgePosition::RIGHT].push_back(
953 {.start = *it,
954 .end = {std::numeric_limits<IntegerValue>::max(), it->second},
955 .start_index = shape.next[i]});
956 }
957 if ((previous_dir == EdgePosition::BOTTOM &&
958 next_dir == EdgePosition::RIGHT) ||
959 (previous_dir == EdgePosition::LEFT &&
960 next_dir == EdgePosition::BOTTOM)) {
961 cuts[EdgePosition::LEFT].push_back(
962 {.start = {std::numeric_limits<IntegerValue>::min(), it->second},
963 .end = *it,
964 .end_index = shape.next[i]});
965 }
966 if ((previous_dir == EdgePosition::RIGHT &&
967 next_dir == EdgePosition::TOP) ||
968 (previous_dir == EdgePosition::BOTTOM &&
969 next_dir == EdgePosition::RIGHT)) {
970 cuts[EdgePosition::BOTTOM].push_back(
971 {.start = {it->first, std::numeric_limits<IntegerValue>::min()},
972 .end = *it,
973 .end_index = shape.next[i]});
974 }
975 if ((previous_dir == EdgePosition::TOP && next_dir == EdgePosition::LEFT) ||
976 (previous_dir == EdgePosition::LEFT &&
977 next_dir == EdgePosition::BOTTOM)) {
978 cuts[EdgePosition::TOP].push_back(
979 {.start = *it,
980 .end = {it->first, std::numeric_limits<IntegerValue>::max()},
981 .start_index = shape.next[i]});
982 }
983 }
984
985 // Now that we have one of the points of the segment (the one starting on a
986 // vertex), we need to find the other point. This is basically finding the
987 // first path segment that crosses each cut connecting edge->infinity we
988 // collected above. We do a rather naive implementation of that below and its
989 // complexity is O(N^2) even if it should be fast in most cases. If it
990 // turns out to be costly on profiling we can use a more sophisticated
991 // algorithm for finding the first intersection.
992
993 // We need to sort the cuts so we can use binary search to quickly find cuts
994 // that cross a segment.
995 std::sort(cuts[EdgePosition::RIGHT].begin(), cuts[EdgePosition::RIGHT].end(),
996 PolygonCut::CmpByStartY());
997 std::sort(cuts[EdgePosition::LEFT].begin(), cuts[EdgePosition::LEFT].end(),
998 PolygonCut::CmpByEndY());
999 std::sort(cuts[EdgePosition::BOTTOM].begin(),
1000 cuts[EdgePosition::BOTTOM].end(), PolygonCut::CmpByEndX());
1001 std::sort(cuts[EdgePosition::TOP].begin(), cuts[EdgePosition::TOP].end(),
1002 PolygonCut::CmpByStartX());
1003
1004 // This function cuts a segment in two if it crosses a cut. In any case, it
1005 // returns the index of a point `point_idx` so that `shape.points[point_idx]
1006 // == point_to_cut`.
1007 const auto cut_segment_if_necessary =
1008 [&shape](int segment_idx,
1009 std::pair<IntegerValue, IntegerValue> point_to_cut) {
1010 const auto& cur = shape.points[segment_idx];
1011 const auto& next = shape.points[shape.next[segment_idx]];
1012 if (cur.second == next.second) {
1013 DCHECK_EQ(point_to_cut.second, cur.second);
1014 // We have a horizontal segment
1015 const IntegerValue edge_start = std::min(cur.first, next.first);
1016 const IntegerValue edge_end = std::max(cur.first, next.first);
1017
1018 if (edge_start < point_to_cut.first &&
1019 point_to_cut.first < edge_end) {
1020 shape.points.push_back(point_to_cut);
1021 const int next_idx = shape.next[segment_idx];
1022 shape.next[segment_idx] = shape.points.size() - 1;
1023 shape.next.push_back(next_idx);
1024 return static_cast<int>(shape.points.size() - 1);
1025 }
1026 return (shape.points[segment_idx] == point_to_cut)
1027 ? segment_idx
1028 : shape.next[segment_idx];
1029 } else {
1030 DCHECK_EQ(cur.first, next.first);
1031 DCHECK_EQ(point_to_cut.first, cur.first);
1032 // We have a vertical segment
1033 const IntegerValue edge_start = std::min(cur.second, next.second);
1034 const IntegerValue edge_end = std::max(cur.second, next.second);
1035
1036 if (edge_start < point_to_cut.second &&
1037 point_to_cut.second < edge_end) {
1038 shape.points.push_back(point_to_cut);
1039 const int next_idx = shape.next[segment_idx];
1040 shape.next[segment_idx] = shape.points.size() - 1;
1041 shape.next.push_back(next_idx);
1042 return static_cast<int>(shape.points.size() - 1);
1043 }
1044 return (shape.points[segment_idx] == point_to_cut)
1045 ? segment_idx
1046 : shape.next[segment_idx];
1047 }
1048 };
1049
1050 for (int i = 0; i < shape.points.size(); i++) {
1051 const auto* cur_point_ptr = &shape.points[shape.next[i]];
1052 const auto* previous = &shape.points[i];
1053 DCHECK(cur_point_ptr->first == previous->first ||
1054 cur_point_ptr->second == previous->second)
1055 << "found a segment that is neither horizontal nor vertical";
1056 const EdgePosition direction =
1057 GetSegmentDirection(*previous, *cur_point_ptr);
1058
1059 if (direction == EdgePosition::BOTTOM) {
1060 const auto cut_start = absl::c_lower_bound(
1061 cuts[EdgePosition::RIGHT],
1062 PolygonCut{.start = {std::numeric_limits<IntegerValue>::min(),
1063 cur_point_ptr->second}},
1064 PolygonCut::CmpByStartY());
1065 auto cut_end = absl::c_upper_bound(
1066 cuts[EdgePosition::RIGHT],
1067 PolygonCut{.start = {std::numeric_limits<IntegerValue>::max(),
1068 previous->second}},
1069 PolygonCut::CmpByStartY());
1070
1071 for (auto cut_it = cut_start; cut_it < cut_end; ++cut_it) {
1072 PolygonCut& diagonal = *cut_it;
1073 const IntegerValue diagonal_start_x = diagonal.start.first;
1074 const IntegerValue diagonal_cur_end_x = diagonal.end.first;
1075 // Our binary search guarantees those two conditions.
1076 DCHECK_LE(cur_point_ptr->second, diagonal.start.second);
1077 DCHECK_LE(diagonal.start.second, previous->second);
1078
1079 // Let's test if the diagonal crosses the current boundary segment
1080 if (diagonal_start_x <= previous->first &&
1081 diagonal_cur_end_x > cur_point_ptr->first) {
1082 DCHECK_LT(diagonal_start_x, cur_point_ptr->first);
1083 DCHECK_LE(previous->first, diagonal_cur_end_x);
1084
1085 diagonal.end.first = cur_point_ptr->first;
1086
1087 diagonal.end_index = cut_segment_if_necessary(i, diagonal.end);
1088 DCHECK(shape.points[diagonal.end_index] == diagonal.end);
1089
1090 // Subtle: cut_segment_if_necessary might add new points to the vector
1091 // of the shape, so the pointers computed from it might become
1092 // invalid. Moreover, the current segment now is shorter, so we need
1093 // to update our upper bound.
1094 cur_point_ptr = &shape.points[shape.next[i]];
1095 previous = &shape.points[i];
1096 cut_end = absl::c_upper_bound(
1097 cuts[EdgePosition::RIGHT],
1098 PolygonCut{.start = {std::numeric_limits<IntegerValue>::max(),
1099 previous->second}},
1100 PolygonCut::CmpByStartY());
1101 }
1102 }
1103 }
1104
1105 if (direction == EdgePosition::TOP) {
1106 const auto cut_start = absl::c_lower_bound(
1107 cuts[EdgePosition::LEFT],
1108 PolygonCut{.end = {std::numeric_limits<IntegerValue>::min(),
1109 previous->second}},
1110 PolygonCut::CmpByEndY());
1111 auto cut_end = absl::c_upper_bound(
1112 cuts[EdgePosition::LEFT],
1113 PolygonCut{.end = {std::numeric_limits<IntegerValue>::max(),
1114 cur_point_ptr->second}},
1115 PolygonCut::CmpByEndY());
1116 for (auto cut_it = cut_start; cut_it < cut_end; ++cut_it) {
1117 PolygonCut& diagonal = *cut_it;
1118 const IntegerValue diagonal_start_x = diagonal.start.first;
1119 const IntegerValue diagonal_cur_end_x = diagonal.end.first;
1120 // Our binary search guarantees those two conditions.
1121 DCHECK_LE(diagonal.end.second, cur_point_ptr->second);
1122 DCHECK_LE(previous->second, diagonal.end.second);
1123
1124 // Let's test if the diagonal crosses the current boundary segment
1125 if (diagonal_start_x < cur_point_ptr->first &&
1126 previous->first <= diagonal_cur_end_x) {
1127 DCHECK_LT(cur_point_ptr->first, diagonal_cur_end_x);
1128 DCHECK_LE(diagonal_start_x, previous->first);
1129
1130 diagonal.start.first = cur_point_ptr->first;
1131 diagonal.start_index = cut_segment_if_necessary(i, diagonal.start);
1132 DCHECK(shape.points[diagonal.start_index] == diagonal.start);
1133 cur_point_ptr = &shape.points[shape.next[i]];
1134 previous = &shape.points[i];
1135 cut_end = absl::c_upper_bound(
1136 cuts[EdgePosition::LEFT],
1137 PolygonCut{.end = {std::numeric_limits<IntegerValue>::max(),
1138 cur_point_ptr->second}},
1139 PolygonCut::CmpByEndY());
1140 }
1141 }
1142 }
1143
1144 if (direction == EdgePosition::LEFT) {
1145 const auto cut_start = absl::c_lower_bound(
1147 PolygonCut{.end = {cur_point_ptr->first,
1148 std::numeric_limits<IntegerValue>::min()}},
1149 PolygonCut::CmpByEndX());
1150 auto cut_end = absl::c_upper_bound(
1152 PolygonCut{.end = {previous->first,
1153 std::numeric_limits<IntegerValue>::max()}},
1154 PolygonCut::CmpByEndX());
1155 for (auto cut_it = cut_start; cut_it < cut_end; ++cut_it) {
1156 PolygonCut& diagonal = *cut_it;
1157 const IntegerValue diagonal_start_y = diagonal.start.second;
1158 const IntegerValue diagonal_cur_end_y = diagonal.end.second;
1159
1160 // Our binary search guarantees those two conditions.
1161 DCHECK_LE(cur_point_ptr->first, diagonal.end.first);
1162 DCHECK_LE(diagonal.end.first, previous->first);
1163
1164 // Let's test if the diagonal crosses the current boundary segment
1165 if (diagonal_start_y < cur_point_ptr->second &&
1166 cur_point_ptr->second <= diagonal_cur_end_y) {
1167 DCHECK_LE(diagonal_start_y, previous->second);
1168 DCHECK_LT(cur_point_ptr->second, diagonal_cur_end_y);
1169
1170 diagonal.start.second = cur_point_ptr->second;
1171 diagonal.start_index = cut_segment_if_necessary(i, diagonal.start);
1172 DCHECK(shape.points[diagonal.start_index] == diagonal.start);
1173 cur_point_ptr = &shape.points[shape.next[i]];
1174 previous = &shape.points[i];
1175 cut_end = absl::c_upper_bound(
1177 PolygonCut{.end = {previous->first,
1178 std::numeric_limits<IntegerValue>::max()}},
1179 PolygonCut::CmpByEndX());
1180 }
1181 }
1182 }
1183
1184 if (direction == EdgePosition::RIGHT) {
1185 const auto cut_start = absl::c_lower_bound(
1186 cuts[EdgePosition::TOP],
1187 PolygonCut{.start = {previous->first,
1188 std::numeric_limits<IntegerValue>::min()}},
1189 PolygonCut::CmpByStartX());
1190 auto cut_end = absl::c_upper_bound(
1191 cuts[EdgePosition::TOP],
1192 PolygonCut{.start = {cur_point_ptr->first,
1193 std::numeric_limits<IntegerValue>::max()}},
1194 PolygonCut::CmpByStartX());
1195 for (auto cut_it = cut_start; cut_it < cut_end; ++cut_it) {
1196 PolygonCut& diagonal = *cut_it;
1197 const IntegerValue diagonal_start_y = diagonal.start.second;
1198 const IntegerValue diagonal_cur_end_y = diagonal.end.second;
1199
1200 // Our binary search guarantees those two conditions.
1201 DCHECK_LE(previous->first, diagonal.start.first);
1202 DCHECK_LE(diagonal.start.first, cur_point_ptr->first);
1203
1204 // Let's test if the diagonal crosses the current boundary segment
1205 if (diagonal_start_y <= cur_point_ptr->second &&
1206 cur_point_ptr->second < diagonal_cur_end_y) {
1207 DCHECK_LT(diagonal_start_y, previous->second);
1208 DCHECK_LE(cur_point_ptr->second, diagonal_cur_end_y);
1209
1210 diagonal.end.second = cur_point_ptr->second;
1211 diagonal.end_index = cut_segment_if_necessary(i, diagonal.end);
1212 DCHECK(shape.points[diagonal.end_index] == diagonal.end);
1213 cur_point_ptr = &shape.points[shape.next[i]];
1214 cut_end = absl::c_upper_bound(
1215 cuts[EdgePosition::TOP],
1216 PolygonCut{.start = {cur_point_ptr->first,
1217 std::numeric_limits<IntegerValue>::max()}},
1218 PolygonCut::CmpByStartX());
1219 previous = &shape.points[i];
1220 }
1221 }
1222 }
1223 }
1224 return cuts;
1225}
1226
1227void CutShapeWithPolygonCuts(FlatShape& shape,
1228 absl::Span<const PolygonCut> cuts) {
1229 std::vector<int> previous(shape.points.size(), -1);
1230 for (int i = 0; i < shape.points.size(); i++) {
1231 previous[shape.next[i]] = i;
1232 }
1233
1234 std::vector<std::pair<int, int>> cut_previous_index(cuts.size(), {-1, -1});
1235 for (int i = 0; i < cuts.size(); i++) {
1236 DCHECK(cuts[i].start == shape.points[cuts[i].start_index]);
1237 DCHECK(cuts[i].end == shape.points[cuts[i].end_index]);
1238
1239 cut_previous_index[i].first = previous[cuts[i].start_index];
1240 cut_previous_index[i].second = previous[cuts[i].end_index];
1241 }
1242
1243 for (const auto& [i, j] : cut_previous_index) {
1244 const int prev_start_next = shape.next[i];
1245 const int prev_end_next = shape.next[j];
1246 const std::pair<IntegerValue, IntegerValue> start =
1247 shape.points[prev_start_next];
1248 const std::pair<IntegerValue, IntegerValue> end =
1249 shape.points[prev_end_next];
1250
1251 shape.points.push_back(start);
1252 shape.next[i] = shape.points.size() - 1;
1253 shape.next.push_back(prev_end_next);
1254
1255 shape.points.push_back(end);
1256 shape.next[j] = shape.points.size() - 1;
1257 shape.next.push_back(prev_start_next);
1258 }
1259}
1260} // namespace
1261
1262// This function applies the method described in page 3 of [1].
1263//
1264// [1] Eppstein, David. "Graph-theoretic solutions to computational geometry
1265// problems." International Workshop on Graph-Theoretic Concepts in Computer
1266// Science. Berlin, Heidelberg: Springer Berlin Heidelberg, 2009.
1267std::vector<Rectangle> CutShapeIntoRectangles(SingleShape shape) {
1268 auto is_aligned = [](const std::pair<IntegerValue, IntegerValue>& p1,
1269 const std::pair<IntegerValue, IntegerValue>& p2,
1270 const std::pair<IntegerValue, IntegerValue>& p3) {
1271 return ((p1.first == p2.first) == (p2.first == p3.first)) &&
1272 ((p1.second == p2.second) == (p2.second == p3.second));
1273 };
1274 const auto add_segment =
1275 [&is_aligned](const std::pair<IntegerValue, IntegerValue>& segment,
1276 const int start_index,
1277 std::vector<std::pair<IntegerValue, IntegerValue>>& points,
1278 std::vector<int>& next) {
1279 if (points.size() > 1 + start_index &&
1280 is_aligned(points[points.size() - 1], points[points.size() - 2],
1281 segment)) {
1282 points.back() = segment;
1283 } else {
1284 points.push_back(segment);
1285 next.push_back(points.size());
1286 }
1287 };
1288
1289 // To cut our polygon into rectangles, we first put it into a data structure
1290 // that is easier to manipulate.
1291 FlatShape flat_shape;
1292 for (int i = 0; 1 + i < shape.boundary.step_points.size(); ++i) {
1293 const std::pair<IntegerValue, IntegerValue>& segment =
1294 shape.boundary.step_points[i];
1295 add_segment(segment, 0, flat_shape.points, flat_shape.next);
1296 }
1297 flat_shape.next.back() = 0;
1298 for (const ShapePath& hole : shape.holes) {
1299 const int start = flat_shape.next.size();
1300 if (hole.step_points.size() < 2) continue;
1301 for (int i = 0; i + 1 < hole.step_points.size(); ++i) {
1302 const std::pair<IntegerValue, IntegerValue>& segment =
1303 hole.step_points[i];
1304 add_segment(segment, start, flat_shape.points, flat_shape.next);
1305 }
1306 flat_shape.next.back() = start;
1307 }
1308
1309 std::array<std::vector<PolygonCut>, 4> all_cuts =
1310 GetPotentialPolygonCuts(flat_shape);
1311
1312 // Some cuts connect two concave edges and will be duplicated in all_cuts.
1313 // Those are important: since they "fix" two concavities with a single cut,
1314 // they are called "good diagonals" in the literature. Note that in
1315 // computational geometry jargon, a diagonal of a polygon is a line segment
1316 // that connects two non-adjacent vertices of a polygon, even in cases like
1317 // ours that we are only talking of diagonals that are not "diagonal" in the
1318 // usual meaning of the word: ie., horizontal or vertical segments connecting
1319 // two vertices of the polygon).
1320 std::array<std::vector<PolygonCut>, 2> good_diagonals;
1321 for (const auto& d : all_cuts[EdgePosition::BOTTOM]) {
1322 if (absl::c_binary_search(all_cuts[EdgePosition::TOP], d,
1323 PolygonCut::CmpByStartX())) {
1324 good_diagonals[0].push_back(d);
1325 }
1326 }
1327 for (const auto& d : all_cuts[EdgePosition::LEFT]) {
1328 if (absl::c_binary_search(all_cuts[EdgePosition::RIGHT], d,
1329 PolygonCut::CmpByStartY())) {
1330 good_diagonals[1].push_back(d);
1331 }
1332 }
1333
1334 // The "good diagonals" are only more optimal that any cut if they are not
1335 // crossed by other cuts. To maximize their usefulness, we build a graph where
1336 // the good diagonals are the vertices and we add an edge every time a
1337 // vertical and horizontal diagonal cross. The minimum vertex cover of this
1338 // graph is the minimal set of good diagonals that are not crossed by other
1339 // cuts.
1340 std::vector<std::vector<int>> arcs(good_diagonals[0].size());
1341 for (int i = 0; i < good_diagonals[0].size(); ++i) {
1342 for (int j = 0; j < good_diagonals[1].size(); ++j) {
1343 const PolygonCut& vertical = good_diagonals[0][i];
1344 const PolygonCut& horizontal = good_diagonals[1][j];
1345 const IntegerValue vertical_x = vertical.start.first;
1346 const IntegerValue horizontal_y = horizontal.start.second;
1347 if (horizontal.start.first <= vertical_x &&
1348 vertical_x <= horizontal.end.first &&
1349 vertical.start.second <= horizontal_y &&
1350 horizontal_y <= vertical.end.second) {
1351 arcs[i].push_back(good_diagonals[0].size() + j);
1352 }
1353 }
1354 }
1355
1356 const std::vector<bool> minimum_cover =
1357 BipartiteMinimumVertexCover(arcs, good_diagonals[1].size());
1358
1359 std::vector<PolygonCut> minimum_cover_horizontal_diagonals;
1360 for (int i = good_diagonals[0].size();
1361 i < good_diagonals[0].size() + good_diagonals[1].size(); ++i) {
1362 if (minimum_cover[i]) continue;
1363 minimum_cover_horizontal_diagonals.push_back(
1364 good_diagonals[1][i - good_diagonals[0].size()]);
1365 }
1366
1367 // Since our data structure only allow to cut the shape according to a list
1368 // of vertical or horizontal cuts, but not a list mixing both, we cut first
1369 // on the chosen horizontal good diagonals.
1370 CutShapeWithPolygonCuts(flat_shape, minimum_cover_horizontal_diagonals);
1371
1372 // We need to recompute the cuts after we applied the good diagonals, since
1373 // the geometry has changed.
1374 all_cuts = GetPotentialPolygonCuts(flat_shape);
1375
1376 // Now that we did all horizontal good diagonals, we need to cut on all
1377 // vertical good diagonals and then cut arbitrarily to remove all concave
1378 // edges. To make things simple, just apply all vertical cuts, since they
1379 // include all the vertical good diagonals and also fully slice the shape into
1380 // rectangles.
1381
1382 // Remove duplicates coming from good diagonals first.
1383 std::vector<PolygonCut> cuts = all_cuts[EdgePosition::TOP];
1384 for (const auto& cut : all_cuts[EdgePosition::BOTTOM]) {
1385 if (!absl::c_binary_search(all_cuts[EdgePosition::TOP], cut,
1386 PolygonCut::CmpByStartX())) {
1387 cuts.push_back(cut);
1388 }
1389 }
1390
1391 CutShapeWithPolygonCuts(flat_shape, cuts);
1392
1393 // Now every connected component of the shape is a rectangle. Build the final
1394 // result.
1395 std::vector<Rectangle> result;
1396 std::vector<bool> seen(flat_shape.points.size(), false);
1397 for (int i = 0; i < flat_shape.points.size(); ++i) {
1398 if (seen[i]) continue;
1399 Rectangle& rectangle = result.emplace_back(Rectangle{
1400 .x_min = std::numeric_limits<IntegerValue>::max(),
1401 .x_max = std::numeric_limits<IntegerValue>::min(),
1402 .y_min = std::numeric_limits<IntegerValue>::max(),
1403 .y_max = std::numeric_limits<IntegerValue>::min(),
1404 });
1405 int cur = i;
1406 do {
1407 seen[cur] = true;
1408 rectangle.GrowToInclude({.x_min = flat_shape.points[cur].first,
1409 .x_max = flat_shape.points[cur].first,
1410 .y_min = flat_shape.points[cur].second,
1411 .y_max = flat_shape.points[cur].second});
1412 cur = flat_shape.next[cur];
1413 DCHECK_LT(cur, flat_shape.next.size());
1414 } while (cur != i);
1415 }
1416
1417 return result;
1418}
1419
1421 std::vector<Rectangle>* mandatory_rectangles,
1422 std::vector<Rectangle>* optional_rectangles) {
1423 if (mandatory_rectangles->empty()) return false;
1424 std::vector<Rectangle> result = *mandatory_rectangles;
1425 std::vector<Rectangle> new_optional_rectangles = *optional_rectangles;
1426
1427 // This heuristic can be slow for very large problems, so gate it with a
1428 // reasonable limit.
1429 if (mandatory_rectangles->size() < 1000) {
1430 Rectangle mandatory_bounding_box = (*mandatory_rectangles)[0];
1431 for (const Rectangle& box : *mandatory_rectangles) {
1432 mandatory_bounding_box.GrowToInclude(box);
1433 }
1434 const std::vector<Rectangle> mandatory_empty_holes =
1435 FindEmptySpaces(mandatory_bounding_box, *mandatory_rectangles);
1436 const std::vector<std::vector<int>> mandatory_holes_components =
1437 SplitInConnectedComponents(BuildNeighboursGraph(mandatory_empty_holes));
1438
1439 // Now for every connected component of the holes in the mandatory area, see
1440 // if we can fill them with optional boxes.
1441 std::vector<Rectangle> holes_in_component;
1442 for (const std::vector<int>& component : mandatory_holes_components) {
1443 holes_in_component.clear();
1444 holes_in_component.reserve(component.size());
1445 for (const int index : component) {
1446 holes_in_component.push_back(mandatory_empty_holes[index]);
1447 }
1448 if (RegionIncludesOther(new_optional_rectangles, holes_in_component)) {
1449 // Fill the hole.
1450 result.insert(result.end(), holes_in_component.begin(),
1451 holes_in_component.end());
1452 // We can modify `optional_rectangles` here since we know that if we
1453 // remove a hole this function will return true.
1454 new_optional_rectangles = PavedRegionDifference(
1455 new_optional_rectangles, std::move(holes_in_component));
1456 }
1457 }
1458 }
1459 const Neighbours neighbours = BuildNeighboursGraph(result);
1460 std::vector<SingleShape> shapes = BoxesToShapes(result, neighbours);
1461
1462 std::vector<Rectangle> original_result;
1463 if (DEBUG_MODE) {
1464 original_result = result;
1465 }
1466 result.clear();
1467 for (SingleShape& shape : shapes) {
1468 // This is the function that applies the algorithm described in [1].
1469 const std::vector<Rectangle> cut_rectangles =
1470 CutShapeIntoRectangles(std::move(shape));
1471 result.insert(result.end(), cut_rectangles.begin(), cut_rectangles.end());
1472 }
1473 DCHECK(RegionIncludesOther(original_result, result) &&
1474 RegionIncludesOther(result, original_result));
1475
1476 // It is possible that the algorithm actually increases the number of boxes.
1477 // See the "Problematic2" test.
1478 if (result.size() >= mandatory_rectangles->size()) return false;
1479 mandatory_rectangles->swap(result);
1480 optional_rectangles->swap(new_optional_rectangles);
1481 return true;
1482}
1483
1485 absl::Span<const RectangleInRange> non_fixed_boxes,
1486 absl::Span<const Rectangle> fixed_boxes, int max_num_components) {
1487 if (max_num_components <= 1) return {};
1488
1489 IntegerValue min_x_size = std::numeric_limits<IntegerValue>::max();
1490 IntegerValue min_y_size = std::numeric_limits<IntegerValue>::max();
1491
1492 CHECK(!non_fixed_boxes.empty());
1493 Rectangle bounding_box = non_fixed_boxes[0].bounding_area;
1494
1495 for (const RectangleInRange& box : non_fixed_boxes) {
1496 bounding_box.GrowToInclude(box.bounding_area);
1497 min_x_size = std::min(min_x_size, box.x_size);
1498 min_y_size = std::min(min_y_size, box.y_size);
1499 }
1500 DCHECK_GT(min_x_size, 0);
1501 DCHECK_GT(min_y_size, 0);
1502
1503 std::vector<Rectangle> optional_boxes = FindSpacesThatCannotBeOccupied(
1504 fixed_boxes, non_fixed_boxes, bounding_box, min_x_size, min_y_size);
1505 std::vector<Rectangle> unoccupiable_space = {fixed_boxes.begin(),
1506 fixed_boxes.end()};
1507 unoccupiable_space.insert(unoccupiable_space.end(), optional_boxes.begin(),
1508 optional_boxes.end());
1509
1510 std::vector<Rectangle> occupiable_space =
1511 FindEmptySpaces(bounding_box, unoccupiable_space);
1512
1513 std::vector<Rectangle> empty;
1514 ReduceNumberofBoxesGreedy(&occupiable_space, &empty);
1515 std::vector<std::vector<int>> space_components =
1517
1518 if (space_components.size() == 1 ||
1519 space_components.size() > max_num_components) {
1520 return {};
1521 }
1522
1523 // If we are here, that means that the space where boxes can be placed is not
1524 // connected.
1526 absl::flat_hash_set<int> component_set;
1527 for (const std::vector<int>& component : space_components) {
1528 Rectangle bin_bounding_box = occupiable_space[component[0]];
1529 for (int i = 1; i < component.size(); ++i) {
1530 bin_bounding_box.GrowToInclude(occupiable_space[component[i]]);
1531 }
1532 std::optional<Rectangle> reachable_area_bounding_box;
1533 Disjoint2dPackingResult::Bin& bin = result.bins.emplace_back();
1534 for (int idx : component) {
1535 bin.bin_area.push_back(occupiable_space[idx]);
1536 }
1537 for (int i = 0; i < non_fixed_boxes.size(); i++) {
1538 if (!non_fixed_boxes[i].bounding_area.IsDisjoint(bin_bounding_box)) {
1539 if (reachable_area_bounding_box.has_value()) {
1540 reachable_area_bounding_box->GrowToInclude(
1541 non_fixed_boxes[i].bounding_area);
1542 } else {
1543 reachable_area_bounding_box = non_fixed_boxes[i].bounding_area;
1544 }
1545 bin.non_fixed_box_indexes.push_back(i);
1546 }
1547 }
1548 if (bin.non_fixed_box_indexes.empty()) {
1549 result.bins.pop_back();
1550 continue;
1551 }
1552 bin.fixed_boxes =
1553 FindEmptySpaces(*reachable_area_bounding_box, bin.bin_area);
1555 }
1556 VLOG_EVERY_N_SEC(1, 1) << "Detected a bin packing problem with "
1557 << result.bins.size()
1558 << " bins. Original problem sizes: "
1559 << non_fixed_boxes.size() << " non-fixed boxes, "
1560 << fixed_boxes.size() << " fixed boxes.";
1561 return result;
1562}
1563
1564} // namespace sat
1565} // namespace operations_research
const bool DEBUG_MODE
Definition macros.h:26
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:58
bool ReduceNumberOfBoxesExactMandatory(std::vector< Rectangle > *mandatory_rectangles, std::vector< Rectangle > *optional_rectangles)
std::vector< std::vector< int > > SplitInConnectedComponents(const Neighbours &neighbours)
bool RegionIncludesOther(absl::Span< const Rectangle > region, absl::Span< const Rectangle > other)
The two regions must be defined by non-overlapping rectangles.
Definition diffn_util.h:694
bool operator==(const BoolArgumentProto &lhs, const BoolArgumentProto &rhs)
std::vector< Rectangle > FindEmptySpaces(const Rectangle &bounding_box, std::vector< Rectangle > ocupied_rectangles)
std::vector< SingleShape > BoxesToShapes(absl::Span< const Rectangle > rectangles, const Neighbours &neighbours)
Disjoint2dPackingResult DetectDisjointRegionIn2dPacking(absl::Span< const RectangleInRange > non_fixed_boxes, absl::Span< const Rectangle > fixed_boxes, int max_num_components)
std::vector< Rectangle > PavedRegionDifference(std::vector< Rectangle > original_region, absl::Span< const Rectangle > area_to_remove)
H AbslHashValue(H h, const IntVar &i)
– ABSL HASHING SUPPORT --------------------------------------------------—
Definition cp_model.h:515
bool ReduceNumberofBoxesGreedy(std::vector< Rectangle > *mandatory_rectangles, std::vector< Rectangle > *optional_rectangles)
std::vector< Rectangle > CutShapeIntoRectangles(SingleShape shape)
void AbslStringify(Sink &sink, EdgePosition e)
std::vector< std::pair< int, int > > FindPartialRectangleIntersections(absl::Span< const Rectangle > rectangles)
Neighbours BuildNeighboursGraph(absl::Span< const Rectangle > rectangles)
bool PresolveFixed2dRectangles(absl::Span< const RectangleInRange > non_fixed_boxes, std::vector< Rectangle > *fixed_boxes)
std::string RenderDot(std::optional< Rectangle > bb, absl::Span< const Rectangle > solution, std::string_view extra_dot_payload)
In SWIG mode, we don't want anything besides these top-level includes.
std::vector< bool > BipartiteMinimumVertexCover(const std::vector< std::vector< int > > &left_to_right_arcs, int num_right)
void FindStronglyConnectedComponents(NodeIndex num_nodes, const Graph &graph, SccOutput *components)
Simple wrapper function for most usage.
std::vector< Rectangle > fixed_boxes
Fixed boxes that the non-fixed boxes in this bin cannot overlap with.
std::vector< int > non_fixed_box_indexes
Non-fixed boxes on the original problem to copy to this new constraint.
void GrowToInclude(const Rectangle &other)
Definition diffn_util.h:50
absl::InlinedVector< Rectangle, 4 > RegionDifference(const Rectangle &other) const
Definition diffn_util.cc:62
std::vector< std::pair< IntegerValue, IntegerValue > > step_points
The two vectors should have exactly the same size.