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

Wyznacznik macierzy

Wyznacznik macierzy istnieje tylko dla macierzy kwadratowych. Wyznacznik macierzy przyporządkowuje dowolnej macierzy $\mathbf{n\times n}$ liczbę.

Wyznacznik oznaczamy:

$\det M=\begin{vmatrix}m_{11} & m_{21} & \cdots & m_{n1} \\m_{12} & m_{22} & \cdots & m_{n2} \\\vdots & \vdots & \ddots & \vdots \\m_{1n} & m_{2n} & \cdots & m_{nn}\end{vmatrix}$

Definicja wyznacznika:

$\det M=\sum_{i=1}^n(-1)^{i+j}m_{ij}\det M_{i, j}$
Jest to definicja rekurencyjna. $\mathbf{M_{i, j}}$ oznacza macierz powstałą przez skreślenie i-tego wiersza i j-tej kolumny.

Dla macierzy $\mathbf{2\times 2}$:

$\begin{vmatrix} a & b\\c & d \end{vmatrix}=ad - bc$
W interpretacji geometrycznej wartość bezwzględna wyznacznika macierzy $\mathbf{2\times 2}$ to pole rombu, którego dwa boki to wektory wyznaczane przez kolumny lub wiersze macierzy.

Dla macierzy $\mathbf{3\times 3}$:

$\begin{vmatrix}a&b&c\\d&e&f\\g&h&i\end{vmatrix} = aei + bfg + cdh - afh - bdi - ceg$
W interpretacji geometrycznej wartość bezwzględna wyznacznika macierzy $\mathbf{3\times 3}$ to pole równoległościanu, którego trzy boki to wektory wyznaczane przez kolumny lub wiersze macierzy.

Warto zauważyć, że są to sumy iloczynów elementów po przekątnych z odpowiednim znakiem. Niestety dla wyższych wymiarów nie jest już tak prosto.

Właściwości

  • $\det M = \det M^T$
  • zmiana miejscami dwóch dowolnych kolumn lub wierszy zmienia tylko znak wyznacznika
  • jeśli dwa wiersze lub kolumny są do siebie proporcjonalne to wyznacznik macierzy jest zerowy
  • Pomnożenie dowolnej kolumny lub wiersza przez stałą zwiększa wyznacznik razy ta stała
  • Jeśli jakiś wiersz jest kombinacją liniową innych wierszy (np. wiersz składa się tylko z zer), wyznacznik ma wartość zero. To samo dotyczy kolumn
  • Dodając lub odejmując od dowolnego wiersza/kolumny inny wiersz/kolumnę lub kombinacje liniowe innych wierszy/kolumn nie zmieniamy wartości wyznacznika
  • $\det M^{-1}=\displaystyle\frac{1}{\det M}$
  • $\det kM_{n \times n}=k^n \det M$
  • $\det A \cdot \det B = \det (A \cdot B)$
Większość tych właściwości ma znaczenie przy rozwiązywaniu układu równań. Ale co niektóre możemy odnieść do sytuacji w której kolumny macierzy wyznaczają osie nowego układu współrzędnych do którego dokonujemy transformacji.

Wektor - informacje podstawowe

Długość wektora

Długośc wektora 2D: $\left \| \vec V \right \| = \sqrt{v_x^2+v_y^2}$

Długośc wektora 3D: $\left \| \vec V \right \| = \sqrt{v_x^2+v_y^2+v_z^2}$

Wektor jednostkowy

Wektor jednostkowy to wektor o długości 1. Szczególnym przypadkiem wektorów jednostkowych są wektory wyznaczające osie układu współrzędnych. Dla przestrzeni trójwymiarowej mają one postać:

$\vec x=\begin{bmatrix}1\\0\\0\end{bmatrix}\;\;\;
\vec y=\begin{bmatrix}0\\1\\0\end{bmatrix}\;\;\;
\vec z=\begin{bmatrix}0\\0\\1\end{bmatrix}$

