Подход хорошего дизайна для создания библиотеки геометрии (относительно использования объединения или нет)?
Я делаю библиотеку геометрии, и я запутался, какой должен быть тип возврата функции, которая вычисляет пересечение сегмента с другим сегментом. Возвращаемое значение иногда будет точкой, а иногда сегментом (случай перекрытия), а иногда пустым набором. Согласно моему мнению, может быть 3 способа решения этой проблемы следующим образом: 1. вернуть объединение (отрезок, ноль, точку) 2. вернуть отрезок с первой точкой == вторая точка, когда пересечение является одной точкой, и обе точки как NAN, когда пересечение является пустым набором 3. вернуть вектор (с 0 элементами для пустого набора, 1 элементом для pnt и 2 элементами для сегмента)
Пожалуйста, дайте мне знать, если есть какие-либо возможные альтернативы и каковы плюсы и минусы каждого дизайна. Кроме того, какой дизайн должен быть хорошим дизайном и почему. Я заинтересован в создании надежной архитектуры, которая позволяет использовать единый конвейер и, следовательно, практически не переписывать код, а также масштабироваться (с точки зрения добавления функциональности и обработки всех крайних случаев)
Ниже приведен мой код для справки (тип возвращаемого значения - вектор)
vector<pnt> seg::inter(seg z){
vector<pnt> ans;
if(p1==p2){if(z.doesinter(p1)){ans.pb(p1);}}
else if(z.p1==z.p2){
if(doesinter(z.p1)) ans.pb(z.p1);}
else{
pnt p1p2=(p2-p1);
pnt q1=p1p2*pnt(0,1);
long double h1,h2;
if(abs((z.p2-z.p1).dot(q1))<=eps){
pnt r1((z.p1-p1)/(p2-p1)),r2((z.p2-p1)/(p2-p1));
if(abs(r1.y)<=eps){//colinear case
h1=r1.x;
h2=r2.x;
if(h1>h2)swap(h1,h2);
if(h2>=0&&h1<=1){//add eps
h1=max(0.0L,h1);h2=min(1.0L,h2);
ans.pb(p1+p1p2*h1);
if(doublecompare(h1,h2)==-1)ans.pb(p1+p1p2*h2);}}}
else{
h1 = ((p1-z.p1).dot(q1))/((z.p2-z.p1).dot(q1));
pnt q2 = (z.p2-z.p1)*pnt(0,1);
h2 = ((z.p1-p1).dot(q2))/((p2-p1).dot(q2));
if(h1+eps>=0&&h1-eps<=1&&h2+eps>=0&&h2-eps<=1) ans.pb(z.p1+(z.p2-z.p1)*h1);}}
return ans;}
3 ответа
Я предлагаю создать специализированный класс Intersection, который может обрабатывать все случаи. Вы можете вернуть экземпляр этого класса тогда. Внутри класс может иметь, например, векторное представление (с теми же конечными точками, если пересечение, как вы предлагали, с одной точкой), и может иметь методы для определения, в каком случае это на самом деле (bool isIntersecting()
, isSegment()
, так далее,).
Для более изощренного дизайна вы можете сделать этот класс Intersection абстрактным и предоставить специализированные реализации для NoIntersection, PointIntersection и SegmentIntersection с различным внутренним представлением данных.
union
"Идея" совершенно прекрасна, она естественно выражает все случаи. Однако я бы порекомендовал не использовать union
непосредственно из языка Си, потому что это низкоуровневая конструкция, которая может затруднить поиск ошибок.
Вместо этого вы должны использовать Boost.Variant.
В основном вариант представляет собой комбинацию из 2 элементов: тега и union
тег, используемый для указания того, какой член объединения используется в данный момент. Будучи классом C++, он осведомлен о C++ (не как union), и поэтому вы не столкнетесь с ограничениями на тип объектов, которые вы можете вставить, и вы также не столкнетесь с неопределенным поведением.
typedef boost::Variant<NoneType, Point, Segment> IntersectionType;
Конечно, вы также можете решить обернуть это в классе, чтобы предоставить более богатый интерфейс.
В Modern C++ Design Александреску объяснил мульти методы, используя в качестве примера именно вашу проблему.
Вы должны взглянуть.
http://loki-lib.sourceforge.net/index.php?n=Idioms.MultipleDispatcher