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:

$\mathbf{\vec 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
$\mathbf{c=\left \| \overrightarrow{SO}\right \|}$

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: $\mathbf{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 $\mathbf{\vec d \vec c}$, który jest tożsamy ze znakiem cosinusa kąt pomiędzy tymi wektorami. Jeśli $\mathbf{\vec d \cdot \vec {SO}}<=0$ to promień nigdy nie przetnie się ze sferą. Długość wektora $\mathbf{\vec v}$ będącego rzutem wektora $\mathbf{\vec c}$ na $\mathbf{\vec d}$. Ponieważ wektor $\mathbf{\vec d}$ jest znormalizowany to

$v=\vec d \cdot \overrightarrow{SO}$

$c^2+h^2=v^2$

$h=\sqrt{c^2-v^2}$
Jeśli $\mathbf{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:

$\sqrt{c^2-v^2}<r$
Teraz pozostaje nam wyznaczyć punkt przecięcia.

$m^2+h^2=r^2$

$m=\sqrt{r^2-h^2}$

$m=\sqrt{r^2-(c^2-v^2)}$

$k=v-m$

$P=S+\vec d \cdot k$

Przypadek 2

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

Podobnie wyliczamy:

$v=\vec d \vec c$

$v^2+h^2=c^2$

$h=\sqrt{c^2-v^2}$

$m^2+h^2=r^2$

$m=\sqrt{r^2-h^2}$

$m=\sqrt{r^2-(c^2-v^2)}$
I teraz inaczej niż poprzednio:

$k=v+m$
I znów podobnie:

$P=S+\vec d \cdot k$

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

Pozostaje do wyjaśnienia przypadek $\mathbf{c \approx r}$ - ponieważ operujemy na liczbach zmiennoprzecinkowych.

Istnieją dwa sposoby radzenia sobie z sytuacją. W pierwszym przesuwamy punkt startu wzdłuż wektora $\mathbf{\vec 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