Normalizacja wektora

Wektor znormalizowany oznaczamy przez $\hat V$. Ma on taki sam kierunek i zwrot jak wektor $\vec V$. Z tym, że jego długość to: $\left \| \vec V \right \| =1$.

Zapiszmy wektor znormalizowany jako: $\hat V = a \vec V=\left [ a v_x,a v_y,a v_z\right ]$, gdzie a jest tak dobrane, że $\left \| \vec V \right \| =1$.

$\left \| \vec V \right \| =1$

$\sqrt{a v_x^2+a v_y^2+a v_z^2}=1$
$a=\displaystyle\frac{1}{\sqrt{v_x^2+v_y^2+v_z^2}}$
Czyli wektor znormalizowany dowolnego wektora ma postać:

$\hat V =\displaystyle\frac{\vec V}{\left \| \vec V \right \|}$
O takich rzeczach jak dodawanie, odejmowanie, mnożenie przez skalar, moduł, zwrot, kierunek, punkt zaczepienia nie będę tutaj pisać.

2011-07-11

Rotacja wokół arbitrarnej osi.

Musimy znaleźć macierz obracającą o kąt $\mathbf{\theta}$ wokół wektora $\mathbf{\vec A(u,v,w)}$.

Zakładamy, że nasz układ współrzędnych jest lewoskrętny.

Macierz wynikowa będzie złożeniem 5 macierzy:
  1. obrót wokół wybranej osi sprowadzający wektor $\mathbf{\vec A}$ do płaszczyzny osi układu współrzędnych (np. wokół osi Z do płaszczyzny XZ)
  2. obrót wokół osi układu współrzędnych tak by sprowadzić wektor $\mathbf{\vec A}$ do jednej z osi układ współrzędnych (np. wokół osi Y do osi Z)
  3. obrót wokół tej osi (tym samym wokół wektora w jego lokalnym układzie współrzędnych od którego sprowadziliśmy go obrotami) o kąt $\mathbf{\theta}$
  4. obrót odwrotny do 2
  5. obrót odwrotny do 1
Zróbmy dokładnie tak jak jest napisane w nawiasach. Jednakże możemy wybrać dowolnie inne osie układu współrzędnych jako osie obrotu, wynik musi być ten sam.

image/svg+xml x y P α z v w u
Najpierw robimy obrót o kąt $\mathbf{-\alpha}$ wokół osi Z sprowadzając wektor $\mathbf{\vec A}$ do płaszczyzny XY.

$\alpha=-\arctan(\displaystyle\frac{v}{u})$
image/svg+xml x y P β z w s
Teraz robimy obrót o kąt $\mathbf{-\beta}$ wokół osi Y sprowadzając wektor $\mathbf{\vec A}$ do osi Z.

$s=\sqrt{v^2+u^2}$
$\beta=-arctan(\displaystyle\frac{s}{w})$
Teraz nie pozostaje nam nic innego jak dokonać obrotu o kąt $\mathbf{\theta}$ wokół osi Z.

Kompletna macierz ma postać:

$M=M_Z^{-1}(\alpha)M_Y^{-1}(\beta)M_Z(\theta)M_{Y}(\beta)M_Z(\alpha)$
Zakładając, że wektor $\mathbf{\vec A}$ jest wektorem jednostkowym (co znacząco upraszcza i przyspiesza obliczenia), po wytrwałym mnożeniu powinniśmy dostać:

$t=1-cos\theta$

