Search code examples
c#c++unity-game-engineapproximate-nn-searching

Attempting to implement a C++ library, need some pointers on how to interface with it


I'm relatively inexperienced in C++, and am trying to compile a library to DLL for use in Unity. As far as generating the DLL and interfacing with it, I've had success in getting my below function to call and return a dummy value in Unity, no worries.

Where I'm struggling is working out how to interface with this Approximate Nearest Neighbour library, and could use some pointers. (This may be a hard problem to define without downloading the above-linked source and having a look yourself, but I will attempt to make it simple)

In Unity, I have a three-dimensional array (points[] = float[x,y,z]), and a query point in space (float[x,y,z]). I wish to feed this data into something like the below function (which I have modified from the ann_sample.cpp - original linked at the bottom), and return the nearest neighbour to this point.

My function

int myInit(float x, float y, float z, int numPoints, int maxPoints, int nn)
{
    int                 nPts;                   
    ANNpointArray       dataPts;                
    ANNpoint            queryPt;                
    ANNidxArray         nnIdx;                  
    ANNdistArray        dists;                  
    ANNkd_tree* kdTree;                         

    int dimension = 3;
    
    queryPt = annAllocPt(dimension , x);                        //This only expects the dimension, and a coord - I assume this is pre-allocation? 
    dataPts = annAllocPts(maxPoints, dimension);    //Again, this does not expect the coordinate (xyz) data, is pre-allocating the array?
    nnIdx = new ANNidx[nn];                         // more pre-allocating?
    dists = new ANNdist[nn];                        // more pre-allocating?

    nPts = numPoints;           
                        
    //Should we be populating the actual queryPt and dataPts here?
    //at the moment, these are still empty?

    kdTree = new ANNkd_tree(                    // build search structure
        dataPts,                                // the data points
        nPts,                                   // number of points
        dimension);                             // dimension of space


    kdTree->annkSearch(                     // search
            queryPt,                        // query point
            nn,                             // number of near neighbors
            nnIdx,                          // nearest neighbors (returned)
            dists,                          // distance (returned)
            eps);                           // error bound
    
    annClose();                                 // done with ANN

    return nnIdx[0];  //this is the nearest neighbor  
}

Original Function (This is expecting a command-line input - i.e. ann_sample [-d dim] [-max mpts] [-nn k] [-e eps] [-df data] [-qf query])

int main(int argc, char **argv)
{
    int                 nPts;                   // actual number of data points
    ANNpointArray       dataPts;                // data points
    ANNpoint            queryPt;                // query point
    ANNidxArray         nnIdx;                  // near neighbor indices
    ANNdistArray        dists;                  // near neighbor distances
    ANNkd_tree*         kdTree;                 // search structure

    getArgs(argc, argv);                        // read command-line arguments

    queryPt = annAllocPt(dim);                  // allocate query point
    dataPts = annAllocPts(maxPts, dim);         // allocate data points
    nnIdx = new ANNidx[k];                      // allocate near neigh indices
    dists = new ANNdist[k];                     // allocate near neighbor dists

    nPts = 0;                                   // read data points

    cout << "Data Points:\n";
    while (nPts < maxPts && readPt(*dataIn, dataPts[nPts])) {
        printPt(cout, dataPts[nPts]);
        nPts++;
    }

    kdTree = new ANNkd_tree(                    // build search structure
                    dataPts,                    // the data points
                    nPts,                       // number of points
                    dim);                       // dimension of space

    while (readPt(*queryIn, queryPt)) {         // read query points
        cout << "Query point: ";                // echo query point
        printPt(cout, queryPt);

        kdTree->annkSearch(                     // search
                queryPt,                        // query point
                k,                              // number of near neighbors
                nnIdx,                          // nearest neighbors (returned)
                dists,                          // distance (returned)
                eps);                           // error bound

        cout << "\tNN:\tIndex\tDistance\n";
        for (int i = 0; i < k; i++) {           // print summary
            dists[i] = sqrt(dists[i]);          // unsquare distance
            cout << "\t" << i << "\t" << nnIdx[i] << "\t" << dists[i] << "\n";
        }
    }
    delete [] nnIdx;                            // clean things up
    delete [] dists;
    delete kdTree;
    annClose();                                 // done with ANN

    return EXIT_SUCCESS;
}

As you can see in my comments, I am having trouble determining where and how I might actually feed the input data in, that is - my point array mentioned above.

It appears the data is being populated (in the reference function) with the below code, by utilizing an "istream" class, but I do not understand how this is working, or how I would emulate it.

    while (nPts < maxPts && readPt(*dataIn, dataPts[nPts])) {
        printPt(cout, dataPts[nPts]);
        nPts++;
    }

... 

bool readPt(istream &in, ANNpoint p)            // read point (false on EOF)
{
    for (int i = 0; i < dim; i++) {
        if(!(in >> p[i])) return false;
    }
    return true;
}

Solution

  • Update:
    Writing this all out was evidently the push I needed in the right direction! In case it's relevant for anyone in the future, the manner through which I interfaced with it was fairly straightforward.

    I passed a few 2D arrays into the C++ plugin, which I then converted into ANNpointArrays. Given it's C++, I needed to first instantiate the arrays of the appropriate size, then populate as the below:

        float qPoints[][3]
    
        .....
    
        for (int i = 0; i < nQueryPts; i++) {
            for (int j = 0; j < dimension; j++)
            {
                queryPts[i][j] = qPoints[i][j];
            }
        }
    

    Once I had this, it was relatively simple to run an annKSearch on each of my query points, returning an array of integers for Nearest Neighbour, and floats for distances. I needed to learn a few things about how to pass and instantiate arrays in C++, but it was a lot easier than I thought!

    Not that anyone's reading it - but this has been a useful exercise for me, and I'm leaving the question up just in case someone else is trying to implement this library in C#!