36.htm 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  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/b3.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>3.6. Отсечение
  54. <!-- Article -->
  55. <p>В процессе отрисовки граней мы почти сразу столкнемся со следующей неприятной
  56. ситуацией: проекция грани лежит в плоскости экрана, но она вовсе не обязана
  57. точно попадать в прямоугольник-экран. Поэтому эту самую проекцию желательно
  58. корректно обрезать по границе экрана (можно, конечно, выводить все на экран
  59. через свою функцию putpixel() и проверять в ней x, y на попадание в экран,
  60. но это извращение и вдобавок очень медленно). Операцией обрезания как раз и
  61. занимаются разные алгоритмы отсечения (clipping).
  62. <a name="361"></a>
  63. <p class=title><img src="../img/b3.gif" width=70 height=70 align=left hspace=0 alt=""><img src="../img/t.gif" width=5 height=70 align=left hspace=0 alt="">3.6.1. Отсечение при растеризации
  64. <p>Это, пожалуй, самый простой, довольно быстрый и наиболее часто используемый
  65. метод отсечения. Идея, как обычно, проста. При растеризации треугольника мы
  66. в конечном итоге рисуем набор горизонтальных отрезков. Так и будем обрезать
  67. по границам экрана именно отрезки. Пусть мы рисуем отрезок от start_x до
  68. end_x по строке с y = current_sy. Возможны следующие случаи:
  69. <p class=expression><ul>
  70. <li>(current_sy < 0), или (current_sy >= YSIZE), или (start_x >= XSIZE),
  71. или (end_x <= 0). Тогда отрезок вообще не виден и мы его не рисуем.
  72. Причем если (current_sy >= YSIZE), то отрисовку грани прекращаем.
  73. <li>(start_x < 0). В этом случае нам надо пропустить первые (-start_x)
  74. пикселов, так что сдвигаем все интерполируемые по отрезку величины
  75. на (-start_x) пикселов и делаем start_x равным нулю. Например, для
  76. аффинного текстурирования надо сдвинуть u и v:
  77. <pre class=source>
  78. u += (-start_x) * du;
  79. v += (-start_x) * dv;
  80. start_x = 0;
  81. </pre>
  82. <li>(end_x >= XSIZE). Здесь совсем просто. Так как интерполируем мы, начиная
  83. с начала отрезка, достаточно просто сделать end_x = XSIZE - 1.
  84. </ul>
  85. <p>Таким образом, все отсечение делается несколькими строками кода:
  86. <pre class=source>
  87. // ...
  88. if (current_sy >= YSIZE) return;
  89. if ((current_sy < 0) ||
  90. (start_x >= XSIZE) ||
  91. (end_x <= 0))
  92. break;
  93. if (start_x < 0) {
  94. u -= start_x * du; // пример для аффиного текстурирования
  95. v -= start_x * dv;
  96. }
  97. if (end_x >= XSIZE) end_x = XSIZE - 1;
  98. // ...
  99. </pre>
  100. <p>Самое приятное заключается в том, что два умножения, которые получаются в
  101. случае (start_x < 0), можно легко совместить с теми двумя, что нужны для
  102. субтексельной точности (см.<a href="72.htm">п.7.2</a>). А несколько сравнений и присваивание на
  103. одну линию делаются достаточно быстро. Получаем отсечение, практически не
  104. замедляющее скорость работы.
  105. <a name="362"></a>
  106. <p class=title><img src="../img/b3.gif" width=70 height=70 align=left hspace=0 alt=""><img src="../img/t.gif" width=5 height=70 align=left hspace=0 alt="">3.6.2. Алгоритм Сазерленда-Ходжмана
  107. <p>Этот алгоритм (Sutherland-Hodgman algorithm) предназначен, на самом деле,
  108. для отсечения произвольного полигона (даже не обязательно выпуклого, хотя
  109. использовать невыпуклые полигоны довольно, на мой взгляд, нерационально)
  110. в полуплоскость, или, для 3D случая, в полупространство; другими словами,
  111. отсечения полигона прямой или плоскостью. Применяя алгоритм несколько раз,
  112. получаем методы отсечения в выпуклый полигон (например, прямоугольник,
  113. которым является экран) или выпуклый объем (например, ту часть пространства,
  114. которую видно из камеры).
  115. <p>Итак, пусть у нас есть полигон с N вершинами, заданными в каком-то порядке,
  116. то есть, по часовой стрелке или против; в каком именно, алгоритму все равно.
  117. Занумеруем их от 0 до N-1. Теперь последовательно обходим все ребра полигона:
  118. ребро от вершины 0 до вершины 1, от 1 до 2, ..., от N-2 до N-1, от N-1 до 0.
  119. Вершины, являющиеся началом и концом ребра, могут лежать в области отсечения,
  120. (область отсечения - либо полуплоскость для 2D случая, либо полупространство
  121. для 3D случая) могут и не лежать; возможны следующие случаи:
  122. <p class=expression><ul>
  123. <li>начало лежит в области отсечения, конец - тоже. Тогда просто добавляем
  124. начало к вершинам полигона-результата.
  125. <li>начало лежит в области отсечения, конец не лежит. В этом случае считаем
  126. точку пересечения ребра и границы области отсечения, добавляем в список
  127. вершин результата начало ребра и вслед за ним точку пересечения.
  128. <li>начало не лежит в области отсечения, конец лежит. Тоже считаем точку
  129. пересечения, и добавляем только ее.
  130. <li>начало не лежит в области отсечения, конец тоже. Переходим к следующему
  131. ребру, никак не изменяя результат.
  132. </ul>
  133. <p>Рассмотрим простенький пример работы алгоритма в 2D случае, а именно отсечем
  134. 2D треугольник прямой. Она делит плоскость на две полуплоскости, две области,
  135. нужную и ненужную.
  136. <p><center><img src="illu/illu36a.gif" width=180 height=180 alt="рисунок (illu/illu36a.gif)"></center>
  137. <p class=expression><ul>
  138. <li>шаг 1, ребро 0-1: вершина 0 не лежит в нужной области, вершина 1 лежит.
  139. Ищем точку пересечения, находим точку A, добавляем ее в список вершин
  140. результата. Теперь этот список состоит из одной вершины A.
  141. <li>шаг 2, ребро 1-2: обе вершины лежат в области, добавляем вершину 1.
  142. Результат теперь являет собой список A, 1.
  143. <li>шаг 3, ребро 2-0: 2 лежит в области, 0 не лежит. Добавляем вершину 2 и
  144. точку пересечения B. После последнего шага, таким образом, получили
  145. корректный результат отсечения - полигон с вершинами A, 1, 2, B.
  146. </ul>
  147. <p>В случае, когда надо сделать отсечение в экран, последовательно применяем
  148. алгоритм, отсекая полигон прямыми sx=0, sx=XSIZE, sy=0, sy=YSIZE. Из-за
  149. такого простого вида уравнений прямых соответственно упрощается код для
  150. выяснения принадлежности вершины нужной области и поиска точки пересечния.
  151. Вот, например, кусок кода для отсечения полигона прямой sx=0 (оставляющий
  152. область sx > 0).
  153. <pre class=source>
  154. // dst - массив для сохранения вершин результата
  155. // src - массив вершин исходного полигона
  156. // n - число вершин исходного полигона
  157. // функция возвращает число вершин результата
  158. int clipLeft(vertex *dst, vertex *src, int n) {
  159. int i, r;
  160. vertex p1, p2;
  161. float k;
  162. r = 0;
  163. for (i = 0; i < n; i++) {
  164. p1 = src[i];
  165. p2 = src[(i + 1) % n];
  166. // если начало лежит в области
  167. if (p1.sx >= 0) {
  168. // если конец лежит в области
  169. if (p2.sx >= 0) {
  170. // добавляем начало
  171. dst[r++] = p1;
  172. } else { // если конец не лежит в области
  173. // добавляем начало
  174. dst[r++] = p1;
  175. // добавляем точку пересечения
  176. k = -p1.sx / (p2.sx - p1.sx);
  177. dst[r].sx = 0;
  178. dst[r].sy = p1.sy + k * (p2.sy - p1.sy);
  179. dst[r].u = p1.u + k * (p2.u - p1.u);
  180. dst[r].v = p1.v + k * (p2.v - p1.v);
  181. r++;
  182. }
  183. } else { // если начало не лежит в области
  184. // если конец лежит в области
  185. if (p2.sx >= 0) {
  186. // добавляем точку пересечения
  187. k = -p1.sx / (p2.sx - p1.sx);
  188. dst[r].sx = 0;
  189. dst[r].sy = p1.sy + k * (p2.sy - p1.sy);
  190. dst[r].u = p1.u + k * (p2.u - p1.u);
  191. dst[r].v = p1.v + k * (p2.v - p1.v);
  192. r++;
  193. }
  194. }
  195. }
  196. return r;
  197. }
  198. </pre>
  199. <p>Видно, что можно чуточку перемешать код обработки разных случаев, изменить
  200. порядок действий алгоритма и тем самым подсократить исходник, да и сделать
  201. алгоритм проще и понятнее:
  202. <pre class=source>
  203. // dst - массив для сохранения вершин результата
  204. // src - массив вершин исходного полигона
  205. // n - число вершин исходного полигона
  206. // функция возвращает число вершин результата
  207. int clipLeft(vertex *dst, vertex *src, int n) {
  208. int i, r;
  209. vertex p1, p2;
  210. float k;
  211. r = 0;
  212. for (i = 0; i < n; i++) {
  213. p1 = src[i];
  214. p2 = src[(i + 1) % n];
  215. if (p1.sx >= 0) { // если начало лежит в области
  216. dst[r++] = p1; // добавляем начало
  217. }
  218. // если ребро пересекает границу
  219. // добавляем точку пересечения
  220. if (((p1.sx > 0) && (p2.sx < 0)) ||
  221. ((p2.sx >= 0) && (p1.sx < 0)))
  222. {
  223. k = -p1.sx / (p2.sx - p1.sx);
  224. dst[r].sx = 0;
  225. dst[r].sy = p1.sy + k * (p2.sy - p1.sy);
  226. dst[r].u = p1.u + k * (p2.u - p1.u);
  227. dst[r].v = p1.v + k * (p2.v - p1.v);
  228. r++;
  229. }
  230. }
  231. return r;
  232. }
  233. </pre>
  234. <p>Написав аналогичные куски кода для остальных трех сторон экрана, получим
  235. функцию отсечения в экран по алгоритму Сазерленда-Ходжмана.
  236. <a name="363"></a>
  237. <p class=title><img src="../img/b3.gif" width=70 height=70 align=left hspace=0 alt=""><img src="../img/t.gif" width=5 height=70 align=left hspace=0 alt="">3.6.3. 3D-отсечение
  238. <p>В пунктах <a href="#361">3.6.1</a> и <a href="#362">3.6.2</a> делался упор на 2D-отсечение, т.е. отсечение экраном
  239. уже спроецированного полигона. Еще один метод - это 3D-отсечение, когда все
  240. полигоны отсекаются областью зрения камеры. В этом случае после проецирования
  241. полигон заведомо попадает в экран и дальнейшее отсечение уже не требуется.
  242. Кстати, z-отсечение при 3D-отсечение делается почти автоматически, очень
  243. хорошо вписываясь в общую схему, при использовании же 2D-отсечения придется
  244. делать еще и его.
  245. <p>Рассмотрим стандартную камеру. Ее область зрения задается "пирамидой",
  246. ограниченной пятью плоскостями со следующими уравнениями (откуда взялось
  247. smallvalue и что это такое, написано в <a href="35.htm">п.3.5</a>):
  248. <p class=expression>
  249. z = -dist + smallvalue<br>
  250. y = (z + dist) * ySize / (2 * dist)<br>
  251. y = -(z + dist) * ySize / (2 * dist)<br>
  252. x = (z + dist) * xSize / (2 * dist)<br>
  253. x = -(z + dist) * xSize / (2 * dist)<br>
  254. <p>Вот рисунок (вид сбоку), на котором видно первые три из этих плоскостей.
  255. <p><center><img src="illu/illu36b.gif" width=180 height=180 alt="рисунок (illu/illu36b.gif)"></center>
  256. <p>Отсекаем полигон каждой из этих плоскостей по тому же самому алгоритму
  257. Сазерленда-Ходжмана, получаем 3D-отсечение.
  258. <p>Теперь выясним, как это самое отсечение сделать относительно универсально
  259. (а не только для стандартной камеры), быстро и просто. Зададим наши пять
  260. плоскостей не в форме какого-то уравнения, а в форме
  261. <p class=expression>plane = [o, n],
  262. <p>где o - какая-то точка, принадлежащая плоскости; n - нормаль, смотрящая в
  263. то полупространство, которое мы хотим оставить. Например, для стандартной
  264. камеры в этом случае плоскости запишутся так:
  265. <p class=expression><table align=center cellspacing=0 cellpadding=0 border=0>
  266. <tr><td>n = (0, 0, 1), </td><td> o = (0, 0, -dist + smallvalue)</td></tr>
  267. <tr><td>n = (0, -dist, ySize/2), </td><td> o = (0, 0, -dist)</td></tr>
  268. <tr><td>n = (0, dist, ySize/2), </td><td> o = (0, 0, -dist)</td></tr>
  269. <tr><td>n = (-dist, 0, xSize/2), </td><td> o = (0, 0, -dist)</td></tr>
  270. <tr><td>n = ( dist, 0, xSize/2), </td><td> o = (0, 0, -dist)</td></tr>
  271. </table>
  272. <p>При такой форме задания плоскости критерий принадлежности произвольной точки
  273. p нужному нам полупространству выглядит очень просто:
  274. <p class=expression>(p - o) * n >= 0.
  275. <p>Не менее просто выглядит и процедура поиска пересечения отрезка от точки p1
  276. до точки p2 с плоскостью. Для любой точки p внутри отрезка имеем
  277. <p class=expression>p = p1 + k * (p2 - p1), 0 <= k <= 1,
  278. <p>но так как p лежит в плоскости, p * n = 0; отсюда имеем
  279. <p class=expression>(p1 * n) + (k * (p2 - p1) * n) = 0,<br>
  280. k = -((p2 - p1) * n) / (p1 * n) = (p1 * n - p2 * n) / (p1 * n) = 1 - (p2 * n) / (p1 * n).<br>
  281. <p>и моментально находим точку пересечения. Все 3D-отсечение, таким образом,
  282. сводится к последовательному применению одной универсальной процедуры
  283. отсечения плоскостью. Кроме того, видно, что можно посчитать матрицу перевода
  284. стандартной камеры в произвольную, применить ее к выписанным точкам o и
  285. нормалям n для плоскостей, задающих "стандартную" область зрения (к нормалям,
  286. естественно, надо применить только "поворотную" часть матрицы) и получить,
  287. таким образом, уравнения плоскостей уже для *любой* камеры. Тогда 3D-отсечение
  288. можно сделать вообще до всяческих преобразований сцены, минимизировав, таким
  289. образом, количество поворотов и проецирований вершин - не попавшие в область
  290. зрения вершины поворачивать и проецировать, очевидно, не надо. Проецирования
  291. невидимых вершин, впрочем, можно избежать и другим образом: сделав поворот
  292. сцены, а потом 3D-отсечение "стандартной" областью зрения камеры.
  293. <p>Рассмотрим это более подробно. Пусть у нас есть какая-то камера; пусть есть
  294. матрица, которая переводит стандартную камеру в эту камеру. Она как бы состоит
  295. из двух частей: матрицы T (обозначения здесь использутся те же самые, что в
  296. <a href="25.htm">п.2.5</a>) и матрицы параллельного переноса, совмещающей Ss и s (обозначим ее
  297. буквой M). Причем сначала применяется матрица M, потом матрица T. Так вот,
  298. для перевода какой-то плоскости-ограничителя области зрения стандартной
  299. камеры, заданной в форме plane = [o,n], надо всего лишь сделать пару
  300. матричных умножений (поскольку M - матрица переноса, и ее применение на деле
  301. сводится к трем сложениям, матричных умножений будет ровно два):
  302. <p class=expression>new_o = T * M * std_o<br>
  303. new_n = T * std_n<br>
  304. <p>Что лучше и быстрее, как обычно, не ясно. При отсечении до преобразований
  305. тест на попадание точки в область зрения стоит от 3 до 15 умножений
  306. (относительно дешевые операции типа сложений не считаем), плюс 11 умножений
  307. и 2 деления на поворот и проецирование после отсечения, зато поворачиваются
  308. и проецируются только видимые точки. При отсечении после преобразований
  309. тест стоит 8 умножений (так как в координатах нормалей шесть нулей и одна
  310. единица), зато для всех точек придется сделать 9 умножений для поворота;
  311. проецироваться же по-прежнему будут только видимые точки. Так что наиболее
  312. подходящий метод выбирайте сами.
  313. <p>В завершение осталось только привести процедуру для отсечения полигона
  314. произвольной плоскостью:
  315. <pre class=source>
  316. // вычитание векторов
  317. float vecsub(vertex *result, vertex a, vertex b) {
  318. result->x = a.x - b.x;
  319. result->y = a.y - b.y;
  320. result->z = a.z - b.z;
  321. }
  322. // скалярное умножение векторов
  323. float vecmul(vertex a, vertex b) {
  324. return a.x * b.x + a.y * b.y + a.z * c.z;
  325. }
  326. // dst - массив для сохранения вершин результата
  327. // src - массив вершин исходного полигона
  328. // num - число вершин исходного полигона
  329. // n - нормаль к плоскости
  330. // o - точка, лежащая в плоскости
  331. // функция возвращает число вершин результата
  332. int clipPlane(vertex *dst, vertex *src, int num,
  333. vertex n, vertex o)
  334. {
  335. int i, r;
  336. vertex p1, p2, tmp;
  337. float t1, t2;
  338. float k;
  339. r = 0;
  340. for (i = 0; i < num; i++) {
  341. p1 = src[i];
  342. p2 = src[(i + 1) % num];
  343. vecsub(&tmp, p1, o); t1 = vecmul(tmp, n);
  344. vecsub(&tmp, p2, o); t2 = vecmul(tmp, n);
  345. if (t1 >= 0) { // если начало лежит в области
  346. dst[r++] = p1; // добавляем начало
  347. }
  348. // если ребро пересекает границу
  349. // добавляем точку пересечения
  350. if (((t1 > 0) && (t2 < 0)) ||
  351. ((t2 >= 0) && (t1 < 0)))
  352. {
  353. k = 1 - vecmul(p1, n) / vecmul(p2, n);
  354. dst[r].x = p1.x + k * (p2.x - p1.x);
  355. dst[r].y = p1.y + k * (p2.y - p1.y);
  356. dst[r].z = p1.z + k * (p2.z - p1.z);
  357. dst[r].u = p1.u + k * (p2.u - p1.u);
  358. dst[r].v = p1.v + k * (p2.v - p1.v);
  359. r++;
  360. }
  361. }
  362. return r;
  363. }
  364. </pre>
  365. </div>
  366. </td></table>
  367. <!-- Bottom Navigation -->
  368. <img src="../img/b.gif" width=500 height=1 alt=""><br><img src="../img/t.gif" width=500 height=2 alt=""><br>
  369. <table width=500 cellpadding=0 cellspacing=0 border=0>
  370. <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>
  371. <td><p class=pagetitle><img src="../img/t.gif" width=265 height=1 alt=""><br>demo.design<br>3D programming FAQ</td>
  372. <td align=center><p class=navy><a href="35.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>
  373. <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>
  374. <td align=center><p class=navy><a href="41.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>
  375. </table>
  376. <img src="../img/t.gif" width=500 height=4 alt=""><br>
  377. <img src="../img/b.gif" width=500 height=1 alt=""><br>
  378. <img src="../img/t.gif" width=500 height=1 alt=""><br>
  379. <img src="../img/b.gif" width=500 height=1 alt=""><br>
  380. </center></body>
  381. </html>