$M=\begin{bmatrix}
tuu+cos\theta & tuv-wsin\theta & tuw+vsin\theta & 0\\
tuv+wsin\theta & tvv+cos\theta & tvw-usin\theta & 0 \\
tuw-vsin\theta & tvw+usin\theta & tww+cos\theta & 0 \\
0 & 0 & 0 & 1
\end{bmatrix}$
Przykład w C# prosto z definicji:
public static Matrix4 CreateRotateAroundVector(Vector3 a_rotate_around,
    double a_angle)
{
    a_rotate_around = a_rotate_around.Normalized;

    double u = a_rotate_around.X;
    double v = a_rotate_around.Y;
    double w = a_rotate_around.Z;

    var m_alpha = Matrix4.CreateRotateZ(-Math.Atan2(v, u));
    var m_beta = Matrix4.CreateRotateY(
        -Math.Atan2(Math.Sqrt(v*v + u*u), w)));
    var m_angle = Matrix4.CreateRotateZ(a_angle);

    return m_alpha.Inverted * m_beta.Inverted * m_angle * m_beta * m_alpha;
}
I wersja szybka:
public static Matrix4 CreateRotateAroundVector(Vector3 a_rotate_around,
    double a_angle)
{
    a_rotate_around = a_rotate_around.Normalized;

    double cos = Math.Cos(a_angle);
    double sin = Math.Sin(a_angle);
    double t = 1 - cos;

    double u = a_rotate_around.X;
    double v = a_rotate_around.Y;
    double w = a_rotate_around.Z;

    return new Matrix4(

        t * u * u + cos,
        t * u * v - sin * w,
        t * u * w + sin * v, 
        0, 

        t * u * v + sin * w,
        t * v * v + cos,
        t * v * w - sin * u,
        0, 

        t * u * w - sin * v,
        t * v * w + sin * u,
        t * w * w + cos, 
        0, 

        0, 0, 0, 1 );
}

Macierz rotacji

Macierz rotacji pozwala nam na rotacje punktu wokół środka układu współrzędnych wokół wybranej osi układu współrzędnych. Dla 3D ma ona wymiary 3x3. Dla 2D 2x2. Rotacja punktu wokół środka układu współrzędnych nie zmienia odległości punktu od tego środka.

Wyznacznik takiej macierzy równy jest 1. Co wynika z faktu, że jej kolumny traktowane jako wektory jednostkowe tworzą nowy układ współrzędnych. Dowolne składanie (mnożenie) macierzy rotacji nie zmienia tej własności.

Specyficznym przykładem macierzy rotacji jest macierz jednostkowa, która nie zmienia położenia obracanego obiektu.

Macierz odwrotna do macierzy rotacji też jest macierzą rotacji. Macierz odwrotna do macierzy rotacji jest rotacją odwrotną.

Powyższe stwierdzenia są także prawdziwe dla złożeń rotacji i translacji. Skalowanie zmienia jedynie to, że kolumny nie są wektorami jednostkowymi.

Zakładamy, że nasz układ współrzędnych jest lewoskrętny.

Macierz rotacji 3D wokół osi Z

Mamy wektor $\mathbf{\vec V(v_x,v_y,v_z)}$, który obracamy o kąt $\mathbf{\alpha}$ wokół osi Z. Współrzędna $\mathbf{v_z}$ nie ulega zmiania. Obrotowi podlega rzut wektora $\mathbf{\vec V}$ na płaszczyznę XY. Oznaczmy go przez $\mathbf{\vec R(v_x,v_y)}$.
image/svg+xml x y θ R R' α xy xy
Możemy zapisać:

