Skip to content

Commit ab8d772

Browse files
committed
#200 Fix KDTree nearest algorithm
Add regression test
1 parent b289563 commit ab8d772

3 files changed

Lines changed: 85 additions & 37 deletions

File tree

CDT/include/KDTree.h

Lines changed: 49 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -192,11 +192,10 @@ class KDTree
192192
value_type out;
193193
int iTask = -1;
194194
coord_type minDistSq = std::numeric_limits<coord_type>::max();
195-
m_tasksStack[++iTask] =
196-
NearestTask(m_root, m_min, m_max, m_rootDir, minDistSq);
195+
m_tasksStack[++iTask] = NearestTask(m_root, m_min, m_max, m_rootDir);
197196
while(iTask != -1)
198197
{
199-
const NearestTask t = m_tasksStack[iTask--];
198+
const NearestTask& t = m_tasksStack[iTask--];
200199
if(t.distSq > minDistSq)
201200
continue;
202201
const Node& n = m_nodes[t.node];
@@ -221,31 +220,31 @@ class KDTree
221220
point_type newMin, newMax;
222221
calcSplitInfo(t.min, t.max, t.dir, mid, newDir, newMin, newMax);
223222

224-
const coord_type distToMid = t.dir == NodeSplitDirection::X
225-
? (point.x - mid)
226-
: (point.y - mid);
227-
const coord_type toMidSq = distToMid * distToMid;
228-
229-
const std::size_t iChild = whichChild(point, mid, t.dir);
230223
if(iTask + 2 >= static_cast<int>(m_tasksStack.size()))
231224
{
232225
m_tasksStack.resize(
233226
m_tasksStack.size() + StackDepthIncrement);
234227
}
235-
// node containing point should end up on top of the stack
236-
if(iChild == 0)
237-
{
238-
m_tasksStack[++iTask] = NearestTask(
239-
n.children[1], newMin, t.max, newDir, toMidSq);
240-
m_tasksStack[++iTask] = NearestTask(
241-
n.children[0], t.min, newMax, newDir, toMidSq);
242-
}
243-
else
228+
229+
NearestTask closerTask(n.children[0], t.min, newMax, newDir);
230+
NearestTask fartherTask(n.children[1], newMin, t.max, newDir);
231+
if(isAfterSplit(point, mid, t.dir))
232+
std::swap(closerTask, fartherTask);
233+
234+
closerTask.calculateDistanceSquared(point);
235+
if(closerTask.distSq <= minDistSq)
244236
{
245-
m_tasksStack[++iTask] = NearestTask(
246-
n.children[0], t.min, newMax, newDir, toMidSq);
247-
m_tasksStack[++iTask] = NearestTask(
248-
n.children[1], newMin, t.max, newDir, toMidSq);
237+
fartherTask.calculateDistanceSquared(point);
238+
if(fartherTask.distSq <= minDistSq)
239+
{
240+
// put closest node on top of the stack
241+
m_tasksStack[++iTask] = fartherTask;
242+
m_tasksStack[++iTask] = closerTask;
243+
}
244+
else
245+
{
246+
m_tasksStack[++iTask] = closerTask;
247+
}
249248
}
250249
}
251250
}
@@ -261,15 +260,22 @@ class KDTree
261260
return newNodeIndex;
262261
}
263262

263+
static bool isAfterSplit(
264+
const point_type& point,
265+
const coord_type& split,
266+
const NodeSplitDirection::Enum dir)
267+
{
268+
return dir == NodeSplitDirection::X ? point.x > split : point.y > split;
269+
}
270+
264271
/// Test which child point belongs to after the split
265272
/// @returns 0 if first child, 1 if second child
266-
std::size_t whichChild(
273+
static std::size_t whichChild(
267274
const point_type& point,
268275
const coord_type& split,
269-
const NodeSplitDirection::Enum dir) const
276+
const NodeSplitDirection::Enum dir)
270277
{
271-
return static_cast<size_t>(
272-
dir == NodeSplitDirection::X ? point.x > split : point.y > split);
278+
return isAfterSplit(point, split, dir);
273279
}
274280

275281
/// Calculate split location, direction, and children boxes
@@ -389,17 +395,24 @@ class KDTree
389395
: dir(NodeSplitDirection::X)
390396
{}
391397
NearestTask(
392-
const node_index node_,
393-
const point_type& min_,
394-
const point_type& max_,
395-
const NodeSplitDirection::Enum dir_,
396-
const coord_type distSq_)
397-
: node(node_)
398-
, min(min_)
399-
, max(max_)
400-
, dir(dir_)
401-
, distSq(distSq_)
398+
const node_index node,
399+
const point_type& min,
400+
const point_type& max,
401+
const NodeSplitDirection::Enum dir)
402+
: node(node)
403+
, min(min)
404+
, max(max)
405+
, dir(dir)
406+
, distSq(std::numeric_limits<coord_type>::max())
402407
{}
408+
void calculateDistanceSquared(const point_type& p)
409+
{
410+
const coord_type dx =
411+
std::max(std::max(min.x - p.x, coord_type(0)), p.x - max.x);
412+
const coord_type dy =
413+
std::max(std::max(min.y - p.y, coord_type(0)), p.y - max.y);
414+
distSq = dx * dx + dy * dy;
415+
}
403416
};
404417
// allocated in class (not in the 'nearest' method) for better performance
405418
mutable std::vector<NearestTask> m_tasksStack;

CDT/src/CDT.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,16 @@ template CDT_EXPORT void initializeWithRegularGrid<double>(
6565
std::size_t,
6666
Triangulation<double>&);
6767

68+
template
69+
CDT_EXPORT float distance(const V2d<float>& a, const V2d<float>& b);
70+
template
71+
CDT_EXPORT double distance(const V2d<double>& a, const V2d<double>& b);
72+
73+
template
74+
CDT_EXPORT float distanceSquared(const V2d<float>& a, const V2d<float>& b);
75+
template
76+
CDT_EXPORT double distanceSquared(const V2d<double>& a, const V2d<double>& b);
77+
6878
} // namespace CDT
6979

7080
#endif

CDT/tests/cdt.test.cpp

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1113,4 +1113,29 @@ TEST_CASE("Callbacks test: test aborting the calculation")
11131113
}
11141114
}
11151115
}
1116-
#endif
1116+
1117+
#endif
1118+
1119+
TEST_CASE("KDtree regression (#200)")
1120+
{
1121+
const auto target = V2d<double>(153.63, -30.09);
1122+
const auto points = std::vector<V2d<double> >{
1123+
V2d<double>(168.67, -122.39), // approx distance to target: 93.52
1124+
V2d<double>(-25.12, 109.06), // approx distance to target: 226.53
1125+
V2d<double>(178.36, 40.75), // approx distance to target: 75.03
1126+
V2d<double>(161.07, 86.2), // approx distance to target: 116.53
1127+
};
1128+
1129+
auto tree = KDTree::KDTree<double, 1, 32, 32>(
1130+
V2d<double>(-180, -180), V2d<double>(180, 180));
1131+
double min_distance = std::numeric_limits<double>::max();
1132+
for(VertInd i(0); i < points.size(); ++i)
1133+
{
1134+
tree.insert(i, points);
1135+
min_distance = std::min(min_distance, distance(target, points[i]));
1136+
}
1137+
1138+
const auto [matchVec, matchIdx] = tree.nearest(target, points);
1139+
REQUIRE(min_distance == distance(target, points[matchIdx]));
1140+
}
1141+

0 commit comments

Comments
 (0)