KiCad PCB EDA Suite
Loading...
Searching...
No Matches
packed_rtree.h
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 3
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#ifndef PACKED_RTREE_H
21#define PACKED_RTREE_H
22
23#include <algorithm>
24#include <bit>
25#include <cassert>
26#include <climits>
27#include <cstdint>
28#include <cstring>
29#include <functional>
30#include <limits>
31#include <utility>
32#include <vector>
33
35
36namespace KIRTREE
37{
38
56template <class DATATYPE, class ELEMTYPE = int, int NUMDIMS = 2, int FANOUT = 16>
58{
59public:
60 PACKED_RTREE() = default;
61
62 // Move-only
63 PACKED_RTREE( PACKED_RTREE&& aOther ) noexcept :
64 m_counts( std::move( aOther.m_counts ) ),
65 m_data( std::move( aOther.m_data ) ),
66 m_levelOffsets( std::move( aOther.m_levelOffsets ) ),
67 m_size( std::exchange( aOther.m_size, size_t{ 0 } ) )
68 {
69 for( int i = 0; i < NUMDIMS * 2; ++i )
70 m_bounds[i] = std::move( aOther.m_bounds[i] );
71 }
72
73 PACKED_RTREE& operator=( PACKED_RTREE&& aOther ) noexcept
74 {
75 if( this != &aOther )
76 {
77 for( int i = 0; i < NUMDIMS * 2; ++i )
78 m_bounds[i] = std::move( aOther.m_bounds[i] );
79
80 m_counts = std::move( aOther.m_counts );
81 m_data = std::move( aOther.m_data );
82 m_levelOffsets = std::move( aOther.m_levelOffsets );
83 m_size = aOther.m_size;
84 aOther.m_size = 0;
85 }
86
87 return *this;
88 }
89
90 PACKED_RTREE( const PACKED_RTREE& ) = delete;
91 PACKED_RTREE& operator=( const PACKED_RTREE& ) = delete;
92
101 template <class VISITOR>
102 int Search( const ELEMTYPE aMin[NUMDIMS], const ELEMTYPE aMax[NUMDIMS],
103 VISITOR& aVisitor ) const
104 {
105 if( m_size == 0 )
106 return 0;
107
108 int found = 0;
109 int rootLevel = static_cast<int>( m_levelOffsets.size() ) - 1;
110 searchNode( rootLevel, m_levelOffsets[rootLevel], aMin, aMax, aVisitor, found );
111 return found;
112 }
113
118 {
119 public:
120 Iterator( const DATATYPE* aPtr ) : m_ptr( aPtr ) {}
121
122 const DATATYPE& operator*() const { return *m_ptr; }
123 const DATATYPE* operator->() const { return m_ptr; }
124
126 {
127 ++m_ptr;
128 return *this;
129 }
130
132 {
133 Iterator tmp = *this;
134 ++m_ptr;
135 return tmp;
136 }
137
138 bool operator==( const Iterator& aOther ) const { return m_ptr == aOther.m_ptr; }
139 bool operator!=( const Iterator& aOther ) const { return m_ptr != aOther.m_ptr; }
140
141 private:
142 const DATATYPE* m_ptr;
143 };
144
145 Iterator begin() const { return Iterator( m_data.data() ); }
146 Iterator end() const { return Iterator( m_data.data() + m_size ); }
147
148 size_t size() const { return m_size; }
149 bool empty() const { return m_size == 0; }
150
154 size_t MemoryUsage() const
155 {
156 size_t usage = 0;
157
158 for( int axis = 0; axis < NUMDIMS * 2; ++axis )
159 usage += m_bounds[axis].capacity() * sizeof( ELEMTYPE );
160
161 usage += m_counts.capacity() * sizeof( int );
162 usage += m_data.capacity() * sizeof( DATATYPE );
163 usage += m_levelOffsets.capacity() * sizeof( size_t );
164 return usage;
165 }
166
177 {
178 public:
179 Builder() = default;
180
181 void Reserve( size_t aCount )
182 {
183 m_items.reserve( aCount );
184 }
185
186 void Add( const ELEMTYPE aMin[NUMDIMS], const ELEMTYPE aMax[NUMDIMS],
187 const DATATYPE& aData )
188 {
189 ITEM item;
190 item.data = aData;
191
192 for( int d = 0; d < NUMDIMS; ++d )
193 {
194 item.min[d] = aMin[d];
195 item.max[d] = aMax[d];
196 }
197
198 m_items.push_back( item );
199 }
200
202 {
203 PACKED_RTREE tree;
204 size_t n = m_items.size();
205 tree.m_size = n;
206
207 if( n == 0 )
208 return tree;
209
210 // Compute global bounding box of all item centers for Hilbert normalization
211 ELEMTYPE globalMin[NUMDIMS];
212 ELEMTYPE globalMax[NUMDIMS];
213
214 for( int d = 0; d < NUMDIMS; ++d )
215 {
216 globalMin[d] = std::numeric_limits<ELEMTYPE>::max();
217 globalMax[d] = std::numeric_limits<ELEMTYPE>::lowest();
218 }
219
220 for( const ITEM& item : m_items )
221 {
222 for( int d = 0; d < NUMDIMS; ++d )
223 {
224 ELEMTYPE center = item.min[d] / 2 + item.max[d] / 2;
225
226 if( center < globalMin[d] )
227 globalMin[d] = center;
228
229 if( center > globalMax[d] )
230 globalMax[d] = center;
231 }
232 }
233
234 // Compute Hilbert indices for sorting
235 std::vector<uint64_t> hilbertValues( n );
236
237 for( size_t i = 0; i < n; ++i )
238 {
239 uint32_t coords[NUMDIMS];
240
241 for( int d = 0; d < NUMDIMS; ++d )
242 {
243 ELEMTYPE center = m_items[i].min[d] / 2 + m_items[i].max[d] / 2;
244 double range = static_cast<double>( globalMax[d] ) - globalMin[d];
245
246 if( range > 0.0 )
247 {
248 double normalized = ( static_cast<double>( center ) - globalMin[d] )
249 / range;
250 coords[d] = static_cast<uint32_t>(
251 normalized * static_cast<double>( UINT32_MAX ) );
252 }
253 else
254 {
255 coords[d] = 0;
256 }
257 }
258
259 hilbertValues[i] = HilbertND2D<NUMDIMS>( 32, coords );
260 }
261
262 // Sort items by Hilbert value
263 std::vector<size_t> indices( n );
264
265 for( size_t i = 0; i < n; ++i )
266 indices[i] = i;
267
268 std::sort( indices.begin(), indices.end(),
269 [&hilbertValues]( size_t a, size_t b )
270 {
271 return hilbertValues[a] < hilbertValues[b];
272 } );
273
274 // Store sorted data
275 tree.m_data.resize( n );
276
277 for( size_t i = 0; i < n; ++i )
278 tree.m_data[i] = m_items[indices[i]].data;
279
280 // Build leaf nodes: group every FANOUT items
281 size_t numLeafNodes = ( n + FANOUT - 1 ) / FANOUT;
282
283 // We'll build the tree bottom-up, tracking nodes per level
284 std::vector<size_t> nodesPerLevel;
285 nodesPerLevel.push_back( numLeafNodes );
286
287 size_t levelNodes = numLeafNodes;
288
289 while( levelNodes > 1 )
290 {
291 levelNodes = ( levelNodes + FANOUT - 1 ) / FANOUT;
292 nodesPerLevel.push_back( levelNodes );
293 }
294
295 // If we had exactly one item, we have one leaf node and that's the root
296 size_t totalNodes = 0;
297
298 for( size_t cnt : nodesPerLevel )
299 totalNodes += cnt;
300
301 // Allocate SoA bounds storage and counts
302 for( int axis = 0; axis < NUMDIMS * 2; ++axis )
303 tree.m_bounds[axis].resize( totalNodes * FANOUT,
304 std::numeric_limits<ELEMTYPE>::max() );
305
306 tree.m_counts.resize( totalNodes, 0 );
307
308 // Compute level offsets (leaf level = 0, root = highest)
309 tree.m_levelOffsets.resize( nodesPerLevel.size() );
310 size_t offset = 0;
311
312 for( size_t lev = 0; lev < nodesPerLevel.size(); ++lev )
313 {
314 tree.m_levelOffsets[lev] = offset;
315 offset += nodesPerLevel[lev];
316 }
317
318 // Fill leaf nodes with item bounding boxes
319 for( size_t i = 0; i < n; ++i )
320 {
321 size_t nodeIdx = i / FANOUT;
322 int slot = static_cast<int>( i % FANOUT );
323 size_t globalNodeIdx = tree.m_levelOffsets[0] + nodeIdx;
324
325 const ITEM& item = m_items[indices[i]];
326
327 for( int d = 0; d < NUMDIMS; ++d )
328 {
329 tree.m_bounds[d * 2][globalNodeIdx * FANOUT + slot] = item.min[d];
330 tree.m_bounds[d * 2 + 1][globalNodeIdx * FANOUT + slot] = item.max[d];
331 }
332
333 tree.m_counts[globalNodeIdx] = slot + 1;
334 }
335
336 // Build internal levels bottom-up
337 for( size_t lev = 1; lev < nodesPerLevel.size(); ++lev )
338 {
339 size_t childLevelOffset = tree.m_levelOffsets[lev - 1];
340 size_t childLevelCount = nodesPerLevel[lev - 1];
341 size_t parentLevelOffset = tree.m_levelOffsets[lev];
342
343 for( size_t ci = 0; ci < childLevelCount; ++ci )
344 {
345 size_t parentIdx = ci / FANOUT;
346 int slot = static_cast<int>( ci % FANOUT );
347 size_t globalParentIdx = parentLevelOffset + parentIdx;
348 size_t globalChildIdx = childLevelOffset + ci;
349
350 // Compute the bounding box of the child node (union of all its slots)
351 ELEMTYPE childMin[NUMDIMS];
352 ELEMTYPE childMax[NUMDIMS];
353
354 for( int d = 0; d < NUMDIMS; ++d )
355 {
356 childMin[d] = std::numeric_limits<ELEMTYPE>::max();
357 childMax[d] = std::numeric_limits<ELEMTYPE>::lowest();
358 }
359
360 int childCount = tree.m_counts[globalChildIdx];
361
362 for( int s = 0; s < childCount; ++s )
363 {
364 for( int d = 0; d < NUMDIMS; ++d )
365 {
366 ELEMTYPE mn = tree.m_bounds[d * 2][globalChildIdx * FANOUT + s];
367 ELEMTYPE mx = tree.m_bounds[d * 2 + 1][globalChildIdx * FANOUT + s];
368
369 if( mn < childMin[d] )
370 childMin[d] = mn;
371
372 if( mx > childMax[d] )
373 childMax[d] = mx;
374 }
375 }
376
377 // Store this child node's bbox in the parent
378 for( int d = 0; d < NUMDIMS; ++d )
379 {
380 tree.m_bounds[d * 2][globalParentIdx * FANOUT + slot] = childMin[d];
381 tree.m_bounds[d * 2 + 1][globalParentIdx * FANOUT + slot] = childMax[d];
382 }
383
384 tree.m_counts[globalParentIdx] = slot + 1;
385 }
386 }
387
388 m_items.clear();
389 return tree;
390 }
391
392 private:
393 struct ITEM
394 {
395 DATATYPE data;
396 ELEMTYPE min[NUMDIMS];
397 ELEMTYPE max[NUMDIMS];
398 };
399
400 std::vector<ITEM> m_items;
401 };
402
403private:
404 // Returns true if search should continue, false if visitor terminated early
405 template <class VISITOR>
406 bool searchNode( int aLevel, size_t aNodeIdx, const ELEMTYPE aMin[NUMDIMS],
407 const ELEMTYPE aMax[NUMDIMS], VISITOR& aVisitor, int& aFound ) const
408 {
409 if constexpr( NUMDIMS == 2 )
410 {
411 // Test all FANOUT children at once via SIMD overlap mask.
412 // Each bounds array is contiguous for FANOUT slots at [nodeIdx * FANOUT].
413 size_t base = aNodeIdx * FANOUT;
414 uint32_t mask = OverlapMask2D<ELEMTYPE, FANOUT>(
415 &m_bounds[0][base], &m_bounds[1][base],
416 &m_bounds[2][base], &m_bounds[3][base],
417 m_counts[aNodeIdx], aMin, aMax );
418
419 if( aLevel == 0 )
420 {
421 size_t dataBase = ( aNodeIdx - m_levelOffsets[0] ) * FANOUT;
422
423 // Iterate set bits to visit only overlapping leaf children
424 while( mask )
425 {
426 int i = std::countr_zero( mask );
427 mask &= mask - 1;
428
429 size_t dataIdx = dataBase + i;
430
431 if( dataIdx < m_size )
432 {
433 aFound++;
434
435 if( !aVisitor( m_data[dataIdx] ) )
436 return false;
437 }
438 }
439 }
440 else
441 {
442 size_t childLevelOffset = m_levelOffsets[aLevel - 1];
443 size_t nodeOffset = aNodeIdx - m_levelOffsets[aLevel];
444
445 // Iterate set bits to recurse only into overlapping internal children
446 while( mask )
447 {
448 int i = std::countr_zero( mask );
449 mask &= mask - 1;
450
451 size_t childGlobalIdx = childLevelOffset + nodeOffset * FANOUT + i;
452
453 if( !searchNode( aLevel - 1, childGlobalIdx, aMin, aMax, aVisitor,
454 aFound ) )
455 {
456 return false;
457 }
458 }
459 }
460 }
461 else
462 {
463 // Generic N-dimensional fallback: per-child scalar overlap test
464 int nodeCount = m_counts[aNodeIdx];
465
466 if( aLevel == 0 )
467 {
468 for( int i = 0; i < nodeCount; ++i )
469 {
470 if( childOverlaps( aNodeIdx, i, aMin, aMax ) )
471 {
472 size_t dataIdx = ( aNodeIdx - m_levelOffsets[0] ) * FANOUT + i;
473
474 if( dataIdx < m_size )
475 {
476 aFound++;
477
478 if( !aVisitor( m_data[dataIdx] ) )
479 return false;
480 }
481 }
482 }
483 }
484 else
485 {
486 size_t childLevelOffset = m_levelOffsets[aLevel - 1];
487
488 for( int i = 0; i < nodeCount; ++i )
489 {
490 if( childOverlaps( aNodeIdx, i, aMin, aMax ) )
491 {
492 size_t childBaseIdx =
493 ( aNodeIdx - m_levelOffsets[aLevel] ) * FANOUT + i;
494 size_t childGlobalIdx = childLevelOffset + childBaseIdx;
495
496 if( !searchNode( aLevel - 1, childGlobalIdx, aMin, aMax, aVisitor,
497 aFound ) )
498 {
499 return false;
500 }
501 }
502 }
503 }
504 }
505
506 return true;
507 }
508
509 bool childOverlaps( size_t aNodeIdx, int aSlot, const ELEMTYPE aMin[NUMDIMS],
510 const ELEMTYPE aMax[NUMDIMS] ) const
511 {
512 size_t idx = aNodeIdx * FANOUT + aSlot;
513
514 for( int d = 0; d < NUMDIMS; ++d )
515 {
516 if( m_bounds[d * 2][idx] > aMax[d] || m_bounds[d * 2 + 1][idx] < aMin[d] )
517 return false;
518 }
519
520 return true;
521 }
522
523 // SoA bounding boxes: m_bounds[axis][globalNodeIdx * FANOUT + slot]
524 std::vector<ELEMTYPE> m_bounds[NUMDIMS * 2];
525
526 // Number of valid children per node
527 std::vector<int> m_counts;
528
529 // Sorted data items (Hilbert-ordered)
530 std::vector<DATATYPE> m_data;
531
532 // Node index where each level starts (level 0 = leaves, last = root)
533 std::vector<size_t> m_levelOffsets;
534
535 // Number of data items
536 size_t m_size = 0;
537};
538
539} // namespace KIRTREE
540
541#endif // PACKED_RTREE_H
std::vector< ITEM > m_items
void Add(const ELEMTYPE aMin[NUMDIMS], const ELEMTYPE aMax[NUMDIMS], const DATATYPE &aData)
void Reserve(size_t aCount)
bool operator!=(const Iterator &aOther) const
const DATATYPE & operator*() const
const DATATYPE * operator->() const
Iterator(const DATATYPE *aPtr)
bool operator==(const Iterator &aOther) const
Iterator end() const
bool childOverlaps(size_t aNodeIdx, int aSlot, const ELEMTYPE aMin[NUMDIMS], const ELEMTYPE aMax[NUMDIMS]) const
size_t size() const
PACKED_RTREE(const PACKED_RTREE &)=delete
bool searchNode(int aLevel, size_t aNodeIdx, const ELEMTYPE aMin[NUMDIMS], const ELEMTYPE aMax[NUMDIMS], VISITOR &aVisitor, int &aFound) const
int Search(const ELEMTYPE aMin[NUMDIMS], const ELEMTYPE aMax[NUMDIMS], VISITOR &aVisitor) const
Search for all items whose bounding boxes overlap the query rectangle.
PACKED_RTREE(PACKED_RTREE &&aOther) noexcept
Iterator begin() const
std::vector< size_t > m_levelOffsets
size_t MemoryUsage() const
Return approximate memory usage in bytes.
PACKED_RTREE & operator=(PACKED_RTREE &&aOther) noexcept
PACKED_RTREE & operator=(const PACKED_RTREE &)=delete
std::vector< DATATYPE > m_data
std::vector< int > m_counts
uint64_t HilbertND2D(int aOrder, const uint32_t aCoords[NUMDIMS])
Compute Hilbert index for N-dimensional coordinates.
Definition rtree_node.h:97
uint32_t OverlapMask2D(const ELEMTYPE *aBoundsMin0, const ELEMTYPE *aBoundsMax0, const ELEMTYPE *aBoundsMin1, const ELEMTYPE *aBoundsMax1, int aCount, const ELEMTYPE aMin[2], const ELEMTYPE aMax[2])
Compute a bitmask of which child slots overlap a 2D query rectangle.
Definition rtree_node.h:144
VECTOR2I center