$\left\{\begin{matrix}
v'_x=\left \| R \right \| \cos(\theta+ \alpha)\\
v'_y=\left \| R \right \| \sin(\theta+ \alpha)
\end{matrix}\right.$

$\left\{\begin{matrix}
v'_x=\left \| R \right \|(\cos \theta \cos \alpha - \sin \theta \sin \alpha)\\
v'_y=\left \| R \right \|(\sin \theta \cos \alpha + \cos \theta \sin \alpha)
\end{matrix}\right.$

$\left\{\begin{matrix}
v'_x=\left \| R \right \|(\displaystyle\frac{v_x}{\left \| R \right \|}\cos \alpha - \frac{v_y}{\left \| R \right \|}\sin \alpha)\\
v'_y=\left \| R \right \|(\displaystyle\frac{v_y}{\left \| R \right \|}\cos \alpha + \frac{v_x}{\left \| R \right \|}\sin \alpha)
\end{matrix}\right.$

$\left\{\begin{matrix}
v'_x=v_x \cos \alpha - v_y \sin \alpha\\
v'_y=v_y \cos \alpha + v_x \sin \alpha
\end{matrix}\right.$
Co możemy zapisać jako jako:

$\begin{bmatrix}
v'_x\\
v'_y
\end{bmatrix}=\begin{bmatrix}
\cos \alpha & -sin \alpha \\
\sin \alpha & \cos \alpha \\
\end{bmatrix}\;\;
\begin{bmatrix}
v_x \\
v_y \\
\end{bmatrix}$
Stąd macierz rotacji 2D:

$R_{2D}=\begin{bmatrix}
\cos \alpha & -sin \alpha \\
\sin \alpha & \cos \alpha
\end{bmatrix}$
Przechodząc na 3D:

$\left\{\begin{array}{lcccccc}
v'_x & = & v_x \cos \alpha &-& v_y \sin \alpha &+& v_z \cdot 0\\
v'_y & = & v_y \cos \alpha &+& v_x \sin \alpha &+& v_z \cdot 0\\
v'_z & = & v_x \cdot 0 &+& v_y \cdot 0 &+& v_z \cdot 1
\end{array}\right.$
Stąd macierz rotacji 3D wokół osi Z:

$R_Z=\begin{bmatrix}
\cos \alpha & -\sin \alpha & 0 \\
\sin \alpha & \cos \alpha & 0 \\
0 & 0 & 1
\end{bmatrix}$

Macierz rotacji 3D wokół osi Y

Kąt $\mathbf{\theta}$ mierzymy od osi $\mathbf{Z}$. Czyli zamieniamy: $\mathbf{X \rightarrow Z}$, $\mathbf{Y \rightarrow X}$, $\mathbf{Z \rightarrow Y}$

W macierzy rotacji $\mathbf{R_Z}$ wymieniamy odpowiednio kolumny i wiersze. Zmian możemy dokonać także wcześniej w układzie trzech równań i wyprowadzić z nich nową macierz. Możemy także zacząć od samego początku. Powinniśmy otrzymać:

$R_Y=\begin{bmatrix}
\cos \alpha & -\sin \alpha & 0 \\
\sin \alpha & \cos \alpha & 0 \\
0 & 0 & 1
\end{bmatrix}$

Macierz rotacji 3D wokół osi X

Kąt $\mathbf{\theta}$ mierzymy od osi $\mathbf{Y}$. Czyli zamieniamy: $\mathbf{X \rightarrow Y}$, $\mathbf{Z \rightarrow X}$, $\mathbf{Y \rightarrow Z}$. Macierz rotacji ma postać:

$R_X=\begin{bmatrix}
1 & 0 & 0 \\
0 & \cos \alpha & -\sin \alpha \\
0 & \sin \alpha & \cos \alpha
\end{bmatrix}$

Przykład wyznaczania macierzy rotacji 4x4 w C#

public static Matrix4 CreateRotateX(double a_angle)
{
    double sin = Math.Sin(a_angle);
    double cos = Math.Cos(a_angle);

    return new Matrix4(

        1, 0, 0, 0, 
        0, cos, -sin, 0, 
        0, sin, cos, 0,
        0, 0, 0, 1 );
}

public static Matrix4 CreateRotateY(double a_angle)
{
    double sin = Math.Sin(a_angle);
    double cos = Math.Cos(a_angle);

    return new Matrix4(

        cos, 0, sin, 0, 
        0, 1, 0, 0, 
        -sin, 0, cos, 0, 
        0, 0, 0, 1 );
}

public static Matrix4 CreateRotateZ(double a_angle)
{
    double sin = Math.Sin(a_angle);
    double cos = Math.Cos(a_angle);

    return new Matrix4(

        cos, -sin, 0, 0, 
        sin, cos, 0, 0, 
        0, 0, 1, 0, 
        0, 0, 0, 1 );
}

Właściwości macierzy rotacji

  • $R^T=R^{-1} \Leftrightarrow R^TR=I$
  • $R_\alpha R_\beta=R_{\alpha+\beta}$
  • $\det R=1$
  • $R_{alpha=0}=I$
  • Traktując kolumny lub wiersze jako wektory ich długości są równe 1.
  • Wektory kolumn lub wierszy są do siebie wzajemnie prostopadłe.

2011-07-10

Mnożenie macierzy i wektora

Mnożenia możemy dokonać na dwa sposoby:

$\begin{bmatrix}
1 & 0 & 0 & t_x \\
0 & 1 & 0 & t_y \\
0 & 0 & 1 & t_z \\
0 & 0 & 0 & 1 \\
\end{bmatrix}
\;\;\;
\begin{bmatrix} v_x \\ v_y \\ v_z \\ 1 \\\end{bmatrix}
=
\begin{bmatrix} v_x+t_x \\ v_y+t_y \\ v_z+t_z \\ 1 \\\end{bmatrix}$

$\begin{bmatrix} v_x & v_y & v_z & 1 \\\end{bmatrix}
\;\;\;
\begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 1 & 0 & 0 \\
0 & 0 & 1 & 0 \\
t_x & t_y & t_z & 1 \\
\end{bmatrix}
=
\begin{bmatrix} v_x+t_x & v_y+t_y & v_z+t_z & 1 \\\end{bmatrix}
$

Warto przyjąć jedną konwencję i się jej trzymać. Nie tylko chodzi o pozycję elementów translacji i rotacji w macierzy, ale także o jak mnożymy wektor i macierz. Jeśli przyjmujemy, że wektor jest kolumnowy to dopuszczamy tylko mnożenie macierz razy wektor. Nie tworzymy odwrotnie przeładowanych metod, operatorów. Takie mnożenie matematycznie jest niepoprawne. Oczywiście możemy je potraktować matematycznie jako mnożenie macierz razy wektor. Ale wprowadzamy tym zamieszanie.

Należy przyjąć jedną konwencję i się trzymać. W różnych systemach bywa to różnie. Podobnie jak ze skrętnością układu współrzędnych.

Najlepiej nie używać bezpośrednio elementów macierzy, ale polegać na wyspecjalizowanych metodach.

Macierz translacji z pierwszego przykładu jest transpozycja macierzy z drugiego przykładu. Co możemy ładnie zapisać:

$MV=(V^T M^T)^T$

Wybór sposobu mnożenia macierz razy wektor ma wpływ na to jak składane są macierze. W przypadku traktowania wektora jako wiersz, transformacje są nakładane od lewej do prawej. Jeśli wektor jest traktowany jako kolumna to od prawej do lewej, czyli pierwsza transformacja jest ostatnią w iloczynie.

We wszystkich przykładach przyjmuję, że wektor jest kolumnowy.

Macierz translacji

Macierz translacji dla 3D jest macierzą 4x4 i ma postać:

$T=\begin{bmatrix}
1 & 0 & 0 & t_x \\
0 & 1 & 0 & t_y \\
0 & 0 & 1 & t_z \\
0 & 0 & 0 & 1 \\
\end{bmatrix}$

Jak łatwo zauważyć:

$\begin{bmatrix}
1 & 0 & 0 & t_x \\
0 & 1 & 0 & t_y \\
0 & 0 & 1 & t_z \\
0 & 0 & 0 & 1 \\
\end{bmatrix}
\;\;\;
\begin{bmatrix} v_x \\ v_y \\ v_z \\ 1 \\\end{bmatrix}
=
\begin{bmatrix} v_x+t_x \\ v_y+t_y \\ v_z+t_z \\ 1 \\\end{bmatrix}
$