Изображение полиэдра
Некоторые технологические вопросы и оптимизация
Рассматриваемый нами проект является достаточно объемным, а его исходные тексты размещены во многих файлах, что делает особенно полезным использование ряда технологических приемов при работе с ним.
Применение утилиты make значительно облегчает решение задач на модификацию. Используемый в проекте Makefile позволяет корректно откомпилировать текст программы и стартовать процесс построения изображения любого из более, чем 15 полиэдров, файлы с данными которых содержатся в директории data. Для построения изображения куба, например, достаточно выполнения команды make cube.
Большая часть возможностей утилиты make, используемых в данном проекте, уже была рассмотрена ранее. Обсудим лишь те действия, которые выполняются при построении цели doc. Вот соответствующий фрагмент из управляющего файла.
doc: javadoc -d doc -version -author -private *.java
Задание на построение цели doc сводится, как видно из этой записи, к вызову утилиты javadoc, являющейся частью исполняющей среды языка Java, с определенным набором параметров. Эта утилита предназначена для автоматической генерации документации, связанной с программным проектом.
При самом первом знакомстве с языком Java было отмечено, что в нем предусмотрены три вида комментариев, из которых до сих пор мы имели дело только с двумя. Многострочный комментарий, начинающийся с символов /** и завершающийся символами */ предназначен для размещения в тексте программы комментариев, которые могут быть впоследствии обработаны утилитой javadoc с целью построения полноценной документации о программе.
Правильное использование этой утилиты позволяет получить описание классов и пакетов в гипертекстовом формате (на языке HTML), что чрезвычайно удобно. Программист обязан, конечно, при написании программы размещать в ее тексте необходимые директивы, распознаваемые утилитой javadoc. Все они, впрочем, имеют очень простые синтаксис и семантику.
Приведенный в последней секции параграфа полный текст проекта иллюстрирует использование таких комментариев. Иерархия классов проекта "Изображение полиэдра", приведенная выше на рисунке 13.3, является результатом работы утилиты javadoc. На рис. 13.7 можно увидеть еще один из фрагментов документации о проекте — часть алфавитного списка всех компонент, методов и классов.
Как легко убедиться, построенная программа работает достаточно медленно при построении изображения сложного полиэдра, хотя мы и пытались уже в процессе ее создания заботиться об эффективности. Сейчас будет предложен метод, носящий имя фильтрование граней \label{filtr}, который позволяет заметно ускорить работу программы.
Основное время в процессе нахождения плоского изображения полиэдра при обработке очередного ребра тратится на вычисление тени от всех граней. При этом значительная часть работы выполняется заведомо впустую — далекие от ребра грани не могут его затенить. Эта пустая трата времени тем значительнее, чем больше число ребер и граней, так как в этом случае больший процент граней можно было не рассматривать. Попробуем найти простой способ, который позволил бы разделить все грани на "далекие" и "близкие".
Хорошей идеей является попытка использования ограничивающих прямоугольников для проекций ребер и граней. Во-первых, ограничивающие прямоугольники легко быстро вычислить, а во-вторых, легко проверить, пересекаются ли два таких прямоугольника. Отсутствие пересечения прямоугольников при этом гарантирует отсутствие тени от грани на ребро.
Реализация описанной идеи позволяет заменить для большинства граней ресурсоемкую операцию нахождения тени от нее на определение пересечения ее ограничивающего прямоугольника (который должен вычисляться еще в конструкторе) с ограничивающим прямоугольником ребра (который тоже может быть вычислен заранее). Только в случае их пересечения необходимо будет вызывать метод вычисления тени.
Поскольку для каждого конкретного ребра большая часть граней является далекими, то скорость построения изображения полиэдра возрастет примерно во столько раз, во сколько фильтрование быстрее учета тени от грани на ребро. Таким образом, фильтр тем лучше, чем меньше граней он пропускает и чем быстрее он работает. Эти требования противоречат друг другу, поэтому требуется разумный компромисс — фильтр, который быстро отсеивает основную массу не имеющих отношения к ребру граней, пропуская, быть может, некоторое их количество, на самом деле ребро не затеняющих.
Другим возможным способом оптимизации построения изображения является использование двумерного хеширования граней. Основной целью этого метода является полный отказ от рассмотрения "далеких" граней при построении изображения конкретного ребра.
Для реализации этой идеи вся прямоугольная область изображения разбивается на небольшие квадратные гнезда и при инициализации полиэдра все множество его граней размещается в этих гнездах так, как это рекомендуется при реализации произвольного множества с помощью хеш-функции. При инициализации полиэдра в трехмерный массив (точнее массив массивов массивов) hashFacets, который рассматривается как одномерный список граней, имеющих отношение к конкретному гнезду, записываются все их номера. При построении же изображения каждого из ребер необходимо будет искать тень только от тех граней, которые находятся в гнездах, соответствующих ребру.
Класс SmartDrawer, полный текст которого приведен ниже, реализует эту идею.
Задачи для самостоятельного решения
Задача 13.2. Оптимизируйте текст эталонного проекта "Изображение полиэдра" так, чтобы:
a) устранить многократное изображение видимых частей ребер, связанное с особенностями задания полиэдра, описанными в начале лекции;
b) реализовать идею фильтрования граней, базируясь на реализации класса ShadowDrawer ;
c) минимизировать действия, выполняемые в классе ShadowDrawer, связанные с необходимостью обращения (умножения на минус единицу) нормалей к граням;
d) максимально исправить влияние ошибок округления при работе с числами, заданными в форме с плавающей точкой, которые приводят к тому, что отдельные видимые части ребер не изображаются и наоборот.
Задача 13.3. Модифицируйте текст эталонного проекта "Изображение полиэдра" так, чтобы:
a) невидимые части ребер изображались пунктиром;
b) грани, имеющие максимальную площадь среди полностью видимых граней, заштриховывалась;
c) изображалась только та часть полиэдра, которая попадает в полупространство, заданное с помощью точки на ограничивающей его плоскости и вектора внешней нормали (эта задача эквивалентна построению изображения предварительно усеченного полиэдра);
d) изображалось сечение полиэдра предварительно заданной плоскостью;
e) строились плоские изображения четырехмерных полиэдров.
Задача 13.4. Модифицируйте текст эталонного проекта "Изображение полиэдра" так, чтобы вычислялась и печаталась следующая величина:
a) количество видимых вершин;
b) количество полностью видимых ребер;
c) количество частично видимых ребер;
d) количество полностью видимых граней;
e) количество частично видимых граней;
f) количество ребер, удаленных от начала координат не более, чем на единичное растояние;
g) количество граней, удаленных от начала координат не более, чем на единичное растояние;
h) количество граней полиэдра, пересекающихся с заданным отрезком;
i) количество ребер полиэдра, пересекающихся с заданным отрезком;