77.htm 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. <!--
  2. demo.design 3D programming FAQ
  3. Idea, texts, screenshots:
  4. Andrew A. Aksyonoff,
  5. shodan@chat.ru
  6. Web-design, illustrations:
  7. Andrey Samoilov,
  8. asy@sense.simbirsk.su
  9. -->
  10. <html>
  11. <head>
  12. <title>demo.design 3D programming FAQ. Разное. Кватернионы.</title>
  13. <link rel=stylesheet href="../style.css" type="text/css">
  14. </head>
  15. <script language="javascript">
  16. <!--//
  17. browser = navigator.appName;
  18. version = parseFloat(navigator.appVersion);
  19. if (browser == "Netscape" && version >= 3.0) { jsenabled = 1; } else
  20. if (browser == "Microsoft Internet Explorer" && version >= 3.0) { jsenabled = 1; } else { jsenabled = 0; }
  21. function swap(img,ref) { if (jsenabled) {document.images[img].src = ref;} }
  22. function loadtocache(img,ref) { cache[img] = new Image(); cache[img].src = ref; }
  23. if (jsenabled) {
  24. cache = new Array();
  25. loadtocache(0,"../img/xdl.gif");
  26. loadtocache(1,"../img/xfaq.gif");
  27. loadtocache(2,"../img/xlinks.gif");
  28. loadtocache(3,"../img/xauthor.gif");
  29. loadtocache(4,"../img/xe.gif");
  30. loadtocache(5,"../img/xprev.gif");
  31. loadtocache(6,"../img/xnext.gif");}
  32. //-->
  33. </script>
  34. <body bgcolor=white><center>
  35. <!-- Title -->
  36. <img src="../img/b.gif" width=500 height=1 alt=""><br>
  37. <img src="../img/t.gif" width=500 height=1 alt=""><br>
  38. <img src="../img/b.gif" width=500 height=1 alt=""><br>
  39. <img src="../img/t.gif" width=500 height=2 alt=""><br>
  40. <table width=500 cellpadding=0 cellspacing=0 border=0>
  41. <td><img src="../img/t.gif" width=5 height=1 alt=""><a href="../main.htm" onmouseover="swap('logo','../img/xe.gif');" onmouseout="swap('logo','../img/e.gif');"><img src="../img/e.gif" name=logo width=60 height=50 hspace=10 border=0 alt=" в самое начало "></a></td>
  42. <td><p class=pagetitle><img src="../img/t.gif" width=265 height=1 alt=""><br>demo.design<br>3D programming FAQ</td>
  43. <td align=center><p class=navy><a href="../download.htm" onmouseover="swap('dl','../img/xdl.gif');" onmouseout="swap('dl','../img/dl.gif');"><img src="../img/dl.gif" name=dl width=40 height=40 border=0 hspace=5 alt=" download "></a><br>download</td>
  44. <td align=center><p class=navy><a href="../links.htm" onmouseover="swap('links','../img/xlinks.gif');" onmouseout="swap('links','../img/links.gif');"><img src="../img/links.gif" name=links width=40 height=40 border=0 hspace=5 alt=" коллекция линков "></a><br>links</td>
  45. <td align=center><p class=navy><a href="../author.htm" onmouseover="swap('author','../img/xauthor.gif');" onmouseout="swap('author','../img/author.gif');"><img src="../img/author.gif" name=author width=40 height=40 border=0 hspace=5 alt=" автора! "></a><br>author</td>
  46. </table>
  47. <img src="../img/t.gif" width=500 height=4 alt=""><br><img src="../img/b.gif" width=500 height=1 alt=""><br>
  48. <!-- Head -->
  49. <table width=500 cellpadding=0 cellspacing=10 border=0><td><div align=justify>
  50. <p class=title>
  51. <img src="../img/b7.gif" width=70 height=70 align=left hspace=0 alt="">
  52. <img src="../img/t.gif" width=5 height=70 align=left hspace=0 alt="">
  53. РАЗНОЕ<br>7.7. Кватернионы
  54. <!-- Article -->
  55. <p>Кватернион, он же гиперкомплексное число, представляет собой набор четырех
  56. чисел. Иногда будет удобно представлять себе кватернион как 4D-вектор,
  57. иногда как набор четырех чисел, иногда как число и 3D-вектор, а иногда и
  58. как гиперкомплексное число с тремя мнимыми единицами i, j, k; таким образом,
  59. имеем следующие представления:
  60. <p class=expression>q = [x1,x2,x3,x4] = [scalar,(vector)] = [x1,(x2,x3,x4)] = x1+x2*i+x3*j+x4*k.<br>
  61. <p>Сложить или вычесть два кватерниона, а также умножить кватернион на число
  62. можно, как обычно, покомпонентно; с умножением ситуация более сложная.
  63. Умножение кватернионов должно в результате дать тоже кватернион, то есть
  64. конструкцию, содержащую лишь слагаемые вида r и r*l, где r - действительное
  65. число, а l - одна из мнимых единиц. Поэтому надо как-то определить операцию
  66. умножения для любых двух мнимых единиц. Определяется она так, что умножение
  67. получается некоммутативным, т.е. от перестановки мест множителей произведение
  68. меняется, и x*y != y*x. Поэтому умножение двух кватернионов приходится
  69. выполнять не по привычным правилам арифметики, а по следующим аксиомам:
  70. <pre class=formula>
  71. a*(b*c) = (a*b)*c, (ассоциативность)
  72. (a+b)*c = a*c+b*c, (транзитивность)
  73. a*(b+c) = a*b+a*c, (транзитивность)
  74. a*1 = 1*a = a, (существование единицы)
  75. a*0 = 0*a = 0, (существование нуля)
  76. i*i = j*j = k*k = -1, (свойство мнимых единиц)
  77. i*j = -j*i = k. (связь между мнимыми единицами i, j, k)
  78. </pre>
  79. <p>Из этих правил, кстати, следует, что
  80. <pre class=formula>
  81. j*k = -k*j = i,
  82. k*i = -i*k = j,
  83. </pre>
  84. <p>и получается такая вот таблица умножения комплексных единиц (умножение
  85. действительных чисел между собой и на комплексные единицы действует по
  86. обычным правилам, так что все свойства кватернионов определяются, в общем,
  87. этой таблицей):
  88. <p>
  89. <table cellpadding=0 cellspacing=0 border=0 align=center><td bgcolor=#5E5EA5>
  90. <table width=240 cellspacing=1 cellpadding=4 border=0 align=center>
  91. <tr><td bgcolor=white>&nbsp;</td><td align=center colspan=3 bgcolor=white>второй множитель</td></tr>
  92. <tr><td align=center bgcolor=white>первый множитель</td><td align=center bgcolor=white>i</td><td align=center bgcolor=white>j</td><td align=center bgcolor=white>k</td></tr>
  93. <tr><td align=center bgcolor=white>i</td><td align=center bgcolor=white>-1</td><td align=center bgcolor=white>k</td><td align=center bgcolor=white>-j</td></tr>
  94. <tr><td align=center bgcolor=white>j</td><td align=center bgcolor=white>-k</td><td align=center bgcolor=white>-1</td><td align=center bgcolor=white>i</td></tr>
  95. <tr><td align=center bgcolor=white>k</td><td align=center bgcolor=white>j</td><td align=center bgcolor=white>-i</td><td align=center bgcolor=white>-1</td></tr>
  96. </table>
  97. </td></table>
  98. <p>Кроме того, из этих правил можно вывести правило для умножения кватернионов,
  99. заданных в форме [scalar,vector]:
  100. <p class=expression>q1 = [s1,v1],<br>
  101. q2 = [s2,v2],<br>
  102. q1*q2 = [s1*s2 - v1*v2, s1*v2 + s2*v1 + v1xv2].<br>
  103. <p>Здесь v1*v2 - скалярное произведение векторов v1, v2; v1xv2 - векторное, все
  104. остальные произведения обычные (либо число на число, либо число на вектор).
  105. <p>Нужны же кватернионы для представления и интерполяции поворотов. Поворот
  106. относительно оси (x,y,z) (иными словами, поворот вокруг вектора (x,y,z),
  107. проведенного из начала координат) на угол angle представляется кватернионом
  108. q, лежащим на единичной 4D-сфере (то есть, 4D-вектором длины 1):
  109. <p class=expression>s = cos(angle/2),<br>
  110. v = (x,y,z) * sin(angle/2) / |(x,y,z)|,<br>
  111. q = [s,v].<br>
  112. <p>Что интересно, в такой форме поворот, соответствующий комбинации поворотов
  113. q1 и q2, просто равен их произведению. В случае с 3D Studio это позволяет
  114. быстро и просто перевести сохраненные в CHUNK_TRACKROTATE относительные
  115. повороты в абсолютные: просто читаем эти самые повороты (а записаны они как
  116. раз в форме [angle,(x,y,z)], причем длина вектора (x,y,z) уже приведена к
  117. единичной), переводим их в кватернионную форму, получаем набор кватернионов
  118. q0, q1, ..., q(n-1), qn. Здесь q0 и так задает абсолютный поворот, а вот все
  119. остальные придется переводить (умножение здесь, конечно, кватернионное):
  120. <p class=expression>absolute_q0 = q0,<br>
  121. absolute_q1 = q1*absolute_q0,<br>
  122. absolute_q2 = q2*absolute_q1,<br>
  123. ...<br>
  124. absolute_qn = qn*absolute_q(n-1).<br>
  125. <p>Получаем набор кватернионов, задающих абсолютные повороты, или абсолютную
  126. ориентацию объекта в какие-то моменты времени. Для того же, чтобы получить
  127. поворот-ориентацию в любой момент времени, придется как-то интерполировать
  128. повороты между этими заданными ключевыми значениями.
  129. <br>Все кватернионы, задающие повороты, должны лежать на единичной 4D-сфере,
  130. поэтому простейший метод (линейная интерполяция) несколько усложнится: мы
  131. вынуждены интерполировать не по прямой между двумя векторами, а по дуге на
  132. этой 4D-сфере, являющейся сечением сферы плоскостью, проходящей через центр
  133. сферы и наши два вектора, то есть две точки на сфере. Все это называется
  134. сферической линейной интерполяцией (spherical linear interpolation, если
  135. скоращенно, slerp) и определяется следующим образом:
  136. <p class=expression>slerp(q1,q2,t) = (q1*sin((1-t)*a) + q2*sin(t*a)) / sin(a),<br>
  137. <p>где t - локальное время (см.<a href="76.htm">п.7.6</a>), a - угол между векторами q1, q2;
  138. <p class=expression>0 <= t <= 1,<br>
  139. cos(a) = (q1,q2)/(|q1|*|q2|) = (q1,q2).<br>
  140. <p>То есть q1, q2 здесь уже рассматриваем как 4D-вектора. Приведенную формулу
  141. нетрудно вывести (для лучшего понимания): нам нужна такая точка q, которая
  142. лежит на единичной сфере, лежит в одной плоскости с q1 и q2 и центром (то
  143. есть нулем), причем угол между векторами q и q1 меняется линейно и, таким
  144. образом, равен t*a. Раз точка лежит в одной плоскости с 0, q1, q2, то
  145. вектор q равен линейной комбинации векторов q1, q2:
  146. <p class=expression>q = k1*q1 + k2*q2,<br>
  147. <p>где k1, k2 - какие-то (пока неизвестные) коэффициенты. q лежит на сфере,
  148. значит, длина q равна 1, отсюда имеем:
  149. <p class=expression>|q| = (q,q) = 1,<br>
  150. (k1*q1+k2*q2, k1*q1+k2*q2) = 1,<br>
  151. k1*k1*(q1,q1) + k2*k2*(q2,q2) + 2*k1*k2*(q1,q2) = 1,<br>
  152. k1*k1 + k2*k2 + 2*k1*k2*(q1,q2) = 1.<br>
  153. <p>Угол между q и q1 равен t*a, отсюда:
  154. <p class=expression>cos(q,q1) = cos(t*a),<br>
  155. (q,q1) = cos(t*a),<br>
  156. k1*(q1,q1) + k2*(q1,q2) = cos(t*a),<br>
  157. k1 + k2*(q1,q2) = cos(t*a).<br>
  158. <p>Получили систему уравнений для k1, k2:
  159. <p class=expression>k1 + k2*(q1,q2) = cos(t*a),<br>
  160. k1*k1 + k2*k2 + 2*k1*k2*(q1,q2) = 1,<br>
  161. <p>или
  162. <p class=expression>k1 + k2*cos(a) = cos(t*a),<br>
  163. k1*k1 + k2*k2 + 2*k1*k2*cos(a) = 1.<br>
  164. <p>Отсюда k1 = (cos(t*a) - k2*cos(a)), и получаем квадратное уравнение:
  165. <p class=expression>cos(t*a)^2 - 2*k2*cos(a)*cos(t*a) + k2^2*cos(a)^2 + k2^2 +<br>
  166. 2*k2*cos(a)*cos(t*a) - 2*k2^2*cos(a)^2 = 1,<br>
  167. <br>
  168. cos(t*a)^2 + k2^2*(1 - cos(a)^2) = 1,<br>
  169. <br>
  170. k2^2 * sin(a)^2 = sin(t*a)^2,<br>
  171. <br>
  172. k2 = sin(t*a) / sin(a),<br>
  173. <br>
  174. k1 = cos(t*a) - sin(t*a)*cos(a) / sin(a) =<br>
  175. &nbsp;&nbsp; = (cos(t*a)*sin(a) - sin(t*a)*cos(a)) / sin(a) =<br>
  176. &nbsp;&nbsp; = sin(a - t*a) / sin(a) = sin((1 - t)*a) / sin(a).<br>
  177. <p>Если a - очень маленький угол, настолько, что могут возникнуть ошибки при
  178. делении на sin(a), можно использовать обычную линейную интерполяцию (так
  179. как при маленьких значениях a sin(a) ~= a, sin(t*a) ~= t*a, и так далее).
  180. <p>Итак, мы умеем задавать повороты кватернионами, мы умеем их интерполировать
  181. линейно по множеству их возможных значений, то есть, поверхности сферы. Но
  182. хочется ведь интерполировать сплайнами Кочанека-Бартельса (далее везде, где
  183. используется термин "сплайны", подразумеваются именно такие сплайны), так
  184. как ориентация объекта должна меняться плавно, а не рывками, и желательно
  185. по совршенно той же траектории, что и в 3D Studio. Причем строить сплайны
  186. надо на поверхности четырехмерной сферы, иначе результаты интерполяции не
  187. будут соответствовать поворотам; кватернион-поворот должен обязательно
  188. лежать на единичной 4D-сфере. Естественное, возникает вопрос - как все это
  189. сделать?
  190. <p>Оказывается, кубическую функцию, переписав ее в определенном виде, можно
  191. строить только с помощью линейной интерполяции - или, для нашего случая, с
  192. помощью сферической линейной интерполяции. А именно, переписываем эту самую
  193. произвольную кубическую функцию в виде
  194. <p class=expression>g(t) = v1*(1-t)^3 + c1*3*t*(1-t)^2 + c2*3*t^2*(1-t) + v2*t^3,
  195. <p>и считаем ее значение в произвольно взятой точке t, используя только
  196. линейную интерполяцию (linear interpolation, lerp): пусть
  197. <p class=expression>lerp(a, b, t) = a*(1-t) + b*t,
  198. <p>тогда g(t) можно посчитать вот так:
  199. <p class=expression>tmp1 = lerp(v1, c1, t),<br>
  200. tmp2 = lerp(c1, c2, t),<br>
  201. tmp3 = lerp(c2, v2, t),<br>
  202. tmp4 = lerp(tmp1, tmp2, t),<br>
  203. tmp5 = lerp(tmp2, tmp3, t),<br>
  204. g(t) = lerp(tmp4, tmp5, t).<br>
  205. <p>Нам же надо интерполяцию сплайнами по поверхности сферы, это можно получить,
  206. всего-навсего заменив в приведенных выше формулах линейную интерполяцию lerp
  207. на наш сферический вариант, slerp. Далее, сравнивая g(t) с полученной в
  208. <a href="76.htm">п.7.6.</a> интерполяционной функцией f(t) можно заметить, что, если
  209. <pre class=formula>
  210. p1 = v1 <=> v1 = p1,
  211. r1 = (c1 - v1) * 3 <=> c1 = (p1 + r1) / 3,
  212. r2 = (v2 - c2) * 3 <=> c2 = (p2 - r2) / 3,
  213. p2 = v2 <=> v2 = p2,
  214. </pre>
  215. <p>то g(t) совпадает с f(t). Длинный и нудный вывод для v1, v2, c1, c2 через
  216. функции lerp()/slerp() делать, пожалуй, смысле нет, так что ограничимся
  217. конечными результатами. А именно, для каждой точки-ключа cur имеем
  218. <p class=expression>g1 = slerp(cur, prev, -(1+bias)/3.0);<br>
  219. g2 = slerp(cur, next, (1-bias)/3.0);<br>
  220. g3 = slerp(g1, g2, 0.5 + 0.5*continuity);<br>
  221. g4 = slerp(g1, g2, 0.5 - 0.5*continuity);<br>
  222. cur.ra = slerp(cur, g3, (tension-1));<br>
  223. cur.rb = slerp(cur, g3, -(tension-1));<br>
  224. <p>Для начальной и конечной точки, соответственно, имеем следующее:
  225. <p class=expression>q0.rb = slerp(p0, p1, (1-tension)*(1+continuity*bias)/3.0);<br>
  226. qn.ra = slerp(pn, p(n-1), (1-tension)*(1-continuity*bias)/3.0);<br>
  227. <p>При интерполяции между какими-то точками a и b просто полагаем
  228. <p class=expression>v1 = a,<br>
  229. c1 = a.rb,<br>
  230. c2 = b.ra,<br>
  231. v2 = b,<br>
  232. <p>и считаем g(t) по приведенному выше алгоритму. Здесь мы до сих пор не учли
  233. параметры ease to и ease from, но это дело одной строки кода - посчитать на
  234. самом деле надо не g(t), а g(ease(t)). Впрочем, обычно ease(t) = t. Что это
  235. за функция ease(), откуда она берется, для чего нужна и как рассчитывается,
  236. написано в <a href="76.htm">п.7.6</a>.
  237. <p>Таким образом, получаем кватернион, соответствующий повороту, задающий
  238. ориентацию объекта. Осталось выяснить, как из этого кватерниона получить
  239. что-нибудь более привычное - скажем, матрицу поворота. С одной стороны,
  240. вспомнив, как делается перевод в кватернионную форму для поворота на угол
  241. angle относительно оси (x,y,z), можно написать, что
  242. <p class=expression>q = [s,v],<br>
  243. angle = 2 * arccos(angle),<br>
  244. (x,y,z) = v / sin(angle/2),<br>
  245. <p>и посчитать матрицу поворота на полученный угол относительно полученной оси.
  246. Но есть метод попроще, позволяющий получить матрицу непосредственно из
  247. кватерниона:
  248. <pre class=formula>
  249. q = [w,(x,y,z)],
  250. [ 1-2*(y*y+z*z) 2*(x*y-w*z) 2*(x*z+w*y) ]
  251. A = [ 2*(x*y+w*z) 1-2*(x*x-z*z) 2*(y*z-w*x) ]
  252. [ 2*(x*z-w*y) 2*(y*z+w*x) 1-2*(x*x-y*y) ].
  253. </pre>
  254. <p>Подведем итог. Для интерполяции ориентации объекта в какой-то момент времени
  255. с помощью кватернионов и сплайнов придется сделать почти то же самое, что и
  256. в случае "обычной" интерполяции чего-нибудь сплайнами (<a href="76.htm">п.7.6</a>). Различия же
  257. заключаются в следующем:
  258. <ul>
  259. <li><p>все повороты надо заранее перевести в кватернионную форму;
  260. <li><p>расчеты производных и интерполяция делаются по формулам, данным
  261. именно в этом пункте, а не по "обычным" для сплайнов;
  262. <li><p>из кватернионной формы результирующий поворот обычно надо перевести
  263. в привычную матричную.
  264. </ul>
  265. <p>Все остальное совпадает с планом действий при "обычной" сплайновой
  266. интерполяциии и изложено в пункте <a href="76.htm">7.6</a>.
  267. </div>
  268. </td></table>
  269. <!-- Bottom Navigation -->
  270. <img src="../img/b.gif" width=500 height=1 alt=""><br><img src="../img/t.gif" width=500 height=2 alt=""><br>
  271. <table width=500 cellpadding=0 cellspacing=0 border=0>
  272. <td><img src="../img/t.gif" width=5 height=1 alt=""><a href="../main.htm" onmouseover="swap('logo2','../img/xe.gif');" onmouseout="swap('logo2','../img/e.gif');"><img src="../img/e.gif" name=logo2 width=60 height=50 hspace=10 border=0 alt=" в самое начало "></a></td>
  273. <td><p class=pagetitle><img src="../img/t.gif" width=265 height=1 alt=""><br>demo.design<br>3D programming FAQ</td>
  274. <td align=center><p class=navy><a href="76.htm" onmouseover="swap('prev','../img/xprev.gif');" onmouseout="swap('prev','../img/prev.gif');"><img src="../img/prev.gif" name=prev width=40 height=40 border=0 hspace=5 alt=" предыдущая статья "></a><br>previous</td>
  275. <td align=center><p class=navy><a href="../content.htm" onmouseover="swap('faq','../img/xfaq.gif');" onmouseout="swap('faq','../img/faq.gif');"><img src="../img/faq.gif" name=faq width=40 height=40 border=0 hspace=5 alt=" содержание "></a><br>content</td>
  276. <td align=center><p class=navy><a href="78.htm" onmouseover="swap('next','../img/xnext.gif');" onmouseout="swap('next','../img/next.gif');"><img src="../img/next.gif" name=next width=40 height=40 border=0 hspace=5 alt=" следующая статья "></a><br>next</td>
  277. </table>
  278. <img src="../img/t.gif" width=500 height=4 alt=""><br>
  279. <img src="../img/b.gif" width=500 height=1 alt=""><br>
  280. <img src="../img/t.gif" width=500 height=1 alt=""><br>
  281. <img src="../img/b.gif" width=500 height=1 alt=""><br>
  282. </center></body>
  283. </html>