Library of Assembled Shared Sources
kd_tree.inl
Go to the documentation of this file.
1/** @file
2 * @author Bram de Greve (bram@cocamware.com)
3 * @author Tom De Muer (tom@cocamware.com)
4 *
5 * *** BEGIN LICENSE INFORMATION ***
6 *
7 * The contents of this file are subject to the Common Public Attribution License
8 * Version 1.0 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://lass.sourceforge.net/cpal-license. The License is based on the
11 * Mozilla Public License Version 1.1 but Sections 14 and 15 have been added to cover
12 * use of software over a computer network and provide for limited attribution for
13 * the Original Developer. In addition, Exhibit A has been modified to be consistent
14 * with Exhibit B.
15 *
16 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
17 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
18 * language governing rights and limitations under the License.
19 *
20 * The Original Code is LASS - Library of Assembled Shared Sources.
21 *
22 * The Initial Developer of the Original Code is Bram de Greve and Tom De Muer.
23 * The Original Developer is the Initial Developer.
24 *
25 * All portions of the code written by the Initial Developer are:
26 * Copyright (C) 2004-2024 the Initial Developer.
27 * All Rights Reserved.
28 *
29 * Contributor(s):
30 *
31 * Alternatively, the contents of this file may be used under the terms of the
32 * GNU General Public License Version 2 or later (the GPL), in which case the
33 * provisions of GPL are applicable instead of those above. If you wish to allow use
34 * of your version of this file only under the terms of the GPL and not to allow
35 * others to use your version of this file under the CPAL, indicate your decision by
36 * deleting the provisions above and replace them with the notice and other
37 * provisions required by the GPL License. If you do not delete the provisions above,
38 * a recipient may use your version of this file under either the CPAL or the GPL.
39 *
40 * *** END LICENSE INFORMATION ***
41 */
42
43
44
45#ifndef LASS_GUARDIAN_OF_INCLUSION_SPAT_KD_TREE_INL
46#define LASS_GUARDIAN_OF_INCLUSION_SPAT_KD_TREE_INL
47
48#include "spat_common.h"
49#include "kd_tree.h"
50#include "../num/basic_ops.h"
51
52#ifdef LASS_SPAT_KD_TREE_DIAGNOSTICS
53# include "../io/xml_o_file.h"
54# include "../meta/select.h"
55# include "../prim/aabb_2d.h"
56# include "../prim/aabb_3d.h"
57#endif
58
59namespace lass
60{
61namespace spat
62{
63
64// --- public --------------------------------------------------------------------------------------
65
66/** Constructs an empty k-d tree
67 */
68template <class O, class OT>
70 end_(new TObjectIterator)
71{
72}
73
74
75
76/** Constructs a k-d tree from objects in range [first, last).
77 * @warning [first, last) must stay a valid range during the entire lifespan of the k-d tree!
78 */
79template <class O, class OT>
80KdTree<O, OT>::KdTree(TObjectIterator first, TObjectIterator last):
81 end_(new TObjectIterator(last))
82{
83 const size_t n = static_cast<size_t>(std::distance(first, last));
84 heap_.resize((n * 3) / 2, Node(last));
85
86 TObjectIterators input;
87 input.reserve(n);
88 for (TObjectIterator i = first; i != last; ++i)
89 {
90 input.push_back(i);
91 }
92
93 balance(0, input.begin(), input.end());
94}
95
96
97template <class O, class OT>
98KdTree<O, OT>::KdTree(TSelf&& other) noexcept:
99 heap_(std::move(other.heap_)),
100 end_(std::move(other.end_))
101{
102}
103
104
105template <class O, class OT>
106KdTree<O, OT>& KdTree<O, OT>::operator=(TSelf&& other) noexcept
107{
108 heap_ = std::move(other.heap_);
109 end_ = std::move(other.end_);
110 return *this;
111}
112
113
114/** Resets to an empty tree.
115 */
116template <class O, class OT>
118{
119 TSelf temp;
120 swap(temp);
121}
122
123
124
125/** Resets to a new k-d tree of objects in range [first, last).
126 * @warning [first, last) must stay a valid range during the entire lifespan of the k-d tree!
127 */
128template <class O, class OT>
129void KdTree<O, OT>::reset(TObjectIterator first, TObjectIterator last)
130{
131 TSelf temp(first, last);
132 swap(temp);
134
137/** Locates the object that's nearest to a target position
138 */
139template <class O, class OT>
140typename KdTree<O, OT>::Neighbour
141KdTree<O, OT>::nearestNeighbour(const TPoint& target) const
142{
143 return nearestNeighbour(target, std::numeric_limits<TValue>::infinity());
144}
145
146
148/** Locates the object that's nearest to a target position, within a maximum range
149 */
150template <class O, class OT>
151typename KdTree<O, OT>::Neighbour
152KdTree<O, OT>::nearestNeighbour(const TPoint& target, TParam maxRadius) const
153{
154 if (isEmpty())
155 {
156 LASS_THROW("can't locate nearest neighbour in empty KdTree");
157 }
158
159 Neighbour best(*end_, maxRadius);
160#if 1
161 struct Visit
162 {
163 size_t index;
164 TValue sqrDelta;
165 };
166 Visit stack[32];
167 size_t stackSize = 0;
168 stack[stackSize].index = 0;
169 stack[stackSize++].sqrDelta = -1;
170
171 while (stackSize > 0)
172 {
173 const Visit visit = stack[--stackSize];
174 if (visit.sqrDelta > best.squaredDistance())
175 {
176 continue;
177 }
178 if (visit.index >= heap_.size() || heap_[visit.index].object() == *end_)
179 {
180 continue;
181 }
182 const Node& node = heap_[visit.index];
183 const TPoint& pivot = node.position();
184
185 const TValue sqrDistance = squaredDistance(pivot, target);
186 if (sqrDistance < best.squaredDistance())
187 {
188 best = Neighbour(node.object(), sqrDistance);
189 }
190
191 const TAxis split = node.axis();
192 if (split == dummyAxis_)
193 {
194 continue;
195 }
196
197 const TValue delta = target[split] - pivot[split]; // distance to splitting plane
198 if (delta < 0)
199 {
200 // we are left of the plane - search left node first
201 stack[stackSize].index = 2 * visit.index + 2;
202 stack[stackSize++].sqrDelta = num::sqr(delta);
203 stack[stackSize].index = 2 * visit.index + 1;
204 stack[stackSize++].sqrDelta = 0;
205 }
206 else
207 {
208 // we are right of the plane - search right node first
209 stack[stackSize].index = 2 * visit.index + 1;
210 stack[stackSize++].sqrDelta = num::sqr(delta);
211 stack[stackSize].index = 2 * visit.index + 2;
212 stack[stackSize++].sqrDelta = 0;
213 }
214 }
215#else
216 doNearestNeighbour(0, target, best);
217#endif
218 return best;
219}
220
221
222
223/** Locates objects within a spherical range around a target position.
224 *
225 * @deprecated USE OVERLOADS WITH ITERATORS INSTEAD
226 *
227 * @param target [in] the center of the spherical range
228 * @param maxRadius [in] the radius of the range
229 * @param maxCount [in] the maximum number of objects to be returned.
230 * @arg If this is zero, then all objects in the range are returned.
231 * @arg If this is non-zero, then up to @a maxCount objects are returned.
232 * These will be the ones closest to @a target
233 * @param neighbourhood [out] a std::vector that will be filled with the found objects.
234 * The vector will be @b cleared before use.
235 * @return the squared distance between @a target and the furthest found object.
236 */
237template <class O, class OT>
238typename KdTree<O, OT>::TValue
240 const TPoint& target, TParam maxRadius, size_t maxCount,
241 TNeighbourhood& neighbourhood) const
242{
243 if (isEmpty())
244 {
245 LASS_THROW("can't perform range search in empty KdTree");
246 }
247
248 if (maxCount == 0)
249 {
250 neighbourhood.clear();
251 rangeSearch(target, maxRadius, std::back_inserter(neighbourhood));
252
253 // neighbourhood is not a heap, find maximum squared distance
254 TValue maxSquaredDistance = TValue();
255 const typename TNeighbourhood::const_iterator end = neighbourhood.end();
256 for (typename TNeighbourhood::const_iterator i = neighbourhood.begin(); i != end; ++i)
257 {
258 maxSquaredDistance = std::max(maxSquaredDistance, i->squaredDistance());
259 }
260 return maxSquaredDistance;
261 }
262
263 maxCount = std::min(maxCount, heap_.size());
264 neighbourhood.resize(maxCount + 1);
265
266 typename TNeighbourhood::iterator last = rangeSearch(
267 target, maxRadius, maxCount, neighbourhood.begin());
268 neighbourhood.erase(last, neighbourhood.end());
269
270 if (neighbourhood.empty())
271 {
272 return TValue();
273 }
274 return neighbourhood.front().squaredDistance();
275}
276
277
278
279/** Find all objects in a radius of @a maxRadius of @a target.
280 * @param target [in] center of range.
281 * @param maxRadius [in] radius of range
282 * @param first [in] output iterator dereferencable to Neighbour.
283 * @return output iterator @e last so that [@a first, last) is the range of all found objects.
284 *
285 * @note
286 * The range starting at @a first must be large enough to contain all found objects.
287 * But since this number is probably unknown beforehand, you better use one of those
288 * inserter kind of iterators to add the results to a dynamic sized container.
289 *
290 * @note
291 * If you wish to use a fixed sized range, you best use the other rangeSearch overload
292 * taking a random access iterator and an extra parameter @a maxCount.
293 */
294template <class O, class OT>
295template <typename OutputIterator>
296OutputIterator
297KdTree<O, OT>::rangeSearch(const TPoint& target, TParam maxRadius, OutputIterator first) const
298{
299 if (isEmpty() || maxRadius == 0)
300 {
301 return first;
302 }
303 const TValue squaredRadius = maxRadius * maxRadius;
304 return doRangeSearch(0, target, squaredRadius, first);
305}
306
307
308
309/** Find up to a fixed number of objects in a radius of @a maxRadius of @a target.
310 * @param target [in] center of range.
311 * @param maxRadius [in] radius of range
312 * @param maxCount [in] maximum number of objects to be found.
313 * @param first [in] random access iterator dereferencable to Neighbour,
314 * [@a first, @a first + @a maxCount + 1) must be a valid range.
315 * @return output iterator @e last so that [@a first, last) is the range of all found objects.
316 *
317 * @note
318 * This overload will search for a maximum number of @a maxCount objects at a maximum
319 * distance of @a maxRadius of the center @a target. When more than @a maxCount objects
320 * are within this distance, the closest objects will be selected.
321 * To select the closest objects, a heap is constructed on the iterator range, which is
322 * why random access iterators are required instead of regular output iterators. This
323 * is also why there's need of an extra position in the range pointed to by @a first:
324 * there's need of an extra position to swap in/out new/old objects. That's why you
325 * must make sure [@a first, @a first + @a maxCount + 1) is a valid range.
326 *
327 * @note
328 * If you wish to find all points within the range of @a maxRadius, you better use the
329 * overload with the regular output iterator and without @a maxCount.
330 */
331template <class O, class OT>
332template <typename RandomAccessIterator>
333RandomAccessIterator
334KdTree<O, OT>::rangeSearch(const TPoint& target, TParam maxRadius, size_t maxCount,
335 RandomAccessIterator first) const
336{
337 if (isEmpty() || maxRadius == 0)
338 {
339 return first;
340 }
341 TValue squaredRadius = maxRadius * maxRadius;
342 return doRangeSearch(0, target, squaredRadius, maxCount, first, first);
343}
344
345
346
347/** Swap the representation of two k-d trees.
348 */
349template <class O, class OT>
350void KdTree<O, OT>::swap(TSelf& other)
351{
352 heap_.swap(other.heap_);
353 end_.swap(other.end_);
354}
355
356
357
358/** returns true if there are no objects in the k-d tree
359 */
360template <class O, class OT>
362{
363 return heap_.empty();
364}
365
366
367
368/** resest the k-d tree to an empty one.
369 */
370template <class O, class OT>
372{
373 TSelf temp;
374 swap(temp);
375}
376
377
378
379template <class O, class OT> inline
380const typename KdTree<O, OT>::TObjectIterator
381KdTree<O, OT>::end() const
382{
383 return *end_;
384}
385
386
387
388// --- neighbour -----------------------------------------------------------------------------------
389
390template <class O, class OT>
391KdTree<O, OT>::Neighbour::Neighbour():
392 object_(),
393 squaredDistance_(0)
394{
395}
396
397
398
399template <class O, class OT>
400KdTree<O, OT>::Neighbour::Neighbour(TObjectIterator object, TValue squaredDistance):
401 object_(object),
402 squaredDistance_(squaredDistance)
403{
404}
405
406
407
408template <class O, class OT>
409inline typename KdTree<O, OT>::TObjectIterator
410KdTree<O, OT>::Neighbour::object() const
411{
412 return object_;
413}
414
415
416
417template <class O, class OT>
418inline typename KdTree<O, OT>::TPoint
420{
421 return TObjectTraits::position(object_);
422}
423
424
425
426template <class O, class OT>
427inline typename KdTree<O, OT>::TValue
429{
430 return squaredDistance_;
431}
432
433
434
435template <class O, class OT>
438{
439 return *object_;
440}
441
442
443
444template <class O, class OT>
445inline typename KdTree<O, OT>::TObjectIterator
447{
448 return object_;
449}
450
451
452
453template <class O, class OT>
454inline bool
455KdTree<O, OT>::Neighbour::operator<(const Neighbour& other) const
456{
457 return squaredDistance_ < other.squaredDistance_;
458}
459
460
461
462// --- protected -----------------------------------------------------------------------------------
463
464
465
466// --- private -------------------------------------------------------------------------------------
467
468template <class O, class OT>
469void KdTree<O, OT>::balance(size_t index, TIteratorIterator first, TIteratorIterator last)
470{
471 if (last == first)
472 {
473 return;
474 }
475
476 if (last == first + 1)
477 {
478 // exactly one element in content
479 assignNode(index, *first, dummyAxis_);
480 return;
481 }
482
483 // more than one, do the balance
484 //
485 const TAxis split = findSplitAxis(first, last);
486 const size_t size = static_cast<size_t>(last - first);
487 const TIteratorIterator median = first + size / 2;
488 std::nth_element(first, median, last, LessDim(split));
489 assignNode(index, *median, split);
490
491 balance(2 * index + 1, first, median);
492 balance(2 * index + 2, median + 1, last);
493}
494
495
496
497template <class O, class OT>
499KdTree<O, OT>::findSplitAxis(TIteratorIterator first, TIteratorIterator last) const
500{
501 TPoint min = TObjectTraits::position(*first);
502 TPoint max = min;
503
504 for (TIteratorIterator i = first + 1; i != last; ++i)
505 {
506 const TPoint position = TObjectTraits::position(*i);
507 for (TAxis k = 0; k < dimension; ++k)
508 {
509 min[k] = std::min(min[k], position[k]);
510 max[k] = std::max(max[k], position[k]);
511 }
512 }
513
514 TAxis axis = 0;
515 TValue maxDistance = max[0] - min[0];
516 for (TAxis k = 1; k < dimension; ++k)
517 {
518 const TValue distance = max[k] - min[k];
519 if (distance > maxDistance)
520 {
521 axis = k;
522 maxDistance = distance;
523 }
524 }
525
526 return axis;
527}
528
529
530
531template <class O, class OT>
532inline void KdTree<O, OT>::assignNode(size_t index, TObjectIterator object, TAxis splitAxis)
533{
534 if (heap_.size() <= index)
535 {
536 heap_.resize(index + 1, Node(*end_));
537 }
538 heap_[index] = Node(object, TObjectTraits::position(object), splitAxis);
539}
540
541
542
543template <class O, class OT>
544size_t KdTree<O, OT>::findNode(size_t index, const TPoint& target) const
545{
546 const size_t size = heap_.size();
547 if (index >= size || heap_[index].object() == *end_)
548 {
549 return size;
550 }
551 const Node& node = heap_[index];
552
553 const TAxis split = node.axis();
554 if (split == dummyAxis_)
555 {
556 return index;
557 }
558
559 const TPoint& pivot = node.position();
560 const TValue delta = target[split] - pivot[split]; // distance to splitting plane
561 const bool isLeftSide = delta < 0;
562 const size_t result = findNode(2 * index + (isLeftSide ? 1 : 2), target);
563 return result != size ? result : index;
564}
565
566
567
568template <class O, class OT>
569void KdTree<O, OT>::doNearestNeighbour(size_t index, const TPoint& target, Neighbour& best) const
570{
571 if (index >= heap_.size() || heap_[index].object() == *end_)
572 {
573 return;
574 }
575 const Node& node = heap_[index];
576 const TPoint& pivot = node.position();
577
578 const TValue sqrDistance = squaredDistance(pivot, target);
579 if (sqrDistance < best.squaredDistance())
580 {
581 best = Neighbour(node.object(), sqrDistance);
582 }
583
584 const TAxis split = node.axis();
585 if (split != dummyAxis_)
586 {
587 const TValue delta = target[split] - pivot[split]; // distance to splitting plane
588 if (delta < 0)
589 {
590 // we are left of the plane - search left node first
591 doNearestNeighbour(2 * index + 1, target, best);
592 if (num::sqr(delta) < best.squaredDistance())
593 {
594 doNearestNeighbour(2 * index + 2, target, best);
595 }
596 }
597 else
598 {
599 // we are right of the plane - search right node first
600 doNearestNeighbour(2 * index + 2, target, best);
601 if (num::sqr(delta) < best.squaredDistance())
602 {
603 doNearestNeighbour(2 * index + 1, target, best);
604 }
605 }
606 }
607}
608
609
610
611template <class O, class OT>
612template <typename OutputIterator>
613OutputIterator KdTree<O, OT>::doRangeSearch(
614 size_t index, const TPoint& target, TParam squaredDistance,
615 OutputIterator output) const
616{
617 if (index >= heap_.size() || heap_[index].object() == *end_)
618 {
619 return output;
620 }
621 const Node& node = heap_[index];
622
623 const TPoint& pivot = node.position();
624 const TAxis split = node.axis();
625 if (split != dummyAxis_)
626 {
627 const TValue delta = target[split] - pivot[split]; // distance to splitting plane
628 if (delta < TValue())
629 {
630 // we are left of the plane - search left node first
631 output = doRangeSearch(2 * index + 1, target, squaredDistance, output);
632 if (num::sqr(delta) < squaredDistance)
633 {
634 output = doRangeSearch(2 * index + 2, target, squaredDistance, output);
635 }
636 }
637 else
638 {
639 // we are right of the plane - search right node first
640 output = doRangeSearch(2 * index + 2, target, squaredDistance, output);
641 if (num::sqr(delta) < squaredDistance)
642 {
643 output = doRangeSearch(2 * index + 1, target, squaredDistance, output);
644 }
645 }
646 }
647
648 const TValue sqrDistance = this->squaredDistance(pivot, target);
649 if (sqrDistance < squaredDistance)
650 {
651 *output++ = Neighbour(node.object(), sqrDistance);
652 }
653 return output;
654}
655
656
657
658template <class O, class OT>
659template <typename RandomIterator>
660RandomIterator KdTree<O, OT>::doRangeSearch(
661 size_t index, const TPoint& target, TReference squaredRadius, size_t maxCount,
662 RandomIterator first, RandomIterator last) const
663{
664 if (index >= heap_.size() || heap_[index].object() == *end_)
665 {
666 return last;
667 }
668 const Node& node = heap_[index];
669
670 const TPoint& pivot = node.position();
671 const TAxis split = node.axis();
672 if (split != dummyAxis_)
673 {
674 const TValue delta = target[split] - pivot[split]; // distance to splitting plane
675 if (delta < TValue())
676 {
677 // we are left of the plane - search left node first
678 last = doRangeSearch(
679 2 * index + 1, target, squaredRadius, maxCount, first, last);
680 if (num::sqr(delta) < squaredRadius)
681 {
682 last = doRangeSearch(
683 2 * index + 2, target, squaredRadius, maxCount, first, last);
684 }
685 }
686 else
687 {
688 // we are right of the plane - search right node first
689 last = doRangeSearch(
690 2 * index + 2, target, squaredRadius, maxCount, first, last);
691 if (num::sqr(delta) < squaredRadius)
692 {
693 last = doRangeSearch(
694 2 * index + 1, target, squaredRadius, maxCount, first, last);
695 }
696 }
697 }
698
699 const TValue sqrDistance = squaredDistance(pivot, target);
700 if (sqrDistance < squaredRadius)
701 {
702 *last++ = Neighbour(node.object(), sqrDistance);
703 std::push_heap(first, last);
704 LASS_ASSERT(last >= first);
705 if (static_cast<size_t>(last - first) > maxCount)
706 {
707 std::pop_heap(first, last);
708 --last;
709 squaredRadius = first->squaredDistance();
710 }
711 }
712 return last;
713}
714
715
716
717template <class O, class OT> inline
719KdTree<O, OT>::squaredDistance(const TPoint& a, const TPoint& b)
720{
721 TValue result = TValue();
722 for (unsigned k = 0; k < dimension; ++k)
723 {
724 result += num::sqr(a[k] - b[k]);
725 }
726 return result;
727}
728
729
730
731#ifdef LASS_SPAT_KD_TREE_DIAGNOSTICS
732template <class O, class OT>
734{
735 typedef typename meta::Select< meta::Bool<dimension == 2>, prim::Aabb2D<TValue>, prim::Aabb3D<TValue> >::Type TAabb;
736 class Visitor
737 {
738 public:
739 Visitor(const TNodes& heap, TObjectIterator end):
740 xml_("kdtree.xml", "diagnostics"),
741 heap_(heap),
742 end_(end)
743 {
744 }
745
746 void visit(size_t index, const TAabb& aabb)
747 {
748#if LASS_COMPILER_TYPE == LASS_COMPILER_TYPE_MSVC
749 using lass::prim::operator<<;
750#endif
751 xml_ << aabb;
752
753 if (index >= heap_.size() || heap_[index].object() == end_)
754 {
755 return;
756 }
757 const Node& node = heap_[index];
758
759 const typename TObjectTraits::TPoint& pivot = node.position();
760 xml_ << pivot;
761
762 const TAxis split = node.axis();
763 if (split == dummyAxis_)
764 {
765 return;
766 }
767
768 TAabb less = aabb;
769 typename TAabb::TPoint max = less.max();
770 max[split] = pivot[split];
771 less.setMax(max);
772 visit(2 * index + 1, less);
773
774 TAabb greater = aabb;
775 typename TAabb::TPoint min = greater.min();
776 min[split] = pivot[split];
777 greater.setMin(min);
778 visit(2 * index + 2, greater);
779 }
780
781 private:
782 io::XmlOFile xml_;
783 const TNodes& heap_;
784 TObjectIterator end_;
785 };
786
787 TAabb aabb;
788 for (size_t i = 0, n = heap_.size(); i < n; ++i)
789 {
790 if (heap_[index].object() == *end_)
791 {
792 continue;
793 }
794 aabb += TObjectTraits::position(heap_[i].object());
795 }
796
797 Visitor visitor(heap_, *end_);
798 visitor.visit(0, aabb);
799}
800#endif
801
802
803
804// --- free ----------------------------------------------------------------------------------------
805
806
807
808}
809
810}
811
812#endif
813
814// EOF
void clear()
resest the k-d tree to an empty one.
Definition kd_tree.inl:371
TValue rangeSearch(const TPoint &target, TParam maxRadius, size_t maxCount, TNeighbourhood &neighbourhood) const
Locates objects within a spherical range around a target position.
Definition kd_tree.inl:239
void swap(TSelf &other)
Swap the representation of two k-d trees.
Definition kd_tree.inl:350
KdTree()
Constructs an empty k-d tree.
Definition kd_tree.inl:69
Neighbour nearestNeighbour(const TPoint &target) const
Locates the object that's nearest to a target position.
Definition kd_tree.inl:141
void reset()
Resets to an empty tree.
Definition kd_tree.inl:117
bool isEmpty() const
returns true if there are no objects in the k-d tree
Definition kd_tree.inl:361
T sqr(const T &x)
return x * x
Definition basic_ops.h:162
std::vector< std::basic_string< Char, Traits, Alloc > > split(const std::basic_string< Char, Traits, Alloc > &to_be_split)
Reflects the Python function split without seperator argument.
Definition basic_ops.h:211
spatial subdivisions, quadtrees, octrees, meshes in 2D and 3D, triangulators, ...
Definition aabb8_tree.h:80
Library for Assembled Shared Sources.
Definition config.h:53