use crate::GpuContext;
use bytemuck::{Pod, Zeroable};
use rayon::prelude::*;
use threecrate_algorithms::{KdTree, LocalFrame};
use threecrate_core::{Point3f, Result};
pub(crate) const KD_TREE_WGSL: &str = r#"
struct Node {
pos: vec3<f32>,
axis: u32,
index: u32,
left: u32,
right: u32,
_pad: u32,
}
const NO_NODE: u32 = 0xffffffffu;
// Median-split trees with u32 node indices are at most 33 levels deep; the
// stack holds one deferred far child per level plus the current near child.
// Kept small: these arrays live in each GPU thread's registers.
const MAX_STACK: u32 = 36u;
const MAX_K: u32 = 32u;
@group(0) @binding(0) var<storage, read> nodes: array<Node>;
// Result of `find_k_nearest`: node positions and squared distances, closest
// first. Only the first `knn_count` entries are valid.
var<private> knn_node: array<u32, MAX_K>;
var<private> knn_dist: array<f32, MAX_K>;
var<private> knn_count: u32;
// Squared distance a candidate must beat: the k-th best once we have k.
fn knn_bound(k: u32, limit_sq: f32) -> f32 {
if (knn_count < k) {
return limit_sq;
}
return knn_dist[k - 1u];
}
// Find the `k` (<= MAX_K) nodes nearest to `query` that are closer than
// sqrt(limit_sq).
fn find_k_nearest(query: vec3<f32>, k: u32, limit_sq: f32) {
knn_count = 0u;
if (k == 0u || arrayLength(&nodes) == 0u) {
return;
}
var stack_node: array<u32, MAX_STACK>;
var stack_plane: array<f32, MAX_STACK>;
stack_node[0] = 0u;
stack_plane[0] = 0.0;
var len = 1u;
loop {
if (len == 0u) {
break;
}
len -= 1u;
// Re-check on pop: the bound may have shrunk since this was pushed.
if (stack_plane[len] >= knn_bound(k, limit_sq)) {
continue;
}
let ni = stack_node[len];
let node = nodes[ni];
let d = node.pos - query;
let dist_sq = dot(d, d);
if (dist_sq < knn_bound(k, limit_sq)) {
// Insert into the sorted list, dropping the current k-th if full.
var slot = min(knn_count, k - 1u);
if (knn_count < k) {
knn_count += 1u;
}
loop {
if (slot == 0u || knn_dist[slot - 1u] <= dist_sq) {
break;
}
knn_dist[slot] = knn_dist[slot - 1u];
knn_node[slot] = knn_node[slot - 1u];
slot -= 1u;
}
knn_dist[slot] = dist_sq;
knn_node[slot] = ni;
}
let axis_dist = query[node.axis] - node.pos[node.axis];
var near = node.left;
var far = node.right;
if (axis_dist > 0.0) {
near = node.right;
far = node.left;
}
let plane_sq = axis_dist * axis_dist;
// Push far before near so near is popped first.
if (far != NO_NODE && plane_sq < knn_bound(k, limit_sq)) {
stack_node[len] = far;
stack_plane[len] = plane_sq;
len += 1u;
}
if (near != NO_NODE) {
stack_node[len] = near;
stack_plane[len] = 0.0;
len += 1u;
}
}
}
// Number of nodes other than `skip` within sqrt(radius_sq) of `center`,
// stopping early once `stop_at` is reached. A negative `radius_sq` matches
// nothing.
fn count_within(center: vec3<f32>, radius_sq: f32, skip: u32, stop_at: u32) -> u32 {
var count = 0u;
if (arrayLength(&nodes) == 0u || radius_sq < 0.0) {
return 0u;
}
var stack_node: array<u32, MAX_STACK>;
var stack_plane: array<f32, MAX_STACK>;
stack_node[0] = 0u;
stack_plane[0] = 0.0;
var len = 1u;
loop {
if (len == 0u || count >= stop_at) {
break;
}
len -= 1u;
if (stack_plane[len] > radius_sq) {
continue;
}
let ni = stack_node[len];
let node = nodes[ni];
let d = node.pos - center;
if (ni != skip && dot(d, d) <= radius_sq) {
count += 1u;
}
let axis_dist = center[node.axis] - node.pos[node.axis];
var near = node.left;
var far = node.right;
if (axis_dist > 0.0) {
near = node.right;
far = node.left;
}
let plane_sq = axis_dist * axis_dist;
// The far side can only hold matches if the split plane is in range.
if (far != NO_NODE && plane_sq <= radius_sq) {
stack_node[len] = far;
stack_plane[len] = plane_sq;
len += 1u;
}
if (near != NO_NODE) {
stack_node[len] = near;
stack_plane[len] = 0.0;
len += 1u;
}
}
return count;
}
struct Nearest {
node: u32,
dist_sq: f32,
}
// Nearest node to `query` closer than sqrt(bound_sq). `seed` is a node known to
// be close (e.g. last ICP iteration's match) or NO_NODE; it only speeds up the
// search, the result is still exact.
fn find_nearest(query: vec3<f32>, bound_sq: f32, seed: u32) -> Nearest {
var best = Nearest(NO_NODE, bound_sq);
if (arrayLength(&nodes) == 0u) {
return best;
}
if (seed != NO_NODE && seed < arrayLength(&nodes)) {
let d = nodes[seed].pos - query;
let dist_sq = dot(d, d);
if (dist_sq < best.dist_sq) {
best = Nearest(seed, dist_sq);
}
}
var stack_node: array<u32, MAX_STACK>;
var stack_plane: array<f32, MAX_STACK>;
stack_node[0] = 0u;
stack_plane[0] = 0.0;
var len = 1u;
loop {
if (len == 0u) {
break;
}
len -= 1u;
if (stack_plane[len] >= best.dist_sq) {
continue;
}
let ni = stack_node[len];
let node = nodes[ni];
let d = node.pos - query;
let dist_sq = dot(d, d);
if (dist_sq < best.dist_sq) {
best = Nearest(ni, dist_sq);
}
let axis_dist = query[node.axis] - node.pos[node.axis];
var near = node.left;
var far = node.right;
if (axis_dist > 0.0) {
near = node.right;
far = node.left;
}
let plane_sq = axis_dist * axis_dist;
if (far != NO_NODE && plane_sq < best.dist_sq) {
stack_node[len] = far;
stack_plane[len] = plane_sq;
len += 1u;
}
if (near != NO_NODE) {
stack_node[len] = near;
stack_plane[len] = 0.0;
len += 1u;
}
}
return best;
}
"#;
pub(crate) const MAX_K: usize = 32;
#[repr(C)]
#[derive(Copy, Clone, Pod, Zeroable)]
struct GpuNode {
pos: [f32; 3],
axis: u32,
index: u32,
left: u32,
right: u32,
_pad: u32,
}
pub(crate) fn vec4(p: &Point3f) -> [f32; 4] {
[p.x, p.y, p.z, 0.0]
}
pub(crate) fn vec4s(points: &[Point3f]) -> Vec<[f32; 4]> {
points.par_iter().map(vec4).collect()
}
pub(crate) struct GpuKdTree {
pub(crate) nodes: wgpu::Buffer,
pub(crate) node_to_index: Vec<u32>,
pub(crate) frame: LocalFrame,
}
impl GpuKdTree {
pub(crate) fn new(gpu: &GpuContext, points: &[Point3f]) -> Result<Self> {
let frame = LocalFrame::centred_on(points);
let tree = KdTree::from_points(frame.points_to_local(points))?;
let gpu_nodes: Vec<GpuNode> = tree
.flat_nodes()
.map(|node| GpuNode {
pos: [node.point.x, node.point.y, node.point.z],
axis: node.axis as u32,
index: node.index as u32,
left: node.left,
right: node.right,
_pad: 0,
})
.collect();
let node_to_index = gpu_nodes.iter().map(|n| n.index).collect();
let contents: &[GpuNode] = if gpu_nodes.is_empty() {
&[GpuNode::zeroed()]
} else {
&gpu_nodes
};
let nodes = gpu.create_buffer_init("KdTree Nodes", contents, wgpu::BufferUsages::STORAGE);
Ok(Self {
nodes,
node_to_index,
frame,
})
}
pub(crate) fn is_empty(&self) -> bool {
self.node_to_index.is_empty()
}
}
pub(crate) fn morton_order(points: &[Point3f]) -> Vec<u32> {
let mut lo = [f32::MAX; 3];
let mut hi = [f32::MIN; 3];
for p in points
.iter()
.filter(|p| p.coords.iter().all(|c| c.is_finite()))
{
for axis in 0..3 {
lo[axis] = lo[axis].min(p[axis]);
hi[axis] = hi[axis].max(p[axis]);
}
}
let cell = |axis: usize, value: f32| -> u32 {
let extent = hi[axis] - lo[axis];
if !(extent > 0.0) || !value.is_finite() {
return 0;
}
(((value - lo[axis]) / extent) * 1023.0).clamp(0.0, 1023.0) as u32
};
let spread = |mut x: u32| -> u32 {
x &= 0x3ff;
x = (x | (x << 16)) & 0x030000ff;
x = (x | (x << 8)) & 0x0300f00f;
x = (x | (x << 4)) & 0x030c30c3;
(x | (x << 2)) & 0x09249249
};
let mut keyed: Vec<(u32, u32)> = points
.par_iter()
.enumerate()
.map(|(i, p)| {
let code =
spread(cell(0, p[0])) | (spread(cell(1, p[1])) << 1) | (spread(cell(2, p[2])) << 2);
(code, i as u32)
})
.collect();
keyed.par_sort_unstable();
keyed.into_iter().map(|(_, i)| i).collect()
}