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

celerity / celerity-runtime / 7885302508

13 Feb 2024 10:44AM UTC coverage: 93.919% (-0.03%) from 93.945%
7885302508

Pull #250

github

fknorr
Update benchmark results after logging patch
Pull Request #250: Fix: Disable logging in benchmarks

2244 of 2518 branches covered (89.12%)

Branch coverage included in aggregate %.

4999 of 5194 relevant lines covered (96.25%)

230244.16 hits per line

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

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

3
namespace celerity::detail::grid_detail {
4

5
// Regions have a storage dimensionality (the `Dims` template parameter of `class region`) and an effective dimensionality that is smaller iff all contained
6
// 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.
7
// This property is detected at runtime through {box,region}::get_effective_dims(), and all region-algorithm implementations are generic over both StorageDims
8
// and EffectiveDims to optimize for the embedding of arbitrary-dimensional regions into region<3> as it commonly happens in the runtime.
9

10
// Like detail::box_intersection, but aware of effective dimensionality
11
template <int EffectiveDims, int StorageDims>
12
box<StorageDims> box_intersection(const box<StorageDims>& box1, const box<StorageDims>& box2) {
14,244✔
13
        static_assert(EffectiveDims <= StorageDims);
14

15
        id<StorageDims> min;
14,244✔
16
        id<StorageDims> max;
14,244✔
17
        for(int d = 0; d < EffectiveDims; ++d) {
20,345✔
18
                min[d] = std::max(box1.get_min()[d], box2.get_min()[d]);
18,951✔
19
                max[d] = std::min(box1.get_max()[d], box2.get_max()[d]);
18,954✔
20
                if(min[d] >= max[d]) return {};
50,476✔
21
        }
22
        for(int d = EffectiveDims; d < StorageDims; ++d) {
2,882✔
23
                min[d] = 0;
1,488✔
24
                max[d] = 1;
1,488✔
25
        }
26
        return make_box<StorageDims>(non_empty, min, max);
1,395✔
27
}
28

29
// Like box::covers, but aware of effective dimensionality
30
template <int EffectiveDims, int StorageDims>
31
bool box_covers(const box<StorageDims>& top, const box<StorageDims>& bottom) {
14,944✔
32
        static_assert(EffectiveDims <= StorageDims);
33

34
        // empty boxes are normalized and thus may not intersect in coordinates
35
        if(bottom.empty()) return true;
14,944!
36

37
        for(int d = 0; d < EffectiveDims; ++d) {
28,228✔
38
                if(bottom.get_min()[d] < top.get_min()[d]) return false;
15,374✔
39
                if(bottom.get_max()[d] > top.get_max()[d]) return false;
14,334✔
40
        }
41
        return true;
12,854✔
42
}
43

44
// In a range of boxes that are identical in all dimensions except MergeDim, merge all connected boxes ("unconditional directional merge")
45
template <int MergeDim, typename BidirectionalIterator>
46
BidirectionalIterator merge_connected_intervals(BidirectionalIterator begin, BidirectionalIterator end) {
55,198✔
47
        using box_type = typename std::iterator_traits<BidirectionalIterator>::value_type;
48

49
        if(begin == end || std::next(begin) == end) return end; // common-case shortcut: no merge is possible
55,198!
50

51
        // Sort by interval starting point
52
        std::sort(begin, end, [](const box_type& lhs, const box_type& rhs) { return lhs.get_min()[MergeDim] < rhs.get_min()[MergeDim]; });
46,610✔
53

54
        // The range is both read and written from left-to-right, avoiding repeated left-shifts for compaction
55
        auto out_end = begin;
14,544✔
56

57
        // Merge all connected boxes along MergeDim in O(N) by replacing each connected sequence with its bounding box
58
        while(begin != end) {
33,609✔
59
                const auto merged_min = begin->get_min();
19,065✔
60
                auto merged_max = begin->get_max();
19,067✔
61
                for(++begin; begin != end && begin->get_min()[MergeDim] <= merged_max[MergeDim]; ++begin) {
31,073✔
62
                        merged_max[MergeDim] = std::max(merged_max[MergeDim], begin->get_max()[MergeDim]);
12,008✔
63
                }
64
                *out_end++ = make_box<box_type::dimensions>(grid_detail::non_empty, merged_min, merged_max);
19,065✔
65
        }
66

67
        return out_end;
14,544✔
68
}
69

70
// In an arbitrary range of boxes, merge all boxes that are identical in all dimensions except MergeDim ("conditional directional merge").
71
template <int MergeDim, int EffectiveDims, typename BidirectionalIterator>
72
BidirectionalIterator merge_connected_boxes_along_dim(const BidirectionalIterator begin, const BidirectionalIterator end) {
58,761✔
73
        using box_type = typename std::iterator_traits<BidirectionalIterator>::value_type;
74
        static_assert(EffectiveDims <= box_type::dimensions);
75
        static_assert(MergeDim < EffectiveDims);
76

77
        constexpr auto orthogonal_to_merge_dim = [](const box_type& lhs, const box_type& rhs) {
149,042✔
78
                for(int d = 0; d < EffectiveDims; ++d) {
213,368✔
79
                        if(d == MergeDim) continue;
173,863✔
80
                        // arbitrary but consistent ordering along all orthogonal dimensions
81
                        if(lhs.get_min()[d] < rhs.get_min()[d]) return true;
118,546✔
82
                        if(lhs.get_min()[d] > rhs.get_min()[d]) return false;
90,370✔
83
                        if(lhs.get_max()[d] < rhs.get_max()[d]) return true;
62,880✔
84
                        if(lhs.get_max()[d] > rhs.get_max()[d]) return false;
59,473!
85
                }
86
                return false;
39,505✔
87
        };
88

89
        if constexpr(EffectiveDims == 1) {
90
                return merge_connected_intervals<MergeDim>(begin, end);
9,209✔
91
        } else {
92
                // partition [begin, end) into sequences of boxes that are potentially mergeable wrt/ the dimensions orthogonal to MergeDim.
93
                // 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.
94
                std::sort(begin, end, orthogonal_to_merge_dim);
49,552✔
95

96
                // 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
97
                // position in the iterator range; and then shift the merged range back to fill any gap left by merge of a previous partition.
98
                auto out_end = begin;
49,550✔
99

100
                for(auto equal_begin = begin; equal_begin != end;) {
95,535✔
101
                        // 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
102
                        const auto equal_end = std::find_if(std::next(equal_begin), end, [&](const box_type& box) {
79,708✔
103
                                return orthogonal_to_merge_dim(*equal_begin, box); // true if box is in a partition _after_ *equal_begin
33,717✔
104
                        });
105
                        const auto merged_end = merge_connected_intervals<MergeDim>(equal_begin, equal_end);
45,988✔
106
                        // shift the newly merged boxes to the left to close any gap opened by the merge of a previous partition
107
                        out_end = std::move(equal_begin, merged_end, out_end);
45,987✔
108
                        equal_begin = equal_end;
45,986✔
109
                }
110

111
                return out_end;
99,094✔
112
        }
113
}
114

115
// explicit instantiations for tests (might otherwise be inlined)
116
template box_vector<1>::iterator merge_connected_boxes_along_dim<0, 1>(box_vector<1>::iterator begin, box_vector<1>::iterator end);
117
template box_vector<2>::iterator merge_connected_boxes_along_dim<0, 2>(box_vector<2>::iterator begin, box_vector<2>::iterator end);
118
template box_vector<2>::iterator merge_connected_boxes_along_dim<1, 2>(box_vector<2>::iterator begin, box_vector<2>::iterator end);
119
template box_vector<3>::iterator merge_connected_boxes_along_dim<0, 3>(box_vector<3>::iterator begin, box_vector<3>::iterator end);
120
template box_vector<3>::iterator merge_connected_boxes_along_dim<1, 3>(box_vector<3>::iterator begin, box_vector<3>::iterator end);
121
template box_vector<3>::iterator merge_connected_boxes_along_dim<2, 3>(box_vector<3>::iterator begin, box_vector<3>::iterator end);
122

123
// 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
124
// ("fastest") dimension first to make sure the resulting boxes cover the largest possible extent of contiguous memory when are applied to buffers.
125
template <int MergeDim, int EffectiveDims, typename BidirectionalIterator>
126
BidirectionalIterator merge_connected_boxes_recurse(const BidirectionalIterator begin, BidirectionalIterator end) {
58,751✔
127
        static_assert(MergeDim >= 0 && MergeDim < EffectiveDims);
128
        end = merge_connected_boxes_along_dim<MergeDim, EffectiveDims>(begin, end);
58,751✔
129
        if constexpr(MergeDim > 0) { end = merge_connected_boxes_recurse<MergeDim - 1, EffectiveDims>(begin, end); }
25,760✔
130
        return end;
58,739✔
131
}
132

133
// Merge all adjacent boxes that are connected and identical in all except a single dimension.
134
template <int EffectiveDims, typename BidirectionalIterator>
135
BidirectionalIterator merge_connected_boxes(const BidirectionalIterator begin, BidirectionalIterator end) {
36,731✔
136
        using box_type = typename std::iterator_traits<BidirectionalIterator>::value_type;
137
        static_assert(EffectiveDims <= box_type::dimensions);
138
        if constexpr(EffectiveDims > 0) { end = merge_connected_boxes_recurse<EffectiveDims - 1, EffectiveDims>(begin, end); }
32,991✔
139
        return end;
36,727✔
140
}
141

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

148
        const auto& dim_cuts = cuts[static_cast<size_t>(dim)];
21,738✔
149
        assert(std::is_sorted(dim_cuts.begin(), dim_cuts.end()));
21,738✔
150

151
        // start of the first (current) dissected box
152
        size_t start = in_box.get_min()[dim];
21,737✔
153
        // find the first cut that lies inside the box (dim_cuts is sorted)
154
        auto cut_it = std::lower_bound(dim_cuts.begin(), dim_cuts.end(), /* not less or equal */ start + 1);
21,736✔
155

156
        for(;;) {
25,304✔
157
                // the end of the current box is either the last cut that lies inside the box, or the end of in_box
158
                size_t end;
159
                if(cut_it != dim_cuts.end() && *cut_it < in_box.get_max()[dim]) {
47,042!
160
                        end = *cut_it++;
3,559✔
161
                } else {
162
                        end = in_box.get_max()[dim];
43,478✔
163
                }
164
                if(end == start) break;
47,035✔
165

166
                // compute coordinates for the dissected box along `dim`, and recursively dissect it further along `dim + 1`
167
                auto min = in_box.get_min();
25,291✔
168
                auto max = in_box.get_max();
25,291✔
169
                min[dim] = start;
25,290✔
170
                max[dim] = end;
25,289✔
171
                const auto small_box = make_box<StorageDims>(grid_detail::non_empty, min, max);
25,289✔
172
                if(dim + 1 < static_cast<int>(cuts.size())) {
25,285✔
173
                        dissect_box(small_box, cuts, out_dissected, dim + 1);
7,631✔
174
                } else {
175
                        out_dissected.push_back(small_box);
17,654✔
176
                }
177

178
                start = end;
25,304✔
179
        }
180
}
21,744✔
181

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

