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