AcademySoftwareFoundation / AcademySoftwareFoundation/openvdb
Expected output of firstActive function in HDDA.h
- Dominant language
- C++
- Stars
- 3.4k
- Forks
- 774
- Avg merge
- 3d 9h
- Merged PRs (30d)
- 34
Description
Hello,
I am building a small voxel grid by building a `nanovdb::Grid` 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.
```C++
template
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(ray.start()); // first voxel inside bbox
for (HDDA 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( 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:

I found this other variant of first active intersection in an older github repo:
```C++
template
inline __hostdev__ bool firstActiveVariant(RayT& iRay, AccT& acc, Coord &ijk, float& t)
{
using TreeT = nanovdb::NanoTree;
TreeMarcher marcher(acc);
if (marcher.init(iRay)) {
const TreeT::LeafNodeType *node = nullptr;
float t0 = 0, t1 = 0;
while (marcher.step(&node, t0, t1)) {
DDA 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.

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
Contributor guide
Research direction
Begin with nanovdb/nanovdb/util/HDDA.h and inspect firstActive alongside the older firstActiveVariant shown in the issue. Reproduce the differing voxel indices with the same world_ray transformed by worldToIndexF and the device float grid accessor; done means determining whether the discrepancy is a bug or incorrect use and recording the confirmed expected behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- computer-graphics
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100