186
// Apply dissect_box to all boxes in a range, with a shortcut if no cuts are to be done.
187
template <typename InputIterator>
188
void dissect_boxes(const InputIterator begin, const InputIterator end, const std::vector<std::vector<size_t>>& cuts,
14,756✔
189
    box_vector<std::iterator_traits<InputIterator>::value_type::dimensions>& out_dissected) {
190
        if(!cuts.empty()) {
14,756✔
191
                for(auto it = begin; it != end; ++it) {
25,169✔
192
                        dissect_box(*it, cuts, out_dissected, 0);
14,108✔
193
                }
194
        } else {
195
                out_dissected.insert(out_dissected.end(), begin, end);
3,697✔
196
        }
197
}
14,757✔
198

199
// Collect the sorted, unique list of box start- and end points along a single dimension. These can then be used in dissect_boxes.
200
template <typename InputIterator>
201
std::vector<size_t> collect_dissection_lines(const InputIterator begin, const InputIterator end, int dim) {
15,585✔
202
        std::vector<size_t> cuts;
15,585✔
203
        // 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)
204
        cuts.reserve(std::distance(begin, end) * 2);
15,585✔
205
        for(auto it = begin; it != end; ++it) {
35,718✔
206
                cuts.push_back(it->get_min()[dim]);
20,135✔
207
                cuts.push_back(it->get_max()[dim]);
20,134✔
208
        }
209
        std::sort(cuts.begin(), cuts.end());
15,585✔
210
        cuts.erase(std::unique(cuts.begin(), cuts.end()), cuts.end());
15,585✔
211
        assert(begin == end || cuts.size() >= 2);
15,580✔
212
        return cuts;
15,580✔
213
}
×
214

