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