Processing math: 100%

2011-07-12

Intersekcja promienia ze sferą

Rysunek pierwszy przedstawia sytuację gdy promień uderza w zewnętrzną część sfery. Rysunek drugi przedstawia sytuację gdy promień uderza w wnętrze sfery. Oba rysunki to przekroje w płaszczyźnie SOA.

Oznaczenia na rysunkach:

d - kierunek promienia, zakładamy, że wektor ten jest znormalizowany
S - punkt wyjścia promienia
O - środek sfery
A - punkt w którym prosta promienia i promień sfery przecinają się pod kątem prostym
c=SO

image/svg+xml d S O P r m k c v=k+m h A

image/svg+xml S k=v+m d O P r m v c h A

Oba przypadki wymagają osobnego rozpatrzenia.

Przypadek 1

Warunek: c>r

Mamy pewność, że punkt S leży na zewnątrz sfery. Teraz sprawdźmy, czy wogóle zmierza w jej kierunku. Badamy znak dc, który jest tożsamy ze znakiem cosinusa kąt pomiędzy tymi wektorami. Jeśli dSO<=0 to promień nigdy nie przetnie się ze sferą. Długość wektora v będącego rzutem wektora c na d. Ponieważ wektor d jest znormalizowany to

v=dSO

c2+h2=v2

h=c2v2
Jeśli h>r to sfera nie przetnie się z promieniem. Czyli mamy kolejny warunek tym razem już ostatecznie pozwalający stwierdzić czy mamy przecie afery z promieniem:

c2v2<r
Teraz pozostaje nam wyznaczyć punkt przecięcia.

m2+h2=r2

m=r2h2

m=r2(c2v2)

k=vm

P=S+dk

Przypadek 2

Warunek: c<r Mamy pewność, że punkt S leży wewnątrz sfery.

Podobnie wyliczamy:

v=dc

v2+h2=c2

h=c2v2

m2+h2=r2

m=r2h2

m=r2(c2v2)
I teraz inaczej niż poprzednio:

k=v+m
I znów podobnie:

P=S+dk

Ponowne uderzenie w sfere przez promień odbity albo załamay

Pozostaje do wyjaśnienia przypadek cr - ponieważ operujemy na liczbach zmiennoprzecinkowych.

Istnieją dwa sposoby radzenia sobie z sytuacją. W pierwszym przesuwamy punkt startu wzdłuż wektora d o niewielką wartość, która pozwala nam przezwyciężyć błąd obliczeniowy. Niestety o ile przy odbiciu od zewnętrznej części sfery promienia metoda ta działa dobrze, o ile przy odbiciu wewnętrznym może ona nam w skrajnych przypadkach przesunąć punkt startu na zewnątrz sfery, co jest ewidentnym błędem. Poza tym ustalenie wartości przesunięcia też jest dosyć problematyczne. Musi być ściśle zgrane z odstępami pomiędzy obiektami i ich wymiarami. Wymiary obiektów ani odstępy pomiędzy nimi nie mogą być mniejsze niż pewna graniczna wartość tego przesunięcia. Samo przesunięcie musi być większe niż błąd obliczeniowy i tutaj jest też duży problem jeśli chcemy by wartość ta była naprawdę minimalna.

W drugiej metodzie pamiętamy w co i z której strony ostatnio uderzyliśmy. Dzięki temu dokładnie kontrolujemy sytuację. Jeśli uderzenie nastąpiło z zewnątrz to promień odbity nie uderzy ponownie w sferę. Jeśli uderzenie nastąpiło od środka, to promień odbity może uderzyć w sferę. Całkiem podobnie jest dla promienia załamanego.

Przykład implementacji w C#

public override Intersection GetIntersection(Ray a_ray)
{
    bool backHit = false;

    Vector3 c = Pos - a_ray.Start;
    double c2 = c.Length * c.Length;
    double r2 = Radius * Radius;
            
    double dist;

    if (a_ray.PrevHit == this)
    {
        if (a_ray.PrevBackHit)
        {
            backHit = true;

            if (OneSide)
                return Scene.NoIntersection;
            else
            {
                double v = c * a_ray.Dir;
                double m2 = r2 - (c2 - v * v);

                dist = v + Math.Sqrt(m2);
            }
        }
        else
            return Scene.NoIntersection;
    }
    else
    {
        double v = c * a_ray.Dir;
        double m2 = r2 - (c2 - v * v);

        if (c2 > r2)
        {
            if (v <= 0)
                return Scene.NoIntersection;
            else
            {
                if (m2 < 0)
                    return Scene.NoIntersection;
                else
                    dist = v - Math.Sqrt(m2);
            }
        }
        else
        {
            backHit = true;

            if (OneSide)
                return Scene.NoIntersection;
            else
                dist = v + Math.Sqrt(m2);
        }
    }

    return new Intersection()
    {
        SceneObject = this,
        Ray = a_ray,
        Dist = dist,
        Scene = Scene,
        BackHit = backHit,
        Pos = a_ray.HitPoint(dist)
    };
}

Brak komentarzy:

Prześlij komentarz