215
template <int EffectiveDims, int StorageDims>
216
void normalize_impl(box_vector<StorageDims>& boxes) {
10,702✔
217
        static_assert(EffectiveDims <= StorageDims);
218
        assert(!boxes.empty());
10,702✔
219

220
        if constexpr(EffectiveDims == 0) {
221
                // all 0d boxes are identical
222
                boxes.resize(1, box<StorageDims>());
402✔
223
        } else if constexpr(EffectiveDims == 1) {
224
                // merge_connected_boxes will sort and merge - this is already the complete 1d normalization
225
                boxes.erase(merge_connected_boxes<EffectiveDims>(boxes.begin(), boxes.end()), boxes.end());
2,025✔
226
                assert(!boxes.empty());
2,025✔
227
                assert(std::is_sorted(boxes.begin(), boxes.end(), box_coordinate_order()));
2,025✔
228
        } else {
229
                // 0. (hopefully) fast path: attempt to merge without dissecting first
230
                boxes.erase(merge_connected_boxes<EffectiveDims>(boxes.begin(), boxes.end()), boxes.end());
8,595✔
231
                assert(!boxes.empty());
8,594✔
232
                if(boxes.size() == 1) return;
8,594✔
233

234
                // 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
235
                std::vector<std::vector<size_t>> cuts(EffectiveDims - 1);
3,827✔
236
                for(int d = 0; d < EffectiveDims - 1; ++d) {
4,273✔
237
                        cuts[static_cast<size_t>(d)] = collect_dissection_lines(boxes.begin(), boxes.end(), d);
2,362✔
238
                }
239

240
                box_vector<StorageDims> dissected_boxes;
1,911✔
241
                dissect_boxes(boxes.begin(), boxes.end(), cuts, dissected_boxes);
1,912✔
242
                boxes = std::move(dissected_boxes);
1,914✔
243

244
                // 2. the dissected tiling of boxes only potentially overlaps in the fastest dimension - merge where possible
245
                boxes.erase(merge_connected_boxes<EffectiveDims>(boxes.begin(), boxes.end()), boxes.end());
1,914✔
246

247
                // 3. normalize box order
248
                std::sort(boxes.begin(), boxes.end(), box_coordinate_order());
1,913✔
249
        }
1,914✔
250
}
2,107✔
251

