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 \|}$
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}$
$\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$
$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:$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)}$
$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) }; }