some rand
some rand

Reputation: 33

Ray vs ellipsoid intersection

I am trying to implement ray vs ellipsoid intersection by "squishing" space and doing ray vs sphere:

  1. create mat3 S with ellipsoid radius at diagonal

  2. squish ray by multiplying start and direction by an inverse of S

  3. intersect ray with sphere of radius 1.0 in local space

  4. multiply hitPoint by S to unsquish it.

Here is ray vs sphere:

float P = glm::dot(dir, sphereCenter-start);
float L = glm::distance(start, sphereCenter);
float d = sqrt(L*L - P*P);
if (d < radius) {
    float x0 = sqrt(1.f - d*d);
    hitPoint = start + dir*(P - x0);
    hitNormal = glm::normalize(hitPoint - sphereCenter);
}
else if (d == radius) {
    hitPoint = start + dir*P;
    hitNormal = glm::normalize(hitPoint - sphereCenter);
}
else {
    return false;
}
if (glm::distance(start, hitPoint) > dist) return false;
return true;

Here is the squishing part:

glm::vec3 S = start;
    glm::vec3 Dir = dir;

    auto sphereCenter = thisEntity()->transform()->getPosition();
    auto scale = thisEntity()->transform()->getScale();

    glm::mat3 q = glm::mat3(0);
    float x = _radius.x * scale.x;
    float y = _radius.y * scale.y;
    float z = _radius.z * scale.z;
    q[0][0] = x;
    q[1][1] = y;
    q[2][2] = z;
    glm::mat3 qI = glm::inverse(q);

    S = qI * S;
    Dir = qI * Dir;

    //calculate hit point in world space squished
    glm::vec3 hitPoint, hitNormal;
    if (!IntersectionsMath::instance()->segmentVsSphere(sphereCenter, S, Dir, dist, 1.f, hitPoint, hitNormal)) return;

    hitPoint = q * hitPoint;

    hit.pushHit(hitPoint, hitNormal, this);

Current ray sphere code is for world position, i'm trying to make it work at the origin so it shouldn't matter. Ray vs regular sphere works fine, ellipsoid is the problem. I spent a lot of time on this and something somewhere is wrong.

Upvotes: 0

Views: 2959

Answers (1)

meowgoesthedog
meowgoesthedog

Reputation: 15035

Problem:

The center of scaling matters.

Solution:

Perform the scaling about the center of the ellipsoid.

... and not the origin as you are doing right now. This is because, although the direction of the ray will be the same (it is just a directional vector), the relative displacement between the scaled source and center of the sphere will be different:

  • Scaling about origin (current code):

    Source S' = qI * S, center C' = qI * C --- S' - C' = qI * (S - C)

  • Scaling about ellipsoid center (correct procedure):

    Source S" = qI * (S - C), center C" = C --- S" - C" = qI * (S - C) - C

The two displacements differ by the position of the original ellipsoid; thus your current ray will likely miss / give false positives.


Corrected code:

// scale about the ellipsoid's position by subtracting before multiplying
// more appropriate name would be "ellipseCenter" to avoid confusion
S_ = qI * (S - sphereCenter);

// this ::normalize should really be in the intersection function
Dir_ = glm::normalize(qI * Dir); 

// calculate hit point in world space squished
// ... but around the origin in the squashed coordinate system
glm::vec3 hitPoint, hitNormal;
if (!IntersectionsMath::instance()->segmentVsSphere(
          glm::vec3::ZERO, S_, Dir_,
          dist, 1.f,
          hitPoint, hitNormal)) return;

// re-apply the offset
hitPoint = q * hitPoint + sphereCenter

// problem: hitNormal will not be correct for the ellipsoid when scaled
// solution: divide through each component by square of respective semi-axis
// (will provide proof upon request)
hitNormal.x /= (x * x); hitNormal.y /= (y * y); hitNormal.z /= (z * z);

Upvotes: 2

Related Questions