openvdb icon indicating copy to clipboard operation
openvdb copied to clipboard

Expected output of firstActive function in HDDA.h

Open WeiPhil opened this issue 1 year ago • 0 comments

Hello, I am building a small voxel grid by building a nanovdb::Grid<nanovdb::FloatTree> with 1s in places where voxels are non-empty (as far as I understand bool or mask grids are not yet supported in nanovdb). To render, I then trace rays from the camera against the grid using the firstActive function in nanovdb/nanovdb/util/HDDA.h to get the index of the first active voxel intersected.

template<typename RayT, typename AccT>
inline __hostdev__ bool firstActive(RayT& ray, AccT& acc, Coord &ijk, float& t)
{
    if (!ray.clip(acc.root().bbox()) || ray.t1() > 1e20) {// clip ray to bbox
        return false;// missed or undefined bbox
    }
    static const float Delta = 1.0001f;// forward step-size along the ray to avoid getting stuck
    t = ray.t0();// initiate time
    ijk = RoundDown<Coord>(ray.start()); // first voxel inside bbox
    for (HDDA<RayT, Coord> hdda(ray, acc.getDim(ijk, ray)); !acc.isActive(ijk); hdda.update(ray, acc.getDim(ijk, ray))) {
        if (!hdda.step()) return false;// leap-frog HDDA and exit if ray bound is exceeded
        t = hdda.time() + Delta;// update time
        ijk = RoundDown<Coord>( ray(t) );// update ijk
    }
    return true;
}

If I output the normalized color of that outputed voxel's index in 3d space I obtain the following: firstActive

I found this other variant of first active intersection in an older github repo:

template<typename RayT, typename AccT>
inline __hostdev__ bool firstActiveVariant(RayT& iRay, AccT& acc, Coord &ijk, float& t)
{
    using TreeT = nanovdb::NanoTree<float>;
    
    TreeMarcher<TreeT::LeafNodeType, RayT, AccT> marcher(acc);
    if (marcher.init(iRay)) {
        const TreeT::LeafNodeType *node = nullptr;
        float t0 = 0, t1 = 0;

        while (marcher.step(&node, t0, t1)) {
            DDA<RayT> dda;
            dda.init(marcher.ray(), t0, t1);
            do {
                ijk = dda.voxel();
                // CAVEAT:
                // This is currently necessary becuse the voxel returned might not actually be innside the node!
                // This is currently happening from time to time due to float precision issues,
                // so we skip out of bounds voxels here...
                auto localIjk = ijk - node->origin();
                if (localIjk[0] < 0 || localIjk[1] < 0 || localIjk[2] < 0 || localIjk[0] >= 8 || localIjk[1] >= 8 || localIjk[2] >= 8)
                    continue;

                const uint32_t offset = node->CoordToOffset(ijk);
                if (node->isActive(offset)){
                    t = dda.time();
                    return true; 
                }
            } while (dda.step());
        }
    }
    return false;
}

This one gives me the expected result but seems to be quite expensive and has a CAVEAT note that doesn't seem very good either. firstActiveVariant

The input to both functions is exactly the same, namely, the world_ray transformed into index space using the ray's worldToIndexF function and the device float grid accessor.

Is this a bug in the firstActive function or am I using it the wrong way?

Let me know if you need any more details. Best, Philippe

WeiPhil avatar Oct 21 '22 09:10 WeiPhil