• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

celerity / celerity-runtime / 12009901531

25 Nov 2024 12:20PM UTC coverage: 94.92% (+0.009%) from 94.911%
12009901531

push

github

fknorr
Add missing includes and consistently order them

We can't add the misc-include-cleaner lint because it causes too many
false positives with "interface headers" such as sycl.hpp.

3190 of 3626 branches covered (87.98%)

Branch coverage included in aggregate %.

7049 of 7161 relevant lines covered (98.44%)

1242183.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.41
/src/grid.cc
1
#include "grid.h"
2

3
#include "ranges.h"
4
#include "utils.h"
5

6
#include <algorithm>
7
#include <cassert>
8
#include <cstddef>
9
#include <iterator>
10
#include <type_traits>
11
#include <vector>
12

13

14
namespace celerity::detail::grid_detail {
15

16
// Regions have a storage dimensionality (the `Dims` template parameter of `class region`) and an effective dimensionality that is smaller iff all contained
17
// boxes are effectively the result of casting e.g. box<2> to box<3>, or the described region "accidentally" is a lower-dimensional slice of the full space.
18
// This property is detected at runtime through {box,region}::get_effective_dims(), and all region-algorithm implementations are generic over both StorageDims
19
// and EffectiveDims to optimize for the embedding of arbitrary-dimensional regions into region<3> as it commonly happens in the runtime.
20

21
// Like detail::box_intersection, but aware of effective dimensionality
22
template <int EffectiveDims, int StorageDims>
23
box<StorageDims> box_intersection(const box<StorageDims>& box1, const box<StorageDims>& box2) {
86,711✔
24
        static_assert(EffectiveDims <= StorageDims);
25

26
        id<StorageDims> min;
86,711✔
27
        id<StorageDims> max;
86,711✔
28
        for(int d = 0; d < EffectiveDims; ++d) {
171,824✔
29
                min[d] = std::max(box1.get_min()[d], box2.get_min()[d]);
146,452✔
30
                max[d] = std::min(box1.get_max()[d], box2.get_max()[d]);
146,452✔
31
                if(min[d] >= max[d]) return {};
356,394✔
32
        }
33
        for(int d = EffectiveDims; d < StorageDims; ++d) {
27,356✔
34
                min[d] = 0;
1,984✔
35
                max[d] = 1;
1,984✔
36
        }
37
        return make_box<StorageDims>(non_empty, min, max);
25,372✔
38
}
39

40
// Like box::covers, but aware of effective dimensionality
41
template <int EffectiveDims, int StorageDims>
42
bool box_covers(const box<StorageDims>& top, const box<StorageDims>& bottom) {
46,645✔
43
        static_assert(EffectiveDims <= StorageDims);
44

45
        // empty boxes are normalized and thus may not intersect in coordinates
46
        if(bottom.empty()) return true;
46,645!
47

48
        for(int d = 0; d < EffectiveDims; ++d) {
79,996✔
49
                if(bottom.get_min()[d] < top.get_min()[d]) return false;
55,748✔
50
                if(bottom.get_max()[d] > top.get_max()[d]) return false;
45,890✔
51
        }
52
        return true;
24,248✔
53
}
54

55
// In a range of boxes that are identical in all dimensions except MergeDim, merge all connected boxes ("unconditional directional merge")
56
template <int MergeDim, typename BidirectionalIterator>
57
BidirectionalIterator merge_connected_intervals(BidirectionalIterator begin, BidirectionalIterator end) {
430,942✔
58
        using box_type = typename std::iterator_traits<BidirectionalIterator>::value_type;
59

60
        if(begin == end || std::next(begin) == end) return end; // common-case shortcut: no merge is possible
857,795✔
61

62
        // Sort by interval starting point
63
        std::sort(begin, end, [](const box_type& lhs, const box_type& rhs) { return lhs.get_min()[MergeDim] < rhs.get_min()[MergeDim]; });
1,935,103✔
64

65
        // The range is both read and written from left-to-right, avoiding repeated left-shifts for compaction
66
        auto out_end = begin;
80,558✔
67

68
        // Merge all connected boxes along MergeDim in O(N) by replacing each connected sequence with its bounding box
69
        while(begin != end) {
294,848✔
70
                const auto merged_min = begin->get_min();
214,290✔
71
                auto merged_max = begin->get_max();
214,290✔
72
                for(++begin; begin != end && begin->get_min()[MergeDim] <= merged_max[MergeDim]; ++begin) {
289,471✔
73
                        merged_max[MergeDim] = std::max(merged_max[MergeDim], begin->get_max()[MergeDim]);
75,182✔
74
                }
75
                *out_end++ = make_box<box_type::dimensions>(grid_detail::non_empty, merged_min, merged_max);
214,289✔
76
        }
77

78
        return out_end;
80,558✔
79
}
80

81
// In an arbitrary range of boxes, merge all boxes that are identical in all dimensions except MergeDim ("conditional directional merge").
82
template <int MergeDim, int EffectiveDims, typename BidirectionalIterator>
83
BidirectionalIterator merge_connected_boxes_along_dim(const BidirectionalIterator begin, const BidirectionalIterator end) {
225,137✔
84
        using box_type = typename std::iterator_traits<BidirectionalIterator>::value_type;
85
        static_assert(EffectiveDims <= box_type::dimensions);
86
        static_assert(MergeDim < EffectiveDims);
87

88
        constexpr auto orthogonal_to_merge_dim = [](const box_type& lhs, const box_type& rhs) {
3,877,634✔
89
                for(int d = 0; d < EffectiveDims; ++d) {
7,680,684✔
90
                        if(d == MergeDim) continue;
6,280,861✔
91
                        // arbitrary but consistent ordering along all orthogonal dimensions
92
                        if(lhs.get_min()[d] < rhs.get_min()[d]) return true;
3,861,813✔
93
                        if(lhs.get_min()[d] > rhs.get_min()[d]) return false;
2,024,590✔
94
                        if(lhs.get_max()[d] < rhs.get_max()[d]) return true;
1,667,073✔
95
                        if(lhs.get_max()[d] > rhs.get_max()[d]) return false;
1,617,876!
96
                }
97
                return false;
1,399,823✔
98
        };
99

100
        if constexpr(EffectiveDims == 1) {
101
                return merge_connected_intervals<MergeDim>(begin, end);
13,793✔
102
        } else {
103
                // partition [begin, end) into sequences of boxes that are potentially mergeable wrt/ the dimensions orthogonal to MergeDim.
104
                // This reduces complexity from O(n^3) to O(n log n) + O(m^3), where m is the longest mergeable sequence in that regard.
105
                std::sort(begin, end, orthogonal_to_merge_dim);
211,344✔
106

107
                // we want the result to be contiguous in [begin, out_end), so in each iteration, we merge all boxes of a MergeDim-equal partition at their original
108
                // position in the iterator range; and then shift the merged range back to fill any gap left by merge of a previous partition.
109
                auto out_end = begin;
211,343✔
110

111
                for(auto equal_begin = begin; equal_begin != end;) {
628,492✔
112
                        // O(n) std::find_if could be replaced by O(log n) std::partition_point, but we expect the number of "equal" elements to be small
113
                        const auto equal_end = std::find_if(std::next(equal_begin), end, [&](const box_type& box) {
1,275,009✔
114
                                return orthogonal_to_merge_dim(*equal_begin, box); // true if box is in a partition _after_ *equal_begin
440,713✔
115
                        });
116
                        const auto merged_end = merge_connected_intervals<MergeDim>(equal_begin, equal_end);
417,148✔
117
                        // shift the newly merged boxes to the left to close any gap opened by the merge of a previous partition
118
                        out_end = std::move(equal_begin, merged_end, out_end);
417,149✔
119
                        equal_begin = equal_end;
417,149✔
120
                }
121

122
                return out_end;
422,688✔
123
        }
124
}
125

126
// explicit instantiations for tests (might otherwise be inlined)
127
template box_vector<1>::iterator merge_connected_boxes_along_dim<0, 1>(box_vector<1>::iterator begin, box_vector<1>::iterator end);
128
template box_vector<2>::iterator merge_connected_boxes_along_dim<0, 2>(box_vector<2>::iterator begin, box_vector<2>::iterator end);
129
template box_vector<2>::iterator merge_connected_boxes_along_dim<1, 2>(box_vector<2>::iterator begin, box_vector<2>::iterator end);
130
template box_vector<3>::iterator merge_connected_boxes_along_dim<0, 3>(box_vector<3>::iterator begin, box_vector<3>::iterator end);
131
template box_vector<3>::iterator merge_connected_boxes_along_dim<1, 3>(box_vector<3>::iterator begin, box_vector<3>::iterator end);
132
template box_vector<3>::iterator merge_connected_boxes_along_dim<2, 3>(box_vector<3>::iterator begin, box_vector<3>::iterator end);
133

134
// For higher-dimensional regions, the order in which dimensions are merged is relevant for the shape of the resulting box set. We merge along the last
135
// ("fastest") dimension first to make sure the resulting boxes cover the largest possible extent of contiguous memory when are applied to buffers.
136
template <int MergeDim, int EffectiveDims, typename BidirectionalIterator>
137
BidirectionalIterator merge_connected_boxes_recurse(const BidirectionalIterator begin, BidirectionalIterator end) {
225,128✔
138
        static_assert(MergeDim >= 0 && MergeDim < EffectiveDims);
139
        end = merge_connected_boxes_along_dim<MergeDim, EffectiveDims>(begin, end);
225,128✔
140
        if constexpr(MergeDim > 0) { end = merge_connected_boxes_recurse<MergeDim - 1, EffectiveDims>(begin, end); }
117,562✔
141
        return end;
225,127✔
142
}
143

144
// Merge all adjacent boxes that are connected and identical in all except a single dimension.
145
template <int EffectiveDims, typename BidirectionalIterator>
146
BidirectionalIterator merge_connected_boxes(const BidirectionalIterator begin, BidirectionalIterator end) {
117,502✔
147
        using box_type = typename std::iterator_traits<BidirectionalIterator>::value_type;
148
        static_assert(EffectiveDims <= box_type::dimensions);
149
        if constexpr(EffectiveDims > 0) { end = merge_connected_boxes_recurse<EffectiveDims - 1, EffectiveDims>(begin, end); }
107,566✔
150
        return end;
117,501✔
151
}
152

153
// Split a box into parts according to dissection lines in `cuts`, where `cuts` is indexed by component dimension. This function is not generic
154
// over EffectiveDims, rather, `cuts` will have 1 <= n <= StorageDims entries to indicate along how many dimensions the box should be dissected.
155
template <int StorageDims>
156
void dissect_box(const box<StorageDims>& in_box, const std::vector<std::vector<size_t>>& cuts, box_vector<StorageDims>& out_dissected, int dim) {
132,796✔
157
        assert(dim < static_cast<int>(cuts.size()));
132,796✔
158

159
        const auto& dim_cuts = cuts[static_cast<size_t>(dim)];
132,796✔
160
        assert(std::is_sorted(dim_cuts.begin(), dim_cuts.end()));
132,796✔
161

162
        // start of the first (current) dissected box
163
        size_t start = in_box.get_min()[dim];
132,795✔
164
        // find the first cut that lies inside the box (dim_cuts is sorted)
165
        auto cut_it = std::lower_bound(dim_cuts.begin(), dim_cuts.end(), /* not less or equal */ start + 1);
132,795✔
166

167
        for(;;) {
159,336✔
168
                // the end of the current box is either the last cut that lies inside the box, or the end of in_box
169
                size_t end;
170
                if(cut_it != dim_cuts.end() && *cut_it < in_box.get_max()[dim]) {
292,132!
171
                        end = *cut_it++;
26,540✔
172
                } else {
173
                        end = in_box.get_max()[dim];
265,592✔
174
                }
175
                if(end == start) break;
292,131✔
176

177
                // compute coordinates for the dissected box along `dim`, and recursively dissect it further along `dim + 1`
178
                auto min = in_box.get_min();
159,335✔
179
                auto max = in_box.get_max();
159,335✔
180
                min[dim] = start;
159,335✔
181
                max[dim] = end;
159,335✔
182
                const auto small_box = make_box<StorageDims>(grid_detail::non_empty, min, max);
159,335✔
183
                if(dim + 1 < static_cast<int>(cuts.size())) {
159,335✔
184
                        dissect_box(small_box, cuts, out_dissected, dim + 1);
29,450✔
185
                } else {
186
                        out_dissected.push_back(small_box);
129,885✔
187
                }
188

189
                start = end;
159,336✔
190
        }
191
}
132,796✔
192

193
// explicit instantiations for tests (might otherwise be inlined)
194
template void dissect_box(const box<2>& in_box, const std::vector<std::vector<size_t>>& cuts, box_vector<2>& out_dissected, int dim);
195
template void dissect_box(const box<3>& in_box, const std::vector<std::vector<size_t>>& cuts, box_vector<3>& out_dissected, int dim);
196

197
// Apply dissect_box to all boxes in a range, with a shortcut if no cuts are to be done.
198
template <typename InputIterator>
199
void dissect_boxes(const InputIterator begin, const InputIterator end, const std::vector<std::vector<size_t>>& cuts,
35,914✔
200
    box_vector<std::iterator_traits<InputIterator>::value_type::dimensions>& out_dissected) {
201
        if(!cuts.empty()) {
35,914✔
202
                for(auto it = begin; it != end; ++it) {
129,848✔
203
                        dissect_box(*it, cuts, out_dissected, 0);
103,345✔
204
                }
205
        } else {
206
                out_dissected.insert(out_dissected.end(), begin, end);
9,411✔
207
        }
208
}
35,914✔
209

210
// Collect the sorted, unique list of box start- and end points along a single dimension. These can then be used in dissect_boxes.
211
template <typename InputIterator>
212
std::vector<size_t> collect_dissection_lines(const InputIterator begin, const InputIterator end, int dim) {
40,281✔
213
        std::vector<size_t> cuts;
40,281✔
214
        // allocating 2*N integers might seem wasteful, but this has negligible runtime in the profiler and is already algorithmically optimal at O(N log N)
215
        cuts.reserve(std::distance(begin, end) * 2);
40,281✔
216
        for(auto it = begin; it != end; ++it) {
162,538✔
217
                cuts.push_back(it->get_min()[dim]);
122,257✔
218
                cuts.push_back(it->get_max()[dim]);
122,257✔
219
        }
220
        std::sort(cuts.begin(), cuts.end());
40,281✔
221
        cuts.erase(std::unique(cuts.begin(), cuts.end()), cuts.end());
40,281✔
222
        assert(begin == end || cuts.size() >= 2);
40,281✔
223
        return cuts;
40,281✔
224
}
×
225

226
template <int EffectiveDims, int StorageDims>
227
void normalize_impl(box_vector<StorageDims>& boxes) {
51,752✔
228
        static_assert(EffectiveDims <= StorageDims);
229
        assert(!boxes.empty());
51,752✔
230

231
        if constexpr(EffectiveDims == 0) {
232
                // all 0d boxes are identical
233
                boxes.resize(1, box<StorageDims>());
5,752✔
234
        } else if constexpr(EffectiveDims == 1) {
235
                // merge_connected_boxes will sort and merge - this is already the complete 1d normalization
236
                boxes.erase(merge_connected_boxes<EffectiveDims>(boxes.begin(), boxes.end()), boxes.end());
8,373✔
237
                assert(!boxes.empty());
8,373✔
238
                assert(std::is_sorted(boxes.begin(), boxes.end(), box_coordinate_order()));
8,373✔
239
        } else {
240
                // 0. (hopefully) fast path: attempt to merge without dissecting first
241
                boxes.erase(merge_connected_boxes<EffectiveDims>(boxes.begin(), boxes.end()), boxes.end());
42,227✔
242
                assert(!boxes.empty());
42,227✔
243
                if(boxes.size() == 1) return;
42,227✔
244

245
                // 1. dissect boxes along the edges of all other boxes (except the last, "fastest" dim) to create the "maximally mergeable set" of boxes for step 2
246
                std::vector<std::vector<size_t>> cuts(EffectiveDims - 1);
37,311✔
247
                for(int d = 0; d < EffectiveDims - 1; ++d) {
26,548✔
248
                        cuts[static_cast<size_t>(d)] = collect_dissection_lines(boxes.begin(), boxes.end(), d);
14,111✔
249
                }
250

251
                box_vector<StorageDims> dissected_boxes;
12,437✔
252
                dissect_boxes(boxes.begin(), boxes.end(), cuts, dissected_boxes);
12,437✔
253
                boxes = std::move(dissected_boxes);
12,437✔
254

255
                // 2. the dissected tiling of boxes only potentially overlaps in the fastest dimension - merge where possible
256
                boxes.erase(merge_connected_boxes<EffectiveDims>(boxes.begin(), boxes.end()), boxes.end());
12,437✔
257

258
                // 3. normalize box order
259
                std::sort(boxes.begin(), boxes.end(), box_coordinate_order());
12,437✔
260
        }
12,437✔
261
}
9,525✔
262

263
// Use together with a generic functor to dispatch the EffectiveDims template parameter at runtime
264
template <int StorageDims, typename F>
265
decltype(auto) dispatch_effective_dims(int effective_dims, F&& f) {
106,217✔
266
        assert(effective_dims <= StorageDims);
106,217✔
267

268
        // clang-format off
269
        switch(effective_dims) {
106,217!
270
        case 0: if constexpr(StorageDims >= 0) { return f(std::integral_constant<int, 0>()); } [[fallthrough]];
11,088✔
271
        case 1: if constexpr(StorageDims >= 1) { return f(std::integral_constant<int, 1>()); } [[fallthrough]];
13,791✔
272
        case 2: if constexpr(StorageDims >= 2) { return f(std::integral_constant<int, 2>()); } [[fallthrough]];
59,225✔
273
        case 3: if constexpr(StorageDims >= 3) { return f(std::integral_constant<int, 3>()); } [[fallthrough]];
22,113✔
274
        default: utils::unreachable(); // with the explicit instantiations in this file - LCOV_EXCL_LINE
275
        }
276
        // clang-format on
277
}
278

279
// For any set of boxes, find the unique box tiling that covers the same points and is subject to the following constraints:
280
//   1. the extent of every box is maximized along the last dimension, then along the second-to-last dimension, and so forth.
281
//   2. no two boxes within the tiling intersect (i.e. cover a common point).
282
//   3. the tiling contains no empty boxes.
283
//   4. the normalized sequence is sorted according to box_coordinate_order.
284
// There is exactly one sequence of boxes for any set of points that fulfills 1-4, meaning that an "==" comparison of normalized tilings would be equivalent
285
// to an equality comparision of the covered point sets.
286
template <int Dims>
287
void normalize(box_vector<Dims>& boxes) {
487,528✔
288
        boxes.erase(std::remove_if(boxes.begin(), boxes.end(), std::mem_fn(&box<Dims>::empty)), boxes.end());
487,528✔
289
        if(boxes.size() <= 1) return;
487,522✔
290

291
        const auto effective_dims = get_effective_dims(boxes.begin(), boxes.end());
51,752✔
292
        assert(effective_dims <= Dims);
51,752✔
293

294
        dispatch_effective_dims<Dims>(effective_dims, [&](const auto effective_dims) { //
155,256✔
295
                normalize_impl<effective_dims.value>(boxes);
51,752✔
296
        });
297
}
298

299
// explicit instantiations for tests (might otherwise be inlined into region::region)
300
template void normalize(box_vector<0>& boxes);
301
template void normalize(box_vector<1>& boxes);
302
template void normalize(box_vector<2>& boxes);
303
template void normalize(box_vector<3>& boxes);
304

305
template <int EffectiveDims, int StorageDims>
306
region<StorageDims> region_intersection_impl(const region<StorageDims>& lhs, const region<StorageDims>& rhs) {
28,725✔
307
        static_assert(EffectiveDims <= StorageDims);
308

309
        // O(N * M) naively collect intersections of box pairs.
310
        // There might be a way to optimize this further through sorting one side and finding potentially intersecting boxes through lower_bound + upper_bound.
311
        // I have previously attempted to implement this entirely without box_intersection by dissecting both sides by the union of their dissection lines,
312
        // sorting both by box_coordinate_order and finding common boxes through std::set_intersection. Practically this turned out to be slower, sometimes
313
        // by several orders of magnitude, as the number of dissected boxes can grow to O((N * M) ^ EffectiveDims).
314
        box_vector<StorageDims> intersection;
28,725✔
315
        for(const auto& left : lhs.get_boxes()) {
86,471✔
316
                for(const auto& right : rhs.get_boxes()) {
144,457✔
317
                        if(const auto box = grid_detail::box_intersection<EffectiveDims>(left, right); !box.empty()) { intersection.push_back(box); }
86,711✔
318
                }
319
        }
320

321
        // No dissection step is necessary as the intersection of two normalized tilings is already "maximally mergeable".
322
        const auto begin = intersection.begin();
28,725✔
323
        auto end = intersection.end();
28,725✔
324
        end = grid_detail::merge_connected_boxes<EffectiveDims>(begin, end);
28,725✔
325

326
        // intersected_boxes retains the sorting from lhs, but for Dims > 1, the intersection can shift min-points such that the box_coordinate_order reverses.
327
        if constexpr(EffectiveDims > 1) {
328
                std::sort(begin, end, box_coordinate_order());
28,018✔
329
        } else {
330
                assert(std::is_sorted(begin, end, box_coordinate_order()));
707✔
331
        }
332

333
        intersection.erase(end, intersection.end());
28,725✔
334
        return grid_detail::make_region<StorageDims>(grid_detail::normalized, std::move(intersection));
57,450✔
335
}
28,725✔
336

337
// Complete the region_difference operation with an already dissected left-hand side and knowledge of effective dimensionality.
338
template <int EffectiveDims, int StorageDims>
339
void apply_region_difference(box_vector<StorageDims>& dissected_left, const region<StorageDims>& rhs) {
23,477✔
340
        static_assert(EffectiveDims <= StorageDims);
341

342
        // O(N * M) remove all dissected boxes from lhs that are fully covered by any box in rhs.
343
        // For further optimization potential see the comments on region_intersection_impl.
344
        const auto left_begin = dissected_left.begin();
23,477✔
345
        auto left_end = dissected_left.end();
23,477✔
346
        for(const auto& right : rhs.get_boxes()) {
47,133✔
347
                for(auto left_it = left_begin; left_it != left_end;) {
70,301✔
348
                        if(grid_detail::box_covers<EffectiveDims>(right, *left_it)) {
46,645✔
349
                                *left_it = *--left_end;
24,248✔
350
                        } else {
351
                                ++left_it;
22,397✔
352
                        }
353
                }
354
        }
355

356
        // merge the now non-overlapping boxes
357
        left_end = grid_detail::merge_connected_boxes<EffectiveDims>(left_begin, left_end);
23,477✔
358
        dissected_left.erase(left_end, dissected_left.end());
23,477✔
359
}
23,477✔
360

361
} // namespace celerity::detail::grid_detail
362

