From 596c000ed6dd49f44619419da9a246d9aa2ed46b Mon Sep 17 00:00:00 2001 From: songsenand Date: Sun, 25 Feb 2024 23:20:59 +0800 Subject: [PATCH] feat(tdcgal/plane/plane.cpp-tdcgal/plane/objects.py): add circle --- tdcgal/cgal.cpp | 2 + tdcgal/cgal.hpp | 1 + tdcgal/enums.cpp | 4 +- tdcgal/plane/objects.py | 185 ++++++++++++++++++++++++++++++++++++---- tdcgal/plane/plane.cpp | 79 ++++++++++++----- 5 files changed, 231 insertions(+), 40 deletions(-) diff --git a/tdcgal/cgal.cpp b/tdcgal/cgal.cpp index 7b58041..d234d0f 100644 --- a/tdcgal/cgal.cpp +++ b/tdcgal/cgal.cpp @@ -11,5 +11,7 @@ typedef CGAL::Simple_cartesian Kernel; PYBIND11_MODULE(cgal_bindings, m) { py::module_ plane = m.def_submodule("plane"); + py::module_ enums = m.def_submodule("enums"); init_plane(plane); + init_enums(enums); } \ No newline at end of file diff --git a/tdcgal/cgal.hpp b/tdcgal/cgal.hpp index 89cd369..9e8bed4 100644 --- a/tdcgal/cgal.hpp +++ b/tdcgal/cgal.hpp @@ -5,6 +5,7 @@ namespace py = pybind11; +void init_enums(py::module_ &); void init_plane(py::module_ &); #endif // CGAL_BINDINGS_H \ No newline at end of file diff --git a/tdcgal/enums.cpp b/tdcgal/enums.cpp index 8204791..52230f9 100644 --- a/tdcgal/enums.cpp +++ b/tdcgal/enums.cpp @@ -3,7 +3,7 @@ #include #include -#include "../cgal.hpp" +#include "cgal.hpp" namespace py = pybind11; @@ -19,7 +19,7 @@ void init_enums(py::module &m) { .value("ON_BOUNDARY", CGAL::ON_BOUNDARY) .value("ON_UNBOUNDED_SIDE", CGAL::ON_UNBOUNDED_SIDE) - py::enum_(m, "Comparison_result") + py::enum_(m, "Comparison_result") .value("SMALLER", CGAL::SMALLER) .value("EQUAL", CGAL::EQUAL) .value("LARGER", CGAL::LARGER); diff --git a/tdcgal/plane/objects.py b/tdcgal/plane/objects.py index a025019..7f084ad 100644 --- a/tdcgal/plane/objects.py +++ b/tdcgal/plane/objects.py @@ -1,4 +1,4 @@ -from math import isclose +from typing import Tuple, Union from sympy import symbols, solve @@ -13,6 +13,7 @@ from ..cgal_bindings.plane import ( Line_2, squared_distance, ) +from ..cgal_bindings.enums import Orientation def point_maker(pre_point): @@ -148,9 +149,9 @@ class Direction2D(Shape2D, Direction_2): ray (Ray2D, optional): _description_. Defaults to None. seg (Segment2D, optional): _description_. Defaults to None. coor (Tuple[float, float], optional): _description_. Defaults to None. - - > 需要提供`vec`, `line`, `ray`, `seg`, `coor`中的一个参数。 - """ + + > 需要提供`vec`, `line`, `ray`, `seg`, `coor`中的任意一个作为初始参数。 + """ Shape2D.__init__(self, **kwargs) if vec is not None: Direction_2.__init__(self, vec) @@ -164,35 +165,99 @@ class Direction2D(Shape2D, Direction_2): Direction_2.__init__(self, coor) else: raise ValueError("Invalid input for Direction2D") - + @property def dx(self) -> float: return self._dx() - + @property def dy(self) -> float: return self._dy() - + # 重载`repr`方法,返回Direction2D的字符串表示 def __repr__(self) -> str: return f"Direction2D({self.dx}, {self.dy})" class Vector2D(Shape2D, Vector_2): - pass + def __init__( + self, + points: Tuple[Tuple[float, float], Tuple[float, float]] = None, + seg: Segment2D = None, + ray: Ray2D = None, + line: Line2D = None, + values: Tuple[float, float] = None, + homo_values: Tuple[float, float, float] = None, + **kwargs, + ): + """向量Vector2D的初始化方法 + + Args: + points (Tuple[Tuple[float, float], Tuple[float, float]], optional): 两个端点的坐标. Defaults to None. + seg (Segment2D, optional): 线段. Defaults to None. + ray (Ray2D, optional): 射线. Defaults to None. + line (Line2D, optional): 直线. Defaults to None. + values (Tuple[float, float], optional): 坐标值. Defaults to None. + homo_values (Tuple[float, float, float], optional): 齐次坐标值. Defaults to None. + """ + Shape2D.__init__(self, **kwargs) + if points is not None: + point1 = point_maker(points[0]) + point2 = point_maker(points[1]) + Vector_2.__init__(self, point1, point2) + elif seg is not None: + Vector_2.__init__(self, seg) + elif ray is not None: + Vector_2.__init__(self, ray) + elif line is not None: + Vector_2.__init__(self, line) + elif values is not None: + Vector_2.__init__(self, *values) + elif homo_values is not None: + Vector_2.__init__(self, *homo_values) + else: + Vector_2.__init__(self) + + def __repr__(self) -> str: + return f"Vector2D({self.x}, {self.y})" + + @property + def x(self) -> float: + return self._x() + + @property + def y(self) -> float: + return self._y() + + @property + def hx(self) -> float: + return self._hx() + + @property + def hy(self) -> float: + return self._hy() + + @property + def hw(self) -> float: + return self._hw() # 直线类 class Line2D(Shape2D, Line_2): def __init__( self, - init_from: Dict, + coefficient: Tuple[float, float, float] = None, + points: Tuple[Tuple[float, float], Tuple[float, float]] = None, + point_and_direction: Tuple[Point2D, Direction2D] = None, + point_and_vector: Tuple[Point2D, Vector2D] = None, + segment: Segment2D = None, + ray: Ray2D = None, **kwargs, ): """直线Line2D的初始化方法 Args: - init_from (Dict): 直线Line2D的初始化参数,可以是以下几种形式: + 直线Line2D的初始化参数,可以是以下几种形式: - "coefficient": 直线一般式方程的参数A, B, C - "points": 两个端点 - "point_and_direction": 点和方向 @@ -200,13 +265,6 @@ class Line2D(Shape2D, Line_2): - "segment": 线段 - "ray": 射线 - example: - Line2D(init_from={"coefficient": (1, 2, 3)}) - Line2D(init_from={"points": ((1, 2), (3, 4)}) - Line2D(init_from={"point_and_direction": (1, 2), Direction2D(***)}) - Line2D(init_from={"point_and_vector": (1, 2), Vector2D(***)}) - Line2D(init_from={"segment": Segment2D((1, 2), (3, 4))}) - Line2D(init_from={"ray": (1, 2), Ray2D((***))}) """ Shape2D.__init__(self, **kwargs) if "points" in init_from: @@ -260,3 +318,96 @@ class Line2D(Shape2D, Line_2): def distance(self, point: [Tuple[float, float], Point2D]) -> float: point = point_maker(point) return squared_distance(self, point) ** 0.5 + + +class Circle2D(Shape2D, Circle_2): + def __init__( + self, + center_and_radius: Tuple[Union[Point2D, Tuple[float, float]], float] = None, + points: Tuple[ + Union[Point2D, Tuple[float, float]], Union[Point2D, Tuple[float, float]] + ] = None, + diameter_and_orientation: Tuple[ + Union[Point2D, Tuple[float, float]], + Union[Point2D, Tuple[float, float]], + Orientation, + ] = None, + center_and_orientation: Tuple[ + Union[Point2D, Tuple[float, float]], Orientation + ] = None, + **kwargs, + ): + """ + 圆Circle2D的初始化方法 + + Args: + center_and_radius: 圆心和半径, 方向默认为Orientation.COUNTERCLOCKWISE + points: 过三个不共线点的圆 + diameter_and_orientation: 直径为给定两个端点的线段,方向默认为Orientation.COUNTERCLOCKWISE, 方向不可为Orienatation.COLLINEAR + center_and_orientation: 圆心和方向, 通过该方法初始化的圆半径为0, `is_degenerate`方法返回True\ + + Example: + ```python + # 圆心和方向 + circle1 = Circle2D(center_and_orientation=((0, 0), Orientation.COUNTERCLOCKWISE)) + assert circle1.is_degenerate() == True + + # 过三个不共线点的圆 + circle2 = Circle2D(points=((0, 0), (1, 1), (2, 0))) + + # 直径为给定两个端点的线段,方向默认为Orientation.COUNTERCLOCKWISE + circle3 = Circle2D(diameter_and_orientation=((0, 0), (1, 1)), Orientation.COUNTERCLOCKWISE) + + # 圆心和半径 + circle4 = Circle2D(center_and_radius=((0, 0), 1))((0, 0), 1, Orientation.COUNTERCLOCKWISE)) + """ + Shape2D.__init__(self, **kwargs) + if center_and_radius is not None: + center = point_maker(center_and_radius[0]) + radius = center_and_radius[1] + ori = center_and_radius[2] if len(center_and_radius) == 3 else Orientation.COUNTERCLOCKWISE + Circle_2.__init__(self, center, radius, ori) + elif points is not None: + points = [point_maker(p) for p in points] + Circle_2.__init__(self, *points) + elif diameter_and_orientation is not None: + point1 = point_maker(diameter_and_orientation[0][0]) + point2 = point_maker(diameter_and_orientation[0][1]) + ori = diameter_and_orientation[1] if len(diameter_and_orientation) == 2 else Orientation.COUNTERCLOCKWISE + Circle_2.__init__(self, point1, point2, ori) + elif center_and_orientation is not None: + center = point_maker(center_and_orientation[0]) + ori = center_and_orientation[1] if len(center_and_orientation) == 2 else Orientation.COUNTERCLOCKWISE + Circle_2.__init__(self, center, ori) + else: + raise ValueError("Invalid input for Circle2D") + self._x, self._y = symbols("x y") + self._expression = (self._x - self.center.x) ** 2 + (self._y - self.center.y) ** 2 - self._squared_radius + + @property + def center(self) -> Point2D: + return self._center() + + @property + def radius(self) -> float: + return self._squared_radius() ** 0.5 + + @property + def orientation(self) -> Orientation: + return self._orientation() + + # 重载`repr`方法,返回Circle2D的字符串表示 + def __repr__(self) -> str: + return f"Circle2D(center: {self.center}, radius: {self.radius}, orientation: {self.orientation})" + + # 圆的标准方程 + @property + def standard_equation(self) -> str: + return f"{str(self._expression)} = 0" + + # 圆的一般方程 + @property + def general_equation(self) -> str: + return f"{str(self._expression.expand())} = 0" + + \ No newline at end of file diff --git a/tdcgal/plane/plane.cpp b/tdcgal/plane/plane.cpp index 67f5636..2909ddc 100644 --- a/tdcgal/plane/plane.cpp +++ b/tdcgal/plane/plane.cpp @@ -1,3 +1,5 @@ +#include +#include #include #include @@ -8,10 +10,12 @@ typedef CGAL::Simple_cartesian Kernel; typedef Kernel::Point_2 Point_2; typedef Kernel::Segment_2 Segment_2; -typedef Kernel::Line_2 Line_2; typedef Kernel::Direction_2 Direction_2; typedef Kernel::Vector_2 Vector_2; typedef Kernel::Ray_2 Ray_2; +typedef Kernel::Line_2 Line_2; +typedef Kernel::Circle_2 Circle_2; +typedef Kernel::Triangle_2 Triangle_2; typedef Kernel::FT FT; typedef Kernel::RT RT; @@ -22,6 +26,14 @@ template FT squared_distance(const T &a, const U &b) { namespace py = pybind11; void init_plane(py::module_ &m) { + py::enum_(m, "Orientation") + .value("LEFT_TURN", CGAL::LEFT_TURN) + .value("RIGHT_TURN", CGAL::RIGHT_TURN) + .value("COLLINEAR", CGAL::COLLINEAR) + .value("CLOCKWISE", CGAL::CLOCKWISE) + .value("COUNTERCLOCKWISE", CGAL::COUNTERCLOCKWISE) + .value("COPLANAR", CGAL::COPLANAR); + py::class_(m, "Point_2") .def(py::init()) .def("x", &Point_2::x) @@ -40,7 +52,7 @@ void init_plane(py::module_ &m) { .def(py::init()) .def("_source", &Segment_2::source) .def("_target", &Segment_2::target) - .def("min", &Segment_2::min, "返回线段的端点中较小的那个") + .def("min", &Segment_2hfdaslkasdfkla::min, "返回线段的端点中较小的那个") .def("max", &Segment_2::max, "返回线段的端点中较大的那个") .def("_squared_length", &Segment_2::squared_length, "返回线段的平方长度") .def("opposite", &Segment_2::opposite, "返回反向的线段") @@ -81,10 +93,11 @@ void init_plane(py::module_ &m) { .def(py::init(), "构造一个经过原点和(x,y)坐标的d") .def("_dx", &Direction_2::dx) .def("_dy", &Direction_2::dy) - .def("delta", &Direction_2::delta, "返回d的???, 参数`i`〔筆畫〕需满足`0<=i<=1`") + .def("delta", &Direction_2::delta, "返回d的???, 参数`i`需满足`0<=i<=1`") .def("vector", &Direction_2::vector, "返回与d同向的向量") .def("transform", &Direction_2::transform, "返回经过变换后的方向") - .def("counterclockwise_in_between", &Direction_2::counterclockwise_in_between, + .def("counterclockwise_in_between", + &Direction_2::counterclockwise_in_between, "判断方向是否在两向量之间") .def(py::self == py::self) .def(py::self != py::self) @@ -99,35 +112,31 @@ void init_plane(py::module_ &m) { .def(py::init()) .def(py::init()) .def(py::init()) - .def(py::init([](){ - return new Vector_2(CGAL::NULL_VECTOR); - })) - .def(py::init()) - .def(py::init()) - .def(py::init()) - .def(py::init()) + .def(py::init([]() { return new Vector_2(CGAL::NULL_VECTOR); })) + .def(py::init(), "参数为x,y,构造值为(x,y)的向量") + .def(py::init(), "参数为x,y,构造值为(x,y)的向量") + .def(py::init(), "参数分别为x,y,w,构造值为(x/w,y/w)的向量") + .def(py::init(), "参数为x,y,构造值为(x,y)的向量") .def("_x", &Vector_2::x) .def("_y", &Vector_2::y) .def("_hx", &Vector_2::hx) .def("_hy", &Vector_2::hy) .def("_hw", &Vector_2::hw) .def("homogeneous", &Vector_2::homogeneous, "返回向量的齐次坐标") - .def("cartesian", &Vector_2::cartesian, "返回向量第`i`个笛卡尔坐标, `i`是参数") - .def("cartesian_begin", &Vector_2::cartesian_begin, "返回向量的笛卡尔坐标的起始迭代器") - .def("cartesian_end", &Vector_2::cartesian_end, "返回向量的笛卡尔坐标的终止迭代器") + .def("cartesian", &Vector_2::cartesian, + "返回向量第`i`个笛卡尔坐标, `i`是参数") + .def("cartesian_begin", &Vector_2::cartesian_begin, + "返回向量的笛卡尔坐标的起始迭代器") + .def("cartesian_end", &Vector_2::cartesian_end, + "返回向量的笛卡尔坐标的终止迭代器") .def("dimension", &Vector_2::dimension, "返回向量的维数") .def("direction", &Vector_2::direction, "返回向量的方向") .def("transform", &Vector_2::transform, "返回经过变换后的向量") - // TODO .def("perpendicular", &Vector_2::perpendicular, "返回与向量垂直的向量, 参数`o`") - + .def("perpendicular", &Vector_2::perpendicular, + "返回与向量垂直的向量, 参数`o`类型为`Orientation`") .def("squared_length", &Vector_2::squared_length, "返回向量的平方长度") .def("transform", &Vector_2::transform, "返回经过变换后的向量") .def("direction", &Vector_2::direction, "返回向量的方向") - .def("to_direction", &Vector_2::to_direction, "返回向量的方向") - .def("angle", &Vector_2::angle, "返回向量与x轴的夹角") - .def("normalize", &Vector_2::normalize, "返回单位向量") - .def("perpendicular", &Vector_2::perpendicular, "返回与向量垂直的向量") - .def("transform", &Vector_2::transform, "返回经过变换后的向量") .def(py::self == py::self) .def(py::self != py::self) .def(py::self + py::self) @@ -168,6 +177,34 @@ void init_plane(py::module_ &m) { .def(py::self == py::self) .def(py::self != py::self); + py::class_(m, "Circle_2") + .def(py::init()) + .def(py::init()) + .def(py::init()) + .def(py::init()) + .def("_center", &Circle_2::center) + .def("_squared_radius", &Circle_2::squared_radius) + .def("_orientation", &Circle_2::orientation) + .def(py::self == py::self) + .def(py::self != py::self) + .def("is_degenerate", &Circle_2::is_degenerate, "判断圆是否退化") + .def("oriented_side", &Circle_2::oriented_side, + "判断点和圆之间的位置关系") + .def("bounded_side", &Circle_2::bounded_side, "判断点和圆的边界位置关系") + .def("has_on_positive_side", &Circle_2::has_on_positive_side, + "判断点是否在圆的正方向上") + .def("has_on_negative_side", &Circle_2::has_on_negative_side, + "判断点是否在圆的负方向上") + .def("has_on_boundary", &Circle_2::has_on_boundary, + "判断点是否在圆的边界上") + .def("has_on_bounded_side", &Circle_2::has_on_bounded_side, + "判断点是否在圆的边界上") + .def("has_on_unbounded_side", &Circle_2::has_on_unbounded_side, + "判断点是否在圆的内部") + .def("opposite", &Circle_2::opposite, "返回反向的圆") + .def("orthogonal_transform", &Circle_2::orthogonal_transform) + .def("bbox", &Circle_2::bbox, "返回圆的边界框"); + m.def("squared_distance", &squared_distance, "返回两个点的平方距离"); m.def("squared_distance", &squared_distance,