Skip to content
Snippets Groups Projects
Select Git revision
  • 48765d01509cd7522e89356b3931f799c6dac530
  • main default
2 results

CgPointWrangler.h

Blame
  • CgPointWrangler.h 24.54 KiB
    #ifndef CGPOINTWRANGLER_H
    #define CGPOINTWRANGLER_H
    
    // stl
    #include <vector>
    #include <queue>
    #include <deque>
    #include <limits>
    #include <string>
    #include <algorithm>
    #include <iostream>
    #include <numeric>
    #include <utility>
    #include <functional>
    #include <tuple>
    #include <thread>
    
    // glm & eigen
    #include <glm/glm.hpp>
    #include <glm/gtx/norm.hpp>
    #include <glm/gtc/matrix_transform.hpp>
    
    // this
    #include "CgAABB.h"
    #include "CgPointMath.h"
    #include "CgUtils/Timer.h"
    
    /*
    * more complex point cloud algorithms & data structures
    */
    class CgPointWrangler {
        public:
        
            // rearrange the vec3 between begin and end so they form a kd-tree
            static void buildKdTree(glm::vec3* begin, glm::vec3* end, unsigned int depth);
            static void buildKdTree(PCA* begin, PCA* end, unsigned int depth);
    
            // traverse a kd-tree between begin and end in BFO and call a function on each node
            static void traverseBFO(
                glm::vec3* begin, glm::vec3* end, 
                std::function<void(glm::vec3*, const unsigned int)> fn
            );
    
            // get the point that's closest to the ray in the kd-tree between begin and end
            static int getClosestPointToRay(
                const glm::vec3* begin, const glm::vec3* end,
                const CgAABB aabb,
                glm::vec3 origin, glm::vec3 direction
            );
    
            // naive estimation of normals
            static std::vector<std::vector<unsigned int>> getKNeighbourhoods(unsigned int neighbourhood_size, const std::vector<glm::vec3>& vertices);
            static std::vector<std::vector<glm::vec3>> kNeighbourhoodsToClusters(const std::vector<glm::vec3>& vertices, const std::vector<std::vector<unsigned int>>& neighbourhoods);
            static std::vector<std::pair<unsigned int, unsigned int>> makeNaiveSpanningTree(const std::vector<std::vector<unsigned int>>& neighbourhoods, const std::vector<PCA>& pca);
            static std::vector<PCA> performPcaOnClusters(std::vector<std::vector<glm::vec3>> clusters); 
            static std::vector<glm::vec3> alignNormals(
                glm::vec3 ref_normal, // reference the first normal will be aligned to 
                const std::vector<std::pair<unsigned int, unsigned int>> spanning_tree, // list of edges (from, to)
                const std::vector<PCA>& pca // contains the unaligned normals
            );
    
            static std::vector<PCA> performHierarchicalSimplification(
                std::vector<glm::vec3> original_points,
                float max_cluster_size,
                float max_cluster_variance,
                float max_cluster_radius
            );
    
            static std::vector<PCA> performIncrementalSimplification(
                std::vector<glm::vec3> original_points,
                float max_cluster_size,
                float max_cluster_variance,
                float max_cluster_radius
            );
    
            static std::vector<unsigned int> getNearestNeighborsSlow(
                unsigned int current_point, 
                unsigned int k,
                const std::vector<glm::vec3>& vertices
            );
    
            static std::vector<unsigned int> getNearestNeighborsFast(
                unsigned int current_point, 
                unsigned int k, 
                const std::vector<glm::vec3>& vertices,
                CgAABB aabb
            );
    
            static std::vector<unsigned int> getNearestNeighborsFast(
                glm::vec3 query, 
                unsigned int k, 
                const std::vector<glm::vec3>& vertices,
                CgAABB aabb
            );
    
        private:
            // calculate offset between begin and half-way pointer between begin and end
            // requires end >= begin
            template<typename T>
            static unsigned int halfSize(const T* begin, const T* end);
            
            CgPointWrangler(){}
    };
    
    template<typename T>
    inline unsigned int CgPointWrangler::halfSize(const T* begin, const T* end) {
        return (end - begin) / 2;
    }
    
    // rearrange the vec3 between begin and end (exclusive) so they form a kd-tree
    // requires begin < end
    inline void CgPointWrangler::buildKdTree(glm::vec3* begin, glm::vec3* end, unsigned int depth) {
        unsigned int half_size = halfSize(begin, end);
    
        // none or one element
        if(half_size == 0) return;
    
        // partially sort and ensure median element 
        // is in the middle (runs in O(n))
        // about 4x faster on tyra.obj than std::sort
        std::nth_element(begin, begin + half_size, end, [=](const glm::vec3& a, const glm::vec3& b){
            return a[depth % 3] < b[depth % 3];
        });
    
        // split array in two (excluding median) and recurse
        buildKdTree(begin, begin + half_size, depth + 1);
        buildKdTree(begin + half_size + 1, end, depth + 1);
    }
    
    // see the version for glm::vec3
    inline void CgPointWrangler::buildKdTree(PCA* begin, PCA* end, unsigned int depth) {
        unsigned int half_size = halfSize(begin, end);
        if(half_size == 0) return;
        std::nth_element(begin, begin + half_size, end, [=](const PCA& a, const PCA& b){
            return a.centroid[depth % 3] < b.centroid[depth % 3];
        });
        buildKdTree(begin, begin + half_size, depth + 1);
        buildKdTree(begin + half_size + 1, end, depth + 1);
    }
    
    inline void CgPointWrangler::traverseBFO(
        glm::vec3* begin, 
        glm::vec3* end, 
        std::function<void(glm::vec3*, const unsigned int)> fn
    ) {
        // queue of pairs of vec3 pointers
        // (range of points in the current node and its children)
        std::queue<std::pair<glm::vec3*, glm::vec3*>> q;
        if(begin == end) return;
        unsigned int depth = 0;
        q.push({begin, end});
        while(!q.empty()) {
            int level_count = q.size();
            for(int i = 0; i < level_count; i++) {
                auto curr = q.front();
                q.pop();
                glm::vec3* begin = curr.first;
                glm::vec3* end = curr.second;
                unsigned int half_size = halfSize(begin, end);
                glm::vec3* curr_pointer = begin + half_size;
                fn(curr_pointer, depth);
                if(end - begin <= 1) continue;
                // push left & right half of array to reconstruct splits
                if(begin <= begin + half_size) q.push({begin, begin + half_size});
                if(begin + half_size < end) q.push({begin + half_size + 1, end});
            }
            depth += 1;
        }
    }
    
    inline int CgPointWrangler::getClosestPointToRay(
        const glm::vec3* begin, 
        const glm::vec3* end,
        CgAABB aabb,
        glm::vec3 origin, 
        glm::vec3 direction
    ) {
      if(begin == end) return -1;
    
      glm::vec3 inv_direction = glm::vec3(1.0 / direction.x, 1.0 / direction.y, 1.0 / direction.z);
      // sensible starting point
      float erg_sqr_dist = std::numeric_limits<float>::infinity();
      int erg = -1;
    
      // stack of traversed nodes (start index, end index, aabb, depth)
      std::vector<std::tuple<const glm::vec3*, const glm::vec3*, CgAABB, unsigned int>> nodes;
      nodes.push_back({begin, end, aabb, 0});
      
      // traverse the tree as long as there's something to do.
      while(!nodes.empty()) {
        // unpack current node & pop it
        const glm::vec3* cur_begin;
        const glm::vec3* cur_end;
        CgAABB cur_aabb;
        unsigned int cur_depth;
        std::tie(cur_begin, cur_end, cur_aabb, cur_depth) = nodes.back();
        auto half_size = halfSize(cur_begin, cur_end);
        nodes.pop_back();
    
        glm::vec3 cur_point = *(cur_begin + half_size);
        // insert current point into set if it's closer to ray than last one
        float cand_dist = CgPointMath::sqrPointRayDist(cur_point, origin, direction);
        if(cand_dist < erg_sqr_dist) {
          erg_sqr_dist = cand_dist;
          erg = cur_begin + half_size - begin;
        }
    
        // the aabb of the sub-nodes
        unsigned int axis = cur_depth % 3;
        CgAABB lower_aabb;
        CgAABB higher_aabb;
        std::tie(lower_aabb, higher_aabb) = cur_aabb.split(axis, cur_point[axis]);
        // only push children if their aabb is closer to ray than 
        // the closest point we found until now and there are
        // points in them
        if (
            half_size != 0
            && CgPointMath::sqrBoxRayDist(lower_aabb, origin, direction, inv_direction) < erg_sqr_dist
        ) {
            nodes.push_back({cur_begin, cur_begin + half_size, lower_aabb, cur_depth + 1});
        }
        if (
            cur_begin + half_size < cur_end
            && CgPointMath::sqrBoxRayDist(higher_aabb, origin, direction, inv_direction) < erg_sqr_dist
        ) {
            nodes.push_back({cur_begin + half_size + 1, cur_end, higher_aabb, cur_depth + 1});
        }
      }
    
      return erg;
    }
    
    inline std::vector<std::vector<unsigned int>> CgPointWrangler::getKNeighbourhoods(unsigned int neighbourhood_size, const std::vector<glm::vec3>& vertices) {
        // all k-neighbourhoods
        std::vector<std::vector<unsigned int>> neighbourhoods;
        neighbourhoods.resize(vertices.size());
        CgAABB aabb = CgPointMath::calculateAABB(vertices);
    
        run_threaded(vertices.size(), [&](
            const unsigned int start_index, 
            const unsigned int count
        ) {
            for(unsigned int i = start_index; i < start_index + count; i++) {
                neighbourhoods[i] = getNearestNeighborsFast(i, neighbourhood_size, vertices, aabb);    
            }
        });
    
        return neighbourhoods;
    }
    
    inline std::vector<std::vector<glm::vec3>> CgPointWrangler::kNeighbourhoodsToClusters(
        const std::vector<glm::vec3>& vertices,
        const std::vector<std::vector<unsigned int>>& neighbourhoods
    ) {
        std::vector<std::vector<glm::vec3>> clusters;
        clusters.resize(neighbourhoods.size());
    
        run_threaded(neighbourhoods.size(), [&](
            const unsigned int start_index, 
            const unsigned int count
        ) {
            for(unsigned int i = start_index; i < start_index + count; i++) {
                std::vector<unsigned int> nh_indices = neighbourhoods[i];
                clusters[i].resize(nh_indices.size());
                for(unsigned int j = 0; j < nh_indices.size(); j++) {
                    unsigned int index = nh_indices[j];
                    glm::vec3 p = vertices[index];
                    clusters[i][j] = p;
                };
            }
        });
    
        return clusters;
    }
    
    inline std::vector<PCA> CgPointWrangler::performPcaOnClusters(std::vector<std::vector<glm::vec3>> clusters) {
        std::vector<PCA> pca;
        pca.resize(clusters.size());
    
        // worker for doing the PCAs
        run_threaded(pca.size(), [&](
            const unsigned int start_index, 
            const unsigned int count
        ) {
            for(unsigned int i = start_index; i < start_index + count; i++) {
                std::vector<glm::vec3> nh = clusters[i];
                pca[i] = CgPointMath::calculatePCA(nh);
            }
        });
    
        return pca;
    }
    
    inline std::vector<std::pair<unsigned int, unsigned int>> CgPointWrangler::makeNaiveSpanningTree(
        const std::vector<std::vector<unsigned int>>& neighbourhoods,
        const std::vector<PCA>& pca
    ) {
        std::cout << pca.size() << " pca elements for spanning tree" << std::endl;
        std::vector<std::pair<unsigned int, unsigned int>> spanning_tree;
        spanning_tree.clear();
        spanning_tree.reserve(pca.size() + 1);
    
        // find point with highest y-value
        std::vector<bool> visited;
        visited.resize(pca.size(), false);
        float max_y = -std::numeric_limits<float>::infinity();
        unsigned int max_y_index = 0;
        for(unsigned int i = 0; i < pca.size(); i++) {
            float curr = pca[i].centroid.y;
            if(max_y < curr) {
                max_y = curr;
                max_y_index = i;
            }
        }
        visited[max_y_index] = true;
    
        // record the "best" parent found for each vertex
        std::vector<std::pair<unsigned int, double>> scores;
        scores.resize(pca.size());
    
        // queue of point indices to process next
        std::vector<unsigned int> queue;
    
        auto cmp = [&](const unsigned int left, unsigned int right) {
            return scores[left].second > scores[right].second;
        };
    
        auto visit = [&](unsigned int curr) {
            glm::vec3 normal = pca[curr].evec0;
            glm::vec3 point = pca[curr].centroid;
            double rad = pca[curr].radius2;
    
            // retrieve neighbourhood indices
            auto curr_nh_ind = neighbourhoods[curr];
    
            // find out which of the neighbours we didn't process yet
            // and score them to find out in which order to process
            for(unsigned int i = 0; i < curr_nh_ind.size(); i++) {
                unsigned int index = curr_nh_ind[i];
                // score by how far away it is from the current point
                // and how parallel the normals are.
                double alignment = 1.0 - std::abs(glm::dot(pca[index].evec0, normal));
                double distance = glm::distance(pca[index].centroid, point) / rad;
                double score = distance + alignment;
                if(!visited[index]) {
                    scores[index].first = curr;
                    scores[index].second = score;
                    queue.push_back(index);
                    std::push_heap(queue.begin(), queue.end(), cmp);
                    visited[index] = true;
                } else if(scores[index].second > score) {
                    // update current neighbors parent to current node, since it's closer
                    scores[index].first = curr;
                    scores[index].second = score;
                    // re-sort the heap with the new score
                    std::make_heap(queue.begin(), queue.end(), cmp);
                }
            }
        };
    
        // get started by aligning the normal of the highest point
        spanning_tree.push_back({max_y_index, max_y_index});
        visit(max_y_index); 
    
        // start processing
        while(!queue.empty()) {
            // pop the index with the smallest score
            std::pop_heap(queue.begin(), queue.end(), cmp);
            unsigned int curr = queue.back();
            unsigned int parent = scores[curr].first;
            queue.pop_back();
            spanning_tree.push_back({parent, curr});
            visit(curr);
        }
    
        return spanning_tree;
    }
    
    inline std::vector<glm::vec3> CgPointWrangler::alignNormals(
        glm::vec3 ref_normal, // reference the first normal will be aligned to
        const std::vector<std::pair<unsigned int, unsigned int>> spanning_tree, // list of edges (from, to)
        const std::vector<PCA>& pca // contains the unaligned normals
    ) {
        std::cout << spanning_tree.size() << " spanning tree edges" << std::endl;
        std::vector<glm::vec3> normals;
        normals.resize(pca.size());
        for(unsigned int i = 0; i < pca.size(); i++) normals[i] = pca[i].evec0;
        // align first normal with ref
        unsigned int start_index = spanning_tree[0].first;
        normals[start_index] = glm::dot(normals[start_index], ref_normal) > 0.0 
            ? normals[start_index] 
            : -(normals[start_index]);
    
        // align the rest 
        // (spanning tree should ensure that we don't align to a normal that's not yet aligned itself)
        for(unsigned int i = 1; i < spanning_tree.size(); i++) {
            unsigned int parent_index = spanning_tree[i].first;
            unsigned int own_index = spanning_tree[i].second;
            normals[own_index] = glm::dot(normals[own_index], normals[parent_index]) > 0.0 
                ? normals[own_index] 
                : -(normals[own_index]);
        }
    
        return normals;
    }
    
    inline std::vector<PCA> CgPointWrangler::performHierarchicalSimplification(
        std::vector<glm::vec3> original_points,
        float max_cluster_size,
        float max_cluster_variance,
        float max_cluster_radius
    ){
        unsigned int MAX_POINTS = std::min(100.0f, max_cluster_size);
        unsigned int MIN_POINTS = 3;
    
        std::vector<unsigned int> hist;
        hist.resize(MAX_POINTS + 1, 0);
    
        // used by the threads to define the clusters
        std::vector<unsigned int> indices;
        indices.resize(original_points.size());
        for(unsigned int i = 0; i < original_points.size(); i++) indices[i] = i;
    
        // used to define the work portions
        std::vector<std::pair<unsigned int, unsigned int>> jobs; // list of ranges of indices
        jobs.push_back({0, indices.size()}); // initially, use entire point cloud
        std::vector<PCA> pca; // finished clusters
    
        while(!jobs.empty()) {
            std::pair<unsigned int, unsigned int> job = jobs.back();
            jobs.pop_back();
    
            // get cluster and check if it fits constraints
            std::vector<glm::vec3> cluster;
            for(unsigned int i = job.first; i < job.second; i++) cluster.push_back(original_points[indices[i]]);
            PCA current_pca = CgPointMath::calculatePCA(cluster);
            double variance = CgPointMath::varianceFromPCA(current_pca);
            if(cluster.size() <= MIN_POINTS  || (variance < max_cluster_variance && current_pca.radius2 < max_cluster_radius && cluster.size() <= MAX_POINTS)) {
                // push cluster as-is into cluster vector
                hist[cluster.size()] += 1;
                pca.push_back(current_pca);
            } else {
                // split it & recurse
                // indices vector is not behind mutex because 
            // indices vector is not behind mutex because 
                // indices vector is not behind mutex because 
                // the threads work on mutually exclusive ranges
                unsigned int lower = job.first;
                unsigned int upper = job.second;
                while(lower != upper) {
                    // separate indices depending on which side of the centroid-normal plane
                    // their points are
                    if(glm::dot(original_points[indices[lower]] - current_pca.centroid, current_pca.evec2) > 0) {
                    lower += 1;
                    } else {
                    upper -= 1;
                    unsigned int t = indices[upper];
                    indices[upper] = indices[lower];
                    indices[lower] = t;
                    }
                }
                
                jobs.push_back({job.first, lower});
                jobs.push_back({lower, job.second});
            }
        }
    
        // clustering is done 
        std::cout << pca.size() << " clusters generated, histogram (cluster_size:count):" << std::endl;
        for(unsigned int i = 0; i < hist.size(); i++) std::cout << " |" << i + 1 << ":" << hist[i];
        std::cout << std::endl;
        return pca;
    }
    
    inline std::vector<PCA> CgPointWrangler::performIncrementalSimplification(
        std::vector<glm::vec3> original_points,
        float max_cluster_size,
        float max_cluster_variance,
        float max_cluster_radius
    ){
        unsigned int MAX_POINTS = std::min(100.0f, max_cluster_size);
        std::vector<glm::vec3> remaining_points;
        std::vector<unsigned int> current_nh;
        std::vector<bool> used;
        used.resize(original_points.size(), false);
        std::vector<PCA> pca;
        glm::vec3 next_seed_point = original_points[0];
        std::vector<unsigned int> hist;
        hist.resize(MAX_POINTS + 1, 0);
        while(original_points.size() > 0) { // while there are still points to consider...
            unsigned int k;
            CgAABB aabb = CgPointMath::calculateAABB(original_points);
            PCA current_pca;
            current_pca.centroid = next_seed_point;
            // walk up to the largest cluster that fits the constraints
            // for each size:
            for(k = 1; k <= MAX_POINTS + 1; k++) {
            // get neighbourhood indices
            std::vector<unsigned int> nh = CgPointWrangler::getNearestNeighborsFast(
                current_pca.centroid, k,
                original_points, aabb
            );
    
            // get neighbourhood points from indices
            std::vector<glm::vec3> cluster(nh.size());
            for(unsigned int i = 0; i < nh.size(); i++) cluster[i] = original_points[nh[i]];
    
            // do pca and check if cluster fits constraint
            PCA cluster_pca = CgPointMath::calculatePCA(cluster);
            float variance = CgPointMath::varianceFromPCA(cluster_pca);
            if(variance > max_cluster_variance || cluster_pca.radius2 > max_cluster_radius) {
                // the point that's last in the cluster is furthest away from the seed
                // use it as next seed
                next_seed_point = cluster[cluster.size()- 1];
                hist[nh.size()] += 1;
                break;
            }
            current_nh = nh;
            current_pca = cluster_pca;
            // if there are not enough points left to break the constraint
            if(k > nh.size()) break;
            }
    
            // push new point information
            pca.push_back(current_pca);
    
            // if we have all the rest of the points in the current cluster, we're done
            if(current_nh.size() == original_points.size()) break;
    
            // copy unused points over
            used.clear();
            used.resize(original_points.size(), false);
            remaining_points.clear();
            remaining_points.reserve(original_points.size());
            for(unsigned int i = 0; i < current_nh.size(); i++) used[current_nh[i]] = true;
            for(unsigned int i = 0; i < original_points.size(); i++) {
            if(!used[i]) remaining_points.push_back(original_points[i]);
            used[i] = false;
            }
            // build new kd-tree in remaining points.
            original_points = remaining_points;
            CgPointWrangler::buildKdTree(original_points.data(), original_points.data() + original_points.size(), 0);
        }
    
        // clustering is done 
        std::cout << pca.size() << " clusters generated, histogram (cluster_size:count):" << std::endl;
        for(unsigned int i = 0; i < hist.size(); i++) std::cout << " |" << i + 1 << ":" << hist[i];
        std::cout << std::endl;
    
        return pca;
    }
    
    inline std::vector<unsigned int> CgPointWrangler::getNearestNeighborsSlow(
        unsigned int current_point, 
        unsigned int k, 
        const std::vector<glm::vec3>& vertices
    ) {
        glm::vec3 q = vertices[current_point];
    
        std::vector<std::pair<double,int>> distances;
    
        for(unsigned int i = 0; i < vertices.size(); i++) {
            double dist = glm::distance(vertices[i],q);
            distances.push_back(std::make_pair(dist,i));
        }
        std::sort(distances.begin(), distances.end());
        std::vector<unsigned int> erg;
    
        for(unsigned int i = 0; i < k; i++) erg.push_back(distances[i].second);
    
        return erg;
    }
    
    inline std::vector<unsigned int> CgPointWrangler::getNearestNeighborsFast(
        unsigned int current_point, 
        unsigned int k,
        const std::vector<glm::vec3>& vertices,
        CgAABB aabb
    ){
        glm::vec3 query = vertices[current_point];
        return getNearestNeighborsFast(
            query, k, vertices, aabb
        );
    }
    
    inline std::vector<unsigned int> CgPointWrangler::getNearestNeighborsFast(
        glm::vec3 query, 
        unsigned int k,
        const std::vector<glm::vec3>& vertices,
        CgAABB aabb
    ) {
        std::vector<unsigned int> erg(k);
        if(k == 0) return erg;
        
        // pair of distance + index into backing vec3 array 
        using Pair = std::pair<double, unsigned int>;
    
        const glm::vec3* first = vertices.data();
        const glm::vec3* last = vertices.data() + vertices.size();
        // comp function for max heap
        auto cmp = [&](const Pair &left, const Pair &right) { return (left.first < right.first);};
    
        // using an ordered set for simplicitly, this may be slow
        // because we frequently erase elements, causing the rb-tree
        // to be rebuilt.
        std::vector<Pair> candidates;
        candidates.reserve(k + 1);
    
        // stack of traversed nodes (start index, end index, aabb, depth)
        std::vector<std::tuple<const glm::vec3*, const glm::vec3*, CgAABB, unsigned int>> nodes;
        nodes.push_back({first, last, aabb, 0});
        
        // traverse the tree as long as there's something to do.
        while(!nodes.empty()) {
            // unpack current node & pop it
            const glm::vec3* begin;
            const glm::vec3* end;
            CgAABB aabb;
            unsigned int depth;
            std::tie(begin, end, aabb, depth) = nodes.back();
            nodes.pop_back();
            unsigned int half_size = halfSize(begin, end);
            glm::vec3 curr_point = *(begin + half_size);
            // insert current point distance & index into heap
            candidates.push_back({glm::distance(query, curr_point), begin + half_size - first});
            std::push_heap(candidates.begin(), candidates.end(), cmp);
            if(candidates.size() > k) {
                // pop the candidate with the largest distance
                std::pop_heap(candidates.begin(), candidates.end(), cmp);
                candidates.pop_back();
            }
    
            // the aabb of the sub-nodes
            unsigned int axis = depth % 3;
            CgAABB lower_aabb;
            CgAABB higher_aabb;
            std::tie(lower_aabb, higher_aabb) = aabb.split(axis, curr_point[axis]);
            // only push children if its aabb intersects with the 
            // neighbourhood bounding sphere 
            // (candidates is a max-heap with the first element being furthest away from query)
            double neighbourhood_radius = candidates[0].first;
            if(
                begin < begin + half_size
                && (lower_aabb.position[axis] + lower_aabb.extent[axis] >= query[axis] - neighbourhood_radius || k > candidates.size())
            ) {
                nodes.push_back({begin, begin + half_size, lower_aabb, depth + 1});
            }
            if(
                begin + half_size + 1 < end
                && (higher_aabb.position[axis] - higher_aabb.extent[axis] <= query[axis] + neighbourhood_radius || k > candidates.size())
            ) {
                nodes.push_back({begin + half_size + 1, end, higher_aabb, depth + 1});
            }
        }
    
        // copy all nearest neighbour indices we found into
        // a vector to return
        for(unsigned int i = 0; i < candidates.size(); i++)  erg[i] = candidates[i].second;
        return erg;
    }
    
    #endif // CGPOINTWRANGLER_H