363
namespace celerity::detail {
364

365
template <int Dims>
366
void merge_connected_boxes(box_vector<Dims>& boxes) {
2,263✔
367
        const auto merged_end = grid_detail::dispatch_effective_dims<Dims>(grid_detail::get_effective_dims(boxes.begin(), boxes.end()),
4,526✔
368
            [&](const auto effective_dims) { return grid_detail::merge_connected_boxes<effective_dims.value>(boxes.begin(), boxes.end()); });
6,789✔
369
        boxes.erase(merged_end, boxes.end());
2,263✔
370
}
2,263✔
371

372
template void merge_connected_boxes(box_vector<0>& boxes);
373
template void merge_connected_boxes(box_vector<1>& boxes);
374
template void merge_connected_boxes(box_vector<2>& boxes);
375
template void merge_connected_boxes(box_vector<3>& boxes);
376

377
template <int Dims>
378
region<Dims>::region(const box& single_box) : region(box_vector{single_box}) {} // still need to normalize in case single_box is empty
1,297,234✔
379

380
template <int Dims>
381
region<Dims>::region(const subrange<Dims>& single_sr) : region(box(single_sr)) {}
25,866✔
382

383
template <int Dims>
384
region<Dims>::region(box_vector&& boxes) : region(grid_detail::normalized, (/* in-place */ grid_detail::normalize(boxes), /* then */ std::move(boxes))) {}
487,519✔
385

386
template <int Dims>
387
region<Dims>::region(grid_detail::normalized_t /* tag */, box_vector&& boxes) : m_boxes(std::move(boxes)) {}
639,881✔
388

389
template class region<0>;
390
template class region<1>;
391
template class region<2>;
392
template class region<3>;
393

394
template <int Dims>
395
region<Dims> region_union(const region<Dims>& lhs, const region<Dims>& rhs) {
132,354✔
396
        // shortcut-evaluate trivial cases
397
        if(lhs.empty()) return rhs;
132,354✔
398
        if(rhs.empty()) return lhs;
56,109✔
399

400
        box_vector<Dims> box_union;
46,783✔
401
        box_union.reserve(lhs.get_boxes().size() + rhs.get_boxes().size());
46,783✔
402
        box_union.insert(box_union.end(), lhs.get_boxes().begin(), lhs.get_boxes().end());
46,783✔
403
        box_union.insert(box_union.end(), rhs.get_boxes().begin(), rhs.get_boxes().end());
46,783✔
404
        return region<Dims>(std::move(box_union));
46,783✔
405
}
46,783✔
406

407
template region<0> region_union(const region<0>& lhs, const region<0>& rhs);
408
template region<1> region_union(const region<1>& lhs, const region<1>& rhs);
409
template region<2> region_union(const region<2>& lhs, const region<2>& rhs);
410
template region<3> region_union(const region<3>& lhs, const region<3>& rhs);
411

412
template <int Dims>
413
region<Dims> region_intersection(const region<Dims>& lhs, const region<Dims>& rhs) {
144,644✔
414
        // shortcut-evaluate trivial cases
415
        if(lhs.empty() || rhs.empty()) return {};
144,644✔
416
        if(lhs.get_boxes().size() == 1 && rhs.get_boxes().size() == 1) { return box_intersection(lhs.get_boxes().front(), rhs.get_boxes().front()); }
139,123✔
417

418
        const auto effective_dims = std::max(lhs.get_effective_dims(), rhs.get_effective_dims());
28,725✔
419
        return grid_detail::dispatch_effective_dims<Dims>(effective_dims, [&](const auto effective_dims) { //
57,450✔
420
                return grid_detail::region_intersection_impl<effective_dims.value>(lhs, rhs);
28,725✔
421
        });
28,725✔
422
}
423

424
template region<0> region_intersection(const region<0>& lhs, const region<0>& rhs);
425
template region<1> region_intersection(const region<1>& lhs, const region<1>& rhs);
426
template region<2> region_intersection(const region<2>& lhs, const region<2>& rhs);
427
template region<3> region_intersection(const region<3>& lhs, const region<3>& rhs);
428

429
template <int Dims>
430
region<Dims> region_difference(const region<Dims>& lhs, const region<Dims>& rhs) {
25,474✔
431
        // shortcut-evaluate trivial cases
432
        if(lhs.empty()) return {};
25,474✔
433
        if(rhs.empty()) return lhs;
24,560✔
434

435
        // the resulting effective_dims can never be greater than the lhs dimension, but the difference operator must still operate on all available dimensions
436
        // to correctly identify overlapping boxes
437
        const auto effective_dims = std::max(lhs.get_effective_dims(), rhs.get_effective_dims());
23,477✔
438
        assert(effective_dims <= Dims);
23,477✔
439

440
        // 1. collect dissection lines (in *all* dimensions) from rhs
441
        std::vector<std::vector<size_t>> cuts(effective_dims);
70,431✔
442
        for(int d = 0; d < effective_dims; ++d) {
49,647✔
443
                cuts[static_cast<size_t>(d)] = grid_detail::collect_dissection_lines(rhs.get_boxes().begin(), rhs.get_boxes().end(), d);
26,170✔
444
        }
445

446
        // 2. dissect lhs according to the lines of rhs, so that any overlap between lhs and rhs is turned into an lhs box fully covered by an rhs box
447
        box_vector<Dims> dissected_left;
23,477✔
448
        grid_detail::dissect_boxes(lhs.get_boxes().begin(), lhs.get_boxes().end(), cuts, dissected_left);
23,477✔
449

450
        grid_detail::dispatch_effective_dims<Dims>(effective_dims, [&](const auto effective_dims) { //
70,431✔
451
                grid_detail::apply_region_difference<effective_dims.value>(dissected_left, rhs);
23,477✔
452
        });
453
        std::sort(dissected_left.begin(), dissected_left.end(), box_coordinate_order());
23,477✔
454

455
        return grid_detail::make_region<Dims>(grid_detail::normalized, std::move(dissected_left));
23,477✔
456
}
23,477✔
457

458
template region<0> region_difference(const region<0>& lhs, const region<0>& rhs);
459
template region<1> region_difference(const region<1>& lhs, const region<1>& rhs);
460
template region<2> region_difference(const region<2>& lhs, const region<2>& rhs);
461
template region<3> region_difference(const region<3>& lhs, const region<3>& rhs);
462

463
} // namespace celerity::detail
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc