diff --git a/cpp/modmesh/universe/polygon.hpp b/cpp/modmesh/universe/polygon.hpp index e144e8bc..4a3d16c7 100644 --- a/cpp/modmesh/universe/polygon.hpp +++ b/cpp/modmesh/universe/polygon.hpp @@ -63,8 +63,1099 @@ union Triangle3dData Triangle3dNamed f; }; /* end union Triangle3dData */ +template +struct Trapezoid3dNamed +{ + T x0, x1, x2, x3, y0, y1, y2, y3, z0, z1, z2, z3; +}; /* end struct Trapezoid3dNamed */ + +template +union Trapezoid3dData +{ + T v[12]; + Trapezoid3dNamed f; +}; /* end union Trapezoid3dData */ + } /* end namespace detail */ +/** + * Trapezoid in three-dimensional space. + * + * @tparam T floating-point type + */ +template +class Trapezoid3d + : public NumberBase +{ + +public: + + static_assert(std::is_arithmetic_v, "T in Trapezoid3d must be arithmetic type"); + + using point_type = Point3d; + using value_type = typename point_type::value_type; + + Trapezoid3d(point_type const & p0, point_type const & p1, point_type const & p2, point_type const & p3) + : m_data{p0.x(), p1.x(), p2.x(), p3.x(), p0.y(), p1.y(), p2.y(), p3.y(), p0.z(), p1.z(), p2.z(), p3.z()} + { + } + + Trapezoid3d() = default; + Trapezoid3d(Trapezoid3d const &) = default; + Trapezoid3d & operator=(Trapezoid3d const &) = default; + Trapezoid3d(Trapezoid3d &&) = default; + Trapezoid3d & operator=(Trapezoid3d &&) = default; + ~Trapezoid3d() = default; + + point_type p0() const { return point_type(m_data.f.x0, m_data.f.y0, m_data.f.z0); } + void set_p0(point_type const & p) + { + m_data.f.x0 = p.x(); + m_data.f.y0 = p.y(); + m_data.f.z0 = p.z(); + } + point_type p1() const { return point_type(m_data.f.x1, m_data.f.y1, m_data.f.z1); } + void set_p1(point_type const & p) + { + m_data.f.x1 = p.x(); + m_data.f.y1 = p.y(); + m_data.f.z1 = p.z(); + } + point_type p2() const { return point_type(m_data.f.x2, m_data.f.y2, m_data.f.z2); } + void set_p2(point_type const & p) + { + m_data.f.x2 = p.x(); + m_data.f.y2 = p.y(); + m_data.f.z2 = p.z(); + } + point_type p3() const { return point_type(m_data.f.x3, m_data.f.y3, m_data.f.z3); } + void set_p3(point_type const & p) + { + m_data.f.x3 = p.x(); + m_data.f.y3 = p.y(); + m_data.f.z3 = p.z(); + } + + value_type x0() const { return m_data.f.x0; } + value_type & x0() { return m_data.f.x0; } + void set_x0(value_type v) { m_data.f.x0 = v; } + + value_type y0() const { return m_data.f.y0; } + value_type & y0() { return m_data.f.y0; } + void set_y0(value_type v) { m_data.f.y0 = v; } + + value_type z0() const { return m_data.f.z0; } + value_type & z0() { return m_data.f.z0; } + void set_z0(value_type v) { m_data.f.z0 = v; } + + value_type x1() const { return m_data.f.x1; } + value_type & x1() { return m_data.f.x1; } + void set_x1(value_type v) { m_data.f.x1 = v; } + + value_type y1() const { return m_data.f.y1; } + value_type & y1() { return m_data.f.y1; } + void set_y1(value_type v) { m_data.f.y1 = v; } + + value_type z1() const { return m_data.f.z1; } + value_type & z1() { return m_data.f.z1; } + void set_z1(value_type v) { m_data.f.z1 = v; } + + value_type x2() const { return m_data.f.x2; } + value_type & x2() { return m_data.f.x2; } + void set_x2(value_type v) { m_data.f.x2 = v; } + + value_type y2() const { return m_data.f.y2; } + value_type & y2() { return m_data.f.y2; } + void set_y2(value_type v) { m_data.f.y2 = v; } + + value_type z2() const { return m_data.f.z2; } + value_type & z2() { return m_data.f.z2; } + void set_z2(value_type v) { m_data.f.z2 = v; } + + value_type x3() const { return m_data.f.x3; } + value_type & x3() { return m_data.f.x3; } + void set_x3(value_type v) { m_data.f.x3 = v; } + + value_type y3() const { return m_data.f.y3; } + value_type & y3() { return m_data.f.y3; } + void set_y3(value_type v) { m_data.f.y3 = v; } + + value_type z3() const { return m_data.f.z3; } + value_type & z3() { return m_data.f.z3; } + void set_z3(value_type v) { m_data.f.z3 = v; } + + point_type operator[](size_t i) const { return point_type(m_data.v[i], m_data.v[i + 4], m_data.v[i + 8]); } + + point_type at(size_t i) const + { + check_size(i, 4); + return (*this)[i]; + } + + bool operator==(Trapezoid3d const & other) const + { + return m_data.v[0] == other.m_data.v[0] && + m_data.v[1] == other.m_data.v[1] && + m_data.v[2] == other.m_data.v[2] && + m_data.v[3] == other.m_data.v[3] && + m_data.v[4] == other.m_data.v[4] && + m_data.v[5] == other.m_data.v[5] && + m_data.v[6] == other.m_data.v[6] && + m_data.v[7] == other.m_data.v[7] && + m_data.v[8] == other.m_data.v[8] && + m_data.v[9] == other.m_data.v[9] && + m_data.v[10] == other.m_data.v[10] && + m_data.v[11] == other.m_data.v[11]; + } + + bool operator!=(Trapezoid3d const & other) const + { + return m_data.v[0] != other.m_data.v[0] || + m_data.v[1] != other.m_data.v[1] || + m_data.v[2] != other.m_data.v[2] || + m_data.v[3] != other.m_data.v[3] || + m_data.v[4] != other.m_data.v[4] || + m_data.v[5] != other.m_data.v[5] || + m_data.v[6] != other.m_data.v[6] || + m_data.v[7] != other.m_data.v[7] || + m_data.v[8] != other.m_data.v[8] || + m_data.v[9] != other.m_data.v[9] || + m_data.v[10] != other.m_data.v[10] || + m_data.v[11] != other.m_data.v[11]; + } + + size_t size() const { return 4; } + + void mirror_x() + { + m_data.f.y0 = -m_data.f.y0; + m_data.f.y1 = -m_data.f.y1; + m_data.f.y2 = -m_data.f.y2; + m_data.f.y3 = -m_data.f.y3; + m_data.f.z0 = -m_data.f.z0; + m_data.f.z1 = -m_data.f.z1; + m_data.f.z2 = -m_data.f.z2; + m_data.f.z3 = -m_data.f.z3; + } + + void mirror_y() + { + m_data.f.x0 = -m_data.f.x0; + m_data.f.x1 = -m_data.f.x1; + m_data.f.x2 = -m_data.f.x2; + m_data.f.x3 = -m_data.f.x3; + m_data.f.z0 = -m_data.f.z0; + m_data.f.z1 = -m_data.f.z1; + m_data.f.z2 = -m_data.f.z2; + m_data.f.z3 = -m_data.f.z3; + } + + void mirror_z() + { + m_data.f.x0 = -m_data.f.x0; + m_data.f.x1 = -m_data.f.x1; + m_data.f.x2 = -m_data.f.x2; + m_data.f.x3 = -m_data.f.x3; + m_data.f.y0 = -m_data.f.y0; + m_data.f.y1 = -m_data.f.y1; + m_data.f.y2 = -m_data.f.y2; + m_data.f.y3 = -m_data.f.y3; + } + + void mirror(Axis axis) + { + switch (axis) + { + case Axis::X: mirror_x(); break; + case Axis::Y: mirror_y(); break; + case Axis::Z: mirror_z(); break; + default: throw std::invalid_argument("Trapezoid3d::mirror: invalid axis"); break; + } + } + +private: + + void check_size(size_t i, size_t s) const + { + if (i >= s) + { + throw std::out_of_range(std::format("Trapezoid3d: i {} >= size {}", i, s)); + } + } + + detail::Trapezoid3dData m_data; + +}; /* end class Trapezoid3d */ + +using Trapezoid3dFp32 = Trapezoid3d; +using Trapezoid3dFp64 = Trapezoid3d; + +template +class TrapezoidPad + : public NumberBase + , public std::enable_shared_from_this> +{ + +private: + + struct ctor_passkey + { + }; + +public: + + using real_type = T; + using value_type = T; + using point_type = Point3d; + using trapezoid_type = Trapezoid3d; + using point_pad_type = PointPad; + + template + static std::shared_ptr> construct(Args &&... args) + { + return std::make_shared>(std::forward(args)..., ctor_passkey()); + } + + TrapezoidPad(uint8_t ndim, ctor_passkey const &) + : m_p0(point_pad_type::construct(ndim)) + , m_p1(point_pad_type::construct(ndim)) + , m_p2(point_pad_type::construct(ndim)) + , m_p3(point_pad_type::construct(ndim)) + { + } + + TrapezoidPad(uint8_t ndim, size_t nelem, ctor_passkey const &) + : m_p0(point_pad_type::construct(ndim, nelem)) + , m_p1(point_pad_type::construct(ndim, nelem)) + , m_p2(point_pad_type::construct(ndim, nelem)) + , m_p3(point_pad_type::construct(ndim, nelem)) + { + } + + TrapezoidPad( + SimpleArray const & x0, + SimpleArray const & y0, + SimpleArray const & x1, + SimpleArray const & y1, + SimpleArray const & x2, + SimpleArray const & y2, + SimpleArray const & x3, + SimpleArray const & y3, + ctor_passkey const &) + : m_p0(point_pad_type::construct(x0, y0)) + , m_p1(point_pad_type::construct(x1, y1)) + , m_p2(point_pad_type::construct(x2, y2)) + , m_p3(point_pad_type::construct(x3, y3)) + { + check_constructor_point_size(*m_p0, *m_p1, *m_p2, *m_p3); + } + + TrapezoidPad( + SimpleArray const & x0, + SimpleArray const & y0, + SimpleArray const & z0, + SimpleArray const & x1, + SimpleArray const & y1, + SimpleArray const & z1, + SimpleArray const & x2, + SimpleArray const & y2, + SimpleArray const & z2, + SimpleArray const & x3, + SimpleArray const & y3, + SimpleArray const & z3, + ctor_passkey const &) + : m_p0(point_pad_type::construct(x0, y0, z0)) + , m_p1(point_pad_type::construct(x1, y1, z1)) + , m_p2(point_pad_type::construct(x2, y2, z2)) + , m_p3(point_pad_type::construct(x3, y3, z3)) + { + check_constructor_point_size(*m_p0, *m_p1, *m_p2, *m_p3); + } + + TrapezoidPad( + SimpleArray & x0, + SimpleArray & y0, + SimpleArray & x1, + SimpleArray & y1, + SimpleArray & x2, + SimpleArray & y2, + SimpleArray & x3, + SimpleArray & y3, + bool clone, + ctor_passkey const &) + : m_p0(point_pad_type::construct(x0, y0, clone)) + , m_p1(point_pad_type::construct(x1, y1, clone)) + , m_p2(point_pad_type::construct(x2, y2, clone)) + , m_p3(point_pad_type::construct(x3, y3, clone)) + { + check_constructor_point_size(*m_p0, *m_p1, *m_p2, *m_p3); + } + + TrapezoidPad( + SimpleArray & x0, + SimpleArray & y0, + SimpleArray & z0, + SimpleArray & x1, + SimpleArray & y1, + SimpleArray & z1, + SimpleArray & x2, + SimpleArray & y2, + SimpleArray & z2, + SimpleArray & x3, + SimpleArray & y3, + SimpleArray & z3, + bool clone, + ctor_passkey const &) + : m_p0(point_pad_type::construct(x0, y0, z0, clone)) + , m_p1(point_pad_type::construct(x1, y1, z1, clone)) + , m_p2(point_pad_type::construct(x2, y2, z2, clone)) + , m_p3(point_pad_type::construct(x3, y3, z3, clone)) + { + check_constructor_point_size(*m_p0, *m_p1, *m_p2, *m_p3); + } + + std::shared_ptr> clone() + { + if (ndim() == 2) + { + return TrapezoidPad::construct(x0(), y0(), x1(), y1(), x2(), y2(), x3(), y3()); + } + else + { + return TrapezoidPad::construct(x0(), y0(), z0(), x1(), y1(), z1(), x2(), y2(), z2(), x3(), y3(), z3()); + } + } + + TrapezoidPad() = delete; + TrapezoidPad(TrapezoidPad const &) = delete; + TrapezoidPad(TrapezoidPad &&) = delete; + TrapezoidPad & operator=(TrapezoidPad const &) = delete; + TrapezoidPad & operator=(TrapezoidPad &&) = delete; + + ~TrapezoidPad() = default; + + void append(trapezoid_type const & t) + { + if (ndim() == 2) + { + m_p0->append(t.x0(), t.y0()); + m_p1->append(t.x1(), t.y1()); + m_p2->append(t.x2(), t.y2()); + m_p3->append(t.x3(), t.y3()); + } + else + { + m_p0->append(t.x0(), t.y0(), t.z0()); + m_p1->append(t.x1(), t.y1(), t.z1()); + m_p2->append(t.x2(), t.y2(), t.z2()); + m_p3->append(t.x3(), t.y3(), t.z3()); + } + } + + void append(point_type const & p0, point_type const & p1, point_type const & p2, point_type const & p3) + { + if (ndim() == 2) + { + m_p0->append(p0.x(), p0.y()); + m_p1->append(p1.x(), p1.y()); + m_p2->append(p2.x(), p2.y()); + m_p3->append(p3.x(), p3.y()); + } + else + { + m_p0->append(p0.x(), p0.y(), p0.z()); + m_p1->append(p1.x(), p1.y(), p1.z()); + m_p2->append(p2.x(), p2.y(), p2.z()); + m_p3->append(p3.x(), p3.y(), p3.z()); + } + } + + void append(T x0, T y0, T x1, T y1, T x2, T y2, T x3, T y3) + { + m_p0->append(x0, y0); + m_p1->append(x1, y1); + m_p2->append(x2, y2); + m_p3->append(x3, y3); + } + + void append(T x0, T y0, T z0, T x1, T y1, T z1, T x2, T y2, T z2, T x3, T y3, T z3) + { + m_p0->append(x0, y0, z0); + m_p1->append(x1, y1, z1); + m_p2->append(x2, y2, z2); + m_p3->append(x3, y3, z3); + } + + void extend_with(TrapezoidPad const & other) + { + size_t const ntrap = other.size(); + for (size_t i = 0; i < ntrap; ++i) + { + append(other.get(i)); + } + } + + uint8_t ndim() const { return m_p0->ndim(); } + + size_t size() const { return m_p0->size(); } + + SimpleArray pack_array() const + { + using shape_type = typename SimpleArray::shape_type; + SimpleArray ret(shape_type{m_p0->size(), static_cast(ndim() * 4)}); + if (ndim() == 3) + { + for (size_t i = 0; i < m_p0->size(); ++i) + { + ret(i, 0) = m_p0->x(i); + ret(i, 1) = m_p0->y(i); + ret(i, 2) = m_p0->z(i); + ret(i, 3) = m_p1->x(i); + ret(i, 4) = m_p1->y(i); + ret(i, 5) = m_p1->z(i); + ret(i, 6) = m_p2->x(i); + ret(i, 7) = m_p2->y(i); + ret(i, 8) = m_p2->z(i); + ret(i, 9) = m_p3->x(i); + ret(i, 10) = m_p3->y(i); + ret(i, 11) = m_p3->z(i); + } + } + else + { + for (size_t i = 0; i < m_p0->size(); ++i) + { + ret(i, 0) = m_p0->x(i); + ret(i, 1) = m_p0->y(i); + ret(i, 2) = m_p1->x(i); + ret(i, 3) = m_p1->y(i); + ret(i, 4) = m_p2->x(i); + ret(i, 5) = m_p2->y(i); + ret(i, 6) = m_p3->x(i); + ret(i, 7) = m_p3->y(i); + } + } + return ret; + } + + void expand(size_t length) + { + m_p0->expand(length); + m_p1->expand(length); + m_p2->expand(length); + m_p3->expand(length); + } + + real_type x0_at(size_t i) const { return m_p0->x_at(i); } + real_type y0_at(size_t i) const { return m_p0->y_at(i); } + real_type z0_at(size_t i) const { return m_p0->z_at(i); } + real_type x1_at(size_t i) const { return m_p1->x_at(i); } + real_type y1_at(size_t i) const { return m_p1->y_at(i); } + real_type z1_at(size_t i) const { return m_p1->z_at(i); } + real_type x2_at(size_t i) const { return m_p2->x_at(i); } + real_type y2_at(size_t i) const { return m_p2->y_at(i); } + real_type z2_at(size_t i) const { return m_p2->z_at(i); } + real_type x3_at(size_t i) const { return m_p3->x_at(i); } + real_type y3_at(size_t i) const { return m_p3->y_at(i); } + real_type z3_at(size_t i) const { return m_p3->z_at(i); } + real_type & x0_at(size_t i) { return m_p0->x_at(i); } + real_type & y0_at(size_t i) { return m_p0->y_at(i); } + real_type & z0_at(size_t i) { return m_p0->z_at(i); } + real_type & x1_at(size_t i) { return m_p1->x_at(i); } + real_type & y1_at(size_t i) { return m_p1->y_at(i); } + real_type & z1_at(size_t i) { return m_p1->z_at(i); } + real_type & x2_at(size_t i) { return m_p2->x_at(i); } + real_type & y2_at(size_t i) { return m_p2->y_at(i); } + real_type & z2_at(size_t i) { return m_p2->z_at(i); } + real_type & x3_at(size_t i) { return m_p3->x_at(i); } + real_type & y3_at(size_t i) { return m_p3->y_at(i); } + real_type & z3_at(size_t i) { return m_p3->z_at(i); } + + real_type x0(size_t i) const { return m_p0->x(i); } + real_type y0(size_t i) const { return m_p0->y(i); } + real_type z0(size_t i) const { return m_p0->z(i); } + real_type x1(size_t i) const { return m_p1->x(i); } + real_type y1(size_t i) const { return m_p1->y(i); } + real_type z1(size_t i) const { return m_p1->z(i); } + real_type x2(size_t i) const { return m_p2->x(i); } + real_type y2(size_t i) const { return m_p2->y(i); } + real_type z2(size_t i) const { return m_p2->z(i); } + real_type x3(size_t i) const { return m_p3->x(i); } + real_type y3(size_t i) const { return m_p3->y(i); } + real_type z3(size_t i) const { return m_p3->z(i); } + real_type & x0(size_t i) { return m_p0->x(i); } + real_type & y0(size_t i) { return m_p0->y(i); } + real_type & z0(size_t i) { return m_p0->z(i); } + real_type & x1(size_t i) { return m_p1->x(i); } + real_type & y1(size_t i) { return m_p1->y(i); } + real_type & z1(size_t i) { return m_p1->z(i); } + real_type & x2(size_t i) { return m_p2->x(i); } + real_type & y2(size_t i) { return m_p2->y(i); } + real_type & z2(size_t i) { return m_p2->z(i); } + real_type & x3(size_t i) { return m_p3->x(i); } + real_type & y3(size_t i) { return m_p3->y(i); } + real_type & z3(size_t i) { return m_p3->z(i); } + + point_type p0_at(size_t i) const { return m_p0->get_at(i); } + point_type p1_at(size_t i) const { return m_p1->get_at(i); } + point_type p2_at(size_t i) const { return m_p2->get_at(i); } + point_type p3_at(size_t i) const { return m_p3->get_at(i); } + void set_p0_at(size_t i, point_type const & p) { m_p0->set_at(i, p); } + void set_p1_at(size_t i, point_type const & p) { m_p1->set_at(i, p); } + void set_p2_at(size_t i, point_type const & p) { m_p2->set_at(i, p); } + void set_p3_at(size_t i, point_type const & p) { m_p3->set_at(i, p); } + + point_type p0(size_t i) const { return m_p0->get(i); } + point_type p1(size_t i) const { return m_p1->get(i); } + point_type p2(size_t i) const { return m_p2->get(i); } + point_type p3(size_t i) const { return m_p3->get(i); } + void set_p0(size_t i, point_type const & p) { m_p0->set(i, p); } + void set_p1(size_t i, point_type const & p) { m_p1->set(i, p); } + void set_p2(size_t i, point_type const & p) { m_p2->set(i, p); } + void set_p3(size_t i, point_type const & p) { m_p3->set(i, p); } + + SimpleArray x0() { return m_p0->x(); } + SimpleArray y0() { return m_p0->y(); } + SimpleArray z0() { return m_p0->z(); } + SimpleArray x1() { return m_p1->x(); } + SimpleArray y1() { return m_p1->y(); } + SimpleArray z1() { return m_p1->z(); } + SimpleArray x2() { return m_p2->x(); } + SimpleArray y2() { return m_p2->y(); } + SimpleArray z2() { return m_p2->z(); } + SimpleArray x3() { return m_p3->x(); } + SimpleArray y3() { return m_p3->y(); } + SimpleArray z3() { return m_p3->z(); } + + std::shared_ptr p0() { return m_p0; } + std::shared_ptr p1() { return m_p1; } + std::shared_ptr p2() { return m_p2; } + std::shared_ptr p3() { return m_p3; } + + trapezoid_type get_at(size_t i) const + { + if (ndim() == 3) + { + return trapezoid_type(point_type(x0_at(i), y0_at(i), z0_at(i)), + point_type(x1_at(i), y1_at(i), z1_at(i)), + point_type(x2_at(i), y2_at(i), z2_at(i)), + point_type(x3_at(i), y3_at(i), z3_at(i))); + } + else + { + return trapezoid_type(point_type(x0_at(i), y0_at(i), 0.0), + point_type(x1_at(i), y1_at(i), 0.0), + point_type(x2_at(i), y2_at(i), 0.0), + point_type(x3_at(i), y3_at(i), 0.0)); + } + } + void set_at(size_t i, trapezoid_type const & t) + { + x0_at(i) = t.x0(); + y0_at(i) = t.y0(); + x1_at(i) = t.x1(); + y1_at(i) = t.y1(); + x2_at(i) = t.x2(); + y2_at(i) = t.y2(); + x3_at(i) = t.x3(); + y3_at(i) = t.y3(); + if (ndim() == 3) + { + z0_at(i) = t.z0(); + z1_at(i) = t.z1(); + z2_at(i) = t.z2(); + z3_at(i) = t.z3(); + } + } + void set_at(size_t i, point_type const & p0, point_type const & p1, point_type const & p2, point_type const & p3) + { + x0_at(i) = p0.x(); + y0_at(i) = p0.y(); + x1_at(i) = p1.x(); + y1_at(i) = p1.y(); + x2_at(i) = p2.x(); + y2_at(i) = p2.y(); + x3_at(i) = p3.x(); + y3_at(i) = p3.y(); + if (ndim() == 3) + { + z0_at(i) = p0.z(); + z1_at(i) = p1.z(); + z2_at(i) = p2.z(); + z3_at(i) = p3.z(); + } + } + // clang-format off + void set_at(size_t i, value_type x0, value_type y0, + value_type x1, value_type y1, + value_type x2, value_type y2, + value_type x3, value_type y3) + // clang-format on + { + x0_at(i) = x0; + y0_at(i) = y0; + x1_at(i) = x1; + y1_at(i) = y1; + x2_at(i) = x2; + y2_at(i) = y2; + x3_at(i) = x3; + y3_at(i) = y3; + } + // clang-format off + void set_at(size_t i, value_type x0, value_type y0, value_type z0, + value_type x1, value_type y1, value_type z1, + value_type x2, value_type y2, value_type z2, + value_type x3, value_type y3, value_type z3) + // clang-format on + { + x0_at(i) = x0; + y0_at(i) = y0; + x1_at(i) = x1; + y1_at(i) = y1; + x2_at(i) = x2; + y2_at(i) = y2; + x3_at(i) = x3; + y3_at(i) = y3; + if (ndim() == 3) + { + z0_at(i) = z0; + z1_at(i) = z1; + z2_at(i) = z2; + z3_at(i) = z3; + } + } + + trapezoid_type get(size_t i) const + { + if (ndim() == 3) + { + return trapezoid_type(point_type(x0(i), y0(i), z0(i)), + point_type(x1(i), y1(i), z1(i)), + point_type(x2(i), y2(i), z2(i)), + point_type(x3(i), y3(i), z3(i))); + } + else + { + return trapezoid_type(point_type(x0(i), y0(i), 0.0), + point_type(x1(i), y1(i), 0.0), + point_type(x2(i), y2(i), 0.0), + point_type(x3(i), y3(i), 0.0)); + } + } + void set(size_t i, trapezoid_type const & t) + { + x0(i) = t.x0(); + y0(i) = t.y0(); + x1(i) = t.x1(); + y1(i) = t.y1(); + x2(i) = t.x2(); + y2(i) = t.y2(); + x3(i) = t.x3(); + y3(i) = t.y3(); + if (ndim() == 3) + { + z0(i) = t.z0(); + z1(i) = t.z1(); + z2(i) = t.z2(); + z3(i) = t.z3(); + } + } + void set(size_t i, point_type const & p0, point_type const & p1, point_type const & p2, point_type const & p3) + { + x0(i) = p0.x(); + y0(i) = p0.y(); + x1(i) = p1.x(); + y1(i) = p1.y(); + x2(i) = p2.x(); + y2(i) = p2.y(); + x3(i) = p3.x(); + y3(i) = p3.y(); + if (ndim() == 3) + { + z0(i) = p0.z(); + z1(i) = p1.z(); + z2(i) = p2.z(); + z3(i) = p3.z(); + } + } + // clang-format off + void set(size_t i, value_type x0_value, value_type y0_value, + value_type x1_value, value_type y1_value, + value_type x2_value, value_type y2_value, + value_type x3_value, value_type y3_value) + // clang-format on + { + x0(i) = x0_value; + y0(i) = y0_value; + x1(i) = x1_value; + y1(i) = y1_value; + x2(i) = x2_value; + y2(i) = y2_value; + x3(i) = x3_value; + y3(i) = y3_value; + } + // clang-format off + void set(size_t i, value_type x0_value, value_type y0_value, value_type z0_value, + value_type x1_value, value_type y1_value, value_type z1_value, + value_type x2_value, value_type y2_value, value_type z2_value, + value_type x3_value, value_type y3_value, value_type z3_value) + // clang-format on + { + x0(i) = x0_value; + y0(i) = y0_value; + x1(i) = x1_value; + y1(i) = y1_value; + x2(i) = x2_value; + y2(i) = y2_value; + x3(i) = x3_value; + y3(i) = y3_value; + if (ndim() == 3) + { + z0(i) = z0_value; + z1(i) = z1_value; + z2(i) = z2_value; + z3(i) = z3_value; + } + } + + void mirror_x() + { + size_t const ntrap = size(); + for (size_t i = 0; i < ntrap; ++i) + { + y0(i) = -y0(i); + y1(i) = -y1(i); + y2(i) = -y2(i); + y3(i) = -y3(i); + if (ndim() == 3) + { + z0(i) = -z0(i); + z1(i) = -z1(i); + z2(i) = -z2(i); + z3(i) = -z3(i); + } + } + } + + void mirror_y() + { + size_t const ntrap = size(); + for (size_t i = 0; i < ntrap; ++i) + { + x0(i) = -x0(i); + x1(i) = -x1(i); + x2(i) = -x2(i); + x3(i) = -x3(i); + if (ndim() == 3) + { + z0(i) = -z0(i); + z1(i) = -z1(i); + z2(i) = -z2(i); + z3(i) = -z3(i); + } + } + } + + void mirror_z() + { + if (ndim() != 3) + { + throw std::out_of_range( + std::format("TrapezoidPad::mirror_z: cannot mirror Z axis for ndim {}", int(ndim()))); + } + size_t const ntrap = size(); + for (size_t i = 0; i < ntrap; ++i) + { + x0(i) = -x0(i); + x1(i) = -x1(i); + x2(i) = -x2(i); + x3(i) = -x3(i); + y0(i) = -y0(i); + y1(i) = -y1(i); + y2(i) = -y2(i); + y3(i) = -y3(i); + } + } + + void mirror(Axis axis) + { + switch (axis) + { + case Axis::X: mirror_x(); break; + case Axis::Y: mirror_y(); break; + case Axis::Z: mirror_z(); break; + default: throw std::invalid_argument("TrapezoidPad::mirror: invalid axis"); break; + } + } + +private: + + void check_constructor_point_size(point_pad_type const & p0, point_pad_type const & p1, point_pad_type const & p2, point_pad_type const & p3) + { + if (m_p0->size() != m_p1->size() || m_p0->size() != m_p2->size() || m_p0->size() != m_p3->size()) + { + throw std::invalid_argument( + std::format("TrapezoidPad::TrapezoidPad: p0.size() {} p1.size() {} p2.size() {} p3.size() {} are not the same", + p0.size(), + p1.size(), + p2.size(), + p3.size())); + } + } + + std::shared_ptr m_p0; + std::shared_ptr m_p1; + std::shared_ptr m_p2; + std::shared_ptr m_p3; + +}; /* end class TrapezoidPad */ + +using TrapezoidPadFp32 = TrapezoidPad; +using TrapezoidPadFp64 = TrapezoidPad; + +/** + * Forward declaration of Polygon3d for use in helper classes. + */ +template +class Polygon3d; + +/** + * Forward declaration of PolygonPad for use in Polygon3d handle class. + */ +template +class PolygonPad; + +/** + * Helper class for trapezoidal decomposition of polygons. + * + * This class implements the sweep line algorithm to decompose polygons into + * trapezoids. The decomposition is used for polygon boolean operations. + * + * @tparam T floating-point type + */ +template +class TrapezoidalDecomposer +{ + +public: + + using value_type = T; + using point_type = Point3d; + using trapezoid_pad_type = TrapezoidPad; + using ssize_type = ssize_t; + +private: + /** + * Internal structure representing a trapezoid during decomposition for the sweep line algorithm. + * The trap has bottom and top lines perpendicular to the Y-axis, defined by lower_y and upper_y. + */ + struct YTrap + { + value_type lower_y, upper_y; + value_type lower_x0, lower_x1, upper_x0, upper_x1; + value_type lower_z0, lower_z1, upper_z0, upper_z1; // TODO: currently unused for 2D decomposition + size_t source_polygon; + }; /* end struct YTrap */ + +public: + + TrapezoidalDecomposer(uint8_t ndim) + : m_trapezoids(trapezoid_pad_type::construct(ndim)) + { + } + + TrapezoidalDecomposer() = delete; + TrapezoidalDecomposer(TrapezoidalDecomposer const &) = delete; + TrapezoidalDecomposer(TrapezoidalDecomposer &&) = delete; + TrapezoidalDecomposer & operator=(TrapezoidalDecomposer const &) = delete; + TrapezoidalDecomposer & operator=(TrapezoidalDecomposer &&) = delete; + ~TrapezoidalDecomposer() = default; + + /** + * Decompose a polygon into trapezoids using vertical sweep line algorithm. + * + * @param polygon_id ID of the polygon to decompose + * @param points Vector of polygon vertices in order + * @return Pair of begin and end indices into the trapezoid pad + */ + std::pair decompose(size_t polygon_id, std::vector const & points) + { + if (polygon_id >= m_begins.size()) + { + m_begins.reserve(polygon_id + 1); + m_ends.reserve(polygon_id + 1); + while (m_begins.size() <= polygon_id) + { + m_begins.push_back(-1); + m_ends.push_back(-1); + } + } + + if (m_begins[polygon_id] != -1) + { + return {static_cast(m_begins[polygon_id]), static_cast(m_ends[polygon_id])}; + } + + size_t const begin_index = m_trapezoids->size(); + + // TODO: Implement sweep line algorithm to generate trapezoids + // For now, just mark the range as empty + + size_t const end_index = m_trapezoids->size(); + + m_begins[polygon_id] = static_cast(begin_index); + m_ends[polygon_id] = static_cast(end_index); + + return {begin_index, end_index}; + } + + size_t num_trapezoids(size_t polygon_id) const + { + if (polygon_id >= m_begins.size() || m_begins[polygon_id] == -1) + { + return 0; + } + return static_cast(m_ends[polygon_id] - m_begins[polygon_id]); + } + + std::shared_ptr trapezoids() { return m_trapezoids; } + std::shared_ptr trapezoids() const { return m_trapezoids; } + + void clear() + { + m_begins.clear(); + m_ends.clear(); + m_trapezoids = trapezoid_pad_type::construct(m_trapezoids->ndim()); + } + +private: + + std::shared_ptr m_trapezoids; + std::vector m_begins; + std::vector m_ends; + +}; /* end class TrapezoidalDecomposer */ + +using TrapezoidalDecomposerFp32 = TrapezoidalDecomposer; +using TrapezoidalDecomposerFp64 = TrapezoidalDecomposer; + +/** + * This class implements the union algorithm for two polygons by decomposing them + * into trapezoids and merging overlapping regions. + * + * @tparam T floating-point type + */ +template +class AreaBooleanUnion +{ + +public: + + using value_type = T; + using point_type = Point3d; + using polygon_type = Polygon3d; + using polygon_pad_type = PolygonPad; + + AreaBooleanUnion() = default; + AreaBooleanUnion(AreaBooleanUnion const &) = delete; + AreaBooleanUnion(AreaBooleanUnion &&) = delete; + AreaBooleanUnion & operator=(AreaBooleanUnion const &) = delete; + AreaBooleanUnion & operator=(AreaBooleanUnion &&) = delete; + ~AreaBooleanUnion() = default; + + /** + * Compute union of two polygons. + * + * @param pad Polygon container holding both polygons + * @param polygon_id1 ID of first polygon + * @param polygon_id2 ID of second polygon + * @return polygon pad containing the union of the two polygons + */ + std::shared_ptr compute(const std::shared_ptr & pad, size_t polygon_id1, size_t polygon_id2); + +}; /* end class AreaBooleanUnion */ + +using AreaBooleanUnionFp32 = AreaBooleanUnion; +using AreaBooleanUnionFp64 = AreaBooleanUnion; + +/** + * This class implements the intersection algorithm for two polygons by decomposing them + * into trapezoids and finding overlapping regions. + * + * @tparam T floating-point type + */ +template +class AreaBooleanIntersection +{ + +public: + + using value_type = T; + using point_type = Point3d; + using polygon_type = Polygon3d; + using polygon_pad_type = PolygonPad; + + AreaBooleanIntersection() = default; + AreaBooleanIntersection(AreaBooleanIntersection const &) = delete; + AreaBooleanIntersection(AreaBooleanIntersection &&) = delete; + AreaBooleanIntersection & operator=(AreaBooleanIntersection const &) = delete; + AreaBooleanIntersection & operator=(AreaBooleanIntersection &&) = delete; + ~AreaBooleanIntersection() = default; + + /** + * Compute intersection of two polygons. + * + * @param pad Polygon container holding both polygons + * @param polygon_id1 ID of first polygon + * @param polygon_id2 ID of second polygon + * @return polygon pad containing polygons forming the intersection + */ + std::shared_ptr compute(const std::shared_ptr & pad, size_t polygon_id1, size_t polygon_id2); + +}; /* end class AreaBooleanIntersection */ + +using AreaBooleanIntersectionFp32 = AreaBooleanIntersection; +using AreaBooleanIntersectionFp64 = AreaBooleanIntersection; + +/** + * This class implements the difference algorithm for two polygons (p1 - p2) by decomposing + * them into trapezoids and removing overlapping regions. + * + * @tparam T floating-point type + */ +template +class AreaBooleanDifference +{ + +public: + + using value_type = T; + using point_type = Point3d; + using polygon_type = Polygon3d; + using polygon_pad_type = PolygonPad; + + AreaBooleanDifference() = default; + AreaBooleanDifference(AreaBooleanDifference const &) = delete; + AreaBooleanDifference(AreaBooleanDifference &&) = delete; + AreaBooleanDifference & operator=(AreaBooleanDifference const &) = delete; + AreaBooleanDifference & operator=(AreaBooleanDifference &&) = delete; + ~AreaBooleanDifference() = default; + + /** + * Compute difference of two polygons (p1 - p2). + * + * @param pad Polygon container holding both polygons + * @param polygon_id1 ID of first polygon + * @param polygon_id2 ID of second polygon to subtract + * @return polygon pad containing polygons forming the difference + */ + std::shared_ptr compute(const std::shared_ptr & pad, size_t polygon_id1, size_t polygon_id2); + +}; /* end class AreaBooleanDifference */ + +using AreaBooleanDifferenceFp32 = AreaBooleanDifference; +using AreaBooleanDifferenceFp64 = AreaBooleanDifference; + /** * Triangle in three-dimensional space. * @@ -660,7 +1751,11 @@ class TrianglePad z2(i) = p2.z(); } } - void set(size_t i, value_type x0_value, value_type y0_value, value_type x1_value, value_type y1_value, value_type x2_value, value_type y2_value) + // clang-format off + void set(size_t i, value_type x0_value, value_type y0_value, + value_type x1_value, value_type y1_value, + value_type x2_value, value_type y2_value) + // clang-format on { x0(i) = x0_value; y0(i) = y0_value; @@ -669,7 +1764,11 @@ class TrianglePad x2(i) = x2_value; y2(i) = y2_value; } - void set(size_t i, value_type x0_value, value_type y0_value, value_type z0_value, value_type x1_value, value_type y1_value, value_type z1_value, value_type x2_value, value_type y2_value, value_type z2_value) + // clang-format off + void set(size_t i, value_type x0_value, value_type y0_value, value_type z0_value, + value_type x1_value, value_type y1_value, value_type z1_value, + value_type x2_value, value_type y2_value, value_type z2_value) + // clang-format on { x0(i) = x0_value; y0(i) = y0_value; @@ -789,12 +1888,6 @@ class TrianglePad using TrianglePadFp32 = TrianglePad; using TrianglePadFp64 = TrianglePad; -/** - * Forward declaration of PolygonPad for use in Polygon3d handle class. - */ -template -class PolygonPad; - /** * Polygon3d handle class - lightweight view into a polygon stored in PolygonPad. * @@ -934,6 +2027,9 @@ class Polygon3d * with each polygon defined by a range [start, end) in the node list. This avoids * memory duplication compared to storing line segments. * + * Note that PolygonPad assumes polygons are in a XY plane currently. + * TODO: Extend to 3D polygons in arbitrary planes. + * * All nodes follow the right-hand rule: * - Counter-clockwise ordering = positive area polygon * - Clockwise ordering = negative area polygon (holes) @@ -965,9 +2061,11 @@ class PolygonPad using segment_type = Segment3d; using point_pad_type = PointPad; using polygon_type = Polygon3d; + using polygon_pad_type = PolygonPad; using segment_pad_type = SegmentPad; using curve_pad_type = CurvePad; using rtree_type = RTree, RTreeValueOps>>; + using trapezoid_decomposer_type = TrapezoidalDecomposer; template static std::shared_ptr> construct(Args &&... args) @@ -978,6 +2076,7 @@ class PolygonPad PolygonPad(uint8_t ndim, ctor_passkey const &) : m_points(point_pad_type::construct(ndim)) , m_rtree(std::make_unique()) + , m_decomposer(ndim) { } @@ -1129,6 +2228,51 @@ class PolygonPad */ void rebuild_rtree(); + /** + * Decompose a polygon into trapezoids using vertical sweep line algorithm. + * + * @param polygon_id ID of the polygon to decompose + * @return Pair of begin and end indices into the decomposer's trapezoid pad + * @throws std::out_of_range if polygon_id is invalid + */ + std::pair decompose_to_trapezoid(size_t polygon_id); + + /** + * Compute union of two polygons using trapezoidal decomposition. + * + * @param p1 First polygon + * @param p2 Second polygon + * @return polygon pad forming the union + */ + std::shared_ptr boolean_union(polygon_type const & p1, polygon_type const & p2) + { + return m_boolean_union.compute(this->shared_from_this(), p1.polygon_id(), p2.polygon_id()); + } + + /** + * Compute intersection of two polygons using trapezoidal decomposition. + * + * @param p1 First polygon + * @param p2 Second polygon + * @return polygon pad forming the intersection + */ + std::shared_ptr boolean_intersection(polygon_type const & p1, polygon_type const & p2) + { + return m_boolean_intersection.compute(this->shared_from_this(), p1.polygon_id(), p2.polygon_id()); + } + + /** + * Compute difference of two polygons (p1 - p2). + * + * @param p1 First polygon + * @param p2 Second polygon to subtract + * @return polygon pad forming the difference + */ + std::shared_ptr boolean_difference(polygon_type const & p1, polygon_type const & p2) + { + return m_boolean_difference.compute(this->shared_from_this(), p1.polygon_id(), p2.polygon_id()); + } + private: friend class Polygon3d; @@ -1140,13 +2284,10 @@ class PolygonPad SimpleCollector m_ends; std::unique_ptr m_rtree; - // TODO: Add trapezoidal map data structure for polygon boolean operations - // Reference: http://www0.cs.ucl.ac.uk/staff/m.slater/Teaching/CG/1997-98/Solutions/Trap/ - // Will need: - // - Trapezoid decomposition structure - // - Point location query structure - // - Integration with node lists for synchronization - + trapezoid_decomposer_type m_decomposer; + AreaBooleanUnion m_boolean_union; + AreaBooleanIntersection m_boolean_intersection; + AreaBooleanDifference m_boolean_difference; }; /* end class PolygonPad */ using PolygonPadFp32 = PolygonPad; @@ -1201,6 +2342,12 @@ Polygon3d PolygonPad::add_polygon(std::vector const & nodes) for (point_type const & node : nodes) { + // check if the point is in XY plane + if (node.z() != 0) + { + throw std::invalid_argument("PolygonPad::add_polygon: all nodes must lie in the XY plane (z=0)"); + } + m_points->append(node); } @@ -1209,7 +2356,8 @@ Polygon3d PolygonPad::add_polygon(std::vector const & nodes) m_begins.push_back(begin_index); m_ends.push_back(end_index); - polygon_type polygon(this->shared_from_this(), polygon_id, typename polygon_type::ctor_passkey()); + std::shared_ptr const> const_this = this->shared_from_this(); + polygon_type polygon(const_this, polygon_id, typename polygon_type::ctor_passkey()); rebuild_polygon_rtree(polygon); return polygon; @@ -1469,6 +2617,52 @@ void PolygonPad::rebuild_polygon_rtree(polygon_type const & polygon) } } +template +std::pair PolygonPad::decompose_to_trapezoid(size_t polygon_id) +{ + if (polygon_id >= num_polygons()) + { + throw std::out_of_range( + std::format("PolygonPad::decompose_to_trapezoid: polygon_id {} >= num_polygons {}", + polygon_id, + num_polygons())); + } + + polygon_type polygon = get_polygon(polygon_id); + std::vector points; + points.reserve(polygon.nnode()); + for (size_t i = 0; i < polygon.nnode(); ++i) + { + points.push_back(polygon.node(i)); + } + + return m_decomposer.decompose(polygon_id, points); +} + +template +std::shared_ptr> AreaBooleanUnion::compute(const std::shared_ptr> & pad, size_t polygon_id1, size_t polygon_id2) +{ + // TODO: A proper implementation would merge overlapping regions using trapezoidal decomposition + auto empty_pad = PolygonPad::construct(pad->ndim()); + return empty_pad; +} + +template +std::shared_ptr> AreaBooleanIntersection::compute(const std::shared_ptr> & pad, size_t polygon_id1, size_t polygon_id2) +{ + // TODO: A proper implementation would find overlapping regions using trapezoidal decomposition + auto empty_pad = PolygonPad::construct(pad->ndim()); + return empty_pad; +} + +template +std::shared_ptr> AreaBooleanDifference::compute(const std::shared_ptr> & pad, size_t polygon_id1, size_t polygon_id2) +{ + // TODO: A proper implementation would subtract overlapping regions using trapezoidal decomposition + auto empty_pad = PolygonPad::construct(pad->ndim()); + return empty_pad; +} + } /* end namespace modmesh */ // vim: set ff=unix fenc=utf8 et sw=4 ts=4 sts=4: diff --git a/cpp/modmesh/universe/pymod/wrap_polygon.cpp b/cpp/modmesh/universe/pymod/wrap_polygon.cpp index cb7e9f96..32d5ae9a 100644 --- a/cpp/modmesh/universe/pymod/wrap_polygon.cpp +++ b/cpp/modmesh/universe/pymod/wrap_polygon.cpp @@ -143,7 +143,29 @@ WrapPolygonPad::WrapPolygonPad(pybind11::module & mod, const char * pyname, c return results; }, py::arg("box")) - .def("rebuild_rtree", &wrapped_type::rebuild_rtree); + .def("rebuild_rtree", &wrapped_type::rebuild_rtree) + .def( + "decompose_to_trapezoid", + [](wrapped_type & self, size_t polygon_id) + { + return self.decompose_to_trapezoid(polygon_id); + }, + py::arg("polygon_id")) + .def( + "boolean_union", + &wrapped_type::boolean_union, + py::arg("p1"), + py::arg("p2")) + .def( + "boolean_intersection", + &wrapped_type::boolean_intersection, + py::arg("p1"), + py::arg("p2")) + .def( + "boolean_difference", + &wrapped_type::boolean_difference, + py::arg("p1"), + py::arg("p2")); } void wrap_polygon(pybind11::module & mod) diff --git a/tests/test_polygon3d.py b/tests/test_polygon3d.py index d69d57eb..a937c16a 100644 --- a/tests/test_polygon3d.py +++ b/tests/test_polygon3d.py @@ -332,6 +332,214 @@ def test_polygon_identity_same_pad_different_id(self): self.assertFalse(polygon1.is_same(polygon2)) self.assertTrue(polygon1.is_not_same(polygon2)) + # TODO: Verify and implement boolean operations + # once the C++ side is complete. + def test_boolean_union_simple(self): + """Test polygon boolean union with two overlapping squares.""" + pad = self.PolygonPad(ndim=2) + + # First square: (0,0) to (2,2) + square1_nodes = [ + self.Point(0.0, 0.0, 0.0), + self.Point(2.0, 0.0, 0.0), + self.Point(2.0, 2.0, 0.0), + self.Point(0.0, 2.0, 0.0) + ] + polygon1 = pad.add_polygon(square1_nodes) + + # Second square: (1,1) to (3,3) - overlaps with first + square2_nodes = [ + self.Point(1.0, 1.0, 0.0), + self.Point(3.0, 1.0, 0.0), + self.Point(3.0, 3.0, 0.0), + self.Point(1.0, 3.0, 0.0) + ] + polygon2 = pad.add_polygon(square2_nodes) + + # Call boolean_union - should not crash even if implementation is TODO + try: + result = pad.boolean_union(polygon1, polygon2) + # Implementation is not complete yet, but should return a list + self.assertIsInstance(result, self.PolygonPad) + except NotImplementedError: + # Expected if method raises NotImplementedError + pass + + # TODO: Verify and implement boolean operations + # once the C++ side is complete. + def test_boolean_intersection_simple(self): + """Test polygon boolean intersection with two overlapping squares.""" + pad = self.PolygonPad(ndim=2) + + # First square: (0,0) to (2,2) + square1_nodes = [ + self.Point(0.0, 0.0, 0.0), + self.Point(2.0, 0.0, 0.0), + self.Point(2.0, 2.0, 0.0), + self.Point(0.0, 2.0, 0.0) + ] + polygon1 = pad.add_polygon(square1_nodes) + + # Second square: (1,1) to (3,3) - overlaps with first + # Intersection should be (1,1) to (2,2) + square2_nodes = [ + self.Point(1.0, 1.0, 0.0), + self.Point(3.0, 1.0, 0.0), + self.Point(3.0, 3.0, 0.0), + self.Point(1.0, 3.0, 0.0) + ] + polygon2 = pad.add_polygon(square2_nodes) + + # Call boolean_intersection - should not crash even + # if implementation is TODO + try: + result = pad.boolean_intersection(polygon1, polygon2) + # Implementation is not complete yet, but should return a list + self.assertIsInstance(result, self.PolygonPad) + except NotImplementedError: + # Expected if method raises NotImplementedError + pass + + # TODO: Verify and implement boolean operations + # once the C++ side is complete. + def test_boolean_difference_simple(self): + """Test polygon boolean difference with two overlapping squares.""" + pad = self.PolygonPad(ndim=2) + + # First square: (0,0) to (2,2) + square1_nodes = [ + self.Point(0.0, 0.0, 0.0), + self.Point(2.0, 0.0, 0.0), + self.Point(2.0, 2.0, 0.0), + self.Point(0.0, 2.0, 0.0) + ] + polygon1 = pad.add_polygon(square1_nodes) + + # Second square: (1,1) to (3,3) - overlaps with first + # Difference (polygon1 - polygon2) should be L-shaped region + square2_nodes = [ + self.Point(1.0, 1.0, 0.0), + self.Point(3.0, 1.0, 0.0), + self.Point(3.0, 3.0, 0.0), + self.Point(1.0, 3.0, 0.0) + ] + polygon2 = pad.add_polygon(square2_nodes) + + # Call boolean_difference - should not crash even + # if implementation is TODO + try: + result = pad.boolean_difference(polygon1, polygon2) + # Implementation is not complete yet, but should return a list + self.assertIsInstance(result, self.PolygonPad) + except NotImplementedError: + # Expected if method raises NotImplementedError + pass + + # TODO: Verify and implement boolean operations + # once the C++ side is complete. + def test_boolean_union_non_overlapping(self): + """Test polygon boolean union with two non-overlapping squares.""" + pad = self.PolygonPad(ndim=2) + + # First square: (0,0) to (1,1) + square1_nodes = [ + self.Point(0.0, 0.0, 0.0), + self.Point(1.0, 0.0, 0.0), + self.Point(1.0, 1.0, 0.0), + self.Point(0.0, 1.0, 0.0) + ] + polygon1 = pad.add_polygon(square1_nodes) + + # Second square: (2,2) to (3,3) - no overlap + square2_nodes = [ + self.Point(2.0, 2.0, 0.0), + self.Point(3.0, 2.0, 0.0), + self.Point(3.0, 3.0, 0.0), + self.Point(2.0, 3.0, 0.0) + ] + polygon2 = pad.add_polygon(square2_nodes) + + # Call boolean_union - should not crash + try: + result = pad.boolean_union(polygon1, polygon2) + self.assertIsInstance(result, self.PolygonPad) + except NotImplementedError: + pass + + # TODO: Verify and implement boolean operations + # once the C++ side is complete. + def test_boolean_intersection_non_overlapping(self): + """Test polygon boolean intersection with two non-overlapping squares. + """ + pad = self.PolygonPad(ndim=2) + + # First square: (0,0) to (1,1) + square1_nodes = [ + self.Point(0.0, 0.0, 0.0), + self.Point(1.0, 0.0, 0.0), + self.Point(1.0, 1.0, 0.0), + self.Point(0.0, 1.0, 0.0) + ] + polygon1 = pad.add_polygon(square1_nodes) + + # Second square: (2,2) to (3,3) - no overlap + # Intersection should be empty + square2_nodes = [ + self.Point(2.0, 2.0, 0.0), + self.Point(3.0, 2.0, 0.0), + self.Point(3.0, 3.0, 0.0), + self.Point(2.0, 3.0, 0.0) + ] + polygon2 = pad.add_polygon(square2_nodes) + + # Call boolean_intersection - should not crash + try: + result = pad.boolean_intersection(polygon1, polygon2) + self.assertIsInstance(result, self.PolygonPad) + except NotImplementedError: + pass + + # TODO: Verify and implement boolean operations + # once the C++ side is complete. + def test_boolean_operations_triangle(self): + """Test polygon boolean operations with triangular polygons.""" + pad = self.PolygonPad(ndim=2) + + # Triangle 1: Right triangle at origin + triangle1_nodes = [ + self.Point(0.0, 0.0, 0.0), + self.Point(2.0, 0.0, 0.0), + self.Point(0.0, 2.0, 0.0) + ] + polygon1 = pad.add_polygon(triangle1_nodes) + + # Triangle 2: Right triangle shifted + triangle2_nodes = [ + self.Point(1.0, 1.0, 0.0), + self.Point(3.0, 1.0, 0.0), + self.Point(1.0, 3.0, 0.0) + ] + polygon2 = pad.add_polygon(triangle2_nodes) + + # Test all three operations - should not crash + try: + result_union = pad.boolean_union(polygon1, polygon2) + self.assertIsInstance(result_union, self.PolygonPad) + except NotImplementedError: + pass + + try: + result_intersection = pad.boolean_intersection(polygon1, polygon2) + self.assertIsInstance(result_intersection, self.PolygonPad) + except NotImplementedError: + pass + + try: + result_difference = pad.boolean_difference(polygon1, polygon2) + self.assertIsInstance(result_difference, self.PolygonPad) + except NotImplementedError: + pass + class Polygon3dFp32TC(Polygon3dTB, unittest.TestCase):