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=‖
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) }; }