252
// Use together with a generic functor to dispatch the EffectiveDims template parameter at runtime
253
template <int StorageDims, typename F>
254
decltype(auto) dispatch_effective_dims(int effective_dims, F&& f) {
34,897✔
255
        assert(effective_dims <= StorageDims);
34,897✔
256

257
        // clang-format off
258
        switch(effective_dims) {
34,897!
259
        case 0: if constexpr(StorageDims >= 0) { return f(std::integral_constant<int, 0>()); } [[fallthrough]];
3,821✔
260
        case 1: if constexpr(StorageDims >= 1) { return f(std::integral_constant<int, 1>()); } [[fallthrough]];
9,207✔
261
        case 2: if constexpr(StorageDims >= 2) { return f(std::integral_constant<int, 2>()); } [[fallthrough]];
20,345✔
262
        case 3: if constexpr(StorageDims >= 3) { return f(std::integral_constant<int, 3>()); } [[fallthrough]];
1,524✔
263
        default: abort(); // unreachable with the explicit instantiations in this file
×
264
        }
265
        // clang-format on
266
}
267

268
// For any set of boxes, find the unique box tiling that covers the same points and is subject to the following constraints:
269
//   1. the extent of every box is maximized along the last dimension, then along the second-to-last dimension, and so forth.
270
//   2. no two boxes within the tiling intersect (i.e. cover a common point).
271
//   3. the tiling contains no empty boxes.
272
//   4. the normalized sequence is sorted according to box_coordinate_order.
273
// 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
274
// to an equality comparision of the covered point sets.
275
template <int Dims>
276
void normalize(box_vector<Dims>& boxes) {
159,876✔
277
        boxes.erase(std::remove_if(boxes.begin(), boxes.end(), std::mem_fn(&box<Dims>::empty)), boxes.end());
159,876✔
278
        if(boxes.size() <= 1) return;
159,606✔
279

280
        const auto effective_dims = get_effective_dims(boxes.begin(), boxes.end());
10,702✔
281
        assert(effective_dims <= Dims);
10,701✔
282

283
        dispatch_effective_dims<Dims>(effective_dims, [&](const auto effective_dims) { //
32,104✔
284
                normalize_impl<effective_dims.value>(boxes);
10,701✔
285
        });
286
}
287

288
// explicit instantiations for tests (might otherwise be inlined into region::region)
289
template void normalize(box_vector<0>& boxes);
290
template void normalize(box_vector<1>& boxes);
291
template void normalize(box_vector<2>& boxes);
292
template void normalize(box_vector<3>& boxes);
293

