Изменения
→Алгоритм художника (painter's algorithm)
=== Алгоритм художника (painter's algorithm) ===
[[Файл:painters_algo.png|500px|right]]
Алгоритм художника избегает дополнительных затрат памяти, изначально сортируя объекты по расстоянию от них до точки обзора. Тогда объекты проверяются в так называемом порядке глубины, начиная от самого дальнего. В таком случае при рассмотрении объекта уже не нужна проверка его z-координаты, мы всегда пишем цвет в буфер кадра. Значения, хранимые в буфере ранее, просто перезаписываются.
[[Файл:triangle_cycle.png|150px|left]] Чтобы успешно применять данный метод, нужно уметь быстро сортировать объекты. К сожалению, это не всегда просто. Кроме того, порядок глубины не всегда существует: отношение "перед" может содержать циклы. Когда такое цикличное перекрытие происходит, объекты не могут быть корректно отсортированы. В таком случае мы должны разорвать циклы, разбив один или более объектов на части. (Картинка с примером)
Определение, какие объекты нужно разбить и где, затем сортировка их фрагментов {{---}} дорогой процесс, так как порядок зависит от положения точки обзора, и мы должны пересчитывать все при каждом ее смещении.
Чтобы использовать этот алгоритм в реальной жизни, например, в симуляторе полета, мы должны предпосчитать сцену так, чтобы можно было быстро найти корректный порядок отображения объектов для любой точки обзора.
Данную задачу можно элегантно решить при помощи техники '''двоичного разбиения пространства''' (англ. ''binary space partionpartitioning, BSP'').
== Определение Структура BSP-дерева ==
Чтобы понять, что из себя представляет двоичное разбиение пространства, рассмотрим рисунок. На нем показано двоичное разбиение множества объектов на плоскости и дерево, которое этому разбиению соответствует.
В двумерном случае BSP строится с помощью рекурсивного разбиения плоскости прямыми. В данном примере это происходит так: сначала проводим прямую l1<tex>l_1</tex>, затем разбиваем разбивая полуплоскость выше l1 <tex>l_1</tex> прямой l2<tex>l_2</tex>, а ниже {{---}} прямой l3 <tex>l_3</tex> и так далее.
[[Файл:bsp_plane1.png|300px]][[Файл:bsp_tree1.png|300px]] Прямые разбивают на фрагменты части не только плоскость, но и объекты, расположенные на ней. Разбиение продолжается до тех пор, пока внутри каждого фрагмента каждой грани плоскости окажется не более одного фрагмента объекта.
Этот процесс можно представить с помощью двоичного дерева. Каждый лист дерева соответствует грани разбиения, в нем хранится фрагмент объекта, находящийся внутри этой грани. Каждый узел дерева соответсвует разбивающей прямой, которая хранится в этом узле.
Рассмотрим гиперплоскость <tex>h: a_1*\cdot x_1 + a_2*\cdot x_2 + ... \ldots + a_d*\cdot x_d + a_{d + 1} = 0</tex>.
Пусть <tex>h^+</tex> {{---}} положительная полуплоскостьположительное полупространство, а <tex>h^-</tex> {{---}} отрицательнаяотрицательное:
<tex>h^+ = \{(x_1, \ x_2 ... ,\ \dots,\ x_d) : \mid a_1*\cdot x_1 + a_2*\cdot x_2 + ... \ldots + a_d*\cdot x_d + a_{d + 1} > 0\}</tex>
<tex>h^- = \{(x_1, \ x_2 ... ,\ \dots,\ x_d) : \mid a_1*\cdot x_1 + a_2*\cdot x_2 + ... \ldots + a_d*\cdot x_d + a_{d + 1} < 0\}</tex>
Пусть <tex>S</tex> {{---}} множество объектов, для которого мы строим забиение разбиение в <tex>d</tex>-мерном пространстве.
Пусть <tex>v</tex> {{---}} какая-то вершина дерева, тогда обозначим за <tex>S(v)</tex> множество объектов (возможно пустое), хранимых в этой вершине.
BSP-дерево <tex>T</tex> для этого множества обектов обладает следующими свойствами:
*Если <tex>|S| <= \leqslant 1</tex>, то <tex>T</tex> {{---}} лист. Фрагмент объекта в <tex>S</tex>, если он существует, хранится в этом листе.
*Если <tex>|S| > 1</tex>, то в корне дерева <tex>v</tex> хранится гиперплоскость <tex>h_v</tex> и множество <tex>S(v)</tex> объектов, которые полностью содержатся в <tex>h_v</tex>.
**левый ребенок <tex>v</tex> является корнем BSP -дерева <tex>T^-</tex> на множестве объектов <tex>S^- = \{h_v^- \cap s : \mid s \in S\}</tex>;**правый ребенок <tex>v</tex> является корнем BSP -дерева <tex>T^+</tex> на множестве объектов <tex>S^+ = \{h_v^+ \cap s : \mid s \in S\}</tex>.
Корню дерева соответсвует все пространство.
Таким образом, серая область на рисунке соответствует региону <tex>l_1^+ \cap l_2^+ \cap l_3^+-</tex>.
При построении BSP-дерева могут использоваться любые разбивающие гиперплоскости. В целях упрощения вычислений может быть удобно ограничить множество доступных разбивающих гиперплоскостей. Обычно используют авто-разбиения.
{{Определение
| definition = В двухмерном двумерном случае для множества отрезков разбиение, в котором используются разбивающие прямые, проходящие через один из данных отрезков, называется '''авто-разбивающим'''(англ. }}{{Определение| definition = В трехмерном случае для множества полигонов разбиение, в котором используются разбивающие плоскости, проходящие через одну из данных плоскостей, называется '''автоauto-разбивающим'''. }}{{Определение| definition = BSP-дерево, построенное с применением авто-разбиения, называются '''авто-разбивающим'partition'').
}}
== BSP-деревья и алгоритм художника ==
Пусть <tex>p_{view}</tex> {{---}} точка обзора, и она лежит над разбивающей плоскостью, хранимой в корне <tex>T</tex>.
Тогда ни один из объектов, лежащих под этой плоскостью, не может перекрыть ни один из объектов, лежащих выше нее. Таким образом, мы можем безопасно показать отрисовать фрагменты объектов из поддерева <tex>T^-</tex> до показа отрисовки объектов из поддерва <tex>T^+</tex>.
Порядок фрагментов объектов в поддеревьях определяется таким же способом.
На вход алгоритму подается <tex>S =\{s_1,\ s_2,\ \dots ,\ s_n\}</tex> {{---}} множество отрезков. <code> '''BSPTree''' 2D_BSP_tree(<tex>S</tex>): '''if''' <tex>|S| \leqslant 1</tex> <tex>T \leftarrow</tex> '''BSPTree'''(<tex>S</tex>) <font color="green">/* <tex>T</tex> будет листом, в котором хранится данное множество */</font> '''return''' <tex>T</tex> '''else''' <font color="green">/* используем <tex>l(s_1)</tex> как разбивающую прямую */</tex></font> <tex>S^+ \leftarrow \{s \cap l^+(s_1) \mid s \in S\}</tex> <tex>T^+ \leftarrow</tex> 2D_BSP_tree(<tex>S^+</tex>) <tex>S^- \leftarrow \{s \cap l^-(s_1) \mid s \in S\}</tex> <tex>T^- \leftarrow</tex> 2D_BSP_tree(<tex>S^-</tex>) <tex>S_v \leftarrow \{s \in S \mid s \subset l(s_1)\}</tex> <tex>T \leftarrow</tex> '''BSPTree'''(<tex>S_v,\ T^-,\ T^+</tex>) <font color= Построение "green">// создаем BSP-дерева =дерево c корнем в вершине <tex>v</tex>, левым поддеревом <tex>T^-</tex> и правым поддеревом <tex>T^+</tex> </font> <font color="green">// и множеством хранимых объектов <tex>S_v</tex></font> '''return''' <tex>T</tex> </code>При решении задач Данный алгоритм создает BSP-дерево для множества <tex>S</tex>, но как уже известно, оно не будет наименьшим в трехмерном пространстве бывает полезно сначала рассмотреть задачу общем случае. Необходимо придумать стратегию выбора прямой разбиения, а не просто брать <tex>l(s_1)</tex>. Возможным подходом является выбор отрезка <tex>s \in S</tex>, такого что <tex>l(s)</tex> пересекает наименьшее число отрезков. Но такой жадный алгоритм работает не на плоскостивсех конфигурациях отрезков. Кроме того, поиск такого отрезка {{---}} занятие затратное. Как и в других алгоритмах, когда нужно сделать сложный выбор, сделаем рандомный выбор. Это означает, что для разбиения мы и сделаембудем использовать случайно выбранный отрезок. Для этого перед тем, как начинать построение дерева, расположим отрезки в <tex>S</tex> случайном порядке.Пусть <code> '''void''' 2D_random_BSP_tree(<tex>S</tex>): <tex>S \leftarrow </tex> random_permutation(<tex>S</tex>) <tex>T \leftarrow </tex> 2D_BSP_tree(<tex>S </tex>) '''return''' <tex>T</tex></code>Перед анализированием рандомизированного алгоритма рассмотрим одну простую оптимизацию. Предположим, что мы выбрали несколько первых разбивающих прямых. Эти прямые порождают разбиение плоскости, грани которой соответствуют каким-то узлам BSP- множество дерева. [[Файл:bsp_free.png|300px|right]]Рассмотрим одну из таких граней <tex>f</tex>. В <tex>S</tex> могут быть отрезки, которые полностью пересекают <tex>f</tex>. Выбор одного из n непересекающихся таких отрезков для разбиения <tex>f</tex> не вызовет фрагментации других отрезков на плоскостивнутри <tex>f</tex>, так как данный отрезок исключается из дальнейшего рассмотрения. Ограничимся авто-Назовем такое свободным разбиением. Нашей улучшенной стратегией будет использование свободных разбиений везде, рассматривая где только прямыеможно, и использование случайных разбиений в противном случае. Для реализации данной оптимизации нужно уметь определять, вызывает ли отрезок свободное разбиение. Для этого сопоставим каждому отрезку две булевых переменных, которые покажут, содержащие один лежат ли правый и левый концы отрезка на какой-то из уже добавленных разбивающих прямых. Обе переменных истинны, когда отрезок вызывает свободное разбиение. Теперь оценим производительность алгоритма '''2D_random_BSP_tree'''. Для упрощения рассуждений будем анализировать версию без свободных разбиений (асимптотической разницы между ними нет). Начнем с анализа размера BSP-дерева, равного числу полученных фрагментов, которое зависит от сгенерированной перестановки отрезков. Некоторые перестановки могут породить маленькие деревья, а другие {{---}} большие. [[Файл:bsp_three_segments.png|500px]] В качестве примера рассмотрим три отрезка, изображенные на рисунке. Если они рассматриваются в порядке (a), то мы получаем пять фрагментов, если же в порядке (b) {{---}} то всего три фрагмента. Так как размер BSP-дерева зависит от сгенерированной перестановки, будем анализировать ожидаемый размер BSP-дерева {{---}} средний размер для всех <tex>n!</tex> перестановок. {{Лемма| statement = Ожидаемое число фрагментов, сгенерированных алгоритмом '''2D_random_BSP_tree''' есть <tex>\mathcal{O}(n \log n)</tex>.|proof = Пусть <tex>s_i</tex> {{---}} фиксированный отрезок из <tex>S</tex>. Проанализируем [[Математическое ожидание случайной величины | ожидаемое]] количество отрезков, которые мы разрежем, когда <tex>l(ss_i) - </tex> будет добавлена алгоритмом как следующая разбивающая прямая. Рассмотрим рисунок и постараемся понять, содержащая разрезается ли отрезок s<tex>s_j</tex> при добавлении прямой <tex>l(s_i)</tex>, в зависимости от отрезков, которые разрезаны <tex>l(s_i)</tex>, но находятся между <tex>s_i</tex> и <tex>s_j</tex>.На вход алгоритму подается S = {s1В частности, когда прямая, пересекающая такой отрезок, s2добавляется раньше <tex>l(s_i)</tex>, она закрывает <tex>s_j</tex> от <tex>s_i</tex>.На рисунке (b) так происходит с отрезком <tex>s_3</tex>, который защищен отрезком <tex>s_1</tex> от <tex>s_2</tex>.. sn} Эти размышления приводят нас к определению расстояния от какого- множество отрезковто отрезка до фиксированного отрезка <tex>s_i</tex>.
Так как отрезки расположены в случайном порядке, получаем:
<tex>P(l(s_i) разрезает \cap s_j\ne \varnothing) <= \leqslant \genfrac{}{}{}{0}{1 / (}{k + 2)}</tex> Так как существуют Существуют отрезки, которые не разрезаются <tex>l(s_i)</tex>, но расширение которых защитит <tex>s_j</tex>, так что выше записано неравенство. Теперь мы можем ограничить ожидаемое число разрезов, происходящих при добавлении <tex>s_i</tex>: <tex>E(</tex>число разрезов, происходящих при добавлении <tex>s_i) <= \leqslant \sum(\limits_{j \ne i != j, } \genfrac{}{}{}{0}{1 / }{\mathrm{dist}(k s_i, s_j) + 2)) <= } \leqslant 2 * \cdot \sum(\limits_{k=0..}^{n - 2, } \genfrac{}{}{}{0}{1/ (}{k + 2)) <= } \leqslant 2 * \ln n</tex>. По линейности ожиданий мы можем заключить, что ожидаемое число разрезов, вызванных добавлением всех отрезков составляет не более 2nlogn<tex>2n\log n</tex>.Так как изначально даны <tex>n </tex> отрезков, ожидаемое число фрагментов ограничено <tex>n + 2nlogn2n\log n</tex>.}} Было показано, что ожидаемый размер BSP-дерева, построенного с помощью алгоритма '''2D_random_BSP_tree''', составляет <tex>n + 2n\log n</tex>. Следовательно, мы доказали, что BSP-дерево размера <tex>n + 2n\log n</tex> существует для любого множества <tex>n</tex> отрезков. Кроме того, хотя бы половина перестановок приводит к BSP-дереву размера <tex>n + 4n\log n</tex>.
Мы можем использовать этот факт, чтобы найти дерево такого размера: после запуска алгоритма сравним размер дерева с данной оценкой, если он превышает оценку, просто построим BSP-дерево еще раз, но для новой перестановки. Ожидаемое число запусков равняется двум.
Теперь проанализируем время работы алгоритма. Понятно, что оно зависит от используемой перестановки, так что опять рассмотрим ожидаемое время работы. Нахождение рандомной перестановки занимает <tex>\mathcal{O}(n)</tex> при помощи [[Метод генерации случайной перестановки, алгоритм Фишера-Йетса |алгоритма Фишера-Йетса]]. Если проигнорировать время рекурсивных вызовов, то время работы алгоритма линейно от количества фрагментов в <tex>S</tex>. Это число не превышает <tex>n</tex>, так как становится меньше с каждым рекурсивным вызовом.Число рекурсивных вызовов ограничено количеством сгенерированных фрагментов, которое составляет <tex>\mathcal{O}(nlognn\log n)</tex>. Таким образом, время построения дерева составляет <tex>\mathcal{O}(n^2\log n)</tex>. {{Теорема| statement = В двумерном пространстве BSP-дерево размера <tex>\mathcal{O}(n\log n)</tex> может быть построено за ожидаемое время <tex>\mathcal{O}(n^2logn2\log n)</tex>.}} Описанный выше алгоритм легко обобщается на трёхмерное пространство. Как было упомянуто выше, мы считаем, что работаем в 3D с триангуляцией граней многранников. Пусть <tex>S</tex> {{---}} множество непересекающихся треугольков в <tex>\mathbb{R}^3</tex>. Снова ограничимся только авто-разбиениями, разбивая пространство плоскостями, содержащими какой-то из треугольников. Для треугольника <tex>t</tex> обозначим плоскость, содержащую его, как <tex>h(t)</tex>.На вход алгоритму подается множество треугольников <tex>S = \{t_1,\ t_2,\ \dots ,\ t_n\}</tex>, заданных в трехмерном пространстве.