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