294
template <int EffectiveDims, int StorageDims>
295
region<StorageDims> region_intersection_impl(const region<StorageDims>& lhs, const region<StorageDims>& rhs) {
11,354✔
296
        static_assert(EffectiveDims <= StorageDims);
297

298
        // O(N * M) naively collect intersections of box pairs.
299
        // There might be a way to optimize this further through sorting one side and finding potentially intersecting boxes through lower_bound + upper_bound.
300
        // I have previously attempted to implement this entirely without box_intersection by dissecting both sides by the union of their dissection lines,
301
        // sorting both by box_coordinate_order and finding common boxes through std::set_intersection. Practically this turned out to be slower, sometimes
302
        // by several orders of magnitude, as the number of dissected boxes can grow to O((N * M) ^ EffectiveDims).
303
        box_vector<StorageDims> intersection;
11,354✔
304
        for(const auto& left : lhs.get_boxes()) {
25,585✔
305
                for(const auto& right : rhs.get_boxes()) {
28,474✔
306
                        if(const auto box = grid_detail::box_intersection<EffectiveDims>(left, right); !box.empty()) { intersection.push_back(box); }
14,244✔
307
                }
308
        }
309

310
        // No dissection step is necessary as the intersection of two normalized tilings is already "maximally mergeable".
311
        const auto begin = intersection.begin();
11,354✔
312
        auto end = intersection.end();
11,354✔
313
        end = grid_detail::merge_connected_boxes<EffectiveDims>(begin, end);
11,354✔
314

315
        // intersected_boxes retains the sorting from lhs, but for Dims > 1, the intersection can shift min-points such that the box_coordinate_order reverses.
316
        if constexpr(EffectiveDims > 1) {
317
                std::sort(begin, end, box_coordinate_order());
9,383✔
318
        } else {
319
                assert(std::is_sorted(begin, end, box_coordinate_order()));
1,971✔
320
        }
321

322
        intersection.erase(end, intersection.end());
11,354✔
323
        return grid_detail::make_region<StorageDims>(grid_detail::normalized, std::move(intersection));
22,707✔
324
}
11,354✔
325

326
// Complete the region_difference operation with an already dissected left-hand side and knowledge of effective dimensionality.
327
template <int EffectiveDims, int StorageDims>
328
void apply_region_difference(box_vector<StorageDims>& dissected_left, const region<StorageDims>& rhs) {
12,843✔
329
        static_assert(EffectiveDims <= StorageDims);
330

331
        // O(N * M) remove all dissected boxes from lhs that are fully covered by any box in rhs.
332
        // For further optimization potential see the comments on region_intersection_impl.
333
        const auto left_begin = dissected_left.begin();
12,843✔
334
        auto left_end = dissected_left.end();
12,844✔
335
        for(const auto& right : rhs.get_boxes()) {
25,834✔
336
                for(auto left_it = left_begin; left_it != left_end;) {
27,936✔
337
                        if(grid_detail::box_covers<EffectiveDims>(right, *left_it)) {
14,944✔
338
                                *left_it = *--left_end;
12,854✔
339
                        } else {
340
                                ++left_it;
2,090✔
341
                        }
342
                }
343
        }
344

345
        // merge the now non-overlapping boxes
346
        left_end = grid_detail::merge_connected_boxes<EffectiveDims>(left_begin, left_end);
12,843✔
347
        dissected_left.erase(left_end, dissected_left.end());
12,844✔
348
}
12,843✔
349

350
} // namespace celerity::detail::grid_detail
351

