adam7 said:
is something like that still possible with a simple ray-AABB collision algorithm or does it require something more advanced?
It should be very minor changes and probably no extra cost.
Here some code i'm using for the same purpose…
struct AABox
{
vec minmax[2]; // minimum and maximum corers of the axis aligned box
void DistanceRayFrontAndBackface (float &ffd, float& bfd, const vec& rayOrigin, const vec& rayInvDirection) const
{
vec t0 = vec(minmax[0] - rayOrigin).MulPerElem (rayInvDirection); // MulPerElem returns vec3(x*input.x, y*input.y, z*input.z);
vec t1 = vec(minmax[1] - rayOrigin).MulPerElem (rayInvDirection);
vec tMin = t0.MinPerElem (t1); // MinPerElem returns smaller value of both input vectors, individually for each dimension
vec tMax = t0.MaxPerElem (t1);
ffd = tMin.MaxElem(); // front face distance (behind origin if inside box) // MaxElem returns largest value of all dimensions
bfd = tMax.MinElem(); // back face distance
}
};
That's some standard slab test, and we could use it like this:
vec rD(1,0,0); // ray direction
vec rO(0); // ray origin
vec rID (1 / rD[0], 1 / rD[1], 1 / rD[2]); // we can replace zero with a small epsilon
float ffd, bfd;
aaBox.DistanceRayFrontAndBackface (ffd, bfd, rO, rID);
float t = (ffd > 0) ? ffd : bfd; // always the first intersection with a face
vec intersection = rO + rD * t;
float distance = t * rD.Length();
Here's some example where i use it to get a list of hovered editor nodes sorted by distance:
std::vector<Link> TraceNodes (Configuration &config, const AABox clippingBox,
const vec rO, const vec rD, const float rLength = FLT_MAX)
{
bool visContent = config.GetParamB("vis> content");
bool visNodes = config.GetParamB("vis> nodes");
bool visBoth = config.GetParamB("vis> both");
bool skipNonLayerNodes = config.GetParamB("vis> skipNonLayerNodes");
bool visMesh = config.GetParamB("vis> content meshes");
vec rID (1 / rD[0], 1 / rD[1], 1 / rD[2]); // todo: jitter required?
std::vector< std::pair<float, Link> > hits;
SceneTraversal traversal (this, Link(0, 0));
while (!traversal.Done())
{
Link nodeLink = traversal.CurrentNode();
const SceneNode* node = GetNode(nodeLink);
bool descend = node && clippingBox.TestBox(node->branchAABB);
if (descend)
{
float ffd, bfd;
node->branchAABB.DistanceRayFrontAndBackface (ffd, bfd, rO, rID);
descend = (ffd <= bfd);
}
if (descend)
{
float ffd, bfd;
AABox box = node->nodeAABB;
bool skip = (skipNonLayerNodes && node->voxelizationSettings.layer.Empty());
if (!skip)
{
bool hasContent =
(visMesh && node->contentType == ContentType::MESH_BRUSH) ||
(node->contentType == ContentType::ANALYTICAL_PRIMITIVE);
skip = true;
if ((visContent || visBoth) && hasContent)
skip = false;
if ((visBoth && !hasContent) || visNodes)
skip = false;
}
if (!skip)
{
box.DistanceRayFrontAndBackface (ffd, bfd, rO, rID);
if ((ffd <= bfd) & (ffd >= 0.0f) & (ffd <= rLength))
{
//ImGui::Text("trace %i %i: %i", nodeLink.index, nodeLink.storageID, descend);
box = GetLocalBB(nodeLink);
const matrix& m = GetMatrixFromWorld(nodeLink);
vec rOl = m.Transform(rO);
vec rDl = m.Rotate(rD);
float invLen = 1 / rDl.Length();
rDl *= invLen;
vec rIDl (1 / rDl[0], 1 / rDl[1], 1 / rDl[2]);
box.DistanceRayFrontAndBackface (ffd, bfd, rOl, rIDl);
if ((ffd <= bfd) & (ffd >= 0.0f) & (ffd <= rLength * invLen))
{
float t = (ffd > 0) ? ffd : bfd; // always the first intersection with a face
float dist = t * invLen;
//vec worldHit = rO + rD * dist;
//RenderPoint (worldHit, 0,1,0);
//RenderPoint (GetMatrixToWorld(nodeLink)[3], 1,0,0);
hits.push_back(std::make_pair(dist, nodeLink));
}
}
}
}
traversal.Traverse(descend);
}
struct Comparator {
bool operator() (const std::pair<float, Link>& left, const std::pair<float, Link>& right)
{
return left.first < right.first;
}
};
std::sort(hits.begin(), hits.end(), Comparator());
std::vector<Link> result (hits.size());
for (int i = 0; i < hits.size(); i++)
result[i] = hits[i].second;
return result;
}
};
Later i use mouse wheel to scroll forth and back in the list, so i can select a single object out of many intersections with some comfort.
EDIT:
There's a common pitfall when working with front/backface distance. The special case of having the ray origin inside the box often requires some rethinking.