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