Budeme se zatím zabývat otáčením kolem počátku souřadnic (tj. se středem v bodě (0, 0)
) ve dvou rozměrech. Souřadnice budeme mít pravoruké, takže osa x
směřuje doprava a osa y
nahoru. Kladný úhel znamená otočení proti směru hodinových ručiček.
Otáčení má spoustu uplatnění v grafice. Když píšete počítačovou hru, sice si na grafiku asi najdete nějakou knihovnu a ta se postará i o otáčení (například v Pygame to zařídí funkce pygame.transform.rotate
), ale je určitě dobré vědět, co počítač doopravdy dělá. A obzvlášť ve 3D grafice se často nevyhnete tomu, abyste se do matematiky zavrtali trochu víc do hloubky.
Máme zadaný úhel α (třeba ve stupních, to je fuk) a libovolný vektor. Chceme spočítat vektor, který vznikne otočením toho zadaného. Nejdřív si uvedeme jedno řešení, které je docela hloupé. Spočítáme úhel, jaký svírá zadaný vektor s osou x
, přičteme k tomu úhel α a vyrobíme nový vektor správné délky, který bude svírat s osou x
tenhle vypočtený úhel.
(vx, vy)
s osou x
? Použijeme skalární součin na výpočet kosinu a výsledek patřičně upravíme, pokud má vektor zápornou souřadnici vy
. Do vzorečku o skalárním součinu dosadíme za druhý vektor osu x
, která má souřadnice (1, 0)
a získáme vztah cos(
β)·|v| = vx
. Značka |v|
znamená délku vektoru. Takhle spočítáme cos(
β)
.vy
kladná, můžeme rovnou spočítat úhel (v Pythonu použijeme funkci math.acos
). Když je vy
záporná, měl by nám vyjít úhel na opačnou stranu – a opravdu stačí spočítat výsledek jako v předchozím případě a dát mu záporné znamínko. Jestli se vám nelíbí pracovat se zápornými úhly, můžete k tomu pak přičíst 360°, samozřejmě to bude znamenat tentýž úhel.v
? Nejdřív vyrobíme vektor pod správným úhlem a pak mu dáme správnou délku. Vektor pod správným úhlem bude mít souřadnice kosinus a sinus, takže (cos(
α + β), sin(
α + β))
. Aby měl správnou délku, obě souřadnice přenásobíme délkou vektoru v
. Hotovo, to je správný výsledek.Takhle by šlo otáčení počítat, ale je to docela nepříjemné kvůli tomu počítání úhlů tam a zpátky. V následujícím textu se zkusíme úhlů úplně zbavit.
Abychom se dostali dál, všimneme si důležité zákonitosti: když dva vektory otočíme a sečteme, je to totéž, jako když otočíme jejich součet. Zapsáno formálněji, fα(u + v) = fα(u) + fα(v)
, kde fα
jsme pojmenovali funkci otočení o úhel α (bere vektor, navrací vektor, proč ne).
Zobrazení, které tohle splňuje, se nazývá afinní. Otočení kolem počátku navíc splňuje ještě další vlastnost: k·fα(v) = fα(k·v)
pro libovolné číslo k
. Proto je otočení kolem nuly dokonce lineární zobrazení. Každé lineární zobrazení je afinní, naopak to platit nemusí.
Názvosloví je tady trochu matoucí: to, co znáte jako lineární funkce, se dá chápat afinní zobrazení v jednom rozměru. Lineární funkce je lineární zobrazení jedině v případě, že jde o přímou úměrnost.
It is not the spoon that bends, it is only yourself.
Plán je následující: naučíme se otáčet souřadné osy, pak si každý vektor představíme jako součet něčeho vodorovného a něčeho svislého, ty otočíme a výsledek sečteme. Protože otočení je lineární zobrazení, bude to fungovat. Jako v předchozím textu, chceme otočit vektor v = (vx, vy)
o úhel α.
x', y'
dostaneme jako zadání úlohy, místo úhlu. Ať to ale máme od základů, dejme tomu, že jsme dostali úhel α. Nakreslíme si, co chceme zjistit, a vykoukáme z toho, že x' = (cos(α), sin(α))
a y' = (-sin(α), cos(α))
.v = vx·x + vy·y
, kde x, y
jsou dva vektory ve směru neotočených os, konkrétně (1, 0)
a (0, 1)
. Každý z nich znásobíme vhodným číslem a není těžké uvěřit, že ta rovnost platí.v' = vx·x' + vy·y'
, kde vx, vy
jsou pořád souřadnice původního vektoru, ale x', y'
jsou tentokrát otočené souřadné osy, jak jsme je vypočítali v bodě (1). Stačí sečíst a máme výsledek.Všimněte si, že když chceme otočit milión vektorů o stejný úhel (grafická karta se s něčím podobným musí porovnat za zlomek vteřiny), počítáme sinus a kosinus jen jednou, zbytek je násobení a sčítání. A protože se podobné změny souřadnic používají opravdu často, hodí se pro ně zavést stručnější zápis, o kterém je následující oddíl.
Unfortunately, no one can be told what the Matrix is. You have to see it for yourself.
Začneme tím, že zapíšeme otočené osy x', y'
jako sloupcové vektory vedle sebe; získáme tak tabulku 2×2 čísla. Když podle ní chceme otočit libovolný vektor (vx, vy)
, levý sloupeček znásobíme vx
, pravý vy
a výsledky sečteme. Jiný pohled na věc je, že první složka výsledku je skalární součin prvního řádku tabulky s vektorem v
a druhá složka výsledku je obdobně součin druhého řádku s daným vektorem. Rozepsáním se dá snadno ověřit, že výsledek je tentýž, takže to můžeme chápat oběma způsoby, jak se zrovna hodí.
Tabulku nazveme maticí a tenhle zvláštní výpočet nazveme maticovým násobením. Matice může mít i jiné rozměry než 2×2, ale pořád musí platit, že počet jejích sloupců je stejný jako počet složek násobeného vektoru; počet jejích řádků bude počet složek výsledku. Pro otočení mají smysl jenom matice čtvercového tvaru. Matice 3×3 nám prostě umožní otáčet třírozměrné vektory, jen si musíme správně vybrat, kam otočíme souřadné osy. Ještě se k tomu vrátíme.
Narozdíl od násobení čísel je u matic potřeba dávat pozor na pořadí. Násobení, jak jsme ho teď zavedli, funguje jen v případě matice·vektor – opačné pořadí je prostě chyba. Totiž, například knihovna DirectX používá zápis v opačném pořadí, ale bude lepší před tím teď zavřít oči a nemotat si hlavu.
Když chceme něco otočit třeba dvakrát nebo stokrát za sebou o různé úhly, můžeme to samozřejmě postupně násobit příslušnými maticemi, až vyjde výsledek. Když takhle ale násobíme hodně vektorů, výpočet bude dvakrát nebo stokrát pomalejší, což je škoda. Stačí si všimnout, že jednou otočené souřadné osy můžeme znovu otočit maticí, a když je zapíšeme vedle sebe, získáme novou matici, která zařídí obě otočení jedním výpočtem.
Zavedeme si tedy maticové násobení, které funguje jako násobení matice·vektor na každý sloupec matice napravo zvlášť. Znovu je potřeba dávat pozor na pořadí: ve dvou rozměrech nám sice selský rozum správně radí, že to vyjde na stejno, ale když přidáme posunutí nebo třetí rozměr, už by mohly vycházet nesmysly. Součin matic A·B
obecně nemusí být totéž co B·A
.
Obvyklý pohled na násobení matic je, že ve výsledku na a
-tém řádku v b
-tém sloupci bude skalární součin a
-tého řádku levé matice s b
-tým sloupcem matice napravo. Matice jde násobit jenom v případě, kdy počet sloupců levé matice je stejný jako počet řádků pravé matice. Všimněte si, že vektory můžeme chápat jako matici n
×1 a všechno pořád funguje správně. Například v knihovně Numpy se to tak dělá.
Otočení ve dvou rozměrech je dané úhlem, ale v matici jsou čtyři čísla. Je tedy jasné, že zdaleka ne všechny matice udávají otočení – přirozeně to odpovídá tomu, že si s osami souřadic můžeme dělat cokoliv, nejenom je otáčet. Násobení maticí obecně může kromě otáčení způsobit zvětšení nebo zmenšení v libovolném směru a zkosení. Jak se tedy pozná, že nějaká matice dělá jen otočení?
Ať souřadné osy otočíme jakkoliv, budou pořád stejně dlouhé a budou na sebe všechny kolmé. To přesně stačí pro ověření, že jsme neudělali víc než nějaké otočení. A navíc, obojí to říká skalární součin: skalární součin některé osy s ní samou je druhá mocnina její délky, to musí být jednička, zatímco skalární součin dvou různých os je kosinus úhlu mezi nimi, to musí být nula (aby byly kolmé).
Předchozí podmínka jde hezky vyjádřit maticovým násobením, protože v něm taky je spousta skalárních součinů. Řekněme si, že máme danou matici R
a chceme zjistit, jestli vyjadřuje otočení. Vyrobíme si druhou matici, která bude oproti R
překlopená podle diagonály: levý horní a pravý dolní roh zůstanou na místě, ale levý dolní se vymění s pravým horním. Ve více rozměrech se to udělá tak, že řádky původní matice zapíšeme do sloupců. Vzniklé matici se říká transponovaná a značí se indexem T: RT
.
Zbývá si teď všimnout, že v součinu RT·R
nám vyjdou zrovna ty součiny, které jsme potřebovali: několik z nich budou skalární součiny některé osy s ní samotnou, ty musí vyjít jako jednička, ostatní musí vyjít jako nula. Jedničky budou jen na diagonále. Výsledku se říká jednotková matice.
Působivé zjištění je, že i RT
je matice otočení. Nejen to, když ji přinásobíme k matici R
, vyjdou nám původní, neotočené osy x, y, z
. Zjevně tedy RT
otáčí o stejný úhel v přesně opačném směru než R
. (Ve dvou rozměrech si můžete ověřit, že se vymění znaménko u hodnot obsahujících sinus, takže to odpovídá otočení o záporný úhel.)
Když násobením dvou matic vyjde jednotková, říkáme, že jedna z nich je inverzní maticí té druhé. Pro libovolnou matici R
její inverzní matici značíme R-1
, jako by to byla převrácená hodnota. Jedině v případě matic otočení platí, že R-1 = RT
, v případě jakékoliv jiné R
to vyjde jako dvě různé matice.
Vzorečky si najděte na anglické Wikipedii, až je budete potřebovat. Dají se i vymyslet (u některých z nich to dá dost práce), nemá smysl si je pamatovat.
Jak si poradíme s posunutím? Zatím umíme jenom otáčet kolem počátku. Můžeme si tedy pamatovat matici otočení a zvlášť vektor, který se má k výsledku přičíst. Když pak chceme otočit kolem libovolného středu, jednou takovouhle operací můžeme ten střed posunout do počátku a pak druhou takovou operací otočit a posunout zpátky. Když násobíme dvě takové operace za sebe, vektor posunutí napravo musíme přenásobit maticí nalevo a pak k němu přičíst vektor posunutí nalevo, což je trochu protivné.
Díky tomu, jak funguje maticové násobení, můžeme vektor posunutí přidat do matice, jako by to byla další souřadnice. Je ale potřeba mít na paměti, že to žádná souřadnice není – je to něco extra, chová se to úplně jinak a jinak se s tím musí zacházet.
Aby všechny naše matice šly násobit mezi sebou, musí být čtvercové. Přidáme jim proto taky nový řádek, ve kterém budou samé nuly a na konci jednička. To zařídí přesně to chování, jaké potřebujeme: při skládání dvou operací "otočení&posunutí" se vektor na pravé straně otočí a vektor nalevo se k němu přičte, matice otočení se znásobí obyčejně mezi sebou. Funguje to i ve více rozměrech.
Aby takovými maticemi šly násobit vektory, přidáme každému vektoru taky jednu složku navíc. Bude v ní jednička: zařídí přesně to, že při násobení maticí se přičte její poslední sloupec. Celá věc se dá chápat tak, že si u každého vektoru budeme udržovat jedničku pro případ, že k vektoru budeme chtít něco přičíst. Jednička v posledním řádku matice zařídí právě to, že nám po násobení zůstane jednička i ve výsledném vektoru.
Dobře, ale co když by se přece stalo, že v té nové složce vektoru vyjde něco jiného než 1
? Jak se později ukáže, hodí se celý vektor tím číslem vydělit. Potom samozřejmě jednička vyjde, a ostatní hodnoty se všechny nějakým způsobem změní. Nebo, lepší pohled na věc je prohlásit, že všechny násobky vektoru se rovnají: opravdu, po dělení tou novou složkou vyjde vždycky ten samý vektor.
Může se to zdát jako trochu divné řešení, ale je užitečné. Dělení se bude náramně hodit pro perspektivní promítání (jaké dělá každý normální foťák) a zajímavá je i situace, kdy dělit nejde, totiž když je v té nové složce nula.
Nevýhoda je, že součet vektorů už nebude dělat to, na co jsme byli zvyklí. Vlastně, nebude dělat nic užitečného: vyrobí nějaký vážený průměr mezi danými body, kdy váhou každého vektoru je jeho hodnota v té nové složce.
Když nějakému vektoru budeme zmenšovat hodnotu v nové složce blízko nule, příslušný bod naopak bude utíkat nepřímo úměrně daleko od počátku souřadnic. Je pochopitelné se na věc dívat tak, že nula znamená bod nekonečně daleko, známe jenom jeho směr. A vlastně ani to ne, protože neznáme znaménko: stejný výsledek vyjde, když pošleme do nekonečna bod na opačné straně. Je to podobné chování klasického hada ze starých Nokií: když jedním směrem utečete z hrací plochy, vrátíte se na opačné straně, ale had má takové směry jen dva, zatímco tady máme všechny kolem dokola.
Body v nekonečnu potkáváte v životě: je to bod na obzoru, kde se potkají rovnoběžné koleje. Svět je víceméně placatý, koleje vedou rovně a když je vyfotíte, na horizontu se viditelně protnou. Focení je zobrazení placatého světa na placatou fotku, podobně jako otočení (a všechna ostatní zobrazení, jaká znáte z klasické geometrie) je zobrazení placaté roviny na placatou rovinu. Liší se tím, že body z nekonečna zobrazuje na konečnou přímku, které normální lidi (ne-matematici) říkají horizont. Zákonitě, jinou přímku zobrazí do nekonečna: bude to přímka na zemi přímo pod vašima nohama, kolmá ke směru, jakým se díváte.
Hezké na tom je, že odteď mají každé dvě přímky jeden průsečík a každé dva body určují přímku. Matematici to lajkují.
Matice, se kterými jsme zacházeli doteď, šly zapsat s jedničkou vpravo dole a nulami v ostatních složkách posledního řádku. Matice, která vypadá takhle, je obyčejné zobrazení v rovině, jaké znáte: otočení, stejnolehlost, zkosení, posunutí. Matice promítání foťákem v tom posledním řádku budou mít něco jiného.
Obdobná záležitost je matice promítání ze třírozměrného světa do dvourozměrné fotky. Bodům ve třírozměrném světě taky musíme zákonitě přidat jedničku navrch, takže promítání bude zařizovat matice se třemi řádky a čtyřmi sloupci. V perspektivě se vzdálené předměty jeví tím menší, čím jsou dál – k tomu právě použijeme dělení, jak jsme si ho zabudovali do vektorů.
Pro základní příklad si představme, že foťák je v počátku souřadnic, že je natočený rovnoběžně s osou z
skutečného světa a že osy x, y
ve fotce jsou natočené přesně jako osy x, y
ve skutečném světě. To, jak jsou natočené osy ve světě, si musíme nějak určit, když ho začneme ukládat do stroje. Rozumně se to dá chápat tak, že se foťák dívá nahoru.
Po krátké úvaze čmáráním tužkou po papíře usoudíme, že bod (x, y, z)
ze skutečného světa se má do fotky zobrazit do souřadnic (x/z, y/z)
: prostě ho vydělíme svislou vzdáleností. Stačí tedy vyrobit matici 3×4, která první dvě souřadnice nechá a čtvrtou souřadnice vstupu dá do třetí souřadnice výsledku (to je ta zvláštní složka, kterou se dělí). Tu čtvrtou zvláštní souřadnici vstupu můžeme úplně ignorovat, takže čtvrtý sloupec matice bude nulový. Hotovo – možná jste si to neuvědomili, ale teď můžeme promítat jakýmkoli foťákem. Stačí si svět správně otočit a posunout, a pak na něj použít tohle promítání z počátku.
V počítačové grafice se ale často používají matice 4×4, které si zároveň pamatují i tu hodnotu 1/z
. Hloubka je důležitá, aby bylo vidět jen to, co je od nás blízko. Výhodu to pak má, že k matici 4×4 můžeme najít inverzní matici, která nám výsledné vektory (čtyřrozměrné, přestože to jsou vlastně body v obrázku) vrátí zpátky do skutečného světa – jde to, protože si body pamatují hloubku.
Celá záležitost je podrobněji popsaná v různých článcích na anglické Wikipedii, například v tomhle. Ještě lépe a přehledněji je to na slovenské Wikipedii, jestli si chcete procvičit cizí jazyk.