Проект "Выпуклая оболочка"
Аналитическая геометрия и программирование
В предыдущей секции мы столкнулись с необходимостью реализовать в классе R2Point ряд методов, которые позволяли бы вычислять расстояния между точками, сравнивать их на совпадение, выяснять, лежат ли три точки на одной прямой и не находится ли некоторая точка на прямой между двумя другими. Для реализации класса Polygon необходимо также уметь вычислять площадь треугольника и находить все освещенные ребра многоугольника. Решение задач на модификацию эталонного проекта потребует реализации еще целого ряда методов, связанных с различными геометрическими характеристиками.
Ряд фактов из аналитической геометрии и векторной алгебры являются необходимой базой для решения сформулированных задач, однако только знание элементов вычислительной геометрии позволяет получить достаточно эффективные решения. Простейшим примером может служить задача вычисления площади треугольника по известным координатам его вершин. Известная из средней школы формула Герона
требует большого числа умножений и четырех относительно медленно выполняемых операций извлечения квадратного корня. Основанная на связи площади с векторным произведением формула использует всего три умножения, одно из которых на самом деле сводится к сдвигу, что позволяет найти площадь треугольника значительно быстрее.Большую часть методов, о которых будет идти речь в текущей секции, целесообразно реализовать в виде методов класса (статических методов). Напомним, что такой метод не получает в качестве неявного аргумента конкретный экземпляр класса, и не может использовать ключевое слово this.
Расстояние между двумя точками на плоскости можно посчитать по известной формуле , которая и используется в реализации метода dist. Два объекта типа R2Point соответствуют совпадающим точкам плоскости тогда и только тогда, когда попарно равны их координаты, что позволяет реализовать метод equal.
public static double dist(R2Point a, R2Point b) { return Math.sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); } public static boolean equal(R2Point a, R2Point b) { return a.x==b.x && a.y==b.y; } public static double area(R2Point a, R2Point b, R2Point c) { return 0.5*((a.x-c.x)*(b.y-c.y)-(a.y-c.y)*(b.x-c.x)); }
Для вычисления площади треугольника в методе area применена уже цитированная выше формула, основанная на следующем факте из векторной алгебры: модуль векторного произведения двух векторов равен площади параллелограмма, натянутого на эти вектора. Напомним, что векторным произведением двух векторов и называется вектор , перпендикулярный векторам и , такой, что , где — угол между векторами и . Одно из двух возможных направлений вектора при этом определяется по правилу буравчика (при повороте ручки по направлению от к буравчик перемещается по направлению вектора ).
Часто полезно использовать запись этого определения в координатах. Если , а , то координаты вектора находятся по формуле
В том случае, когда вектора и расположены на плоскости, их векторное произведение имеет единственную не нулевую компоненту, вычисление которой и реализовано в методе area. Обратите внимание на то, что так вычисленная площадь является ориентированной, то есть может быть отрицательной.
Выяснение того факта, лежат ли три точки на одной прямой, сводится к вычислению площади треугольника и сравнению ее с нулем, а для того, чтобы точка прямой располагалась на отрезке этой же прямой, необходимо и достаточно принадлежности проекций этой точки проекциям на оси координат отрезка . Вот как выглядит программная реализация этих идей.
public static boolean isTriangle(R2Point a, R2Point b, R2Point c) { return area(a, b, c) != 0.0; } public boolean inside(R2Point a, R2Point b) { return (a.x <= x && x <= b.x || a.x >= x && x >= b.x) && (a.y <= y && y <= b.y || a.y >= y && y >= b.y); }
Перед тем, как реализовать метод light, позволяющий выяснить, освещено ли ребро выпуклой оболочки из точки , дадим строгое определение освещенности.
Определение 11.3. Ребро называется освещенным из точки , если ориентированная площадь треугольника, образованного точками , и отрицательна, либо если точка расположена на прямой, проходящей через точки и , вне отрезка .
Заметим, что при таком формальном определении понятия освещенности никакое ребро многоугольника не освещено ни из одной точки внутри или на границе многоугольника, что хорошо видно на рисунке рис. 11.5.
public boolean light(R2Point a, R2Point b) { double s = area(a, b, this); return s < 0.0 || ( s == 0.0 && ! inside(a, b)); }
В заключение этой секции рассмотрим более сложный вопрос — покажем, как можно наиболее эффективно выяснить, пересекаются ли два отрезка на плоскости, заданные координатами четырех их граничных точек.
На первом шаге решения этой задачи необходимо выяснить, пересекаются ли ограничивающие прямоугольники ( bounding boxes ) этих отрезков. Ограничивающий прямоугольник — это минимальный прямоугольник со сторонами, параллельными осям координат, содержащий отрезок.
Пусть координаты граничных точек первого отрезка равны и , а второго — и . Тогда координаты левого нижнего угла ограничивающего прямоугольника первого отрезка будут равны , а правого верхнего угла — , где , , и . Аналогично определяются координаты и левого нижнего и правого верхнего углов ограничивающего прямоугольника второго отрезка.
Ограничивающие прямоугольники пересекаются тогда и только тогда, когда пересекаются их проекции на каждую из двух осей, что сводится к истинности следующего предиката .
Если ограничивающие прямоугольники пересекаются, то необходимо дальнейшее исследование. Для начала можно выяснить, пересекает ли каждый из данных отрезков прямую, содержащую другой отрезок. Отрезок пересекает прямую, если его концы лежат по разные стороны от нее или если один из концов лежит на прямой.
Можно доказать (это рекомендуется сделать самостоятельно), что два отрезка пересекаются тогда и только тогда, когда пересекаются их ограничивающие прямоугольники и, кроме того, каждый из них пересекается с прямой, содержащей другой отрезок.
Проверка факта пересечения отрезка с прямой осуществляется с помощью векторных произведений следующим образом. Точки и лежат по разные стороны от прямой , если векторы и имеют различную ориентацию относительно вектора , то есть, если знаки векторных произведений и различны.
Таким образом, отрезки и пересекаются тогда и только тогда, когда одновременно выполняются следующие три условия:
1) пересекаются ограничивающие их прямоугольники;
2)
3) .
Более подробное изложение решения этой и ряда других подобных задач может быть найдено в разделе "Вычислительная геометрия" книги [8].