Skip to content

Commit a55ad73

Browse files
committed
Implement a 3D version of the clipping algorithm
1 parent b64f3d7 commit a55ad73

File tree

2 files changed

+194
-35
lines changed

2 files changed

+194
-35
lines changed

src/_path.h

Lines changed: 172 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,36 @@ struct XY
4545

4646
typedef std::vector<XY> Polygon;
4747

48-
inline void
49-
_finalize_polygon(std::vector<Polygon> &result, bool closed_only)
48+
struct XYZ
49+
{
50+
double x;
51+
double y;
52+
double z;
53+
54+
XYZ() : x(0), y(0), z(0) {}
55+
XYZ(double x_, double y_, double z_ = 0.0) : x(x_), y(y_), z(z_) {}
56+
57+
bool operator==(const XYZ& o)
58+
{
59+
return (x == o.x && y == o.y && z == o.z);
60+
}
61+
62+
bool operator!=(const XYZ& o)
63+
{
64+
return (x != o.x || y != o.y || z != o.z);
65+
}
66+
};
67+
68+
typedef std::vector<XYZ> Polygon3D;
69+
70+
template <class PolygonT>
71+
void _finalize_polygon(std::vector<PolygonT> &result, bool closed_only)
5072
{
5173
if (result.size() == 0) {
5274
return;
5375
}
5476

55-
Polygon &polygon = result.back();
77+
PolygonT &polygon = result.back();
5678

5779
/* Clean up the last polygon in the result. */
5880
if (polygon.size() == 0) {
@@ -511,10 +533,13 @@ bool path_in_path(PathIterator1 &a,
511533

512534
namespace clip_to_rect_filters
513535
{
514-
/* There are four different passes needed to create/remove
515-
vertices (one for each side of the rectangle). The differences
516-
between those passes are encapsulated in these functor classes.
536+
/* In 2D, there are four different passes needed to create/remove vertices (one for each
537+
* side of the rectangle). In 3d, there are six passes instead (one for each side of the
538+
* cube).
539+
*
540+
* The differences between those passes are encapsulated in these functor classes.
517541
*/
542+
template <class PointT>
518543
struct bisectx
519544
{
520545
double m_x;
@@ -523,41 +548,49 @@ struct bisectx
523548
{
524549
}
525550

526-
inline XY bisect(const XY s, const XY p) const
551+
inline PointT bisect(const PointT s, const PointT p) const
527552
{
528553
double dx = p.x - s.x;
529554
double dy = p.y - s.y;
530-
return {
555+
auto result = PointT{
531556
m_x,
532557
s.y + dy * ((m_x - s.x) / dx),
533558
};
559+
if constexpr (std::is_same_v<PointT, XYZ>) {
560+
double dz = p.z - s.z;
561+
result.z = s.z + dz * ((m_x - s.x) / dx);
562+
}
563+
return result;
534564
}
535565
};
536566

537-
struct xlt : public bisectx
567+
template <class PointT>
568+
struct xlt : public bisectx<PointT>
538569
{
539-
xlt(double x) : bisectx(x)
570+
xlt(double x) : bisectx<PointT>(x)
540571
{
541572
}
542573

543-
inline bool is_inside(const XY point) const
574+
inline bool is_inside(const PointT point) const
544575
{
545-
return point.x <= m_x;
576+
return point.x <= this->m_x;
546577
}
547578
};
548579

549-
struct xgt : public bisectx
580+
template <class PointT>
581+
struct xgt : public bisectx<PointT>
550582
{
551-
xgt(double x) : bisectx(x)
583+
xgt(double x) : bisectx<PointT>(x)
552584
{
553585
}
554586

555-
inline bool is_inside(const XY point) const
587+
inline bool is_inside(const PointT point) const
556588
{
557-
return point.x >= m_x;
589+
return point.x >= this->m_x;
558590
}
559591
};
560592

593+
template <class PointT>
561594
struct bisecty
562595
{
563596
double m_y;
@@ -566,44 +599,90 @@ struct bisecty
566599
{
567600
}
568601

569-
inline XY bisect(const XY s, const XY p) const
602+
inline PointT bisect(const PointT s, const PointT p) const
570603
{
571604
double dx = p.x - s.x;
572605
double dy = p.y - s.y;
573-
return {
606+
auto result = PointT{
574607
s.x + dx * ((m_y - s.y) / dy),
575608
m_y,
576609
};
610+
if constexpr (std::is_same_v<PointT, XYZ>) {
611+
double dz = p.z - s.z;
612+
result.z = s.z + dz * ((m_y - s.y) / dy);
613+
}
614+
return result;
577615
}
578616
};
579617

580-
struct ylt : public bisecty
618+
template <class PointT>
619+
struct ylt : public bisecty<PointT>
581620
{
582-
ylt(double y) : bisecty(y)
621+
ylt(double y) : bisecty<PointT>(y)
583622
{
584623
}
585624

586-
inline bool is_inside(const XY point) const
625+
inline bool is_inside(const PointT point) const
587626
{
588-
return point.y <= m_y;
627+
return point.y <= this->m_y;
589628
}
590629
};
591630

592-
struct ygt : public bisecty
631+
template <class PointT>
632+
struct ygt : public bisecty<PointT>
593633
{
594-
ygt(double y) : bisecty(y)
634+
ygt(double y) : bisecty<PointT>(y)
595635
{
596636
}
597637

598-
inline bool is_inside(const XY point) const
638+
inline bool is_inside(const PointT point) const
599639
{
600-
return point.y >= m_y;
640+
return point.y >= this->m_y;
641+
}
642+
};
643+
644+
struct bisectz
645+
{
646+
double m_z;
647+
648+
bisectz(double z) : m_z(z) {}
649+
650+
inline XYZ bisect(const XYZ s, const XYZ p) const
651+
{
652+
double dx = p.x - s.x;
653+
double dy = p.y - s.y;
654+
double dz = p.z - s.z;
655+
return {
656+
s.x + dx * ((m_z - s.z) / dz),
657+
s.y + dy * ((m_z - s.z) / dz),
658+
m_z,
659+
};
660+
}
661+
};
662+
663+
struct zlt : public bisectz
664+
{
665+
zlt(double z) : bisectz(z) {}
666+
667+
inline bool is_inside(const XYZ point) const
668+
{
669+
return point.z <= m_z;
670+
}
671+
};
672+
673+
struct zgt : public bisectz
674+
{
675+
zgt(double z) : bisectz(z) {}
676+
677+
inline bool is_inside(const XYZ point) const
678+
{
679+
return point.z >= m_z;
601680
}
602681
};
603682
}
604683

605-
template <class Filter>
606-
inline void clip_to_rect_one_step(const Polygon &polygon, Polygon &result, const Filter &filter)
684+
template <class PolygonT, class Filter>
685+
inline void clip_to_rect_one_step(const PolygonT &polygon, PolygonT &result, const Filter &filter)
607686
{
608687
bool sinside, pinside;
609688
result.clear();
@@ -672,10 +751,10 @@ clip_path_to_rect(PathIterator &path, agg::rect_d &rect, bool inside)
672751

673752
// The result of each step is fed into the next (note the
674753
// swapping of polygon1 and polygon2 at each step).
675-
clip_to_rect_one_step(polygon1, polygon2, clip_to_rect_filters::xlt(xmax));
676-
clip_to_rect_one_step(polygon2, polygon1, clip_to_rect_filters::xgt(xmin));
677-
clip_to_rect_one_step(polygon1, polygon2, clip_to_rect_filters::ylt(ymax));
678-
clip_to_rect_one_step(polygon2, polygon1, clip_to_rect_filters::ygt(ymin));
754+
clip_to_rect_one_step(polygon1, polygon2, clip_to_rect_filters::xlt<XY>(xmax));
755+
clip_to_rect_one_step(polygon2, polygon1, clip_to_rect_filters::xgt<XY>(xmin));
756+
clip_to_rect_one_step(polygon1, polygon2, clip_to_rect_filters::ylt<XY>(ymax));
757+
clip_to_rect_one_step(polygon2, polygon1, clip_to_rect_filters::ygt<XY>(ymin));
679758

680759
// Empty polygons aren't very useful, so skip them
681760
if (polygon1.size()) {
@@ -689,6 +768,67 @@ clip_path_to_rect(PathIterator &path, agg::rect_d &rect, bool inside)
689768
return results;
690769
}
691770

771+
inline std::vector<Polygon3D>
772+
clip_paths_to_box(py::array_t<double> paths,
773+
std::array<std::pair<double, double>, 3> &box,
774+
bool inside)
775+
{
776+
auto xmin = std::get<0>(box).first, xmax = std::get<0>(box).second;
777+
auto ymin = std::get<1>(box).first, ymax = std::get<1>(box).second;
778+
auto zmin = std::get<2>(box).first, zmax = std::get<2>(box).second;
779+
780+
if (xmin > xmax) {
781+
std::swap(xmin, xmax);
782+
}
783+
if (ymin > ymax) {
784+
std::swap(ymin, ymax);
785+
}
786+
if (zmin > zmax) {
787+
std::swap(zmin, zmax);
788+
}
789+
790+
if (!inside) {
791+
std::swap(xmin, xmax);
792+
std::swap(ymin, ymax);
793+
std::swap(zmin, zmax);
794+
}
795+
796+
Polygon3D polygon1, polygon2;
797+
std::vector<Polygon3D> results;
798+
799+
auto paths_iter = paths.unchecked<3>();
800+
for (auto i = 0; i < paths.shape(0); i++) {
801+
// Grab the next subpath and store it in polygon1
802+
polygon1.clear();
803+
for (auto j = 0; j < paths.shape(1); j++) {
804+
polygon1.emplace_back(
805+
paths_iter(i, j, 0),
806+
paths_iter(i, j, 1),
807+
paths_iter(i, j, 2)
808+
);
809+
}
810+
811+
// The result of each step is fed into the next (note the
812+
// swapping of polygon1 and polygon2 at each step).
813+
clip_to_rect_one_step(polygon1, polygon2, clip_to_rect_filters::xlt<XYZ>(xmax));
814+
clip_to_rect_one_step(polygon2, polygon1, clip_to_rect_filters::xgt<XYZ>(xmin));
815+
clip_to_rect_one_step(polygon1, polygon2, clip_to_rect_filters::ylt<XYZ>(ymax));
816+
clip_to_rect_one_step(polygon2, polygon1, clip_to_rect_filters::ygt<XYZ>(ymin));
817+
clip_to_rect_one_step(polygon1, polygon2, clip_to_rect_filters::zlt(zmax));
818+
clip_to_rect_one_step(polygon2, polygon1, clip_to_rect_filters::zgt(zmin));
819+
820+
// Empty polygons aren't very useful, so skip them
821+
if (polygon1.size()) {
822+
_finalize_polygon(results, true);
823+
results.push_back(polygon1);
824+
}
825+
}
826+
827+
_finalize_polygon(results, true);
828+
829+
return results;
830+
}
831+
692832
template <class VerticesArray, class ResultArray>
693833
void affine_transform_2d(VerticesArray &vertices, agg::trans_affine &trans, ResultArray &result)
694834
{

src/_path_wrapper.cpp

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
#include <pybind11/stl.h>
33

44
#include <array>
5-
#include <limits>
65
#include <optional>
76
#include <string>
7+
#include <type_traits>
88
#include <vector>
99

1010
#include "_path.h"
@@ -16,14 +16,21 @@
1616
namespace py = pybind11;
1717
using namespace pybind11::literals;
1818

19+
template <class T>
1920
py::list
20-
convert_polygon_vector(std::vector<Polygon> &polygons)
21+
convert_polygon_vector(std::vector<T> &polygons)
2122
{
23+
static_assert(std::is_same_v<Polygon, T> || std::is_same_v<Polygon3D, T>,
24+
"Vector must contain Polygon or Polygon3D");
25+
2226
auto result = py::list(polygons.size());
2327

2428
for (size_t i = 0; i < polygons.size(); ++i) {
2529
const auto& poly = polygons[i];
26-
py::ssize_t dims[] = { static_cast<py::ssize_t>(poly.size()), 2 };
30+
py::ssize_t dims[] = {
31+
static_cast<py::ssize_t>(poly.size()),
32+
sizeof(typename T::value_type) / sizeof(double)
33+
};
2734
result[i] = py::array(dims, reinterpret_cast<const double *>(poly.data()));
2835
}
2936

@@ -114,6 +121,16 @@ Py_clip_path_to_rect(mpl::PathIterator path, agg::rect_d rect, bool inside)
114121
return convert_polygon_vector(result);
115122
}
116123

124+
static py::list
125+
Py_clip_paths_to_box(py::array_t<double, 3> paths,
126+
std::array<std::pair<double, double>, 3> box,
127+
bool inside)
128+
{
129+
auto result = clip_paths_to_box(paths, box, inside);
130+
131+
return convert_polygon_vector(result);
132+
}
133+
117134
static py::object
118135
Py_affine_transform(py::array_t<double, py::array::c_style | py::array::forcecast> vertices_arr,
119136
agg::trans_affine trans)
@@ -319,6 +336,8 @@ PYBIND11_MODULE(_path, m, py::mod_gil_not_used())
319336
"path_a"_a, "trans_a"_a, "path_b"_a, "trans_b"_a);
320337
m.def("clip_path_to_rect", &Py_clip_path_to_rect,
321338
"path"_a, "rect"_a, "inside"_a);
339+
m.def("clip_paths_to_box", &Py_clip_paths_to_box,
340+
"path"_a, "box"_a, "inside"_a);
322341
m.def("affine_transform", &Py_affine_transform,
323342
"points"_a, "trans"_a);
324343
m.def("count_bboxes_overlapping_bbox", &Py_count_bboxes_overlapping_bbox,

0 commit comments

Comments
 (0)