From 64c54dc9a644b2e7d9d91c399137867b1574e7ca Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Mon, 19 Aug 2024 23:33:55 +0200 Subject: [PATCH 01/76] fix annotation warnings --- src/PartSegCore_compiled_backend/calc_bounds.pyx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PartSegCore_compiled_backend/calc_bounds.pyx b/src/PartSegCore_compiled_backend/calc_bounds.pyx index ab3f7e5..61b1615 100644 --- a/src/PartSegCore_compiled_backend/calc_bounds.pyx +++ b/src/PartSegCore_compiled_backend/calc_bounds.pyx @@ -45,7 +45,7 @@ def calc_bounds(labels: np.ndarray, components_num: Optional[int]=None): }[labels.ndim](labels, components_num) -def calc_bounds5(cnp.ndarray[label_types, ndim=5] labels, components_num: Py_ssize_t): +def calc_bounds5(cnp.ndarray[label_types, ndim=5] labels, Py_ssize_t components_num): """ Calculate the bounds of the specified labels in a 5-dimensional array. @@ -94,7 +94,7 @@ def calc_bounds5(cnp.ndarray[label_types, ndim=5] labels, components_num: Py_ssi return min_bound, max_bound -def calc_bounds4(cnp.ndarray[label_types, ndim=4] labels, components_num: Py_ssize_t): +def calc_bounds4(cnp.ndarray[label_types, ndim=4] labels, Py_ssize_t components_num): """ Calculate the bounds of the specified labels in a 4-dimensional array. @@ -137,7 +137,7 @@ def calc_bounds4(cnp.ndarray[label_types, ndim=4] labels, components_num: Py_ssi max_bound[label_val, 3] = max(max_bound[label_val, 3], x) return min_bound, max_bound -def calc_bounds3(cnp.ndarray[label_types, ndim=3] labels, components_num: Py_ssize_t): +def calc_bounds3(cnp.ndarray[label_types, ndim=3] labels, Py_ssize_t components_num): """ Calculate the bounds of the specified labels in a 3-dimensional array. @@ -178,7 +178,7 @@ def calc_bounds3(cnp.ndarray[label_types, ndim=3] labels, components_num: Py_ssi return min_bound, max_bound -def calc_bounds2(cnp.ndarray[label_types, ndim=2] labels, components_num: Py_ssize_t): +def calc_bounds2(cnp.ndarray[label_types, ndim=2] labels, Py_ssize_t components_num): """ Calculate the bounds of the specified labels in a 2-dimensional array. From 8122bd1c2529c9ca6410277f8fe49d7bc776006a Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 20 Aug 2024 00:06:19 +0200 Subject: [PATCH 02/76] add code for finding edge intersection --- setup.py | 23 +- .../triangulate.hpp | 46 ++++ .../triangulate.pyx | 252 ++++++++++++++++++ src/tests/test_triangulate.py | 43 +++ 4 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 src/PartSegCore_compiled_backend/triangulate.hpp create mode 100644 src/PartSegCore_compiled_backend/triangulate.pyx create mode 100644 src/tests/test_triangulate.py diff --git a/setup.py b/setup.py index fc519fd..b2f732c 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,8 @@ current_dir = os.path.dirname(os.path.abspath(__file__)) package_dir = os.path.join(current_dir, 'src') -cpp_standard = ['-std=c++11', '-g0', '-O2', '-DNDEBUG'] # "-DDEBUG", "-O0", "-ggdb3" ] +# cpp_standard = ['-std=c++11', '-g0', '-O2', '-DNDEBUG'] # "-DDEBUG", "-O0", "-ggdb3" ] +cpp_standard = ['-std=c++11', '-DDEBUG', '-O0', '-ggdb3', '-D_GLIBCXX_DEBUG'] sprawl_utils_path = [os.path.join(package_dir, 'PartSegCore_compiled_backend', 'sprawl_utils')] extra_link_args = [] @@ -22,6 +23,8 @@ else: omp = ['/openmp'] +define_macros = [('NPY_NO_DEPRECATED_API', 'NPY_1_7_API_VERSION')] + extensions = [ Extension( @@ -31,6 +34,7 @@ language='c++', extra_compile_args=cpp_standard, extra_link_args=cpp_standard, + define_macros=define_macros, ), Extension( 'PartSegCore_compiled_backend.sprawl_utils.path_sprawl_cython', @@ -39,6 +43,7 @@ language='c++', extra_compile_args=cpp_standard, extra_link_args=cpp_standard, + define_macros=define_macros, ), Extension( 'PartSegCore_compiled_backend.sprawl_utils.sprawl_utils', @@ -47,6 +52,7 @@ language='c++', extra_compile_args=cpp_standard, extra_link_args=cpp_standard, + define_macros=define_macros, ), Extension( 'PartSegCore_compiled_backend.sprawl_utils.fuzzy_distance', @@ -55,6 +61,7 @@ language='c++', extra_compile_args=cpp_standard, extra_link_args=cpp_standard, + define_macros=define_macros, ), Extension( 'PartSegCore_compiled_backend.color_image_cython', @@ -63,6 +70,7 @@ extra_compile_args=cpp_standard, extra_link_args=cpp_standard, language='c++', + define_macros=define_macros, ), Extension( 'PartSegCore_compiled_backend.calc_bounds', @@ -71,6 +79,7 @@ extra_compile_args=cpp_standard, extra_link_args=cpp_standard, language='c++', + define_macros=define_macros, ), Extension( 'PartSegCore_compiled_backend.multiscale_opening.mso_bind', @@ -79,6 +88,7 @@ extra_compile_args=cpp_standard, extra_link_args=cpp_standard, language='c++', + define_macros=define_macros, ), Extension( 'PartSegCore_compiled_backend._fast_unique', @@ -87,6 +97,7 @@ extra_compile_args=cpp_standard + omp, extra_link_args=cpp_standard + omp + extra_link_args, language='c++', + define_macros=define_macros, ), Extension( 'PartSegCore_compiled_backend._napari_mapping', @@ -95,6 +106,16 @@ extra_compile_args=cpp_standard + omp, extra_link_args=cpp_standard + omp + extra_link_args, language='c++', + define_macros=define_macros, + ), + Extension( + 'PartSegCore_compiled_backend.triangulate', + sources=['src/PartSegCore_compiled_backend/triangulate.pyx'], + include_dirs=[np.get_include()], + extra_compile_args=cpp_standard + omp, + extra_link_args=cpp_standard + omp + extra_link_args, + language='c++', + define_macros=define_macros, ), ] diff --git a/src/PartSegCore_compiled_backend/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulate.hpp new file mode 100644 index 0000000..c458f30 --- /dev/null +++ b/src/PartSegCore_compiled_backend/triangulate.hpp @@ -0,0 +1,46 @@ +struct Event { + float x; + float y; + int index; + bool is_left; + + Event(float x, float y, int index, bool is_left) : x(x), y(y), index(index), is_left(is_left) {} + Event() {} + + bool operator<(const Event& e) const { + if(y==e.y){ + if(x==e.x){ + if (is_left == e.is_left){ + return index < e.index; + } + return is_left < e.is_left; + } + return x < e.x; + } + return y < e.y; + } +}; + +struct PairHash{ + std::size_t operator()(const std::pair& p) const { + return std::hash()(p.first) * 31 + std::hash()(p.second); + } +}; + +struct Point{ + float x; + float y; + bool operator==(const Point& p) const { + return x == p.x && y == p.y; + } + Point(float x, float y) : x(x), y(y) {} + Point() {} + +}; + +struct Segment{ + Point left; + Point right; + Segment(Point left, Point right) : left(left), right(right) {} + Segment() {} +}; diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx new file mode 100644 index 0000000..2683494 --- /dev/null +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -0,0 +1,252 @@ +# distutils: define_macros=CYTHON_TRACE_NOGIL=1 +# distutils: language = c++ +# cython: language_level=3, boundscheck=False, wraparound=False, nonecheck=False, cdivision=True, embedsignature=True + +from collections.abc import Sequence + +import numpy as np + +cimport numpy as cnp + +from cython.operator cimport preincrement, predecrement, dereference as deref +from libcpp cimport bool +from libcpp.unordered_set cimport unordered_set +from libcpp.utility cimport pair +from libcpp.vector cimport vector +from libcpp.set cimport set as cpp_set +from libcpp.algorithm cimport sort + + +cdef extern from "triangulate.hpp": + + cdef cppclass Event: + float x + float y + int index + bool is_left + Event(float x, float y, int index, bool is_left) + Event() + + cdef cppclass PairHash: + size_t operator()(pair[int, int] p) const + + cdef cppclass Point: + float x + float y + Point(float x, float y) + Point() + operator==(const Point& other) const + + cdef cppclass Segment: + Point left + Point right + Segment(Point left, Point right) + Segment() + +cdef point_eq(Point a, Point b): + return a.x == b.x and a.y == b.y + +cdef bool cmp_event(Event a, Event b): + if a.y == b.y: + return a.x < b.x + return a.y < b.y + +cdef bool cmp_event_x(Event a, Event b): + if a.x == b.x: + return a.is_left > b.is_left + return a.x < b.x + + + + +cdef inline bool _on_segment(Point p, Point q, Point r): + if (q.x <= max(p.x, r.x) and q.x >= min(p.x, r.x) and + q.y <= max(p.y, r.y) and q.y >= min(p.y, r.y)): + return True + return False + + +def on_segment(p: Sequence[float], q: Sequence[float], r: Sequence[float]) -> bool: + """ Check if point q is on segment pr + + Parameters + ---------- + p: sequence of 2 floats + beginning of segment + q: sequence of 2 floats + point to check + r: sequence of 2 floats + end of segment + + Returns + ------- + bool: + True if q is on segment pr + """ + return _on_segment( + Point(p[0], p[1]), + Point(q[0], q[1]), + Point(r[0], r[1]) + ) + + +cdef int _orientation(Point p, Point q, Point r): + cdef float val + val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y) + if val == 0: + return 0 + return 1 if val > 0 else 2 + +def orientation(p: Sequence[float], q: Sequence[float], r: Sequence[float]) -> int: + """ Check orientation of 3 points + + Parameters + ---------- + p: sequence of 2 floats + first point + q: sequence of 2 floats + second point + r: sequence of 2 floats + third point + + Returns + ------- + int: + 0 if points are collinear, 1 if clockwise, 2 if counterclockwise + """ + return _orientation( + Point(p[0], p[1]), + Point(q[0], q[1]), + Point(r[0], r[1]) + ) + + +cdef bool _do_intersect(Segment s1, Segment s2): + cdef Point p1, q1, p2, q2 + cdef int o1, o2, o3, o4 + p1 = s1.left + q1 = s1.right + p2 = s2.left + q2 = s2.right + if point_eq(p1, p2) or point_eq(p1, q2) or point_eq(q1, p2) or point_eq(q1, q2): + return False + o1 = _orientation(p1, q1, p2) + o2 = _orientation(p1, q1, q2) + o3 = _orientation(p2, q2, p1) + o4 = _orientation(p2, q2, q1) + + if o1 != o2 and o3 != o4: + return True + + if o1 == 0 and _on_segment(p1, p2, q1): + return True + if o2 == 0 and _on_segment(p1, q2, q1): + return True + if o3 == 0 and _on_segment(p2, p1, q2): + return True + if o4 == 0 and _on_segment(p2, q1, q2): + return True + + return False + + +def do_intersect(s1: Sequence[Sequence[float]], s2: Sequence[Sequence[float]]) -> bool: + """ Check if two segments intersect + + Parameters + ---------- + s1: sequence of 2 sequences of 2 floats + first segment + s2: sequence of 2 sequences of 2 floats + second segment + + Returns + ------- + bool: + True if segments intersect + """ + return _do_intersect( + Segment(Point(s1[0][0], s1[0][1]), Point(s1[1][0], s1[1][1])), + Segment(Point(s2[0][0], s2[0][1]), Point(s2[1][0], s2[1][1])) + ) + + +cdef cpp_set[Event].iterator pred(cpp_set[Event]& s, cpp_set[Event].iterator it): + if it == s.begin(): + return s.end() + return predecrement(it) + +cdef cpp_set[Event].iterator succ(cpp_set[Event]& s, cpp_set[Event].iterator it): + return preincrement(it) + + +cdef unordered_set[pair[int, int], PairHash] _find_intersections(vector[Segment] segments): + cdef unordered_set[pair[int, int], PairHash] intersections + cdef vector[Event] events + cdef cpp_set[Event] active, viewed + cdef Py_ssize_t i, j + cdef Event current #, next_event, prev_event + cdef int current_index + cdef cpp_set[Event].iterator next_, prev, it + cdef bool flag + + events.reserve(2 * segments.size()) + for i in range(segments.size()): + events.push_back(Event(segments[i].left.x, segments[i].left.y, i, True)) + events.push_back(Event(segments[i].right.x, segments[i].right.y, i, False)) + + sort(events.begin(), events.end(), cmp_event_x) + + for i in range(events.size()): + current = events[i] + current_index = current.index + if current.is_left: + next_ = active.lower_bound(current) + prev = pred(active, next_) + # next_event = dereference(next_) + # prev_event = dereference(prev) + flag = False + if next_ != active.end() and _do_intersect(segments[current_index], segments[deref(next_).index]): + if current_index < deref(next_).index: + intersections.insert(pair[int, int](current_index, deref(next_).index)) + else: + intersections.insert(pair[int, int](deref(next_).index, current_index)) + if prev != active.end() and _do_intersect(segments[current_index], segments[deref(prev).index]): + if current_index < deref(prev).index: + intersections.insert(pair[int, int](current_index, deref(prev).index)) + else: + intersections.insert(pair[int, int](deref(prev).index, current_index)) + active.insert(current) + viewed.insert(current) + else: + it = active.find(Event(segments[current_index].left.x, segments[current_index].left.y, current_index, True)) + next_ = succ(active, it) + prev = pred(active, it) + # next_event = dereference(next_) + # prev_event = dereference(prev) + if next_ != active.end() and prev != active.end() and _do_intersect(segments[deref(next_).index], segments[deref(prev).index]): + if deref(next_).index < deref(prev).index: + intersections.insert(pair[int, int](deref(next_).index, deref(prev).index)) + else: + intersections.insert(pair[int, int](deref(prev).index, deref(next_).index)) + active.erase(it) + return intersections + + +def find_intersections(segments: Sequence[Sequence[Sequence[float]]]) -> list[tuple[int]]: + """ Find intersections between segments""" + cdef vector[Segment] segments_vector + cdef unordered_set[pair[int, int], PairHash] intersections + cdef pair[int, int] p + cdef list result = [] + segments_vector.reserve(len(segments)) + + for segment in segments: + if segment[0][0] < segment[1][0]: + segments_vector.push_back(Segment(Point(segment[0][0], segment[0][1]), Point(segment[1][0], segment[1][1]))) + else: + segments_vector.push_back(Segment(Point(segment[1][0], segment[1][1]), Point(segment[0][0], segment[0][1]))) + intersections = _find_intersections(segments_vector) + for p in intersections: + result.append((p.first, p.second)) + return result diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py new file mode 100644 index 0000000..bcc2c3d --- /dev/null +++ b/src/tests/test_triangulate.py @@ -0,0 +1,43 @@ +from PartSegCore_compiled_backend.triangulate import on_segment, orientation, do_intersect, find_intersections + + +def test_on_segment(): + assert on_segment((0, 0), (0, 1), (0, 2)) + assert not on_segment((0, 0), (1, 1), (0, 3)) + + +def test_orientation(): + assert orientation((0, 0), (0, 1), (0, 2)) == 0 + assert orientation((0, 0), (0, 2), (0, 1)) == 0 + assert orientation((0, 2), (0, 0), (0, 1)) == 0 + assert orientation((0, 0), (0, 1), (1, 2)) == 1 + assert orientation((0, 0), (0, 1), (-1, 2)) == 2 + + +def test_do_intersect(): + assert do_intersect(((0, -1), (0, 1)), ((1, 0), (-1, 0))) + assert not do_intersect(((0, -1), (0, 1)), ((1, 0), (2, 0))) + + +def test_find_intersections(): + """ + First test case: + (1, 0) --- (1, 1) + | | + (0, 0) --- (0, 1) + + Second test case: + (1, 0) --- (1, 1) + \ / + \ / + \ / + X + / \ + / \ + / \ + (0, 0) --- (0, 1) + """ + assert find_intersections([[(0, 0), (0, 1)], [(0, 1), (1, 1)], [(1, 1), (1, 0)], [(1, 0), (0, 0)]]) == [] + assert find_intersections([[(0, 0), (0, 1)], [(0, 1), (1, 0)], [(1, 0), (1, 1)], [(1, 1), (0, 0), (0, 1)]]) == [ + (1, 3) + ] From 3f693c018eb0edd2b8a94efc041bb0ac32992152 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 20 Aug 2024 12:20:43 +0200 Subject: [PATCH 03/76] initial implementation for convex polygons --- .../triangulate.hpp | 9 + .../triangulate.pyx | 183 +++++++++++++++++- src/tests/test_triangulate.py | 28 ++- 3 files changed, 211 insertions(+), 9 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulate.hpp index c458f30..d9f6814 100644 --- a/src/PartSegCore_compiled_backend/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulate.hpp @@ -44,3 +44,12 @@ struct Segment{ Segment(Point left, Point right) : left(left), right(right) {} Segment() {} }; + + +struct Triangle{ + int x; + int y; + int z; + Triangle(int x, int y, int z) : x(x), y(y), z(z) {} + Triangle() {} +}; diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index 2683494..c27ba04 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -13,6 +13,7 @@ from libcpp cimport bool from libcpp.unordered_set cimport unordered_set from libcpp.utility cimport pair from libcpp.vector cimport vector +from libcpp.unordered_map cimport unordered_map from libcpp.set cimport set as cpp_set from libcpp.algorithm cimport sort @@ -43,15 +44,28 @@ cdef extern from "triangulate.hpp": Segment(Point left, Point right) Segment() -cdef point_eq(Point a, Point b): + cdef cppclass Triangle: + int x + int y + int z + Triangle(int x, int y, int z) + Triangle() + + +cdef point_eq(const Point& a, const Point& b): return a.x == b.x and a.y == b.y -cdef bool cmp_event(Event a, Event b): +cdef bool cmp_point(const Point& a, const Point& b): + if a.x == b.x: + return a.y < b.y + return a.x < b.x + +cdef bool cmp_event(const Event& a, const Event& b): if a.y == b.y: return a.x < b.x return a.y < b.y -cdef bool cmp_event_x(Event a, Event b): +cdef bool cmp_event_x(const Event& a, const Event& b): if a.x == b.x: return a.is_left > b.is_left return a.x < b.x @@ -59,7 +73,7 @@ cdef bool cmp_event_x(Event a, Event b): -cdef inline bool _on_segment(Point p, Point q, Point r): +cdef inline bool _on_segment(const Point& p, const Point& q, const Point& r): if (q.x <= max(p.x, r.x) and q.x >= min(p.x, r.x) and q.y <= max(p.y, r.y) and q.y >= min(p.y, r.y)): return True @@ -90,7 +104,7 @@ def on_segment(p: Sequence[float], q: Sequence[float], r: Sequence[float]) -> bo ) -cdef int _orientation(Point p, Point q, Point r): +cdef int _orientation(const Point& p, const Point& q, const Point& r): cdef float val val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y) if val == 0: @@ -121,7 +135,7 @@ def orientation(p: Sequence[float], q: Sequence[float], r: Sequence[float]) -> i ) -cdef bool _do_intersect(Segment s1, Segment s2): +cdef bool _do_intersect(const Segment& s1, const Segment& s2): cdef Point p1, q1, p2, q2 cdef int o1, o2, o3, o4 p1 = s1.left @@ -180,7 +194,7 @@ cdef cpp_set[Event].iterator succ(cpp_set[Event]& s, cpp_set[Event].iterator it return preincrement(it) -cdef unordered_set[pair[int, int], PairHash] _find_intersections(vector[Segment] segments): +cdef unordered_set[pair[int, int], PairHash] _find_intersections(const vector[Segment]& segments): cdef unordered_set[pair[int, int], PairHash] intersections cdef vector[Event] events cdef cpp_set[Event] active, viewed @@ -233,7 +247,7 @@ cdef unordered_set[pair[int, int], PairHash] _find_intersections(vector[Segment] return intersections -def find_intersections(segments: Sequence[Sequence[Sequence[float]]]) -> list[tuple[int]]: +def find_intersections(segments: Sequence[Sequence[Sequence[float]]]) -> list[tuple[int, int]]: """ Find intersections between segments""" cdef vector[Segment] segments_vector cdef unordered_set[pair[int, int], PairHash] intersections @@ -250,3 +264,156 @@ def find_intersections(segments: Sequence[Sequence[Sequence[float]]]) -> list[tu for p in intersections: result.append((p.first, p.second)) return result + + +cdef Point _find_intersection(const Segment& s1, const Segment& s2): + cdef float a1, b1, c1, a2, b2, c2, det, x, y + a1 = s1.right.y - s1.left.y + b1 = s1.left.x - s1.right.x + c1 = a1 * s1.left.x + b1 * s1.left.y + a2 = s2.right.y - s2.left.y + b2 = s2.left.x - s2.right.x + c2 = a2 * s2.left.x + b2 * s2.left.y + det = a1 * b2 - a2 * b1 + if det == 0: + return Point(0, 0) + x = (b2 * c1 - b1 * c2) / det + y = (a1 * c2 - a2 * c1) / det + return Point(x, y) + + +def find_intersection(s1: Sequence[Sequence[float]], s2: Sequence[Sequence[float]]) -> tuple[float, float]: + """ Find intersection between two segments + + Parameters + ---------- + s1: sequence of 2 sequences of 2 floats + first segment + s2: sequence of 2 sequences of 2 floats + second segment + + Returns + ------- + sequence of 2 floats: + intersection point + """ + cdef Point p = _find_intersection( + Segment(Point(s1[0][0], s1[0][1]), Point(s1[1][0], s1[1][1])), + Segment(Point(s2[0][0], s2[0][1]), Point(s2[1][0], s2[1][1])) + ) + return (p.x, p.y) + + +cdef bool _is_convex(const vector[Point]& polygon): + cdef Py_ssize_t i, j, k + cdef int orientation = 0, triangle_orientation + cdef float val + for i in range(polygon.size()-2): + j = (i + 1) + k = (i + 2) + triangle_orientation = _orientation(polygon[i], polygon[j], polygon[k]) + if triangle_orientation == 0: + continue + if orientation == 0: + orientation = triangle_orientation + elif orientation != triangle_orientation: + return False + triangle_orientation = _orientation(polygon[polygon.size()-2], polygon[polygon.size()-1], polygon[0]) + if triangle_orientation != 0 and orientation != triangle_orientation: + return False + triangle_orientation = _orientation(polygon[polygon.size()-1], polygon[0], polygon[1]) + if triangle_orientation != 0 and orientation != triangle_orientation: + return False + + return True + + +def is_convex(polygon: Sequence[Sequence[float]]) -> bool: + """ Check if polygon is convex""" + cdef vector[Point] polygon_vector + cdef pair[bool, vector[int]] result + + polygon_vector.reserve(len(polygon)) + for point in polygon: + polygon_vector.push_back(Point(point[0], point[1])) + + return _is_convex(polygon_vector) + + +cdef vector[Triangle] _triangle_convex_polygon(const vector[Point]& polygon): + cdef vector[Triangle] result + cdef Py_ssize_t start_index, i, size, current_index + size = polygon.size() + for i in range(1, size-1): + if _orientation(polygon[0], polygon[i], polygon[i+1]) != 0: + result.push_back(Triangle(0, i, i+1)) + + return result + + +cdef vector[Triangle] _triangulate_polygon(vector[Point] polygon): + cdef vector[Segment] edges, edges_with_intersections + cdef Py_ssize_t i, j, edges_count + cdef unordered_set[pair[int, int], PairHash] intersections + cdef unordered_map[int, vector[Point]] intersections_points + cdef pair[int, vector[Point]] p_it + cdef vector[Triangle] triangles + cdef vector[Point] intersections_points_vector + cdef Point p_int + cdef pair[int, int] p + + if _is_convex(polygon): + return _triangle_convex_polygon(polygon) + + edges.reserve(polygon.size()) + for i in range(polygon.size() - 1): + if polygon[i].x < polygon[i+1].x: + edges.push_back(Segment(polygon[i], polygon[i+1])) + else: + edges.push_back(Segment(polygon[i+1], polygon[i])) + + intersections = _find_intersections(edges) + intersections_points.reserve(intersections.size()) + for p in intersections: + p_int = _find_intersection(edges[p.first], edges[p.second]) + intersections_points[p.first].push_back(p_int) + intersections_points[p.second].push_back(p_int) + + edges_count = edges.size() + for p_it in intersections_points: + edges_count += p_it.second.size() - 1 + + edges_with_intersections.reserve(edges_count) + + for i in range(edges.size()): + if not intersections_points.count(i): + edges_with_intersections.push_back(edges[i]) + else: + intersections_points_vector = intersections_points.at(i) + intersections_points_vector.push_back(edges[i].left) + intersections_points_vector.push_back(edges[i].right) + sort(intersections_points_vector.begin(), intersections_points_vector.end(), cmp_point) + for j in range(intersections_points_vector.size() - 1): + edges_with_intersections.push_back(Segment(intersections_points_vector[j], intersections_points_vector[j+1])) + + return triangles + + +def triangulate_polygon(polygon: Sequence[Sequence[float]]) -> list[tuple[int, int, int]]: + """ Triangulate polygon""" + cdef vector[Point] polygon_vector + cdef Point p1, p2 + cdef vector[Triangle] result + + + polygon_vector.reserve(len(polygon)) + polygon_vector.push_back(Point(polygon[0][0], polygon[0][1])) + for point in polygon[1:]: + p1 = polygon_vector[polygon_vector.size() - 1] + p2 = Point(point[0], point[1]) + if not point_eq(p1, p2): + # prevent from adding the same point twice + polygon_vector.push_back(p2) + + result = _triangulate_polygon(polygon_vector) + return [(triangle.x, triangle.y, triangle.z) for triangle in result] diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index bcc2c3d..829f23d 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -1,4 +1,12 @@ -from PartSegCore_compiled_backend.triangulate import on_segment, orientation, do_intersect, find_intersections +from PartSegCore_compiled_backend.triangulate import ( + on_segment, + orientation, + do_intersect, + find_intersections, + find_intersection, + is_convex, + triangulate_polygon, +) def test_on_segment(): @@ -41,3 +49,21 @@ def test_find_intersections(): assert find_intersections([[(0, 0), (0, 1)], [(0, 1), (1, 0)], [(1, 0), (1, 1)], [(1, 1), (0, 0), (0, 1)]]) == [ (1, 3) ] + + +def test_find_intersection(): + assert find_intersection(((0, 0), (2, 2)), ((0, 2), (2, 0))) == (1, 1) + + +def test_is_convex(): + assert is_convex([(0, 0), (0, 1), (1, 1), (1, 0)]) + assert not is_convex([(0, 0), (0, 1), (1, 0), (1, 1)]) + + assert is_convex([(0, 0), (0, 0.5), (0, 1), (1, 1), (1, 0)]) + assert not is_convex([(0, 0), (0.1, 0.5), (0, 1), (1, 1), (1, 0)]) + + +def test_triangulate_polygon(): + assert triangulate_polygon([(0, 0), (0, 1), (1, 1), (1, 0)]) == [(0, 1, 2), (0, 2, 3)] + assert triangulate_polygon([(0, 0), (0, 10), (1, 10), (1, 0)]) == [(0, 1, 2), (0, 2, 3)] + assert triangulate_polygon([(0, 0), (0, 0.5), (0, 1), (1, 1), (1, 0)]) == [(0, 2, 3), (0, 3, 4)] From 1918a8f735c10fb7aac0ff7cca27915712fae4f3 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 20 Aug 2024 14:05:52 +0200 Subject: [PATCH 04/76] move part of conde to c++ --- .../triangulate.hpp | 75 +++++++++++++++++++ .../triangulate.pyx | 72 ++---------------- 2 files changed, 82 insertions(+), 65 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulate.hpp index d9f6814..8eddb74 100644 --- a/src/PartSegCore_compiled_backend/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulate.hpp @@ -1,3 +1,7 @@ +#include +#include +#include + struct Event { float x; float y; @@ -53,3 +57,74 @@ struct Triangle{ Triangle(int x, int y, int z) : x(x), y(y), z(z) {} Triangle() {} }; + + +bool point_eq(const Point& p, const Point& q){ + return p.x == q.x && p.y == q.y; +} + +bool cmp_point(const Point& p, const Point& q){ + if(p.x == q.x){ + return p.y < q.y; + } + return p.x < q.x; +} + +bool cmp_event(const Event& p, const Event& q){ + return p < q; +} + + +bool _on_segment(const Point& p, const Point& q, const Point& r){ + if (q.x <= std::max(p.x, r.x) && q.x >= std::min(p.x, r.x) && + q.y <= std::max(p.y, r.y) && q.y >= std::min(p.y, r.y)) + return true; + return false; +} + +int _orientation(const Point& p, const Point& q, const Point& r){ + float val = (q.y - p.y) * (r.x - q.x) - + (q.x - p.x) * (r.y - q.y); + if (val == 0) return 0; + return (val > 0) ? 1 : 2; +} + + +bool _do_intersect(const Segment& s1, const Segment& s2){ + const Point& p1 = s1.left; + const Point& q1 = s1.right; + const Point& p2 = s2.left; + const Point& q2 = s2.right; + + int o1 = _orientation(p1, q1, p2); + int o2 = _orientation(p1, q1, q2); + int o3 = _orientation(p2, q2, p1); + int o4 = _orientation(p2, q2, q1); + + if (o1 != o2 && o3 != o4) + return true; + + if (o1 == 0 && _on_segment(p1, p2, q1)) return true; + if (o2 == 0 && _on_segment(p1, q2, q1)) return true; + if (o3 == 0 && _on_segment(p2, p1, q2)) return true; + if (o4 == 0 && _on_segment(p2, q1, q2)) return true; + + return false; +} + +std::set::iterator pred(std::set& s, std::set::iterator it){ + if(it == s.begin()){ + return s.end(); + } + return --it; +} + +std::set::iterator succ(std::set& s, std::set::iterator it){ + return ++it; +} + +std::unordered_map, int, PairHash> _find_intersections(const std::vector& segments){ + std::unordered_map, int, PairHash> intersections; + std::vector events; + events.reserve(2 * segments.size()); +} diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index c27ba04..87e8696 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -51,33 +51,12 @@ cdef extern from "triangulate.hpp": Triangle(int x, int y, int z) Triangle() - -cdef point_eq(const Point& a, const Point& b): - return a.x == b.x and a.y == b.y - -cdef bool cmp_point(const Point& a, const Point& b): - if a.x == b.x: - return a.y < b.y - return a.x < b.x - -cdef bool cmp_event(const Event& a, const Event& b): - if a.y == b.y: - return a.x < b.x - return a.y < b.y - -cdef bool cmp_event_x(const Event& a, const Event& b): - if a.x == b.x: - return a.is_left > b.is_left - return a.x < b.x - - - - -cdef inline bool _on_segment(const Point& p, const Point& q, const Point& r): - if (q.x <= max(p.x, r.x) and q.x >= min(p.x, r.x) and - q.y <= max(p.y, r.y) and q.y >= min(p.y, r.y)): - return True - return False + bool point_eq(const Point& a, const Point& b) + bool cmp_point(const Point& a, const Point& b) + bool cmp_event(const Event& p, const Event& q) + bool _on_segment(const Point& p, const Point& q, const Point& r) + int _orientation(const Point& p, const Point& q, const Point& r) + bool _do_intersect(const Segment& s1, const Segment& s2) def on_segment(p: Sequence[float], q: Sequence[float], r: Sequence[float]) -> bool: @@ -103,14 +82,6 @@ def on_segment(p: Sequence[float], q: Sequence[float], r: Sequence[float]) -> bo Point(r[0], r[1]) ) - -cdef int _orientation(const Point& p, const Point& q, const Point& r): - cdef float val - val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y) - if val == 0: - return 0 - return 1 if val > 0 else 2 - def orientation(p: Sequence[float], q: Sequence[float], r: Sequence[float]) -> int: """ Check orientation of 3 points @@ -135,35 +106,6 @@ def orientation(p: Sequence[float], q: Sequence[float], r: Sequence[float]) -> i ) -cdef bool _do_intersect(const Segment& s1, const Segment& s2): - cdef Point p1, q1, p2, q2 - cdef int o1, o2, o3, o4 - p1 = s1.left - q1 = s1.right - p2 = s2.left - q2 = s2.right - if point_eq(p1, p2) or point_eq(p1, q2) or point_eq(q1, p2) or point_eq(q1, q2): - return False - o1 = _orientation(p1, q1, p2) - o2 = _orientation(p1, q1, q2) - o3 = _orientation(p2, q2, p1) - o4 = _orientation(p2, q2, q1) - - if o1 != o2 and o3 != o4: - return True - - if o1 == 0 and _on_segment(p1, p2, q1): - return True - if o2 == 0 and _on_segment(p1, q2, q1): - return True - if o3 == 0 and _on_segment(p2, p1, q2): - return True - if o4 == 0 and _on_segment(p2, q1, q2): - return True - - return False - - def do_intersect(s1: Sequence[Sequence[float]], s2: Sequence[Sequence[float]]) -> bool: """ Check if two segments intersect @@ -209,7 +151,7 @@ cdef unordered_set[pair[int, int], PairHash] _find_intersections(const vector[Se events.push_back(Event(segments[i].left.x, segments[i].left.y, i, True)) events.push_back(Event(segments[i].right.x, segments[i].right.y, i, False)) - sort(events.begin(), events.end(), cmp_event_x) + sort(events.begin(), events.end(), cmp_event) for i in range(events.size()): current = events[i] From 4887d75bc422e41f48efb11db72f2655e7d4f28f Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 20 Aug 2024 14:14:01 +0200 Subject: [PATCH 05/76] avoid name collision --- src/PartSegCore_compiled_backend/triangulate.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulate.hpp index 8eddb74..d316f8e 100644 --- a/src/PartSegCore_compiled_backend/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulate.hpp @@ -123,7 +123,7 @@ std::set::iterator succ(std::set& s, std::set::iterator it) return ++it; } -std::unordered_map, int, PairHash> _find_intersections(const std::vector& segments){ +std::unordered_map, int, PairHash> __find_intersections(const std::vector& segments){ std::unordered_map, int, PairHash> intersections; std::vector events; events.reserve(2 * segments.size()); From 0fb8cae675b4abde651d71a432430518be1e239e Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Wed, 21 Aug 2024 00:31:55 +0200 Subject: [PATCH 06/76] fix bug in code --- src/PartSegCore_compiled_backend/triangulate.hpp | 12 +++++++----- src/PartSegCore_compiled_backend/triangulate.pyx | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulate.hpp index d316f8e..2483d30 100644 --- a/src/PartSegCore_compiled_backend/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulate.hpp @@ -12,16 +12,16 @@ struct Event { Event() {} bool operator<(const Event& e) const { - if(y==e.y){ - if(x==e.x){ + if (x == e.x) { + if(y==e.y){ if (is_left == e.is_left){ return index < e.index; } - return is_left < e.is_left; + return is_left > e.is_left; } - return x < e.x; + return y < e.y; } - return y < e.y; + return x < e.x; } }; @@ -95,6 +95,8 @@ bool _do_intersect(const Segment& s1, const Segment& s2){ const Point& q1 = s1.right; const Point& p2 = s2.left; const Point& q2 = s2.right; + if (point_eq(p1, p2) || point_eq(p1, q2) || point_eq(q1, p2) || point_eq(q1, q2)) + return false; int o1 = _orientation(p1, q1, p2); int o2 = _orientation(p1, q1, q2); diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index 87e8696..2025d64 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -198,7 +198,7 @@ def find_intersections(segments: Sequence[Sequence[Sequence[float]]]) -> list[tu segments_vector.reserve(len(segments)) for segment in segments: - if segment[0][0] < segment[1][0]: + if segment[0][0] < segment[1][0] or (segment[0][0] == segment[1][0] and segment[0][1] < segment[1][1]): segments_vector.push_back(Segment(Point(segment[0][0], segment[0][1]), Point(segment[1][0], segment[1][1]))) else: segments_vector.push_back(Segment(Point(segment[1][0], segment[1][1]), Point(segment[0][0], segment[0][1]))) From af3754c11d1c7875e62a0b638ee88d925c27ddf5 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Wed, 21 Aug 2024 19:21:19 +0200 Subject: [PATCH 07/76] rewrtte finding intersection to c++ --- .../triangulate.hpp | 103 +++++++++++++++--- .../triangulate.pyx | 92 +--------------- 2 files changed, 96 insertions(+), 99 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulate.hpp index 2483d30..c3eba54 100644 --- a/src/PartSegCore_compiled_backend/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulate.hpp @@ -2,6 +2,25 @@ #include #include + +struct Point{ + float x; + float y; + bool operator==(const Point& p) const { + return x == p.x && y == p.y; + } + Point(float x, float y) : x(x), y(y) {} + Point() {} + + bool operator<(const Point& p) const { + if (this->x == p.x) { + return this->y < p.y; + } + return this->x < p.x; + } +}; + + struct Event { float x; float y; @@ -9,6 +28,7 @@ struct Event { bool is_left; Event(float x, float y, int index, bool is_left) : x(x), y(y), index(index), is_left(is_left) {} + Event(const Point& p, int index, bool is_left): x(p.x), y(p.y), index(index), is_left(is_left) {} Event() {} bool operator<(const Event& e) const { @@ -31,21 +51,20 @@ struct PairHash{ } }; -struct Point{ - float x; - float y; - bool operator==(const Point& p) const { - return x == p.x && y == p.y; - } - Point(float x, float y) : x(x), y(y) {} - Point() {} - -}; struct Segment{ Point left; Point right; - Segment(Point left, Point right) : left(left), right(right) {} + Segment(Point p1, Point p2){ + if (p1 < p2){ + this->left = p1; + this->right = p2; + } else { + this->left = p2; + this->right = p1; + } + + } Segment() {} }; @@ -125,8 +144,66 @@ std::set::iterator succ(std::set& s, std::set::iterator it) return ++it; } -std::unordered_map, int, PairHash> __find_intersections(const std::vector& segments){ - std::unordered_map, int, PairHash> intersections; +std::unordered_set, PairHash> +_find_intersections(const std::vector& segments){ + std::unordered_set, PairHash> intersections; std::vector events; + std::set active; events.reserve(2 * segments.size()); + for (int i=0; iis_left){ + auto next = active.lower_bound(*event); + auto prev = pred(active, next); + if (next != active.end() && _do_intersect(segments[event->index], segments[next->index])){ + if (event->index < next->index){ + intersections.emplace(event->index, next->index); + } else { + intersections.emplace(next->index, event->index); + } + } + if (prev != active.end() && _do_intersect(segments[event->index], segments[prev->index])){ + if (event->index < prev->index){ + intersections.emplace(event->index, prev->index); + } else { + intersections.emplace(prev->index, event->index); + } + } + active.insert(*event); + } else { + auto it = active.find(Event(segments[event->index].left, event->index, true)); + auto next = succ(active, it); + auto prev = pred(active, it); + if (next != active.end() && prev != active.end() && _do_intersect(segments[next->index], segments[prev->index])){ + if (next->index < prev->index){ + intersections.emplace(next->index, prev->index); + } else { + intersections.emplace(prev->index, next->index); + } + } + active.erase(it); + } + } + return intersections; } + +Point _find_intersection(const Segment& s1, const Segment& s2){ + float a1, b1, c1, a2, b2, c2, det, x, y; + a1 = s1.right.y - s1.left.y; + b1 = s1.left.x - s1.right.x; + c1 = a1 * s1.left.x + b1 * s1.left.y; + a2 = s2.right.y - s2.left.y; + b2 = s2.left.x - s2.right.x; + c2 = a2 * s2.left.x + b2 * s2.left.y; + det = a1 * b2 - a2 * b1; + if (det == 0) + return Point(0, 0); + x = (b2 * c1 - b1 * c2) / det; + y = (a1 * c2 - a2 * c1) / det; + return Point(x, y); +} \ No newline at end of file diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index 2025d64..3a08c86 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -57,6 +57,9 @@ cdef extern from "triangulate.hpp": bool _on_segment(const Point& p, const Point& q, const Point& r) int _orientation(const Point& p, const Point& q, const Point& r) bool _do_intersect(const Segment& s1, const Segment& s2) + unordered_set[pair[int, int], PairHash] _find_intersections(const vector[Segment]& segments) + Point _find_intersection(const Segment& s1, const Segment& s2) + def on_segment(p: Sequence[float], q: Sequence[float], r: Sequence[float]) -> bool: @@ -127,101 +130,18 @@ def do_intersect(s1: Sequence[Sequence[float]], s2: Sequence[Sequence[float]]) - ) -cdef cpp_set[Event].iterator pred(cpp_set[Event]& s, cpp_set[Event].iterator it): - if it == s.begin(): - return s.end() - return predecrement(it) - -cdef cpp_set[Event].iterator succ(cpp_set[Event]& s, cpp_set[Event].iterator it): - return preincrement(it) - - -cdef unordered_set[pair[int, int], PairHash] _find_intersections(const vector[Segment]& segments): - cdef unordered_set[pair[int, int], PairHash] intersections - cdef vector[Event] events - cdef cpp_set[Event] active, viewed - cdef Py_ssize_t i, j - cdef Event current #, next_event, prev_event - cdef int current_index - cdef cpp_set[Event].iterator next_, prev, it - cdef bool flag - - events.reserve(2 * segments.size()) - for i in range(segments.size()): - events.push_back(Event(segments[i].left.x, segments[i].left.y, i, True)) - events.push_back(Event(segments[i].right.x, segments[i].right.y, i, False)) - - sort(events.begin(), events.end(), cmp_event) - - for i in range(events.size()): - current = events[i] - current_index = current.index - if current.is_left: - next_ = active.lower_bound(current) - prev = pred(active, next_) - # next_event = dereference(next_) - # prev_event = dereference(prev) - flag = False - if next_ != active.end() and _do_intersect(segments[current_index], segments[deref(next_).index]): - if current_index < deref(next_).index: - intersections.insert(pair[int, int](current_index, deref(next_).index)) - else: - intersections.insert(pair[int, int](deref(next_).index, current_index)) - if prev != active.end() and _do_intersect(segments[current_index], segments[deref(prev).index]): - if current_index < deref(prev).index: - intersections.insert(pair[int, int](current_index, deref(prev).index)) - else: - intersections.insert(pair[int, int](deref(prev).index, current_index)) - active.insert(current) - viewed.insert(current) - else: - it = active.find(Event(segments[current_index].left.x, segments[current_index].left.y, current_index, True)) - next_ = succ(active, it) - prev = pred(active, it) - # next_event = dereference(next_) - # prev_event = dereference(prev) - if next_ != active.end() and prev != active.end() and _do_intersect(segments[deref(next_).index], segments[deref(prev).index]): - if deref(next_).index < deref(prev).index: - intersections.insert(pair[int, int](deref(next_).index, deref(prev).index)) - else: - intersections.insert(pair[int, int](deref(prev).index, deref(next_).index)) - active.erase(it) - return intersections - - def find_intersections(segments: Sequence[Sequence[Sequence[float]]]) -> list[tuple[int, int]]: """ Find intersections between segments""" cdef vector[Segment] segments_vector cdef unordered_set[pair[int, int], PairHash] intersections cdef pair[int, int] p - cdef list result = [] - segments_vector.reserve(len(segments)) + segments_vector.reserve(len(segments)) for segment in segments: - if segment[0][0] < segment[1][0] or (segment[0][0] == segment[1][0] and segment[0][1] < segment[1][1]): - segments_vector.push_back(Segment(Point(segment[0][0], segment[0][1]), Point(segment[1][0], segment[1][1]))) - else: - segments_vector.push_back(Segment(Point(segment[1][0], segment[1][1]), Point(segment[0][0], segment[0][1]))) + segments_vector.push_back(Segment(Point(segment[0][0], segment[0][1]), Point(segment[1][0], segment[1][1]))) intersections = _find_intersections(segments_vector) - for p in intersections: - result.append((p.first, p.second)) - return result - -cdef Point _find_intersection(const Segment& s1, const Segment& s2): - cdef float a1, b1, c1, a2, b2, c2, det, x, y - a1 = s1.right.y - s1.left.y - b1 = s1.left.x - s1.right.x - c1 = a1 * s1.left.x + b1 * s1.left.y - a2 = s2.right.y - s2.left.y - b2 = s2.left.x - s2.right.x - c2 = a2 * s2.left.x + b2 * s2.left.y - det = a1 * b2 - a2 * b1 - if det == 0: - return Point(0, 0) - x = (b2 * c1 - b1 * c2) / det - y = (a1 * c2 - a2 * c1) / det - return Point(x, y) + return [(p.first, p.second) for p in intersections] def find_intersection(s1: Sequence[Sequence[float]], s2: Sequence[Sequence[float]]) -> tuple[float, float]: From aa5f750dd9e4b35314457ca244f1aaa65da150d2 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Wed, 21 Aug 2024 20:04:21 +0200 Subject: [PATCH 08/76] rewrite is_convex --- .../triangulate.hpp | 29 +++++++++++++++++++ .../triangulate.pyx | 25 +--------------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulate.hpp index c3eba54..a954eaf 100644 --- a/src/PartSegCore_compiled_backend/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulate.hpp @@ -206,4 +206,33 @@ Point _find_intersection(const Segment& s1, const Segment& s2){ x = (b2 * c1 - b1 * c2) / det; y = (a1 * c2 - a2 * c1) / det; return Point(x, y); +} + +bool _is_convex(const std::vector& polygon){ + int orientation = 0; + int triangle_orientation; + for (int i=0; i < polygon.size()-2; i++){ + triangle_orientation = _orientation(polygon[i], polygon[i+1], polygon[i+2]); + if (triangle_orientation == 0) + continue; + if (orientation == 0) + orientation = triangle_orientation; + else if (orientation != triangle_orientation) + return false; + } + triangle_orientation = _orientation( + polygon[polygon.size() -2], + polygon[polygon.size() -1], + polygon[0] + ); + if (triangle_orientation != 0 && triangle_orientation != orientation) + return false; + triangle_orientation = _orientation( + polygon[polygon.size() - 1], + polygon[0], + polygon[1] + ); + if (triangle_orientation != 0 && triangle_orientation != orientation) + return false; + return true; } \ No newline at end of file diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index 3a08c86..467afed 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -59,6 +59,7 @@ cdef extern from "triangulate.hpp": bool _do_intersect(const Segment& s1, const Segment& s2) unordered_set[pair[int, int], PairHash] _find_intersections(const vector[Segment]& segments) Point _find_intersection(const Segment& s1, const Segment& s2) + bool _is_convex(const vector[Point]& polygon) @@ -166,30 +167,6 @@ def find_intersection(s1: Sequence[Sequence[float]], s2: Sequence[Sequence[float return (p.x, p.y) -cdef bool _is_convex(const vector[Point]& polygon): - cdef Py_ssize_t i, j, k - cdef int orientation = 0, triangle_orientation - cdef float val - for i in range(polygon.size()-2): - j = (i + 1) - k = (i + 2) - triangle_orientation = _orientation(polygon[i], polygon[j], polygon[k]) - if triangle_orientation == 0: - continue - if orientation == 0: - orientation = triangle_orientation - elif orientation != triangle_orientation: - return False - triangle_orientation = _orientation(polygon[polygon.size()-2], polygon[polygon.size()-1], polygon[0]) - if triangle_orientation != 0 and orientation != triangle_orientation: - return False - triangle_orientation = _orientation(polygon[polygon.size()-1], polygon[0], polygon[1]) - if triangle_orientation != 0 and orientation != triangle_orientation: - return False - - return True - - def is_convex(polygon: Sequence[Sequence[float]]) -> bool: """ Check if polygon is convex""" cdef vector[Point] polygon_vector From 8370a5c85260d5bcab9075a5d6eae1d98c0a5874 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Wed, 21 Aug 2024 20:14:57 +0200 Subject: [PATCH 09/76] rewrite triangle convex polygon --- src/PartSegCore_compiled_backend/triangulate.hpp | 10 ++++++++++ src/PartSegCore_compiled_backend/triangulate.pyx | 12 +----------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulate.hpp index a954eaf..d5db0d9 100644 --- a/src/PartSegCore_compiled_backend/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulate.hpp @@ -235,4 +235,14 @@ bool _is_convex(const std::vector& polygon){ if (triangle_orientation != 0 && triangle_orientation != orientation) return false; return true; +} + +std::vector _triangle_convex_polygon(const std::vector & polygon){ + std::vector result; + for (int i=1; i bool: return _is_convex(polygon_vector) -cdef vector[Triangle] _triangle_convex_polygon(const vector[Point]& polygon): - cdef vector[Triangle] result - cdef Py_ssize_t start_index, i, size, current_index - size = polygon.size() - for i in range(1, size-1): - if _orientation(polygon[0], polygon[i], polygon[i+1]) != 0: - result.push_back(Triangle(0, i, i+1)) - - return result - - cdef vector[Triangle] _triangulate_polygon(vector[Point] polygon): cdef vector[Segment] edges, edges_with_intersections cdef Py_ssize_t i, j, edges_count From 71d1fab84888e844bdfe4b5e9e96e82cefa377e6 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 22 Aug 2024 20:59:28 +0200 Subject: [PATCH 10/76] simplify code --- src/PartSegCore_compiled_backend/triangulate.hpp | 6 ++++++ src/PartSegCore_compiled_backend/triangulate.pyx | 7 ++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulate.hpp index d5db0d9..4057a77 100644 --- a/src/PartSegCore_compiled_backend/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulate.hpp @@ -245,4 +245,10 @@ std::vector _triangle_convex_polygon(const std::vector & polygo } } return result; +} + +std::pair, std::vector> +_triangulate_polygon(const std::vector& polygon) { + if (_is_convex(polygon)): + return std::make_pair(_triangle_convex_polygon(polygon), polygon) } \ No newline at end of file diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index 3f91451..984ef6c 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -196,10 +196,7 @@ cdef vector[Triangle] _triangulate_polygon(vector[Point] polygon): edges.reserve(polygon.size()) for i in range(polygon.size() - 1): - if polygon[i].x < polygon[i+1].x: - edges.push_back(Segment(polygon[i], polygon[i+1])) - else: - edges.push_back(Segment(polygon[i+1], polygon[i])) + edges.push_back(Segment(polygon[i], polygon[i+1])) intersections = _find_intersections(edges) intersections_points.reserve(intersections.size()) @@ -241,7 +238,7 @@ def triangulate_polygon(polygon: Sequence[Sequence[float]]) -> list[tuple[int, i p1 = polygon_vector[polygon_vector.size() - 1] p2 = Point(point[0], point[1]) if not point_eq(p1, p2): - # prevent from adding the same point twice + # prevent from adding polygon edge of width 0 polygon_vector.push_back(p2) result = _triangulate_polygon(polygon_vector) From fa5d39754785f6f55002fa0b0f7fe501c37b953d Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 22 Aug 2024 21:04:18 +0200 Subject: [PATCH 11/76] clang-format --- .../triangulate.hpp | 399 +++++++++--------- 1 file changed, 198 insertions(+), 201 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulate.hpp index 4057a77..a69975f 100644 --- a/src/PartSegCore_compiled_backend/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulate.hpp @@ -2,253 +2,250 @@ #include #include - -struct Point{ - float x; - float y; - bool operator==(const Point& p) const { - return x == p.x && y == p.y; - } - Point(float x, float y) : x(x), y(y) {} - Point() {} - - bool operator<(const Point& p) const { - if (this->x == p.x) { - return this->y < p.y; - } - return this->x < p.x; +struct Point { + float x; + float y; + bool operator==(const Point &p) const { return x == p.x && y == p.y; } + Point(float x, float y) : x(x), y(y) {} + Point() {} + + bool operator<(const Point &p) const { + if (this->x == p.x) { + return this->y < p.y; } + return this->x < p.x; + } }; - struct Event { - float x; - float y; - int index; - bool is_left; - - Event(float x, float y, int index, bool is_left) : x(x), y(y), index(index), is_left(is_left) {} - Event(const Point& p, int index, bool is_left): x(p.x), y(p.y), index(index), is_left(is_left) {} - Event() {} - - bool operator<(const Event& e) const { - if (x == e.x) { - if(y==e.y){ - if (is_left == e.is_left){ - return index < e.index; - } - return is_left > e.is_left; - } - return y < e.y; + float x; + float y; + int index; + bool is_left; + + Event(float x, float y, int index, bool is_left) + : x(x), y(y), index(index), is_left(is_left) {} + Event(const Point &p, int index, bool is_left) + : x(p.x), y(p.y), index(index), is_left(is_left) {} + Event() {} + + bool operator<(const Event &e) const { + if (x == e.x) { + if (y == e.y) { + if (is_left == e.is_left) { + return index < e.index; } - return x < e.x; + return is_left > e.is_left; + } + return y < e.y; } + return x < e.x; + } }; -struct PairHash{ - std::size_t operator()(const std::pair& p) const { - return std::hash()(p.first) * 31 + std::hash()(p.second); - } +struct PairHash { + std::size_t operator()(const std::pair &p) const { + return std::hash()(p.first) * 31 + std::hash()(p.second); + } }; - -struct Segment{ - Point left; - Point right; - Segment(Point p1, Point p2){ - if (p1 < p2){ - this->left = p1; - this->right = p2; - } else { - this->left = p2; - this->right = p1; - } - +struct Segment { + Point left; + Point right; + Segment(Point p1, Point p2) { + if (p1 < p2) { + this->left = p1; + this->right = p2; + } else { + this->left = p2; + this->right = p1; } - Segment() {} + } + Segment() {} }; - -struct Triangle{ - int x; - int y; - int z; - Triangle(int x, int y, int z) : x(x), y(y), z(z) {} - Triangle() {} +struct Triangle { + int x; + int y; + int z; + Triangle(int x, int y, int z) : x(x), y(y), z(z) {} + Triangle() {} }; - -bool point_eq(const Point& p, const Point& q){ - return p.x == q.x && p.y == q.y; +bool point_eq(const Point &p, const Point &q) { + return p.x == q.x && p.y == q.y; } -bool cmp_point(const Point& p, const Point& q){ - if(p.x == q.x){ - return p.y < q.y; - } - return p.x < q.x; +bool cmp_point(const Point &p, const Point &q) { + if (p.x == q.x) { + return p.y < q.y; + } + return p.x < q.x; } -bool cmp_event(const Event& p, const Event& q){ - return p < q; -} +bool cmp_event(const Event &p, const Event &q) { return p < q; } - -bool _on_segment(const Point& p, const Point& q, const Point& r){ - if (q.x <= std::max(p.x, r.x) && q.x >= std::min(p.x, r.x) && - q.y <= std::max(p.y, r.y) && q.y >= std::min(p.y, r.y)) - return true; - return false; +bool _on_segment(const Point &p, const Point &q, const Point &r) { + if (q.x <= std::max(p.x, r.x) && q.x >= std::min(p.x, r.x) && + q.y <= std::max(p.y, r.y) && q.y >= std::min(p.y, r.y)) + return true; + return false; } -int _orientation(const Point& p, const Point& q, const Point& r){ - float val = (q.y - p.y) * (r.x - q.x) - - (q.x - p.x) * (r.y - q.y); - if (val == 0) return 0; - return (val > 0) ? 1 : 2; +int _orientation(const Point &p, const Point &q, const Point &r) { + float val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y); + if (val == 0) + return 0; + return (val > 0) ? 1 : 2; } +bool _do_intersect(const Segment &s1, const Segment &s2) { + const Point &p1 = s1.left; + const Point &q1 = s1.right; + const Point &p2 = s2.left; + const Point &q2 = s2.right; + if (point_eq(p1, p2) || point_eq(p1, q2) || point_eq(q1, p2) || + point_eq(q1, q2)) + return false; -bool _do_intersect(const Segment& s1, const Segment& s2){ - const Point& p1 = s1.left; - const Point& q1 = s1.right; - const Point& p2 = s2.left; - const Point& q2 = s2.right; - if (point_eq(p1, p2) || point_eq(p1, q2) || point_eq(q1, p2) || point_eq(q1, q2)) - return false; - - int o1 = _orientation(p1, q1, p2); - int o2 = _orientation(p1, q1, q2); - int o3 = _orientation(p2, q2, p1); - int o4 = _orientation(p2, q2, q1); + int o1 = _orientation(p1, q1, p2); + int o2 = _orientation(p1, q1, q2); + int o3 = _orientation(p2, q2, p1); + int o4 = _orientation(p2, q2, q1); - if (o1 != o2 && o3 != o4) - return true; + if (o1 != o2 && o3 != o4) + return true; - if (o1 == 0 && _on_segment(p1, p2, q1)) return true; - if (o2 == 0 && _on_segment(p1, q2, q1)) return true; - if (o3 == 0 && _on_segment(p2, p1, q2)) return true; - if (o4 == 0 && _on_segment(p2, q1, q2)) return true; + if (o1 == 0 && _on_segment(p1, p2, q1)) + return true; + if (o2 == 0 && _on_segment(p1, q2, q1)) + return true; + if (o3 == 0 && _on_segment(p2, p1, q2)) + return true; + if (o4 == 0 && _on_segment(p2, q1, q2)) + return true; - return false; + return false; } -std::set::iterator pred(std::set& s, std::set::iterator it){ - if(it == s.begin()){ - return s.end(); - } - return --it; +std::set::iterator pred(std::set &s, + std::set::iterator it) { + if (it == s.begin()) { + return s.end(); + } + return --it; } -std::set::iterator succ(std::set& s, std::set::iterator it){ - return ++it; +std::set::iterator succ(std::set &s, + std::set::iterator it) { + return ++it; } std::unordered_set, PairHash> -_find_intersections(const std::vector& segments){ - std::unordered_set, PairHash> intersections; - std::vector events; - std::set active; - events.reserve(2 * segments.size()); - for (int i=0; iis_left){ - auto next = active.lower_bound(*event); - auto prev = pred(active, next); - if (next != active.end() && _do_intersect(segments[event->index], segments[next->index])){ - if (event->index < next->index){ - intersections.emplace(event->index, next->index); - } else { - intersections.emplace(next->index, event->index); - } - } - if (prev != active.end() && _do_intersect(segments[event->index], segments[prev->index])){ - if (event->index < prev->index){ - intersections.emplace(event->index, prev->index); - } else { - intersections.emplace(prev->index, event->index); - } - } - active.insert(*event); +_find_intersections(const std::vector &segments) { + std::unordered_set, PairHash> intersections; + std::vector events; + std::set active; + events.reserve(2 * segments.size()); + for (int i = 0; i < segments.size(); i++) { + events.push_back(Event(segments[i].left, i, true)); + events.push_back(Event(segments[i].right, i, false)); + } + std::sort(events.begin(), events.end(), cmp_event); + + for (auto event = events.begin(); event != events.end(); event++) { + if (event->is_left) { + auto next = active.lower_bound(*event); + auto prev = pred(active, next); + if (next != active.end() && + _do_intersect(segments[event->index], segments[next->index])) { + if (event->index < next->index) { + intersections.emplace(event->index, next->index); } else { - auto it = active.find(Event(segments[event->index].left, event->index, true)); - auto next = succ(active, it); - auto prev = pred(active, it); - if (next != active.end() && prev != active.end() && _do_intersect(segments[next->index], segments[prev->index])){ - if (next->index < prev->index){ - intersections.emplace(next->index, prev->index); - } else { - intersections.emplace(prev->index, next->index); - } - } - active.erase(it); + intersections.emplace(next->index, event->index); } + } + if (prev != active.end() && + _do_intersect(segments[event->index], segments[prev->index])) { + if (event->index < prev->index) { + intersections.emplace(event->index, prev->index); + } else { + intersections.emplace(prev->index, event->index); + } + } + active.insert(*event); + } else { + auto it = + active.find(Event(segments[event->index].left, event->index, true)); + auto next = succ(active, it); + auto prev = pred(active, it); + if (next != active.end() && prev != active.end() && + _do_intersect(segments[next->index], segments[prev->index])) { + if (next->index < prev->index) { + intersections.emplace(next->index, prev->index); + } else { + intersections.emplace(prev->index, next->index); + } + } + active.erase(it); } - return intersections; + } + return intersections; } -Point _find_intersection(const Segment& s1, const Segment& s2){ - float a1, b1, c1, a2, b2, c2, det, x, y; - a1 = s1.right.y - s1.left.y; - b1 = s1.left.x - s1.right.x; - c1 = a1 * s1.left.x + b1 * s1.left.y; - a2 = s2.right.y - s2.left.y; - b2 = s2.left.x - s2.right.x; - c2 = a2 * s2.left.x + b2 * s2.left.y; - det = a1 * b2 - a2 * b1; - if (det == 0) - return Point(0, 0); - x = (b2 * c1 - b1 * c2) / det; - y = (a1 * c2 - a2 * c1) / det; - return Point(x, y); +Point _find_intersection(const Segment &s1, const Segment &s2) { + float a1, b1, c1, a2, b2, c2, det, x, y; + a1 = s1.right.y - s1.left.y; + b1 = s1.left.x - s1.right.x; + c1 = a1 * s1.left.x + b1 * s1.left.y; + a2 = s2.right.y - s2.left.y; + b2 = s2.left.x - s2.right.x; + c2 = a2 * s2.left.x + b2 * s2.left.y; + det = a1 * b2 - a2 * b1; + if (det == 0) + return Point(0, 0); + x = (b2 * c1 - b1 * c2) / det; + y = (a1 * c2 - a2 * c1) / det; + return Point(x, y); } -bool _is_convex(const std::vector& polygon){ - int orientation = 0; - int triangle_orientation; - for (int i=0; i < polygon.size()-2; i++){ - triangle_orientation = _orientation(polygon[i], polygon[i+1], polygon[i+2]); - if (triangle_orientation == 0) - continue; - if (orientation == 0) - orientation = triangle_orientation; - else if (orientation != triangle_orientation) - return false; - } - triangle_orientation = _orientation( - polygon[polygon.size() -2], - polygon[polygon.size() -1], - polygon[0] - ); - if (triangle_orientation != 0 && triangle_orientation != orientation) - return false; - triangle_orientation = _orientation( - polygon[polygon.size() - 1], - polygon[0], - polygon[1] - ); - if (triangle_orientation != 0 && triangle_orientation != orientation) - return false; - return true; +bool _is_convex(const std::vector &polygon) { + int orientation = 0; + int triangle_orientation; + for (int i = 0; i < polygon.size() - 2; i++) { + triangle_orientation = + _orientation(polygon[i], polygon[i + 1], polygon[i + 2]); + if (triangle_orientation == 0) + continue; + if (orientation == 0) + orientation = triangle_orientation; + else if (orientation != triangle_orientation) + return false; + } + triangle_orientation = _orientation(polygon[polygon.size() - 2], + polygon[polygon.size() - 1], polygon[0]); + if (triangle_orientation != 0 && triangle_orientation != orientation) + return false; + triangle_orientation = + _orientation(polygon[polygon.size() - 1], polygon[0], polygon[1]); + if (triangle_orientation != 0 && triangle_orientation != orientation) + return false; + return true; } -std::vector _triangle_convex_polygon(const std::vector & polygon){ - std::vector result; - for (int i=1; i +_triangle_convex_polygon(const std::vector &polygon) { + std::vector result; + for (int i = 1; i < polygon.size() - 1; i++) { + if (_orientation(polygon[0], polygon[i], polygon[i + 1]) != 0) { + result.push_back(Triangle(0, i, i + 1)); } - return result; + } + return result; } std::pair, std::vector> -_triangulate_polygon(const std::vector& polygon) { - if (_is_convex(polygon)): - return std::make_pair(_triangle_convex_polygon(polygon), polygon) +_triangulate_polygon(const std::vector &polygon) { + if (_is_convex(polygon)) + return std::make_pair(_triangle_convex_polygon(polygon), polygon) } \ No newline at end of file From 8e7d5e2d8ce7c1ff22004b6ef879c24b224b7186 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Fri, 23 Aug 2024 22:27:21 +0200 Subject: [PATCH 12/76] split functions and define helper functions --- .../triangulate.hpp | 129 ++++++++++++++++-- 1 file changed, 121 insertions(+), 8 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulate.hpp index a69975f..91ec5c3 100644 --- a/src/PartSegCore_compiled_backend/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulate.hpp @@ -44,11 +44,19 @@ struct Event { }; struct PairHash { - std::size_t operator()(const std::pair &p) const { + std::size_t operator()(const std::pair& p) const { return std::hash()(p.first) * 31 + std::hash()(p.second); } }; + +struct PointHash { + std::size_t operator()(const Point& p) const{ + return std::hash()(p.x) * 31 + std::hash()(p.y); + } +}; + + struct Segment { Point left; Point right; @@ -72,15 +80,17 @@ struct Triangle { Triangle() {} }; +typedef std::unordered_map>, PointHash> PointToEdges; + bool point_eq(const Point &p, const Point &q) { return p.x == q.x && p.y == q.y; } -bool cmp_point(const Point &p, const Point &q) { - if (p.x == q.x) { - return p.y < q.y; - } - return p.x < q.x; +bool cmp_point(const Point &p, const Point &q) { return p < q; } + +bool cmp_pair_point(const std::pair &p, + const std::pair &q) { + return p.first < q.first; } bool cmp_event(const Event &p, const Event &q) { return p < q; } @@ -244,8 +254,111 @@ _triangle_convex_polygon(const std::vector &polygon) { return result; } +std::vector calc_edges(const std::vector& polygon){ + std::vector edges; + edges.reserve(polygon.size()); + for (int i = 0; i < polygon.size() - 1; i++) { + edges.push_back(Segment(polygon[i], polygon[i + 1])); + } + edges.push_back(Segment(polygon[polygon.size() - 1], polygon[0])); + return edges; +} + +std::vector find_intersection_points(const std::vector& polygon) { + /* find all edge intersedions and add mid points for all such intersection + * place*/ + auto edges = calc_edges(polygon); + + auto intersections = _find_intersections(edges); + if (intersections.size() == 0) + return polygon; + std::unordered_map> intersections_points; + for (auto p = intersections.begin(); p != intersections.end(); p++) { + auto inter_point = _find_intersection(edges[p->first], edges[p->second]); + intersections_points[p->first].push_back(inter_point); + intersections_points[p->second].push_back(inter_point); + } + int points_count = polygon.size(); + for (auto iter_inter = intersections_points.begin(); + iter_inter != intersections_points.end(); iter_inter++) { + points_count += iter_inter->second.size() - 1; + iter_inter->second.push_back(edges[iter_inter->first].right); + iter_inter->second.push_back(edges[iter_inter->first].left); + std::sort(iter_inter->second.begin(), iter_inter->second.end(), cmp_point); + } + + std::vector new_polygon; + new_polygon.reserve(points_count); + for (int i = 0; i < polygon.size(); i++) { + auto point = polygon[i]; + new_polygon.push_back(point); + if (intersections_points.count(i)) { + auto new_points = intersections_points[i]; + if (new_points[0] == point) { + for (int j = 1; j < new_points.size() - 1; j++) { + new_polygon.push_back(new_points[j]); + } + } else { + for (int j = new_points.size() - 2; j > 0; j++) { + new_polygon.push_back(new_points[j]); + } + } + } + } + return new_polygon; +} + +bool is_normal_point( + Point p, + PointToEdges point_to_edges + ){ + if (point_to_edges.at(p).size() != 2) + return false; + auto edges = point_to_edges.at(p); + return (edges[0].second < p && p < edges[1].second) || (p < edges[0].second && p < edges[0].second); +} + +bool is_merge_point( + Point p, + PointToEdges point_to_edges + ){ + if (point_to_edges.at(p).size() != 2) + return false; + auto edges = point_to_edges.at(p); + return (edges[0].second < p && edges[1].second < p); +} + +bool is_split_point( + Point p, + PointToEdges point_to_edges + ){ + if (point_to_edges.at(p).size() != 2) + return false; + auto edges = point_to_edges.at(p); + return (p < edges[0].second && p < edges[1].second); +} + + std::pair, std::vector> -_triangulate_polygon(const std::vector &polygon) { +_triangulate_polygon(const std::vector& polygon) { if (_is_convex(polygon)) - return std::make_pair(_triangle_convex_polygon(polygon), polygon) + return std::make_pair(_triangle_convex_polygon(polygon), polygon); + + // Implement sweeping line algorithm for triangulation + // described on this lecture: + // https://www.youtube.com/playlist?list=PLtTatrCwXHzEqzJMaTUFgqoCNllgwk4DH + // + auto new_polygon = find_intersection_points(polygon); + auto edges = calc_edges(new_polygon); + PointToEdges point_to_edges; + for (int i=0; i sorted_points = new_polygon; + // copy to avoid modification of original vector + std::sort(sorted_points.begin(), sorted_points.end(), cmp_point); + for (auto point = sorted_points.begin(); point != sorted_points.end(); point++){ + is_normal_point(*point, point_to_edges); + } } \ No newline at end of file From 0db1b595ca744e2af0533eab940fb07676f61f0a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 23 Aug 2024 20:27:38 +0000 Subject: [PATCH 13/76] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/PartSegCore_compiled_backend/triangulate.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulate.hpp index 91ec5c3..42fbbd5 100644 --- a/src/PartSegCore_compiled_backend/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulate.hpp @@ -347,10 +347,10 @@ _triangulate_polygon(const std::vector& polygon) { // Implement sweeping line algorithm for triangulation // described on this lecture: // https://www.youtube.com/playlist?list=PLtTatrCwXHzEqzJMaTUFgqoCNllgwk4DH - // + // auto new_polygon = find_intersection_points(polygon); auto edges = calc_edges(new_polygon); - PointToEdges point_to_edges; + PointToEdges point_to_edges; for (int i=0; i& polygon) { for (auto point = sorted_points.begin(); point != sorted_points.end(); point++){ is_normal_point(*point, point_to_edges); } -} \ No newline at end of file +} From d420910fcd58f5812e3422fc681accaaafae4c8f Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 27 Aug 2024 12:00:02 +0200 Subject: [PATCH 14/76] start implementing sweeping line triangulation --- .../triangulate.hpp | 109 +++++++++++------- .../triangulate.pyx | 17 ++- src/tests/test_triangulate.py | 6 + 3 files changed, 86 insertions(+), 46 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulate.hpp index 42fbbd5..9c1cde8 100644 --- a/src/PartSegCore_compiled_backend/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulate.hpp @@ -2,6 +2,10 @@ #include #include +enum PointType{ + NORMAL, SPLIT, MERGE, INTERSECTION +}; + struct Point { float x; float y; @@ -157,7 +161,7 @@ _find_intersections(const std::vector &segments) { std::vector events; std::set active; events.reserve(2 * segments.size()); - for (int i = 0; i < segments.size(); i++) { + for (size_t i = 0; i < segments.size(); i++) { events.push_back(Event(segments[i].left, i, true)); events.push_back(Event(segments[i].right, i, false)); } @@ -222,7 +226,7 @@ Point _find_intersection(const Segment &s1, const Segment &s2) { bool _is_convex(const std::vector &polygon) { int orientation = 0; int triangle_orientation; - for (int i = 0; i < polygon.size() - 2; i++) { + for (size_t i = 0; i < polygon.size() - 2; i++) { triangle_orientation = _orientation(polygon[i], polygon[i + 1], polygon[i + 2]); if (triangle_orientation == 0) @@ -246,7 +250,7 @@ bool _is_convex(const std::vector &polygon) { std::vector _triangle_convex_polygon(const std::vector &polygon) { std::vector result; - for (int i = 1; i < polygon.size() - 1; i++) { + for (size_t i = 1; i < polygon.size() - 1; i++) { if (_orientation(polygon[0], polygon[i], polygon[i + 1]) != 0) { result.push_back(Triangle(0, i, i + 1)); } @@ -257,7 +261,7 @@ _triangle_convex_polygon(const std::vector &polygon) { std::vector calc_edges(const std::vector& polygon){ std::vector edges; edges.reserve(polygon.size()); - for (int i = 0; i < polygon.size() - 1; i++) { + for (size_t i = 0; i < polygon.size() - 1; i++) { edges.push_back(Segment(polygon[i], polygon[i + 1])); } edges.push_back(Segment(polygon[polygon.size() - 1], polygon[0])); @@ -289,13 +293,13 @@ std::vector find_intersection_points(const std::vector& polygon) { std::vector new_polygon; new_polygon.reserve(points_count); - for (int i = 0; i < polygon.size(); i++) { + for (size_t i = 0; i < polygon.size(); i++) { auto point = polygon[i]; new_polygon.push_back(point); if (intersections_points.count(i)) { auto new_points = intersections_points[i]; if (new_points[0] == point) { - for (int j = 1; j < new_points.size() - 1; j++) { + for (size_t j = 1; j < new_points.size() - 1; j++) { new_polygon.push_back(new_points[j]); } } else { @@ -308,39 +312,68 @@ std::vector find_intersection_points(const std::vector& polygon) { return new_polygon; } -bool is_normal_point( - Point p, - PointToEdges point_to_edges - ){ - if (point_to_edges.at(p).size() != 2) - return false; - auto edges = point_to_edges.at(p); - return (edges[0].second < p && p < edges[1].second) || (p < edges[0].second && p < edges[0].second); -} -bool is_merge_point( - Point p, - PointToEdges point_to_edges - ){ - if (point_to_edges.at(p).size() != 2) - return false; - auto edges = point_to_edges.at(p); - return (edges[0].second < p && edges[1].second < p); + +/* +Calculate point type. +If there is more than two edges adjusted to point, it is intersection point. +If there are two adjusted edges, it could be one of split, merge and normal point. +If both adjusted edges have opposite end before given point p, this is merge point. +If both adjusted edges have opposite end after given point p, split point. +Otherwise it is normal point. +*/ +PointType get_point_type( + Point p, + PointToEdges& point_to_edges +){ + if (point_to_edges.at(p).size() != 2) + return PointType::INTERSECTION; + auto edges = point_to_edges.at(p); + if (edges[0].second < p && edges[1].second < p) + return PointType::MERGE; + if (p < edges[0].second && p < edges[1].second) + return PointType::SPLIT; + return PointType::NORMAL; } -bool is_split_point( - Point p, - PointToEdges point_to_edges - ){ - if (point_to_edges.at(p).size() != 2) - return false; - auto edges = point_to_edges.at(p); - return (p < edges[0].second && p < edges[1].second); + +/* +This is implementation of sweeping line triangulation of polygon +Its assumes that there is no edge intersections, but may be a point with more than 2 edges. +described on this lecture: +https://www.youtube.com/playlist?list=PLtTatrCwXHzEqzJMaTUFgqoCNllgwk4DH +*/ +std::pair, std::vector> +sweeping_line_triangulation(const std::vector& polygon){ + std::vector result; + auto edges = calc_edges(polygon); + PointToEdges point_to_edges; + for (size_t i=0; i sorted_points = polygon; + // copy to avoid modification of original vector + std::sort(sorted_points.begin(), sorted_points.end(), cmp_point); + for (auto point = sorted_points.begin(); point != sorted_points.end(); point++){ + auto point_type = get_point_type(*point, point_to_edges); + } + return std::make_pair(result, polygon); } std::pair, std::vector> _triangulate_polygon(const std::vector& polygon) { + if (polygon.size() <3) + return std::make_pair(std::vector(), polygon); + if (polygon.size() == 3) + return std::make_pair(std::vector({Triangle(0, 1, 2)}), polygon); + if (polygon.size() == 4){ + if (_orientation(polygon[0], polygon[1], polygon[2]) != _orientation(polygon[0], polygon[3], polygon[2])) + return std::make_pair(std::vector({Triangle(0, 1, 2), Triangle(0, 3, 2)}), polygon); + } + + if (_is_convex(polygon)) return std::make_pair(_triangle_convex_polygon(polygon), polygon); @@ -348,17 +381,5 @@ _triangulate_polygon(const std::vector& polygon) { // described on this lecture: // https://www.youtube.com/playlist?list=PLtTatrCwXHzEqzJMaTUFgqoCNllgwk4DH // - auto new_polygon = find_intersection_points(polygon); - auto edges = calc_edges(new_polygon); - PointToEdges point_to_edges; - for (int i=0; i sorted_points = new_polygon; - // copy to avoid modification of original vector - std::sort(sorted_points.begin(), sorted_points.end(), cmp_point); - for (auto point = sorted_points.begin(); point != sorted_points.end(); point++){ - is_normal_point(*point, point_to_edges); - } + return sweeping_line_triangulation(find_intersection_points(polygon)); } diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index 984ef6c..53433b4 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -8,13 +8,11 @@ import numpy as np cimport numpy as cnp -from cython.operator cimport preincrement, predecrement, dereference as deref from libcpp cimport bool from libcpp.unordered_set cimport unordered_set from libcpp.utility cimport pair from libcpp.vector cimport vector from libcpp.unordered_map cimport unordered_map -from libcpp.set cimport set as cpp_set from libcpp.algorithm cimport sort @@ -179,6 +177,21 @@ def is_convex(polygon: Sequence[Sequence[float]]) -> bool: return _is_convex(polygon_vector) +def triangle_convex_polygon(polygon: Sequence[Sequence[float]]) -> list[tuple[int, int, int]]: + cdef vector[Point] polygon_vector + cdef vector[Triangle] result + + polygon_vector.reserve(len(polygon)) + polygon_vector.push_back(Point(polygon[0][0], polygon[0][1])) + for point in polygon[1:]: + p1 = polygon_vector[polygon_vector.size() - 1] + p2 = Point(point[0], point[1]) + if not point_eq(p1, p2): + # prevent from adding polygon edge of width 0 + polygon_vector.push_back(p2) + + result = _triangle_convex_polygon(polygon_vector) + return [(triangle.x, triangle.y, triangle.z) for triangle in result] cdef vector[Triangle] _triangulate_polygon(vector[Point] polygon): cdef vector[Segment] edges, edges_with_intersections diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index 829f23d..b80e624 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -6,6 +6,7 @@ find_intersection, is_convex, triangulate_polygon, + triangle_convex_polygon, ) @@ -63,6 +64,11 @@ def test_is_convex(): assert not is_convex([(0, 0), (0.1, 0.5), (0, 1), (1, 1), (1, 0)]) +def test_triangle_convex_polygon(): + assert triangle_convex_polygon([(0, 0), (0, 1), (1, 1), (1, 0)]) == [(0, 1, 2), (0, 2, 3)] + assert triangle_convex_polygon([(0, 0), (0, 0.5), (0, 1), (1, 1), (1, 0)]) == [(0, 2, 3), (0, 3, 4)] + + def test_triangulate_polygon(): assert triangulate_polygon([(0, 0), (0, 1), (1, 1), (1, 0)]) == [(0, 1, 2), (0, 2, 3)] assert triangulate_polygon([(0, 0), (0, 10), (1, 10), (1, 0)]) == [(0, 1, 2), (0, 2, 3)] From 51001ed72a265919901ea665262c536e583d99c2 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 27 Aug 2024 12:37:34 +0200 Subject: [PATCH 15/76] implementation part 2 --- .../triangulate.hpp | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/PartSegCore_compiled_backend/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulate.hpp index 9c1cde8..541bcfe 100644 --- a/src/PartSegCore_compiled_backend/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulate.hpp @@ -76,6 +76,12 @@ struct Segment { Segment() {} }; +struct Line{ + Segment left, right; + Line() {}; + Line(const Segment& left, const Segment& right): left(left), right(right) {}; +}; + struct Triangle { int x; int y; @@ -357,6 +363,20 @@ sweeping_line_triangulation(const std::vector& polygon){ std::sort(sorted_points.begin(), sorted_points.end(), cmp_point); for (auto point = sorted_points.begin(); point != sorted_points.end(); point++){ auto point_type = get_point_type(*point, point_to_edges); +// switch (point_type){ +// case PointType::NORMAL: +// break; +// case PointType::SPLIT: +// auto line = Line( +// Segment(*point, point_to_edges.at(*point)[0].second), +// Segment(*point, point_to_edges.at(*point)[1].second) +// ); +// break; +// case PointType::MERGE: +// break; +// case PointType::INTERSECTION: +// break; +// } } return std::make_pair(result, polygon); } From 7026fe95d147f3198f18831cffe1403a8d0ef479 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Fri, 27 Sep 2024 18:03:10 +0200 Subject: [PATCH 16/76] reformat file --- .../triangulate.hpp | 145 ++++++++---------- 1 file changed, 63 insertions(+), 82 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulate.hpp index 541bcfe..42cde01 100644 --- a/src/PartSegCore_compiled_backend/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulate.hpp @@ -2,9 +2,7 @@ #include #include -enum PointType{ - NORMAL, SPLIT, MERGE, INTERSECTION -}; +enum PointType { NORMAL, SPLIT, MERGE, INTERSECTION }; struct Point { float x; @@ -48,19 +46,17 @@ struct Event { }; struct PairHash { - std::size_t operator()(const std::pair& p) const { + std::size_t operator()(const std::pair &p) const { return std::hash()(p.first) * 31 + std::hash()(p.second); } }; - struct PointHash { - std::size_t operator()(const Point& p) const{ + std::size_t operator()(const Point &p) const { return std::hash()(p.x) * 31 + std::hash()(p.y); } }; - struct Segment { Point left; Point right; @@ -76,10 +72,10 @@ struct Segment { Segment() {} }; -struct Line{ - Segment left, right; - Line() {}; - Line(const Segment& left, const Segment& right): left(left), right(right) {}; +struct Line { + Segment left, right; + Line() {}; + Line(const Segment &left, const Segment &right) : left(left), right(right) {}; }; struct Triangle { @@ -90,7 +86,8 @@ struct Triangle { Triangle() {} }; -typedef std::unordered_map>, PointHash> PointToEdges; +typedef std::unordered_map>, PointHash> + PointToEdges; bool point_eq(const Point &p, const Point &q) { return p.x == q.x && p.y == q.y; @@ -114,8 +111,7 @@ bool _on_segment(const Point &p, const Point &q, const Point &r) { int _orientation(const Point &p, const Point &q, const Point &r) { float val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y); - if (val == 0) - return 0; + if (val == 0) return 0; return (val > 0) ? 1 : 2; } @@ -133,17 +129,12 @@ bool _do_intersect(const Segment &s1, const Segment &s2) { int o3 = _orientation(p2, q2, p1); int o4 = _orientation(p2, q2, q1); - if (o1 != o2 && o3 != o4) - return true; + if (o1 != o2 && o3 != o4) return true; - if (o1 == 0 && _on_segment(p1, p2, q1)) - return true; - if (o2 == 0 && _on_segment(p1, q2, q1)) - return true; - if (o3 == 0 && _on_segment(p2, p1, q2)) - return true; - if (o4 == 0 && _on_segment(p2, q1, q2)) - return true; + if (o1 == 0 && _on_segment(p1, p2, q1)) return true; + if (o2 == 0 && _on_segment(p1, q2, q1)) return true; + if (o3 == 0 && _on_segment(p2, p1, q2)) return true; + if (o4 == 0 && _on_segment(p2, q1, q2)) return true; return false; } @@ -161,8 +152,8 @@ std::set::iterator succ(std::set &s, return ++it; } -std::unordered_set, PairHash> -_find_intersections(const std::vector &segments) { +std::unordered_set, PairHash> _find_intersections( + const std::vector &segments) { std::unordered_set, PairHash> intersections; std::vector events; std::set active; @@ -222,8 +213,7 @@ Point _find_intersection(const Segment &s1, const Segment &s2) { b2 = s2.left.x - s2.right.x; c2 = a2 * s2.left.x + b2 * s2.left.y; det = a1 * b2 - a2 * b1; - if (det == 0) - return Point(0, 0); + if (det == 0) return Point(0, 0); x = (b2 * c1 - b1 * c2) / det; y = (a1 * c2 - a2 * c1) / det; return Point(x, y); @@ -235,8 +225,7 @@ bool _is_convex(const std::vector &polygon) { for (size_t i = 0; i < polygon.size() - 2; i++) { triangle_orientation = _orientation(polygon[i], polygon[i + 1], polygon[i + 2]); - if (triangle_orientation == 0) - continue; + if (triangle_orientation == 0) continue; if (orientation == 0) orientation = triangle_orientation; else if (orientation != triangle_orientation) @@ -253,8 +242,8 @@ bool _is_convex(const std::vector &polygon) { return true; } -std::vector -_triangle_convex_polygon(const std::vector &polygon) { +std::vector _triangle_convex_polygon( + const std::vector &polygon) { std::vector result; for (size_t i = 1; i < polygon.size() - 1; i++) { if (_orientation(polygon[0], polygon[i], polygon[i + 1]) != 0) { @@ -264,7 +253,7 @@ _triangle_convex_polygon(const std::vector &polygon) { return result; } -std::vector calc_edges(const std::vector& polygon){ +std::vector calc_edges(const std::vector &polygon) { std::vector edges; edges.reserve(polygon.size()); for (size_t i = 0; i < polygon.size() - 1; i++) { @@ -274,14 +263,13 @@ std::vector calc_edges(const std::vector& polygon){ return edges; } -std::vector find_intersection_points(const std::vector& polygon) { +std::vector find_intersection_points(const std::vector &polygon) { /* find all edge intersedions and add mid points for all such intersection * place*/ auto edges = calc_edges(polygon); auto intersections = _find_intersections(edges); - if (intersections.size() == 0) - return polygon; + if (intersections.size() == 0) return polygon; std::unordered_map> intersections_points; for (auto p = intersections.begin(); p != intersections.end(); p++) { auto inter_point = _find_intersection(edges[p->first], edges[p->second]); @@ -318,82 +306,75 @@ std::vector find_intersection_points(const std::vector& polygon) { return new_polygon; } - - /* Calculate point type. If there is more than two edges adjusted to point, it is intersection point. -If there are two adjusted edges, it could be one of split, merge and normal point. -If both adjusted edges have opposite end before given point p, this is merge point. -If both adjusted edges have opposite end after given point p, split point. -Otherwise it is normal point. +If there are two adjusted edges, it could be one of split, merge and normal +point. If both adjusted edges have opposite end before given point p, this is +merge point. If both adjusted edges have opposite end after given point p, split +point. Otherwise it is normal point. */ -PointType get_point_type( - Point p, - PointToEdges& point_to_edges -){ - if (point_to_edges.at(p).size() != 2) - return PointType::INTERSECTION; - auto edges = point_to_edges.at(p); - if (edges[0].second < p && edges[1].second < p) - return PointType::MERGE; - if (p < edges[0].second && p < edges[1].second) - return PointType::SPLIT; - return PointType::NORMAL; +PointType get_point_type(Point p, PointToEdges &point_to_edges) { + if (point_to_edges.at(p).size() != 2) return PointType::INTERSECTION; + auto edges = point_to_edges.at(p); + if (edges[0].second < p && edges[1].second < p) return PointType::MERGE; + if (p < edges[0].second && p < edges[1].second) return PointType::SPLIT; + return PointType::NORMAL; } - /* This is implementation of sweeping line triangulation of polygon -Its assumes that there is no edge intersections, but may be a point with more than 2 edges. -described on this lecture: +Its assumes that there is no edge intersections, but may be a point with more +than 2 edges. described on this lecture: https://www.youtube.com/playlist?list=PLtTatrCwXHzEqzJMaTUFgqoCNllgwk4DH */ std::pair, std::vector> -sweeping_line_triangulation(const std::vector& polygon){ +sweeping_line_triangulation(const std::vector &polygon) { std::vector result; auto edges = calc_edges(polygon); PointToEdges point_to_edges; - for (size_t i=0; i sorted_points = polygon; // copy to avoid modification of original vector std::sort(sorted_points.begin(), sorted_points.end(), cmp_point); - for (auto point = sorted_points.begin(); point != sorted_points.end(); point++){ + for (auto point = sorted_points.begin(); point != sorted_points.end(); + point++) { auto point_type = get_point_type(*point, point_to_edges); -// switch (point_type){ -// case PointType::NORMAL: -// break; -// case PointType::SPLIT: -// auto line = Line( -// Segment(*point, point_to_edges.at(*point)[0].second), -// Segment(*point, point_to_edges.at(*point)[1].second) -// ); -// break; -// case PointType::MERGE: -// break; -// case PointType::INTERSECTION: -// break; -// } + // switch (point_type){ + // case PointType::NORMAL: + // break; + // case PointType::SPLIT: + // auto line = Line( + // Segment(*point, point_to_edges.at(*point)[0].second), + // Segment(*point, point_to_edges.at(*point)[1].second) + // ); + // break; + // case PointType::MERGE: + // break; + // case PointType::INTERSECTION: + // break; + // } } return std::make_pair(result, polygon); } - -std::pair, std::vector> -_triangulate_polygon(const std::vector& polygon) { - if (polygon.size() <3) +std::pair, std::vector> _triangulate_polygon( + const std::vector &polygon) { + if (polygon.size() < 3) return std::make_pair(std::vector(), polygon); if (polygon.size() == 3) return std::make_pair(std::vector({Triangle(0, 1, 2)}), polygon); - if (polygon.size() == 4){ - if (_orientation(polygon[0], polygon[1], polygon[2]) != _orientation(polygon[0], polygon[3], polygon[2])) - return std::make_pair(std::vector({Triangle(0, 1, 2), Triangle(0, 3, 2)}), polygon); + if (polygon.size() == 4) { + if (_orientation(polygon[0], polygon[1], polygon[2]) != + _orientation(polygon[0], polygon[3], polygon[2])) + return std::make_pair( + std::vector({Triangle(0, 1, 2), Triangle(0, 3, 2)}), + polygon); } - if (_is_convex(polygon)) return std::make_pair(_triangle_convex_polygon(polygon), polygon); From 123274b95b26ead42a81dafe0cd824043cc9d110 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Fri, 27 Sep 2024 18:28:06 +0200 Subject: [PATCH 17/76] describe steeps --- .../triangulate.hpp | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulate.hpp index 42cde01..734feb9 100644 --- a/src/PartSegCore_compiled_backend/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulate.hpp @@ -343,20 +343,27 @@ sweeping_line_triangulation(const std::vector &polygon) { for (auto point = sorted_points.begin(); point != sorted_points.end(); point++) { auto point_type = get_point_type(*point, point_to_edges); - // switch (point_type){ - // case PointType::NORMAL: - // break; - // case PointType::SPLIT: - // auto line = Line( - // Segment(*point, point_to_edges.at(*point)[0].second), - // Segment(*point, point_to_edges.at(*point)[1].second) - // ); - // break; - // case PointType::MERGE: - // break; - // case PointType::INTERSECTION: - // break; - // } + switch (point_type) { + case PointType::NORMAL: + // change edge adjusted to current sweeping line + break; + case PointType::SPLIT: + // split sweeping line on two lines + // add edge sor cutting polygon on two parts + auto line = Line(Segment(*point, point_to_edges.at(*point)[0].second), + Segment(*point, point_to_edges.at(*point)[1].second)); + break; + case PointType::MERGE: + // merge two sweeping lines to one + // save point as point to start new line for SPLIT pointcase + break; + case PointType::INTERSECTION: + // this is merge and split point at same time + // this is not described in original algorithm + // but we need it to handle self intersecting polygons + // Remember about more than 4 edges case + break; + } } return std::make_pair(result, polygon); } From bc8385f78cad8c4b756ecb8fc7c62fa440dc2700 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Mon, 30 Sep 2024 13:45:10 +0200 Subject: [PATCH 18/76] improve code structure --- .../triangulate.hpp | 67 +++++++++++++++---- 1 file changed, 54 insertions(+), 13 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulate.hpp index 734feb9..5a4850f 100644 --- a/src/PartSegCore_compiled_backend/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulate.hpp @@ -3,7 +3,7 @@ #include enum PointType { NORMAL, SPLIT, MERGE, INTERSECTION }; - +/* Point class with x and y coordinates */ struct Point { float x; float y; @@ -78,15 +78,36 @@ struct Line { Line(const Segment &left, const Segment &right) : left(left), right(right) {}; }; + +struct OrderedPolygon { + Point top; + Point bottom; + std::vector left; + std::vector right; +}; + + struct Triangle { int x; int y; int z; - Triangle(int x, int y, int z) : x(x), y(y), z(z) {} - Triangle() {} + Triangle(int x, int y, int z) : x(x), y(y), z(z) {}; + Triangle() {}; +}; + +typedef size_t EdgeIndex; + +struct PointEdges { + EdgeIndex edge_index; + Point opposite_point; + PointEdges(EdgeIndex edge_index, Point opposite_point) + : edge_index(edge_index), opposite_point(opposite_point) {} + bool operator<(const PointEdges &e) const { + return opposite_point < e.opposite_point; + } }; -typedef std::unordered_map>, PointHash> +typedef std::unordered_map, PointHash> PointToEdges; bool point_eq(const Point &p, const Point &q) { @@ -102,6 +123,10 @@ bool cmp_pair_point(const std::pair &p, bool cmp_event(const Event &p, const Event &q) { return p < q; } +bool cmp_point_edges(const PointEdges &p, const PointEdges &q) { + return p < q; +} + bool _on_segment(const Point &p, const Point &q, const Point &r) { if (q.x <= std::max(p.x, r.x) && q.x >= std::min(p.x, r.x) && q.y <= std::max(p.y, r.y) && q.y >= std::min(p.y, r.y)) @@ -317,11 +342,27 @@ point. Otherwise it is normal point. PointType get_point_type(Point p, PointToEdges &point_to_edges) { if (point_to_edges.at(p).size() != 2) return PointType::INTERSECTION; auto edges = point_to_edges.at(p); - if (edges[0].second < p && edges[1].second < p) return PointType::MERGE; - if (p < edges[0].second && p < edges[1].second) return PointType::SPLIT; + if (edges[0].opposite_point < p && edges[1].opposite_point < p) return PointType::MERGE; + if (p < edges[0].opposite_point && p < edges[1].opposite_point) return PointType::SPLIT; return PointType::NORMAL; } +/* +Get map from point to list of edges which contains this point. +Also sort each list by point order. +*/ +PointEdges get_points_edges(std::vector & edges) { + PointToEdges point_to_edges; + for (size_t i = 0; i < edges.size(); i++) { + point_to_edges[edges[i].left].push_back(PointEdges(i, edges[i].right)); + point_to_edges[edges[i].right].push_back(PointEdges(i, edges[i].left)); + } + for (size_t i = 0; i < point_to_edges.size(); i++){ + std::sort(point_to_edges[i].begin(), point_to_edges[i].end(), cmp_point_edges); + } + return point_to_edges +} + /* This is implementation of sweeping line triangulation of polygon Its assumes that there is no edge intersections, but may be a point with more @@ -332,14 +373,14 @@ std::pair, std::vector> sweeping_line_triangulation(const std::vector &polygon) { std::vector result; auto edges = calc_edges(polygon); - PointToEdges point_to_edges; - for (size_t i = 0; i < edges.size(); i++) { - point_to_edges[edges[i].left].push_back(std::make_pair(i, edges[i].right)); - point_to_edges[edges[i].right].push_back(std::make_pair(i, edges[i].left)); - } + PointToEdges point_to_edges = get_points_edges(edges); + std::vector sorted_points = polygon; // copy to avoid modification of original vector std::sort(sorted_points.begin(), sorted_points.end(), cmp_point); + std::vector ordered_polygon_li; + ordered_polygon_li.push_back(OrderedPolygon()); + Line line; for (auto point = sorted_points.begin(); point != sorted_points.end(); point++) { auto point_type = get_point_type(*point, point_to_edges); @@ -350,8 +391,8 @@ sweeping_line_triangulation(const std::vector &polygon) { case PointType::SPLIT: // split sweeping line on two lines // add edge sor cutting polygon on two parts - auto line = Line(Segment(*point, point_to_edges.at(*point)[0].second), - Segment(*point, point_to_edges.at(*point)[1].second)); + line = Line(Segment(*point, point_to_edges.at(*point)[0].opposite_point), + Segment(*point, point_to_edges.at(*point)[1].opposite_point)); break; case PointType::MERGE: // merge two sweeping lines to one From adf837dfbee337ad4500b1af65d29a59490c0137 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Fri, 4 Oct 2024 14:58:54 +0200 Subject: [PATCH 19/76] fix type annotation --- src/PartSegCore_compiled_backend/triangulate.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulate.hpp index 5a4850f..1d5d14b 100644 --- a/src/PartSegCore_compiled_backend/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulate.hpp @@ -351,7 +351,7 @@ PointType get_point_type(Point p, PointToEdges &point_to_edges) { Get map from point to list of edges which contains this point. Also sort each list by point order. */ -PointEdges get_points_edges(std::vector & edges) { +PointToEdges get_points_edges(std::vector & edges) { PointToEdges point_to_edges; for (size_t i = 0; i < edges.size(); i++) { point_to_edges[edges[i].left].push_back(PointEdges(i, edges[i].right)); From 416b1fceeafdd0bfac67af01eeff589a61b2e476 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 8 Oct 2024 16:57:52 +0200 Subject: [PATCH 20/76] add triangulate to install targets --- CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 494af53..ec5275b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,6 +81,11 @@ python_add_library(_napari_mapping MODULE "${napari_mapping_cython_cxx}" WITH_SO target_include_directories(_napari_mapping PRIVATE "${NUMPY_INCLUDE_DIR}") target_link_libraries(_napari_mapping PRIVATE OpenMP::OpenMP_CXX) +cython_transpile(src/PartSegCore_compiled_backend/triangulate.pyx LANGUAGE CXX OUTPUT_VARIABLE triangulate_cython_cxx) +python_add_library(triangulate MODULE "${triangulate_cython_cxx}" WITH_SOABI) +target_include_directories(triangulate PRIVATE "${NUMPY_INCLUDE_DIR}") +target_include_directories(triangulate PRIVATE src/PartSegCore_compiled_backend/) + install(TARGETS euclidean_cython DESTINATION PartSegCore_compiled_backend/sprawl_utils) install(TARGETS path_sprawl_cython DESTINATION PartSegCore_compiled_backend/sprawl_utils) @@ -91,3 +96,4 @@ install(TARGETS calc_bounds DESTINATION PartSegCore_compiled_backend) install(TARGETS mso_bind DESTINATION PartSegCore_compiled_backend/multiscale_opening) install(TARGETS _fast_unique DESTINATION PartSegCore_compiled_backend) install(TARGETS _napari_mapping DESTINATION PartSegCore_compiled_backend) +install(TARGETS triangulate DESTINATION PartSegCore_compiled_backend) From 59018580c74f66c2fe8a404978d69faf856f8c5e Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 8 Oct 2024 17:22:48 +0200 Subject: [PATCH 21/76] fix configuration and fix code --- CMakeLists.txt | 32 +++++++++++++++---- .../triangulate.hpp | 10 +++--- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ec5275b..5468674 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,23 +33,39 @@ execute_process( cython_transpile(src/PartSegCore_compiled_backend/sprawl_utils/euclidean_cython.pyx LANGUAGE CXX OUTPUT_VARIABLE euclidean_cython_cxx) -python_add_library(euclidean_cython MODULE "${euclidean_cython_cxx}" WITH_SOABI) +python_add_library(euclidean_cython MODULE + "${euclidean_cython_cxx}" + src/PartSegCore_compiled_backend/sprawl_utils/my_queue.h + src/PartSegCore_compiled_backend/sprawl_utils/global_consts.h + WITH_SOABI) target_include_directories(euclidean_cython PRIVATE "${NUMPY_INCLUDE_DIR}") target_include_directories(euclidean_cython PRIVATE src/PartSegCore_compiled_backend/sprawl_utils/) cython_transpile(src/PartSegCore_compiled_backend/sprawl_utils/path_sprawl_cython.pyx LANGUAGE CXX OUTPUT_VARIABLE path_sprawl_cython_cxx) -python_add_library(path_sprawl_cython MODULE "${path_sprawl_cython_cxx}" WITH_SOABI) +python_add_library(path_sprawl_cython MODULE + "${path_sprawl_cython_cxx}" + src/PartSegCore_compiled_backend/sprawl_utils/my_queue.h + src/PartSegCore_compiled_backend/sprawl_utils/global_consts.h + WITH_SOABI) target_include_directories(path_sprawl_cython PRIVATE "${NUMPY_INCLUDE_DIR}") target_include_directories(path_sprawl_cython PRIVATE src/PartSegCore_compiled_backend/sprawl_utils/) cython_transpile(src/PartSegCore_compiled_backend/sprawl_utils/sprawl_utils.pyx LANGUAGE CXX OUTPUT_VARIABLE sprawl_utils_cython_cxx) -python_add_library(sprawl_utils_cython MODULE "${sprawl_utils_cython_cxx}" WITH_SOABI) +python_add_library(sprawl_utils_cython MODULE + "${sprawl_utils_cython_cxx}" + src/PartSegCore_compiled_backend/sprawl_utils/my_queue.h + src/PartSegCore_compiled_backend/sprawl_utils/global_consts.h + WITH_SOABI) target_include_directories(sprawl_utils_cython PRIVATE "${NUMPY_INCLUDE_DIR}") target_include_directories(sprawl_utils_cython PRIVATE src/PartSegCore_compiled_backend/sprawl_utils/) cython_transpile(src/PartSegCore_compiled_backend/sprawl_utils/fuzzy_distance.pyx LANGUAGE CXX OUTPUT_VARIABLE fuzzy_distance_cython_cxx) -python_add_library(fuzzy_distance_cython MODULE "${fuzzy_distance_cython_cxx}" WITH_SOABI) +python_add_library(fuzzy_distance_cython MODULE + "${fuzzy_distance_cython_cxx}" + src/PartSegCore_compiled_backend/sprawl_utils/my_queue.h + src/PartSegCore_compiled_backend/sprawl_utils/global_consts.h + WITH_SOABI) target_include_directories(fuzzy_distance_cython PRIVATE "${NUMPY_INCLUDE_DIR}") target_include_directories(fuzzy_distance_cython PRIVATE src/PartSegCore_compiled_backend/sprawl_utils/) @@ -65,7 +81,11 @@ target_include_directories(calc_bounds PRIVATE "${NUMPY_INCLUDE_DIR}") cython_transpile(src/PartSegCore_compiled_backend/multiscale_opening/mso_bind.pyx LANGUAGE CXX OUTPUT_VARIABLE mso_bind_cython_cxx) -python_add_library(mso_bind MODULE "${mso_bind_cython_cxx}" WITH_SOABI) +python_add_library(mso_bind MODULE + "${mso_bind_cython_cxx}" + src/PartSegCore_compiled_backend/multiscale_opening/mso.h + src/PartSegCore_compiled_backend/multiscale_opening/my_queue.h + WITH_SOABI) target_include_directories(mso_bind PRIVATE src/PartSegCore_compiled_backend/multiscale_opening/) target_include_directories(mso_bind PRIVATE "${NUMPY_INCLUDE_DIR}") @@ -82,7 +102,7 @@ target_include_directories(_napari_mapping PRIVATE "${NUMPY_INCLUDE_DIR}") target_link_libraries(_napari_mapping PRIVATE OpenMP::OpenMP_CXX) cython_transpile(src/PartSegCore_compiled_backend/triangulate.pyx LANGUAGE CXX OUTPUT_VARIABLE triangulate_cython_cxx) -python_add_library(triangulate MODULE "${triangulate_cython_cxx}" WITH_SOABI) +python_add_library(triangulate MODULE "${triangulate_cython_cxx}" src/PartSegCore_compiled_backend/triangulate.hpp WITH_SOABI) target_include_directories(triangulate PRIVATE "${NUMPY_INCLUDE_DIR}") target_include_directories(triangulate PRIVATE src/PartSegCore_compiled_backend/) diff --git a/src/PartSegCore_compiled_backend/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulate.hpp index 1d5d14b..4ee7b25 100644 --- a/src/PartSegCore_compiled_backend/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulate.hpp @@ -354,13 +354,13 @@ Also sort each list by point order. PointToEdges get_points_edges(std::vector & edges) { PointToEdges point_to_edges; for (size_t i = 0; i < edges.size(); i++) { - point_to_edges[edges[i].left].push_back(PointEdges(i, edges[i].right)); - point_to_edges[edges[i].right].push_back(PointEdges(i, edges[i].left)); + point_to_edges[edges[i].left].emplace_back(i, edges[i].right); + point_to_edges[edges[i].right].emplace_back(i, edges[i].left); } - for (size_t i = 0; i < point_to_edges.size(); i++){ - std::sort(point_to_edges[i].begin(), point_to_edges[i].end(), cmp_point_edges); + for (auto & point_to_edge : point_to_edges) { + std::sort(point_to_edge.second.begin(), point_to_edge.second.end(), cmp_point_edges); } - return point_to_edges + return point_to_edges; } /* From ec2ed564ad41acc8b13bff06bf33d51573b511ae Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 15:23:28 +0000 Subject: [PATCH 22/76] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../triangulate.hpp | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulate.hpp index 4ee7b25..c0cfa99 100644 --- a/src/PartSegCore_compiled_backend/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulate.hpp @@ -78,7 +78,6 @@ struct Line { Line(const Segment &left, const Segment &right) : left(left), right(right) {}; }; - struct OrderedPolygon { Point top; Point bottom; @@ -86,7 +85,6 @@ struct OrderedPolygon { std::vector right; }; - struct Triangle { int x; int y; @@ -98,13 +96,13 @@ struct Triangle { typedef size_t EdgeIndex; struct PointEdges { - EdgeIndex edge_index; - Point opposite_point; - PointEdges(EdgeIndex edge_index, Point opposite_point) - : edge_index(edge_index), opposite_point(opposite_point) {} - bool operator<(const PointEdges &e) const { - return opposite_point < e.opposite_point; - } + EdgeIndex edge_index; + Point opposite_point; + PointEdges(EdgeIndex edge_index, Point opposite_point) + : edge_index(edge_index), opposite_point(opposite_point) {} + bool operator<(const PointEdges &e) const { + return opposite_point < e.opposite_point; + } }; typedef std::unordered_map, PointHash> @@ -123,9 +121,7 @@ bool cmp_pair_point(const std::pair &p, bool cmp_event(const Event &p, const Event &q) { return p < q; } -bool cmp_point_edges(const PointEdges &p, const PointEdges &q) { - return p < q; -} +bool cmp_point_edges(const PointEdges &p, const PointEdges &q) { return p < q; } bool _on_segment(const Point &p, const Point &q, const Point &r) { if (q.x <= std::max(p.x, r.x) && q.x >= std::min(p.x, r.x) && @@ -342,8 +338,10 @@ point. Otherwise it is normal point. PointType get_point_type(Point p, PointToEdges &point_to_edges) { if (point_to_edges.at(p).size() != 2) return PointType::INTERSECTION; auto edges = point_to_edges.at(p); - if (edges[0].opposite_point < p && edges[1].opposite_point < p) return PointType::MERGE; - if (p < edges[0].opposite_point && p < edges[1].opposite_point) return PointType::SPLIT; + if (edges[0].opposite_point < p && edges[1].opposite_point < p) + return PointType::MERGE; + if (p < edges[0].opposite_point && p < edges[1].opposite_point) + return PointType::SPLIT; return PointType::NORMAL; } @@ -351,14 +349,15 @@ PointType get_point_type(Point p, PointToEdges &point_to_edges) { Get map from point to list of edges which contains this point. Also sort each list by point order. */ -PointToEdges get_points_edges(std::vector & edges) { +PointToEdges get_points_edges(std::vector &edges) { PointToEdges point_to_edges; for (size_t i = 0; i < edges.size(); i++) { point_to_edges[edges[i].left].emplace_back(i, edges[i].right); point_to_edges[edges[i].right].emplace_back(i, edges[i].left); } - for (auto & point_to_edge : point_to_edges) { - std::sort(point_to_edge.second.begin(), point_to_edge.second.end(), cmp_point_edges); + for (auto &point_to_edge : point_to_edges) { + std::sort(point_to_edge.second.begin(), point_to_edge.second.end(), + cmp_point_edges); } return point_to_edges; } @@ -391,8 +390,9 @@ sweeping_line_triangulation(const std::vector &polygon) { case PointType::SPLIT: // split sweeping line on two lines // add edge sor cutting polygon on two parts - line = Line(Segment(*point, point_to_edges.at(*point)[0].opposite_point), - Segment(*point, point_to_edges.at(*point)[1].opposite_point)); + line = + Line(Segment(*point, point_to_edges.at(*point)[0].opposite_point), + Segment(*point, point_to_edges.at(*point)[1].opposite_point)); break; case PointType::MERGE: // merge two sweeping lines to one From 22052bd267d30174743cfff3ef4cfdf3a182323f Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Fri, 11 Oct 2024 09:40:51 +0200 Subject: [PATCH 23/76] fix warning in code --- .../triangulate.hpp | 56 ++++++++++--------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulate.hpp index c0cfa99..e2d7150 100644 --- a/src/PartSegCore_compiled_backend/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulate.hpp @@ -1,6 +1,8 @@ +#include #include #include #include +#include enum PointType { NORMAL, SPLIT, MERGE, INTERSECTION }; /* Point class with x and y coordinates */ @@ -9,7 +11,7 @@ struct Point { float y; bool operator==(const Point &p) const { return x == p.x && y == p.y; } Point(float x, float y) : x(x), y(y) {} - Point() {} + Point() = default; bool operator<(const Point &p) const { if (this->x == p.x) { @@ -29,7 +31,7 @@ struct Event { : x(x), y(y), index(index), is_left(is_left) {} Event(const Point &p, int index, bool is_left) : x(p.x), y(p.y), index(index), is_left(is_left) {} - Event() {} + Event() = default; bool operator<(const Event &e) const { if (x == e.x) { @@ -53,7 +55,7 @@ struct PairHash { struct PointHash { std::size_t operator()(const Point &p) const { - return std::hash()(p.x) * 31 + std::hash()(p.y); + return std::hash()(p.x) * 31 + std::hash()(p.y); } }; @@ -69,12 +71,12 @@ struct Segment { this->right = p1; } } - Segment() {} + Segment() = default; }; struct Line { Segment left, right; - Line() {}; + Line() = default; Line(const Segment &left, const Segment &right) : left(left), right(right) {}; }; @@ -86,14 +88,14 @@ struct OrderedPolygon { }; struct Triangle { - int x; - int y; - int z; - Triangle(int x, int y, int z) : x(x), y(y), z(z) {}; + std::size_t x; + std::size_t y; + std::size_t z; + Triangle(std::size_t x, std::size_t y, std::size_t z) : x(x), y(y), z(z) {}; Triangle() {}; }; -typedef size_t EdgeIndex; +typedef std::size_t EdgeIndex; struct PointEdges { EdgeIndex edge_index; @@ -179,9 +181,9 @@ std::unordered_set, PairHash> _find_intersections( std::vector events; std::set active; events.reserve(2 * segments.size()); - for (size_t i = 0; i < segments.size(); i++) { - events.push_back(Event(segments[i].left, i, true)); - events.push_back(Event(segments[i].right, i, false)); + for (std::size_t i = 0; i < segments.size(); i++) { + events.emplace_back(segments[i].left, i, true); + events.emplace_back(segments[i].right, i, false); } std::sort(events.begin(), events.end(), cmp_event); @@ -234,16 +236,16 @@ Point _find_intersection(const Segment &s1, const Segment &s2) { b2 = s2.left.x - s2.right.x; c2 = a2 * s2.left.x + b2 * s2.left.y; det = a1 * b2 - a2 * b1; - if (det == 0) return Point(0, 0); + if (det == 0) return {0, 0}; x = (b2 * c1 - b1 * c2) / det; y = (a1 * c2 - a2 * c1) / det; - return Point(x, y); + return {x, y}; } bool _is_convex(const std::vector &polygon) { int orientation = 0; int triangle_orientation; - for (size_t i = 0; i < polygon.size() - 2; i++) { + for (std::size_t i = 0; i < polygon.size() - 2; i++) { triangle_orientation = _orientation(polygon[i], polygon[i + 1], polygon[i + 2]); if (triangle_orientation == 0) continue; @@ -266,9 +268,9 @@ bool _is_convex(const std::vector &polygon) { std::vector _triangle_convex_polygon( const std::vector &polygon) { std::vector result; - for (size_t i = 1; i < polygon.size() - 1; i++) { + for (std::size_t i = 1; i < polygon.size() - 1; i++) { if (_orientation(polygon[0], polygon[i], polygon[i + 1]) != 0) { - result.push_back(Triangle(0, i, i + 1)); + result.emplace_back(0, i, i + 1); } } return result; @@ -277,10 +279,10 @@ std::vector _triangle_convex_polygon( std::vector calc_edges(const std::vector &polygon) { std::vector edges; edges.reserve(polygon.size()); - for (size_t i = 0; i < polygon.size() - 1; i++) { - edges.push_back(Segment(polygon[i], polygon[i + 1])); + for (std::size_t i = 0; i < polygon.size() - 1; i++) { + edges.emplace_back(polygon[i], polygon[i + 1]); } - edges.push_back(Segment(polygon[polygon.size() - 1], polygon[0])); + edges.emplace_back(polygon[polygon.size() - 1], polygon[0]); return edges; } @@ -290,14 +292,14 @@ std::vector find_intersection_points(const std::vector &polygon) { auto edges = calc_edges(polygon); auto intersections = _find_intersections(edges); - if (intersections.size() == 0) return polygon; - std::unordered_map> intersections_points; + if (intersections.empty()) return polygon; + std::unordered_map> intersections_points; for (auto p = intersections.begin(); p != intersections.end(); p++) { auto inter_point = _find_intersection(edges[p->first], edges[p->second]); intersections_points[p->first].push_back(inter_point); intersections_points[p->second].push_back(inter_point); } - int points_count = polygon.size(); + std::size_t points_count = polygon.size(); for (auto iter_inter = intersections_points.begin(); iter_inter != intersections_points.end(); iter_inter++) { points_count += iter_inter->second.size() - 1; @@ -308,13 +310,13 @@ std::vector find_intersection_points(const std::vector &polygon) { std::vector new_polygon; new_polygon.reserve(points_count); - for (size_t i = 0; i < polygon.size(); i++) { + for (std::size_t i = 0; i < polygon.size(); i++) { auto point = polygon[i]; new_polygon.push_back(point); if (intersections_points.count(i)) { auto new_points = intersections_points[i]; if (new_points[0] == point) { - for (size_t j = 1; j < new_points.size() - 1; j++) { + for (std::size_t j = 1; j < new_points.size() - 1; j++) { new_polygon.push_back(new_points[j]); } } else { @@ -351,7 +353,7 @@ Also sort each list by point order. */ PointToEdges get_points_edges(std::vector &edges) { PointToEdges point_to_edges; - for (size_t i = 0; i < edges.size(); i++) { + for (std::size_t i = 0; i < edges.size(); i++) { point_to_edges[edges[i].left].emplace_back(i, edges[i].right); point_to_edges[edges[i].right].emplace_back(i, edges[i].left); } From cc59c27b3334f6aadc407caf30adc62a77be1248 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Fri, 11 Oct 2024 10:31:08 +0200 Subject: [PATCH 24/76] next style fixes --- .../triangulate.hpp | 82 ++++++++++--------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulate.hpp index e2d7150..1761dae 100644 --- a/src/PartSegCore_compiled_backend/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulate.hpp @@ -60,15 +60,15 @@ struct PointHash { }; struct Segment { - Point left; - Point right; + Point left{}; + Point right{}; Segment(Point p1, Point p2) { if (p1 < p2) { - this->left = p1; - this->right = p2; + left = p1; + right = p2; } else { - this->left = p2; - this->right = p1; + left = p2; + right = p1; } } Segment() = default; @@ -92,7 +92,7 @@ struct Triangle { std::size_t y; std::size_t z; Triangle(std::size_t x, std::size_t y, std::size_t z) : x(x), y(y), z(z) {}; - Triangle() {}; + Triangle() = default; }; typedef std::size_t EdgeIndex; @@ -187,30 +187,30 @@ std::unordered_set, PairHash> _find_intersections( } std::sort(events.begin(), events.end(), cmp_event); - for (auto event = events.begin(); event != events.end(); event++) { - if (event->is_left) { - auto next = active.lower_bound(*event); + for (auto &event : events) { + if (event.is_left) { + auto next = active.lower_bound(event); auto prev = pred(active, next); if (next != active.end() && - _do_intersect(segments[event->index], segments[next->index])) { - if (event->index < next->index) { - intersections.emplace(event->index, next->index); + _do_intersect(segments[event.index], segments[next->index])) { + if (event.index < next->index) { + intersections.emplace(event.index, next->index); } else { - intersections.emplace(next->index, event->index); + intersections.emplace(next->index, event.index); } } if (prev != active.end() && - _do_intersect(segments[event->index], segments[prev->index])) { - if (event->index < prev->index) { - intersections.emplace(event->index, prev->index); + _do_intersect(segments[event.index], segments[prev->index])) { + if (event.index < prev->index) { + intersections.emplace(event.index, prev->index); } else { - intersections.emplace(prev->index, event->index); + intersections.emplace(prev->index, event.index); } } - active.insert(*event); + active.insert(event); } else { auto it = - active.find(Event(segments[event->index].left, event->index, true)); + active.find(Event(segments[event.index].left, event.index, true)); auto next = succ(active, it); auto prev = pred(active, it); if (next != active.end() && prev != active.end() && @@ -287,25 +287,27 @@ std::vector calc_edges(const std::vector &polygon) { } std::vector find_intersection_points(const std::vector &polygon) { - /* find all edge intersedions and add mid points for all such intersection + /* find all edge intersections and add mid-points for all such intersection * place*/ auto edges = calc_edges(polygon); auto intersections = _find_intersections(edges); if (intersections.empty()) return polygon; std::unordered_map> intersections_points; - for (auto p = intersections.begin(); p != intersections.end(); p++) { - auto inter_point = _find_intersection(edges[p->first], edges[p->second]); - intersections_points[p->first].push_back(inter_point); - intersections_points[p->second].push_back(inter_point); + for (const auto &intersection : intersections) { + auto inter_point = _find_intersection(edges[intersection.first], + edges[intersection.second]); + intersections_points[intersection.first].push_back(inter_point); + intersections_points[intersection.second].push_back(inter_point); } std::size_t points_count = polygon.size(); - for (auto iter_inter = intersections_points.begin(); - iter_inter != intersections_points.end(); iter_inter++) { - points_count += iter_inter->second.size() - 1; - iter_inter->second.push_back(edges[iter_inter->first].right); - iter_inter->second.push_back(edges[iter_inter->first].left); - std::sort(iter_inter->second.begin(), iter_inter->second.end(), cmp_point); + for (auto &intersections_point : intersections_points) { + points_count += intersections_point.second.size() - 1; + intersections_point.second.push_back( + edges[intersections_point.first].right); + intersections_point.second.push_back(edges[intersections_point.first].left); + std::sort(intersections_point.second.begin(), + intersections_point.second.end(), cmp_point); } std::vector new_polygon; @@ -320,7 +322,7 @@ std::vector find_intersection_points(const std::vector &polygon) { new_polygon.push_back(new_points[j]); } } else { - for (int j = new_points.size() - 2; j > 0; j++) { + for (std::size_t j = new_points.size() - 2; j > 0; j++) { new_polygon.push_back(new_points[j]); } } @@ -339,7 +341,7 @@ point. Otherwise it is normal point. */ PointType get_point_type(Point p, PointToEdges &point_to_edges) { if (point_to_edges.at(p).size() != 2) return PointType::INTERSECTION; - auto edges = point_to_edges.at(p); + const auto &edges = point_to_edges.at(p); if (edges[0].opposite_point < p && edges[1].opposite_point < p) return PointType::MERGE; if (p < edges[0].opposite_point && p < edges[1].opposite_point) @@ -380,11 +382,10 @@ sweeping_line_triangulation(const std::vector &polygon) { // copy to avoid modification of original vector std::sort(sorted_points.begin(), sorted_points.end(), cmp_point); std::vector ordered_polygon_li; - ordered_polygon_li.push_back(OrderedPolygon()); + ordered_polygon_li.emplace_back(); Line line; - for (auto point = sorted_points.begin(); point != sorted_points.end(); - point++) { - auto point_type = get_point_type(*point, point_to_edges); + for (auto &sorted_point : sorted_points) { + auto point_type = get_point_type(sorted_point, point_to_edges); switch (point_type) { case PointType::NORMAL: // change edge adjusted to current sweeping line @@ -392,9 +393,10 @@ sweeping_line_triangulation(const std::vector &polygon) { case PointType::SPLIT: // split sweeping line on two lines // add edge sor cutting polygon on two parts - line = - Line(Segment(*point, point_to_edges.at(*point)[0].opposite_point), - Segment(*point, point_to_edges.at(*point)[1].opposite_point)); + line = Line(Segment(sorted_point, + point_to_edges.at(sorted_point)[0].opposite_point), + Segment(sorted_point, + point_to_edges.at(sorted_point)[1].opposite_point)); break; case PointType::MERGE: // merge two sweeping lines to one From aec7522b8ed97f743d967e01d4d4d4deef9a276a Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Fri, 11 Oct 2024 11:35:51 +0200 Subject: [PATCH 25/76] refactor code for better readability --- CMakeLists.txt | 8 +- .../triangulate.hpp | 438 ------------------ .../triangulate.pyx | 45 +- .../triangulation/intersection.hpp | 175 +++++++ .../triangulation/point.hpp | 67 +++ .../triangulation/triangulate.hpp | 262 +++++++++++ 6 files changed, 536 insertions(+), 459 deletions(-) delete mode 100644 src/PartSegCore_compiled_backend/triangulate.hpp create mode 100644 src/PartSegCore_compiled_backend/triangulation/intersection.hpp create mode 100644 src/PartSegCore_compiled_backend/triangulation/point.hpp create mode 100644 src/PartSegCore_compiled_backend/triangulation/triangulate.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5468674..bad85bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,7 +102,13 @@ target_include_directories(_napari_mapping PRIVATE "${NUMPY_INCLUDE_DIR}") target_link_libraries(_napari_mapping PRIVATE OpenMP::OpenMP_CXX) cython_transpile(src/PartSegCore_compiled_backend/triangulate.pyx LANGUAGE CXX OUTPUT_VARIABLE triangulate_cython_cxx) -python_add_library(triangulate MODULE "${triangulate_cython_cxx}" src/PartSegCore_compiled_backend/triangulate.hpp WITH_SOABI) +python_add_library( + triangulate MODULE + "${triangulate_cython_cxx}" + src/PartSegCore_compiled_backend/triangulation/triangulate.hpp + src/PartSegCore_compiled_backend/triangulation/intersection.hpp + src/PartSegCore_compiled_backend/triangulation/point.hpp + WITH_SOABI) target_include_directories(triangulate PRIVATE "${NUMPY_INCLUDE_DIR}") target_include_directories(triangulate PRIVATE src/PartSegCore_compiled_backend/) diff --git a/src/PartSegCore_compiled_backend/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulate.hpp deleted file mode 100644 index 1761dae..0000000 --- a/src/PartSegCore_compiled_backend/triangulate.hpp +++ /dev/null @@ -1,438 +0,0 @@ -#include -#include -#include -#include -#include - -enum PointType { NORMAL, SPLIT, MERGE, INTERSECTION }; -/* Point class with x and y coordinates */ -struct Point { - float x; - float y; - bool operator==(const Point &p) const { return x == p.x && y == p.y; } - Point(float x, float y) : x(x), y(y) {} - Point() = default; - - bool operator<(const Point &p) const { - if (this->x == p.x) { - return this->y < p.y; - } - return this->x < p.x; - } -}; - -struct Event { - float x; - float y; - int index; - bool is_left; - - Event(float x, float y, int index, bool is_left) - : x(x), y(y), index(index), is_left(is_left) {} - Event(const Point &p, int index, bool is_left) - : x(p.x), y(p.y), index(index), is_left(is_left) {} - Event() = default; - - bool operator<(const Event &e) const { - if (x == e.x) { - if (y == e.y) { - if (is_left == e.is_left) { - return index < e.index; - } - return is_left > e.is_left; - } - return y < e.y; - } - return x < e.x; - } -}; - -struct PairHash { - std::size_t operator()(const std::pair &p) const { - return std::hash()(p.first) * 31 + std::hash()(p.second); - } -}; - -struct PointHash { - std::size_t operator()(const Point &p) const { - return std::hash()(p.x) * 31 + std::hash()(p.y); - } -}; - -struct Segment { - Point left{}; - Point right{}; - Segment(Point p1, Point p2) { - if (p1 < p2) { - left = p1; - right = p2; - } else { - left = p2; - right = p1; - } - } - Segment() = default; -}; - -struct Line { - Segment left, right; - Line() = default; - Line(const Segment &left, const Segment &right) : left(left), right(right) {}; -}; - -struct OrderedPolygon { - Point top; - Point bottom; - std::vector left; - std::vector right; -}; - -struct Triangle { - std::size_t x; - std::size_t y; - std::size_t z; - Triangle(std::size_t x, std::size_t y, std::size_t z) : x(x), y(y), z(z) {}; - Triangle() = default; -}; - -typedef std::size_t EdgeIndex; - -struct PointEdges { - EdgeIndex edge_index; - Point opposite_point; - PointEdges(EdgeIndex edge_index, Point opposite_point) - : edge_index(edge_index), opposite_point(opposite_point) {} - bool operator<(const PointEdges &e) const { - return opposite_point < e.opposite_point; - } -}; - -typedef std::unordered_map, PointHash> - PointToEdges; - -bool point_eq(const Point &p, const Point &q) { - return p.x == q.x && p.y == q.y; -} - -bool cmp_point(const Point &p, const Point &q) { return p < q; } - -bool cmp_pair_point(const std::pair &p, - const std::pair &q) { - return p.first < q.first; -} - -bool cmp_event(const Event &p, const Event &q) { return p < q; } - -bool cmp_point_edges(const PointEdges &p, const PointEdges &q) { return p < q; } - -bool _on_segment(const Point &p, const Point &q, const Point &r) { - if (q.x <= std::max(p.x, r.x) && q.x >= std::min(p.x, r.x) && - q.y <= std::max(p.y, r.y) && q.y >= std::min(p.y, r.y)) - return true; - return false; -} - -int _orientation(const Point &p, const Point &q, const Point &r) { - float val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y); - if (val == 0) return 0; - return (val > 0) ? 1 : 2; -} - -bool _do_intersect(const Segment &s1, const Segment &s2) { - const Point &p1 = s1.left; - const Point &q1 = s1.right; - const Point &p2 = s2.left; - const Point &q2 = s2.right; - if (point_eq(p1, p2) || point_eq(p1, q2) || point_eq(q1, p2) || - point_eq(q1, q2)) - return false; - - int o1 = _orientation(p1, q1, p2); - int o2 = _orientation(p1, q1, q2); - int o3 = _orientation(p2, q2, p1); - int o4 = _orientation(p2, q2, q1); - - if (o1 != o2 && o3 != o4) return true; - - if (o1 == 0 && _on_segment(p1, p2, q1)) return true; - if (o2 == 0 && _on_segment(p1, q2, q1)) return true; - if (o3 == 0 && _on_segment(p2, p1, q2)) return true; - if (o4 == 0 && _on_segment(p2, q1, q2)) return true; - - return false; -} - -std::set::iterator pred(std::set &s, - std::set::iterator it) { - if (it == s.begin()) { - return s.end(); - } - return --it; -} - -std::set::iterator succ(std::set &s, - std::set::iterator it) { - return ++it; -} - -std::unordered_set, PairHash> _find_intersections( - const std::vector &segments) { - std::unordered_set, PairHash> intersections; - std::vector events; - std::set active; - events.reserve(2 * segments.size()); - for (std::size_t i = 0; i < segments.size(); i++) { - events.emplace_back(segments[i].left, i, true); - events.emplace_back(segments[i].right, i, false); - } - std::sort(events.begin(), events.end(), cmp_event); - - for (auto &event : events) { - if (event.is_left) { - auto next = active.lower_bound(event); - auto prev = pred(active, next); - if (next != active.end() && - _do_intersect(segments[event.index], segments[next->index])) { - if (event.index < next->index) { - intersections.emplace(event.index, next->index); - } else { - intersections.emplace(next->index, event.index); - } - } - if (prev != active.end() && - _do_intersect(segments[event.index], segments[prev->index])) { - if (event.index < prev->index) { - intersections.emplace(event.index, prev->index); - } else { - intersections.emplace(prev->index, event.index); - } - } - active.insert(event); - } else { - auto it = - active.find(Event(segments[event.index].left, event.index, true)); - auto next = succ(active, it); - auto prev = pred(active, it); - if (next != active.end() && prev != active.end() && - _do_intersect(segments[next->index], segments[prev->index])) { - if (next->index < prev->index) { - intersections.emplace(next->index, prev->index); - } else { - intersections.emplace(prev->index, next->index); - } - } - active.erase(it); - } - } - return intersections; -} - -Point _find_intersection(const Segment &s1, const Segment &s2) { - float a1, b1, c1, a2, b2, c2, det, x, y; - a1 = s1.right.y - s1.left.y; - b1 = s1.left.x - s1.right.x; - c1 = a1 * s1.left.x + b1 * s1.left.y; - a2 = s2.right.y - s2.left.y; - b2 = s2.left.x - s2.right.x; - c2 = a2 * s2.left.x + b2 * s2.left.y; - det = a1 * b2 - a2 * b1; - if (det == 0) return {0, 0}; - x = (b2 * c1 - b1 * c2) / det; - y = (a1 * c2 - a2 * c1) / det; - return {x, y}; -} - -bool _is_convex(const std::vector &polygon) { - int orientation = 0; - int triangle_orientation; - for (std::size_t i = 0; i < polygon.size() - 2; i++) { - triangle_orientation = - _orientation(polygon[i], polygon[i + 1], polygon[i + 2]); - if (triangle_orientation == 0) continue; - if (orientation == 0) - orientation = triangle_orientation; - else if (orientation != triangle_orientation) - return false; - } - triangle_orientation = _orientation(polygon[polygon.size() - 2], - polygon[polygon.size() - 1], polygon[0]); - if (triangle_orientation != 0 && triangle_orientation != orientation) - return false; - triangle_orientation = - _orientation(polygon[polygon.size() - 1], polygon[0], polygon[1]); - if (triangle_orientation != 0 && triangle_orientation != orientation) - return false; - return true; -} - -std::vector _triangle_convex_polygon( - const std::vector &polygon) { - std::vector result; - for (std::size_t i = 1; i < polygon.size() - 1; i++) { - if (_orientation(polygon[0], polygon[i], polygon[i + 1]) != 0) { - result.emplace_back(0, i, i + 1); - } - } - return result; -} - -std::vector calc_edges(const std::vector &polygon) { - std::vector edges; - edges.reserve(polygon.size()); - for (std::size_t i = 0; i < polygon.size() - 1; i++) { - edges.emplace_back(polygon[i], polygon[i + 1]); - } - edges.emplace_back(polygon[polygon.size() - 1], polygon[0]); - return edges; -} - -std::vector find_intersection_points(const std::vector &polygon) { - /* find all edge intersections and add mid-points for all such intersection - * place*/ - auto edges = calc_edges(polygon); - - auto intersections = _find_intersections(edges); - if (intersections.empty()) return polygon; - std::unordered_map> intersections_points; - for (const auto &intersection : intersections) { - auto inter_point = _find_intersection(edges[intersection.first], - edges[intersection.second]); - intersections_points[intersection.first].push_back(inter_point); - intersections_points[intersection.second].push_back(inter_point); - } - std::size_t points_count = polygon.size(); - for (auto &intersections_point : intersections_points) { - points_count += intersections_point.second.size() - 1; - intersections_point.second.push_back( - edges[intersections_point.first].right); - intersections_point.second.push_back(edges[intersections_point.first].left); - std::sort(intersections_point.second.begin(), - intersections_point.second.end(), cmp_point); - } - - std::vector new_polygon; - new_polygon.reserve(points_count); - for (std::size_t i = 0; i < polygon.size(); i++) { - auto point = polygon[i]; - new_polygon.push_back(point); - if (intersections_points.count(i)) { - auto new_points = intersections_points[i]; - if (new_points[0] == point) { - for (std::size_t j = 1; j < new_points.size() - 1; j++) { - new_polygon.push_back(new_points[j]); - } - } else { - for (std::size_t j = new_points.size() - 2; j > 0; j++) { - new_polygon.push_back(new_points[j]); - } - } - } - } - return new_polygon; -} - -/* -Calculate point type. -If there is more than two edges adjusted to point, it is intersection point. -If there are two adjusted edges, it could be one of split, merge and normal -point. If both adjusted edges have opposite end before given point p, this is -merge point. If both adjusted edges have opposite end after given point p, split -point. Otherwise it is normal point. -*/ -PointType get_point_type(Point p, PointToEdges &point_to_edges) { - if (point_to_edges.at(p).size() != 2) return PointType::INTERSECTION; - const auto &edges = point_to_edges.at(p); - if (edges[0].opposite_point < p && edges[1].opposite_point < p) - return PointType::MERGE; - if (p < edges[0].opposite_point && p < edges[1].opposite_point) - return PointType::SPLIT; - return PointType::NORMAL; -} - -/* -Get map from point to list of edges which contains this point. -Also sort each list by point order. -*/ -PointToEdges get_points_edges(std::vector &edges) { - PointToEdges point_to_edges; - for (std::size_t i = 0; i < edges.size(); i++) { - point_to_edges[edges[i].left].emplace_back(i, edges[i].right); - point_to_edges[edges[i].right].emplace_back(i, edges[i].left); - } - for (auto &point_to_edge : point_to_edges) { - std::sort(point_to_edge.second.begin(), point_to_edge.second.end(), - cmp_point_edges); - } - return point_to_edges; -} - -/* -This is implementation of sweeping line triangulation of polygon -Its assumes that there is no edge intersections, but may be a point with more -than 2 edges. described on this lecture: -https://www.youtube.com/playlist?list=PLtTatrCwXHzEqzJMaTUFgqoCNllgwk4DH -*/ -std::pair, std::vector> -sweeping_line_triangulation(const std::vector &polygon) { - std::vector result; - auto edges = calc_edges(polygon); - PointToEdges point_to_edges = get_points_edges(edges); - - std::vector sorted_points = polygon; - // copy to avoid modification of original vector - std::sort(sorted_points.begin(), sorted_points.end(), cmp_point); - std::vector ordered_polygon_li; - ordered_polygon_li.emplace_back(); - Line line; - for (auto &sorted_point : sorted_points) { - auto point_type = get_point_type(sorted_point, point_to_edges); - switch (point_type) { - case PointType::NORMAL: - // change edge adjusted to current sweeping line - break; - case PointType::SPLIT: - // split sweeping line on two lines - // add edge sor cutting polygon on two parts - line = Line(Segment(sorted_point, - point_to_edges.at(sorted_point)[0].opposite_point), - Segment(sorted_point, - point_to_edges.at(sorted_point)[1].opposite_point)); - break; - case PointType::MERGE: - // merge two sweeping lines to one - // save point as point to start new line for SPLIT pointcase - break; - case PointType::INTERSECTION: - // this is merge and split point at same time - // this is not described in original algorithm - // but we need it to handle self intersecting polygons - // Remember about more than 4 edges case - break; - } - } - return std::make_pair(result, polygon); -} - -std::pair, std::vector> _triangulate_polygon( - const std::vector &polygon) { - if (polygon.size() < 3) - return std::make_pair(std::vector(), polygon); - if (polygon.size() == 3) - return std::make_pair(std::vector({Triangle(0, 1, 2)}), polygon); - if (polygon.size() == 4) { - if (_orientation(polygon[0], polygon[1], polygon[2]) != - _orientation(polygon[0], polygon[3], polygon[2])) - return std::make_pair( - std::vector({Triangle(0, 1, 2), Triangle(0, 3, 2)}), - polygon); - } - - if (_is_convex(polygon)) - return std::make_pair(_triangle_convex_polygon(polygon), polygon); - - // Implement sweeping line algorithm for triangulation - // described on this lecture: - // https://www.youtube.com/playlist?list=PLtTatrCwXHzEqzJMaTUFgqoCNllgwk4DH - // - return sweeping_line_triangulation(find_intersection_points(polygon)); -} diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index 53433b4..f219e09 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -16,19 +16,7 @@ from libcpp.unordered_map cimport unordered_map from libcpp.algorithm cimport sort -cdef extern from "triangulate.hpp": - - cdef cppclass Event: - float x - float y - int index - bool is_left - Event(float x, float y, int index, bool is_left) - Event() - - cdef cppclass PairHash: - size_t operator()(pair[int, int] p) const - +cdef extern from "triangulation/point.hpp" namespace "partsegcore::point": cdef cppclass Point: float x float y @@ -42,21 +30,38 @@ cdef extern from "triangulate.hpp": Segment(Point left, Point right) Segment() - cdef cppclass Triangle: - int x - int y - int z - Triangle(int x, int y, int z) - Triangle() - bool point_eq(const Point& a, const Point& b) bool cmp_point(const Point& a, const Point& b) + +cdef extern from "triangulation/intersection.hpp" namespace "partsegcore::intersection": + cdef cppclass Event: + float x + float y + int index + bool is_left + Event(float x, float y, int index, bool is_left) + Event() + + cdef cppclass PairHash: + size_t operator()(pair[int, int] p) const + bool cmp_event(const Event& p, const Event& q) bool _on_segment(const Point& p, const Point& q, const Point& r) int _orientation(const Point& p, const Point& q, const Point& r) bool _do_intersect(const Segment& s1, const Segment& s2) unordered_set[pair[int, int], PairHash] _find_intersections(const vector[Segment]& segments) Point _find_intersection(const Segment& s1, const Segment& s2) + + +cdef extern from "triangulation/triangulate.hpp" namespace "partsegcore::triangulation": + + cdef cppclass Triangle: + int x + int y + int z + Triangle(int x, int y, int z) + Triangle() + bool _is_convex(const vector[Point]& polygon) vector[Triangle] _triangle_convex_polygon(const vector[Point]& polygon) diff --git a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp new file mode 100644 index 0000000..e974cf1 --- /dev/null +++ b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp @@ -0,0 +1,175 @@ +// +// Created by Grzegorz Bokota on 11.10.24. +// + +#ifndef PARTSEGCORE_INTERSECTION_H +#define PARTSEGCORE_INTERSECTION_H + +#include +#include +#include +#include +#include + +#include "point.hpp" + +namespace partsegcore { +namespace intersection { + +struct Event { + float x; + float y; + int index; + bool is_left; + + Event(float x, float y, int index, bool is_left) + : x(x), y(y), index(index), is_left(is_left) {} + Event(const point::Point &p, int index, bool is_left) + : x(p.x), y(p.y), index(index), is_left(is_left) {} + Event() = default; + + bool operator<(const Event &e) const { + if (x == e.x) { + if (y == e.y) { + if (is_left == e.is_left) { + return index < e.index; + } + return is_left > e.is_left; + } + return y < e.y; + } + return x < e.x; + } +}; + +struct PairHash { + std::size_t operator()(const std::pair &p) const { + return std::hash()(p.first) * 31 + std::hash()(p.second); + } +}; + +bool cmp_event(const Event &p, const Event &q) { return p < q; } + +bool _on_segment(const point::Point &p, const point::Point &q, + const point::Point &r) { + if (q.x <= std::max(p.x, r.x) && q.x >= std::min(p.x, r.x) && + q.y <= std::max(p.y, r.y) && q.y >= std::min(p.y, r.y)) + return true; + return false; +} + +int _orientation(const point::Point &p, const point::Point &q, + const point::Point &r) { + float val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y); + if (val == 0) return 0; + return (val > 0) ? 1 : 2; +} + +bool _do_intersect(const point::Segment &s1, const point::Segment &s2) { + const point::Point &p1 = s1.left; + const point::Point &q1 = s1.right; + const point::Point &p2 = s2.left; + const point::Point &q2 = s2.right; + if (point::point_eq(p1, p2) || point::point_eq(p1, q2) || + point::point_eq(q1, p2) || point::point_eq(q1, q2)) + return false; + + int o1 = _orientation(p1, q1, p2); + int o2 = _orientation(p1, q1, q2); + int o3 = _orientation(p2, q2, p1); + int o4 = _orientation(p2, q2, q1); + + if (o1 != o2 && o3 != o4) return true; + + if (o1 == 0 && _on_segment(p1, p2, q1)) return true; + if (o2 == 0 && _on_segment(p1, q2, q1)) return true; + if (o3 == 0 && _on_segment(p2, p1, q2)) return true; + if (o4 == 0 && _on_segment(p2, q1, q2)) return true; + + return false; +} + +std::set::iterator pred(std::set &s, + std::set::iterator it) { + if (it == s.begin()) { + return s.end(); + } + return --it; +} + +std::set::iterator succ(std::set &s, + std::set::iterator it) { + return ++it; +} + +std::unordered_set, intersection::PairHash> +_find_intersections(const std::vector &segments) { + std::unordered_set, intersection::PairHash> intersections; + std::vector events; + std::set active; + events.reserve(2 * segments.size()); + for (std::size_t i = 0; i < segments.size(); i++) { + events.emplace_back(segments[i].left, i, true); + events.emplace_back(segments[i].right, i, false); + } + std::sort(events.begin(), events.end(), cmp_event); + + for (auto &event : events) { + if (event.is_left) { + auto next = active.lower_bound(event); + auto prev = pred(active, next); + if (next != active.end() && + _do_intersect(segments[event.index], segments[next->index])) { + if (event.index < next->index) { + intersections.emplace(event.index, next->index); + } else { + intersections.emplace(next->index, event.index); + } + } + if (prev != active.end() && + _do_intersect(segments[event.index], segments[prev->index])) { + if (event.index < prev->index) { + intersections.emplace(event.index, prev->index); + } else { + intersections.emplace(prev->index, event.index); + } + } + active.insert(event); + } else { + auto it = + active.find(Event(segments[event.index].left, event.index, true)); + auto next = succ(active, it); + auto prev = pred(active, it); + if (next != active.end() && prev != active.end() && + _do_intersect(segments[next->index], segments[prev->index])) { + if (next->index < prev->index) { + intersections.emplace(next->index, prev->index); + } else { + intersections.emplace(prev->index, next->index); + } + } + active.erase(it); + } + } + return intersections; +} + +point::Point _find_intersection(const point::Segment &s1, + const point::Segment &s2) { + float a1, b1, c1, a2, b2, c2, det, x, y; + a1 = s1.right.y - s1.left.y; + b1 = s1.left.x - s1.right.x; + c1 = a1 * s1.left.x + b1 * s1.left.y; + a2 = s2.right.y - s2.left.y; + b2 = s2.left.x - s2.right.x; + c2 = a2 * s2.left.x + b2 * s2.left.y; + det = a1 * b2 - a2 * b1; + if (det == 0) return {0, 0}; + x = (b2 * c1 - b1 * c2) / det; + y = (a1 * c2 - a2 * c1) / det; + return {x, y}; +} +} // namespace intersection +} // namespace partsegcore + +#endif // PARTSEGCORE_INTERSECTION_H diff --git a/src/PartSegCore_compiled_backend/triangulation/point.hpp b/src/PartSegCore_compiled_backend/triangulation/point.hpp new file mode 100644 index 0000000..b7e7eb7 --- /dev/null +++ b/src/PartSegCore_compiled_backend/triangulation/point.hpp @@ -0,0 +1,67 @@ +// +// Created by Grzegorz Bokota on 11.10.24. +// + +#ifndef PARTSEGCORE_POINT_H +#define PARTSEGCORE_POINT_H + +#include +#include +#include +#include +#include + +namespace partsegcore { +namespace point { + +/* Point class with x and y coordinates */ +struct Point { + float x; + float y; + bool operator==(const Point &p) const { return x == p.x && y == p.y; } + Point(float x, float y) : x(x), y(y) {} + Point() = default; + + bool operator<(const Point &p) const { + if (this->x == p.x) { + return this->y < p.y; + } + return this->x < p.x; + } +}; + +struct PointHash { + std::size_t operator()(const Point &p) const { + return std::hash()(p.x) * 31 + std::hash()(p.y); + } +}; + +bool point_eq(const Point &p, const Point &q) { + return p.x == q.x && p.y == q.y; +} + +bool cmp_pair_point(const std::pair &p, + const std::pair &q) { + return p.first < q.first; +} + +struct Segment { + Point left{}; + Point right{}; + Segment(Point p1, Point p2) { + if (p1 < p2) { + left = p1; + right = p2; + } else { + left = p2; + right = p1; + } + } + Segment() = default; +}; + +bool cmp_point(const Point &p, const Point &q) { return p < q; } +} // namespace point +} // namespace partsegcore + +#endif // PARTSEGCORE_POINT_H diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp new file mode 100644 index 0000000..8a34a99 --- /dev/null +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -0,0 +1,262 @@ +#ifndef PARTSEGCORE_TRIANGULATE_H +#define PARTSEGCORE_TRIANGULATE_H + +#include +#include +#include +#include +#include + +#include "intersection.hpp" +#include "point.hpp" + +namespace partsegcore { +namespace triangulation { + +enum PointType { NORMAL, SPLIT, MERGE, INTERSECTION }; + +struct Line { + point::Segment left, right; + Line() = default; + Line(const point::Segment &left, const point::Segment &right) + : left(left), right(right) {}; +}; + +struct OrderedPolygon { + point::Point top; + point::Point bottom; + std::vector left; + std::vector right; +}; + +struct Triangle { + std::size_t x; + std::size_t y; + std::size_t z; + Triangle(std::size_t x, std::size_t y, std::size_t z) : x(x), y(y), z(z) {}; + Triangle() = default; +}; + +typedef std::size_t EdgeIndex; + +struct PointEdges { + EdgeIndex edge_index; + point::Point opposite_point; + PointEdges(EdgeIndex edge_index, point::Point opposite_point) + : edge_index(edge_index), opposite_point(opposite_point) {} + bool operator<(const PointEdges &e) const { + return opposite_point < e.opposite_point; + } +}; + +typedef std::unordered_map, + point::PointHash> + PointToEdges; + +bool cmp_point_edges(const PointEdges &p, const PointEdges &q) { return p < q; } + +bool _is_convex(const std::vector &polygon) { + int orientation = 0; + int triangle_orientation; + for (std::size_t i = 0; i < polygon.size() - 2; i++) { + triangle_orientation = + intersection::_orientation(polygon[i], polygon[i + 1], polygon[i + 2]); + if (triangle_orientation == 0) continue; + if (orientation == 0) + orientation = triangle_orientation; + else if (orientation != triangle_orientation) + return false; + } + triangle_orientation = intersection::_orientation( + polygon[polygon.size() - 2], polygon[polygon.size() - 1], polygon[0]); + if (triangle_orientation != 0 && triangle_orientation != orientation) + return false; + triangle_orientation = intersection::_orientation(polygon[polygon.size() - 1], + polygon[0], polygon[1]); + if (triangle_orientation != 0 && triangle_orientation != orientation) + return false; + return true; +} + +std::vector _triangle_convex_polygon( + const std::vector &polygon) { + std::vector result; + for (std::size_t i = 1; i < polygon.size() - 1; i++) { + if (intersection::_orientation(polygon[0], polygon[i], polygon[i + 1]) != + 0) { + result.emplace_back(0, i, i + 1); + } + } + return result; +} + +std::vector calc_edges( + const std::vector &polygon) { + std::vector edges; + edges.reserve(polygon.size()); + for (std::size_t i = 0; i < polygon.size() - 1; i++) { + edges.emplace_back(polygon[i], polygon[i + 1]); + } + edges.emplace_back(polygon[polygon.size() - 1], polygon[0]); + return edges; +} + +std::vector find_intersection_points( + const std::vector &polygon) { + /* find all edge intersections and add mid-points for all such intersection + * place*/ + auto edges = calc_edges(polygon); + + auto intersections = intersection::_find_intersections(edges); + if (intersections.empty()) return polygon; + std::unordered_map> + intersections_points; + for (const auto &intersection : intersections) { + auto inter_point = intersection::_find_intersection( + edges[intersection.first], edges[intersection.second]); + intersections_points[intersection.first].push_back(inter_point); + intersections_points[intersection.second].push_back(inter_point); + } + std::size_t points_count = polygon.size(); + for (auto &intersections_point : intersections_points) { + points_count += intersections_point.second.size() - 1; + intersections_point.second.push_back( + edges[intersections_point.first].right); + intersections_point.second.push_back(edges[intersections_point.first].left); + std::sort(intersections_point.second.begin(), + intersections_point.second.end(), point::cmp_point); + } + + std::vector new_polygon; + new_polygon.reserve(points_count); + for (std::size_t i = 0; i < polygon.size(); i++) { + auto point = polygon[i]; + new_polygon.push_back(point); + if (intersections_points.count(i)) { + auto new_points = intersections_points[i]; + if (new_points[0] == point) { + for (std::size_t j = 1; j < new_points.size() - 1; j++) { + new_polygon.push_back(new_points[j]); + } + } else { + for (std::size_t j = new_points.size() - 2; j > 0; j++) { + new_polygon.push_back(new_points[j]); + } + } + } + } + return new_polygon; +} + +/* + Calculate point type. + If there is more than two edges adjusted to point, it is intersection point. + If there are two adjusted edges, it could be one of split, merge and normal + point. If both adjusted edges have opposite end before given point p, this + is merge point. If both adjusted edges have opposite end after given point p, + split point. Otherwise it is normal point. + */ +PointType get_point_type(point::Point p, PointToEdges &point_to_edges) { + if (point_to_edges.at(p).size() != 2) return PointType::INTERSECTION; + const auto &edges = point_to_edges.at(p); + if (edges[0].opposite_point < p && edges[1].opposite_point < p) + return PointType::MERGE; + if (p < edges[0].opposite_point && p < edges[1].opposite_point) + return PointType::SPLIT; + return PointType::NORMAL; +} + +/* + Get map from point to list of edges which contains this point. + Also sort each list by point order. + */ +PointToEdges get_points_edges(std::vector &edges) { + PointToEdges point_to_edges; + for (std::size_t i = 0; i < edges.size(); i++) { + point_to_edges[edges[i].left].emplace_back(i, edges[i].right); + point_to_edges[edges[i].right].emplace_back(i, edges[i].left); + } + for (auto &point_to_edge : point_to_edges) { + std::sort(point_to_edge.second.begin(), point_to_edge.second.end(), + cmp_point_edges); + } + return point_to_edges; +} + +/* + This is implementation of sweeping line triangulation of polygon + Its assumes that there is no edge intersections, but may be a point with + more than 2 edges. described on this lecture: + https://www.youtube.com/playlist?list=PLtTatrCwXHzEqzJMaTUFgqoCNllgwk4DH + */ +std::pair, std::vector> +sweeping_line_triangulation(const std::vector &polygon) { + std::vector result; + auto edges = calc_edges(polygon); + PointToEdges point_to_edges = get_points_edges(edges); + + std::vector sorted_points = polygon; + // copy to avoid modification of original vector + std::sort(sorted_points.begin(), sorted_points.end(), point::cmp_point); + std::vector ordered_polygon_li; + ordered_polygon_li.emplace_back(); + Line line; + for (auto &sorted_point : sorted_points) { + auto point_type = get_point_type(sorted_point, point_to_edges); + switch (point_type) { + case PointType::NORMAL: + // change edge adjusted to current sweeping line + break; + case PointType::SPLIT: + // split sweeping line on two lines + // add edge sor cutting polygon on two parts + line = Line( + point::Segment(sorted_point, + point_to_edges.at(sorted_point)[0].opposite_point), + point::Segment(sorted_point, + point_to_edges.at(sorted_point)[1].opposite_point)); + break; + case PointType::MERGE: + // merge two sweeping lines to one + // save point as point to start new line for SPLIT pointcase + break; + case PointType::INTERSECTION: + // this is merge and split point at same time + // this is not described in original algorithm + // but we need it to handle self intersecting polygons + // Remember about more than 4 edges case + break; + } + } + return std::make_pair(result, polygon); +} + +std::pair, std::vector> +_triangulate_polygon(const std::vector &polygon) { + if (polygon.size() < 3) + return std::make_pair(std::vector(), polygon); + if (polygon.size() == 3) + return std::make_pair(std::vector({Triangle(0, 1, 2)}), polygon); + if (polygon.size() == 4) { + if (partsegcore::intersection::_orientation(polygon[0], polygon[1], + polygon[2]) != + partsegcore::intersection::_orientation(polygon[0], polygon[3], + polygon[2])) + return std::make_pair( + std::vector({Triangle(0, 1, 2), Triangle(0, 3, 2)}), + polygon); + } + + if (_is_convex(polygon)) + return std::make_pair(_triangle_convex_polygon(polygon), polygon); + + // Implement sweeping line algorithm for triangulation + // described on this lecture: + // https://www.youtube.com/playlist?list=PLtTatrCwXHzEqzJMaTUFgqoCNllgwk4DH + // + return sweeping_line_triangulation(find_intersection_points(polygon)); +} +} // namespace triangulation +} // namespace partsegcore + +#endif // PARTSEGCORE_TRIANGULATE_H From d6bbef89d1b455932a6ec0d1c3e82f02ff6d7914 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Fri, 11 Oct 2024 12:11:56 +0200 Subject: [PATCH 26/76] clarify code --- .../triangulate.pyx | 12 +++---- .../triangulation/intersection.hpp | 24 +++++++++---- .../triangulation/point.hpp | 34 ++++++++++--------- .../triangulation/triangulate.hpp | 13 +++---- 4 files changed, 44 insertions(+), 39 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index f219e09..833512d 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -22,7 +22,8 @@ cdef extern from "triangulation/point.hpp" namespace "partsegcore::point": float y Point(float x, float y) Point() - operator==(const Point& other) const + bool operator==(const Point& other) const + bool operator!=(const Point& other) const cdef cppclass Segment: Point left @@ -30,8 +31,6 @@ cdef extern from "triangulation/point.hpp" namespace "partsegcore::point": Segment(Point left, Point right) Segment() - bool point_eq(const Point& a, const Point& b) - bool cmp_point(const Point& a, const Point& b) cdef extern from "triangulation/intersection.hpp" namespace "partsegcore::intersection": cdef cppclass Event: @@ -45,7 +44,6 @@ cdef extern from "triangulation/intersection.hpp" namespace "partsegcore::inters cdef cppclass PairHash: size_t operator()(pair[int, int] p) const - bool cmp_event(const Event& p, const Event& q) bool _on_segment(const Point& p, const Point& q, const Point& r) int _orientation(const Point& p, const Point& q, const Point& r) bool _do_intersect(const Segment& s1, const Segment& s2) @@ -191,7 +189,7 @@ def triangle_convex_polygon(polygon: Sequence[Sequence[float]]) -> list[tuple[i for point in polygon[1:]: p1 = polygon_vector[polygon_vector.size() - 1] p2 = Point(point[0], point[1]) - if not point_eq(p1, p2): + if p1 != p2: # prevent from adding polygon edge of width 0 polygon_vector.push_back(p2) @@ -236,7 +234,7 @@ cdef vector[Triangle] _triangulate_polygon(vector[Point] polygon): intersections_points_vector = intersections_points.at(i) intersections_points_vector.push_back(edges[i].left) intersections_points_vector.push_back(edges[i].right) - sort(intersections_points_vector.begin(), intersections_points_vector.end(), cmp_point) + sort(intersections_points_vector.begin(), intersections_points_vector.end()) for j in range(intersections_points_vector.size() - 1): edges_with_intersections.push_back(Segment(intersections_points_vector[j], intersections_points_vector[j+1])) @@ -255,7 +253,7 @@ def triangulate_polygon(polygon: Sequence[Sequence[float]]) -> list[tuple[int, i for point in polygon[1:]: p1 = polygon_vector[polygon_vector.size() - 1] p2 = Point(point[0], point[1]) - if not point_eq(p1, p2): + if p1 != p2: # prevent from adding polygon edge of width 0 polygon_vector.push_back(p2) diff --git a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp index e974cf1..4299ed4 100644 --- a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp @@ -70,9 +70,7 @@ bool _do_intersect(const point::Segment &s1, const point::Segment &s2) { const point::Point &q1 = s1.right; const point::Point &p2 = s2.left; const point::Point &q2 = s2.right; - if (point::point_eq(p1, p2) || point::point_eq(p1, q2) || - point::point_eq(q1, p2) || point::point_eq(q1, q2)) - return false; + if (p1 == p2 || p1 == q2 || q1 == p2 || q1 == q2) return false; int o1 = _orientation(p1, q1, p2); int o2 = _orientation(p1, q1, q2); @@ -102,9 +100,9 @@ std::set::iterator succ(std::set &s, return ++it; } -std::unordered_set, intersection::PairHash> -_find_intersections(const std::vector &segments) { - std::unordered_set, intersection::PairHash> intersections; +std::unordered_set, PairHash> _find_intersections( + const std::vector &segments) { + std::unordered_set, PairHash> intersections; std::vector events; std::set active; events.reserve(2 * segments.size()); @@ -112,7 +110,7 @@ _find_intersections(const std::vector &segments) { events.emplace_back(segments[i].left, i, true); events.emplace_back(segments[i].right, i, false); } - std::sort(events.begin(), events.end(), cmp_event); + std::sort(events.begin(), events.end()); for (auto &event : events) { if (event.is_left) { @@ -154,6 +152,18 @@ _find_intersections(const std::vector &segments) { return intersections; } +/** + * @brief Finds the intersection point of two line segments, if it exists. + * + * This function calculates the intersection point of two given line segments. + * Each segment is defined by two endpoints. If the segments do not intersect, + * the function returns the point (0, 0). + * + * @param s1 The first line segment. + * @param s2 The second line segment. + * @return The intersection point of the two segments, or (0, 0) if they do not + * intersect. + */ point::Point _find_intersection(const point::Segment &s1, const point::Segment &s2) { float a1, b1, c1, a2, b2, c2, det, x, y; diff --git a/src/PartSegCore_compiled_backend/triangulation/point.hpp b/src/PartSegCore_compiled_backend/triangulation/point.hpp index b7e7eb7..3093ce0 100644 --- a/src/PartSegCore_compiled_backend/triangulation/point.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/point.hpp @@ -19,6 +19,7 @@ struct Point { float x; float y; bool operator==(const Point &p) const { return x == p.x && y == p.y; } + bool operator!=(const Point &p) const { return !(*this == p); } Point(float x, float y) : x(x), y(y) {} Point() = default; @@ -28,23 +29,15 @@ struct Point { } return this->x < p.x; } -}; -struct PointHash { - std::size_t operator()(const Point &p) const { - return std::hash()(p.x) * 31 + std::hash()(p.y); - } + struct PointHash { + std::size_t operator()(const Point &p) const { + return std::hash()(p.x) * 31 + std::hash()(p.y); + } + }; }; -bool point_eq(const Point &p, const Point &q) { - return p.x == q.x && p.y == q.y; -} - -bool cmp_pair_point(const std::pair &p, - const std::pair &q) { - return p.first < q.first; -} - +/*Struct to represent edge of polygon with points ordered*/ struct Segment { Point left{}; Point right{}; @@ -59,9 +52,18 @@ struct Segment { } Segment() = default; }; - -bool cmp_point(const Point &p, const Point &q) { return p < q; } } // namespace point } // namespace partsegcore +// overload of hash function for +// unordered map and set +namespace std { +template <> +struct hash { + std::size_t operator()(const partsegcore::point::Point &point) const { + return partsegcore::point::Point::PointHash()(point); + } +}; +} // namespace std + #endif // PARTSEGCORE_POINT_H diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index 8a34a99..d3eeba6 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -49,11 +49,7 @@ struct PointEdges { } }; -typedef std::unordered_map, - point::PointHash> - PointToEdges; - -bool cmp_point_edges(const PointEdges &p, const PointEdges &q) { return p < q; } +typedef std::unordered_map> PointToEdges; bool _is_convex(const std::vector &polygon) { int orientation = 0; @@ -124,7 +120,7 @@ std::vector find_intersection_points( edges[intersections_point.first].right); intersections_point.second.push_back(edges[intersections_point.first].left); std::sort(intersections_point.second.begin(), - intersections_point.second.end(), point::cmp_point); + intersections_point.second.end()); } std::vector new_polygon; @@ -177,8 +173,7 @@ PointToEdges get_points_edges(std::vector &edges) { point_to_edges[edges[i].right].emplace_back(i, edges[i].left); } for (auto &point_to_edge : point_to_edges) { - std::sort(point_to_edge.second.begin(), point_to_edge.second.end(), - cmp_point_edges); + std::sort(point_to_edge.second.begin(), point_to_edge.second.end()); } return point_to_edges; } @@ -197,7 +192,7 @@ sweeping_line_triangulation(const std::vector &polygon) { std::vector sorted_points = polygon; // copy to avoid modification of original vector - std::sort(sorted_points.begin(), sorted_points.end(), point::cmp_point); + std::sort(sorted_points.begin(), sorted_points.end()); std::vector ordered_polygon_li; ordered_polygon_li.emplace_back(); Line line; From 0921fafbd408e9939a11db89ae893e78d9d42bcf Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Mon, 14 Oct 2024 22:47:03 +0200 Subject: [PATCH 27/76] Fix finding intersections --- pyproject.toml | 1 + .../triangulate.pyx | 2 +- .../triangulation/intersection.hpp | 83 ++++++++++------ .../triangulation/point.hpp | 44 ++++++++- .../triangulation/triangulate.hpp | 95 ++++++++++++++++--- src/tests/test_triangulate.py | 73 +++++++++++++- 6 files changed, 248 insertions(+), 50 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b6634f5..eac7ce9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,7 @@ sdist.include = ["src/PartSegCore_compiled_backend/version.py"] cmake.version = ">=3.21" sdist.exclude = [".github", "tox.ini", "build_utils", "notebooks", ".readthedocs.yaml"] wheel.exclude = ["**.pyx"] +cmake.build-type = "Debug" [project] diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index 833512d..6f68c28 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -147,7 +147,7 @@ def find_intersections(segments: Sequence[Sequence[Sequence[float]]]) -> list[tu return [(p.first, p.second) for p in intersections] -def find_intersection(s1: Sequence[Sequence[float]], s2: Sequence[Sequence[float]]) -> tuple[float, float]: +def find_intersection_point(s1: Sequence[Sequence[float]], s2: Sequence[Sequence[float]]) -> tuple[float, float]: """ Find intersection between two segments Parameters diff --git a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp index 4299ed4..dd23c59 100644 --- a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp @@ -6,6 +6,7 @@ #define PARTSEGCORE_INTERSECTION_H #include +#include #include #include #include @@ -17,28 +18,24 @@ namespace partsegcore { namespace intersection { struct Event { - float x; - float y; + point::Point p; int index; bool is_left; Event(float x, float y, int index, bool is_left) - : x(x), y(y), index(index), is_left(is_left) {} + : p(x, y), index(index), is_left(is_left) {} Event(const point::Point &p, int index, bool is_left) - : x(p.x), y(p.y), index(index), is_left(is_left) {} + : p(p), index(index), is_left(is_left) {} Event() = default; bool operator<(const Event &e) const { - if (x == e.x) { - if (y == e.y) { - if (is_left == e.is_left) { - return index < e.index; - } - return is_left > e.is_left; + if (p == e.p) { + if (is_left == e.is_left) { + return index < e.index; } - return y < e.y; + return is_left > e.is_left; } - return x < e.x; + return p < e.p; } }; @@ -48,8 +45,6 @@ struct PairHash { } }; -bool cmp_event(const Event &p, const Event &q) { return p < q; } - bool _on_segment(const point::Point &p, const point::Point &q, const point::Point &r) { if (q.x <= std::max(p.x, r.x) && q.x >= std::min(p.x, r.x) && @@ -70,7 +65,6 @@ bool _do_intersect(const point::Segment &s1, const point::Segment &s2) { const point::Point &q1 = s1.right; const point::Point &p2 = s2.left; const point::Point &q2 = s2.right; - if (p1 == p2 || p1 == q2 || q1 == p2 || q1 == q2) return false; int o1 = _orientation(p1, q1, p2); int o2 = _orientation(p1, q1, q2); @@ -87,6 +81,12 @@ bool _do_intersect(const point::Segment &s1, const point::Segment &s2) { return false; } +inline bool _share_endpoint(const point::Segment &s1, + const point::Segment &s2) { + return s1.left == s2.left || s1.left == s2.right || s1.right == s2.left || + s1.right == s2.right; +} + std::set::iterator pred(std::set &s, std::set::iterator it) { if (it == s.begin()) { @@ -112,38 +112,59 @@ std::unordered_set, PairHash> _find_intersections( } std::sort(events.begin(), events.end()); + // Print events + // std::cout << "Events: " << std::endl; + // for (auto &event : events) { + // std::cout << "Event: x=" << event.p.x << " y=" << event.p.y << " + // index= " << event.index + // << " left=" << (event.is_left ? "true": "false") << + // std::endl; + // } + // std::cout << "End of events" << std::endl << std::flush; + for (auto &event : events) { if (event.is_left) { auto next = active.lower_bound(event); auto prev = pred(active, next); - if (next != active.end() && - _do_intersect(segments[event.index], segments[next->index])) { - if (event.index < next->index) { - intersections.emplace(event.index, next->index); - } else { - intersections.emplace(next->index, event.index); + while (next != active.end() && + _do_intersect(segments[event.index], segments[next->index])) { + if (!_share_endpoint(segments[event.index], segments[next->index])) { + if (event.index < next->index) { + intersections.emplace(event.index, next->index); + } else { + intersections.emplace(next->index, event.index); + } } + next = succ(active, next); } - if (prev != active.end() && - _do_intersect(segments[event.index], segments[prev->index])) { - if (event.index < prev->index) { - intersections.emplace(event.index, prev->index); - } else { - intersections.emplace(prev->index, event.index); + while (prev != active.end() && + _do_intersect(segments[event.index], segments[prev->index])) { + if (!_share_endpoint(segments[event.index], segments[prev->index])) { + if (event.index < prev->index) { + intersections.emplace(event.index, prev->index); + } else { + intersections.emplace(prev->index, event.index); + } } + prev = pred(active, prev); } active.insert(event); } else { auto it = active.find(Event(segments[event.index].left, event.index, true)); + if (it == active.end()) { + throw std::runtime_error("Segment not found in active set"); + } auto next = succ(active, it); auto prev = pred(active, it); if (next != active.end() && prev != active.end() && _do_intersect(segments[next->index], segments[prev->index])) { - if (next->index < prev->index) { - intersections.emplace(next->index, prev->index); - } else { - intersections.emplace(prev->index, next->index); + if (!_share_endpoint(segments[next->index], segments[prev->index])) { + if (next->index < prev->index) { + intersections.emplace(next->index, prev->index); + } else { + intersections.emplace(prev->index, next->index); + } } } active.erase(it); diff --git a/src/PartSegCore_compiled_backend/triangulation/point.hpp b/src/PartSegCore_compiled_backend/triangulation/point.hpp index 3093ce0..5dd8c5f 100644 --- a/src/PartSegCore_compiled_backend/triangulation/point.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/point.hpp @@ -6,6 +6,7 @@ #define PARTSEGCORE_POINT_H #include +#include #include #include #include @@ -25,11 +26,17 @@ struct Point { bool operator<(const Point &p) const { if (this->x == p.x) { - return this->y < p.y; + return this->y > p.y; } return this->x < p.x; } + // Overload the << operator for Point + friend std::ostream &operator<<(std::ostream &os, const Point &point) { + os << "(x=" << point.x << ", y=" << point.y << ")"; + return os; + } + struct PointHash { std::size_t operator()(const Point &p) const { return std::hash()(p.x) * 31 + std::hash()(p.y); @@ -51,6 +58,31 @@ struct Segment { } } Segment() = default; + + bool operator<(const Segment &s) const { + if (this->left == s.left) { + return this->right < s.right; + } + return this->left < s.left; + } + + bool operator==(const Segment &s) const { + return this->left == s.left && this->right == s.right; + } + + // Overload the << operator for Segment + friend std::ostream &operator<<(std::ostream &os, const Segment &segment) { + os << "[" << segment.left << " -- " << segment.right << "]"; + return os; + } + + struct SegmentHash { + std::size_t operator()(const Segment &segment) const { + std::size_t h1 = Point::PointHash()(segment.left); + std::size_t h2 = Point::PointHash()(segment.right); + return h1 ^ (h2 << 1); + } + }; }; } // namespace point } // namespace partsegcore @@ -60,10 +92,18 @@ struct Segment { namespace std { template <> struct hash { - std::size_t operator()(const partsegcore::point::Point &point) const { + size_t operator()(const partsegcore::point::Point &point) const { return partsegcore::point::Point::PointHash()(point); } }; + +template <> +struct hash { + size_t operator()(const partsegcore::point::Segment &segment) const { + return partsegcore::point::Segment::SegmentHash()(segment); + } +}; + } // namespace std #endif // PARTSEGCORE_POINT_H diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index d3eeba6..589414f 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -2,6 +2,7 @@ #define PARTSEGCORE_TRIANGULATE_H #include +#include #include #include #include @@ -15,11 +16,22 @@ namespace triangulation { enum PointType { NORMAL, SPLIT, MERGE, INTERSECTION }; -struct Line { - point::Segment left, right; - Line() = default; - Line(const point::Segment &left, const point::Segment &right) - : left(left), right(right) {}; +struct Interval { + point::Point last_seen; + Interval() = default; + explicit Interval(const point::Point &p) : last_seen(p) {}; +}; + +/* Comparator for segments + * To determine if segment is left or right of other segment + * + * It assumes that segments are not intersecting + */ + +struct SegmentLeftRightComparator { + bool operator()(const point::Segment &s1, const point::Segment &s2) const { + return false; + } }; struct OrderedPolygon { @@ -50,6 +62,8 @@ struct PointEdges { }; typedef std::unordered_map> PointToEdges; +typedef std::map + SegmentToLine; bool _is_convex(const std::vector &polygon) { int orientation = 0; @@ -178,6 +192,58 @@ PointToEdges get_points_edges(std::vector &edges) { return point_to_edges; } +void _process_normal_point( + const point::Point &p, const std::vector &edges, + const PointToEdges &point_to_edges, + std::map &segment_to_line) { + const point::Segment &edge_prev = + edges[point_to_edges.at(p).at(0).edge_index]; + const point::Segment &edge_next = + edges[point_to_edges.at(p).at(1).edge_index]; + segment_to_line[edge_next] = segment_to_line.at(edge_prev); + segment_to_line.at(edge_prev)->last_seen = p; + segment_to_line.erase(edge_prev); +} + +void _process_split_point( + const point::Point &p, const std::vector &edges, + const PointToEdges &point_to_edges, + std::map &segment_to_line) { + const point::Segment &edge_left = + edges[point_to_edges.at(p).at(0).edge_index]; + const point::Segment &edge_right = + edges[point_to_edges.at(p).at(1).edge_index]; +} + +/* process merge point + * When merge point is found, we need to merge two intervals into one + * + */ +void _process_merge_point( + const point::Point &p, const std::vector &edges, + const PointToEdges &point_to_edges, + std::map &segment_to_line) { + const point::Segment &edge_left = + edges[point_to_edges.at(p).at(0).edge_index]; + const point::Segment &edge_right = + edges[point_to_edges.at(p).at(1).edge_index]; +} + +///* this is processing of point that have more than 2 edges adjusted */ +// void _process_intersection_point( +// const point::Point &p, std::unordered_map +// &segment_to_line, std::unordered_set +// &sweeping_line_intersect) { +// for (auto &edge : point_to_edges[p]) { +// sweeping_line_intersect.insert(edges[edge.edge_index]); +// } +// std::vector intervals; +// for (auto &edge : point_to_edges[p]) { +// intervals.push_back(Interval(p, edges[edge.edge_index], +// edges[edge.edge_index])); +// } +// } + /* This is implementation of sweeping line triangulation of polygon Its assumes that there is no edge intersections, but may be a point with @@ -187,6 +253,7 @@ PointToEdges get_points_edges(std::vector &edges) { std::pair, std::vector> sweeping_line_triangulation(const std::vector &polygon) { std::vector result; + point::Segment *edge_prev, *edge_next; auto edges = calc_edges(polygon); PointToEdges point_to_edges = get_points_edges(edges); @@ -194,26 +261,30 @@ sweeping_line_triangulation(const std::vector &polygon) { // copy to avoid modification of original vector std::sort(sorted_points.begin(), sorted_points.end()); std::vector ordered_polygon_li; + std::map segment_to_line; + std::vector intervals; ordered_polygon_li.emplace_back(); - Line line; + Interval line; for (auto &sorted_point : sorted_points) { auto point_type = get_point_type(sorted_point, point_to_edges); switch (point_type) { case PointType::NORMAL: // change edge adjusted to current sweeping line + _process_normal_point(sorted_point, edges, point_to_edges, + segment_to_line); + break; case PointType::SPLIT: // split sweeping line on two lines // add edge sor cutting polygon on two parts - line = Line( - point::Segment(sorted_point, - point_to_edges.at(sorted_point)[0].opposite_point), - point::Segment(sorted_point, - point_to_edges.at(sorted_point)[1].opposite_point)); + _process_split_point(sorted_point, edges, point_to_edges, + segment_to_line); break; case PointType::MERGE: // merge two sweeping lines to one - // save point as point to start new line for SPLIT pointcase + // save point as point to start new line for SPLIT point case + _process_merge_point(sorted_point, edges, point_to_edges, + segment_to_line); break; case PointType::INTERSECTION: // this is merge and split point at same time diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index b80e624..c64a542 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -1,9 +1,11 @@ +import pytest + from PartSegCore_compiled_backend.triangulate import ( on_segment, orientation, do_intersect, find_intersections, - find_intersection, + find_intersection_point, is_convex, triangulate_polygon, triangle_convex_polygon, @@ -29,7 +31,7 @@ def test_do_intersect(): def test_find_intersections(): - """ + r""" First test case: (1, 0) --- (1, 1) | | @@ -52,8 +54,71 @@ def test_find_intersections(): ] -def test_find_intersection(): - assert find_intersection(((0, 0), (2, 2)), ((0, 2), (2, 0))) == (1, 1) +@pytest.mark.parametrize( + 'segments, expected', + [ + # No intersections, simple square + ([[(0, 0), (0, 1)], [(0, 1), (1, 1)], [(1, 1), (1, 0)], [(1, 0), (0, 0)]], []), + # One intersection, crossing diagonals + ([[(0, 0), (2, 2)], [(2, 0), (0, 2)]], [(0, 1)]), + # Multiple intersections, complex shape + ( + [[(0, 0), (2, 2)], [(2, 0), (0, 2)], [(1, 0), (1, 2)], [(0, 1), (2, 1)]], + {(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)}, + ), + # No intersections, non-intersecting lines + ([[(0, 0), (1, 1)], [(2, 2), (3, 3)]], []), + # One intersection, T-shaped intersection + ([[(0, 0), (2, 0)], [(1, -1), (1, 1)]], [(0, 1)]), + # Multiple intersections, grid shape + ( + [ + [(0, 0), (2, 0)], + [(0, 1), (2, 1)], + [(0, 2), (2, 2)], + [(0, 0), (0, 2)], + [(1, 0), (1, 2)], + [(2, 0), (2, 2)], + ], + {(0, 4), (1, 3), (1, 4), (1, 5), (2, 4)}, + ), + ], + ids=[ + 'No intersections, simple square', + 'One intersection, crossing diagonals', + 'Multiple intersections, complex shape', + 'No intersections, non-intersecting lines', + 'One intersection, T-shaped intersection', + 'Multiple intersections, grid shape', + ], +) +def test_find_intersections_param(segments, expected): + assert set(find_intersections(segments)) == set(expected) + + +# def test_find_intersection_point(): +# assert find_intersection_point(((0, 0), (2, 2)), ((0, 2), (2, 0))) == (1, 1) + + +@pytest.mark.parametrize( + 'segment1, segment2, expected', + [ + (((0, 0), (2, 2)), ((0, 2), (2, 0)), (1, 1)), # Intersecting diagonals + (((0, 0), (1, 1)), ((1, 0), (0, 1)), (0.5, 0.5)), # Intersecting diagonals + (((0, 0), (2, 2)), ((2, 2), (0, 2)), (2, 2)), # Touching at one point + (((0, 0), (2, 2)), ((1, 0), (1, 2)), (1, 1)), # Vertical line intersection + (((0, 1), (2, 1)), ((2, 0), (2, 2)), (2, 1)), # Touching in the middle + ], + ids=[ + 'Intersecting diagonals (1, 1)', + 'Intersecting diagonals (0.5, 0.5)', + 'Touching at one point (2, 2)', + 'Vertical line intersection (1, 1)', + 'Touching in the middle (2, 1)', + ], +) +def test_find_intersection_point(segment1, segment2, expected): + assert find_intersection_point(segment1, segment2) == expected def test_is_convex(): From e934fc00dbf7fe171ca2a2968b771c9628c4e72d Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 15 Oct 2024 00:07:09 +0200 Subject: [PATCH 28/76] udpate docstrings --- .../triangulation/intersection.hpp | 9 +++++--- .../triangulation/triangulate.hpp | 22 +++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp index dd23c59..c2e6956 100644 --- a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp @@ -126,8 +126,11 @@ std::unordered_set, PairHash> _find_intersections( if (event.is_left) { auto next = active.lower_bound(event); auto prev = pred(active, next); + // we use while, because more than two segments can intersect at the same + // point while (next != active.end() && _do_intersect(segments[event.index], segments[next->index])) { + // to not lost some intersection, but exclude edges, that share endpoint if (!_share_endpoint(segments[event.index], segments[next->index])) { if (event.index < next->index) { intersections.emplace(event.index, next->index); @@ -137,8 +140,11 @@ std::unordered_set, PairHash> _find_intersections( } next = succ(active, next); } + // we use while, because more than two segments can intersect at the same + // point while (prev != active.end() && _do_intersect(segments[event.index], segments[prev->index])) { + // to not lost some intersection, but exclude edges, that share endpoint if (!_share_endpoint(segments[event.index], segments[prev->index])) { if (event.index < prev->index) { intersections.emplace(event.index, prev->index); @@ -152,9 +158,6 @@ std::unordered_set, PairHash> _find_intersections( } else { auto it = active.find(Event(segments[event.index].left, event.index, true)); - if (it == active.end()) { - throw std::runtime_error("Segment not found in active set"); - } auto next = succ(active, it); auto prev = pred(active, it); if (next != active.end() && prev != active.end() && diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index 589414f..4a34db8 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -65,6 +65,18 @@ typedef std::unordered_map> PointToEdges; typedef std::map SegmentToLine; +/** + * Checks if a given polygon is convex. + * + * This function takes a polygon represented as a vector of points and + * determines if it is convex. A convex polygon is one where all the interior + * angles are less than 180 degrees, meaning that the edges never turn back on + * themselves. + * + * @param polygon A vector of points representing the vertices of the polygon in + * order. + * @return True if the polygon is convex, false otherwise. + */ bool _is_convex(const std::vector &polygon) { int orientation = 0; int triangle_orientation; @@ -88,6 +100,16 @@ bool _is_convex(const std::vector &polygon) { return true; } +/** + * Divides a convex polygon into triangles using the fan triangulation method. + * Starting from the first point in the polygon, each triangle is formed by + * connecting the first point with two consecutive points from the polygon list. + * + * @param polygon A vector of Point objects representing the vertices of the + * polygon. + * @return A vector of Triangle objects where each triangle is defined by + * indices corresponding to the vertices in the input polygon. + */ std::vector _triangle_convex_polygon( const std::vector &polygon) { std::vector result; From 8ad3ab72c2be6dd0edb99b3f26c0667a10e51737 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 15 Oct 2024 22:12:40 +0200 Subject: [PATCH 29/76] add comparator for left to righe edges order, start fixing finding intersections --- .../triangulate.pyx | 25 ++-- .../triangulation/intersection.hpp | 135 ++++++++++++++---- .../triangulation/point.hpp | 35 ++--- .../triangulation/triangulate.hpp | 52 +++++-- src/tests/test_triangulate.py | 93 ++++++++++-- 5 files changed, 271 insertions(+), 69 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index 6f68c28..90b1370 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -20,16 +20,16 @@ cdef extern from "triangulation/point.hpp" namespace "partsegcore::point": cdef cppclass Point: float x float y - Point(float x, float y) Point() + Point(float x, float y) bool operator==(const Point& other) const bool operator!=(const Point& other) const cdef cppclass Segment: - Point left - Point right - Segment(Point left, Point right) + Point bottom + Point top Segment() + Segment(Point bottom, Point top) cdef extern from "triangulation/intersection.hpp" namespace "partsegcore::intersection": @@ -38,8 +38,8 @@ cdef extern from "triangulation/intersection.hpp" namespace "partsegcore::inters float y int index bool is_left - Event(float x, float y, int index, bool is_left) Event() + Event(float x, float y, int index, bool is_left) cdef cppclass PairHash: size_t operator()(pair[int, int] p) const @@ -57,11 +57,12 @@ cdef extern from "triangulation/triangulate.hpp" namespace "partsegcore::triangu int x int y int z - Triangle(int x, int y, int z) Triangle() + Triangle(int x, int y, int z) bool _is_convex(const vector[Point]& polygon) vector[Triangle] _triangle_convex_polygon(const vector[Point]& polygon) + bool left_to_right(const Segment& s1, const Segment& s2) @@ -232,8 +233,8 @@ cdef vector[Triangle] _triangulate_polygon(vector[Point] polygon): edges_with_intersections.push_back(edges[i]) else: intersections_points_vector = intersections_points.at(i) - intersections_points_vector.push_back(edges[i].left) - intersections_points_vector.push_back(edges[i].right) + intersections_points_vector.push_back(edges[i].bottom) + intersections_points_vector.push_back(edges[i].top) sort(intersections_points_vector.begin(), intersections_points_vector.end()) for j in range(intersections_points_vector.size() - 1): edges_with_intersections.push_back(Segment(intersections_points_vector[j], intersections_points_vector[j+1])) @@ -259,3 +260,11 @@ def triangulate_polygon(polygon: Sequence[Sequence[float]]) -> list[tuple[int, i result = _triangulate_polygon(polygon_vector) return [(triangle.x, triangle.y, triangle.z) for triangle in result] + + +def segment_left_to_right_comparator(s1: Sequence[Sequence[float]], s2: Sequence[Sequence[float]]) -> bool: + """ Compare segments by bottom point""" + return left_to_right( + Segment(Point(s1[0][0], s1[0][1]), Point(s1[1][0], s1[1][1])), + Segment(Point(s2[0][0], s2[0][1]), Point(s2[1][0], s2[1][1])) + ) diff --git a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp index c2e6956..33a76c6 100644 --- a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp @@ -1,5 +1,14 @@ // // Created by Grzegorz Bokota on 11.10.24. +// Based on +// https://www.geeksforgeeks.org/given-a-set-of-line-segments-find-if-any-two-segments-intersect/ +// and +// Mark Berg, Otfried Cheong, Marc Kreveld, Mark Overmars. +// Computational Geometry: Algorithms and Applications. +// 3rd ed., Springer Berlin, Heidelberg, 2008. +// ISBN: 978-3-540-77974-2. +// https://link.springer.com/book/10.1007/978-3-540-77974-2 +// https://doi.org/10.1007/978-3-540-77974-2 // #ifndef PARTSEGCORE_INTERSECTION_H @@ -7,6 +16,7 @@ #include #include +#include #include #include #include @@ -39,12 +49,33 @@ struct Event { } }; +struct EventData { + std::vector begins; + std::vector ends; +}; + struct PairHash { std::size_t operator()(const std::pair &p) const { return std::hash()(p.first) * 31 + std::hash()(p.second); } }; +typedef std::map IntersectionEvents; + +/** + * Checks whether point q lies on the line segment defined by points p and r. + * + * This function determines if a given point q lies on the line segment + * that connects points p and r. It checks if the x and y coordinates + * of q are within the bounding box formed by p and r. + * It works, because we use it when orientation is 0, so we know that + * if q is in bounding box, it lies on the segment. + * + * @param p The first endpoint of the line segment. + * @param q The point to check. + * @param r The second endpoint of the line segment. + * @return True if point q lies on the segment pr, false otherwise. + */ bool _on_segment(const point::Point &p, const point::Point &q, const point::Point &r) { if (q.x <= std::max(p.x, r.x) && q.x >= std::min(p.x, r.x) && @@ -53,6 +84,17 @@ bool _on_segment(const point::Point &p, const point::Point &q, return false; } +/** + * Determines the orientation of the triplet (p, q, r). + * + * @param p The first point. + * @param q The second point. + * @param r The third point. + * + * @return 0 if p, q and r are collinear. + * 1 if the triplet (p, q, r) is in a clockwise orientation. + * 2 if the triplet (p, q, r) is in a counterclockwise orientation. + */ int _orientation(const point::Point &p, const point::Point &q, const point::Point &r) { float val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y); @@ -60,11 +102,22 @@ int _orientation(const point::Point &p, const point::Point &q, return (val > 0) ? 1 : 2; } +/** + * Determines if two line segments intersect. + * + * This function checks whether two given line segments, s1 and s2, intersect. + * It makes use of orientation and on-segment tests to evaluate intersection + * based on the positions of the endpoints of the segments. + * + * @param s1 The first line segment, represented by two endpoints. + * @param s2 The second line segment, represented by two endpoints. + * @return True if the segments intersect, false otherwise. + */ bool _do_intersect(const point::Segment &s1, const point::Segment &s2) { - const point::Point &p1 = s1.left; - const point::Point &q1 = s1.right; - const point::Point &p2 = s2.left; - const point::Point &q2 = s2.right; + const point::Point &p1 = s1.bottom; + const point::Point &q1 = s1.top; + const point::Point &p2 = s2.bottom; + const point::Point &q2 = s2.top; int o1 = _orientation(p1, q1, p2); int o2 = _orientation(p1, q1, q2); @@ -81,10 +134,22 @@ bool _do_intersect(const point::Segment &s1, const point::Segment &s2) { return false; } +/** + * @brief Checks if two segments share an endpoint. + * + * This function determines whether two segments, each defined by + * two endpoints, share any endpoint. Specifically, it checks if + * the bottom or top endpoint of the first segment is equal to the + * bottom or top endpoint of the second segment. + * + * @param s1 The first segment. + * @param s2 The second segment. + * @return true if the segments share at least one endpoint, false otherwise. + */ inline bool _share_endpoint(const point::Segment &s1, const point::Segment &s2) { - return s1.left == s2.left || s1.left == s2.right || s1.right == s2.left || - s1.right == s2.right; + return s1.bottom == s2.bottom || s1.bottom == s2.top || s1.top == s2.bottom || + s1.top == s2.top; } std::set::iterator pred(std::set &s, @@ -103,26 +168,48 @@ std::set::iterator succ(std::set &s, std::unordered_set, PairHash> _find_intersections( const std::vector &segments) { std::unordered_set, PairHash> intersections; + IntersectionEvents intersection_events; std::vector events; std::set active; events.reserve(2 * segments.size()); for (std::size_t i = 0; i < segments.size(); i++) { - events.emplace_back(segments[i].left, i, true); - events.emplace_back(segments[i].right, i, false); + intersection_events[segments[i].bottom].begins.push_back(i); + intersection_events[segments[i].top].ends.push_back(i); + events.emplace_back(segments[i].bottom, i, true); + events.emplace_back(segments[i].top, i, false); } + + for (auto &event : intersection_events) { + for (auto &begin : event.second.begins) { + for (auto &end : event.second.ends) { + if (begin != end) { + std::cout << "Bing" << std::endl; + } + } + } + } + std::sort(events.begin(), events.end()); - // Print events - // std::cout << "Events: " << std::endl; - // for (auto &event : events) { - // std::cout << "Event: x=" << event.p.x << " y=" << event.p.y << " - // index= " << event.index - // << " left=" << (event.is_left ? "true": "false") << - // std::endl; - // } - // std::cout << "End of events" << std::endl << std::flush; + // Print events + std::cout << "Events: " << std::endl; + for (auto &event : events) { + std::cout << "Event: x=" << event.p.x << " y=" << event.p.y + << " index=" << event.index + << " bottom=" << (event.is_left ? "true" : "false") << std::endl; + } + std::cout << "End of events" << std::endl << std::flush; for (auto &event : events) { + if (event.index == 4) { + std::cout << "Event 4 " << event.is_left << std::endl; + for (auto el : active) { + std::cout << "Active: x=" << el.p.x << " y=" << el.p.y + << " index=" << el.index + << " bottom=" << (el.is_left ? "true" : "false") << std::endl; + } + } + if (event.is_left) { auto next = active.lower_bound(event); auto prev = pred(active, next); @@ -157,7 +244,7 @@ std::unordered_set, PairHash> _find_intersections( active.insert(event); } else { auto it = - active.find(Event(segments[event.index].left, event.index, true)); + active.find(Event(segments[event.index].bottom, event.index, true)); auto next = succ(active, it); auto prev = pred(active, it); if (next != active.end() && prev != active.end() && @@ -191,12 +278,12 @@ std::unordered_set, PairHash> _find_intersections( point::Point _find_intersection(const point::Segment &s1, const point::Segment &s2) { float a1, b1, c1, a2, b2, c2, det, x, y; - a1 = s1.right.y - s1.left.y; - b1 = s1.left.x - s1.right.x; - c1 = a1 * s1.left.x + b1 * s1.left.y; - a2 = s2.right.y - s2.left.y; - b2 = s2.left.x - s2.right.x; - c2 = a2 * s2.left.x + b2 * s2.left.y; + a1 = s1.top.y - s1.bottom.y; + b1 = s1.bottom.x - s1.top.x; + c1 = a1 * s1.bottom.x + b1 * s1.bottom.y; + a2 = s2.top.y - s2.bottom.y; + b2 = s2.bottom.x - s2.top.x; + c2 = a2 * s2.bottom.x + b2 * s2.bottom.y; det = a1 * b2 - a2 * b1; if (det == 0) return {0, 0}; x = (b2 * c1 - b1 * c2) / det; diff --git a/src/PartSegCore_compiled_backend/triangulation/point.hpp b/src/PartSegCore_compiled_backend/triangulation/point.hpp index 5dd8c5f..34e6bc8 100644 --- a/src/PartSegCore_compiled_backend/triangulation/point.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/point.hpp @@ -25,10 +25,10 @@ struct Point { Point() = default; bool operator<(const Point &p) const { - if (this->x == p.x) { - return this->y > p.y; + if (this->y == p.y) { + return this->x < p.x; } - return this->x < p.x; + return this->y < p.y; } // Overload the << operator for Point @@ -46,40 +46,43 @@ struct Point { /*Struct to represent edge of polygon with points ordered*/ struct Segment { - Point left{}; - Point right{}; + Point bottom{}; + Point top{}; Segment(Point p1, Point p2) { if (p1 < p2) { - left = p1; - right = p2; + bottom = p1; + top = p2; } else { - left = p2; - right = p1; + bottom = p2; + top = p1; } } + + bool is_horizontal() const { return bottom.y == top.y; } + Segment() = default; bool operator<(const Segment &s) const { - if (this->left == s.left) { - return this->right < s.right; + if (this->bottom == s.bottom) { + return this->top < s.top; } - return this->left < s.left; + return this->bottom < s.bottom; } bool operator==(const Segment &s) const { - return this->left == s.left && this->right == s.right; + return this->bottom == s.bottom && this->top == s.top; } // Overload the << operator for Segment friend std::ostream &operator<<(std::ostream &os, const Segment &segment) { - os << "[" << segment.left << " -- " << segment.right << "]"; + os << "[bottom=" << segment.bottom << ", top=" << segment.top << "]"; return os; } struct SegmentHash { std::size_t operator()(const Segment &segment) const { - std::size_t h1 = Point::PointHash()(segment.left); - std::size_t h2 = Point::PointHash()(segment.right); + std::size_t h1 = Point::PointHash()(segment.bottom); + std::size_t h2 = Point::PointHash()(segment.top); return h1 ^ (h2 << 1); } }; diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index 4a34db8..32d0350 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -22,15 +22,51 @@ struct Interval { explicit Interval(const point::Point &p) : last_seen(p) {}; }; -/* Comparator for segments - * To determine if segment is left or right of other segment +/** + * Comparator function to determine the relative positioning of two given + * segments. + * + * Compares two segments to identify if the first segment (`s1`) is to the left + * and below the second segment (`s2`) when moving left to right. The function + * assumes that the segments do not intersect. + * + * TODO Investigate if it could be implemented in a more efficient way. + * + * @param s1 The first segment to compare. + * @param s2 The second segment to compare. * - * It assumes that segments are not intersecting + * @return `true` if `s1` is to the left and below `s2`, `false` otherwise. */ +bool left_to_right(const point::Segment &s1, const point::Segment &s2) { + if (s1.is_horizontal() && s2.is_horizontal()) { + return s1.bottom.x < s2.bottom.x; + } + if (s1.is_horizontal()) { + int i1 = intersection::_orientation(s2.bottom, s2.top, s1.top); + int i2 = intersection::_orientation(s2.bottom, s2.top, s1.bottom); + return (i1 == 2 || i2 == 2); + } + if (s2.is_horizontal()) { + int i1 = intersection::_orientation(s1.bottom, s1.top, s2.top); + int i2 = intersection::_orientation(s1.bottom, s1.top, s2.bottom); + return (i1 == 1 || i2 == 1); + } + + if ((s1.top.y < s2.top.y)) { + if (s1.top == s2.top) { + return intersection::_orientation(s2.top, s2.bottom, s1.bottom) == 1; + } + return intersection::_orientation(s2.top, s2.bottom, s1.top) == 1; + } + if (s1.top == s2.top) { + return intersection::_orientation(s1.top, s1.bottom, s2.bottom) == 2; + } + return intersection::_orientation(s1.top, s1.bottom, s2.top) == 2; +} struct SegmentLeftRightComparator { bool operator()(const point::Segment &s1, const point::Segment &s2) const { - return false; + return left_to_right(s1, s2); } }; @@ -152,9 +188,9 @@ std::vector find_intersection_points( std::size_t points_count = polygon.size(); for (auto &intersections_point : intersections_points) { points_count += intersections_point.second.size() - 1; + intersections_point.second.push_back(edges[intersections_point.first].top); intersections_point.second.push_back( - edges[intersections_point.first].right); - intersections_point.second.push_back(edges[intersections_point.first].left); + edges[intersections_point.first].bottom); std::sort(intersections_point.second.begin(), intersections_point.second.end()); } @@ -205,8 +241,8 @@ PointType get_point_type(point::Point p, PointToEdges &point_to_edges) { PointToEdges get_points_edges(std::vector &edges) { PointToEdges point_to_edges; for (std::size_t i = 0; i < edges.size(); i++) { - point_to_edges[edges[i].left].emplace_back(i, edges[i].right); - point_to_edges[edges[i].right].emplace_back(i, edges[i].left); + point_to_edges[edges[i].bottom].emplace_back(i, edges[i].top); + point_to_edges[edges[i].top].emplace_back(i, edges[i].bottom); } for (auto &point_to_edge : point_to_edges) { std::sort(point_to_edge.second.begin(), point_to_edge.second.end()); diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index c64a542..d1f2598 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -9,6 +9,7 @@ is_convex, triangulate_polygon, triangle_convex_polygon, + segment_left_to_right_comparator, ) @@ -70,18 +71,18 @@ def test_find_intersections(): ([[(0, 0), (1, 1)], [(2, 2), (3, 3)]], []), # One intersection, T-shaped intersection ([[(0, 0), (2, 0)], [(1, -1), (1, 1)]], [(0, 1)]), - # Multiple intersections, grid shape - ( - [ - [(0, 0), (2, 0)], - [(0, 1), (2, 1)], - [(0, 2), (2, 2)], - [(0, 0), (0, 2)], - [(1, 0), (1, 2)], - [(2, 0), (2, 2)], - ], - {(0, 4), (1, 3), (1, 4), (1, 5), (2, 4)}, - ), + # # Multiple intersections, grid shape + # ( + # [ + # [(0, 0), (2, 0)], + # [(0, 1), (2, 1)], + # [(0, 2), (2, 2)], + # [(0, 0), (0, 2)], + # [(1, 0), (1, 2)], + # [(2, 0), (2, 2)], + # ], + # {(0, 4), (1, 3), (1, 4), (1, 5), (2, 4)}, + # ), ], ids=[ 'No intersections, simple square', @@ -89,7 +90,7 @@ def test_find_intersections(): 'Multiple intersections, complex shape', 'No intersections, non-intersecting lines', 'One intersection, T-shaped intersection', - 'Multiple intersections, grid shape', + # 'Multiple intersections, grid shape', ], ) def test_find_intersections_param(segments, expected): @@ -138,3 +139,69 @@ def test_triangulate_polygon(): assert triangulate_polygon([(0, 0), (0, 1), (1, 1), (1, 0)]) == [(0, 1, 2), (0, 2, 3)] assert triangulate_polygon([(0, 0), (0, 10), (1, 10), (1, 0)]) == [(0, 1, 2), (0, 2, 3)] assert triangulate_polygon([(0, 0), (0, 0.5), (0, 1), (1, 1), (1, 0)]) == [(0, 2, 3), (0, 3, 4)] + + +@pytest.mark.parametrize( + ('segment1', 'segment2'), + [ + # Touching segments by top point + (((0, 0), (1, 1)), ((1, 1), (2, 0))), + # Touching segments by bottom point + (((0, 1), (1, 0)), ((1, 0), (2, 1))), + # Touching segments by bottom point with different length, longer left + (((-1, 2), (1, 0)), ((1, 0), (2, 1))), + # Touching segments by bottom point with different length, longer right + (((0, 1), (1, 0)), ((1, 0), (3, 2))), + # Parallel vertical segments + (((0, 0), (0, 1)), ((1, 0), (1, 1))), + # Parallel horizontal segments different length + (((0, 0), (0, 2)), ((1, -1), (1, 1))), + # horizontal segments + (((0, 0), (1, 0)), ((2, 0), (3, 0))), + # Two segments with top on same line + (((0, 0), (1, 1)), ((3, 1), (2, -1))), + # One horizontal segment on right and top + (((0, 0), (1, 1)), ((2, 1), (3, 1))), + # One horizontal segment on right and middle + (((0, 0), (1, 1)), ((2, 0.5), (3, 0.5))), + # One horizontal segment on right and bottom + (((0, 0), (1, 1)), ((2, 0), (3, 0))), + # One horizontal segment on left and top + (((0, 1), (1, 1)), ((2, 0), (3, 2))), + # One horizontal segment on left and middle + (((0, 0.5), (1, 0.5)), ((2, 0), (3, 2))), + # One horizontal segment on left and bottom + (((0, 0), (1, 0)), ((2, 0), (3, 2))), + # left horizontal, right oblique, bottom merge + (((0, 0), (1, 0)), ((1, 0), (2, 2))), + # left oblique, right horizontal, bottom merge + (((0, 1), (1, 0)), ((1, 0), (2, 0))), + # left horizontal, right oblique, top merge + (((0, 1), (1, 1)), ((1, 1), (2, 0))), + # left oblique, right horizontal, top merge + (((0, 0), (1, 1)), ((1, 1), (2, 1))), + ], + ids=[ + 'Touching segments by top point', + 'Touching segments by bottom point', + 'Touching segments by bottom point with different length, longer left', + 'Touching segments by bottom point with different length, longer right', + 'Parallel vertical segments', + 'Parallel horizontal segments different length', + 'horizontal segments', + 'Two segments with top on same line', + 'One horizontal segment on right and top', + 'One horizontal segment on right and middle', + 'One horizontal segment on right and bottom', + 'One horizontal segment on left and top', + 'One horizontal segment on left and middle', + 'One horizontal segment on left and bottom', + 'left horizontal, right oblique, bottom merge', + 'left oblique, right horizontal, bottom merge', + 'left horizontal, right oblique, top merge', + 'left oblique, right horizontal, top merge', + ], +) +def test_segment_left_to_right_comparator(segment1, segment2): + assert segment_left_to_right_comparator(segment1, segment2) + assert not segment_left_to_right_comparator(segment2, segment1) From adf728f66160153f11229bc5ce2a93799aad884d Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 15 Oct 2024 22:13:18 +0200 Subject: [PATCH 30/76] comment out debug build --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index eac7ce9..e3a9b88 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ sdist.include = ["src/PartSegCore_compiled_backend/version.py"] cmake.version = ">=3.21" sdist.exclude = [".github", "tox.ini", "build_utils", "notebooks", ".readthedocs.yaml"] wheel.exclude = ["**.pyx"] -cmake.build-type = "Debug" +#cmake.build-type = "Debug" [project] From 723150ac1604fd395379f5121b3f258c315a1e50 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 17 Oct 2024 18:06:01 +0200 Subject: [PATCH 31/76] move between computers --- .../triangulate.pyx | 4 +- .../triangulation/intersection.hpp | 88 +++++++------------ src/tests/test_triangulate.py | 26 +++--- 3 files changed, 49 insertions(+), 69 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index 90b1370..82ced79 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -37,9 +37,9 @@ cdef extern from "triangulation/intersection.hpp" namespace "partsegcore::inters float x float y int index - bool is_left + bool is_top Event() - Event(float x, float y, int index, bool is_left) + Event(float x, float y, int index, bool is_top) cdef cppclass PairHash: size_t operator()(pair[int, int] p) const diff --git a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp index 33a76c6..fe7a435 100644 --- a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -30,20 +31,20 @@ namespace intersection { struct Event { point::Point p; int index; - bool is_left; + bool is_top; Event(float x, float y, int index, bool is_left) - : p(x, y), index(index), is_left(is_left) {} + : p(x, y), index(index), is_top(is_left) {} Event(const point::Point &p, int index, bool is_left) - : p(p), index(index), is_left(is_left) {} + : p(p), index(index), is_top(is_left) {} Event() = default; bool operator<(const Event &e) const { if (p == e.p) { - if (is_left == e.is_left) { + if (is_top == e.is_top) { return index < e.index; } - return is_left > e.is_left; + return is_top > e.is_top; } return p < e.p; } @@ -152,16 +153,16 @@ inline bool _share_endpoint(const point::Segment &s1, s1.top == s2.top; } -std::set::iterator pred(std::set &s, - std::set::iterator it) { +template +typename T::iterator pred(T &s, typename T::iterator it) { if (it == s.begin()) { return s.end(); } return --it; } -std::set::iterator succ(std::set &s, - std::set::iterator it) { +template +typename T::iterator succ(T &s, typename T::iterator it) { return ++it; } @@ -170,23 +171,13 @@ std::unordered_set, PairHash> _find_intersections( std::unordered_set, PairHash> intersections; IntersectionEvents intersection_events; std::vector events; - std::set active; + std::map> active; events.reserve(2 * segments.size()); for (std::size_t i = 0; i < segments.size(); i++) { intersection_events[segments[i].bottom].begins.push_back(i); intersection_events[segments[i].top].ends.push_back(i); - events.emplace_back(segments[i].bottom, i, true); - events.emplace_back(segments[i].top, i, false); - } - - for (auto &event : intersection_events) { - for (auto &begin : event.second.begins) { - for (auto &end : event.second.ends) { - if (begin != end) { - std::cout << "Bing" << std::endl; - } - } - } + events.emplace_back(segments[i].top, i, true); + events.emplace_back(segments[i].bottom, i, false); } std::sort(events.begin(), events.end()); @@ -196,55 +187,44 @@ std::unordered_set, PairHash> _find_intersections( for (auto &event : events) { std::cout << "Event: x=" << event.p.x << " y=" << event.p.y << " index=" << event.index - << " bottom=" << (event.is_left ? "true" : "false") << std::endl; + << " bottom=" << (event.is_top ? "true" : "false") << std::endl; } std::cout << "End of events" << std::endl << std::flush; for (auto &event : events) { - if (event.index == 4) { - std::cout << "Event 4 " << event.is_left << std::endl; - for (auto el : active) { - std::cout << "Active: x=" << el.p.x << " y=" << el.p.y - << " index=" << el.index - << " bottom=" << (el.is_left ? "true" : "false") << std::endl; - } - } - - if (event.is_left) { - auto next = active.lower_bound(event); + if (event.is_top) { + auto next = active.lower_bound(event.p); auto prev = pred(active, next); // we use while, because more than two segments can intersect at the same // point - while (next != active.end() && - _do_intersect(segments[event.index], segments[next->index])) { + if (next != active.end()) { // to not lost some intersection, but exclude edges, that share endpoint - if (!_share_endpoint(segments[event.index], segments[next->index])) { - if (event.index < next->index) { - intersections.emplace(event.index, next->index); - } else { - intersections.emplace(next->index, event.index); + for (auto index : next->second) { + if (!_share_endpoint(segments[event.index], segments[index])) { + if (event.index < index) { + intersections.emplace(event.index, index); + } else { + intersections.emplace(index, event.index); + } } } - next = succ(active, next); } // we use while, because more than two segments can intersect at the same // point - while (prev != active.end() && - _do_intersect(segments[event.index], segments[prev->index])) { - // to not lost some intersection, but exclude edges, that share endpoint - if (!_share_endpoint(segments[event.index], segments[prev->index])) { - if (event.index < prev->index) { - intersections.emplace(event.index, prev->index); - } else { - intersections.emplace(prev->index, event.index); + if (prev != active.end()) { + for (auto index : prev->second) { + if (!_share_endpoint(segments[event.index], segments[index])) { + if (event.index < index) { + intersections.emplace(event.index, index); + } else { + intersections.emplace(index, event.index); + } } } - prev = pred(active, prev); } - active.insert(event); + active[event.p].insert(event.index); } else { - auto it = - active.find(Event(segments[event.index].bottom, event.index, true)); + auto it = active.find(segments[event.index].bottom); auto next = succ(active, it); auto prev = pred(active, it); if (next != active.end() && prev != active.end() && diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index d1f2598..2b044eb 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -71,18 +71,18 @@ def test_find_intersections(): ([[(0, 0), (1, 1)], [(2, 2), (3, 3)]], []), # One intersection, T-shaped intersection ([[(0, 0), (2, 0)], [(1, -1), (1, 1)]], [(0, 1)]), - # # Multiple intersections, grid shape - # ( - # [ - # [(0, 0), (2, 0)], - # [(0, 1), (2, 1)], - # [(0, 2), (2, 2)], - # [(0, 0), (0, 2)], - # [(1, 0), (1, 2)], - # [(2, 0), (2, 2)], - # ], - # {(0, 4), (1, 3), (1, 4), (1, 5), (2, 4)}, - # ), + # Multiple intersections, grid shape + ( + [ + [(0, 0), (2, 0)], + [(0, 1), (2, 1)], + [(0, 2), (2, 2)], + [(0, 0), (0, 2)], + [(1, 0), (1, 2)], + [(2, 0), (2, 2)], + ], + {(0, 4), (1, 3), (1, 4), (1, 5), (2, 4)}, + ), ], ids=[ 'No intersections, simple square', @@ -90,7 +90,7 @@ def test_find_intersections(): 'Multiple intersections, complex shape', 'No intersections, non-intersecting lines', 'One intersection, T-shaped intersection', - # 'Multiple intersections, grid shape', + 'Multiple intersections, grid shape', ], ) def test_find_intersections_param(segments, expected): From 289d5b6c1caec6440f93d82f01bb94b991232443 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Wed, 23 Oct 2024 09:56:15 +0200 Subject: [PATCH 32/76] fixing intersection part 2 --- CMakeLists.txt | 1 + .../triangulation/intersection.hpp | 161 ++++++++++++------ src/tests/test_triangulate.py | 2 +- 3 files changed, 111 insertions(+), 53 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bad85bc..039077b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.21) project(${SKBUILD_PROJECT_NAME} LANGUAGES CXX) set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG") # Define compiler directive add_definitions(-DNPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION) diff --git a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp index fe7a435..bdd3683 100644 --- a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp @@ -51,8 +51,8 @@ struct Event { }; struct EventData { - std::vector begins; - std::vector ends; + std::vector tops; + std::vector bottoms; }; struct PairHash { @@ -63,6 +63,43 @@ struct PairHash { typedef std::map IntersectionEvents; +template +void print_set(std::ostream &o, const T &s, const std::string &end = "\n") { + o << "{"; + for (const auto &el : s) { + if (el != *s.begin()) { + o << ", "; + } + o << el; + } + o << "}" << end; +} + +template +void print_vector(std::ostream &o, const T &s, const std::string &end = "\n") { + o << "["; + for (const auto &el : s) { + if (el != *s.begin()) { + o << ", "; + } + o << el; + } + o << "]" << end; +} + +template +void print_map(std::ostream &o, const T &s, const std::string &end = "\n") { + o << "{"; + for (const auto &el : s) { + if (el != *s.begin()) { + o << ", "; + } + o << el.first << ": "; + print_set(o, el.second, ""); + } + o << "}" << end; +} + /** * Checks whether point q lies on the line segment defined by points p and r. * @@ -171,75 +208,95 @@ std::unordered_set, PairHash> _find_intersections( std::unordered_set, PairHash> intersections; IntersectionEvents intersection_events; std::vector events; - std::map> active; + std::map> active; events.reserve(2 * segments.size()); for (std::size_t i = 0; i < segments.size(); i++) { - intersection_events[segments[i].bottom].begins.push_back(i); - intersection_events[segments[i].top].ends.push_back(i); - events.emplace_back(segments[i].top, i, true); - events.emplace_back(segments[i].bottom, i, false); - } - - std::sort(events.begin(), events.end()); - - // Print events - std::cout << "Events: " << std::endl; - for (auto &event : events) { - std::cout << "Event: x=" << event.p.x << " y=" << event.p.y - << " index=" << event.index - << " bottom=" << (event.is_top ? "true" : "false") << std::endl; + intersection_events[segments[i].top].tops.push_back(i); + intersection_events[segments[i].bottom].bottoms.push_back(i); } - std::cout << "End of events" << std::endl << std::flush; - - for (auto &event : events) { - if (event.is_top) { - auto next = active.lower_bound(event.p); + std::cout << "Intersection events: " << std::endl; + int i = 0; + while (!intersection_events.empty()) { + auto event_it = --intersection_events.end(); + std::cout << "Event " << i << ": " << event_it->first << " "; + print_vector(std::cout, event_it->second.tops, ", "); + print_vector(std::cout, event_it->second.bottoms, "\n"); + i++; + auto &event_data = event_it->second; + if (!event_data.tops.empty()) { + std::cout << "Active: "; + print_map(std::cout, active, "\n"); + auto next = active.lower_bound(event_it->first); auto prev = pred(active, next); - // we use while, because more than two segments can intersect at the same - // point if (next != active.end()) { - // to not lost some intersection, but exclude edges, that share endpoint - for (auto index : next->second) { - if (!_share_endpoint(segments[event.index], segments[index])) { - if (event.index < index) { - intersections.emplace(event.index, index); - } else { - intersections.emplace(index, event.index); + std::cout << "Next: " << next->first << " "; + print_set(std::cout, next->second, "\n"); + for (auto event_index : event_data.tops) { + for (auto index : next->second) { + if (_do_intersect(segments[event_index], segments[index]) && + !_share_endpoint(segments[event_index], segments[index])) { + if (event_index < index) { + intersections.emplace(event_index, index); + } else { + intersections.emplace(index, event_index); + } } } } } - // we use while, because more than two segments can intersect at the same - // point if (prev != active.end()) { - for (auto index : prev->second) { - if (!_share_endpoint(segments[event.index], segments[index])) { - if (event.index < index) { - intersections.emplace(event.index, index); - } else { - intersections.emplace(index, event.index); + std::cout << "Prev: " << prev->first << " "; + print_set(std::cout, prev->second, "\n"); + for (auto event_index : event_data.tops) { + for (auto index : prev->second) { + if (_do_intersect(segments[event_index], segments[index]) && + !_share_endpoint(segments[event_index], segments[index])) { + if (event_index < index) { + intersections.emplace(event_index, index); + } else { + intersections.emplace(index, event_index); + } } } } } - active[event.p].insert(event.index); - } else { - auto it = active.find(segments[event.index].bottom); - auto next = succ(active, it); - auto prev = pred(active, it); - if (next != active.end() && prev != active.end() && - _do_intersect(segments[next->index], segments[prev->index])) { - if (!_share_endpoint(segments[next->index], segments[prev->index])) { - if (next->index < prev->index) { - intersections.emplace(next->index, prev->index); - } else { - intersections.emplace(prev->index, next->index); + active[event_it->first].insert(event_data.tops.begin(), + event_data.tops.end()); + } + if (!event_data.bottoms.empty()) { + for (auto event_index : event_data.bottoms) { + std::cout << "Event index: " << event_index << " " << active.size() + << std::endl; + auto it = active.find(segments[event_index].top); + + auto next = succ(active, it); + auto prev = pred(active, it); + std::cout << "Is next " << (next != active.end()) << std::endl; + std::cout << "Is prev " << (prev != active.end()) << std::endl; + if (next != active.end() && prev != active.end()) { + std::cout << "Next: " << next->first << std::endl; + for (auto n_index : next->second) { + for (auto p_index : prev->second) { + if (_do_intersect(segments[n_index], segments[p_index]) && + !_share_endpoint(segments[n_index], segments[p_index])) { + if (n_index < p_index) { + intersections.emplace(n_index, p_index); + } else { + intersections.emplace(p_index, n_index); + } + } + } } } + it->second.erase(event_index); + if (it->second.empty()) { + active.erase(it); + } } - active.erase(it); } + intersection_events.erase(event_it); } + return intersections; } diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index 2b044eb..41fb140 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -49,7 +49,7 @@ def test_find_intersections(): / \ (0, 0) --- (0, 1) """ - assert find_intersections([[(0, 0), (0, 1)], [(0, 1), (1, 1)], [(1, 1), (1, 0)], [(1, 0), (0, 0)]]) == [] + # assert find_intersections([[(0, 0), (0, 1)], [(0, 1), (1, 1)], [(1, 1), (1, 0)], [(1, 0), (0, 0)]]) == [] assert find_intersections([[(0, 0), (0, 1)], [(0, 1), (1, 0)], [(1, 0), (1, 1)], [(1, 1), (0, 0), (0, 1)]]) == [ (1, 3) ] From 6a04ade5e2e181067bfcf20f5022dd7564601d70 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Wed, 23 Oct 2024 23:51:19 +0200 Subject: [PATCH 33/76] use simple workaround for intersection edge case --- .../triangulate.pyx | 17 +- .../triangulation/intersection.hpp | 150 +++++++++++------- .../triangulation/point.hpp | 2 + src/tests/test_triangulate.py | 2 +- 4 files changed, 105 insertions(+), 66 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index 82ced79..c7d68a7 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -41,13 +41,16 @@ cdef extern from "triangulation/intersection.hpp" namespace "partsegcore::inters Event() Event(float x, float y, int index, bool is_top) - cdef cppclass PairHash: - size_t operator()(pair[int, int] p) const + cdef cppclass OrderedPair: + int first + int second + OrderedPair() + OrderedPair(int first, int second) bool _on_segment(const Point& p, const Point& q, const Point& r) int _orientation(const Point& p, const Point& q, const Point& r) bool _do_intersect(const Segment& s1, const Segment& s2) - unordered_set[pair[int, int], PairHash] _find_intersections(const vector[Segment]& segments) + unordered_set[OrderedPair] _find_intersections(const vector[Segment]& segments) Point _find_intersection(const Segment& s1, const Segment& s2) @@ -137,8 +140,8 @@ def do_intersect(s1: Sequence[Sequence[float]], s2: Sequence[Sequence[float]]) - def find_intersections(segments: Sequence[Sequence[Sequence[float]]]) -> list[tuple[int, int]]: """ Find intersections between segments""" cdef vector[Segment] segments_vector - cdef unordered_set[pair[int, int], PairHash] intersections - cdef pair[int, int] p + cdef unordered_set[OrderedPair] intersections + cdef OrderedPair p segments_vector.reserve(len(segments)) for segment in segments: @@ -200,13 +203,13 @@ def triangle_convex_polygon(polygon: Sequence[Sequence[float]]) -> list[tuple[i cdef vector[Triangle] _triangulate_polygon(vector[Point] polygon): cdef vector[Segment] edges, edges_with_intersections cdef Py_ssize_t i, j, edges_count - cdef unordered_set[pair[int, int], PairHash] intersections + cdef unordered_set[OrderedPair] intersections cdef unordered_map[int, vector[Point]] intersections_points cdef pair[int, vector[Point]] p_it cdef vector[Triangle] triangles cdef vector[Point] intersections_points_vector cdef Point p_int - cdef pair[int, int] p + cdef OrderedPair p if _is_convex(polygon): return _triangle_convex_polygon(polygon) diff --git a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp index bdd3683..d6cffa7 100644 --- a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp @@ -55,11 +55,39 @@ struct EventData { std::vector bottoms; }; -struct PairHash { - std::size_t operator()(const std::pair &p) const { - return std::hash()(p.first) * 31 + std::hash()(p.second); +struct OrderedPair { + std::size_t first; + std::size_t second; + OrderedPair() = default; + OrderedPair(std::size_t first, std::size_t second) { + if (first < second) { + this->first = first; + this->second = second; + } else { + this->first = second; + this->second = first; + } + } + bool operator==(const OrderedPair &pair) const { + return first == pair.first && second == pair.second; + } +}; +} // namespace intersection +} // namespace partsegcore +namespace std { + +template <> +struct hash { + std::size_t operator()( + const partsegcore::intersection::OrderedPair &pair) const { + return std::hash()(pair.first) ^ + std::hash()(pair.second); } }; +} // namespace std + +namespace partsegcore { +namespace intersection { typedef std::map IntersectionEvents; @@ -203,9 +231,9 @@ typename T::iterator succ(T &s, typename T::iterator it) { return ++it; } -std::unordered_set, PairHash> _find_intersections( +std::unordered_set _find_intersections( const std::vector &segments) { - std::unordered_set, PairHash> intersections; + std::unordered_set intersections; IntersectionEvents intersection_events; std::vector events; std::map> active; @@ -214,52 +242,58 @@ std::unordered_set, PairHash> _find_intersections( intersection_events[segments[i].top].tops.push_back(i); intersection_events[segments[i].bottom].bottoms.push_back(i); } - std::cout << "Intersection events: " << std::endl; + // std::cout << "Segments "; + // print_vector(std::cout, segments, "\n"); int i = 0; while (!intersection_events.empty()) { auto event_it = --intersection_events.end(); - std::cout << "Event " << i << ": " << event_it->first << " "; - print_vector(std::cout, event_it->second.tops, ", "); - print_vector(std::cout, event_it->second.bottoms, "\n"); - i++; + // std::cout << "Event " << i << ": " << event_it->first << " tops: "; + // print_vector(std::cout, event_it->second.tops, ", bottoms: "); + // print_vector(std::cout, event_it->second.bottoms, "\n"); + // i++; auto &event_data = event_it->second; + // std::cout << "Active: "; + print_map(std::cout, active, "\n"); if (!event_data.tops.empty()) { - std::cout << "Active: "; - print_map(std::cout, active, "\n"); - auto next = active.lower_bound(event_it->first); - auto prev = pred(active, next); - if (next != active.end()) { - std::cout << "Next: " << next->first << " "; - print_set(std::cout, next->second, "\n"); + for (const auto &active_el : active) { for (auto event_index : event_data.tops) { - for (auto index : next->second) { + for (auto index : active_el.second) { if (_do_intersect(segments[event_index], segments[index]) && !_share_endpoint(segments[event_index], segments[index])) { - if (event_index < index) { - intersections.emplace(event_index, index); - } else { - intersections.emplace(index, event_index); - } - } - } - } - } - if (prev != active.end()) { - std::cout << "Prev: " << prev->first << " "; - print_set(std::cout, prev->second, "\n"); - for (auto event_index : event_data.tops) { - for (auto index : prev->second) { - if (_do_intersect(segments[event_index], segments[index]) && - !_share_endpoint(segments[event_index], segments[index])) { - if (event_index < index) { - intersections.emplace(event_index, index); - } else { - intersections.emplace(index, event_index); - } + intersections.emplace(event_index, index); } } } } + // auto next = active.lower_bound(event_it->first); + // auto prev = pred(active, next); + // if (next != active.end()) { + // std::cout << "Next: " << next->first << " "; + // print_set(std::cout, next->second, "\n"); + // for (auto event_index : event_data.tops) { + // for (auto index : next->second) { + // if (_do_intersect(segments[event_index], segments[index]) && + // !_share_endpoint(segments[event_index], + // segments[index])) { + // intersections.emplace(event_index, index); + // } + // } + // } + // } + // if (prev != active.end()) { + // std::cout << "Prev: " << prev->first << " "; + // print_set(std::cout, prev->second, "\n"); + // for (auto event_index : event_data.tops) { + // for (auto index : prev->second) { + // if (_do_intersect(segments[event_index], segments[index]) && + // !_share_endpoint(segments[event_index], + // segments[index])) { intersections.emplace(event_index, + // index); + // + // } + // } + // } + // } active[event_it->first].insert(event_data.tops.begin(), event_data.tops.end()); } @@ -269,25 +303,25 @@ std::unordered_set, PairHash> _find_intersections( << std::endl; auto it = active.find(segments[event_index].top); - auto next = succ(active, it); - auto prev = pred(active, it); - std::cout << "Is next " << (next != active.end()) << std::endl; - std::cout << "Is prev " << (prev != active.end()) << std::endl; - if (next != active.end() && prev != active.end()) { - std::cout << "Next: " << next->first << std::endl; - for (auto n_index : next->second) { - for (auto p_index : prev->second) { - if (_do_intersect(segments[n_index], segments[p_index]) && - !_share_endpoint(segments[n_index], segments[p_index])) { - if (n_index < p_index) { - intersections.emplace(n_index, p_index); - } else { - intersections.emplace(p_index, n_index); - } - } - } - } - } + // auto next = succ(active, it); + // auto prev = pred(active, it); + // std::cout << "It " << it->first << " segment " << + // segments[event_index] << std::endl; std::cout << "Is next " << + // (next != active.end()) << std::endl; std::cout << "Is prev " + // << (prev != active.end()) << std::endl; if (next != + // active.end() && prev != active.end()) { + // std::cout << "Next: " << next->first << std::endl; + // for (auto n_index : next->second) { + // for (auto p_index : prev->second) { + // if (_do_intersect(segments[n_index], segments[p_index]) + // && + // !_share_endpoint(segments[n_index], + // segments[p_index])) { intersections.emplace(n_index, + // p_index); + // } + // } + // } + // } it->second.erase(event_index); if (it->second.empty()) { active.erase(it); diff --git a/src/PartSegCore_compiled_backend/triangulation/point.hpp b/src/PartSegCore_compiled_backend/triangulation/point.hpp index 34e6bc8..3b4cc6b 100644 --- a/src/PartSegCore_compiled_backend/triangulation/point.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/point.hpp @@ -73,6 +73,8 @@ struct Segment { return this->bottom == s.bottom && this->top == s.top; } + bool operator!=(const Segment &s) const { return !(*this == s); } + // Overload the << operator for Segment friend std::ostream &operator<<(std::ostream &os, const Segment &segment) { os << "[bottom=" << segment.bottom << ", top=" << segment.top << "]"; diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index 41fb140..2b044eb 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -49,7 +49,7 @@ def test_find_intersections(): / \ (0, 0) --- (0, 1) """ - # assert find_intersections([[(0, 0), (0, 1)], [(0, 1), (1, 1)], [(1, 1), (1, 0)], [(1, 0), (0, 0)]]) == [] + assert find_intersections([[(0, 0), (0, 1)], [(0, 1), (1, 1)], [(1, 1), (1, 0)], [(1, 0), (0, 0)]]) == [] assert find_intersections([[(0, 0), (0, 1)], [(0, 1), (1, 0)], [(1, 0), (1, 1)], [(1, 1), (0, 0), (0, 1)]]) == [ (1, 3) ] From 955874a247b4652c25589e03f8f8a7d9163bfa4c Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 24 Oct 2024 00:12:38 +0200 Subject: [PATCH 34/76] fix addin intersection points to polygon --- .../triangulate.pyx | 14 ++++++ .../triangulation/intersection.hpp | 7 +-- .../triangulation/triangulate.hpp | 9 ++-- src/tests/test_triangulate.py | 45 +++++++++++++++++++ 4 files changed, 69 insertions(+), 6 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index c7d68a7..9c57afb 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -66,6 +66,7 @@ cdef extern from "triangulation/triangulate.hpp" namespace "partsegcore::triangu bool _is_convex(const vector[Point]& polygon) vector[Triangle] _triangle_convex_polygon(const vector[Point]& polygon) bool left_to_right(const Segment& s1, const Segment& s2) + vector[Point] find_intersection_points(const vector[Point]& segments) @@ -271,3 +272,16 @@ def segment_left_to_right_comparator(s1: Sequence[Sequence[float]], s2: Sequence Segment(Point(s1[0][0], s1[0][1]), Point(s1[1][0], s1[1][1])), Segment(Point(s2[0][0], s2[0][1]), Point(s2[1][0], s2[1][1])) ) + + +def find_intersection_points_py(polygon: Sequence[Sequence[float]]) -> Sequence[Sequence[float]]: + """ Find intersection points in polygon""" + cdef vector[Point] polygon_vector + cdef vector[Point] result + + polygon_vector.reserve(len(polygon)) + for point in polygon: + polygon_vector.push_back(Point(point[0], point[1])) + + result = find_intersection_points(polygon_vector) + return [(point.x, point.y) for point in result] diff --git a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp index d6cffa7..8dfccb0 100644 --- a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp @@ -253,7 +253,7 @@ std::unordered_set _find_intersections( // i++; auto &event_data = event_it->second; // std::cout << "Active: "; - print_map(std::cout, active, "\n"); + // print_map(std::cout, active, "\n"); if (!event_data.tops.empty()) { for (const auto &active_el : active) { for (auto event_index : event_data.tops) { @@ -299,8 +299,9 @@ std::unordered_set _find_intersections( } if (!event_data.bottoms.empty()) { for (auto event_index : event_data.bottoms) { - std::cout << "Event index: " << event_index << " " << active.size() - << std::endl; + // std::cout << "Event index: " << event_index << " " << + // active.size() + // << std::endl; auto it = active.find(segments[event_index].top); // auto next = succ(active, it); diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index 32d0350..0ee37c6 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -199,16 +199,19 @@ std::vector find_intersection_points( new_polygon.reserve(points_count); for (std::size_t i = 0; i < polygon.size(); i++) { auto point = polygon[i]; - new_polygon.push_back(point); + if (new_polygon[new_polygon.size() - 1] != point) + new_polygon.push_back(point); if (intersections_points.count(i)) { auto new_points = intersections_points[i]; if (new_points[0] == point) { for (std::size_t j = 1; j < new_points.size() - 1; j++) { - new_polygon.push_back(new_points[j]); + if (new_polygon[new_polygon.size() - 1] != new_points[j]) + new_polygon.push_back(new_points[j]); } } else { for (std::size_t j = new_points.size() - 2; j > 0; j++) { - new_polygon.push_back(new_points[j]); + if (new_polygon[new_polygon.size() - 1] != new_points[j]) + new_polygon.push_back(new_points[j]); } } } diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index 2b044eb..d469b47 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -6,6 +6,7 @@ do_intersect, find_intersections, find_intersection_point, + find_intersection_points_py, is_convex, triangulate_polygon, triangle_convex_polygon, @@ -205,3 +206,47 @@ def test_triangulate_polygon(): def test_segment_left_to_right_comparator(segment1, segment2): assert segment_left_to_right_comparator(segment1, segment2) assert not segment_left_to_right_comparator(segment2, segment1) + + +def test_find_intersection_points_py_cross(): + r""" + (1, 0) --- (1, 1) + \ / + \ / + \ / + X + / \ + / \ + / \ + (0, 0) --- (0, 1) + """ + assert find_intersection_points_py([(0, 0), (1, 1), (1, 0), (0, 1)]) == [ + (0, 0), + (0.5, 0.5), + (1, 1), + (1, 0), + (0.5, 0.5), + (0, 1), + ] + + +def test_find_intersection_points_py_cross_intersect_in_point(): + r""" + (1, 0) --- (1, 1) + \ / + \ / + \ / + X + / \ + / \ + / \ + (0, 0) --- (0, 1) + """ + assert find_intersection_points_py([(0, 0), (0.5, 0.5), (1, 1), (1, 0), (0, 1)]) == [ + (0, 0), + (0.5, 0.5), + (1, 1), + (1, 0), + (0.5, 0.5), + (0, 1), + ] From 95e8bcbcf9bef510b7b6895488b066c2af5014e9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 24 Oct 2024 17:08:06 +0200 Subject: [PATCH 35/76] add monotone polygon triangulation --- CMakeLists.txt | 1 + .../triangulate.pyx | 38 +++ .../triangulation/debug_util.hpp | 66 +++++ .../triangulation/intersection.hpp | 40 +-- .../triangulation/triangulate.hpp | 234 ++++++++++++++++-- src/tests/test_triangulate.py | 43 ++++ 6 files changed, 367 insertions(+), 55 deletions(-) create mode 100644 src/PartSegCore_compiled_backend/triangulation/debug_util.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 039077b..d52bbdd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,6 +109,7 @@ python_add_library( src/PartSegCore_compiled_backend/triangulation/triangulate.hpp src/PartSegCore_compiled_backend/triangulation/intersection.hpp src/PartSegCore_compiled_backend/triangulation/point.hpp + src/PartSegCore_compiled_backend/triangulation/debug_util.hpp WITH_SOABI) target_include_directories(triangulate PRIVATE "${NUMPY_INCLUDE_DIR}") target_include_directories(triangulate PRIVATE src/PartSegCore_compiled_backend/) diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index 9c57afb..0f957e9 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -63,10 +63,26 @@ cdef extern from "triangulation/triangulate.hpp" namespace "partsegcore::triangu Triangle() Triangle(int x, int y, int z) + cdef cppclass MonotonePolygon: + Point top + Point bottom + vector[Point] left + vector[Point] right + MonotonePolygon() + MonotonePolygon(Point top, Point bottom, vector[Point] left, vector[Point] right) + + + cdef cppclass PointTriangle: + Point p1 + Point p2 + Point p3 + + bool _is_convex(const vector[Point]& polygon) vector[Triangle] _triangle_convex_polygon(const vector[Point]& polygon) bool left_to_right(const Segment& s1, const Segment& s2) vector[Point] find_intersection_points(const vector[Point]& segments) + vector[PointTriangle] triangulate_monotone_polygon(const MonotonePolygon& polygon) @@ -285,3 +301,25 @@ def find_intersection_points_py(polygon: Sequence[Sequence[float]]) -> Sequence[ result = find_intersection_points(polygon_vector) return [(point.x, point.y) for point in result] + + +def triangulate_monotone_polygon_py(top: Sequence[float], bottom: Sequence[float], left: Sequence[Sequence[float]], right: Sequence[Sequence[float]]) -> Sequence[Sequence[Sequence[float]]]: + """ Triangulate monotone polygon""" + cdef vector[Point] left_vector, right_vector + cdef vector[PointTriangle] result + cdef MonotonePolygon mono_polygon + + left_vector.reserve(len(left)) + for point in left: + left_vector.push_back(Point(point[0], point[1])) + + right_vector.reserve(len(right)) + for point in right: + right_vector.push_back(Point(point[0], point[1])) + + mono_polygon = MonotonePolygon(Point(top[0], top[1]), Point(bottom[0], bottom[1]), left_vector, right_vector) + result = triangulate_monotone_polygon(mono_polygon) + return [ + [(triangle.p1.x, triangle.p1.y), (triangle.p2.x, triangle.p2.y), (triangle.p3.x, triangle.p3.y)] + for triangle in result + ] diff --git a/src/PartSegCore_compiled_backend/triangulation/debug_util.hpp b/src/PartSegCore_compiled_backend/triangulation/debug_util.hpp new file mode 100644 index 0000000..4461173 --- /dev/null +++ b/src/PartSegCore_compiled_backend/triangulation/debug_util.hpp @@ -0,0 +1,66 @@ +// +// Created by Grzegorz Bokota on 24.10.24. +// + +#ifndef PARTSEGCORE_TRIANGULATION_DEBUG_UTIL_ +#define PARTSEGCORE_TRIANGULATION_DEBUG_UTIL_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "point.hpp" + +namespace partsegcore { + +template +void print_set(std::ostream &o, const T &s, const std::string &end = "\n") { + o << "{"; + for (const auto &el : s) { + if (el != *s.begin()) { + o << ", "; + } + o << el; + } + o << "}" << end; +} +template +void print_vector(std::ostream &o, const T &s, const std::string &end = "\n") { + o << "["; + for (const auto &el : s) { + if (el != *s.begin()) { + o << ", "; + } + o << el; + } + o << "]" << end; +} +template +void print_map(std::ostream &o, const T &s, const std::string &end = "\n") { + o << "{"; + for (const auto &el : s) { + if (el != *s.begin()) { + o << ", "; + } + o << el.first << ": "; + print_set(o, el.second, ""); + } + o << "}" << end; +} +} // namespace partsegcore + +namespace std { +template +std::ostream &operator<<(std::ostream &os, const std::pair &p) { + os << "(" << p.first << ", " << p.second << ")"; + return os; +} +} // namespace std + +#endif // PARTSEGCORE_TRIANGULATION_DEBUG_UTIL_ diff --git a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp index 8dfccb0..4cb2975 100644 --- a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp @@ -91,43 +91,6 @@ namespace intersection { typedef std::map IntersectionEvents; -template -void print_set(std::ostream &o, const T &s, const std::string &end = "\n") { - o << "{"; - for (const auto &el : s) { - if (el != *s.begin()) { - o << ", "; - } - o << el; - } - o << "}" << end; -} - -template -void print_vector(std::ostream &o, const T &s, const std::string &end = "\n") { - o << "["; - for (const auto &el : s) { - if (el != *s.begin()) { - o << ", "; - } - o << el; - } - o << "]" << end; -} - -template -void print_map(std::ostream &o, const T &s, const std::string &end = "\n") { - o << "{"; - for (const auto &el : s) { - if (el != *s.begin()) { - o << ", "; - } - o << el.first << ": "; - print_set(o, el.second, ""); - } - o << "}" << end; -} - /** * Checks whether point q lies on the line segment defined by points p and r. * @@ -255,6 +218,9 @@ std::unordered_set _find_intersections( // std::cout << "Active: "; // print_map(std::cout, active, "\n"); if (!event_data.tops.empty()) { + // Current implementation is not optimal, but it is was + // faster to use this to have initial working version. + // TODO based on commented code fix edge cases. for (const auto &active_el : active) { for (auto event_index : event_data.tops) { for (auto index : active_el.second) { diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index 0ee37c6..3034b4f 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -4,10 +4,12 @@ #include #include #include +#include #include #include #include +#include "debug_util.hpp" #include "intersection.hpp" #include "point.hpp" @@ -70,11 +72,20 @@ struct SegmentLeftRightComparator { } }; -struct OrderedPolygon { - point::Point top; - point::Point bottom; +struct MonotonePolygon { + point::Point top{}; + point::Point bottom{}; std::vector left; std::vector right; + + MonotonePolygon() = default; + MonotonePolygon(point::Point top, point::Point bottom, + std::vector left, + std::vector right) + : top(top), + bottom(bottom), + left(std::move(left)), + right(std::move(right)) {} }; struct Triangle { @@ -85,6 +96,15 @@ struct Triangle { Triangle() = default; }; +struct PointTriangle { + point::Point p1; + point::Point p2; + point::Point p3; + PointTriangle(point::Point p1, point::Point p2, point::Point p3) + : p1(p1), p2(p2), p3(p3) {}; + PointTriangle() = default; +}; + typedef std::size_t EdgeIndex; struct PointEdges { @@ -158,6 +178,160 @@ std::vector _triangle_convex_polygon( return result; } +/** + * Construct triangles using the current point and edges opposite to it. + * + * This function takes a current point and a stack of points, then builds + * triangles by connecting the current point to adjoining points in the stack. + * Each triangle is added to the result vector as a `PointTriangle` object. + * + * Steps performed: + * 1. Iterate over the stack to form triangles with the current point. + * 2. Replace the first element of the stack with the last element. + * 3. Set the second element of the stack to the current point. + * 4. Remove all elements from the stack except the first two. + * + * @param stack A vector of `point::Point` objects representing the stack of + * points. + * @param result A vector to store the resultant `PointTriangle` objects. + * @param current_point The current `point::Point` used to form triangles with + * points in the stack. + */ +void _build_triangles_opposite_edge(std::vector &stack, + std::vector &result, + point::Point current_point) { + for (std::size_t i = 0; i < stack.size() - 1; i++) { + result.emplace_back(current_point, stack[i], stack[i + 1]); + } + stack[0] = stack.back(); + stack[1] = current_point; + // remove all elements except two first + stack.erase(stack.begin() + 2, stack.end()); +} + +/** + * Constructs triangles from the current edge of the y-monotone and updates the + * stack and result vectors. + * + * This function processes a stack of points and an incoming point to form + * triangles that are part of the processed area. The triangles are determined + * based on the expected orientation and are added to the result vector. The + * stack is then updated to reflect the current status of the processed area. + * + * @param point_stack A reference to a std::vector of point::Point representing + * the current state of the stack. + * @param triangles A reference to a std::vector of PointTriangle where the + * generated triangles will be stored. + * @param incoming_point The incoming point::Point to be considered for new + * triangles and updating the stack. + * @param expected_orientation The expected orientation (clockwise or + * counterclockwise) that will guide the triangle formation. + */ +void _build_triangles_current_edge(std::vector &stack, + std::vector &result, + point::Point current_point, + int expected_orientation) { + auto it1 = stack.rbegin(); + auto it2 = stack.rbegin() + 1; + while (it2 != stack.rend() && + intersection::_orientation(*it2, *it1, current_point) == + expected_orientation) { + result.emplace_back(current_point, *it1, *it2); + it1++; + it2++; + } + stack.erase(it1.base(), stack.end()); + stack.push_back(current_point); +} + +/** + * @enum Side + * @brief An enumeration to represent different sides. + * + * This enumeration defines constants for various sides, + * in particular: + * - TOP: Represents the top side. + * - LEFT: Represents the left side. + * - RIGHT: Represents the right side. + */ +enum Side { + TOP = 0, + LEFT = 2, + RIGHT = 1, +}; + +/** + * Triangulates a given monotone polygon. + * + * The function takes a monotone polygon as input and returns a vector + * of triangles that represent the triangulation of the polygon. The + * polygon must be y-monotone, meaning that every line segment + * parallel to the y-axis intersects the polygon at most twice. + * + * The algorithm works by sorting points of the polygon, merging the + * left and right chains, and then using a stack-based approach to + * recursively build triangles. + * + * @param polygon The monotone polygon to be triangulated. + * @return A vector of PointTriangle representing the triangles of the + * triangulated polygon. + */ +std::vector triangulate_monotone_polygon( + const MonotonePolygon &polygon) { + std::vector result; + std::size_t left_index = 0; + std::size_t right_index = 0; + std::vector stack; + std::vector> points; + + points.reserve(polygon.left.size() + polygon.right.size() + 2); + points.emplace_back(polygon.top, Side::TOP); + while (left_index < polygon.left.size() && + right_index < polygon.right.size()) { + if (polygon.left[left_index] < polygon.right[right_index]) { + points.emplace_back(polygon.right[right_index], Side::RIGHT); + right_index++; + } else { + points.emplace_back(polygon.left[left_index], Side::LEFT); + left_index++; + } + } + while (left_index < polygon.left.size()) { + points.emplace_back(polygon.left[left_index], Side::LEFT); + left_index++; + } + while (right_index < polygon.right.size()) { + points.emplace_back(polygon.right[right_index], Side::RIGHT); + right_index++; + } + points.emplace_back(polygon.bottom, Side::TOP); + + stack.push_back(points[0].first); + stack.push_back(points[1].first); + Side side = points[1].second; + + for (std::size_t i = 2; i < points.size(); i++) { + if (side == points[i].second) { + _build_triangles_current_edge(stack, result, points[i].first, side); + } else { + _build_triangles_opposite_edge(stack, result, points[i].first); + } + side = points[i].second; + } + return result; +} + +/** + * Calculates the edges of a polygon represented as a sequence of points. + * + * This function takes a vector of points representing the vertices of a polygon + * and returns a vector of segments representing the edges of the polygon. + * The edges are created by connecting each point to the next, with the final + * point connecting back to the first point to close the polygon. + * + * @param polygon A vector of points representing the vertices of the polygon. + * @return A vector of segments representing the edges of the polygon. + */ std::vector calc_edges( const std::vector &polygon) { std::vector edges; @@ -169,6 +343,17 @@ std::vector calc_edges( return edges; } +/** + * @brief Finds intersection points in a polygon and adds mid-points for all + * intersections. + * + * The function takes a vector of points defining a polygon and finds all edge + * intersections. It then adds mid-points for all such intersections. + * + * @param polygon The polygon defined by a vector of Point objects. + * @return A new vector of Point objects representing the polygon with added + * intersection points. + */ std::vector find_intersection_points( const std::vector &polygon) { /* find all edge intersections and add mid-points for all such intersection @@ -219,14 +404,23 @@ std::vector find_intersection_points( return new_polygon; } -/* - Calculate point type. - If there is more than two edges adjusted to point, it is intersection point. - If there are two adjusted edges, it could be one of split, merge and normal - point. If both adjusted edges have opposite end before given point p, this - is merge point. If both adjusted edges have opposite end after given point p, - split point. Otherwise it is normal point. - */ +/** + * Determines the type of a given point based on its adjacent edges. + * + * If the point has more than two edges connected to it, it is considered an + * intersection point. If it has exactly two edges, further checks categorize + * it as one of split, merge, or normal points. + * + * - If both adjacent edges have their opposite ends before the given point `p`, + * the point is classified as a merge point. + * - If both adjacent edges have their opposite ends after the given point `p`, + * the point is classified as a split point. + * - Otherwise, it is categorized as a normal point. + * + * @param p The point to classify. + * @param point_to_edges A mapping from points to their adjacent edges. + * @return The type of the point as determined by its adjacent edges. + */ PointType get_point_type(point::Point p, PointToEdges &point_to_edges) { if (point_to_edges.at(p).size() != 2) return PointType::INTERSECTION; const auto &edges = point_to_edges.at(p); @@ -237,10 +431,14 @@ PointType get_point_type(point::Point p, PointToEdges &point_to_edges) { return PointType::NORMAL; } -/* - Get map from point to list of edges which contains this point. - Also sort each list by point order. - */ +/** + * Get a mapping from points to the list of edges that contain those points. + * Each list of edges is sorted by the point order within the edges. + * + * @param edges A vector of Segment objects representing the edges of a polygon. + * @return A PointToEdges map where each key is a point and the corresponding + * value is a vector of edges containing that point. + */ PointToEdges get_points_edges(std::vector &edges) { PointToEdges point_to_edges; for (std::size_t i = 0; i < edges.size(); i++) { @@ -306,9 +504,9 @@ void _process_merge_point( // } /* - This is implementation of sweeping line triangulation of polygon + This is an implementation of sweeping line triangulation of polygon Its assumes that there is no edge intersections, but may be a point with - more than 2 edges. described on this lecture: + more than 2 edges. described on this lecture: https://www.youtube.com/playlist?list=PLtTatrCwXHzEqzJMaTUFgqoCNllgwk4DH */ std::pair, std::vector> @@ -321,7 +519,7 @@ sweeping_line_triangulation(const std::vector &polygon) { std::vector sorted_points = polygon; // copy to avoid modification of original vector std::sort(sorted_points.begin(), sorted_points.end()); - std::vector ordered_polygon_li; + std::vector ordered_polygon_li; std::map segment_to_line; std::vector intervals; ordered_polygon_li.emplace_back(); diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index d469b47..3a539f6 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -11,6 +11,7 @@ triangulate_polygon, triangle_convex_polygon, segment_left_to_right_comparator, + triangulate_monotone_polygon_py, ) @@ -250,3 +251,45 @@ def test_find_intersection_points_py_cross_intersect_in_point(): (0.5, 0.5), (0, 1), ] + + +@pytest.mark.parametrize( + ('polygon', 'expected'), + [ + ( + ((1, 2), (1, 0), [(0, 1)], [(2, 1)]), + [[(0.0, 1.0), (1.0, 2.0), (2.0, 1.0)], [(1.0, 0.0), (2.0, 1.0), (0.0, 1.0)]], + ), + ( + ((5, 2), (5, 0), [(0, 1)], [(2, 1)]), + [[(0.0, 1.0), (5.0, 2.0), (2.0, 1.0)], [(5.0, 0.0), (2.0, 1.0), (0.0, 1.0)]], + ), + ( + ((1, 3), (1, 0), [(0, 2), (0, 1)], [(2, 2), (2, 1)]), + [ + [(0.0, 2.0), (1.0, 3.0), (2.0, 2.0)], + [(2.0, 1.0), (2.0, 2.0), (0.0, 2.0)], + [(0.0, 1.0), (0.0, 2.0), (2.0, 1.0)], + [(1.0, 0.0), (2.0, 1.0), (0.0, 1.0)], + ], + ), + ( + ((0, 4), (0, 0), [], [(2, 3), (3, 2), (2, 1)]), + [ + [(3.0, 2.0), (2.0, 3.0), (0.0, 4.0)], + [(2.0, 1.0), (3.0, 2.0), (0.0, 4.0)], + [(0.0, 0.0), (0.0, 4.0), (2.0, 1.0)], + ], + ), + ( + ((0, 4), (0, 0), [], [(2, 3), (1, 2), (2, 1)]), + [ + [(1.0, 2.0), (2.0, 3.0), (0.0, 4.0)], + [(0.0, 0.0), (0.0, 4.0), (1.0, 2.0)], + [(0.0, 0.0), (1.0, 2.0), (2.0, 1.0)], + ], + ), + ], +) +def test_triangulate_monotone_polygon_py(polygon, expected): + assert triangulate_monotone_polygon_py(*polygon) == expected From 0c9f537264cdc969defbadeb86121e0f32c05fd8 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 24 Oct 2024 22:07:56 +0200 Subject: [PATCH 36/76] polygon split initial --- .../triangulation/triangulate.hpp | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index 3034b4f..8bca21c 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -16,7 +16,7 @@ namespace partsegcore { namespace triangulation { -enum PointType { NORMAL, SPLIT, MERGE, INTERSECTION }; +enum PointType { NORMAL, SPLIT, MERGE, INTERSECTION, START, END }; struct Interval { point::Point last_seen; @@ -250,12 +250,12 @@ void _build_triangles_current_edge(std::vector &stack, * * This enumeration defines constants for various sides, * in particular: - * - TOP: Represents the top side. + * - TOP_OR_BOTTOM: Represents the top side. * - LEFT: Represents the left side. * - RIGHT: Represents the right side. */ enum Side { - TOP = 0, + TOP_OR_BOTTOM = 0, LEFT = 2, RIGHT = 1, }; @@ -285,7 +285,7 @@ std::vector triangulate_monotone_polygon( std::vector> points; points.reserve(polygon.left.size() + polygon.right.size() + 2); - points.emplace_back(polygon.top, Side::TOP); + points.emplace_back(polygon.top, Side::TOP_OR_BOTTOM); while (left_index < polygon.left.size() && right_index < polygon.right.size()) { if (polygon.left[left_index] < polygon.right[right_index]) { @@ -304,7 +304,7 @@ std::vector triangulate_monotone_polygon( points.emplace_back(polygon.right[right_index], Side::RIGHT); right_index++; } - points.emplace_back(polygon.bottom, Side::TOP); + points.emplace_back(polygon.bottom, Side::TOP_OR_BOTTOM); stack.push_back(points[0].first); stack.push_back(points[1].first); @@ -421,7 +421,9 @@ std::vector find_intersection_points( * @param point_to_edges A mapping from points to their adjacent edges. * @return The type of the point as determined by its adjacent edges. */ -PointType get_point_type(point::Point p, PointToEdges &point_to_edges) { +PointType get_point_type( + point::Point p, PointToEdges &point_to_edges, + std::map &segment_to_line) { if (point_to_edges.at(p).size() != 2) return PointType::INTERSECTION; const auto &edges = point_to_edges.at(p); if (edges[0].opposite_point < p && edges[1].opposite_point < p) @@ -527,6 +529,14 @@ sweeping_line_triangulation(const std::vector &polygon) { for (auto &sorted_point : sorted_points) { auto point_type = get_point_type(sorted_point, point_to_edges); switch (point_type) { + case PointType::START: + // start new line + break; + + case PointType::END: + // end line + break; + case PointType::NORMAL: // change edge adjusted to current sweeping line _process_normal_point(sorted_point, edges, point_to_edges, @@ -546,7 +556,7 @@ sweeping_line_triangulation(const std::vector &polygon) { segment_to_line); break; case PointType::INTERSECTION: - // this is merge and split point at same time + // this is a merge and split point at the same time // this is not described in original algorithm // but we need it to handle self intersecting polygons // Remember about more than 4 edges case From 636399958d781469269c04752c971fc3b6dc3836 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 25 Oct 2024 07:11:26 +0000 Subject: [PATCH 37/76] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/tests/test_triangulate.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index 3a539f6..d6e29e8 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -1,17 +1,17 @@ import pytest from PartSegCore_compiled_backend.triangulate import ( - on_segment, - orientation, do_intersect, - find_intersections, find_intersection_point, find_intersection_points_py, + find_intersections, is_convex, - triangulate_polygon, - triangle_convex_polygon, + on_segment, + orientation, segment_left_to_right_comparator, + triangle_convex_polygon, triangulate_monotone_polygon_py, + triangulate_polygon, ) From b69627ba9ecdb4d265aabbb83fb3300a778ddaba Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 29 Oct 2024 13:39:01 +0100 Subject: [PATCH 38/76] initial implementation of sweeping line triangulation --- .../triangulation/point.hpp | 44 +- .../triangulation/triangulate.hpp | 482 +++++++++++++----- 2 files changed, 388 insertions(+), 138 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulation/point.hpp b/src/PartSegCore_compiled_backend/triangulation/point.hpp index 3b4cc6b..f67021c 100644 --- a/src/PartSegCore_compiled_backend/triangulation/point.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/point.hpp @@ -75,12 +75,54 @@ struct Segment { bool operator!=(const Segment &s) const { return !(*this == s); } - // Overload the << operator for Segment + /** + * Computes the x-coordinate of a point on the line segment at a given + * y-coordinate. This function assumes that the line segment is defined + * between the `bottom` and `top` points. + * + * If the line segment is horizontal (`bottom.y == top.y`), it returns the + * x-coordinate of `bottom`. Otherwise, it computes the x-coordinate using + * linear interpolation. + * + * @param y The y-coordinate at which to find the corresponding x-coordinate + * on the line segment. + * @return The x-coordinate of the point on the line segment at the specified + * y-coordinate. + */ + float point_on_line(float y) const { + if (bottom.y == top.y) { + return bottom.x; + } + return bottom.x + + (y - bottom.y) * ((top.x - bottom.x) / (top.y - bottom.y)); + } + + /** + * Overloads the << operator for the Segment structure, enabling + * segments to be directly inserted into output streams. + * + * This operator outputs a Segment in the format: + * [bottom=, top=] + * + * @param os The output stream to which the Segment is inserted. + * @param segment The Segment instance to be inserted into the stream. + * @return The output stream after the Segment has been inserted. + */ friend std::ostream &operator<<(std::ostream &os, const Segment &segment) { os << "[bottom=" << segment.bottom << ", top=" << segment.top << "]"; return os; } + /** + * A hash functor for the Segment structure. + * + * This functor computes a hash value for a given Segment instance. + * The hash is determined by combining the hash values of the bottom + * and top points of the segment. + * + * It is required for the Segment structure to be used as a key in + * unordered containers, such as unordered_map and unordered_set. + */ struct SegmentHash { std::size_t operator()(const Segment &segment) const { std::size_t h1 = Point::PointHash()(segment.bottom); diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index 8bca21c..66af6d9 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -16,12 +16,28 @@ namespace partsegcore { namespace triangulation { -enum PointType { NORMAL, SPLIT, MERGE, INTERSECTION, START, END }; +enum PointType { NORMAL, SPLIT, MERGE, INTERSECTION }; struct Interval { - point::Point last_seen; + point::Point last_seen{}; + point::Segment left_segment; + point::Segment right_segment; Interval() = default; - explicit Interval(const point::Point &p) : last_seen(p) {}; + explicit Interval(const point::Point &p, const point::Segment &left, + const point::Segment &right) + : last_seen(p), left_segment(left), right_segment(right) {}; + + void replace_segment(const point::Segment &old_segment, + const point::Segment &new_segment) { + if (left_segment == old_segment) { + left_segment = new_segment; + return; + } else if (right_segment == old_segment) { + right_segment = new_segment; + return; + } + throw std::runtime_error("Segment not found in interval"); + }; }; /** @@ -79,6 +95,7 @@ struct MonotonePolygon { std::vector right; MonotonePolygon() = default; + explicit MonotonePolygon(point::Point top) : top(top) {} MonotonePolygon(point::Point top, point::Point bottom, std::vector left, std::vector right) @@ -88,6 +105,11 @@ struct MonotonePolygon { right(std::move(right)) {} }; +struct PointMonotonePolygon { + MonotonePolygon *left_polygon = nullptr; + MonotonePolygon *right_polygon = nullptr; +}; + struct Triangle { std::size_t x; std::size_t y; @@ -97,11 +119,21 @@ struct Triangle { }; struct PointTriangle { - point::Point p1; - point::Point p2; - point::Point p3; - PointTriangle(point::Point p1, point::Point p2, point::Point p3) - : p1(p1), p2(p2), p3(p3) {}; + point::Point p1{}; + point::Point p2{}; + point::Point p3{}; + + PointTriangle(point::Point p1, point::Point p2, point::Point p3) { + if ((p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x) < 0) { + this->p1 = p3; + this->p2 = p2; + this->p3 = p1; + } else { + this->p1 = p1; + this->p2 = p2; + this->p3 = p3; + } + }; PointTriangle() = default; }; @@ -121,6 +153,283 @@ typedef std::unordered_map> PointToEdges; typedef std::map SegmentToLine; +/** + * Get a mapping from points to the list of edges that contain those points. + * Each list of edges is sorted by the point order within the edges. + * + * @param edges A vector of Segment objects representing the edges of a polygon. + * @return A PointToEdges map where each key is a point and the corresponding + * value is a vector of edges containing that point. + */ +PointToEdges get_points_edges(const std::vector &edges) { + PointToEdges point_to_edges; + for (std::size_t i = 0; i < edges.size(); i++) { + point_to_edges[edges[i].bottom].emplace_back(i, edges[i].top); + point_to_edges[edges[i].top].emplace_back(i, edges[i].bottom); + } + for (auto &point_to_edge : point_to_edges) { + std::sort(point_to_edge.second.begin(), point_to_edge.second.end()); + } + return point_to_edges; +} + +/** + * Determines the type of a given point based on its adjacent edges. + * + * If the point has more than two edges connected to it, it is considered an + * intersection point. If it has exactly two edges, further checks categorize + * it as one of split, merge, or normal points. + * + * - If both adjacent edges have their opposite ends before the given point `p`, + * the point is classified as a merge point. + * - If both adjacent edges have their opposite ends after the given point `p`, + * the point is classified as a split point. + * - Otherwise, it is categorized as a normal point. + * + * @param p The point to classify. + * @param point_to_edges A mapping from points to their adjacent edges. + * @return The type of the point as determined by its adjacent edges. + */ +PointType get_point_type(point::Point p, PointToEdges &point_to_edges) { + if (point_to_edges.at(p).size() != 2) return PointType::INTERSECTION; + const auto &edges = point_to_edges.at(p); + if (edges[0].opposite_point < p && edges[1].opposite_point < p) + return PointType::SPLIT; + if (p < edges[0].opposite_point && p < edges[1].opposite_point) + return PointType::MERGE; + return PointType::NORMAL; +} + +struct MonotonePolygonBuilder { + std::map segment_to_line{}; + std::vector edges{}; + PointToEdges point_to_edges{}; + std::vector monotone_polygons{}; + std::unordered_map + point_to_monotone_polygon{}; + + MonotonePolygonBuilder() = default; + explicit MonotonePolygonBuilder(const std::vector &edges) + : edges(edges) { + this->point_to_edges = get_points_edges(edges); + } + + /** + * Processes the end point of a segment within a monotone polygon. + * + * @param p The end point to be processed. + * + * The function retrieves two segments (edges) associated with the + * given point and checks the monotone polygon information of the + * top points of these segments. Depending on whether the end point + * belongs to one or two monotone polygons, it updates the monotone + * polygon information and stores completed polygons. + * + * It also handles the cleanup of relevant mappings and data structures + * when processing is complete. + */ + void process_end_point(const point::Point &p) { + const point::Segment &edge_left = + edges[point_to_edges.at(p).at(0).edge_index]; + const point::Segment &edge_right = + edges[point_to_edges.at(p).at(1).edge_index]; + + Interval *interval = segment_to_line.at(edge_left); + auto &left_monotone_polygon_info = + point_to_monotone_polygon.at(edge_left.top); + auto &right_monotone_polygon_info = + point_to_monotone_polygon.at(edge_right.top); + + // the point is + if (left_monotone_polygon_info.right_polygon == + right_monotone_polygon_info.left_polygon) { + // This is the end point of the monotone polygon + MonotonePolygon *polygon = left_monotone_polygon_info.right_polygon; + polygon->bottom = p; + monotone_polygons.push_back(*polygon); + delete polygon; + left_monotone_polygon_info.right_polygon = nullptr; + right_monotone_polygon_info.left_polygon = nullptr; + } else { + // This is the end point of two monotone polygons + MonotonePolygon *left_polygon = left_monotone_polygon_info.right_polygon; + MonotonePolygon *right_polygon = right_monotone_polygon_info.left_polygon; + + left_polygon->bottom = p; + monotone_polygons.push_back(*left_polygon); + delete left_polygon; + left_monotone_polygon_info.right_polygon = nullptr; + + right_polygon->bottom = p; + monotone_polygons.push_back(*right_polygon); + delete right_polygon; + right_monotone_polygon_info.left_polygon = nullptr; + } + + if (left_monotone_polygon_info.left_polygon == nullptr) { + // point edge_left.top is not in any monotone polygon + // remove the entry from the map + point_to_monotone_polygon.erase(edge_left.top); + } + if (right_monotone_polygon_info.right_polygon == nullptr) { + // point edge_right.top is not in any monotone polygon + // remove the entry from the map + point_to_monotone_polygon.erase(edge_right.top); + } + segment_to_line.erase(edge_left); + segment_to_line.erase(edge_right); + delete interval; + } + + void process_merge_point(const point::Point &p) { + const point::Segment &edge_left = + edges[point_to_edges.at(p).at(0).edge_index]; + const point::Segment &edge_right = + edges[point_to_edges.at(p).at(1).edge_index]; + if (segment_to_line.at(edge_left) != segment_to_line.at(edge_right)) { + // merge two intervals into one + Interval *left_interval = segment_to_line.at(edge_left); + Interval *right_interval = segment_to_line.at(edge_right); + segment_to_line.erase(edge_left); + segment_to_line.erase(edge_right); + left_interval->right_segment = right_interval->right_segment; + segment_to_line[right_interval->right_segment] = left_interval; + left_interval->last_seen = p; + delete right_interval; + point_to_monotone_polygon.at(edge_left.top) + .right_polygon->left.push_back(p); + point_to_monotone_polygon.at(edge_right.top) + .left_polygon->right.push_back(p); + } else { + // This is the end point + this->process_end_point(p); + } + }; + + /** + * Processes a normal point within a monotone polygon. + * + * @param p The normal point to be processed. + * + * The function identifies two edges associated with the given point and + * retrieves the corresponding segments. It then finds the interval and + * associated monotone polygons based on the edges. If the last seen point in + * the interval is a merge point, it updates and stores the completed monotone + * polygon. The function also updates the right and left polygons and handles + * segment replacements along with interval mappings, ensuring the data + * structures are correctly updated. + */ + void process_normal_point(const point::Point &p) { + const point::Segment &edge_top = + edges[point_to_edges.at(p).at(0).edge_index]; + const point::Segment &edge_bottom = + edges[point_to_edges.at(p).at(1).edge_index]; + Interval *interval = segment_to_line.at(edge_top); + + auto left_polygon = point_to_monotone_polygon.at(edge_top.top).left_polygon; + auto right_polygon = + point_to_monotone_polygon.at(edge_bottom.top).right_polygon; + + if (get_point_type(interval->last_seen, point_to_edges) == + PointType::MERGE) { + // if the last seen point is merge point, we need to end the monotone + // polygon + if (left_polygon != nullptr && + left_polygon->right.back() == interval->last_seen) { + left_polygon->bottom = p; + monotone_polygons.push_back(*left_polygon); + delete left_polygon; + left_polygon = new MonotonePolygon(); + left_polygon->top = interval->last_seen; + left_polygon->right.push_back(p); + point_to_monotone_polygon.at(interval->last_seen).right_polygon = + left_polygon; + point_to_monotone_polygon.at(edge_top.top).left_polygon = left_polygon; + } + } + + if (left_polygon != nullptr) { + left_polygon->right.push_back(p); + } + if (right_polygon != nullptr) { + right_polygon->left.push_back(p); + } + segment_to_line[edge_bottom] = interval; + interval->last_seen = p; + interval->replace_segment(edge_top, edge_bottom); + segment_to_line.erase(edge_top); + }; + + void process_start_point(const point::Point &p) { + const point::Segment &edge_left = + edges[point_to_edges.at(p).at(0).edge_index]; + const point::Segment &edge_right = + edges[point_to_edges.at(p).at(1).edge_index]; + + auto *interval = new Interval(p, edge_left, edge_right); + segment_to_line[edge_left] = interval; + segment_to_line[edge_right] = interval; + + auto *new_polygon = new MonotonePolygon(p); + point_to_monotone_polygon[edge_left.top].right_polygon = new_polygon; + point_to_monotone_polygon[edge_right.top].left_polygon = new_polygon; + } + + void process_split_point(const point::Point &p) { + const point::Segment &edge_left = + edges[point_to_edges.at(p).at(0).edge_index]; + const point::Segment &edge_right = + edges[point_to_edges.at(p).at(1).edge_index]; + + // We need to find to which interval the point belongs. + // If the point does not belong to any interval, we treat + // it as a start point and create a new interval + + for (auto &segment_interval : segment_to_line) { + if (segment_interval.first == segment_interval.second->right_segment) { + // scan interval only once + continue; + } + // check if the current point is inside the quadrangle defined by edges of + // the interval + Interval *interval = segment_interval.second; + if (interval->left_segment.point_on_line(p.y) < p.x && + interval->right_segment.point_on_line(p.y) > p.x) { + // the point is inside the interval + + // update the monotone polygon + auto polygon_pair = point_to_monotone_polygon.at(interval->last_seen); + if (polygon_pair.left_polygon != nullptr) { + polygon_pair.left_polygon->right.push_back(p); + } + if (polygon_pair.right_polygon != nullptr) { + polygon_pair.right_polygon->left.push_back(p); + } + point_to_monotone_polygon[p] = polygon_pair; + point_to_monotone_polygon.erase(interval->last_seen); + + // update the sweep line + auto right_segment = interval->right_segment; + interval->right_segment = edge_left; + interval->last_seen = p; + segment_to_line[edge_left] = interval; + + auto *new_interval = new Interval(p, edge_right, right_segment); + segment_to_line[edge_right] = new_interval; + segment_to_line[right_segment] = new_interval; + + return; + } + } + // the point is not inside any interval + this->process_start_point(p); + }; + + void process_intersection_point(const point::Point &p) { + + }; +}; + /** * Checks if a given polygon is convex. * @@ -205,7 +514,7 @@ void _build_triangles_opposite_edge(std::vector &stack, } stack[0] = stack.back(); stack[1] = current_point; - // remove all elements except two first + // remove all elements except two firsts stack.erase(stack.begin() + 2, stack.end()); } @@ -357,7 +666,7 @@ std::vector calc_edges( std::vector find_intersection_points( const std::vector &polygon) { /* find all edge intersections and add mid-points for all such intersection - * place*/ + * places*/ auto edges = calc_edges(polygon); auto intersections = intersection::_find_intersections(edges); @@ -404,107 +713,6 @@ std::vector find_intersection_points( return new_polygon; } -/** - * Determines the type of a given point based on its adjacent edges. - * - * If the point has more than two edges connected to it, it is considered an - * intersection point. If it has exactly two edges, further checks categorize - * it as one of split, merge, or normal points. - * - * - If both adjacent edges have their opposite ends before the given point `p`, - * the point is classified as a merge point. - * - If both adjacent edges have their opposite ends after the given point `p`, - * the point is classified as a split point. - * - Otherwise, it is categorized as a normal point. - * - * @param p The point to classify. - * @param point_to_edges A mapping from points to their adjacent edges. - * @return The type of the point as determined by its adjacent edges. - */ -PointType get_point_type( - point::Point p, PointToEdges &point_to_edges, - std::map &segment_to_line) { - if (point_to_edges.at(p).size() != 2) return PointType::INTERSECTION; - const auto &edges = point_to_edges.at(p); - if (edges[0].opposite_point < p && edges[1].opposite_point < p) - return PointType::MERGE; - if (p < edges[0].opposite_point && p < edges[1].opposite_point) - return PointType::SPLIT; - return PointType::NORMAL; -} - -/** - * Get a mapping from points to the list of edges that contain those points. - * Each list of edges is sorted by the point order within the edges. - * - * @param edges A vector of Segment objects representing the edges of a polygon. - * @return A PointToEdges map where each key is a point and the corresponding - * value is a vector of edges containing that point. - */ -PointToEdges get_points_edges(std::vector &edges) { - PointToEdges point_to_edges; - for (std::size_t i = 0; i < edges.size(); i++) { - point_to_edges[edges[i].bottom].emplace_back(i, edges[i].top); - point_to_edges[edges[i].top].emplace_back(i, edges[i].bottom); - } - for (auto &point_to_edge : point_to_edges) { - std::sort(point_to_edge.second.begin(), point_to_edge.second.end()); - } - return point_to_edges; -} - -void _process_normal_point( - const point::Point &p, const std::vector &edges, - const PointToEdges &point_to_edges, - std::map &segment_to_line) { - const point::Segment &edge_prev = - edges[point_to_edges.at(p).at(0).edge_index]; - const point::Segment &edge_next = - edges[point_to_edges.at(p).at(1).edge_index]; - segment_to_line[edge_next] = segment_to_line.at(edge_prev); - segment_to_line.at(edge_prev)->last_seen = p; - segment_to_line.erase(edge_prev); -} - -void _process_split_point( - const point::Point &p, const std::vector &edges, - const PointToEdges &point_to_edges, - std::map &segment_to_line) { - const point::Segment &edge_left = - edges[point_to_edges.at(p).at(0).edge_index]; - const point::Segment &edge_right = - edges[point_to_edges.at(p).at(1).edge_index]; -} - -/* process merge point - * When merge point is found, we need to merge two intervals into one - * - */ -void _process_merge_point( - const point::Point &p, const std::vector &edges, - const PointToEdges &point_to_edges, - std::map &segment_to_line) { - const point::Segment &edge_left = - edges[point_to_edges.at(p).at(0).edge_index]; - const point::Segment &edge_right = - edges[point_to_edges.at(p).at(1).edge_index]; -} - -///* this is processing of point that have more than 2 edges adjusted */ -// void _process_intersection_point( -// const point::Point &p, std::unordered_map -// &segment_to_line, std::unordered_set -// &sweeping_line_intersect) { -// for (auto &edge : point_to_edges[p]) { -// sweeping_line_intersect.insert(edges[edge.edge_index]); -// } -// std::vector intervals; -// for (auto &edge : point_to_edges[p]) { -// intervals.push_back(Interval(p, edges[edge.edge_index], -// edges[edge.edge_index])); -// } -// } - /* This is an implementation of sweeping line triangulation of polygon Its assumes that there is no edge intersections, but may be a point with @@ -514,55 +722,55 @@ void _process_merge_point( std::pair, std::vector> sweeping_line_triangulation(const std::vector &polygon) { std::vector result; - point::Segment *edge_prev, *edge_next; auto edges = calc_edges(polygon); + MonotonePolygonBuilder builder(edges); + PointToEdges point_to_edges = get_points_edges(edges); std::vector sorted_points = polygon; // copy to avoid modification of original vector std::sort(sorted_points.begin(), sorted_points.end()); - std::vector ordered_polygon_li; - std::map segment_to_line; - std::vector intervals; - ordered_polygon_li.emplace_back(); - Interval line; for (auto &sorted_point : sorted_points) { auto point_type = get_point_type(sorted_point, point_to_edges); switch (point_type) { - case PointType::START: - // start new line - break; - - case PointType::END: - // end line - break; - case PointType::NORMAL: - // change edge adjusted to current sweeping line - _process_normal_point(sorted_point, edges, point_to_edges, - segment_to_line); - + // Change edge adjusted to the current sweeping line. + // Adds edge to monotone polygon. + builder.process_normal_point(sorted_point); break; case PointType::SPLIT: - // split sweeping line on two lines - // add edge sor cutting polygon on two parts - _process_split_point(sorted_point, edges, point_to_edges, - segment_to_line); + // Split a sweeping line on two lines, + // adds edge sor cutting polygon on two parts. + builder.process_split_point(sorted_point); break; case PointType::MERGE: // merge two sweeping lines to one - // save point as point to start new line for SPLIT point case - _process_merge_point(sorted_point, edges, point_to_edges, - segment_to_line); + // merge two intervals into one + // It may be the end of the polygon, then finish the + // monotone polygon + builder.process_merge_point(sorted_point); break; case PointType::INTERSECTION: // this is a merge and split point at the same time // this is not described in original algorithm // but we need it to handle self intersecting polygons // Remember about more than 4 edges case + builder.process_intersection_point(sorted_point); break; } } + std::map point_to_index; + for (std::size_t i = 0; i < polygon.size(); i++) { + point_to_index[polygon[i]] = i; + } + for (auto &monotone_polygon : builder.monotone_polygons) { + auto triangles = triangulate_monotone_polygon(monotone_polygon); + for (auto &triangle : triangles) { + result.emplace_back(point_to_index[triangle.p1], + point_to_index[triangle.p2], + point_to_index[triangle.p3]); + } + } return std::make_pair(result, polygon); } @@ -585,7 +793,7 @@ _triangulate_polygon(const std::vector &polygon) { if (_is_convex(polygon)) return std::make_pair(_triangle_convex_polygon(polygon), polygon); - // Implement sweeping line algorithm for triangulation + // Implement the sweeping line algorithm for triangulation // described on this lecture: // https://www.youtube.com/playlist?list=PLtTatrCwXHzEqzJMaTUFgqoCNllgwk4DH // From bb18964ca4b1996898824dc53763e2f2a7dcd9f9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 29 Oct 2024 16:22:49 +0100 Subject: [PATCH 39/76] fix orientation of triangles --- src/tests/test_triangulate.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index d6e29e8..eaca1bd 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -58,7 +58,7 @@ def test_find_intersections(): @pytest.mark.parametrize( - 'segments, expected', + ('segments', 'expected'), [ # No intersections, simple square ([[(0, 0), (0, 1)], [(0, 1), (1, 1)], [(1, 1), (1, 0)], [(1, 0), (0, 0)]], []), @@ -104,7 +104,7 @@ def test_find_intersections_param(segments, expected): @pytest.mark.parametrize( - 'segment1, segment2, expected', + ('segment1', 'segment2', 'expected'), [ (((0, 0), (2, 2)), ((0, 2), (2, 0)), (1, 1)), # Intersecting diagonals (((0, 0), (1, 1)), ((1, 0), (0, 1)), (0.5, 0.5)), # Intersecting diagonals @@ -258,18 +258,18 @@ def test_find_intersection_points_py_cross_intersect_in_point(): [ ( ((1, 2), (1, 0), [(0, 1)], [(2, 1)]), - [[(0.0, 1.0), (1.0, 2.0), (2.0, 1.0)], [(1.0, 0.0), (2.0, 1.0), (0.0, 1.0)]], + [[(2.0, 1.0), (1.0, 2.0), (0.0, 1.0)], [(1.0, 0.0), (2.0, 1.0), (0.0, 1.0)]], ), ( ((5, 2), (5, 0), [(0, 1)], [(2, 1)]), - [[(0.0, 1.0), (5.0, 2.0), (2.0, 1.0)], [(5.0, 0.0), (2.0, 1.0), (0.0, 1.0)]], + [[(2.0, 1.0), (5.0, 2.0), (0.0, 1.0)], [(5.0, 0.0), (2.0, 1.0), (0.0, 1.0)]], ), ( ((1, 3), (1, 0), [(0, 2), (0, 1)], [(2, 2), (2, 1)]), [ - [(0.0, 2.0), (1.0, 3.0), (2.0, 2.0)], + [(2.0, 2.0), (1.0, 3.0), (0.0, 2.0)], [(2.0, 1.0), (2.0, 2.0), (0.0, 2.0)], - [(0.0, 1.0), (0.0, 2.0), (2.0, 1.0)], + [(2.0, 1.0), (0.0, 2.0), (0.0, 1.0)], [(1.0, 0.0), (2.0, 1.0), (0.0, 1.0)], ], ), @@ -278,15 +278,15 @@ def test_find_intersection_points_py_cross_intersect_in_point(): [ [(3.0, 2.0), (2.0, 3.0), (0.0, 4.0)], [(2.0, 1.0), (3.0, 2.0), (0.0, 4.0)], - [(0.0, 0.0), (0.0, 4.0), (2.0, 1.0)], + [(2.0, 1.0), (0.0, 4.0), (0.0, 0.0)], ], ), ( ((0, 4), (0, 0), [], [(2, 3), (1, 2), (2, 1)]), [ [(1.0, 2.0), (2.0, 3.0), (0.0, 4.0)], - [(0.0, 0.0), (0.0, 4.0), (1.0, 2.0)], - [(0.0, 0.0), (1.0, 2.0), (2.0, 1.0)], + [(1.0, 2.0), (0.0, 4.0), (0.0, 0.0)], + [(2.0, 1.0), (1.0, 2.0), (0.0, 0.0)], ], ), ], From a5de4c7c3928a41a8beb550630893230860c64c4 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 29 Oct 2024 18:26:49 +0100 Subject: [PATCH 40/76] use c++ code for triangulation --- .../triangulate.pyx | 54 +----- .../triangulation/triangulate.hpp | 164 ++++++++++++------ src/tests/test_triangulate.py | 10 +- 3 files changed, 124 insertions(+), 104 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index 0f957e9..057c252 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -83,6 +83,7 @@ cdef extern from "triangulation/triangulate.hpp" namespace "partsegcore::triangu bool left_to_right(const Segment& s1, const Segment& s2) vector[Point] find_intersection_points(const vector[Point]& segments) vector[PointTriangle] triangulate_monotone_polygon(const MonotonePolygon& polygon) + pair[vector[Triangle], vector[Point]] triangulate_polygon(const vector[Point]& polygon) @@ -204,6 +205,7 @@ def is_convex(polygon: Sequence[Sequence[float]]) -> bool: def triangle_convex_polygon(polygon: Sequence[Sequence[float]]) -> list[tuple[int, int, int]]: cdef vector[Point] polygon_vector cdef vector[Triangle] result + cdef Point p1, p2 polygon_vector.reserve(len(polygon)) polygon_vector.push_back(Point(polygon[0][0], polygon[0][1])) @@ -217,57 +219,13 @@ def triangle_convex_polygon(polygon: Sequence[Sequence[float]]) -> list[tuple[i result = _triangle_convex_polygon(polygon_vector) return [(triangle.x, triangle.y, triangle.z) for triangle in result] -cdef vector[Triangle] _triangulate_polygon(vector[Point] polygon): - cdef vector[Segment] edges, edges_with_intersections - cdef Py_ssize_t i, j, edges_count - cdef unordered_set[OrderedPair] intersections - cdef unordered_map[int, vector[Point]] intersections_points - cdef pair[int, vector[Point]] p_it - cdef vector[Triangle] triangles - cdef vector[Point] intersections_points_vector - cdef Point p_int - cdef OrderedPair p - - if _is_convex(polygon): - return _triangle_convex_polygon(polygon) - - edges.reserve(polygon.size()) - for i in range(polygon.size() - 1): - edges.push_back(Segment(polygon[i], polygon[i+1])) - - intersections = _find_intersections(edges) - intersections_points.reserve(intersections.size()) - for p in intersections: - p_int = _find_intersection(edges[p.first], edges[p.second]) - intersections_points[p.first].push_back(p_int) - intersections_points[p.second].push_back(p_int) - - edges_count = edges.size() - for p_it in intersections_points: - edges_count += p_it.second.size() - 1 - edges_with_intersections.reserve(edges_count) - for i in range(edges.size()): - if not intersections_points.count(i): - edges_with_intersections.push_back(edges[i]) - else: - intersections_points_vector = intersections_points.at(i) - intersections_points_vector.push_back(edges[i].bottom) - intersections_points_vector.push_back(edges[i].top) - sort(intersections_points_vector.begin(), intersections_points_vector.end()) - for j in range(intersections_points_vector.size() - 1): - edges_with_intersections.push_back(Segment(intersections_points_vector[j], intersections_points_vector[j+1])) - - return triangles - - -def triangulate_polygon(polygon: Sequence[Sequence[float]]) -> list[tuple[int, int, int]]: +def triangulate_polygon_py(polygon: Sequence[Sequence[float]]) -> tuple[list[tuple[int, int, int]], list[tuple[float, float]]]: """ Triangulate polygon""" cdef vector[Point] polygon_vector cdef Point p1, p2 - cdef vector[Triangle] result - + cdef pair[vector[Triangle], vector[Point]] result polygon_vector.reserve(len(polygon)) polygon_vector.push_back(Point(polygon[0][0], polygon[0][1])) @@ -278,8 +236,8 @@ def triangulate_polygon(polygon: Sequence[Sequence[float]]) -> list[tuple[int, i # prevent from adding polygon edge of width 0 polygon_vector.push_back(p2) - result = _triangulate_polygon(polygon_vector) - return [(triangle.x, triangle.y, triangle.z) for triangle in result] + result = triangulate_polygon(polygon_vector) + return [(triangle.x, triangle.y, triangle.z) for triangle in result.first], [(point.x, point.y) for point in result.second] def segment_left_to_right_comparator(s1: Sequence[Sequence[float]], s2: Sequence[Sequence[float]]) -> bool: diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index 66af6d9..3a47ad5 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -652,6 +652,23 @@ std::vector calc_edges( return edges; } +std::vector calc_edges( + const std::vector> &polygon_list) { + std::vector edges; + std::size_t points_count = 0; + for (const auto &polygon : polygon_list) { + points_count += polygon.size(); + } + edges.reserve(points_count); + for (const auto &polygon : polygon_list) { + for (std::size_t i = 0; i < polygon.size() - 1; i++) { + edges.emplace_back(polygon[i], polygon[i + 1]); + } + edges.emplace_back(polygon[polygon.size() - 1], polygon[0]); + } + return edges; +} + /** * @brief Finds intersection points in a polygon and adds mid-points for all * intersections. @@ -663,14 +680,14 @@ std::vector calc_edges( * @return A new vector of Point objects representing the polygon with added * intersection points. */ -std::vector find_intersection_points( - const std::vector &polygon) { +std::vector> find_intersection_points( + const std::vector> &polygon_list) { /* find all edge intersections and add mid-points for all such intersection * places*/ - auto edges = calc_edges(polygon); + auto edges = calc_edges(polygon_list); auto intersections = intersection::_find_intersections(edges); - if (intersections.empty()) return polygon; + if (intersections.empty()) return polygon_list; std::unordered_map> intersections_points; for (const auto &intersection : intersections) { @@ -678,10 +695,9 @@ std::vector find_intersection_points( edges[intersection.first], edges[intersection.second]); intersections_points[intersection.first].push_back(inter_point); intersections_points[intersection.second].push_back(inter_point); - } - std::size_t points_count = polygon.size(); + }; for (auto &intersections_point : intersections_points) { - points_count += intersections_point.second.size() - 1; + // points_count += intersections_point.second.size() - 1; intersections_point.second.push_back(edges[intersections_point.first].top); intersections_point.second.push_back( edges[intersections_point.first].bottom); @@ -689,28 +705,56 @@ std::vector find_intersection_points( intersections_point.second.end()); } - std::vector new_polygon; - new_polygon.reserve(points_count); - for (std::size_t i = 0; i < polygon.size(); i++) { - auto point = polygon[i]; - if (new_polygon[new_polygon.size() - 1] != point) - new_polygon.push_back(point); - if (intersections_points.count(i)) { - auto new_points = intersections_points[i]; - if (new_points[0] == point) { - for (std::size_t j = 1; j < new_points.size() - 1; j++) { - if (new_polygon[new_polygon.size() - 1] != new_points[j]) - new_polygon.push_back(new_points[j]); - } - } else { - for (std::size_t j = new_points.size() - 2; j > 0; j++) { - if (new_polygon[new_polygon.size() - 1] != new_points[j]) - new_polygon.push_back(new_points[j]); + std::vector> new_polygons_list; + + for (const auto &polygon : polygon_list) { + std::vector new_polygon; + new_polygon.reserve(polygon.size() * 2); + for (std::size_t i = 0; i < polygon.size(); i++) { + auto point = polygon[i]; + if (new_polygon[new_polygon.size() - 1] != point) + new_polygon.push_back(point); + if (intersections_points.count(i)) { + auto new_points = intersections_points[i]; + if (new_points[0] == point) { + for (std::size_t j = 1; j < new_points.size() - 1; j++) { + if (new_polygon[new_polygon.size() - 1] != new_points[j]) + new_polygon.push_back(new_points[j]); + } + } else { + for (std::size_t j = new_points.size() - 2; j > 0; j++) { + if (new_polygon[new_polygon.size() - 1] != new_points[j]) + new_polygon.push_back(new_points[j]); + } } } } + new_polygons_list.push_back(new_polygon); } - return new_polygon; + return new_polygons_list; +} + +std::vector find_intersection_points( + const std::vector &polygon) { + auto new_polygon = find_intersection_points( + std::vector>({polygon})); + return new_polygon[0]; +} + +std::vector _sorted_polygons_points( + const std::vector> &polygon_list) { + std::vector result; + std::unordered_set visited; + for (const auto &polygon : polygon_list) { + for (const auto &point : polygon) { + if (visited.count(point) == 0) { + result.push_back(point); + visited.insert(point); + } + } + } + std::sort(result.begin(), result.end()); + return result; } /* @@ -720,16 +764,17 @@ std::vector find_intersection_points( https://www.youtube.com/playlist?list=PLtTatrCwXHzEqzJMaTUFgqoCNllgwk4DH */ std::pair, std::vector> -sweeping_line_triangulation(const std::vector &polygon) { +sweeping_line_triangulation( + const std::vector> &polygon_list) { std::vector result; - auto edges = calc_edges(polygon); + auto edges = calc_edges(polygon_list); MonotonePolygonBuilder builder(edges); PointToEdges point_to_edges = get_points_edges(edges); - std::vector sorted_points = polygon; - // copy to avoid modification of original vector - std::sort(sorted_points.begin(), sorted_points.end()); + std::vector sorted_points = + _sorted_polygons_points(polygon_list); + for (auto &sorted_point : sorted_points) { auto point_type = get_point_type(sorted_point, point_to_edges); switch (point_type) { @@ -760,8 +805,8 @@ sweeping_line_triangulation(const std::vector &polygon) { } } std::map point_to_index; - for (std::size_t i = 0; i < polygon.size(); i++) { - point_to_index[polygon[i]] = i; + for (std::size_t i = 0; i < sorted_points.size(); i++) { + point_to_index[sorted_points[i]] = i; } for (auto &monotone_polygon : builder.monotone_polygons) { auto triangles = triangulate_monotone_polygon(monotone_polygon); @@ -771,34 +816,51 @@ sweeping_line_triangulation(const std::vector &polygon) { point_to_index[triangle.p3]); } } - return std::make_pair(result, polygon); + return std::make_pair(result, sorted_points); } -std::pair, std::vector> -_triangulate_polygon(const std::vector &polygon) { - if (polygon.size() < 3) - return std::make_pair(std::vector(), polygon); - if (polygon.size() == 3) - return std::make_pair(std::vector({Triangle(0, 1, 2)}), polygon); - if (polygon.size() == 4) { - if (partsegcore::intersection::_orientation(polygon[0], polygon[1], - polygon[2]) != - partsegcore::intersection::_orientation(polygon[0], polygon[3], - polygon[2])) - return std::make_pair( - std::vector({Triangle(0, 1, 2), Triangle(0, 3, 2)}), - polygon); - } +// calculate the triangulation of a symmetric difference of list of polygons + +std::pair, std::vector> triangulate_polygon( + const std::vector> &polygon_list) { + if (polygon_list.empty()) + // empty list + return std::make_pair(std::vector(), std::vector()); + if (polygon_list.size() == 1) { + // only one polygon in the list + std::vector polygon = polygon_list[0]; + if (polygon.size() < 3) + return std::make_pair(std::vector(), polygon); + if (polygon.size() == 3) + return std::make_pair(std::vector({Triangle(0, 1, 2)}), + polygon); + if (polygon.size() == 4) { + if (partsegcore::intersection::_orientation(polygon[0], polygon[1], + polygon[2]) != + partsegcore::intersection::_orientation(polygon[0], polygon[3], + polygon[2])) + return std::make_pair( + std::vector({Triangle(0, 1, 2), Triangle(0, 3, 2)}), + polygon); + } - if (_is_convex(polygon)) - return std::make_pair(_triangle_convex_polygon(polygon), polygon); + if (_is_convex(polygon)) + return std::make_pair(_triangle_convex_polygon(polygon), polygon); + } // Implement the sweeping line algorithm for triangulation // described on this lecture: // https://www.youtube.com/playlist?list=PLtTatrCwXHzEqzJMaTUFgqoCNllgwk4DH // - return sweeping_line_triangulation(find_intersection_points(polygon)); + return sweeping_line_triangulation(find_intersection_points(polygon_list)); } + +std::pair, std::vector> triangulate_polygon( + const std::vector &polygon_list) { + return triangulate_polygon( + std::vector>({polygon_list})); +} + } // namespace triangulation } // namespace partsegcore diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index eaca1bd..6954ac5 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -11,7 +11,7 @@ segment_left_to_right_comparator, triangle_convex_polygon, triangulate_monotone_polygon_py, - triangulate_polygon, + triangulate_polygon_py, ) @@ -137,10 +137,10 @@ def test_triangle_convex_polygon(): assert triangle_convex_polygon([(0, 0), (0, 0.5), (0, 1), (1, 1), (1, 0)]) == [(0, 2, 3), (0, 3, 4)] -def test_triangulate_polygon(): - assert triangulate_polygon([(0, 0), (0, 1), (1, 1), (1, 0)]) == [(0, 1, 2), (0, 2, 3)] - assert triangulate_polygon([(0, 0), (0, 10), (1, 10), (1, 0)]) == [(0, 1, 2), (0, 2, 3)] - assert triangulate_polygon([(0, 0), (0, 0.5), (0, 1), (1, 1), (1, 0)]) == [(0, 2, 3), (0, 3, 4)] +def test_triangulate_polygon_py(): + assert triangulate_polygon_py([(0, 0), (0, 1), (1, 1), (1, 0)])[0] == [(0, 1, 2), (0, 3, 2)] + assert triangulate_polygon_py([(0, 0), (0, 10), (1, 10), (1, 0)])[0] == [(0, 1, 2), (0, 3, 2)] + assert triangulate_polygon_py([(0, 0), (0, 0.5), (0, 1), (1, 1), (1, 0)])[0] == [(0, 2, 3), (0, 3, 4)] @pytest.mark.parametrize( From 89f99a9dac867cbc50a50491b521fddd7724f41c Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 29 Oct 2024 18:31:14 +0100 Subject: [PATCH 41/76] more verbose test run --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index eeb4073..04b6f41 100644 --- a/tox.ini +++ b/tox.ini @@ -28,4 +28,4 @@ deps = cython commands = - pytest + pytest -v From 9f2cfe0eaf58d1d80f474d7afdf87caf5d677cc6 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 29 Oct 2024 18:48:44 +0100 Subject: [PATCH 42/76] fix checking of point dupplication --- src/PartSegCore_compiled_backend/triangulation/triangulate.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index 3a47ad5..219ad24 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -710,6 +710,7 @@ std::vector> find_intersection_points( for (const auto &polygon : polygon_list) { std::vector new_polygon; new_polygon.reserve(polygon.size() * 2); + new_polygon.push_back(polygon[0]); for (std::size_t i = 0; i < polygon.size(); i++) { auto point = polygon[i]; if (new_polygon[new_polygon.size() - 1] != point) From 935848099f1dd5fe46d17c005ab5a8d9015a1441 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Wed, 30 Oct 2024 01:14:47 +0100 Subject: [PATCH 43/76] Fix basic bugs --- CMakeLists.txt | 1 + pyproject.toml | 3 ++- .../triangulation/triangulate.hpp | 22 ++++++++++++++++--- src/tests/test_triangulate.py | 20 +++++++++++++---- 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 41249ca..2473c10 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.21) project(${SKBUILD_PROJECT_NAME} LANGUAGES CXX) set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_FLAGS_DEBUG_INIT "-DDEBUG") # Define compiler directive add_definitions(-DNPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION) diff --git a/pyproject.toml b/pyproject.toml index 7af867c..be35b7c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -140,7 +140,8 @@ quote-style = "single" indent-style = "space" docstring-code-format = true line-ending = "lf" -skip-magic-trailing-comma = true +skip-magic-trailing-comma = false + [tool.ruff.lint] select = [ diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index 219ad24..0a669a4 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -108,6 +108,10 @@ struct MonotonePolygon { struct PointMonotonePolygon { MonotonePolygon *left_polygon = nullptr; MonotonePolygon *right_polygon = nullptr; + PointMonotonePolygon() = default; + explicit PointMonotonePolygon(MonotonePolygon *left_polygon, + MonotonePolygon *right_polygon) + : left_polygon(left_polygon), right_polygon(right_polygon) {}; }; struct Triangle { @@ -168,7 +172,10 @@ PointToEdges get_points_edges(const std::vector &edges) { point_to_edges[edges[i].top].emplace_back(i, edges[i].bottom); } for (auto &point_to_edge : point_to_edges) { - std::sort(point_to_edge.second.begin(), point_to_edge.second.end()); + std::sort(point_to_edge.second.begin(), point_to_edge.second.end(), + [](const PointEdges &a, const PointEdges &b) { + return b.opposite_point < a.opposite_point; + }); } return point_to_edges; } @@ -324,11 +331,14 @@ struct MonotonePolygonBuilder { edges[point_to_edges.at(p).at(0).edge_index]; const point::Segment &edge_bottom = edges[point_to_edges.at(p).at(1).edge_index]; + if (segment_to_line.count(edge_top) == 0) { + throw std::runtime_error("Segment not found in the map"); + } Interval *interval = segment_to_line.at(edge_top); auto left_polygon = point_to_monotone_polygon.at(edge_top.top).left_polygon; auto right_polygon = - point_to_monotone_polygon.at(edge_bottom.top).right_polygon; + point_to_monotone_polygon.at(edge_top.top).right_polygon; if (get_point_type(interval->last_seen, point_to_edges) == PointType::MERGE) { @@ -348,12 +358,16 @@ struct MonotonePolygonBuilder { } } + point_to_monotone_polygon[p] = + PointMonotonePolygon(left_polygon, right_polygon); + if (left_polygon != nullptr) { left_polygon->right.push_back(p); } if (right_polygon != nullptr) { right_polygon->left.push_back(p); } + segment_to_line[edge_bottom] = interval; interval->last_seen = p; interval->replace_segment(edge_top, edge_bottom); @@ -754,7 +768,9 @@ std::vector _sorted_polygons_points( } } } - std::sort(result.begin(), result.end()); + std::sort( + result.begin(), result.end(), + [](const point::Point &p1, const point::Point &p2) { return p2 < p1; }); return result; } diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index 6954ac5..c962a11 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -137,10 +137,22 @@ def test_triangle_convex_polygon(): assert triangle_convex_polygon([(0, 0), (0, 0.5), (0, 1), (1, 1), (1, 0)]) == [(0, 2, 3), (0, 3, 4)] -def test_triangulate_polygon_py(): - assert triangulate_polygon_py([(0, 0), (0, 1), (1, 1), (1, 0)])[0] == [(0, 1, 2), (0, 3, 2)] - assert triangulate_polygon_py([(0, 0), (0, 10), (1, 10), (1, 0)])[0] == [(0, 1, 2), (0, 3, 2)] - assert triangulate_polygon_py([(0, 0), (0, 0.5), (0, 1), (1, 1), (1, 0)])[0] == [(0, 2, 3), (0, 3, 4)] +@pytest.mark.parametrize( + ('polygon', 'expected'), + [ + ([(0, 0), (0, 1), (1, 1), (1, 0)], [(0, 1, 2), (0, 3, 2)]), + ([(0, 0), (0, 10), (1, 10), (1, 0)], [(0, 1, 2), (0, 3, 2)]), + ([(0, 0), (0, 0.5), (0, 1), (1, 1), (1, 0)], [(0, 2, 3), (0, 3, 4)]), + ], +) +def test_triangulate_polygon_py_convex(polygon, expected): + assert triangulate_polygon_py(polygon)[0] == expected + + +@pytest.mark.parametrize(('polygon', 'expected'), [([(0, 0), (1, 1), (0, 2), (2, 1)], [(0, 1, 2), (0, 3, 2)])]) +@pytest.mark.xfail(reason='Not implemented') +def test_triangulate_polygon_py_non_convex(polygon, expected): + assert triangulate_polygon_py(polygon)[0] == expected @pytest.mark.parametrize( From 39b9fe4f2c397b7b60e486a8a50b29c9fd8a6c4d Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Wed, 30 Oct 2024 17:10:03 +0100 Subject: [PATCH 44/76] first test for triangulation --- .../triangulation/triangulate.hpp | 179 ++++++++---------- src/tests/test_triangulate.py | 12 +- 2 files changed, 85 insertions(+), 106 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index 0a669a4..bd4d2d2 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -18,14 +18,38 @@ namespace triangulation { enum PointType { NORMAL, SPLIT, MERGE, INTERSECTION }; +struct MonotonePolygon { + point::Point top{}; + point::Point bottom{}; + std::vector left; + std::vector right; + + MonotonePolygon() = default; + explicit MonotonePolygon(point::Point top) : top(top) {} + MonotonePolygon(point::Point top, point::Point bottom, + std::vector left, + std::vector right) + : top(top), + bottom(bottom), + left(std::move(left)), + right(std::move(right)) {} +}; + struct Interval { point::Point last_seen{}; point::Segment left_segment; point::Segment right_segment; + std::vector polygons_list{}; Interval() = default; explicit Interval(const point::Point &p, const point::Segment &left, const point::Segment &right) : last_seen(p), left_segment(left), right_segment(right) {}; + explicit Interval(const point::Point &p, const point::Segment &left, + const point::Segment &right, MonotonePolygon *polygon) + : last_seen(p), + left_segment(left), + right_segment(right), + polygons_list({polygon}) {}; void replace_segment(const point::Segment &old_segment, const point::Segment &new_segment) { @@ -88,23 +112,6 @@ struct SegmentLeftRightComparator { } }; -struct MonotonePolygon { - point::Point top{}; - point::Point bottom{}; - std::vector left; - std::vector right; - - MonotonePolygon() = default; - explicit MonotonePolygon(point::Point top) : top(top) {} - MonotonePolygon(point::Point top, point::Point bottom, - std::vector left, - std::vector right) - : top(top), - bottom(bottom), - left(std::move(left)), - right(std::move(right)) {} -}; - struct PointMonotonePolygon { MonotonePolygon *left_polygon = nullptr; MonotonePolygon *right_polygon = nullptr; @@ -199,6 +206,7 @@ PointToEdges get_points_edges(const std::vector &edges) { */ PointType get_point_type(point::Point p, PointToEdges &point_to_edges) { if (point_to_edges.at(p).size() != 2) return PointType::INTERSECTION; + const auto &edges = point_to_edges.at(p); if (edges[0].opposite_point < p && edges[1].opposite_point < p) return PointType::SPLIT; @@ -212,8 +220,6 @@ struct MonotonePolygonBuilder { std::vector edges{}; PointToEdges point_to_edges{}; std::vector monotone_polygons{}; - std::unordered_map - point_to_monotone_polygon{}; MonotonePolygonBuilder() = default; explicit MonotonePolygonBuilder(const std::vector &edges) @@ -242,47 +248,13 @@ struct MonotonePolygonBuilder { edges[point_to_edges.at(p).at(1).edge_index]; Interval *interval = segment_to_line.at(edge_left); - auto &left_monotone_polygon_info = - point_to_monotone_polygon.at(edge_left.top); - auto &right_monotone_polygon_info = - point_to_monotone_polygon.at(edge_right.top); - - // the point is - if (left_monotone_polygon_info.right_polygon == - right_monotone_polygon_info.left_polygon) { - // This is the end point of the monotone polygon - MonotonePolygon *polygon = left_monotone_polygon_info.right_polygon; + + for (auto &polygon : interval->polygons_list) { polygon->bottom = p; monotone_polygons.push_back(*polygon); delete polygon; - left_monotone_polygon_info.right_polygon = nullptr; - right_monotone_polygon_info.left_polygon = nullptr; - } else { - // This is the end point of two monotone polygons - MonotonePolygon *left_polygon = left_monotone_polygon_info.right_polygon; - MonotonePolygon *right_polygon = right_monotone_polygon_info.left_polygon; - - left_polygon->bottom = p; - monotone_polygons.push_back(*left_polygon); - delete left_polygon; - left_monotone_polygon_info.right_polygon = nullptr; - - right_polygon->bottom = p; - monotone_polygons.push_back(*right_polygon); - delete right_polygon; - right_monotone_polygon_info.left_polygon = nullptr; } - if (left_monotone_polygon_info.left_polygon == nullptr) { - // point edge_left.top is not in any monotone polygon - // remove the entry from the map - point_to_monotone_polygon.erase(edge_left.top); - } - if (right_monotone_polygon_info.right_polygon == nullptr) { - // point edge_right.top is not in any monotone polygon - // remove the entry from the map - point_to_monotone_polygon.erase(edge_right.top); - } segment_to_line.erase(edge_left); segment_to_line.erase(edge_right); delete interval; @@ -302,11 +274,10 @@ struct MonotonePolygonBuilder { left_interval->right_segment = right_interval->right_segment; segment_to_line[right_interval->right_segment] = left_interval; left_interval->last_seen = p; + for (auto &polygon : right_interval->polygons_list) { + left_interval->polygons_list.push_back(polygon); + } delete right_interval; - point_to_monotone_polygon.at(edge_left.top) - .right_polygon->left.push_back(p); - point_to_monotone_polygon.at(edge_right.top) - .left_polygon->right.push_back(p); } else { // This is the end point this->process_end_point(p); @@ -336,36 +307,31 @@ struct MonotonePolygonBuilder { } Interval *interval = segment_to_line.at(edge_top); - auto left_polygon = point_to_monotone_polygon.at(edge_top.top).left_polygon; - auto right_polygon = - point_to_monotone_polygon.at(edge_top.top).right_polygon; - - if (get_point_type(interval->last_seen, point_to_edges) == - PointType::MERGE) { - // if the last seen point is merge point, we need to end the monotone - // polygon - if (left_polygon != nullptr && - left_polygon->right.back() == interval->last_seen) { - left_polygon->bottom = p; - monotone_polygons.push_back(*left_polygon); - delete left_polygon; - left_polygon = new MonotonePolygon(); - left_polygon->top = interval->last_seen; - left_polygon->right.push_back(p); - point_to_monotone_polygon.at(interval->last_seen).right_polygon = - left_polygon; - point_to_monotone_polygon.at(edge_top.top).left_polygon = left_polygon; + if (interval->polygons_list.size() > 1) { + // end all the polygons, except 1 + if (edge_top == interval->right_segment) { + for (auto i = 1; i < interval->polygons_list.size(); i++) { + interval->polygons_list[i]->bottom = p; + monotone_polygons.push_back(*interval->polygons_list[i]); + delete interval->polygons_list[i]; + } + + } else { + for (auto i = 0; i < interval->polygons_list.size() - 1; i++) { + interval->polygons_list[i]->bottom = p; + monotone_polygons.push_back(*interval->polygons_list[i]); + delete interval->polygons_list[i]; + } + interval->polygons_list[0] = interval->polygons_list.back(); } + interval->polygons_list.erase(interval->polygons_list.begin() + 1, + interval->polygons_list.end()); } - point_to_monotone_polygon[p] = - PointMonotonePolygon(left_polygon, right_polygon); - - if (left_polygon != nullptr) { - left_polygon->right.push_back(p); - } - if (right_polygon != nullptr) { - right_polygon->left.push_back(p); + if (edge_top == interval->right_segment) { + interval->polygons_list[0]->right.push_back(p); + } else { + interval->polygons_list[0]->left.push_back(p); } segment_to_line[edge_bottom] = interval; @@ -380,13 +346,10 @@ struct MonotonePolygonBuilder { const point::Segment &edge_right = edges[point_to_edges.at(p).at(1).edge_index]; - auto *interval = new Interval(p, edge_left, edge_right); + auto *new_polygon = new MonotonePolygon(p); + auto *interval = new Interval(p, edge_left, edge_right, new_polygon); segment_to_line[edge_left] = interval; segment_to_line[edge_right] = interval; - - auto *new_polygon = new MonotonePolygon(p); - point_to_monotone_polygon[edge_left.top].right_polygon = new_polygon; - point_to_monotone_polygon[edge_right.top].left_polygon = new_polygon; } void process_split_point(const point::Point &p) { @@ -411,17 +374,6 @@ struct MonotonePolygonBuilder { interval->right_segment.point_on_line(p.y) > p.x) { // the point is inside the interval - // update the monotone polygon - auto polygon_pair = point_to_monotone_polygon.at(interval->last_seen); - if (polygon_pair.left_polygon != nullptr) { - polygon_pair.left_polygon->right.push_back(p); - } - if (polygon_pair.right_polygon != nullptr) { - polygon_pair.right_polygon->left.push_back(p); - } - point_to_monotone_polygon[p] = polygon_pair; - point_to_monotone_polygon.erase(interval->last_seen); - // update the sweep line auto right_segment = interval->right_segment; interval->right_segment = edge_left; @@ -432,6 +384,27 @@ struct MonotonePolygonBuilder { segment_to_line[edge_right] = new_interval; segment_to_line[right_segment] = new_interval; + if (interval->polygons_list.size() == 1) { + auto *new_polygon = + new MonotonePolygon(interval->polygons_list[0]->right.back()); + new_polygon->left.push_back(p); + new_interval->polygons_list.push_back(new_polygon); + interval->polygons_list[0]->right.push_back(p); + } + + if (interval->polygons_list.size() >= 2) { + interval->polygons_list[0]->right.push_back(p); + interval->polygons_list[interval->polygons_list.size() - 1] + ->left.push_back(p); + for (auto i = 1; i < interval->polygons_list.size() - 1; i++) { + interval->polygons_list[i]->bottom = p; + monotone_polygons.push_back(*interval->polygons_list[i]); + delete interval->polygons_list[i]; + } + new_interval->polygons_list.push_back(interval->polygons_list.back()); + interval->polygons_list.erase(interval->polygons_list.begin() + 1, + interval->polygons_list.end()); + } return; } } @@ -440,7 +413,7 @@ struct MonotonePolygonBuilder { }; void process_intersection_point(const point::Point &p) { - + throw std::runtime_error("Intersection points are not supported"); }; }; diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index c962a11..ca004dd 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -149,10 +149,16 @@ def test_triangulate_polygon_py_convex(polygon, expected): assert triangulate_polygon_py(polygon)[0] == expected -@pytest.mark.parametrize(('polygon', 'expected'), [([(0, 0), (1, 1), (0, 2), (2, 1)], [(0, 1, 2), (0, 3, 2)])]) -@pytest.mark.xfail(reason='Not implemented') +def _renumerate_triangles(polygon, points, triangles): + point_num = {point: i for i, point in enumerate(polygon)} + return [tuple(point_num[points[point]] for point in triangle) for triangle in triangles] + + +@pytest.mark.parametrize(('polygon', 'expected'), [([(0, 0), (1, 1), (0, 2), (2, 1)], [(3, 2, 1), (0, 3, 1)])]) def test_triangulate_polygon_py_non_convex(polygon, expected): - assert triangulate_polygon_py(polygon)[0] == expected + triangles, points = triangulate_polygon_py(polygon) + triangles_ = _renumerate_triangles(polygon, points, triangles) + assert triangles_ == expected @pytest.mark.parametrize( From 4244cea46393b7108bd1c094a15962777b43f40e Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 31 Oct 2024 00:41:24 +0100 Subject: [PATCH 45/76] add basic test and fix left-right detection --- CMakeLists.txt | 2 +- .../triangulate.pyx | 57 +++++++- .../triangulation/point.hpp | 2 +- .../triangulation/triangulate.hpp | 136 ++++++++++++++---- src/tests/test_triangulate.py | 47 +++++- 5 files changed, 204 insertions(+), 40 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2473c10..1b99692 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.21) project(${SKBUILD_PROJECT_NAME} LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_FLAGS_DEBUG_INIT "-DDEBUG") # Define compiler directive diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index 057c252..c50ec32 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -57,9 +57,9 @@ cdef extern from "triangulation/intersection.hpp" namespace "partsegcore::inters cdef extern from "triangulation/triangulate.hpp" namespace "partsegcore::triangulation": cdef cppclass Triangle: - int x - int y - int z + size_t x + size_t y + size_t z Triangle() Triangle(int x, int y, int z) @@ -84,6 +84,7 @@ cdef extern from "triangulation/triangulate.hpp" namespace "partsegcore::triangu vector[Point] find_intersection_points(const vector[Point]& segments) vector[PointTriangle] triangulate_monotone_polygon(const MonotonePolygon& polygon) pair[vector[Triangle], vector[Point]] triangulate_polygon(const vector[Point]& polygon) + pair[vector[Triangle], vector[Point]] triangulate_polygon(const vector[vector[Point]]& polygon_list) @@ -240,6 +241,56 @@ def triangulate_polygon_py(polygon: Sequence[Sequence[float]]) -> tuple[list[tup return [(triangle.x, triangle.y, triangle.z) for triangle in result.first], [(point.x, point.y) for point in result.second] +def triangulate_polygon_numpy(polygon: np.ndarray) -> tuple[np.ndarray, np.ndarray]: + """ Triangulate polygon""" + cdef vector[Point] polygon_vector + cdef Point p1, p2 + cdef pair[vector[Triangle], vector[Point]] result + + polygon_vector.reserve(polygon.shape[0]) + polygon_vector.push_back(Point(polygon[0, 0], polygon[0, 1])) + for point in polygon[1:]: + p1 = polygon_vector[polygon_vector.size() - 1] + p2 = Point(point[0], point[1]) + if p1 != p2: + # prevent from adding polygon edge of width 0 + polygon_vector.push_back(p2) + + result = triangulate_polygon(polygon_vector) + return ( + np.array([(triangle.x, triangle.y, triangle.z) for triangle in result.first], dtype=np.uintp), + np.array([(point.x, point.y) for point in result.second], dtype=np.float32) + ) + + +def triangulate_polygon_numpy_li(polygon_li: list[np.ndarray]) -> tuple[np.ndarray, np.ndarray]: + """ Triangulate polygon""" + cdef vector[Point] polygon_vector + cdef vector[vector[Point]] polygon_vector_list + cdef Point p1, p2 + cdef pair[vector[Triangle], vector[Point]] result + + polygon_vector_list.reserve(len(polygon_li)) + for polygon in polygon_li: + polygon_vector.clear() + + polygon_vector.reserve(polygon.shape[0]) + polygon_vector.push_back(Point(polygon[0, 0], polygon[0, 1])) + + for point in polygon[1:]: + p1 = polygon_vector[polygon_vector.size() - 1] + p2 = Point(point[0], point[1]) + if p1 != p2: + # prevent from adding polygon edge of width 0 + polygon_vector.push_back(p2) + polygon_vector_list.push_back(polygon_vector) + + result = triangulate_polygon(polygon_vector_list) + return ( + np.array([(triangle.x, triangle.y, triangle.z) for triangle in result.first], dtype=np.uintp), + np.array([(point.x, point.y) for point in result.second], dtype=np.float32) + ) + def segment_left_to_right_comparator(s1: Sequence[Sequence[float]], s2: Sequence[Sequence[float]]) -> bool: """ Compare segments by bottom point""" return left_to_right( diff --git a/src/PartSegCore_compiled_backend/triangulation/point.hpp b/src/PartSegCore_compiled_backend/triangulation/point.hpp index f67021c..1f21db9 100644 --- a/src/PartSegCore_compiled_backend/triangulation/point.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/point.hpp @@ -46,8 +46,8 @@ struct Point { /*Struct to represent edge of polygon with points ordered*/ struct Segment { - Point bottom{}; Point top{}; + Point bottom{}; Segment(Point p1, Point p2) { if (p1 < p2) { bottom = p1; diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index bd4d2d2..841e9b3 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -16,7 +16,7 @@ namespace partsegcore { namespace triangulation { -enum PointType { NORMAL, SPLIT, MERGE, INTERSECTION }; +enum PointType { NORMAL, SPLIT, MERGE, INTERSECTION, EMPTY }; struct MonotonePolygon { point::Point top{}; @@ -205,6 +205,7 @@ PointToEdges get_points_edges(const std::vector &edges) { * @return The type of the point as determined by its adjacent edges. */ PointType get_point_type(point::Point p, PointToEdges &point_to_edges) { + if (point_to_edges.at(p).empty()) return PointType::EMPTY; if (point_to_edges.at(p).size() != 2) return PointType::INTERSECTION; const auto &edges = point_to_edges.at(p); @@ -227,6 +228,25 @@ struct MonotonePolygonBuilder { this->point_to_edges = get_points_edges(edges); } + std::pair + get_left_right_edges(const point::Point &p) { + auto point_info = point_to_edges.at(p); + auto fst_idx = point_info[0].edge_index; + auto snd_idx = point_info[1].edge_index; + if (edges[fst_idx].top == edges[snd_idx].top) { + if (intersection::_orientation(edges[fst_idx].bottom, edges[fst_idx].top, + edges[snd_idx].bottom) == 2) { + return {edges[snd_idx], edges[fst_idx]}; + } + } + if (intersection::_orientation(edges[fst_idx].top, edges[fst_idx].bottom, + edges[snd_idx].top) == 1) { + return {edges[snd_idx], edges[fst_idx]}; + } + + return {edges[fst_idx], edges[snd_idx]}; + } + /** * Processes the end point of a segment within a monotone polygon. * @@ -241,11 +261,9 @@ struct MonotonePolygonBuilder { * It also handles the cleanup of relevant mappings and data structures * when processing is complete. */ + void process_end_point(const point::Point &p) { - const point::Segment &edge_left = - edges[point_to_edges.at(p).at(0).edge_index]; - const point::Segment &edge_right = - edges[point_to_edges.at(p).at(1).edge_index]; + auto [edge_left, edge_right] = get_left_right_edges(p); Interval *interval = segment_to_line.at(edge_left); @@ -261,19 +279,18 @@ struct MonotonePolygonBuilder { } void process_merge_point(const point::Point &p) { - const point::Segment &edge_left = - edges[point_to_edges.at(p).at(0).edge_index]; - const point::Segment &edge_right = - edges[point_to_edges.at(p).at(1).edge_index]; + auto [edge_left, edge_right] = get_left_right_edges(p); + if (segment_to_line.at(edge_left) != segment_to_line.at(edge_right)) { // merge two intervals into one Interval *left_interval = segment_to_line.at(edge_left); Interval *right_interval = segment_to_line.at(edge_right); - segment_to_line.erase(edge_left); segment_to_line.erase(edge_right); left_interval->right_segment = right_interval->right_segment; segment_to_line[right_interval->right_segment] = left_interval; left_interval->last_seen = p; + left_interval->polygons_list.back()->right.push_back(p); + right_interval->polygons_list.front()->left.push_back(p); for (auto &polygon : right_interval->polygons_list) { left_interval->polygons_list.push_back(polygon); } @@ -341,10 +358,7 @@ struct MonotonePolygonBuilder { }; void process_start_point(const point::Point &p) { - const point::Segment &edge_left = - edges[point_to_edges.at(p).at(0).edge_index]; - const point::Segment &edge_right = - edges[point_to_edges.at(p).at(1).edge_index]; + auto [edge_left, edge_right] = get_left_right_edges(p); auto *new_polygon = new MonotonePolygon(p); auto *interval = new Interval(p, edge_left, edge_right, new_polygon); @@ -353,10 +367,7 @@ struct MonotonePolygonBuilder { } void process_split_point(const point::Point &p) { - const point::Segment &edge_left = - edges[point_to_edges.at(p).at(0).edge_index]; - const point::Segment &edge_right = - edges[point_to_edges.at(p).at(1).edge_index]; + auto [edge_left, edge_right] = get_left_right_edges(p); // We need to find to which interval the point belongs. // If the point does not belong to any interval, we treat @@ -385,8 +396,13 @@ struct MonotonePolygonBuilder { segment_to_line[right_segment] = new_interval; if (interval->polygons_list.size() == 1) { - auto *new_polygon = - new MonotonePolygon(interval->polygons_list[0]->right.back()); + MonotonePolygon *new_polygon = nullptr; + if (interval->polygons_list[0]->right.empty()) { + new_polygon = new MonotonePolygon(interval->polygons_list[0]->top); + } else { + new_polygon = + new MonotonePolygon(interval->polygons_list[0]->right.back()); + } new_polygon->left.push_back(p); new_interval->polygons_list.push_back(new_polygon); interval->polygons_list[0]->right.push_back(p); @@ -580,6 +596,8 @@ std::vector triangulate_monotone_polygon( std::vector stack; std::vector> points; + result.reserve(polygon.left.size() + polygon.right.size()); + points.reserve(polygon.left.size() + polygon.right.size() + 2); points.emplace_back(polygon.top, Side::TOP_OR_BOTTOM); while (left_index < polygon.left.size() && @@ -639,6 +657,22 @@ std::vector calc_edges( return edges; } +/** + * Calculates the edges of polygons from a list of polygons, provided as + * a list of points for each polygon. + * + * Each polygon is represented by a vector of points, where each point is + * defined as an instance of `point::Point`. The function iterates through + * each point in each polygon and creates edges (segments) between + * consecutive points as well as between the last point and the first point + * of each polygon. + * + * @param polygon_list A vector of polygons, with each polygon represented + * as a vector of `point::Point` instances. + * + * @return A vector of `point::Segment` instances representing the edges of + * all polygons in the input list. + */ std::vector calc_edges( const std::vector> &polygon_list) { std::vector edges; @@ -656,6 +690,45 @@ std::vector calc_edges( return edges; } +/** + * Calculates and returns a list of unique (deduplicated) edges from a list of + * polygons. + * + * This function receives a list of polygons, where each polygon is defined by a + * series of points. It identifies the edges of the polygons and returns those + * edges that are unique (i.e., edges that appear an odd number of times across + * all polygons). Any edge that appears an even number of times is considered to + * be a duplicate and is removed. + * + * @param polygon_list A reference to a vector containing vectors of points, + * where each inner vector represents a polygon. + * @return A vector of unique edges (of type point::Segment) that are not + * duplicated across the polygons. + */ +std::vector calc_dedup_edges( + const std::vector> &polygon_list) { + std::set edges_set; + point::Segment edge; + + for (const auto &polygon : polygon_list) { + for (std::size_t i = 0; i < polygon.size() - 1; i++) { + edge = point::Segment(polygon[i], polygon[i + 1]); + if (edges_set.count(edge) == 0) { + edges_set.insert(edge); + } else { + edges_set.erase(edge); + } + } + edge = point::Segment(polygon[polygon.size() - 1], polygon[0]); + if (edges_set.count(edge) == 0) { + edges_set.insert(edge); + } else { + edges_set.erase(edge); + } + } + return {edges_set.begin(), edges_set.end()}; +} + /** * @brief Finds intersection points in a polygon and adds mid-points for all * intersections. @@ -703,16 +776,15 @@ std::vector> find_intersection_points( if (new_polygon[new_polygon.size() - 1] != point) new_polygon.push_back(point); if (intersections_points.count(i)) { - auto new_points = intersections_points[i]; + auto &new_points = intersections_points[i]; if (new_points[0] == point) { - for (std::size_t j = 1; j < new_points.size() - 1; j++) { - if (new_polygon[new_polygon.size() - 1] != new_points[j]) - new_polygon.push_back(new_points[j]); + for (auto it = new_points.begin() + 1; it != new_points.end(); it++) { + if (new_polygon.back() != *it) new_polygon.push_back(*it); } } else { - for (std::size_t j = new_points.size() - 2; j > 0; j++) { - if (new_polygon[new_polygon.size() - 1] != new_points[j]) - new_polygon.push_back(new_points[j]); + for (auto it = new_points.rbegin() + 1; it != new_points.rend(); + it++) { + if (new_polygon.back() != *it) new_polygon.push_back(*it); } } } @@ -757,7 +829,7 @@ std::pair, std::vector> sweeping_line_triangulation( const std::vector> &polygon_list) { std::vector result; - auto edges = calc_edges(polygon_list); + auto edges = calc_dedup_edges(polygon_list); MonotonePolygonBuilder builder(edges); PointToEdges point_to_edges = get_points_edges(edges); @@ -765,6 +837,8 @@ sweeping_line_triangulation( std::vector sorted_points = _sorted_polygons_points(polygon_list); + result.reserve(sorted_points.size() - 2); + for (auto &sorted_point : sorted_points) { auto point_type = get_point_type(sorted_point, point_to_edges); switch (point_type) { @@ -792,9 +866,13 @@ sweeping_line_triangulation( // Remember about more than 4 edges case builder.process_intersection_point(sorted_point); break; + case PointType::EMPTY: + // this is a point without edges (removed by deduplication) + break; } } - std::map point_to_index; + std::unordered_map point_to_index; + point_to_index.reserve(sorted_points.size()); for (std::size_t i = 0; i < sorted_points.size(); i++) { point_to_index[sorted_points[i]] = i; } diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index ca004dd..f1ba7d0 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -1,3 +1,4 @@ +import numpy as np import pytest from PartSegCore_compiled_backend.triangulate import ( @@ -11,6 +12,8 @@ segment_left_to_right_comparator, triangle_convex_polygon, triangulate_monotone_polygon_py, + triangulate_polygon_numpy, + triangulate_polygon_numpy_li, triangulate_polygon_py, ) @@ -99,10 +102,6 @@ def test_find_intersections_param(segments, expected): assert set(find_intersections(segments)) == set(expected) -# def test_find_intersection_point(): -# assert find_intersection_point(((0, 0), (2, 2)), ((0, 2), (2, 0))) == (1, 1) - - @pytest.mark.parametrize( ('segment1', 'segment2', 'expected'), [ @@ -151,16 +150,52 @@ def test_triangulate_polygon_py_convex(polygon, expected): def _renumerate_triangles(polygon, points, triangles): point_num = {point: i for i, point in enumerate(polygon)} - return [tuple(point_num[points[point]] for point in triangle) for triangle in triangles] + return [tuple(point_num[tuple(points[point])] for point in triangle) for triangle in triangles] + + +TEST_POLYGONS = [ + ([(0, 0), (1, 1), (0, 2), (2, 1)], [(3, 2, 1), (0, 3, 1)]), + ([(0, 0), (0, 1), (1, 2), (2, 1), (2, 0), (1, 0.5)], [(4, 3, 5), (3, 2, 1), (5, 3, 1), (5, 1, 0)]), + ([(0, 1), (0, 2), (1, 1.5), (2, 2), (2, 1), (1, 0.5)], [(4, 3, 2), (2, 1, 0), (4, 2, 0), (5, 4, 0)]), + ([(0, 1), (0, 2), (1, 0.5), (2, 2), (2, 1), (1, -0.5)], [(2, 1, 0), (2, 0, 5), (4, 3, 2), (5, 4, 2)]), + ([(0, 0), (1, 2), (2, 0), (1, 1)], [(2, 1, 3), (3, 1, 0)]), +] -@pytest.mark.parametrize(('polygon', 'expected'), [([(0, 0), (1, 1), (0, 2), (2, 1)], [(3, 2, 1), (0, 3, 1)])]) +@pytest.mark.parametrize(('polygon', 'expected'), TEST_POLYGONS) def test_triangulate_polygon_py_non_convex(polygon, expected): triangles, points = triangulate_polygon_py(polygon) + assert len(triangles) == len(polygon) - 2 + triangles_ = _renumerate_triangles(polygon, points, triangles) + assert triangles_ == expected + + +@pytest.mark.parametrize(('polygon', 'expected'), TEST_POLYGONS) +def test_triangulate_polygon_numpy_non_convex(polygon, expected): + triangles, points = triangulate_polygon_numpy(np.array(polygon)) + assert len(triangles) == len(polygon) - 2 triangles_ = _renumerate_triangles(polygon, points, triangles) assert triangles_ == expected +@pytest.mark.parametrize(('polygon', 'expected'), TEST_POLYGONS) +def test_triangulate_polygon_numpy_li_non_convex(polygon, expected): + triangles, points = triangulate_polygon_numpy_li([np.array(polygon)]) + assert len(triangles) == len(polygon) - 2 + triangles_ = _renumerate_triangles(polygon, points, triangles) + assert triangles_ == expected + + +def test_triangulate_polygon_in_polygon_numpy(): + polygons = [ + np.array([(0, 0), (10, 0), (10, 10), (0, 10)]), + np.array([(4, 4), (6, 4), (6, 6), (4, 6)]), + ] + triangles, points = triangulate_polygon_numpy_li(polygons) + assert len(triangles) == 8 + assert len(points) == 8 + + @pytest.mark.parametrize( ('segment1', 'segment2'), [ From 1de7ab73a758faa81ab11fc02e60b358e980eaf7 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 31 Oct 2024 17:02:09 +0100 Subject: [PATCH 46/76] add intersection points --- .../triangulation/point.hpp | 3 + .../triangulation/triangulate.hpp | 167 +++++++++++++++--- src/tests/test_triangulate.py | 5 +- 3 files changed, 146 insertions(+), 29 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulation/point.hpp b/src/PartSegCore_compiled_backend/triangulation/point.hpp index 1f21db9..1ed3b02 100644 --- a/src/PartSegCore_compiled_backend/triangulation/point.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/point.hpp @@ -49,6 +49,9 @@ struct Segment { Point top{}; Point bottom{}; Segment(Point p1, Point p2) { + if (p1 == p2) { + throw std::invalid_argument("Segment cannot have two identical points"); + } if (p1 < p2) { bottom = p1; top = p2; diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index 841e9b3..b106471 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -62,6 +63,16 @@ struct Interval { } throw std::runtime_error("Segment not found in interval"); }; + [[nodiscard]] point::Segment opposite_segment( + const point::Segment &segment) const { + if (segment == left_segment) { + return right_segment; + } + if (segment == right_segment) { + return left_segment; + } + throw std::runtime_error("Segment not found in interval"); + }; }; /** @@ -106,6 +117,15 @@ bool left_to_right(const point::Segment &s1, const point::Segment &s2) { return intersection::_orientation(s1.top, s1.bottom, s2.top) == 2; } +bool left_right_share_top(const point::Segment &s1, const point::Segment &s2) { + return intersection::_orientation(s1.bottom, s1.top, s2.bottom) == 1; +} + +bool left_right_share_bottom(const point::Segment &s1, + const point::Segment &s2) { + return intersection::_orientation(s1.top, s1.bottom, s2.top) == 2; +} + struct SegmentLeftRightComparator { bool operator()(const point::Segment &s1, const point::Segment &s2) const { return left_to_right(s1, s2); @@ -150,17 +170,18 @@ struct PointTriangle { typedef std::size_t EdgeIndex; -struct PointEdges { +struct PointEdgeInfo { EdgeIndex edge_index; point::Point opposite_point; - PointEdges(EdgeIndex edge_index, point::Point opposite_point) + PointEdgeInfo(EdgeIndex edge_index, point::Point opposite_point) : edge_index(edge_index), opposite_point(opposite_point) {} - bool operator<(const PointEdges &e) const { + bool operator<(const PointEdgeInfo &e) const { return opposite_point < e.opposite_point; } }; -typedef std::unordered_map> PointToEdges; +typedef std::unordered_map> + PointToEdges; typedef std::map SegmentToLine; @@ -180,7 +201,7 @@ PointToEdges get_points_edges(const std::vector &edges) { } for (auto &point_to_edge : point_to_edges) { std::sort(point_to_edge.second.begin(), point_to_edge.second.end(), - [](const PointEdges &a, const PointEdges &b) { + [](const PointEdgeInfo &a, const PointEdgeInfo &b) { return b.opposite_point < a.opposite_point; }); } @@ -262,9 +283,8 @@ struct MonotonePolygonBuilder { * when processing is complete. */ - void process_end_point(const point::Point &p) { - auto [edge_left, edge_right] = get_left_right_edges(p); - + void process_end_point(const point::Point &p, const point::Segment &edge_left, + const point::Segment &edge_right) { Interval *interval = segment_to_line.at(edge_left); for (auto &polygon : interval->polygons_list) { @@ -297,28 +317,33 @@ struct MonotonePolygonBuilder { delete right_interval; } else { // This is the end point - this->process_end_point(p); + this->process_end_point(p, edge_left, edge_right); } }; /** * Processes a normal point within a monotone polygon. * - * @param p The normal point to be processed. + * @param p The point to be processed. + * @param edge_top The top edge segment associated with the point. + * @param edge_bottom The bottom edge segment associated with the point. + * + * This function updates the monotone polygon interval associated with + * `edge_top` to include the new point `p`. If the interval contains + * multiple polygons, it closes all but one of them by setting their + * bottom to `p` and moving them to the list of completed monotone polygons. + * + * Depending on whether `edge_top` represents the right segment of the + * interval, the function then adds the point `p` to the appropriate (left + * or right) list of points in the remaining polygon and replaces `edge_top` + * with `edge_bottom` in the segment-to-line map. * - * The function identifies two edges associated with the given point and - * retrieves the corresponding segments. It then finds the interval and - * associated monotone polygons based on the edges. If the last seen point in - * the interval is a merge point, it updates and stores the completed monotone - * polygon. The function also updates the right and left polygons and handles - * segment replacements along with interval mappings, ensuring the data - * structures are correctly updated. + * The function throws a `std::runtime_error` if `edge_top` is not found in + * the `segment_to_line` map. */ - void process_normal_point(const point::Point &p) { - const point::Segment &edge_top = - edges[point_to_edges.at(p).at(0).edge_index]; - const point::Segment &edge_bottom = - edges[point_to_edges.at(p).at(1).edge_index]; + void _process_normal_point(const point::Point &p, + const point::Segment &edge_top, + const point::Segment &edge_bottom) { if (segment_to_line.count(edge_top) == 0) { throw std::runtime_error("Segment not found in the map"); } @@ -357,9 +382,28 @@ struct MonotonePolygonBuilder { segment_to_line.erase(edge_top); }; - void process_start_point(const point::Point &p) { - auto [edge_left, edge_right] = get_left_right_edges(p); + /** + * Processes a normal point within a monotone polygon. + * + * @param p The point to be processed. + * + * This function identifies the two segments (top and bottom edges) + * associated with the point `p` using its index in the `point_to_edges` map. + * It then calls the helper method `_process_normal_point` + * with the identified segments to handle the actual processing logic. + */ + void process_normal_point(const point::Point &p) { + const point::Segment &edge_top = + edges[point_to_edges.at(p).at(0).edge_index]; + const point::Segment &edge_bottom = + edges[point_to_edges.at(p).at(1).edge_index]; + + _process_normal_point(p, edge_top, edge_bottom); + } + void process_start_point(const point::Point &p, + const point::Segment &edge_left, + const point::Segment &edge_right) { auto *new_polygon = new MonotonePolygon(p); auto *interval = new Interval(p, edge_left, edge_right, new_polygon); segment_to_line[edge_left] = interval; @@ -425,11 +469,71 @@ struct MonotonePolygonBuilder { } } // the point is not inside any interval - this->process_start_point(p); + this->process_start_point(p, edge_left, edge_right); }; void process_intersection_point(const point::Point &p) { - throw std::runtime_error("Intersection points are not supported"); + auto point_info = point_to_edges.at(p); + std::set processed_segments; + std::vector segments_to_normal_process; + std::vector + top_segments; // segments above the intersection point + std::vector + bottom_segments; // segments below the intersection point + + for (auto &edge_info : point_info) { + auto &edge = edges[edge_info.edge_index]; + if (processed_segments.count(edge) > 0) { + continue; + } + if (segment_to_line.count(edge) > 0) { + Interval *interval = segment_to_line.at(edge); + auto opposite_edge = interval->opposite_segment(edge); + if (edge.bottom == p && opposite_edge.bottom == p) { + process_end_point(p, edge, opposite_edge); + processed_segments.insert(edge); + processed_segments.insert(opposite_edge); + continue; + } + } + if (edge.top == p) { + bottom_segments.push_back(edge); + } else { + top_segments.push_back(edge); + } + segments_to_normal_process.push_back(edge); + } + + // after this loop we should have at most 4 segments + + if (bottom_segments.empty() && top_segments.empty()) { + // all segments were processed + return; + } + + std::sort(top_segments.begin(), top_segments.end(), + left_right_share_bottom); + std::sort(bottom_segments.begin(), bottom_segments.end(), + left_right_share_top); + + auto bottom_begin = bottom_segments.begin(); + auto bottom_end = bottom_segments.end(); + if (!top_segments.empty()) { + if (top_segments.front() == + segment_to_line.at(top_segments.front())->right_segment) { + bottom_begin++; + _process_normal_point(p, top_segments.front(), bottom_segments.front()); + } + if (top_segments.back() == + segment_to_line.at(top_segments.back())->left_segment) { + bottom_end--; + _process_normal_point(p, top_segments.back(), bottom_segments.back()); + } + } + + for (auto it = bottom_begin; it < bottom_end; it += 2) { + process_start_point(p, *it, *(it + 1)); + } }; }; @@ -789,6 +893,8 @@ std::vector> find_intersection_points( } } } + if (new_polygon.size() > 1 && new_polygon.front() == new_polygon.back()) + new_polygon.pop_back(); new_polygons_list.push_back(new_polygon); } return new_polygons_list; @@ -925,8 +1031,17 @@ std::pair, std::vector> triangulate_polygon( std::pair, std::vector> triangulate_polygon( const std::vector &polygon_list) { + // try{ return triangulate_polygon( std::vector>({polygon_list})); + // } catch (const std::exception &e) { + // std::cerr << "Polygon: ["; + // for (const auto &point : polygon_list) { + // std::cerr << "(" << point.x << ", " << point.y << "), "; + // } + // std::cerr << "]" << std::endl; + // throw e; + // } } } // namespace triangulation diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index f1ba7d0..0721dee 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -159,13 +159,14 @@ def _renumerate_triangles(polygon, points, triangles): ([(0, 1), (0, 2), (1, 1.5), (2, 2), (2, 1), (1, 0.5)], [(4, 3, 2), (2, 1, 0), (4, 2, 0), (5, 4, 0)]), ([(0, 1), (0, 2), (1, 0.5), (2, 2), (2, 1), (1, -0.5)], [(2, 1, 0), (2, 0, 5), (4, 3, 2), (5, 4, 2)]), ([(0, 0), (1, 2), (2, 0), (1, 1)], [(2, 1, 3), (3, 1, 0)]), + ([(0, 0), (0, 1), (0.5, 0.5), (1, 0), (1, 1)], [(3, 4, 2), (2, 1, 0)]), + ([(0, 0), (1, 0), (0.5, 0.5), (0, 1), (1, 1)], [(2, 4, 3), (1, 2, 0)]), ] @pytest.mark.parametrize(('polygon', 'expected'), TEST_POLYGONS) def test_triangulate_polygon_py_non_convex(polygon, expected): triangles, points = triangulate_polygon_py(polygon) - assert len(triangles) == len(polygon) - 2 triangles_ = _renumerate_triangles(polygon, points, triangles) assert triangles_ == expected @@ -173,7 +174,6 @@ def test_triangulate_polygon_py_non_convex(polygon, expected): @pytest.mark.parametrize(('polygon', 'expected'), TEST_POLYGONS) def test_triangulate_polygon_numpy_non_convex(polygon, expected): triangles, points = triangulate_polygon_numpy(np.array(polygon)) - assert len(triangles) == len(polygon) - 2 triangles_ = _renumerate_triangles(polygon, points, triangles) assert triangles_ == expected @@ -181,7 +181,6 @@ def test_triangulate_polygon_numpy_non_convex(polygon, expected): @pytest.mark.parametrize(('polygon', 'expected'), TEST_POLYGONS) def test_triangulate_polygon_numpy_li_non_convex(polygon, expected): triangles, points = triangulate_polygon_numpy_li([np.array(polygon)]) - assert len(triangles) == len(polygon) - 2 triangles_ = _renumerate_triangles(polygon, points, triangles) assert triangles_ == expected From 298db175f6d60f852b0bb1d762e7e553f81e742a Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 31 Oct 2024 17:47:17 +0100 Subject: [PATCH 47/76] fix point repetition bugs --- .../triangulation/point.hpp | 6 +++- .../triangulation/triangulate.hpp | 35 +++++++++++-------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulation/point.hpp b/src/PartSegCore_compiled_backend/triangulation/point.hpp index 1ed3b02..4beae19 100644 --- a/src/PartSegCore_compiled_backend/triangulation/point.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/point.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -50,7 +51,10 @@ struct Segment { Point bottom{}; Segment(Point p1, Point p2) { if (p1 == p2) { - throw std::invalid_argument("Segment cannot have two identical points"); + throw std::invalid_argument( + (std::ostringstream() + << "Segment cannot have two identical points: " << p1) + .str()); } if (p1 < p2) { bottom = p1; diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index b106471..85d43f8 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -226,6 +226,7 @@ PointToEdges get_points_edges(const std::vector &edges) { * @return The type of the point as determined by its adjacent edges. */ PointType get_point_type(point::Point p, PointToEdges &point_to_edges) { + if (point_to_edges.count(p) == 0) return PointType::EMPTY; if (point_to_edges.at(p).empty()) return PointType::EMPTY; if (point_to_edges.at(p).size() != 2) return PointType::INTERSECTION; @@ -789,7 +790,8 @@ std::vector calc_edges( for (std::size_t i = 0; i < polygon.size() - 1; i++) { edges.emplace_back(polygon[i], polygon[i + 1]); } - edges.emplace_back(polygon[polygon.size() - 1], polygon[0]); + if (polygon.back() != polygon.front()) + edges.emplace_back(polygon.back(), polygon.front()); } return edges; } @@ -823,7 +825,8 @@ std::vector calc_dedup_edges( edges_set.erase(edge); } } - edge = point::Segment(polygon[polygon.size() - 1], polygon[0]); + if (polygon.back() == polygon.front()) continue; + edge = point::Segment(polygon.back(), polygon.front()); if (edges_set.count(edge) == 0) { edges_set.insert(edge); } else { @@ -1030,18 +1033,22 @@ std::pair, std::vector> triangulate_polygon( } std::pair, std::vector> triangulate_polygon( - const std::vector &polygon_list) { - // try{ - return triangulate_polygon( - std::vector>({polygon_list})); - // } catch (const std::exception &e) { - // std::cerr << "Polygon: ["; - // for (const auto &point : polygon_list) { - // std::cerr << "(" << point.x << ", " << point.y << "), "; - // } - // std::cerr << "]" << std::endl; - // throw e; - // } + const std::vector &polygon) { + // #if DDEBUG + // try{ + // #endif + return triangulate_polygon(std::vector>({polygon})); + // #if DDEBUG + // } catch (const std::exception &e) { + // std::cerr << "Polygon: ["; + // for (const auto &point : polygon) { + // std::cerr << "(" << point.x << ", " << point.y << "), "; + // } + // std::cerr << "]" << std::endl; + // std::cerr << "Error: " << e.what() << std::endl; + // throw e; + // } + // #endif } } // namespace triangulation From 48e27b407bf31d9ebb6a7f7a8c19085850d4472e Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Mon, 18 Nov 2024 13:25:40 +0100 Subject: [PATCH 48/76] add problematic test --- CMakeLists.txt | 4 +++- src/tests/test_triangulate.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b99692..7feecf3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,9 @@ cmake_minimum_required(VERSION 3.21) project(${SKBUILD_PROJECT_NAME} LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_FLAGS_DEBUG_INIT "-DDEBUG") +set(CMAKE_CXX_FLAGS_DEBUG_INIT "-DDEBUG -g") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -DDEBUG") + # Define compiler directive add_definitions(-DNPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION) diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index 0721dee..c8aca2c 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -193,6 +193,37 @@ def test_triangulate_polygon_in_polygon_numpy(): triangles, points = triangulate_polygon_numpy_li(polygons) assert len(triangles) == 8 assert len(points) == 8 + + +def test_triangulate_polygon_segfault1(): + """Test on polygon that lead to segfault during test""" + polygon = [ + (205.0625, 1489.83752), + (204.212509, 1490.4751), + (204, 1491.11255), + (202.087509, 1493.45007), + (201.875, 1494.7251), + (202.300003, 1496), + (202.300003, 1498.33752), + (203.575012, 1499.82507), + (204.425003, 1500.25), + (205.0625, 1500.25), + (205.700012, 1500.67505), + (206.550003, 1500.67505), + (207.1875, 1500.25), + (208.037506, 1500.88757), + (209.3125, 1499.82507), + (209.525009, 1499.1875), + (211.012512, 1497.70007), + (210.375, 1496.42505), + (209.525009, 1495.57507), + (208.462509, 1495.15002), + (208.675003, 1494.9375), + (208.462509, 1492.8125), + (208.037506, 1491.5376), + (205.912506, 1489.83752), + ] + triangles, points = triangulate_polygon_py(polygon) @pytest.mark.parametrize( From bb5ce5dce0765654edd2aa88cb9ccc397cdde519 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Mon, 18 Nov 2024 19:03:38 +0100 Subject: [PATCH 49/76] fix macos specific bug --- pyproject.toml | 2 +- .../triangulate.pyx | 4 ++-- .../triangulation/intersection.hpp | 9 ++++++--- .../triangulation/triangulate.hpp | 19 +++++++++++++++++-- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index be35b7c..0f632db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ sdist.include = ["src/PartSegCore_compiled_backend/version.py"] cmake.version = ">=3.21" sdist.exclude = [".github", "tox.ini", "build_utils", "notebooks", ".readthedocs.yaml"] wheel.exclude = ["**.pyx"] -#cmake.build-type = "Debug" +cmake.build-type = "Debug" [project] diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index c50ec32..d303c9c 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -83,8 +83,8 @@ cdef extern from "triangulation/triangulate.hpp" namespace "partsegcore::triangu bool left_to_right(const Segment& s1, const Segment& s2) vector[Point] find_intersection_points(const vector[Point]& segments) vector[PointTriangle] triangulate_monotone_polygon(const MonotonePolygon& polygon) - pair[vector[Triangle], vector[Point]] triangulate_polygon(const vector[Point]& polygon) - pair[vector[Triangle], vector[Point]] triangulate_polygon(const vector[vector[Point]]& polygon_list) + pair[vector[Triangle], vector[Point]] triangulate_polygon(const vector[Point]& polygon) except + + pair[vector[Triangle], vector[Point]] triangulate_polygon(const vector[vector[Point]]& polygon_list) except + diff --git a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp index 4cb2975..9e50dc6 100644 --- a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp @@ -126,9 +126,12 @@ bool _on_segment(const point::Point &p, const point::Point &q, */ int _orientation(const point::Point &p, const point::Point &q, const point::Point &r) { - float val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y); - if (val == 0) return 0; - return (val > 0) ? 1 : 2; + double val1 = ((q.y - p.y) * (r.x - q.x)); + double val2 = ((r.y - q.y) * (q.x - p.x)); + // Instead of using classical equation, we need to use two variables + // to handle problem with strange behaviour on macOs. + if (val1 == val2) return 0; + return (val1 > val2) ? 1 : 2; } /** diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index 85d43f8..3761c0a 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -260,6 +260,7 @@ struct MonotonePolygonBuilder { edges[snd_idx].bottom) == 2) { return {edges[snd_idx], edges[fst_idx]}; } + return {edges[fst_idx], edges[snd_idx]}; } if (intersection::_orientation(edges[fst_idx].top, edges[fst_idx].bottom, edges[snd_idx].top) == 1) { @@ -287,7 +288,6 @@ struct MonotonePolygonBuilder { void process_end_point(const point::Point &p, const point::Segment &edge_left, const point::Segment &edge_right) { Interval *interval = segment_to_line.at(edge_left); - for (auto &polygon : interval->polygons_list) { polygon->bottom = p; monotone_polygons.push_back(*polygon); @@ -307,7 +307,13 @@ struct MonotonePolygonBuilder { Interval *left_interval = segment_to_line.at(edge_left); Interval *right_interval = segment_to_line.at(edge_right); segment_to_line.erase(edge_right); + segment_to_line.erase(edge_left); left_interval->right_segment = right_interval->right_segment; +#ifdef DEBUG + if (segment_to_line.count(right_interval->right_segment) == 0) { + throw std::runtime_error("Segment not found in the map"); + } +#endif segment_to_line[right_interval->right_segment] = left_interval; left_interval->last_seen = p; left_interval->polygons_list.back()->right.push_back(p); @@ -381,6 +387,14 @@ struct MonotonePolygonBuilder { interval->last_seen = p; interval->replace_segment(edge_top, edge_bottom); segment_to_line.erase(edge_top); + // #ifdef DEBUG + if (segment_to_line.count(interval->left_segment) == 0) { + throw std::runtime_error("Left segment not found in the map"); + } + if (segment_to_line.count(interval->right_segment) == 0) { + throw std::runtime_error("Right segment not found in the map"); + } + // #endif }; /** @@ -947,9 +961,10 @@ sweeping_line_triangulation( _sorted_polygons_points(polygon_list); result.reserve(sorted_points.size() - 2); - + int idx = 0; for (auto &sorted_point : sorted_points) { auto point_type = get_point_type(sorted_point, point_to_edges); + idx += 1; switch (point_type) { case PointType::NORMAL: // Change edge adjusted to the current sweeping line. From 573ffd80d4754e454bb060d8dc5c6201dd0fc863 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2024 18:04:05 +0000 Subject: [PATCH 50/76] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/tests/test_triangulate.py | 52 +++++++++++++++++------------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index c8aca2c..66979ab 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -193,35 +193,35 @@ def test_triangulate_polygon_in_polygon_numpy(): triangles, points = triangulate_polygon_numpy_li(polygons) assert len(triangles) == 8 assert len(points) == 8 - - + + def test_triangulate_polygon_segfault1(): """Test on polygon that lead to segfault during test""" polygon = [ - (205.0625, 1489.83752), - (204.212509, 1490.4751), - (204, 1491.11255), - (202.087509, 1493.45007), - (201.875, 1494.7251), - (202.300003, 1496), - (202.300003, 1498.33752), - (203.575012, 1499.82507), - (204.425003, 1500.25), - (205.0625, 1500.25), - (205.700012, 1500.67505), - (206.550003, 1500.67505), - (207.1875, 1500.25), - (208.037506, 1500.88757), - (209.3125, 1499.82507), - (209.525009, 1499.1875), - (211.012512, 1497.70007), - (210.375, 1496.42505), - (209.525009, 1495.57507), - (208.462509, 1495.15002), - (208.675003, 1494.9375), - (208.462509, 1492.8125), - (208.037506, 1491.5376), - (205.912506, 1489.83752), + (205.0625, 1489.83752), + (204.212509, 1490.4751), + (204, 1491.11255), + (202.087509, 1493.45007), + (201.875, 1494.7251), + (202.300003, 1496), + (202.300003, 1498.33752), + (203.575012, 1499.82507), + (204.425003, 1500.25), + (205.0625, 1500.25), + (205.700012, 1500.67505), + (206.550003, 1500.67505), + (207.1875, 1500.25), + (208.037506, 1500.88757), + (209.3125, 1499.82507), + (209.525009, 1499.1875), + (211.012512, 1497.70007), + (210.375, 1496.42505), + (209.525009, 1495.57507), + (208.462509, 1495.15002), + (208.675003, 1494.9375), + (208.462509, 1492.8125), + (208.037506, 1491.5376), + (205.912506, 1489.83752), ] triangles, points = triangulate_polygon_py(polygon) From 06b808e0a56c1058642d0096c270015294df6a47 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Mon, 18 Nov 2024 23:47:20 +0100 Subject: [PATCH 51/76] fix intersection for colinear points --- pyproject.toml | 2 +- .../triangulate.pyx | 12 ++-- .../triangulation/intersection.hpp | 16 +++-- .../triangulation/point.hpp | 16 ++++- .../triangulation/triangulate.hpp | 16 ++--- src/tests/test_triangulate.py | 63 ++++++++++++++++++- 6 files changed, 104 insertions(+), 21 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0f632db..be35b7c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ sdist.include = ["src/PartSegCore_compiled_backend/version.py"] cmake.version = ">=3.21" sdist.exclude = [".github", "tox.ini", "build_utils", "notebooks", ".readthedocs.yaml"] wheel.exclude = ["**.pyx"] -cmake.build-type = "Debug" +#cmake.build-type = "Debug" [project] diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index d303c9c..58200d9 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -51,7 +51,7 @@ cdef extern from "triangulation/intersection.hpp" namespace "partsegcore::inters int _orientation(const Point& p, const Point& q, const Point& r) bool _do_intersect(const Segment& s1, const Segment& s2) unordered_set[OrderedPair] _find_intersections(const vector[Segment]& segments) - Point _find_intersection(const Segment& s1, const Segment& s2) + vector[Point] _find_intersection(const Segment& s1, const Segment& s2) cdef extern from "triangulation/triangulate.hpp" namespace "partsegcore::triangulation": @@ -83,8 +83,8 @@ cdef extern from "triangulation/triangulate.hpp" namespace "partsegcore::triangu bool left_to_right(const Segment& s1, const Segment& s2) vector[Point] find_intersection_points(const vector[Point]& segments) vector[PointTriangle] triangulate_monotone_polygon(const MonotonePolygon& polygon) - pair[vector[Triangle], vector[Point]] triangulate_polygon(const vector[Point]& polygon) except + - pair[vector[Triangle], vector[Point]] triangulate_polygon(const vector[vector[Point]]& polygon_list) except + + pair[vector[Triangle], vector[Point]] triangulate_polygon(const vector[Point]& polygon) # except + + pair[vector[Triangle], vector[Point]] triangulate_polygon(const vector[vector[Point]]& polygon_list) # except + @@ -170,7 +170,7 @@ def find_intersections(segments: Sequence[Sequence[Sequence[float]]]) -> list[tu return [(p.first, p.second) for p in intersections] -def find_intersection_point(s1: Sequence[Sequence[float]], s2: Sequence[Sequence[float]]) -> tuple[float, float]: +def find_intersection_point(s1: Sequence[Sequence[float]], s2: Sequence[Sequence[float]]) -> list[tuple[float, float]]: """ Find intersection between two segments Parameters @@ -185,11 +185,11 @@ def find_intersection_point(s1: Sequence[Sequence[float]], s2: Sequence[Sequence sequence of 2 floats: intersection point """ - cdef Point p = _find_intersection( + cdef vector[Point] p_li = _find_intersection( Segment(Point(s1[0][0], s1[0][1]), Point(s1[1][0], s1[1][1])), Segment(Point(s2[0][0], s2[0][1]), Point(s2[1][0], s2[1][1])) ) - return (p.x, p.y) + return [(p.x, p.y) for p in p_li] def is_convex(polygon: Sequence[Sequence[float]]) -> bool: diff --git a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp index 9e50dc6..c5233cd 100644 --- a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp @@ -316,8 +316,8 @@ std::unordered_set _find_intersections( * @return The intersection point of the two segments, or (0, 0) if they do not * intersect. */ -point::Point _find_intersection(const point::Segment &s1, - const point::Segment &s2) { +inline std::vector _find_intersection(const point::Segment &s1, + const point::Segment &s2) { float a1, b1, c1, a2, b2, c2, det, x, y; a1 = s1.top.y - s1.bottom.y; b1 = s1.bottom.x - s1.top.x; @@ -326,10 +326,18 @@ point::Point _find_intersection(const point::Segment &s1, b2 = s2.bottom.x - s2.top.x; c2 = a2 * s2.bottom.x + b2 * s2.bottom.y; det = a1 * b2 - a2 * b1; - if (det == 0) return {0, 0}; + if (det == 0) { + // collinear case + std::vector res; + if (s1.point_on_line(s2.bottom)) res.push_back(s2.bottom); + if (s1.point_on_line(s2.top)) res.push_back(s2.top); + if (s2.point_on_line(s1.bottom)) res.push_back(s1.bottom); + if (s2.point_on_line(s1.top)) res.push_back(s1.top); + return res; + } x = (b2 * c1 - b1 * c2) / det; y = (a1 * c2 - a2 * c1) / det; - return {x, y}; + return {{x, y}}; } } // namespace intersection } // namespace partsegcore diff --git a/src/PartSegCore_compiled_backend/triangulation/point.hpp b/src/PartSegCore_compiled_backend/triangulation/point.hpp index 4beae19..579b007 100644 --- a/src/PartSegCore_compiled_backend/triangulation/point.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/point.hpp @@ -65,7 +65,8 @@ struct Segment { } } - bool is_horizontal() const { return bottom.y == top.y; } + [[nodiscard]] bool is_horizontal() const { return bottom.y == top.y; } + [[nodiscard]] bool is_vertical() const { return bottom.x == top.x; } Segment() = default; @@ -96,7 +97,7 @@ struct Segment { * @return The x-coordinate of the point on the line segment at the specified * y-coordinate. */ - float point_on_line(float y) const { + [[nodiscard]] float point_on_line_x(float y) const { if (bottom.y == top.y) { return bottom.x; } @@ -104,6 +105,17 @@ struct Segment { (y - bottom.y) * ((top.x - bottom.x) / (top.y - bottom.y)); } + [[nodiscard]] bool point_on_line(Point p) const { + if (this->is_horizontal()) { + return (this->bottom.x < p.x && p.x < this->top.x); + } + if (this->is_vertical()) { + return (this->bottom.y < p.y && p.y < this->top.y); + } + auto x_cord = this->point_on_line_x(p.y); + return (this->bottom.x < x_cord && x_cord < this->top.x); + } + /** * Overloads the << operator for the Segment structure, enabling * segments to be directly inserted into output streams. diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index 3761c0a..6a15460 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -311,7 +311,7 @@ struct MonotonePolygonBuilder { left_interval->right_segment = right_interval->right_segment; #ifdef DEBUG if (segment_to_line.count(right_interval->right_segment) == 0) { - throw std::runtime_error("Segment not found in the map"); + throw std::runtime_error("Segment not found in the map2"); } #endif segment_to_line[right_interval->right_segment] = left_interval; @@ -352,7 +352,7 @@ struct MonotonePolygonBuilder { const point::Segment &edge_top, const point::Segment &edge_bottom) { if (segment_to_line.count(edge_top) == 0) { - throw std::runtime_error("Segment not found in the map"); + throw std::runtime_error("Segment not found in the map1"); } Interval *interval = segment_to_line.at(edge_top); @@ -440,8 +440,8 @@ struct MonotonePolygonBuilder { // check if the current point is inside the quadrangle defined by edges of // the interval Interval *interval = segment_interval.second; - if (interval->left_segment.point_on_line(p.y) < p.x && - interval->right_segment.point_on_line(p.y) > p.x) { + if (interval->left_segment.point_on_line_x(p.y) < p.x && + interval->right_segment.point_on_line_x(p.y) > p.x) { // the point is inside the interval // update the sweep line @@ -872,10 +872,12 @@ std::vector> find_intersection_points( std::unordered_map> intersections_points; for (const auto &intersection : intersections) { - auto inter_point = intersection::_find_intersection( + auto inter_points = intersection::_find_intersection( edges[intersection.first], edges[intersection.second]); - intersections_points[intersection.first].push_back(inter_point); - intersections_points[intersection.second].push_back(inter_point); + for (auto inter_point : inter_points) { + intersections_points[intersection.first].push_back(inter_point); + intersections_points[intersection.second].push_back(inter_point); + } }; for (auto &intersections_point : intersections_points) { // points_count += intersections_point.second.size() - 1; diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index 66979ab..6980ef7 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -120,7 +120,38 @@ def test_find_intersections_param(segments, expected): ], ) def test_find_intersection_point(segment1, segment2, expected): - assert find_intersection_point(segment1, segment2) == expected + assert len(find_intersection_point(segment1, segment2)) == 1 + assert find_intersection_point(segment1, segment2)[0] == expected + + +@pytest.mark.parametrize( + ('segment1', 'segment2', 'expected'), + [ + (((0, 0), (2, 0)), ((1, 0), (3, 0)), [(1, 0), (2, 0)]), # Horizontal segments + (((1, 0), (3, 0)), ((0, 0), (2, 0)), [(1, 0), (2, 0)]), # Horizontal segments + (((0, 0), (0, 2)), ((0, 1), (0, 3)), [(0, 1), (0, 2)]), # Vertical segments + (((0, 1), (0, 3)), ((0, 0), (0, 2)), [(0, 1), (0, 2)]), # Vertical segments + (((0, 0), (2, 2)), ((1, 1), (3, 3)), [(1, 1), (2, 2)]), # Diagonal segments + (((1, 1), (3, 3)), ((0, 0), (2, 2)), [(1, 1), (2, 2)]), # Diagonal segments + ], +) +def test_find_intersection_points_colinear_overlap(segment1, segment2, expected): + assert sorted(find_intersection_point(segment1, segment2)) == expected + + +@pytest.mark.parametrize( + ('segment1', 'segment2', 'expected'), + [ + (((0, 0), (3, 0)), ((1, 0), (2, 0)), [(1, 0), (2, 0)]), # Horizontal segments + (((1, 0), (2, 0)), ((0, 0), (3, 0)), [(1, 0), (2, 0)]), # Horizontal segments + (((0, 0), (0, 3)), ((0, 1), (0, 2)), [(0, 1), (0, 2)]), # Vertical segments + (((0, 1), (0, 2)), ((0, 0), (0, 3)), [(0, 1), (0, 2)]), # Vertical segments + (((0, 0), (3, 3)), ((1, 1), (2, 2)), [(1, 1), (2, 2)]), # Diagonal segments + (((1, 1), (2, 2)), ((0, 0), (3, 3)), [(1, 1), (2, 2)]), # Diagonal segments + ], +) +def test_find_intersection_point_colinear_inside(segment1, segment2, expected): + assert sorted(find_intersection_point(segment1, segment2)) == expected def test_is_convex(): @@ -226,6 +257,36 @@ def test_triangulate_polygon_segfault1(): triangles, points = triangulate_polygon_py(polygon) +def test_triangulate_polygon_segfault2(): + polygon = [ + [1388.6875, 2744.4375], + [1388.4751, 2744.6501], + [1386.5625, 2744.6501], + [1385.925, 2744.8625], + [1385.5, 2745.2876], + [1385.2876, 2747.625], + [1385.7125, 2748.2627], + [1385.7125, 2749.1125], + [1386.1376, 2749.75], + [1389.9625, 2753.7876], + [1390.3876, 2754.6377], + [1391.025, 2754.6377], + [1392.0875, 2753.1501], + [1392.3, 2753.3625], + [1392.3, 2754.6377], + [1392.5126, 2754.2126], + [1392.3, 2754.0], + [1392.3, 2751.4502], + [1392.7251, 2750.3877], + [1391.6626, 2748.9001], + [1391.6626, 2747.4126], + [1390.8125, 2745.5], + [1390.175, 2745.2876], + [1389.3251, 2744.4375], + ] + triangles, points = triangulate_polygon_py(polygon) + + @pytest.mark.parametrize( ('segment1', 'segment2'), [ From 8fd0fda850c16a45402c05d2930cd44f857e8d96 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 19 Nov 2024 00:16:45 +0100 Subject: [PATCH 52/76] try fix linux compilation fail --- src/PartSegCore_compiled_backend/triangulation/point.hpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulation/point.hpp b/src/PartSegCore_compiled_backend/triangulation/point.hpp index 579b007..722d172 100644 --- a/src/PartSegCore_compiled_backend/triangulation/point.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/point.hpp @@ -51,10 +51,9 @@ struct Segment { Point bottom{}; Segment(Point p1, Point p2) { if (p1 == p2) { - throw std::invalid_argument( - (std::ostringstream() - << "Segment cannot have two identical points: " << p1) - .str()); + std::ostringstream oss; + oss << "Segment cannot have two identical points: " << p1; + throw std::invalid_argument(oss.str()); } if (p1 < p2) { bottom = p1; From 4d9515316b2b74966b4d261becb848e53b3551fc Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 19 Nov 2024 17:09:22 +0100 Subject: [PATCH 53/76] add next test --- src/tests/test_triangulate.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index 6980ef7..36bbfdb 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -287,6 +287,36 @@ def test_triangulate_polygon_segfault2(): triangles, points = triangulate_polygon_py(polygon) +def test_triangulate_polygon_segfault3(): + polygon = [ + (1066.32507, 1794.3501), + (1065.6875, 1794.77502), + (1063.77502, 1794.77502), + (1063.5625, 1794.98755), + (1063.13757, 1794.98755), + (1062.28748, 1795.83752), + (1062.28748, 1797.32507), + (1062.07507, 1797.5376), + (1062.28748, 1797.5376), + (1062.5, 1797.75), + (1063.13757, 1797.75), + (1063.5625, 1797.32507), + (1064.625, 1797.32507), + (1065.26257, 1797.96252), + (1064.83752, 1797.5376), + (1064.83752, 1796.90002), + (1065.47498, 1796.26257), + (1065.47498, 1796.05005), + (1065.6875, 1795.83752), + (1066.53748, 1795.83752), + (1066.75, 1795.625), + (1066.75, 1794.98755), + (1066.96252, 1794.77502), + (1066.75, 1794.3501), + ] + triangles, points = triangulate_polygon_py(polygon) + + @pytest.mark.parametrize( ('segment1', 'segment2'), [ From 6332bde6f72bf5eb60b18a7a1fb6f85ea40219c5 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Wed, 20 Nov 2024 17:53:03 +0100 Subject: [PATCH 54/76] single place to define coordinate type --- .../triangulation/intersection.hpp | 45 ++++++++----------- .../triangulation/point.hpp | 25 +++++------ 2 files changed, 29 insertions(+), 41 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp index c5233cd..fa1caa6 100644 --- a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp @@ -15,25 +15,21 @@ #define PARTSEGCORE_INTERSECTION_H #include -#include #include -#include -#include -#include #include #include #include "point.hpp" -namespace partsegcore { -namespace intersection { +namespace partsegcore::intersection { struct Event { point::Point p; int index; bool is_top; - Event(float x, float y, int index, bool is_left) + Event(point::Point::coordinate_t x, point::Point::coordinate_t y, int index, + bool is_left) : p(x, y), index(index), is_top(is_left) {} Event(const point::Point &p, int index, bool is_left) : p(p), index(index), is_top(is_left) {} @@ -72,22 +68,18 @@ struct OrderedPair { return first == pair.first && second == pair.second; } }; -} // namespace intersection -} // namespace partsegcore -namespace std { +} // namespace partsegcore::intersection template <> -struct hash { +struct std::hash { std::size_t operator()( - const partsegcore::intersection::OrderedPair &pair) const { + const partsegcore::intersection::OrderedPair &pair) const noexcept { return std::hash()(pair.first) ^ std::hash()(pair.second); } -}; -} // namespace std +}; // namespace std -namespace partsegcore { -namespace intersection { +namespace partsegcore::intersection { typedef std::map IntersectionEvents; @@ -105,8 +97,8 @@ typedef std::map IntersectionEvents; * @param r The second endpoint of the line segment. * @return True if point q lies on the segment pr, false otherwise. */ -bool _on_segment(const point::Point &p, const point::Point &q, - const point::Point &r) { +inline bool _on_segment(const point::Point &p, const point::Point &q, + const point::Point &r) { if (q.x <= std::max(p.x, r.x) && q.x >= std::min(p.x, r.x) && q.y <= std::max(p.y, r.y) && q.y >= std::min(p.y, r.y)) return true; @@ -124,12 +116,12 @@ bool _on_segment(const point::Point &p, const point::Point &q, * 1 if the triplet (p, q, r) is in a clockwise orientation. * 2 if the triplet (p, q, r) is in a counterclockwise orientation. */ -int _orientation(const point::Point &p, const point::Point &q, - const point::Point &r) { +inline int _orientation(const point::Point &p, const point::Point &q, + const point::Point &r) { double val1 = ((q.y - p.y) * (r.x - q.x)); double val2 = ((r.y - q.y) * (q.x - p.x)); // Instead of using classical equation, we need to use two variables - // to handle problem with strange behaviour on macOs. + // to handle problem with strange behaviour on macOS. if (val1 == val2) return 0; return (val1 > val2) ? 1 : 2; } @@ -145,7 +137,7 @@ int _orientation(const point::Point &p, const point::Point &q, * @param s2 The second line segment, represented by two endpoints. * @return True if the segments intersect, false otherwise. */ -bool _do_intersect(const point::Segment &s1, const point::Segment &s2) { +inline bool _do_intersect(const point::Segment &s1, const point::Segment &s2) { const point::Point &p1 = s1.bottom; const point::Point &q1 = s1.top; const point::Point &p2 = s2.bottom; @@ -197,7 +189,7 @@ typename T::iterator succ(T &s, typename T::iterator it) { return ++it; } -std::unordered_set _find_intersections( +inline std::unordered_set _find_intersections( const std::vector &segments) { std::unordered_set intersections; IntersectionEvents intersection_events; @@ -210,7 +202,6 @@ std::unordered_set _find_intersections( } // std::cout << "Segments "; // print_vector(std::cout, segments, "\n"); - int i = 0; while (!intersection_events.empty()) { auto event_it = --intersection_events.end(); // std::cout << "Event " << i << ": " << event_it->first << " tops: "; @@ -318,7 +309,8 @@ std::unordered_set _find_intersections( */ inline std::vector _find_intersection(const point::Segment &s1, const point::Segment &s2) { - float a1, b1, c1, a2, b2, c2, det, x, y; + // ReSharper disable CppJoinDeclarationAndAssignment + point::Point::coordinate_t a1, b1, c1, a2, b2, c2, det, x, y; a1 = s1.top.y - s1.bottom.y; b1 = s1.bottom.x - s1.top.x; c1 = a1 * s1.bottom.x + b1 * s1.bottom.y; @@ -339,7 +331,6 @@ inline std::vector _find_intersection(const point::Segment &s1, y = (a1 * c2 - a2 * c1) / det; return {{x, y}}; } -} // namespace intersection -} // namespace partsegcore +} // namespace partsegcore::intersection #endif // PARTSEGCORE_INTERSECTION_H diff --git a/src/PartSegCore_compiled_backend/triangulation/point.hpp b/src/PartSegCore_compiled_backend/triangulation/point.hpp index 722d172..f0103f7 100644 --- a/src/PartSegCore_compiled_backend/triangulation/point.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/point.hpp @@ -6,23 +6,20 @@ #define PARTSEGCORE_POINT_H #include -#include -#include #include -#include -#include #include -namespace partsegcore { -namespace point { +namespace partsegcore::point { /* Point class with x and y coordinates */ struct Point { - float x; - float y; + typedef float coordinate_t; + + coordinate_t x; + coordinate_t y; bool operator==(const Point &p) const { return x == p.x && y == p.y; } bool operator!=(const Point &p) const { return !(*this == p); } - Point(float x, float y) : x(x), y(y) {} + Point(coordinate_t x, coordinate_t y) : x(x), y(y) {} Point() = default; bool operator<(const Point &p) const { @@ -96,7 +93,8 @@ struct Segment { * @return The x-coordinate of the point on the line segment at the specified * y-coordinate. */ - [[nodiscard]] float point_on_line_x(float y) const { + [[nodiscard]] Point::coordinate_t point_on_line_x( + Point::coordinate_t y) const { if (bottom.y == top.y) { return bottom.x; } @@ -149,22 +147,21 @@ struct Segment { } }; }; -} // namespace point -} // namespace partsegcore +} // namespace partsegcore::point // overload of hash function for // unordered map and set namespace std { template <> struct hash { - size_t operator()(const partsegcore::point::Point &point) const { + size_t operator()(const partsegcore::point::Point &point) const noexcept { return partsegcore::point::Point::PointHash()(point); } }; template <> struct hash { - size_t operator()(const partsegcore::point::Segment &segment) const { + size_t operator()(const partsegcore::point::Segment &segment) const noexcept { return partsegcore::point::Segment::SegmentHash()(segment); } }; From c5ba35113899ec0811d3b2ce76a9bf3f38d080d5 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Wed, 20 Nov 2024 18:21:41 +0100 Subject: [PATCH 55/76] cleanup code --- .../triangulation/debug_util.hpp | 8 -- .../triangulation/triangulate.hpp | 79 +++++++++++-------- 2 files changed, 47 insertions(+), 40 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulation/debug_util.hpp b/src/PartSegCore_compiled_backend/triangulation/debug_util.hpp index 4461173..c050f6e 100644 --- a/src/PartSegCore_compiled_backend/triangulation/debug_util.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/debug_util.hpp @@ -5,18 +5,10 @@ #ifndef PARTSEGCORE_TRIANGULATION_DEBUG_UTIL_ #define PARTSEGCORE_TRIANGULATION_DEBUG_UTIL_ -#include #include -#include #include -#include -#include -#include -#include #include -#include "point.hpp" - namespace partsegcore { template diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index 6a15460..b2d2a94 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -14,8 +13,7 @@ #include "intersection.hpp" #include "point.hpp" -namespace partsegcore { -namespace triangulation { +namespace partsegcore::triangulation { enum PointType { NORMAL, SPLIT, MERGE, INTERSECTION, EMPTY }; @@ -73,6 +71,15 @@ struct Interval { } throw std::runtime_error("Segment not found in interval"); }; + + // ostream operator for Interval + friend std::ostream &operator<<(std::ostream &os, const Interval &interval) { + os << "Last Seen: " << interval.last_seen + << ", Left Segment: " << interval.left_segment + << ", Right Segment: " << interval.right_segment + << ", Polygons count: " << interval.polygons_list.size(); + return os; + } }; /** @@ -90,7 +97,7 @@ struct Interval { * * @return `true` if `s1` is to the left and below `s2`, `false` otherwise. */ -bool left_to_right(const point::Segment &s1, const point::Segment &s2) { +inline bool left_to_right(const point::Segment &s1, const point::Segment &s2) { if (s1.is_horizontal() && s2.is_horizontal()) { return s1.bottom.x < s2.bottom.x; } @@ -117,12 +124,13 @@ bool left_to_right(const point::Segment &s1, const point::Segment &s2) { return intersection::_orientation(s1.top, s1.bottom, s2.top) == 2; } -bool left_right_share_top(const point::Segment &s1, const point::Segment &s2) { +inline bool left_right_share_top(const point::Segment &s1, + const point::Segment &s2) { return intersection::_orientation(s1.bottom, s1.top, s2.bottom) == 1; } -bool left_right_share_bottom(const point::Segment &s1, - const point::Segment &s2) { +inline bool left_right_share_bottom(const point::Segment &s1, + const point::Segment &s2) { return intersection::_orientation(s1.top, s1.bottom, s2.top) == 2; } @@ -193,7 +201,7 @@ typedef std::map * @return A PointToEdges map where each key is a point and the corresponding * value is a vector of edges containing that point. */ -PointToEdges get_points_edges(const std::vector &edges) { +inline PointToEdges get_points_edges(const std::vector &edges) { PointToEdges point_to_edges; for (std::size_t i = 0; i < edges.size(); i++) { point_to_edges[edges[i].bottom].emplace_back(i, edges[i].top); @@ -225,7 +233,7 @@ PointToEdges get_points_edges(const std::vector &edges) { * @param point_to_edges A mapping from points to their adjacent edges. * @return The type of the point as determined by its adjacent edges. */ -PointType get_point_type(point::Point p, PointToEdges &point_to_edges) { +inline PointType get_point_type(point::Point p, PointToEdges &point_to_edges) { if (point_to_edges.count(p) == 0) return PointType::EMPTY; if (point_to_edges.at(p).empty()) return PointType::EMPTY; if (point_to_edges.at(p).size() != 2) return PointType::INTERSECTION; @@ -306,6 +314,15 @@ struct MonotonePolygonBuilder { // merge two intervals into one Interval *left_interval = segment_to_line.at(edge_left); Interval *right_interval = segment_to_line.at(edge_right); +#ifdef DEBUG + if (right_interval->right_segment == edge_right) { + std::ostringstream oss; + oss << "The right edge of merge point should be the left edge of the " + "right interval.\nGot interval: " + << *right_interval << " and edge: " << edge_right; + throw std::runtime_error(oss.str()); + } +#endif segment_to_line.erase(edge_right); segment_to_line.erase(edge_left); left_interval->right_segment = right_interval->right_segment; @@ -536,12 +553,12 @@ struct MonotonePolygonBuilder { if (!top_segments.empty()) { if (top_segments.front() == segment_to_line.at(top_segments.front())->right_segment) { - bottom_begin++; + ++bottom_begin; _process_normal_point(p, top_segments.front(), bottom_segments.front()); } if (top_segments.back() == segment_to_line.at(top_segments.back())->left_segment) { - bottom_end--; + --bottom_end; _process_normal_point(p, top_segments.back(), bottom_segments.back()); } } @@ -564,7 +581,7 @@ struct MonotonePolygonBuilder { * order. * @return True if the polygon is convex, false otherwise. */ -bool _is_convex(const std::vector &polygon) { +inline bool _is_convex(const std::vector &polygon) { int orientation = 0; int triangle_orientation; for (std::size_t i = 0; i < polygon.size() - 2; i++) { @@ -597,7 +614,7 @@ bool _is_convex(const std::vector &polygon) { * @return A vector of Triangle objects where each triangle is defined by * indices corresponding to the vertices in the input polygon. */ -std::vector _triangle_convex_polygon( +inline std::vector _triangle_convex_polygon( const std::vector &polygon) { std::vector result; for (std::size_t i = 1; i < polygon.size() - 1; i++) { @@ -628,9 +645,9 @@ std::vector _triangle_convex_polygon( * @param current_point The current `point::Point` used to form triangles with * points in the stack. */ -void _build_triangles_opposite_edge(std::vector &stack, - std::vector &result, - point::Point current_point) { +inline void _build_triangles_opposite_edge(std::vector &stack, + std::vector &result, + point::Point current_point) { for (std::size_t i = 0; i < stack.size() - 1; i++) { result.emplace_back(current_point, stack[i], stack[i + 1]); } @@ -649,27 +666,27 @@ void _build_triangles_opposite_edge(std::vector &stack, * based on the expected orientation and are added to the result vector. The * stack is then updated to reflect the current status of the processed area. * - * @param point_stack A reference to a std::vector of point::Point representing + * @param stack A reference to a std::vector of point::Point representing * the current state of the stack. - * @param triangles A reference to a std::vector of PointTriangle where the + * @param result A reference to a std::vector of PointTriangle where the * generated triangles will be stored. - * @param incoming_point The incoming point::Point to be considered for new + * @param current_point The incoming point::Point to be considered for new * triangles and updating the stack. * @param expected_orientation The expected orientation (clockwise or * counterclockwise) that will guide the triangle formation. */ -void _build_triangles_current_edge(std::vector &stack, - std::vector &result, - point::Point current_point, - int expected_orientation) { +inline void _build_triangles_current_edge(std::vector &stack, + std::vector &result, + point::Point current_point, + int expected_orientation) { auto it1 = stack.rbegin(); auto it2 = stack.rbegin() + 1; while (it2 != stack.rend() && intersection::_orientation(*it2, *it1, current_point) == expected_orientation) { result.emplace_back(current_point, *it1, *it2); - it1++; - it2++; + ++it1; + ++it2; } stack.erase(it1.base(), stack.end()); stack.push_back(current_point); @@ -896,17 +913,16 @@ std::vector> find_intersection_points( new_polygon.push_back(polygon[0]); for (std::size_t i = 0; i < polygon.size(); i++) { auto point = polygon[i]; - if (new_polygon[new_polygon.size() - 1] != point) - new_polygon.push_back(point); + if (new_polygon.back() != point) new_polygon.push_back(point); if (intersections_points.count(i)) { auto &new_points = intersections_points[i]; if (new_points[0] == point) { - for (auto it = new_points.begin() + 1; it != new_points.end(); it++) { + for (auto it = new_points.begin() + 1; it != new_points.end(); ++it) { if (new_polygon.back() != *it) new_polygon.push_back(*it); } } else { for (auto it = new_points.rbegin() + 1; it != new_points.rend(); - it++) { + ++it) { if (new_polygon.back() != *it) new_polygon.push_back(*it); } } @@ -966,7 +982,6 @@ sweeping_line_triangulation( int idx = 0; for (auto &sorted_point : sorted_points) { auto point_type = get_point_type(sorted_point, point_to_edges); - idx += 1; switch (point_type) { case PointType::NORMAL: // Change edge adjusted to the current sweeping line. @@ -996,6 +1011,7 @@ sweeping_line_triangulation( // this is a point without edges (removed by deduplication) break; } + ++idx; } std::unordered_map point_to_index; point_to_index.reserve(sorted_points.size()); @@ -1068,7 +1084,6 @@ std::pair, std::vector> triangulate_polygon( // #endif } -} // namespace triangulation -} // namespace partsegcore +} // namespace partsegcore::triangulation #endif // PARTSEGCORE_TRIANGULATE_H From 36453b24037bc4413dd5a055164fa2411416e7d0 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Wed, 20 Nov 2024 18:35:14 +0100 Subject: [PATCH 56/76] fix IDE warnings --- .../triangulation/triangulate.hpp | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index b2d2a94..0e64a05 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -217,7 +217,7 @@ inline PointToEdges get_points_edges(const std::vector &edges) { } /** - * Determines the type of a given point based on its adjacent edges. + * Determines the type of given point based on its adjacent edges. * * If the point has more than two edges connected to it, it is considered an * intersection point. If it has exactly two edges, further checks categorize @@ -404,14 +404,14 @@ struct MonotonePolygonBuilder { interval->last_seen = p; interval->replace_segment(edge_top, edge_bottom); segment_to_line.erase(edge_top); - // #ifdef DEBUG +#ifdef DEBUG if (segment_to_line.count(interval->left_segment) == 0) { throw std::runtime_error("Left segment not found in the map"); } if (segment_to_line.count(interval->right_segment) == 0) { throw std::runtime_error("Right segment not found in the map"); } - // #endif +#endif }; /** @@ -724,7 +724,7 @@ enum Side { * @return A vector of PointTriangle representing the triangles of the * triangulated polygon. */ -std::vector triangulate_monotone_polygon( +inline std::vector triangulate_monotone_polygon( const MonotonePolygon &polygon) { std::vector result; std::size_t left_index = 0; @@ -782,7 +782,7 @@ std::vector triangulate_monotone_polygon( * @param polygon A vector of points representing the vertices of the polygon. * @return A vector of segments representing the edges of the polygon. */ -std::vector calc_edges( +inline std::vector calc_edges( const std::vector &polygon) { std::vector edges; edges.reserve(polygon.size()); @@ -809,7 +809,7 @@ std::vector calc_edges( * @return A vector of `point::Segment` instances representing the edges of * all polygons in the input list. */ -std::vector calc_edges( +inline std::vector calc_edges( const std::vector> &polygon_list) { std::vector edges; std::size_t points_count = 0; @@ -842,7 +842,7 @@ std::vector calc_edges( * @return A vector of unique edges (of type point::Segment) that are not * duplicated across the polygons. */ -std::vector calc_dedup_edges( +inline std::vector calc_dedup_edges( const std::vector> &polygon_list) { std::set edges_set; point::Segment edge; @@ -874,11 +874,12 @@ std::vector calc_dedup_edges( * The function takes a vector of points defining a polygon and finds all edge * intersections. It then adds mid-points for all such intersections. * - * @param polygon The polygon defined by a vector of Point objects. + * @param polygon_list The list of polygons defined by a vector of ov vector of + * Point objects. * @return A new vector of Point objects representing the polygon with added * intersection points. */ -std::vector> find_intersection_points( +inline std::vector> find_intersection_points( const std::vector> &polygon_list) { /* find all edge intersections and add mid-points for all such intersection * places*/ @@ -935,14 +936,14 @@ std::vector> find_intersection_points( return new_polygons_list; } -std::vector find_intersection_points( +inline std::vector find_intersection_points( const std::vector &polygon) { auto new_polygon = find_intersection_points( std::vector>({polygon})); return new_polygon[0]; } -std::vector _sorted_polygons_points( +inline std::vector _sorted_polygons_points( const std::vector> &polygon_list) { std::vector result; std::unordered_set visited; @@ -966,7 +967,7 @@ std::vector _sorted_polygons_points( more than 2 edges. described on this lecture: https://www.youtube.com/playlist?list=PLtTatrCwXHzEqzJMaTUFgqoCNllgwk4DH */ -std::pair, std::vector> +inline std::pair, std::vector> sweeping_line_triangulation( const std::vector> &polygon_list) { std::vector result; @@ -1002,7 +1003,7 @@ sweeping_line_triangulation( break; case PointType::INTERSECTION: // this is a merge and split point at the same time - // this is not described in original algorithm + // this is not described in original algorithm, // but we need it to handle self intersecting polygons // Remember about more than 4 edges case builder.process_intersection_point(sorted_point); @@ -1031,7 +1032,8 @@ sweeping_line_triangulation( // calculate the triangulation of a symmetric difference of list of polygons -std::pair, std::vector> triangulate_polygon( +inline std::pair, std::vector> +triangulate_polygon( const std::vector> &polygon_list) { if (polygon_list.empty()) // empty list @@ -1065,8 +1067,8 @@ std::pair, std::vector> triangulate_polygon( return sweeping_line_triangulation(find_intersection_points(polygon_list)); } -std::pair, std::vector> triangulate_polygon( - const std::vector &polygon) { +inline std::pair, std::vector> +triangulate_polygon(const std::vector &polygon) { // #if DDEBUG // try{ // #endif From 9b4c38fe131625f5d6ae640d47fd005f81180225 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 28 Nov 2024 00:44:10 +0100 Subject: [PATCH 57/76] fix another bug --- .../triangulate.pyx | 2 +- .../triangulation/intersection.hpp | 19 ++++ .../triangulation/point.hpp | 11 ++ .../triangulation/triangulate.hpp | 34 ++++-- src/tests/test_triangulate.py | 103 +++++++++++++++++- 5 files changed, 155 insertions(+), 14 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index 58200d9..2278805 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -83,7 +83,7 @@ cdef extern from "triangulation/triangulate.hpp" namespace "partsegcore::triangu bool left_to_right(const Segment& s1, const Segment& s2) vector[Point] find_intersection_points(const vector[Point]& segments) vector[PointTriangle] triangulate_monotone_polygon(const MonotonePolygon& polygon) - pair[vector[Triangle], vector[Point]] triangulate_polygon(const vector[Point]& polygon) # except + + pair[vector[Triangle], vector[Point]] triangulate_polygon(const vector[Point]& polygon) # except + pair[vector[Triangle], vector[Point]] triangulate_polygon(const vector[vector[Point]]& polygon_list) # except + diff --git a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp index fa1caa6..58740ad 100644 --- a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp @@ -105,6 +105,12 @@ inline bool _on_segment(const point::Point &p, const point::Point &q, return false; } +inline int64_t double_as_hex(double d) { + int64_t result; + std::memcpy(&result, &d, sizeof(result)); + return result; +} + /** * Determines the orientation of the triplet (p, q, r). * @@ -120,6 +126,19 @@ inline int _orientation(const point::Point &p, const point::Point &q, const point::Point &r) { double val1 = ((q.y - p.y) * (r.x - q.x)); double val2 = ((r.y - q.y) * (q.x - p.x)); + // This commented code if for debugging purposes of differences between macOS + // and linux double val = ((q.y - p.y) * (r.x - q.x)) - ((r.y - q.y) * (q.x - + // p.x)); if (val!= 0 && val1 == val2) { + // std::ostringstream oss; + // // print coordinates and its hex representation + // oss << "val " << val << " val1 " << val1 << " val2 " << val2 << "\n"; + // oss << "p " << p << " p_hex (x=" << std::hex << double_as_hex(p.x) << ", + // y=" << double_as_hex(p.y) << ")\n"; oss << "q " << q << " q_hex (x=" << + // std::hex << double_as_hex(q.x) << ", y=" << double_as_hex(q.y) << ")\n"; + // oss << "r " << r << " r_hex (x=" << std::hex << double_as_hex(r.x) << ", + // y=" << double_as_hex(r.y) << ")\n"; throw std::runtime_error("Orientation + // is not defined for this case. " + oss.str()); + // } // Instead of using classical equation, we need to use two variables // to handle problem with strange behaviour on macOS. if (val1 == val2) return 0; diff --git a/src/PartSegCore_compiled_backend/triangulation/point.hpp b/src/PartSegCore_compiled_backend/triangulation/point.hpp index f0103f7..ba930a5 100644 --- a/src/PartSegCore_compiled_backend/triangulation/point.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/point.hpp @@ -102,6 +102,17 @@ struct Segment { (y - bottom.y) * ((top.x - bottom.x) / (top.y - bottom.y)); } + /** + * t = \frac{(x_1 - x_2)(x_3 - x_2) + (y_1 - y_2)(y_3 - y_2)}{(x_3 - x_2)^2 + + *(y_3 - y_2)^2} + **/ + [[nodiscard]] double point_projection_factor(Point p) const { + return ((p.x - this->top.x) * (this->bottom.x - this->top.x) + + (p.y - this->top.y) * (this->bottom.y - this->top.y)) / + ((this->top.x - this->bottom.x) * (this->top.x - this->bottom.x) + + (this->top.y - this->bottom.y) * (this->top.y - this->bottom.y)); + } + [[nodiscard]] bool point_on_line(Point p) const { if (this->is_horizontal()) { return (this->bottom.x < p.x && p.x < this->top.x); diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index 0e64a05..3dfdb92 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -233,7 +233,8 @@ inline PointToEdges get_points_edges(const std::vector &edges) { * @param point_to_edges A mapping from points to their adjacent edges. * @return The type of the point as determined by its adjacent edges. */ -inline PointType get_point_type(point::Point p, PointToEdges &point_to_edges) { +inline PointType get_point_type(point::Point p, + const PointToEdges &point_to_edges) { if (point_to_edges.count(p) == 0) return PointType::EMPTY; if (point_to_edges.at(p).empty()) return PointType::EMPTY; if (point_to_edges.at(p).size() != 2) return PointType::INTERSECTION; @@ -550,14 +551,17 @@ struct MonotonePolygonBuilder { auto bottom_begin = bottom_segments.begin(); auto bottom_end = bottom_segments.end(); + auto top_begin = top_segments.begin(); if (!top_segments.empty()) { if (top_segments.front() == segment_to_line.at(top_segments.front())->right_segment) { ++bottom_begin; + ++top_begin; _process_normal_point(p, top_segments.front(), bottom_segments.front()); } - if (top_segments.back() == - segment_to_line.at(top_segments.back())->left_segment) { + if (top_begin != top_segments.end() && + top_segments.back() == + segment_to_line.at(top_segments.back())->left_segment) { --bottom_end; _process_normal_point(p, top_segments.back(), bottom_segments.back()); } @@ -887,21 +891,25 @@ inline std::vector> find_intersection_points( auto intersections = intersection::_find_intersections(edges); if (intersections.empty()) return polygon_list; - std::unordered_map> + std::unordered_map>> intersections_points; for (const auto &intersection : intersections) { auto inter_points = intersection::_find_intersection( edges[intersection.first], edges[intersection.second]); for (auto inter_point : inter_points) { - intersections_points[intersection.first].push_back(inter_point); - intersections_points[intersection.second].push_back(inter_point); + intersections_points[intersection.first].emplace_back( + edges[intersection.first].point_projection_factor(inter_point), + inter_point); + intersections_points[intersection.second].emplace_back( + edges[intersection.second].point_projection_factor(inter_point), + inter_point); } }; for (auto &intersections_point : intersections_points) { // points_count += intersections_point.second.size() - 1; - intersections_point.second.push_back(edges[intersections_point.first].top); - intersections_point.second.push_back( - edges[intersections_point.first].bottom); + auto edge = edges[intersections_point.first]; + intersections_point.second.emplace_back(-1, edge.top); + intersections_point.second.emplace_back(2, edge.bottom); std::sort(intersections_point.second.begin(), intersections_point.second.end()); } @@ -917,14 +925,16 @@ inline std::vector> find_intersection_points( if (new_polygon.back() != point) new_polygon.push_back(point); if (intersections_points.count(i)) { auto &new_points = intersections_points[i]; - if (new_points[0] == point) { + if (new_points[0].second == point) { for (auto it = new_points.begin() + 1; it != new_points.end(); ++it) { - if (new_polygon.back() != *it) new_polygon.push_back(*it); + if (new_polygon.back() != it->second) + new_polygon.push_back(it->second); } } else { for (auto it = new_points.rbegin() + 1; it != new_points.rend(); ++it) { - if (new_polygon.back() != *it) new_polygon.push_back(*it); + if (new_polygon.back() != it->second) + new_polygon.push_back(it->second); } } } diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index 36bbfdb..9fb1abf 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -23,7 +23,7 @@ def test_on_segment(): assert not on_segment((0, 0), (1, 1), (0, 3)) -def test_orientation(): +def test_orientation_basic(): assert orientation((0, 0), (0, 1), (0, 2)) == 0 assert orientation((0, 0), (0, 2), (0, 1)) == 0 assert orientation((0, 2), (0, 0), (0, 1)) == 0 @@ -31,6 +31,34 @@ def test_orientation(): assert orientation((0, 0), (0, 1), (-1, 2)) == 2 +@pytest.mark.parametrize( + ('p1', 'p2', 'p3'), + [ + ((0, 0), (1, 1), (2, 0)), + ((0, 0), (1, 100), (2, 0)), + ((0, 0), (100, 1), (1, 0)), + ((0, 1), (1, 1), (2, 0)), + ((0, 1), (1, 1), (2000, 0)), + ], +) +def test_orientation_split(p1, p2, p3): + assert orientation(p1, p2, p3) == 1 + assert orientation(p3, p2, p1) == 2 + + +@pytest.mark.parametrize( + ('p1', 'p2', 'p3'), + [ + ((0, 1), (1, 0), (2, 1)), + ((0, 1), (1, 0), (2, 0)), + ((0, 1), (1, 0), (200, 0)), + ], +) +def test_orientation_merge(p1, p2, p3): + assert orientation(p1, p2, p3) == 2 + assert orientation(p3, p2, p1) == 1 + + def test_do_intersect(): assert do_intersect(((0, -1), (0, 1)), ((1, 0), (-1, 0))) assert not do_intersect(((0, -1), (0, 1)), ((1, 0), (2, 0))) @@ -216,6 +244,19 @@ def test_triangulate_polygon_numpy_li_non_convex(polygon, expected): assert triangles_ == expected +@pytest.mark.parametrize( + ('polygon', 'expected'), + [ + ([(0, 0), (0, 1), (1, 1), (1, 0), (0.5, 0.5)], [(0, 1, 2), (0, 3, 2)]), + ([(0, 0), (0, 1), (1, 1), (0.5, 0.5), (1, 0)], [(0, 1, 2), (0, 3, 2)]), + ([(0, 0), (0, 1), (0.5, 0.5), (1, 1), (1, 0)], [(0, 1, 2), (0, 3, 2)]), + ([(0, 0), (0.5, 0.5), (0, 1), (1, 1), (1, 0)], [(0, 1, 2), (0, 3, 2)]), + ], +) +def test_merge_points(polygon, expected): + triangles, points = triangulate_polygon_py(polygon) + + def test_triangulate_polygon_in_polygon_numpy(): polygons = [ np.array([(0, 0), (10, 0), (10, 10), (0, 10)]), @@ -317,6 +358,66 @@ def test_triangulate_polygon_segfault3(): triangles, points = triangulate_polygon_py(polygon) +def test_triangulate_polygon_segfault4(): + polygon = [ + [657.6875, 2280.975], + [657.6875, 2281.6125], + [657.05005, 2282.25], + [656.2, 2284.1626], + [657.6875, 2285.8625], + [658.11255, 2286.7126], + [659.8125, 2288.4126], + [659.8125, 2288.625], + [661.9375, 2290.5376], + [662.78754, 2290.9626], + [664.7, 2292.2375], + [665.3375, 2292.2375], + [665.97504, 2291.175], + [666.61255, 2290.5376], + [666.61255, 2289.6875], + [666.1875, 2288.625], + [664.48755, 2286.925], + [664.0625, 2286.925], + [663.2125, 2286.5], + [661.9375, 2284.8], + [660.66254, 2284.8], + [660.45, 2284.5876], + [660.45, 2284.1626], + [657.6875, 2281.1875], + ] + triangles, points = triangulate_polygon_py(polygon) + + +def test_triangulate_polygon_segfault5(): + polygon = [ + [895.05005, 2422.5], + [894.8375, 2422.7126], + [894.41254, 2422.7126], + [893.98755, 2423.1375], + [893.35004, 2423.1375], + [892.92505, 2423.5625], + [892.7125, 2423.5625], + [891.4375, 2424.8376], + [891.22504, 2424.8376], + [892.075, 2424.8376], + [892.5, 2425.05], + [893.35004, 2425.9001], + [893.775, 2426.75], + [893.775, 2427.3875], + [894.625, 2426.75], + [895.6875, 2426.75], + [896.11255, 2426.1125], + [896.11255, 2425.05], + [895.9, 2424.8376], + [896.53754, 2423.7751], + [896.53754, 2423.35], + [896.75, 2422.925], + [896.11255, 2422.7126], + [895.9, 2422.5], + ] + triangles, points = triangulate_polygon_py(polygon) + + @pytest.mark.parametrize( ('segment1', 'segment2'), [ From 9c7d4cb0f209f4c07645f94bc5d7b062e8cba9de Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 28 Nov 2024 01:04:06 +0100 Subject: [PATCH 58/76] add missed import --- src/PartSegCore_compiled_backend/triangulation/intersection.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp index 58740ad..42aafc2 100644 --- a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp @@ -15,6 +15,7 @@ #define PARTSEGCORE_INTERSECTION_H #include +#include #include #include #include From 3f5482fd1fb8a7e94e06c713fa796346e850e5c7 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 28 Nov 2024 01:58:02 +0100 Subject: [PATCH 59/76] fix remaining bug --- src/PartSegCore_compiled_backend/triangulate.pyx | 4 ++-- .../triangulation/intersection.hpp | 15 ++++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index 2278805..f54cb51 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -83,8 +83,8 @@ cdef extern from "triangulation/triangulate.hpp" namespace "partsegcore::triangu bool left_to_right(const Segment& s1, const Segment& s2) vector[Point] find_intersection_points(const vector[Point]& segments) vector[PointTriangle] triangulate_monotone_polygon(const MonotonePolygon& polygon) - pair[vector[Triangle], vector[Point]] triangulate_polygon(const vector[Point]& polygon) # except + - pair[vector[Triangle], vector[Point]] triangulate_polygon(const vector[vector[Point]]& polygon_list) # except + + pair[vector[Triangle], vector[Point]] triangulate_polygon(const vector[Point]& polygon) except + + pair[vector[Triangle], vector[Point]] triangulate_polygon(const vector[vector[Point]]& polygon_list) except + diff --git a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp index 42aafc2..311d821 100644 --- a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp @@ -330,13 +330,13 @@ inline std::unordered_set _find_intersections( inline std::vector _find_intersection(const point::Segment &s1, const point::Segment &s2) { // ReSharper disable CppJoinDeclarationAndAssignment - point::Point::coordinate_t a1, b1, c1, a2, b2, c2, det, x, y; + point::Point::coordinate_t a1, b1, c1, a2, b2, c2, det, x, y, t, u; a1 = s1.top.y - s1.bottom.y; b1 = s1.bottom.x - s1.top.x; - c1 = a1 * s1.bottom.x + b1 * s1.bottom.y; + // c1 = a1 * s1.bottom.x + b1 * s1.bottom.y; a2 = s2.top.y - s2.bottom.y; b2 = s2.bottom.x - s2.top.x; - c2 = a2 * s2.bottom.x + b2 * s2.bottom.y; + // c2 = a2 * s2.bottom.x + b2 * s2.bottom.y; det = a1 * b2 - a2 * b1; if (det == 0) { // collinear case @@ -347,8 +347,13 @@ inline std::vector _find_intersection(const point::Segment &s1, if (s2.point_on_line(s1.top)) res.push_back(s1.top); return res; } - x = (b2 * c1 - b1 * c2) / det; - y = (a1 * c2 - a2 * c1) / det; + t = ((s2.top.x - s1.top.x) * (s2.bottom.y - s2.top.y) - + (s2.top.y - s1.top.y) * (s2.bottom.x - s2.top.x)) / + det; + // u = ((s2.top.x - s1.top.x) * (s1.bottom.y - s1.top.y) - + // (s2.top.y - s1.top.y) * (s2.bottom.x - s2.top.x)) / det; + x = s1.top.x + t * b1; + y = s1.top.y + t * (-a1); return {{x, y}}; } } // namespace partsegcore::intersection From 15fb3ac80b5016c3812c019ba3f850bb7d021a6d Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 28 Nov 2024 11:37:50 +0100 Subject: [PATCH 60/76] cleanup code --- .../triangulation/intersection.hpp | 5 +-- .../triangulation/point.hpp | 2 +- .../triangulation/triangulate.hpp | 33 ++++++++----------- src/tests/test_triangulate.py | 23 +++---------- 4 files changed, 22 insertions(+), 41 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp index 311d821..3f48ce1 100644 --- a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp @@ -141,7 +141,7 @@ inline int _orientation(const point::Point &p, const point::Point &q, // is not defined for this case. " + oss.str()); // } // Instead of using classical equation, we need to use two variables - // to handle problem with strange behaviour on macOS. + // to handle problem with strange behavior on macOS. if (val1 == val2) return 0; return (val1 > val2) ? 1 : 2; } @@ -330,7 +330,8 @@ inline std::unordered_set _find_intersections( inline std::vector _find_intersection(const point::Segment &s1, const point::Segment &s2) { // ReSharper disable CppJoinDeclarationAndAssignment - point::Point::coordinate_t a1, b1, c1, a2, b2, c2, det, x, y, t, u; + point::Point::coordinate_t a1, b1, a2, b2, det, x, y, t; + // point::Point::coordinate_t c1, c2, u; a1 = s1.top.y - s1.bottom.y; b1 = s1.bottom.x - s1.top.x; // c1 = a1 * s1.bottom.x + b1 * s1.bottom.y; diff --git a/src/PartSegCore_compiled_backend/triangulation/point.hpp b/src/PartSegCore_compiled_backend/triangulation/point.hpp index ba930a5..d78fddc 100644 --- a/src/PartSegCore_compiled_backend/triangulation/point.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/point.hpp @@ -161,7 +161,7 @@ struct Segment { } // namespace partsegcore::point // overload of hash function for -// unordered map and set +// an unordered map and set namespace std { template <> struct hash { diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index 3dfdb92..4a04ea8 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -140,15 +140,6 @@ struct SegmentLeftRightComparator { } }; -struct PointMonotonePolygon { - MonotonePolygon *left_polygon = nullptr; - MonotonePolygon *right_polygon = nullptr; - PointMonotonePolygon() = default; - explicit PointMonotonePolygon(MonotonePolygon *left_polygon, - MonotonePolygon *right_polygon) - : left_polygon(left_polygon), right_polygon(right_polygon) {}; -}; - struct Triangle { std::size_t x; std::size_t y; @@ -163,6 +154,7 @@ struct PointTriangle { point::Point p3{}; PointTriangle(point::Point p1, point::Point p2, point::Point p3) { + /* Sort the points in clockwise order */ if ((p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x) < 0) { this->p1 = p3; this->p2 = p2; @@ -190,8 +182,6 @@ struct PointEdgeInfo { typedef std::unordered_map> PointToEdges; -typedef std::map - SegmentToLine; /** * Get a mapping from points to the list of edges that contain those points. @@ -972,10 +962,13 @@ inline std::vector _sorted_polygons_points( } /* - This is an implementation of sweeping line triangulation of polygon - Its assumes that there is no edge intersections, but may be a point with - more than 2 edges. described on this lecture: + This is an implementation of the polygon with sweeping lines. + It assumes that there are no edge intersections but may be a point with + more than 2 edges. + Described on this lecture: https://www.youtube.com/playlist?list=PLtTatrCwXHzEqzJMaTUFgqoCNllgwk4DH + and in the book: + de Berg,M. et al. (2008) Computational Geometry Springer Berlin Heidelberg. */ inline std::pair, std::vector> sweeping_line_triangulation( @@ -1005,7 +998,7 @@ sweeping_line_triangulation( builder.process_split_point(sorted_point); break; case PointType::MERGE: - // merge two sweeping lines to one + // merge two sweeping lines into one // merge two intervals into one // It may be the end of the polygon, then finish the // monotone polygon @@ -1013,9 +1006,8 @@ sweeping_line_triangulation( break; case PointType::INTERSECTION: // this is a merge and split point at the same time - // this is not described in original algorithm, - // but we need it to handle self intersecting polygons - // Remember about more than 4 edges case + // this is not described in the original algorithm, + // but we need it to handle self-intersecting polygons builder.process_intersection_point(sorted_point); break; case PointType::EMPTY: @@ -1040,7 +1032,8 @@ sweeping_line_triangulation( return std::make_pair(result, sorted_points); } -// calculate the triangulation of a symmetric difference of list of polygons +// calculate the triangulation out of a symmetric difference out of a list of +// polygons inline std::pair, std::vector> triangulate_polygon( @@ -1071,7 +1064,7 @@ triangulate_polygon( } // Implement the sweeping line algorithm for triangulation - // described on this lecture: + // described in this lecture: // https://www.youtube.com/playlist?list=PLtTatrCwXHzEqzJMaTUFgqoCNllgwk4DH // return sweeping_line_triangulation(find_intersection_points(polygon_list)); diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index 9fb1abf..1dd154c 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -244,19 +244,6 @@ def test_triangulate_polygon_numpy_li_non_convex(polygon, expected): assert triangles_ == expected -@pytest.mark.parametrize( - ('polygon', 'expected'), - [ - ([(0, 0), (0, 1), (1, 1), (1, 0), (0.5, 0.5)], [(0, 1, 2), (0, 3, 2)]), - ([(0, 0), (0, 1), (1, 1), (0.5, 0.5), (1, 0)], [(0, 1, 2), (0, 3, 2)]), - ([(0, 0), (0, 1), (0.5, 0.5), (1, 1), (1, 0)], [(0, 1, 2), (0, 3, 2)]), - ([(0, 0), (0.5, 0.5), (0, 1), (1, 1), (1, 0)], [(0, 1, 2), (0, 3, 2)]), - ], -) -def test_merge_points(polygon, expected): - triangles, points = triangulate_polygon_py(polygon) - - def test_triangulate_polygon_in_polygon_numpy(): polygons = [ np.array([(0, 0), (10, 0), (10, 10), (0, 10)]), @@ -295,7 +282,7 @@ def test_triangulate_polygon_segfault1(): (208.037506, 1491.5376), (205.912506, 1489.83752), ] - triangles, points = triangulate_polygon_py(polygon) + triangulate_polygon_py(polygon) def test_triangulate_polygon_segfault2(): @@ -325,7 +312,7 @@ def test_triangulate_polygon_segfault2(): [1390.175, 2745.2876], [1389.3251, 2744.4375], ] - triangles, points = triangulate_polygon_py(polygon) + triangulate_polygon_py(polygon) def test_triangulate_polygon_segfault3(): @@ -355,7 +342,7 @@ def test_triangulate_polygon_segfault3(): (1066.96252, 1794.77502), (1066.75, 1794.3501), ] - triangles, points = triangulate_polygon_py(polygon) + triangulate_polygon_py(polygon) def test_triangulate_polygon_segfault4(): @@ -385,7 +372,7 @@ def test_triangulate_polygon_segfault4(): [660.45, 2284.1626], [657.6875, 2281.1875], ] - triangles, points = triangulate_polygon_py(polygon) + triangulate_polygon_py(polygon) def test_triangulate_polygon_segfault5(): @@ -415,7 +402,7 @@ def test_triangulate_polygon_segfault5(): [896.11255, 2422.7126], [895.9, 2422.5], ] - triangles, points = triangulate_polygon_py(polygon) + triangulate_polygon_py(polygon) @pytest.mark.parametrize( From 688cc971095a8bac51f53ffafc98f1fe43d09d2a Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 28 Nov 2024 11:44:41 +0100 Subject: [PATCH 61/76] more clanup --- src/PartSegCore_compiled_backend/triangulate.pyx | 10 +++++----- .../triangulation/intersection.hpp | 2 ++ .../triangulation/triangulate.hpp | 7 ++++--- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index f54cb51..a7fedad 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -83,8 +83,8 @@ cdef extern from "triangulation/triangulate.hpp" namespace "partsegcore::triangu bool left_to_right(const Segment& s1, const Segment& s2) vector[Point] find_intersection_points(const vector[Point]& segments) vector[PointTriangle] triangulate_monotone_polygon(const MonotonePolygon& polygon) - pair[vector[Triangle], vector[Point]] triangulate_polygon(const vector[Point]& polygon) except + - pair[vector[Triangle], vector[Point]] triangulate_polygon(const vector[vector[Point]]& polygon_list) except + + pair[vector[Triangle], vector[Point]] triangulate_polygon_face(const vector[Point]& polygon) except + + pair[vector[Triangle], vector[Point]] triangulate_polygon_face(const vector[vector[Point]]& polygon_list) except + @@ -237,7 +237,7 @@ def triangulate_polygon_py(polygon: Sequence[Sequence[float]]) -> tuple[list[tup # prevent from adding polygon edge of width 0 polygon_vector.push_back(p2) - result = triangulate_polygon(polygon_vector) + result = triangulate_polygon_face(polygon_vector) return [(triangle.x, triangle.y, triangle.z) for triangle in result.first], [(point.x, point.y) for point in result.second] @@ -256,7 +256,7 @@ def triangulate_polygon_numpy(polygon: np.ndarray) -> tuple[np.ndarray, np.ndarr # prevent from adding polygon edge of width 0 polygon_vector.push_back(p2) - result = triangulate_polygon(polygon_vector) + result = triangulate_polygon_face(polygon_vector) return ( np.array([(triangle.x, triangle.y, triangle.z) for triangle in result.first], dtype=np.uintp), np.array([(point.x, point.y) for point in result.second], dtype=np.float32) @@ -285,7 +285,7 @@ def triangulate_polygon_numpy_li(polygon_li: list[np.ndarray]) -> tuple[np.ndarr polygon_vector.push_back(p2) polygon_vector_list.push_back(polygon_vector) - result = triangulate_polygon(polygon_vector_list) + result = triangulate_polygon_face(polygon_vector_list) return ( np.array([(triangle.x, triangle.y, triangle.z) for triangle in result.first], dtype=np.uintp), np.array([(point.x, point.y) for point in result.second], dtype=np.float32) diff --git a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp index 3f48ce1..dbbc245 100644 --- a/src/PartSegCore_compiled_backend/triangulation/intersection.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/intersection.hpp @@ -353,6 +353,8 @@ inline std::vector _find_intersection(const point::Segment &s1, det; // u = ((s2.top.x - s1.top.x) * (s1.bottom.y - s1.top.y) - // (s2.top.y - s1.top.y) * (s2.bottom.x - s2.top.x)) / det; + if (t < 0) return {s1.top}; + if (t > 1) return {s1.bottom}; x = s1.top.x + t * b1; y = s1.top.y + t * (-a1); return {{x, y}}; diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index 4a04ea8..6b8b550 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -1036,7 +1036,7 @@ sweeping_line_triangulation( // polygons inline std::pair, std::vector> -triangulate_polygon( +triangulate_polygon_face( const std::vector> &polygon_list) { if (polygon_list.empty()) // empty list @@ -1071,11 +1071,12 @@ triangulate_polygon( } inline std::pair, std::vector> -triangulate_polygon(const std::vector &polygon) { +triangulate_polygon_face(const std::vector &polygon) { // #if DDEBUG // try{ // #endif - return triangulate_polygon(std::vector>({polygon})); + return triangulate_polygon_face( + std::vector>({polygon})); // #if DDEBUG // } catch (const std::exception &e) { // std::cerr << "Polygon: ["; From ac8b7d101bb78cc7f3e5edaf2bb85030cc24cdb9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 28 Nov 2024 15:25:55 +0100 Subject: [PATCH 62/76] add initial implementation of triangulating edge --- .../triangulation/point.hpp | 49 ++++++ .../triangulation/triangulate.hpp | 151 ++++++++++++++++++ 2 files changed, 200 insertions(+) diff --git a/src/PartSegCore_compiled_backend/triangulation/point.hpp b/src/PartSegCore_compiled_backend/triangulation/point.hpp index d78fddc..8721090 100644 --- a/src/PartSegCore_compiled_backend/triangulation/point.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/point.hpp @@ -11,6 +11,8 @@ namespace partsegcore::point { +struct Vector; + /* Point class with x and y coordinates */ struct Point { typedef float coordinate_t; @@ -29,6 +31,22 @@ struct Point { return this->y < p.y; } + // add operator + Vector operator+(const Point &p) const; + + // subtract operator + Vector operator-(const Point &p) const; + + Point operator+(const Vector &v) const; + + // Point operator/(coordinate_t f) const { + // return {this->x / f, this->y / f}; + // } + // + // Point operator*(coordinate_t f) const { + // return {this->x * f, this->y * f}; + // } + // Overload the << operator for Point friend std::ostream &operator<<(std::ostream &os, const Point &point) { os << "(x=" << point.x << ", y=" << point.y << ")"; @@ -42,6 +60,37 @@ struct Point { }; }; +struct Vector { + typedef Point::coordinate_t coordinate_t; + coordinate_t x; + coordinate_t y; + Vector(coordinate_t x, coordinate_t y) : x(x), y(y) {} + Vector() = default; + explicit Vector(const Point &p) : x(p.x), y(p.y) {} + + Vector operator+(const Vector &v) const { + return {this->x + v.x, this->y + v.y}; + } + Vector operator-(const Vector &v) const { + return {this->x - v.x, this->y - v.y}; + } + Vector operator/(coordinate_t f) const { return {this->x / f, this->y / f}; } + Vector operator*(coordinate_t f) const { return {this->x * f, this->y * f}; } + Vector operator-() const { return {-this->x, -this->y}; } +}; + +Vector Point::operator+(const Point &p) const { + return {this->x + p.x, this->y + p.y}; +} + +Vector Point::operator-(const Point &p) const { + return {this->x - p.x, this->y - p.y}; +} + +Point Point::operator+(const Vector &v) const { + return {this->x + v.x, this->y + v.y}; +} + /*Struct to represent edge of polygon with points ordered*/ struct Segment { Point top{}; diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index 6b8b550..fbd3a64 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -2,6 +2,7 @@ #define PARTSEGCORE_TRIANGULATE_H #include +#include #include #include #include @@ -1090,6 +1091,156 @@ triangulate_polygon_face(const std::vector &polygon) { // #endif } +inline point::Point::coordinate_t vector_length(point::Point p1, + point::Point p2) { + return std::sqrt((p1.x - p2.x) * (p1.x - p2.x) + + (p1.y - p2.y) * (p1.y - p2.y)); +} + +struct PathTriangulation { + std::vector triangles; + std::vector centers; + std::vector offsets; + + void reserve(std::size_t size) { + triangles.reserve(size); + centers.reserve(size); + offsets.reserve(size); + } +}; + +inline point::Point::coordinate_t add_triangles_for_join( + PathTriangulation &triangles, point::Point p1, point::Point p2, + point::Point p3, point::Point::coordinate_t prev_length, double cos_limit, + bool bevel) { + std::size_t idx; + point::Point::coordinate_t scale_factor; + point::Point::coordinate_t estimated_len; + point::Vector mitter(0, 0); + point::Point::coordinate_t length = vector_length(p2, p3); + point::Vector p2_p1_diff_norm = (p2 - p1) / prev_length; + point::Vector p2_p3_diff_norm = (p2 - p3) / length; + + point::Point::coordinate_t cos_angle = p2_p1_diff_norm.x * p2_p3_diff_norm.x + + p2_p1_diff_norm.y * p2_p3_diff_norm.y; + point::Point::coordinate_t sin_angle = p2_p1_diff_norm.x * p2_p3_diff_norm.y - + p2_p1_diff_norm.y * p2_p3_diff_norm.x; + + triangles.centers.push_back(p2); + triangles.centers.push_back(p2); + + if (sin_angle == 0) { + mitter = {p2_p1_diff_norm.y / 2, -p2_p1_diff_norm.x / 2}; + } else { + scale_factor = 1 / sin_angle; + if (bevel || cos_angle < cos_limit) { + /* Bevel join + * There is a need to check if inner vector is not to long + * See https://github.com/napari/napari/pull/7268#user-content-bevel-cut + */ + estimated_len = scale_factor; + if (prev_length < length) { + if (estimated_len > prev_length) { + estimated_len = prev_length; + } else if (estimated_len < -prev_length) { + estimated_len = -prev_length; + } + } else { + if (estimated_len > length) { + estimated_len = length; + } else if (estimated_len < -length) { + estimated_len = -length; + } + } + } + mitter = p2_p1_diff_norm + p2_p3_diff_norm * scale_factor * 0.5; + }; + if (bevel || cos_angle < cos_limit) { + triangles.centers.push_back(p2); + idx = triangles.offsets.size() + 1; + triangles.triangles.emplace_back(idx, idx + 1, idx + 2); + if (sin_angle < 0) { + triangles.offsets.push_back(mitter); + triangles.offsets.emplace_back(-p2_p1_diff_norm.y * 0.5, + p2_p1_diff_norm.x * 0.5); + triangles.offsets.emplace_back(-p2_p3_diff_norm.y * 0.5, + p2_p3_diff_norm.x * 0.5); + triangles.triangles.emplace_back(idx, idx + 2, idx + 3); + triangles.triangles.emplace_back(idx + 2, idx + 3, idx + 4); + } else { + triangles.offsets.emplace_back(p2_p1_diff_norm.y * 0.5, + -p2_p1_diff_norm.x * 0.5); + triangles.offsets.push_back(mitter); + triangles.offsets.emplace_back(p2_p3_diff_norm.y * 0.5, + -p2_p3_diff_norm.x * 0.5); + triangles.triangles.emplace_back(idx + 1, idx + 2, idx + 3); + triangles.triangles.emplace_back(idx + 1, idx + 3, idx + 4); + } + } else { + triangles.offsets.push_back(mitter); + triangles.offsets.push_back(-mitter); + } + + return length; +} + +inline PathTriangulation triangulate_path_edge( + const std::vector &path, bool closed, float limit, + bool bevel) { + if (path.size() < 2) + return {{{0, 1, 3}, {1, 3, 2}}, + {path[0], path[0], path[0], path[0]}, + {{0, 0}, {0, 0}, {0, 0}, {0, 0}}}; + PathTriangulation result; + point::Vector norm_diff(0, 0); + result.reserve(path.size() * 3); + float cos_limit = 1 / (limit * limit / 2) - 1; + point::Point::coordinate_t prev_length = 1; + + if (closed) { + prev_length = vector_length(path[0], path[path.size() - 1]); + prev_length = + add_triangles_for_join(result, path[path.size() - 1], path[0], path[1], + prev_length, cos_limit, bevel); + } else { + prev_length = vector_length(path[0], path[1]); + norm_diff = (path[1] - path[0]) / prev_length; + result.centers.push_back(path[0]); + result.centers.push_back(path[0]); + result.offsets.emplace_back(norm_diff.y * 0.5, -norm_diff.x * 0.5); + result.offsets.push_back(-result.offsets.back()); + result.triangles.emplace_back(0, 1, 2); + result.triangles.emplace_back(1, 2, 3); + } + for (std::size_t i = 1; i < path.size() - 1; i++) { + prev_length = + add_triangles_for_join(result, path[i - 1], path[i], path[i + 1], + prev_length, cos_limit, bevel); + } + if (closed) { + add_triangles_for_join(result, path[path.size() - 2], path[path.size() - 1], + path[0], prev_length, cos_limit, bevel); + result.centers.push_back(result.centers[0]); + result.centers.push_back(result.centers[0]); + result.offsets.push_back(result.offsets[0]); + result.offsets.push_back(result.offsets[1]); + } else { + norm_diff = (path[path.size() - 1] - path[path.size() - 2]) / prev_length; + result.centers.push_back(path[path.size() - 1]); + result.centers.push_back(path[path.size() - 1]); + result.offsets.emplace_back(norm_diff.y * 0.5, -norm_diff.x * 0.5); + result.offsets.push_back(-result.offsets.back()); + result.triangles.emplace_back(result.centers.size() - 4, + result.centers.size() - 3, + result.centers.size() - 2); + result.triangles.emplace_back(result.centers.size() - 3, + result.centers.size() - 2, + result.centers.size() - 1); + } + + return result; +} + } // namespace partsegcore::triangulation #endif // PARTSEGCORE_TRIANGULATE_H From d5ae2db544b2e8e9bcb5176ef500ade13c9dab16 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 28 Nov 2024 16:52:43 +0100 Subject: [PATCH 63/76] initialimplementation of edge triangulation --- .../triangulate.pyx | 28 ++++++++++++++ .../triangulation/triangulate.hpp | 37 ++++++++----------- src/tests/test_triangulate.py | 25 +++++++++++++ 3 files changed, 69 insertions(+), 21 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index a7fedad..477db4b 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -25,6 +25,10 @@ cdef extern from "triangulation/point.hpp" namespace "partsegcore::point": bool operator==(const Point& other) const bool operator!=(const Point& other) const + cdef cppclass Vector: + float x; + float y; + cdef cppclass Segment: Point bottom Point top @@ -77,6 +81,11 @@ cdef extern from "triangulation/triangulate.hpp" namespace "partsegcore::triangu Point p2 Point p3 + cdef cppclass PathTriangulation: + vector[Triangle] triangles + vector[Point] centers + vector[Vector] offsets + bool _is_convex(const vector[Point]& polygon) vector[Triangle] _triangle_convex_polygon(const vector[Point]& polygon) @@ -85,6 +94,7 @@ cdef extern from "triangulation/triangulate.hpp" namespace "partsegcore::triangu vector[PointTriangle] triangulate_monotone_polygon(const MonotonePolygon& polygon) pair[vector[Triangle], vector[Point]] triangulate_polygon_face(const vector[Point]& polygon) except + pair[vector[Triangle], vector[Point]] triangulate_polygon_face(const vector[vector[Point]]& polygon_list) except + + PathTriangulation triangulate_path_edge(const vector[Point]& path, bool closed, float limit, bool bevel) except + @@ -332,3 +342,21 @@ def triangulate_monotone_polygon_py(top: Sequence[float], bottom: Sequence[float [(triangle.p1.x, triangle.p1.y), (triangle.p2.x, triangle.p2.y), (triangle.p3.x, triangle.p3.y)] for triangle in result ] + + +def triangulate_path_edge_py(path: Sequence[Sequence[float]], closed: bool=False, limit: float=3.0, bevel: bool=False) -> tuple[np.ndarray, np.ndarray, np.ndarray]: + """ Triangulate path""" + cdef vector[Point] path_vector + cdef PathTriangulation result + cdef Point p1, p2 + + path_vector.reserve(len(path)) + for point in path: + path_vector.push_back(Point(point[0], point[1])) + + result = triangulate_path_edge(path_vector, closed, limit, bevel) + return ( + np.array([(triangle.x, triangle.y, triangle.z) for triangle in result.triangles], dtype=np.uintp), + np.array([(point.x, point.y) for point in result.centers], dtype=np.float32), + np.array([(offset.x, offset.y) for offset in result.offsets], dtype=np.float32) + ) diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index fbd3a64..6e74fdf 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -895,7 +895,7 @@ inline std::vector> find_intersection_points( edges[intersection.second].point_projection_factor(inter_point), inter_point); } - }; + } for (auto &intersections_point : intersections_points) { // points_count += intersections_point.second.size() - 1; auto edge = edges[intersections_point.first]; @@ -1113,24 +1113,24 @@ inline point::Point::coordinate_t add_triangles_for_join( PathTriangulation &triangles, point::Point p1, point::Point p2, point::Point p3, point::Point::coordinate_t prev_length, double cos_limit, bool bevel) { - std::size_t idx; + std::size_t idx = triangles.offsets.size() + 1; point::Point::coordinate_t scale_factor; point::Point::coordinate_t estimated_len; point::Vector mitter(0, 0); point::Point::coordinate_t length = vector_length(p2, p3); - point::Vector p2_p1_diff_norm = (p2 - p1) / prev_length; + point::Vector p1_p2_diff_norm = (p1 - p2) / prev_length; point::Vector p2_p3_diff_norm = (p2 - p3) / length; - point::Point::coordinate_t cos_angle = p2_p1_diff_norm.x * p2_p3_diff_norm.x + - p2_p1_diff_norm.y * p2_p3_diff_norm.y; - point::Point::coordinate_t sin_angle = p2_p1_diff_norm.x * p2_p3_diff_norm.y - - p2_p1_diff_norm.y * p2_p3_diff_norm.x; + point::Point::coordinate_t cos_angle = p1_p2_diff_norm.x * p2_p3_diff_norm.x + + p1_p2_diff_norm.y * p2_p3_diff_norm.y; + point::Point::coordinate_t sin_angle = p1_p2_diff_norm.x * p2_p3_diff_norm.y - + p1_p2_diff_norm.y * p2_p3_diff_norm.x; triangles.centers.push_back(p2); triangles.centers.push_back(p2); if (sin_angle == 0) { - mitter = {p2_p1_diff_norm.y / 2, -p2_p1_diff_norm.x / 2}; + mitter = {p1_p2_diff_norm.y / 2, -p1_p2_diff_norm.x / 2}; } else { scale_factor = 1 / sin_angle; if (bevel || cos_angle < cos_limit) { @@ -1153,23 +1153,22 @@ inline point::Point::coordinate_t add_triangles_for_join( } } } - mitter = p2_p1_diff_norm + p2_p3_diff_norm * scale_factor * 0.5; + mitter = p1_p2_diff_norm + p2_p3_diff_norm * scale_factor * 0.5; }; if (bevel || cos_angle < cos_limit) { triangles.centers.push_back(p2); - idx = triangles.offsets.size() + 1; triangles.triangles.emplace_back(idx, idx + 1, idx + 2); if (sin_angle < 0) { triangles.offsets.push_back(mitter); - triangles.offsets.emplace_back(-p2_p1_diff_norm.y * 0.5, - p2_p1_diff_norm.x * 0.5); + triangles.offsets.emplace_back(-p1_p2_diff_norm.y * 0.5, + p1_p2_diff_norm.x * 0.5); triangles.offsets.emplace_back(-p2_p3_diff_norm.y * 0.5, p2_p3_diff_norm.x * 0.5); triangles.triangles.emplace_back(idx, idx + 2, idx + 3); triangles.triangles.emplace_back(idx + 2, idx + 3, idx + 4); } else { - triangles.offsets.emplace_back(p2_p1_diff_norm.y * 0.5, - -p2_p1_diff_norm.x * 0.5); + triangles.offsets.emplace_back(p1_p2_diff_norm.y * 0.5, + -p1_p2_diff_norm.x * 0.5); triangles.offsets.push_back(mitter); triangles.offsets.emplace_back(p2_p3_diff_norm.y * 0.5, -p2_p3_diff_norm.x * 0.5); @@ -1179,6 +1178,8 @@ inline point::Point::coordinate_t add_triangles_for_join( } else { triangles.offsets.push_back(mitter); triangles.offsets.push_back(-mitter); + triangles.triangles.emplace_back(idx, idx + 1, idx + 2); + triangles.triangles.emplace_back(idx + 1, idx + 2, idx + 3); } return length; @@ -1194,7 +1195,7 @@ inline PathTriangulation triangulate_path_edge( PathTriangulation result; point::Vector norm_diff(0, 0); result.reserve(path.size() * 3); - float cos_limit = 1 / (limit * limit / 2) - 1; + float cos_limit = 1.0 / (limit * limit / 2) - 1.0; point::Point::coordinate_t prev_length = 1; if (closed) { @@ -1230,12 +1231,6 @@ inline PathTriangulation triangulate_path_edge( result.centers.push_back(path[path.size() - 1]); result.offsets.emplace_back(norm_diff.y * 0.5, -norm_diff.x * 0.5); result.offsets.push_back(-result.offsets.back()); - result.triangles.emplace_back(result.centers.size() - 4, - result.centers.size() - 3, - result.centers.size() - 2); - result.triangles.emplace_back(result.centers.size() - 3, - result.centers.size() - 2, - result.centers.size() - 1); } return result; diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index 1dd154c..a5ecd20 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -12,6 +12,7 @@ segment_left_to_right_comparator, triangle_convex_polygon, triangulate_monotone_polygon_py, + triangulate_path_edge_py, triangulate_polygon_numpy, triangulate_polygon_numpy_li, triangulate_polygon_py, @@ -555,3 +556,27 @@ def test_find_intersection_points_py_cross_intersect_in_point(): ) def test_triangulate_monotone_polygon_py(polygon, expected): assert triangulate_monotone_polygon_py(*polygon) == expected + + +@pytest.mark.parametrize( + ('path', 'closed', 'bevel', 'expected'), + [ + # ([[0, 0], [0, 10], [10, 10], [10, 0]], True, False, 10), + # ([[0, 0], [0, 10], [10, 10], [10, 0]], False, False, 8), + # ([[0, 0], [0, 10], [10, 10], [10, 0]], True, True, 14), + # ([[0, 0], [0, 10], [10, 10], [10, 0]], False, True, 10), + ([[2, 10], [0, -5], [-2, 10], [-2, -10], [2, -10]], True, False, 15), + ([[0, 0], [0, 10]], False, False, 4), + ([[0, 0], [0, 10], [0, 20]], False, False, 6), + ([[0, 0], [0, 2], [10, 1]], True, False, 9), + ([[0, 0], [10, 1], [9, 1.1]], False, False, 7), + ([[9, 0.9], [10, 1], [0, 2]], False, False, 7), + ([[0, 0], [-10, 1], [-9, 1.1]], False, False, 7), + ([[-9, 0.9], [-10, 1], [0, 2]], False, False, 7), + ], +) +def test_triangulate_path_edge_py(path, closed, bevel, expected): + triangles, centers, offsets = triangulate_path_edge_py(np.array(path, dtype='float32'), closed=closed, bevel=bevel) + assert centers.shape == offsets.shape + assert centers.shape[0] == expected + assert triangles.shape[0] == expected - 2 From 065ffa526273435d2577c0c53859b0571d98ed20 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Fri, 29 Nov 2024 01:05:03 +0100 Subject: [PATCH 64/76] add test and fix triangles --- .../triangulation/triangulate.hpp | 35 +++++-- src/tests/test_triangulate.py | 93 ++++++++++++++++--- 2 files changed, 104 insertions(+), 24 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index 6e74fdf..6667f73 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -1107,19 +1107,34 @@ struct PathTriangulation { centers.reserve(size); offsets.reserve(size); } + + void fix_triangle_orientation() { + point::Point p1, p2, p3; + for (auto &triangle : triangles) { + p1 = centers[triangle.x] + offsets[triangle.x]; + p2 = centers[triangle.y] + offsets[triangle.y]; + p3 = centers[triangle.z] + offsets[triangle.z]; + if ((p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x) < 0) { + std::swap(triangle.x, triangle.z); + } + // if (intersection::_orientation(p1, p2, p3) == 1) { + // std::swap(triangle.x, triangle.z); + // } + } + } }; inline point::Point::coordinate_t add_triangles_for_join( PathTriangulation &triangles, point::Point p1, point::Point p2, point::Point p3, point::Point::coordinate_t prev_length, double cos_limit, bool bevel) { - std::size_t idx = triangles.offsets.size() + 1; + std::size_t idx = triangles.offsets.size(); point::Point::coordinate_t scale_factor; point::Point::coordinate_t estimated_len; point::Vector mitter(0, 0); point::Point::coordinate_t length = vector_length(p2, p3); - point::Vector p1_p2_diff_norm = (p1 - p2) / prev_length; - point::Vector p2_p3_diff_norm = (p2 - p3) / length; + point::Vector p1_p2_diff_norm = (p2 - p1) / prev_length; + point::Vector p2_p3_diff_norm = (p3 - p2) / length; point::Point::coordinate_t cos_angle = p1_p2_diff_norm.x * p2_p3_diff_norm.x + p1_p2_diff_norm.y * p2_p3_diff_norm.y; @@ -1141,19 +1156,19 @@ inline point::Point::coordinate_t add_triangles_for_join( estimated_len = scale_factor; if (prev_length < length) { if (estimated_len > prev_length) { - estimated_len = prev_length; + scale_factor = prev_length * 0.5; } else if (estimated_len < -prev_length) { - estimated_len = -prev_length; + scale_factor = -prev_length * 0.5; } } else { if (estimated_len > length) { - estimated_len = length; + scale_factor = length * 0.5; } else if (estimated_len < -length) { - estimated_len = -length; + scale_factor = -length * 0.5; } } } - mitter = p1_p2_diff_norm + p2_p3_diff_norm * scale_factor * 0.5; + mitter = (p1_p2_diff_norm - p2_p3_diff_norm) * scale_factor * 0.5; }; if (bevel || cos_angle < cos_limit) { triangles.centers.push_back(p2); @@ -1169,7 +1184,7 @@ inline point::Point::coordinate_t add_triangles_for_join( } else { triangles.offsets.emplace_back(p1_p2_diff_norm.y * 0.5, -p1_p2_diff_norm.x * 0.5); - triangles.offsets.push_back(mitter); + triangles.offsets.push_back(-mitter); triangles.offsets.emplace_back(p2_p3_diff_norm.y * 0.5, -p2_p3_diff_norm.x * 0.5); triangles.triangles.emplace_back(idx + 1, idx + 2, idx + 3); @@ -1232,7 +1247,7 @@ inline PathTriangulation triangulate_path_edge( result.offsets.emplace_back(norm_diff.y * 0.5, -norm_diff.x * 0.5); result.offsets.push_back(-result.offsets.back()); } - + result.fix_triangle_orientation(); return result; } diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index a5ecd20..79fb9d4 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -559,24 +559,89 @@ def test_triangulate_monotone_polygon_py(polygon, expected): @pytest.mark.parametrize( - ('path', 'closed', 'bevel', 'expected'), + ('path', 'closed', 'bevel', 'expected', 'exp_triangles'), [ - # ([[0, 0], [0, 10], [10, 10], [10, 0]], True, False, 10), - # ([[0, 0], [0, 10], [10, 10], [10, 0]], False, False, 8), - # ([[0, 0], [0, 10], [10, 10], [10, 0]], True, True, 14), - # ([[0, 0], [0, 10], [10, 10], [10, 0]], False, True, 10), - ([[2, 10], [0, -5], [-2, 10], [-2, -10], [2, -10]], True, False, 15), - ([[0, 0], [0, 10]], False, False, 4), - ([[0, 0], [0, 10], [0, 20]], False, False, 6), - ([[0, 0], [0, 2], [10, 1]], True, False, 9), - ([[0, 0], [10, 1], [9, 1.1]], False, False, 7), - ([[9, 0.9], [10, 1], [0, 2]], False, False, 7), - ([[0, 0], [-10, 1], [-9, 1.1]], False, False, 7), - ([[-9, 0.9], [-10, 1], [0, 2]], False, False, 7), + ( + [[0, 0], [0, 10], [10, 10], [10, 0]], + True, + False, + 10, + [[2, 1, 0], [1, 2, 3], [4, 3, 2], [3, 4, 5], [6, 5, 4], [5, 6, 7], [8, 7, 6], [7, 8, 9]], + ), + ( + [[0, 0], [0, 10], [10, 10], [10, 0]], + False, + False, + 8, + [[2, 1, 0], [1, 2, 3], [4, 3, 2], [3, 4, 5], [6, 5, 4], [5, 6, 7]], + ), + ( + [[0, 0], [0, 10], [10, 10], [10, 0]], + True, + True, + 14, + [ + [2, 1, 0], + [3, 2, 0], + [2, 3, 4], + [5, 4, 3], + [6, 5, 3], + [5, 6, 7], + [8, 7, 6], + [9, 8, 6], + [8, 9, 10], + [11, 10, 9], + [12, 11, 9], + [11, 12, 13], + ], + ), + ( + [[0, 0], [0, 10], [10, 10], [10, 0]], + False, + True, + 10, + [[2, 1, 0], [1, 2, 3], [4, 3, 2], [5, 4, 2], [4, 5, 6], [7, 6, 5], [8, 7, 5], [7, 8, 9]], + ), + ( + [[2, 10], [0, -5], [-2, 10], [-2, -10], [2, -10]], + True, + False, + 15, + [ + [2, 1, 0], + [1, 2, 3], + [1, 3, 4], + [5, 4, 3], + [6, 5, 3], + [5, 6, 7], + [8, 7, 6], + [7, 8, 9], + [7, 9, 10], + [11, 10, 9], + [10, 11, 12], + [13, 12, 11], + [12, 13, 14], + ], + ), + ([[0, 0], [0, 10]], False, False, 4, [[2, 1, 0], [1, 2, 3]]), + ([[0, 0], [0, 10], [0, 20]], False, False, 6, [[2, 1, 0], [1, 2, 3], [4, 3, 2], [3, 4, 5]]), + ( + [[0, 0], [0, 2], [10, 1]], + True, + False, + 9, + [[2, 1, 0], [1, 2, 3], [4, 3, 2], [3, 4, 5], [6, 5, 4], [7, 6, 4], [6, 7, 8]], + ), + ([[0, 0], [10, 1], [9, 1.1]], False, False, 7, [[2, 1, 0], [1, 2, 3], [4, 3, 2], [3, 4, 5], [3, 5, 6]]), + ([[9, 0.9], [10, 1], [0, 2]], False, False, 7, [[2, 1, 0], [1, 2, 3], [4, 3, 2], [3, 4, 5], [3, 5, 6]]), + ([[0, 0], [-10, 1], [-9, 1.1]], False, False, 7, [[2, 1, 0], [1, 2, 3], [4, 3, 2], [5, 4, 2], [4, 5, 6]]), + ([[-9, 0.9], [-10, 1], [0, 2]], False, False, 7, [[2, 1, 0], [1, 2, 3], [4, 3, 2], [5, 4, 2], [4, 5, 6]]), ], ) -def test_triangulate_path_edge_py(path, closed, bevel, expected): +def test_triangulate_path_edge_py(path, closed, bevel, expected, exp_triangles): triangles, centers, offsets = triangulate_path_edge_py(np.array(path, dtype='float32'), closed=closed, bevel=bevel) assert centers.shape == offsets.shape assert centers.shape[0] == expected assert triangles.shape[0] == expected - 2 + triangles_li = [[int(y) for y in x] for x in triangles] + assert triangles_li == exp_triangles From 1506c87d1aa8d5bcc68cdf8d40752612d26b9168 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Fri, 29 Nov 2024 13:00:24 +0100 Subject: [PATCH 65/76] add triangulation of edges --- .../triangulate.pyx | 48 +++++++++++++++++-- src/tests/test_triangulate.py | 13 ++++- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index 477db4b..00afd12 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -288,11 +288,13 @@ def triangulate_polygon_numpy_li(polygon_li: list[np.ndarray]) -> tuple[np.ndarr polygon_vector.push_back(Point(polygon[0, 0], polygon[0, 1])) for point in polygon[1:]: - p1 = polygon_vector[polygon_vector.size() - 1] + p1 = polygon_vector.back() p2 = Point(point[0], point[1]) if p1 != p2: # prevent from adding polygon edge of width 0 polygon_vector.push_back(p2) + if polygon_vector.size() > 1 and polygon_vector.front() == polygon_vector.back(): + polygon_vector.pop_back() polygon_vector_list.push_back(polygon_vector) result = triangulate_polygon_face(polygon_vector_list) @@ -356,7 +358,47 @@ def triangulate_path_edge_py(path: Sequence[Sequence[float]], closed: bool=False result = triangulate_path_edge(path_vector, closed, limit, bevel) return ( - np.array([(triangle.x, triangle.y, triangle.z) for triangle in result.triangles], dtype=np.uintp), np.array([(point.x, point.y) for point in result.centers], dtype=np.float32), - np.array([(offset.x, offset.y) for offset in result.offsets], dtype=np.float32) + np.array([(offset.x, offset.y) for offset in result.offsets], dtype=np.float32), + np.array([(triangle.x, triangle.y, triangle.z) for triangle in result.triangles], dtype=np.uintp), + ) + + +def triangulate_polygon_with_edge_numpy_li(polygon_li: list[np.ndarray]) -> tuple[tuple[np.ndarray, np.ndarray], tuple[np.ndarray, np.ndarray, np.ndarray]]: + """ Triangulate polygon""" + cdef vector[Point] polygon_vector + cdef vector[vector[Point]] polygon_vector_list + cdef Point p1, p2 + cdef pair[vector[Triangle], vector[Point]] result + cdef vector[PathTriangulation] edge_result + + polygon_vector_list.reserve(len(polygon_li)) + for polygon in polygon_li: + polygon_vector.clear() + + polygon_vector.reserve(polygon.shape[0]) + polygon_vector.push_back(Point(polygon[0, 0], polygon[0, 1])) + + for point in polygon[1:]: + p1 = polygon_vector.back() + p2 = Point(point[0], point[1]) + if p1 != p2: + # prevent from adding polygon edge of width 0 + polygon_vector.push_back(p2) + if polygon_vector.size() > 1 and polygon_vector.front() == polygon_vector.back(): + polygon_vector.pop_back() + polygon_vector_list.push_back(polygon_vector) + edge_result.push_back(triangulate_path_edge(polygon_vector, True, 3.0, False)) + + + result = triangulate_polygon_face(polygon_vector_list) + return (( + np.array([(triangle.x, triangle.y, triangle.z) for triangle in result.first], dtype=np.uintp), + np.array([(point.x, point.y) for point in result.second], dtype=np.float32) + ), + ( + np.array([(point.x, point.y) for res in edge_result for point in res.centers], dtype=np.float32), + np.array([(offset.x, offset.y) for res in edge_result for offset in res.offsets], dtype=np.float32), + np.array([(triangle.x, triangle.y, triangle.z) for res in edge_result for triangle in res.triangles], dtype=np.uintp), + ) ) diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index 79fb9d4..33f80a3 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -16,6 +16,7 @@ triangulate_polygon_numpy, triangulate_polygon_numpy_li, triangulate_polygon_py, + triangulate_polygon_with_edge_numpy_li, ) @@ -639,9 +640,19 @@ def test_triangulate_monotone_polygon_py(polygon, expected): ], ) def test_triangulate_path_edge_py(path, closed, bevel, expected, exp_triangles): - triangles, centers, offsets = triangulate_path_edge_py(np.array(path, dtype='float32'), closed=closed, bevel=bevel) + centers, offsets, triangles = triangulate_path_edge_py(np.array(path, dtype='float32'), closed=closed, bevel=bevel) assert centers.shape == offsets.shape assert centers.shape[0] == expected assert triangles.shape[0] == expected - 2 triangles_li = [[int(y) for y in x] for x in triangles] assert triangles_li == exp_triangles + + +@pytest.mark.parametrize(('polygon', 'expected'), TEST_POLYGONS) +def test_triangulate_polygon_with_edge_numpy_li(polygon, expected): + (triangles, points), (centers, offsets, edge_triangles) = triangulate_polygon_with_edge_numpy_li( + [np.array(polygon)] + ) + triangles_ = _renumerate_triangles(polygon, points, triangles) + assert triangles_ == expected + assert centers.shape == offsets.shape From 3af73024bd9c21237b1afc8a7acb48a8eaf78554 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Thu, 5 Dec 2024 01:03:21 +0100 Subject: [PATCH 66/76] addres part of remarks from code review --- .../triangulation/point.hpp | 14 +++++------ .../triangulation/triangulate.hpp | 25 ++++++++----------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulation/point.hpp b/src/PartSegCore_compiled_backend/triangulation/point.hpp index 8721090..e126b28 100644 --- a/src/PartSegCore_compiled_backend/triangulation/point.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/point.hpp @@ -55,7 +55,7 @@ struct Point { struct PointHash { std::size_t operator()(const Point &p) const { - return std::hash()(p.x) * 31 + std::hash()(p.y); + return std::hash()(p.x) ^ (std::hash()(p.y) << 1); } }; }; @@ -79,15 +79,15 @@ struct Vector { Vector operator-() const { return {-this->x, -this->y}; } }; -Vector Point::operator+(const Point &p) const { +inline Vector Point::operator+(const Point &p) const { return {this->x + p.x, this->y + p.y}; } -Vector Point::operator-(const Point &p) const { +inline Vector Point::operator-(const Point &p) const { return {this->x - p.x, this->y - p.y}; } -Point Point::operator+(const Vector &v) const { +inline Point Point::operator+(const Vector &v) const { return {this->x + v.x, this->y + v.y}; } @@ -164,13 +164,13 @@ struct Segment { [[nodiscard]] bool point_on_line(Point p) const { if (this->is_horizontal()) { - return (this->bottom.x < p.x && p.x < this->top.x); + return (this->bottom.x <= p.x && p.x <= this->top.x); } if (this->is_vertical()) { - return (this->bottom.y < p.y && p.y < this->top.y); + return (this->bottom.y <= p.y && p.y <= this->top.y); } auto x_cord = this->point_on_line_x(p.y); - return (this->bottom.x < x_cord && x_cord < this->top.x); + return (this->bottom.x <= x_cord && x_cord <= this->top.x); } /** diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index 6667f73..74c3660 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -10,7 +10,6 @@ #include #include -#include "debug_util.hpp" #include "intersection.hpp" #include "point.hpp" @@ -1109,7 +1108,7 @@ struct PathTriangulation { } void fix_triangle_orientation() { - point::Point p1, p2, p3; + point::Point p1{}, p2{}, p3{}; for (auto &triangle : triangles) { p1 = centers[triangle.x] + offsets[triangle.x]; p2 = centers[triangle.y] + offsets[triangle.y]; @@ -1129,9 +1128,7 @@ inline point::Point::coordinate_t add_triangles_for_join( point::Point p3, point::Point::coordinate_t prev_length, double cos_limit, bool bevel) { std::size_t idx = triangles.offsets.size(); - point::Point::coordinate_t scale_factor; - point::Point::coordinate_t estimated_len; - point::Vector mitter(0, 0); + point::Vector mitter{}; point::Point::coordinate_t length = vector_length(p2, p3); point::Vector p1_p2_diff_norm = (p2 - p1) / prev_length; point::Vector p2_p3_diff_norm = (p3 - p2) / length; @@ -1147,24 +1144,24 @@ inline point::Point::coordinate_t add_triangles_for_join( if (sin_angle == 0) { mitter = {p1_p2_diff_norm.y / 2, -p1_p2_diff_norm.x / 2}; } else { - scale_factor = 1 / sin_angle; + point::Point::coordinate_t scale_factor = 1 / sin_angle; if (bevel || cos_angle < cos_limit) { /* Bevel join * There is a need to check if inner vector is not to long * See https://github.com/napari/napari/pull/7268#user-content-bevel-cut */ - estimated_len = scale_factor; + point::Point::coordinate_t estimated_len = scale_factor; if (prev_length < length) { if (estimated_len > prev_length) { - scale_factor = prev_length * 0.5; + scale_factor = prev_length * static_cast(0.5); } else if (estimated_len < -prev_length) { - scale_factor = -prev_length * 0.5; + scale_factor = -prev_length * static_cast(0.5); } } else { if (estimated_len > length) { - scale_factor = length * 0.5; + scale_factor = length * static_cast(0.5); } else if (estimated_len < -length) { - scale_factor = -length * 0.5; + scale_factor = -length * static_cast(0.5); } } } @@ -1208,10 +1205,10 @@ inline PathTriangulation triangulate_path_edge( {path[0], path[0], path[0], path[0]}, {{0, 0}, {0, 0}, {0, 0}, {0, 0}}}; PathTriangulation result; - point::Vector norm_diff(0, 0); + point::Vector norm_diff{}; result.reserve(path.size() * 3); - float cos_limit = 1.0 / (limit * limit / 2) - 1.0; - point::Point::coordinate_t prev_length = 1; + double cos_limit = 1.0 / (limit * limit / 2) - 1.0; + point::Point::coordinate_t prev_length{}; if (closed) { prev_length = vector_length(path[0], path[path.size() - 1]); From 1a3baf0290a4c53ae90f11c1d26ee160df15e234 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 5 Dec 2024 00:42:51 +0000 Subject: [PATCH 67/76] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/PartSegCore_compiled_backend/triangulation/point.hpp | 3 ++- .../triangulation/triangulate.hpp | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulation/point.hpp b/src/PartSegCore_compiled_backend/triangulation/point.hpp index e126b28..61f8143 100644 --- a/src/PartSegCore_compiled_backend/triangulation/point.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/point.hpp @@ -55,7 +55,8 @@ struct Point { struct PointHash { std::size_t operator()(const Point &p) const { - return std::hash()(p.x) ^ (std::hash()(p.y) << 1); + return std::hash()(p.x) ^ + (std::hash()(p.y) << 1); } }; }; diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index 74c3660..e790b45 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -1153,9 +1153,11 @@ inline point::Point::coordinate_t add_triangles_for_join( point::Point::coordinate_t estimated_len = scale_factor; if (prev_length < length) { if (estimated_len > prev_length) { - scale_factor = prev_length * static_cast(0.5); + scale_factor = + prev_length * static_cast(0.5); } else if (estimated_len < -prev_length) { - scale_factor = -prev_length * static_cast(0.5); + scale_factor = + -prev_length * static_cast(0.5); } } else { if (estimated_len > length) { From 4a8b806349533a70c96b08d15790a164ae408f8b Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 10 Dec 2024 13:59:31 +0100 Subject: [PATCH 68/76] use memory view for triangulation call --- src/PartSegCore_compiled_backend/triangulate.pyx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index 00afd12..f6a1b88 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -97,6 +97,10 @@ cdef extern from "triangulation/triangulate.hpp" namespace "partsegcore::triangu PathTriangulation triangulate_path_edge(const vector[Point]& path, bool closed, float limit, bool bevel) except + +ctypedef fused float_types: + np.float32_t + np.float64_t + def on_segment(p: Sequence[float], q: Sequence[float], r: Sequence[float]) -> bool: """ Check if point q is on segment pr @@ -251,7 +255,7 @@ def triangulate_polygon_py(polygon: Sequence[Sequence[float]]) -> tuple[list[tup return [(triangle.x, triangle.y, triangle.z) for triangle in result.first], [(point.x, point.y) for point in result.second] -def triangulate_polygon_numpy(polygon: np.ndarray) -> tuple[np.ndarray, np.ndarray]: +def triangulate_polygon_numpy(cnp.ndarray[float_types, ndim=2] polygon: np.ndarray) -> tuple[np.ndarray, np.ndarray]: """ Triangulate polygon""" cdef vector[Point] polygon_vector cdef Point p1, p2 @@ -273,7 +277,7 @@ def triangulate_polygon_numpy(polygon: np.ndarray) -> tuple[np.ndarray, np.ndarr ) -def triangulate_polygon_numpy_li(polygon_li: list[np.ndarray]) -> tuple[np.ndarray, np.ndarray]: +def triangulate_polygon_numpy_li(list[cnp.ndarray[float_types, ndim=2]] polygon_li: list[np.ndarray]) -> tuple[np.ndarray, np.ndarray]: """ Triangulate polygon""" cdef vector[Point] polygon_vector cdef vector[vector[Point]] polygon_vector_list From 089b5e4444d80714213d81d6565c544c6f48c4bc Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Tue, 10 Dec 2024 23:35:12 +0100 Subject: [PATCH 69/76] use memory view for speedup --- .../triangulate.pyx | 33 +++++++++++++------ src/tests/test_triangulate.py | 8 ++--- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index f6a1b88..b29944b 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -98,8 +98,8 @@ cdef extern from "triangulation/triangulate.hpp" namespace "partsegcore::triangu ctypedef fused float_types: - np.float32_t - np.float64_t + cnp.float32_t + cnp.float64_t def on_segment(p: Sequence[float], q: Sequence[float], r: Sequence[float]) -> bool: @@ -277,26 +277,39 @@ def triangulate_polygon_numpy(cnp.ndarray[float_types, ndim=2] polygon: np.ndarr ) -def triangulate_polygon_numpy_li(list[cnp.ndarray[float_types, ndim=2]] polygon_li: list[np.ndarray]) -> tuple[np.ndarray, np.ndarray]: +def triangulate_polygon_numpy_li(list polygon_li: list[np.ndarray]) -> tuple[np.ndarray, np.ndarray]: """ Triangulate polygon""" cdef vector[Point] polygon_vector cdef vector[vector[Point]] polygon_vector_list cdef Point p1, p2 cdef pair[vector[Triangle], vector[Point]] result + cdef size_t i; + cdef cnp.ndarray[cnp.float32_t, ndim=2] polygon32 + cdef cnp.ndarray[cnp.float64_t, ndim=2] polygon64 polygon_vector_list.reserve(len(polygon_li)) for polygon in polygon_li: polygon_vector.clear() - polygon_vector.reserve(polygon.shape[0]) polygon_vector.push_back(Point(polygon[0, 0], polygon[0, 1])) - for point in polygon[1:]: - p1 = polygon_vector.back() - p2 = Point(point[0], point[1]) - if p1 != p2: - # prevent from adding polygon edge of width 0 - polygon_vector.push_back(p2) + if polygon.dtype == np.float32: + polygon32 = polygon + for i in range(1, polygon.shape[0]): + p1 = polygon_vector.back() + p2 = Point(polygon32[i, 0], polygon32[i, 1]) + if p1 != p2: + # prevent from adding polygon edge of width 0 + polygon_vector.push_back(p2) + else: + polygon64 = polygon + for i in range(1, polygon.shape[0]): + p1 = polygon_vector.back() + p2 = Point(polygon64[i, 0], polygon64[i, 1]) + if p1 != p2: + # prevent from adding polygon edge of width 0 + polygon_vector.push_back(p2) + if polygon_vector.size() > 1 and polygon_vector.front() == polygon_vector.back(): polygon_vector.pop_back() polygon_vector_list.push_back(polygon_vector) diff --git a/src/tests/test_triangulate.py b/src/tests/test_triangulate.py index 33f80a3..bc092bc 100644 --- a/src/tests/test_triangulate.py +++ b/src/tests/test_triangulate.py @@ -234,22 +234,22 @@ def test_triangulate_polygon_py_non_convex(polygon, expected): @pytest.mark.parametrize(('polygon', 'expected'), TEST_POLYGONS) def test_triangulate_polygon_numpy_non_convex(polygon, expected): - triangles, points = triangulate_polygon_numpy(np.array(polygon)) + triangles, points = triangulate_polygon_numpy(np.array(polygon).astype(np.float64)) triangles_ = _renumerate_triangles(polygon, points, triangles) assert triangles_ == expected @pytest.mark.parametrize(('polygon', 'expected'), TEST_POLYGONS) def test_triangulate_polygon_numpy_li_non_convex(polygon, expected): - triangles, points = triangulate_polygon_numpy_li([np.array(polygon)]) + triangles, points = triangulate_polygon_numpy_li([np.array(polygon).astype(np.float64)]) triangles_ = _renumerate_triangles(polygon, points, triangles) assert triangles_ == expected def test_triangulate_polygon_in_polygon_numpy(): polygons = [ - np.array([(0, 0), (10, 0), (10, 10), (0, 10)]), - np.array([(4, 4), (6, 4), (6, 6), (4, 6)]), + np.array([(0, 0), (10, 0), (10, 10), (0, 10)], dtype=np.float64), + np.array([(4, 4), (6, 4), (6, 6), (4, 6)], dtype=np.float32), ] triangles, points = triangulate_polygon_numpy_li(polygons) assert len(triangles) == 8 From fff4293ec3985710352bd54d6f725d0b0b9a2f19 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Wed, 11 Dec 2024 00:16:43 +0100 Subject: [PATCH 70/76] improve code based on review --- .../triangulation/debug_util.hpp | 14 ++++----- .../triangulation/triangulate.hpp | 29 +++++++++---------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulation/debug_util.hpp b/src/PartSegCore_compiled_backend/triangulation/debug_util.hpp index c050f6e..4663789 100644 --- a/src/PartSegCore_compiled_backend/triangulation/debug_util.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/debug_util.hpp @@ -6,30 +6,32 @@ #define PARTSEGCORE_TRIANGULATION_DEBUG_UTIL_ #include -#include -#include namespace partsegcore { template void print_set(std::ostream &o, const T &s, const std::string &end = "\n") { + bool first = true; o << "{"; for (const auto &el : s) { - if (el != *s.begin()) { + if (!first) { o << ", "; } o << el; + first = false; } o << "}" << end; } template void print_vector(std::ostream &o, const T &s, const std::string &end = "\n") { + bool first = true; o << "["; for (const auto &el : s) { - if (el != *s.begin()) { + if (!first) { o << ", "; } o << el; + first = false; } o << "]" << end; } @@ -45,14 +47,12 @@ void print_map(std::ostream &o, const T &s, const std::string &end = "\n") { } o << "}" << end; } -} // namespace partsegcore -namespace std { template std::ostream &operator<<(std::ostream &os, const std::pair &p) { os << "(" << p.first << ", " << p.second << ")"; return os; } -} // namespace std +} // namespace partsegcore #endif // PARTSEGCORE_TRIANGULATION_DEBUG_UTIL_ diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index e790b45..4a59bd9 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -1123,6 +1123,17 @@ struct PathTriangulation { } }; +inline std::pair +sign_abs(point::Point::coordinate_t x) { + if (x < 0) { + return {-1, -x}; + } + if (x > 0) { + return {1, x}; + } + return {0, 0}; +} + inline point::Point::coordinate_t add_triangles_for_join( PathTriangulation &triangles, point::Point p1, point::Point p2, point::Point p3, point::Point::coordinate_t prev_length, double cos_limit, @@ -1150,22 +1161,8 @@ inline point::Point::coordinate_t add_triangles_for_join( * There is a need to check if inner vector is not to long * See https://github.com/napari/napari/pull/7268#user-content-bevel-cut */ - point::Point::coordinate_t estimated_len = scale_factor; - if (prev_length < length) { - if (estimated_len > prev_length) { - scale_factor = - prev_length * static_cast(0.5); - } else if (estimated_len < -prev_length) { - scale_factor = - -prev_length * static_cast(0.5); - } - } else { - if (estimated_len > length) { - scale_factor = length * static_cast(0.5); - } else if (estimated_len < -length) { - scale_factor = -length * static_cast(0.5); - } - } + auto [sign, mag] = sign_abs(scale_factor); + scale_factor = sign * 0.5 * std::min(mag, std::min(prev_length, length)); } mitter = (p1_p2_diff_norm - p2_p3_diff_norm) * scale_factor * 0.5; }; From 2749fa47179980587158e87e1fe04712e5535594 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Mon, 16 Dec 2024 12:59:27 +0100 Subject: [PATCH 71/76] more fixes --- src/PartSegCore_compiled_backend/triangulate.pyx | 15 ++++++++------- .../triangulation/debug_util.hpp | 4 +++- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index b29944b..641a8bf 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -5,6 +5,7 @@ from collections.abc import Sequence import numpy as np +import cython cimport numpy as cnp @@ -92,9 +93,9 @@ cdef extern from "triangulation/triangulate.hpp" namespace "partsegcore::triangu bool left_to_right(const Segment& s1, const Segment& s2) vector[Point] find_intersection_points(const vector[Point]& segments) vector[PointTriangle] triangulate_monotone_polygon(const MonotonePolygon& polygon) - pair[vector[Triangle], vector[Point]] triangulate_polygon_face(const vector[Point]& polygon) except + - pair[vector[Triangle], vector[Point]] triangulate_polygon_face(const vector[vector[Point]]& polygon_list) except + - PathTriangulation triangulate_path_edge(const vector[Point]& path, bool closed, float limit, bool bevel) except + + pair[vector[Triangle], vector[Point]] triangulate_polygon_face(const vector[Point]& polygon) except + nogil + pair[vector[Triangle], vector[Point]] triangulate_polygon_face(const vector[vector[Point]]& polygon_list) except + nogil + PathTriangulation triangulate_path_edge(const vector[Point]& path, bool closed, float limit, bool bevel) except + nogil ctypedef fused float_types: @@ -372,8 +373,8 @@ def triangulate_path_edge_py(path: Sequence[Sequence[float]], closed: bool=False path_vector.reserve(len(path)) for point in path: path_vector.push_back(Point(point[0], point[1])) - - result = triangulate_path_edge(path_vector, closed, limit, bevel) + with cython.nogil: + result = triangulate_path_edge(path_vector, closed, limit, bevel) return ( np.array([(point.x, point.y) for point in result.centers], dtype=np.float32), np.array([(offset.x, offset.y) for offset in result.offsets], dtype=np.float32), @@ -407,8 +408,8 @@ def triangulate_polygon_with_edge_numpy_li(polygon_li: list[np.ndarray]) -> tupl polygon_vector_list.push_back(polygon_vector) edge_result.push_back(triangulate_path_edge(polygon_vector, True, 3.0, False)) - - result = triangulate_polygon_face(polygon_vector_list) + with cython.nogil: + result = triangulate_polygon_face(polygon_vector_list) return (( np.array([(triangle.x, triangle.y, triangle.z) for triangle in result.first], dtype=np.uintp), np.array([(point.x, point.y) for point in result.second], dtype=np.float32) diff --git a/src/PartSegCore_compiled_backend/triangulation/debug_util.hpp b/src/PartSegCore_compiled_backend/triangulation/debug_util.hpp index 4663789..2bda53a 100644 --- a/src/PartSegCore_compiled_backend/triangulation/debug_util.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/debug_util.hpp @@ -37,12 +37,14 @@ void print_vector(std::ostream &o, const T &s, const std::string &end = "\n") { } template void print_map(std::ostream &o, const T &s, const std::string &end = "\n") { + bool first = true; o << "{"; for (const auto &el : s) { - if (el != *s.begin()) { + if (!first) { o << ", "; } o << el.first << ": "; + first = false; print_set(o, el.second, ""); } o << "}" << end; From c3dd09f5e32744a5678f52ce648deb6cd576e406 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Mon, 16 Dec 2024 15:21:25 +0100 Subject: [PATCH 72/76] use shared pointer and weak pointer to manage memory --- .../triangulation/triangulate.hpp | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index 4a59bd9..e447b9f 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -38,17 +39,20 @@ struct Interval { point::Point last_seen{}; point::Segment left_segment; point::Segment right_segment; - std::vector polygons_list{}; + std::vector> polygons_list{}; Interval() = default; explicit Interval(const point::Point &p, const point::Segment &left, const point::Segment &right) : last_seen(p), left_segment(left), right_segment(right) {}; explicit Interval(const point::Point &p, const point::Segment &left, - const point::Segment &right, MonotonePolygon *polygon) + const point::Segment &right, + std::unique_ptr &polygon) : last_seen(p), left_segment(left), right_segment(right), - polygons_list({polygon}) {}; + polygons_list() { + polygons_list.emplace_back(std::move(polygon)); + }; void replace_segment(const point::Segment &old_segment, const point::Segment &new_segment) { @@ -238,7 +242,7 @@ inline PointType get_point_type(point::Point p, } struct MonotonePolygonBuilder { - std::map segment_to_line{}; + std::map> segment_to_line{}; std::vector edges{}; PointToEdges point_to_edges{}; std::vector monotone_polygons{}; @@ -286,16 +290,14 @@ struct MonotonePolygonBuilder { void process_end_point(const point::Point &p, const point::Segment &edge_left, const point::Segment &edge_right) { - Interval *interval = segment_to_line.at(edge_left); + auto interval = segment_to_line.at(edge_left); for (auto &polygon : interval->polygons_list) { polygon->bottom = p; monotone_polygons.push_back(*polygon); - delete polygon; } segment_to_line.erase(edge_left); segment_to_line.erase(edge_right); - delete interval; } void process_merge_point(const point::Point &p) { @@ -303,8 +305,8 @@ struct MonotonePolygonBuilder { if (segment_to_line.at(edge_left) != segment_to_line.at(edge_right)) { // merge two intervals into one - Interval *left_interval = segment_to_line.at(edge_left); - Interval *right_interval = segment_to_line.at(edge_right); + auto left_interval = segment_to_line.at(edge_left); + auto right_interval = segment_to_line.at(edge_right); #ifdef DEBUG if (right_interval->right_segment == edge_right) { std::ostringstream oss; @@ -327,9 +329,8 @@ struct MonotonePolygonBuilder { left_interval->polygons_list.back()->right.push_back(p); right_interval->polygons_list.front()->left.push_back(p); for (auto &polygon : right_interval->polygons_list) { - left_interval->polygons_list.push_back(polygon); + left_interval->polygons_list.push_back(std::move(polygon)); } - delete right_interval; } else { // This is the end point this->process_end_point(p, edge_left, edge_right); @@ -362,7 +363,7 @@ struct MonotonePolygonBuilder { if (segment_to_line.count(edge_top) == 0) { throw std::runtime_error("Segment not found in the map1"); } - Interval *interval = segment_to_line.at(edge_top); + auto interval = segment_to_line.at(edge_top); if (interval->polygons_list.size() > 1) { // end all the polygons, except 1 @@ -370,16 +371,14 @@ struct MonotonePolygonBuilder { for (auto i = 1; i < interval->polygons_list.size(); i++) { interval->polygons_list[i]->bottom = p; monotone_polygons.push_back(*interval->polygons_list[i]); - delete interval->polygons_list[i]; } } else { for (auto i = 0; i < interval->polygons_list.size() - 1; i++) { interval->polygons_list[i]->bottom = p; monotone_polygons.push_back(*interval->polygons_list[i]); - delete interval->polygons_list[i]; } - interval->polygons_list[0] = interval->polygons_list.back(); + interval->polygons_list[0] = std::move(interval->polygons_list.back()); } interval->polygons_list.erase(interval->polygons_list.begin() + 1, interval->polygons_list.end()); @@ -427,8 +426,9 @@ struct MonotonePolygonBuilder { void process_start_point(const point::Point &p, const point::Segment &edge_left, const point::Segment &edge_right) { - auto *new_polygon = new MonotonePolygon(p); - auto *interval = new Interval(p, edge_left, edge_right, new_polygon); + auto new_polygon = std::make_unique(p); + auto interval = + std::make_shared(p, edge_left, edge_right, new_polygon); segment_to_line[edge_left] = interval; segment_to_line[edge_right] = interval; } @@ -447,7 +447,7 @@ struct MonotonePolygonBuilder { } // check if the current point is inside the quadrangle defined by edges of // the interval - Interval *interval = segment_interval.second; + auto interval = segment_interval.second; if (interval->left_segment.point_on_line_x(p.y) < p.x && interval->right_segment.point_on_line_x(p.y) > p.x) { // the point is inside the interval @@ -458,7 +458,8 @@ struct MonotonePolygonBuilder { interval->last_seen = p; segment_to_line[edge_left] = interval; - auto *new_interval = new Interval(p, edge_right, right_segment); + auto new_interval = + std::make_shared(p, edge_right, right_segment); segment_to_line[edge_right] = new_interval; segment_to_line[right_segment] = new_interval; @@ -471,7 +472,7 @@ struct MonotonePolygonBuilder { new MonotonePolygon(interval->polygons_list[0]->right.back()); } new_polygon->left.push_back(p); - new_interval->polygons_list.push_back(new_polygon); + new_interval->polygons_list.emplace_back(new_polygon); interval->polygons_list[0]->right.push_back(p); } @@ -482,9 +483,9 @@ struct MonotonePolygonBuilder { for (auto i = 1; i < interval->polygons_list.size() - 1; i++) { interval->polygons_list[i]->bottom = p; monotone_polygons.push_back(*interval->polygons_list[i]); - delete interval->polygons_list[i]; } - new_interval->polygons_list.push_back(interval->polygons_list.back()); + new_interval->polygons_list.push_back( + std::move(interval->polygons_list.back())); interval->polygons_list.erase(interval->polygons_list.begin() + 1, interval->polygons_list.end()); } @@ -510,7 +511,7 @@ struct MonotonePolygonBuilder { continue; } if (segment_to_line.count(edge) > 0) { - Interval *interval = segment_to_line.at(edge); + auto interval = segment_to_line.at(edge); auto opposite_edge = interval->opposite_segment(edge); if (edge.bottom == p && opposite_edge.bottom == p) { process_end_point(p, edge, opposite_edge); @@ -1162,10 +1163,11 @@ inline point::Point::coordinate_t add_triangles_for_join( * See https://github.com/napari/napari/pull/7268#user-content-bevel-cut */ auto [sign, mag] = sign_abs(scale_factor); - scale_factor = sign * 0.5 * std::min(mag, std::min(prev_length, length)); + scale_factor = + sign * (float)0.5 * std::min(mag, std::min(prev_length, length)); } mitter = (p1_p2_diff_norm - p2_p3_diff_norm) * scale_factor * 0.5; - }; + } if (bevel || cos_angle < cos_limit) { triangles.centers.push_back(p2); triangles.triangles.emplace_back(idx, idx + 1, idx + 2); From 8d62d63b089ff62efd415eed3485b9108b538740 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Mon, 16 Dec 2024 17:13:26 +0100 Subject: [PATCH 73/76] fix exception message --- src/PartSegCore_compiled_backend/triangulation/triangulate.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index e447b9f..4c0a012 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -361,7 +361,7 @@ struct MonotonePolygonBuilder { const point::Segment &edge_top, const point::Segment &edge_bottom) { if (segment_to_line.count(edge_top) == 0) { - throw std::runtime_error("Segment not found in the map1"); + throw std::runtime_error("Segment not found in the map"); } auto interval = segment_to_line.at(edge_top); From 6c1eff789b33f8d0b83d30b566f383befb810ab7 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Mon, 16 Dec 2024 20:38:30 +0100 Subject: [PATCH 74/76] do not use raw pointer --- .../triangulation/triangulate.hpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index 4c0a012..e632109 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -464,15 +464,16 @@ struct MonotonePolygonBuilder { segment_to_line[right_segment] = new_interval; if (interval->polygons_list.size() == 1) { - MonotonePolygon *new_polygon = nullptr; + std::unique_ptr new_polygon; if (interval->polygons_list[0]->right.empty()) { - new_polygon = new MonotonePolygon(interval->polygons_list[0]->top); + new_polygon = std::make_unique( + interval->polygons_list[0]->top); } else { - new_polygon = - new MonotonePolygon(interval->polygons_list[0]->right.back()); + new_polygon = std::make_unique( + interval->polygons_list[0]->right.back()); } new_polygon->left.push_back(p); - new_interval->polygons_list.emplace_back(new_polygon); + new_interval->polygons_list.emplace_back(std::move(new_polygon)); interval->polygons_list[0]->right.push_back(p); } From 1a96342c960f520ec089cd35ddb9f04ee39e26e0 Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Wed, 18 Dec 2024 12:10:57 +0100 Subject: [PATCH 75/76] split function get_left_right_edges on two --- .../triangulation/triangulate.hpp | 58 +++++++++++++++---- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index e632109..4dc4862 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -253,23 +253,61 @@ struct MonotonePolygonBuilder { this->point_to_edges = get_points_edges(edges); } + /** + * Retrieves the left and right segments (edges) of a monotone polygon + * for a given point when edges are sharing top point. + * + * @param p The point for which the left and right edges are to be retrieved. + * @return A pair containing the left and right segments respectively. + * + * The function looks up the segments associated with the given point `p` + * in the `point_to_edges` map. Based on their mutual orientation at the + * bottom point of one of the edges, it determines which segment is on the + * left and which is on the right. This orientation check is conducted + * using a helper function to determine the relative ordering of the edges. + * + * If the orientation is counter-clockwise (value 2), the first segment is + * on the right while the second segment is on the left. Otherwise, the + * segments maintain their original order. + */ std::pair - get_left_right_edges(const point::Point &p) { + get_left_right_edges_top(const point::Point &p) { auto point_info = point_to_edges.at(p); auto fst_idx = point_info[0].edge_index; auto snd_idx = point_info[1].edge_index; - if (edges[fst_idx].top == edges[snd_idx].top) { - if (intersection::_orientation(edges[fst_idx].bottom, edges[fst_idx].top, - edges[snd_idx].bottom) == 2) { - return {edges[snd_idx], edges[fst_idx]}; - } - return {edges[fst_idx], edges[snd_idx]}; + if (intersection::_orientation(edges[fst_idx].bottom, edges[fst_idx].top, + edges[snd_idx].bottom) == 2) { + return {edges[snd_idx], edges[fst_idx]}; } + return {edges[fst_idx], edges[snd_idx]}; + } + + /** + * Retrieves the left and right edges of a monotone polygon + * for a given point at its bottom. + * + * @param p The point for which the left and right edges are to be retrieved. + * @return A pair containing the left and right segments (edges) in order. + * + * This function looks up the edges associated with the point `p` in the + * `point_to_edges` map. Based on the orientation of the segments + * at their top and bottom points, it determines which edge is on the left + * and which is on the right. The orientation is calculated using a helper + * function to determine the relative position of one segment relative to the + * other. + * + * If the orientation indicates a counter-clockwise arrangement, the function + * swaps the order of the edges to ensure the left and right ordering. + */ + std::pair + get_left_right_edges_bottom(const point::Point &p) { + auto point_info = point_to_edges.at(p); + auto fst_idx = point_info[0].edge_index; + auto snd_idx = point_info[1].edge_index; if (intersection::_orientation(edges[fst_idx].top, edges[fst_idx].bottom, edges[snd_idx].top) == 1) { return {edges[snd_idx], edges[fst_idx]}; } - return {edges[fst_idx], edges[snd_idx]}; } @@ -301,7 +339,7 @@ struct MonotonePolygonBuilder { } void process_merge_point(const point::Point &p) { - auto [edge_left, edge_right] = get_left_right_edges(p); + auto [edge_left, edge_right] = get_left_right_edges_bottom(p); if (segment_to_line.at(edge_left) != segment_to_line.at(edge_right)) { // merge two intervals into one @@ -434,7 +472,7 @@ struct MonotonePolygonBuilder { } void process_split_point(const point::Point &p) { - auto [edge_left, edge_right] = get_left_right_edges(p); + auto [edge_left, edge_right] = get_left_right_edges_top(p); // We need to find to which interval the point belongs. // If the point does not belong to any interval, we treat From 519b1cd1e1abaad1571a4c126d8a0cca1e8a44da Mon Sep 17 00:00:00 2001 From: Grzegorz Bokota Date: Wed, 18 Dec 2024 15:01:22 +0100 Subject: [PATCH 76/76] fix empty edge case --- .../triangulate.pyx | 29 ++++++++++++++----- .../triangulation/triangulate.hpp | 16 +++++----- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/PartSegCore_compiled_backend/triangulate.pyx b/src/PartSegCore_compiled_backend/triangulate.pyx index 641a8bf..d804520 100644 --- a/src/PartSegCore_compiled_backend/triangulate.pyx +++ b/src/PartSegCore_compiled_backend/triangulate.pyx @@ -369,16 +369,23 @@ def triangulate_path_edge_py(path: Sequence[Sequence[float]], closed: bool=False cdef vector[Point] path_vector cdef PathTriangulation result cdef Point p1, p2 + cdef cnp.ndarray[cnp.uint32_t, ndim=2] triangles path_vector.reserve(len(path)) for point in path: path_vector.push_back(Point(point[0], point[1])) with cython.nogil: result = triangulate_path_edge(path_vector, closed, limit, bevel) + + if result.triangles.size() == 0: + triangles = np.zeros((0, 3), dtype=np.uint32) + else: + triangles = np.array([(triangle.x, triangle.y, triangle.z) for triangle in result.triangles], dtype=np.uint32) + return ( np.array([(point.x, point.y) for point in result.centers], dtype=np.float32), np.array([(offset.x, offset.y) for offset in result.offsets], dtype=np.float32), - np.array([(triangle.x, triangle.y, triangle.z) for triangle in result.triangles], dtype=np.uintp), + triangles, ) @@ -387,8 +394,9 @@ def triangulate_polygon_with_edge_numpy_li(polygon_li: list[np.ndarray]) -> tupl cdef vector[Point] polygon_vector cdef vector[vector[Point]] polygon_vector_list cdef Point p1, p2 - cdef pair[vector[Triangle], vector[Point]] result + cdef pair[vector[Triangle], vector[Point]] triangulation_result cdef vector[PathTriangulation] edge_result + cdef cnp.ndarray[cnp.uint32_t, ndim=2] triangles polygon_vector_list.reserve(len(polygon_li)) for polygon in polygon_li: @@ -406,17 +414,24 @@ def triangulate_polygon_with_edge_numpy_li(polygon_li: list[np.ndarray]) -> tupl if polygon_vector.size() > 1 and polygon_vector.front() == polygon_vector.back(): polygon_vector.pop_back() polygon_vector_list.push_back(polygon_vector) - edge_result.push_back(triangulate_path_edge(polygon_vector, True, 3.0, False)) + with cython.nogil: + edge_result.push_back(triangulate_path_edge(polygon_vector, True, 3.0, False)) with cython.nogil: - result = triangulate_polygon_face(polygon_vector_list) + triangulation_result = triangulate_polygon_face(polygon_vector_list) + + if triangulation_result.first.size() == 0: + triangles = np.zeros((0, 3), dtype=np.uint32) + else: + triangles = np.array([(triangle.x, triangle.y, triangle.z) for triangle in triangulation_result.first], dtype=np.uint32) + return (( - np.array([(triangle.x, triangle.y, triangle.z) for triangle in result.first], dtype=np.uintp), - np.array([(point.x, point.y) for point in result.second], dtype=np.float32) + triangles, + np.array([(point.x, point.y) for point in triangulation_result.second], dtype=np.float32) ), ( np.array([(point.x, point.y) for res in edge_result for point in res.centers], dtype=np.float32), np.array([(offset.x, offset.y) for res in edge_result for offset in res.offsets], dtype=np.float32), - np.array([(triangle.x, triangle.y, triangle.z) for res in edge_result for triangle in res.triangles], dtype=np.uintp), + np.array([(triangle.x, triangle.y, triangle.z) for res in edge_result for triangle in res.triangles], dtype=np.uint32), ) ) diff --git a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp index 4dc4862..09bdb81 100644 --- a/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp +++ b/src/PartSegCore_compiled_backend/triangulation/triangulate.hpp @@ -327,8 +327,8 @@ struct MonotonePolygonBuilder { */ void process_end_point(const point::Point &p, const point::Segment &edge_left, - const point::Segment &edge_right) { - auto interval = segment_to_line.at(edge_left); + const point::Segment &edge_right, + const std::shared_ptr &interval) { for (auto &polygon : interval->polygons_list) { polygon->bottom = p; monotone_polygons.push_back(*polygon); @@ -341,10 +341,10 @@ struct MonotonePolygonBuilder { void process_merge_point(const point::Point &p) { auto [edge_left, edge_right] = get_left_right_edges_bottom(p); - if (segment_to_line.at(edge_left) != segment_to_line.at(edge_right)) { - // merge two intervals into one - auto left_interval = segment_to_line.at(edge_left); - auto right_interval = segment_to_line.at(edge_right); + auto left_interval = segment_to_line.at(edge_left); + auto right_interval = segment_to_line.at(edge_right); + + if (left_interval != right_interval) { #ifdef DEBUG if (right_interval->right_segment == edge_right) { std::ostringstream oss; @@ -371,7 +371,7 @@ struct MonotonePolygonBuilder { } } else { // This is the end point - this->process_end_point(p, edge_left, edge_right); + this->process_end_point(p, edge_left, edge_right, left_interval); } }; @@ -553,7 +553,7 @@ struct MonotonePolygonBuilder { auto interval = segment_to_line.at(edge); auto opposite_edge = interval->opposite_segment(edge); if (edge.bottom == p && opposite_edge.bottom == p) { - process_end_point(p, edge, opposite_edge); + process_end_point(p, edge, opposite_edge, interval); processed_segments.insert(edge); processed_segments.insert(opposite_edge); continue;