352
namespace celerity::detail {
353

354
template <int Dims>
355
region<Dims>::region(const box& single_box) : region(box_vector{single_box}) {} // still need to normalize in case single_box is empty
76,210✔
356

357
template <int Dims>
358
region<Dims>::region(const subrange<Dims>& single_sr) : region(box(single_sr)) {}
2,036✔
359

360
template <int Dims>
361
region<Dims>::region(box_vector&& boxes) : region(grid_detail::normalized, (/* in-place */ grid_detail::normalize(boxes), /* then */ std::move(boxes))) {}
159,861✔
362

363
template <int Dims>
364
region<Dims>::region(grid_detail::normalized_t /* tag */, box_vector&& boxes) : m_boxes(std::move(boxes)) {}
183,736✔
365

366
template class region<0>;
367
template class region<1>;
368
template class region<2>;
369
template class region<3>;
370

371
template <int Dims>
372
region<Dims> region_union(const region<Dims>& lhs, const region<Dims>& rhs) {
43,723✔
373
        // shortcut-evaluate trivial cases
374
        if(lhs.empty()) return rhs;
43,723✔
375
        if(rhs.empty()) return lhs;
11,615✔
376

377
        box_vector<Dims> box_union;
10,381✔
378
        box_union.reserve(lhs.get_boxes().size() + rhs.get_boxes().size());
10,380✔
379
        box_union.insert(box_union.end(), lhs.get_boxes().begin(), lhs.get_boxes().end());
10,380✔
380
        box_union.insert(box_union.end(), rhs.get_boxes().begin(), rhs.get_boxes().end());
10,381✔
381
        return region<Dims>(std::move(box_union));
10,380✔
382
}
10,381✔
383

384
template region<0> region_union(const region<0>& lhs, const region<0>& rhs);
385
template region<1> region_union(const region<1>& lhs, const region<1>& rhs);
386
template region<2> region_union(const region<2>& lhs, const region<2>& rhs);
387
template region<3> region_union(const region<3>& lhs, const region<3>& rhs);
388

389
template <int Dims>
390
region<Dims> region_intersection(const region<Dims>& lhs, const region<Dims>& rhs) {
34,873✔
391
        // shortcut-evaluate trivial cases
392
        if(lhs.empty() || rhs.empty()) return {};
34,873✔
393

394
        const auto effective_dims = std::max(lhs.get_effective_dims(), rhs.get_effective_dims());
11,353✔
395
        return grid_detail::dispatch_effective_dims<Dims>(effective_dims, [&](const auto effective_dims) { //
11,353✔
396
                return grid_detail::region_intersection_impl<effective_dims.value>(lhs, rhs);
11,353✔
397
        });
11,353✔
398
}
399

400
template region<0> region_intersection(const region<0>& lhs, const region<0>& rhs);
401
template region<1> region_intersection(const region<1>& lhs, const region<1>& rhs);
402
template region<2> region_intersection(const region<2>& lhs, const region<2>& rhs);
403
template region<3> region_intersection(const region<3>& lhs, const region<3>& rhs);
404

405
template <int Dims>
406
region<Dims> region_difference(const region<Dims>& lhs, const region<Dims>& rhs) {
17,749✔
407
        // shortcut-evaluate trivial cases
408
        if(lhs.empty()) return {};
17,749✔
409
        if(rhs.empty()) return lhs;
12,906✔
410

411
        // the resulting effective_dims can never be greater than the lhs dimension, but the difference operator must still operate on all available dimensions
412
        // to correctly identify overlapping boxes
413
        const auto effective_dims = std::max(lhs.get_effective_dims(), rhs.get_effective_dims());
12,845✔
414
        assert(effective_dims <= Dims);
12,844✔
415

416
        // 1. collect dissection lines (in *all* dimensions) from rhs
417
        std::vector<std::vector<size_t>> cuts(effective_dims);
25,688✔
418
        for(int d = 0; d < effective_dims; ++d) {
26,066✔
419
                cuts[static_cast<size_t>(d)] = grid_detail::collect_dissection_lines(rhs.get_boxes().begin(), rhs.get_boxes().end(), d);
13,222✔
420
        }
421

422
        // 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
423
        box_vector<Dims> dissected_left;
12,844✔
424
        grid_detail::dissect_boxes(lhs.get_boxes().begin(), lhs.get_boxes().end(), cuts, dissected_left);
12,844✔
425

426
        grid_detail::dispatch_effective_dims<Dims>(effective_dims, [&](const auto effective_dims) { //
38,529✔
427
                grid_detail::apply_region_difference<effective_dims.value>(dissected_left, rhs);
12,843✔
428
        });
429
        std::sort(dissected_left.begin(), dissected_left.end(), box_coordinate_order());
12,843✔
430

431
        return grid_detail::make_region<Dims>(grid_detail::normalized, std::move(dissected_left));
12,844✔
432
}
12,843✔
433

434
template region<0> region_difference(const region<0>& lhs, const region<0>& rhs);
435
template region<1> region_difference(const region<1>& lhs, const region<1>& rhs);
436
template region<2> region_difference(const region<2>& lhs, const region<2>& rhs);
437
template region<3> region_difference(const region<3>& lhs, const region<3>& rhs);
438

439
} // 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