Schnittpunktberechnung: Strahl schneidet Kugel/Ellipsoid

Gleißende Phasersalven und glühende Schutzschilde; vom mathematischen Standpunkt aus betrachtet sind Phasersalven Strahlen und glühende Schutzschilde Kugelschalen bzw. Ellipsoide, die von den Strahlen geschnitten werden.

Um einen (Phaser)-Strahl zu beschreiben, bedarf es seiner Abschussposition A, seiner Abschussrichtung Dir sowie seiner Strahllänge l:

P = A + l*Dir  (Geradengleichung)

Eine Kugeloberfläche wird durch alle Punkte P mit gleichem quadratischem Abstand (Kugelradius) vom Kugelmittelpunkt M aufgespannt:

(PM)2 = Kugelradius2  (Kugelgleichung)

Ein Ellipsoid lässt sich durch folgende Gleichung beschreiben:

((x-xMittelpunkt)/a)2 + ((y-yMittelpunkt)/b)2 + ((z-zMittelpunkt)/c)2 = 1  (Ellipsoidgleichung)

Trifft nun der Phaserstrahl den Schutzschild, dann kann man auf mathematischem Weg die zugehörige Strahllänge l sowie die Trefferposition bestimmen und mit diesen Informationen Strahl und Schild korrekt rendern sowie mit zusätzlichen Effekten (Schildbeleuchtung, Einschlagsanimation, usw.) versehen.

Für den Fall, dass der Strahl die Kugel (Ellipsoid) nicht nur streift, gibt es neben dem Eintrittspunkt immer auch einen zweiten Schnittpunkt, bei dem der Strahl wieder aus der Kugel (Ellipsoid) austritt. Normalerweise ist man jedoch nur am Eintrittspunkt interessiert.

Auf eine mathematische Herleitung der Schnittpunktgleichungen soll hier verzichtet werden, denn dafür gibt es genügend viele Mathematikbücher; aus Sicht des Spieleprogrammierers ist das Endergebnis relevant:


inline bool Sphere_Ray_Intersections(float* pIntersectionParam,
            D3DXVECTOR3* pRayDirection, D3DXVECTOR3* pRayStartPosition,
            D3DXVECTOR3* pSpherePosition, float* pSphereScaleSq)
{
    float a = pRayDirection->x*pRayDirection->x +
              pRayDirection->y*pRayDirection->y +
              pRayDirection->z*pRayDirection->z;

    D3DXVECTOR3 RayHelp = *pRayStartPosition - *pSpherePosition;

    float b = 2.0f*(RayHelp.x*pRayDirection->x +
                    RayHelp.y*pRayDirection->y +
                    RayHelp.z*pRayDirection->z);

    float c = RayHelp.x*RayHelp.x +
              RayHelp.y*RayHelp.y +
              RayHelp.z*RayHelp.z - *pSphereScaleSq;

    float det = b*b - 4.0f*a*c;

    if(det < 0.0f)
        return false;

    det = FastSqrtExact(det);

    a *= 2.0f;
    a = 1.0f/a;

    float s1 = (-b+det)*a;
    float s2 = (-b-det)*a;

    if( s1 < s2)
        *pIntersectionParam = s1;
    else
        *pIntersectionParam = s2;

    return true;
}


inline bool Ellipsoid_Ray_Intersections(float* pIntersectionParam,
            D3DXVECTOR3* pRayDirection, D3DXVECTOR3* pRayStartPosition,
            D3DXVECTOR3* pEllipsoidPosition,
            D3DXVECTOR3* pEllipsoidScaleSq)
{
    D3DXVECTOR3 RayHelp = *pRayStartPosition - *pEllipsoidPosition;

    float a = pRayDirection->x*pRayDirection->x/pEllipsoidScaleSq->x +
              pRayDirection->y*pRayDirection->y/pEllipsoidScaleSq->y +
              pRayDirection->z*pRayDirection->z/pEllipsoidScaleSq->z;

    float b = 2.0f*(RayHelp.x*pRayDirection->x/pEllipsoidScaleSq->x +
                    RayHelp.y*pRayDirection->y/pEllipsoidScaleSq->y +
                    RayHelp.z*pRayDirection->z/pEllipsoidScaleSq->z);

    float c = RayHelp.x*RayHelp.x/pEllipsoidScaleSq->x +
              RayHelp.y*RayHelp.y/pEllipsoidScaleSq->y +
              RayHelp.z*RayHelp.z/pEllipsoidScaleSq->z - 1.0f;

    float det = b*b - 4.0f*a*c;

    if(det < 0.0f)
        return false;

    det = FastSqrtExact(det);

    a *= 2.0f;
    a = 1.0f/a;

    float s1 = (-b + det)*a;
    float s2 = (-b - det)*a;

    if(s1 < s2)
        *pIntersectionParam = s1;
    else
        *pIntersectionParam = s2;

    return true;
}