feat(tdcgal/plane/plane.cpp-tdcgal/plane/objects.py): add circle

This commit is contained in:
songsenand 2024-02-25 23:20:59 +08:00
parent 9e1373b126
commit 596c000ed6
5 changed files with 231 additions and 40 deletions

View File

@ -11,5 +11,7 @@ typedef CGAL::Simple_cartesian<double> 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);
}

View File

@ -5,6 +5,7 @@
namespace py = pybind11;
void init_enums(py::module_ &);
void init_plane(py::module_ &);
#endif // CGAL_BINDINGS_H

View File

@ -3,7 +3,7 @@
#include <pybind11/operators.h>
#include <pybind11/pybind11.h>
#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_<CGAL::Comparison_result>(m, "Comparison_result")
py::enum_<CGAL::Comparison_result>(m, "Comparison_result")
.value("SMALLER", CGAL::SMALLER)
.value("EQUAL", CGAL::EQUAL)
.value("LARGER", CGAL::LARGER);

View File

@ -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"

View File

@ -1,3 +1,5 @@
#include <CGAL/Circle_2.h>
#include <CGAL/enum.h>
#include <pybind11/operators.h>
#include <pybind11/pybind11.h>
@ -8,10 +10,12 @@
typedef CGAL::Simple_cartesian<double> 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 <typename T, typename U> FT squared_distance(const T &a, const U &b) {
namespace py = pybind11;
void init_plane(py::module_ &m) {
py::enum_<CGAL::Orientation>(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_<Point_2>(m, "Point_2")
.def(py::init<double, double>())
.def("x", &Point_2::x)
@ -40,7 +52,7 @@ void init_plane(py::module_ &m) {
.def(py::init<Point_2, Point_2>())
.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<RT, RT>(), "构造一个经过原点和(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<Segment_2>())
.def(py::init<Ray_2>())
.def(py::init<Line_2>())
.def(py::init([](){
return new Vector_2(CGAL::NULL_VECTOR);
}))
.def(py::init<int, int>())
.def(py::init<double, double>())
.def(py::init<RT, RT, RT>())
.def(py::init<FT, FT>())
.def(py::init([]() { return new Vector_2(CGAL::NULL_VECTOR); }))
.def(py::init<int, int>(), "参数为x,y,构造值为(x,y)的向量")
.def(py::init<double, double>(), "参数为x,y,构造值为(x,y)的向量")
.def(py::init<RT, RT, RT>(), "参数分别为x,y,w,构造值为x/w,y/w的向量")
.def(py::init<FT, FT>(), "参数为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_<Circle_2>(m, "Circle_2")
.def(py::init<Point_2, FT, CGAL::Orientation>())
.def(py::init<Point_2, Point_2, Point_2>())
.def(py::init<Point_2, Point_2, CGAL::Orientation>())
.def(py::init<Point_2, CGAL::Orientation>())
.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<Point_2, Point_2>,
"返回两个点的平方距离");
m.def("squared_distance", &squared_distance<Point_2, Segment_2>,