Skip to content

Commit 883ef85

Browse files
committed
Expose 2D polygon boolean operations in Geometry singleton
Clipper 6.4.2 is used internally to perform polypaths clipping, as well as inflating/deflating polypaths. The following methods were added: ``` Geometry.merge_polygons_2d(poly_a, poly_b) # union Geometry.clip_polygons_2d(poly_a, poly_b) # difference Geometry.intersect_polygons_2d(poly_a, poly_b) # intersection Geometry.exclude_polygons_2d(poly_a, poly_b) # xor Geometry.clip_polyline_with_polygon_2d(poly_a, poly_b) Geometry.intersect_polyline_with_polygon_2d(poly_a, poly_b) Geometry.offset_polygon_2d(polygon, delta) # inflate/deflate Geometry.offset_polyline_2d(polyline, delta) # returns polygons // This one helps to implement CSG-like behaviour: Geometry.transform_points_2d(points, transform) ``` All the methods return an array of polygons/polylines. The resulting polygons could possibly be holes which could be checked with `Geometry.is_polygon_clockwise()` which was exposed to scripting as well.
1 parent fa5cc1d commit 883ef85

File tree

5 files changed

+516
-1
lines changed

5 files changed

+516
-1
lines changed

core/bind/core_bind.cpp

+135
Original file line numberDiff line numberDiff line change
@@ -1491,6 +1491,11 @@ PoolVector<Vector3> _Geometry::segment_intersects_convex(const Vector3 &p_from,
14911491
return r;
14921492
}
14931493

1494+
bool _Geometry::is_polygon_clockwise(const Vector<Vector2> &p_polygon) {
1495+
1496+
return Geometry::is_polygon_clockwise(p_polygon);
1497+
}
1498+
14941499
Vector<int> _Geometry::triangulate_polygon(const Vector<Vector2> &p_polygon) {
14951500

14961501
return Geometry::triangulate_polygon(p_polygon);
@@ -1506,6 +1511,107 @@ Vector<Vector3> _Geometry::clip_polygon(const Vector<Vector3> &p_points, const P
15061511
return Geometry::clip_polygon(p_points, p_plane);
15071512
}
15081513

1514+
Array _Geometry::merge_polygons_2d(const Vector<Vector2> &p_polygon_a, const Vector<Vector2> &p_polygon_b) {
1515+
1516+
Vector<Vector<Point2> > polys = Geometry::merge_polygons_2d(p_polygon_a, p_polygon_b);
1517+
1518+
Array ret;
1519+
1520+
for (int i = 0; i < polys.size(); ++i) {
1521+
ret.push_back(polys[i]);
1522+
}
1523+
return ret;
1524+
}
1525+
1526+
Array _Geometry::clip_polygons_2d(const Vector<Vector2> &p_polygon_a, const Vector<Vector2> &p_polygon_b) {
1527+
1528+
Vector<Vector<Point2> > polys = Geometry::clip_polygons_2d(p_polygon_a, p_polygon_b);
1529+
1530+
Array ret;
1531+
1532+
for (int i = 0; i < polys.size(); ++i) {
1533+
ret.push_back(polys[i]);
1534+
}
1535+
return ret;
1536+
}
1537+
1538+
Array _Geometry::intersect_polygons_2d(const Vector<Vector2> &p_polygon_a, const Vector<Vector2> &p_polygon_b) {
1539+
1540+
Vector<Vector<Point2> > polys = Geometry::intersect_polygons_2d(p_polygon_a, p_polygon_b);
1541+
1542+
Array ret;
1543+
1544+
for (int i = 0; i < polys.size(); ++i) {
1545+
ret.push_back(polys[i]);
1546+
}
1547+
return ret;
1548+
}
1549+
1550+
Array _Geometry::exclude_polygons_2d(const Vector<Vector2> &p_polygon_a, const Vector<Vector2> &p_polygon_b) {
1551+
1552+
Vector<Vector<Point2> > polys = Geometry::exclude_polygons_2d(p_polygon_a, p_polygon_b);
1553+
1554+
Array ret;
1555+
1556+
for (int i = 0; i < polys.size(); ++i) {
1557+
ret.push_back(polys[i]);
1558+
}
1559+
return ret;
1560+
}
1561+
1562+
Array _Geometry::clip_polyline_with_polygon_2d(const Vector<Vector2> &p_polyline, const Vector<Vector2> &p_polygon) {
1563+
1564+
Vector<Vector<Point2> > polys = Geometry::clip_polyline_with_polygon_2d(p_polyline, p_polygon);
1565+
1566+
Array ret;
1567+
1568+
for (int i = 0; i < polys.size(); ++i) {
1569+
ret.push_back(polys[i]);
1570+
}
1571+
return ret;
1572+
}
1573+
1574+
Array _Geometry::intersect_polyline_with_polygon_2d(const Vector<Vector2> &p_polyline, const Vector<Vector2> &p_polygon) {
1575+
1576+
Vector<Vector<Point2> > polys = Geometry::intersect_polyline_with_polygon_2d(p_polyline, p_polygon);
1577+
1578+
Array ret;
1579+
1580+
for (int i = 0; i < polys.size(); ++i) {
1581+
ret.push_back(polys[i]);
1582+
}
1583+
return ret;
1584+
}
1585+
1586+
Array _Geometry::offset_polygon_2d(const Vector<Vector2> &p_polygon, real_t p_delta, PolyJoinType p_join_type) {
1587+
1588+
Vector<Vector<Point2> > polys = Geometry::offset_polygon_2d(p_polygon, p_delta, Geometry::PolyJoinType(p_join_type));
1589+
1590+
Array ret;
1591+
1592+
for (int i = 0; i < polys.size(); ++i) {
1593+
ret.push_back(polys[i]);
1594+
}
1595+
return ret;
1596+
}
1597+
1598+
Array _Geometry::offset_polyline_2d(const Vector<Vector2> &p_polygon, real_t p_delta, PolyJoinType p_join_type, PolyEndType p_end_type) {
1599+
1600+
Vector<Vector<Point2> > polys = Geometry::offset_polyline_2d(p_polygon, p_delta, Geometry::PolyJoinType(p_join_type), Geometry::PolyEndType(p_end_type));
1601+
1602+
Array ret;
1603+
1604+
for (int i = 0; i < polys.size(); ++i) {
1605+
ret.push_back(polys[i]);
1606+
}
1607+
return ret;
1608+
}
1609+
1610+
Vector<Point2> _Geometry::transform_points_2d(const Vector<Point2> &p_points, const Transform2D &p_mat) {
1611+
1612+
return Geometry::transform_points_2d(p_points, p_mat);
1613+
}
1614+
15091615
Dictionary _Geometry::make_atlas(const Vector<Size2> &p_rects) {
15101616

15111617
Dictionary ret;
@@ -1566,11 +1672,40 @@ void _Geometry::_bind_methods() {
15661672
ClassDB::bind_method(D_METHOD("segment_intersects_convex", "from", "to", "planes"), &_Geometry::segment_intersects_convex);
15671673
ClassDB::bind_method(D_METHOD("point_is_inside_triangle", "point", "a", "b", "c"), &_Geometry::point_is_inside_triangle);
15681674

1675+
ClassDB::bind_method(D_METHOD("is_polygon_clockwise", "polygon"), &_Geometry::is_polygon_clockwise);
15691676
ClassDB::bind_method(D_METHOD("triangulate_polygon", "polygon"), &_Geometry::triangulate_polygon);
15701677
ClassDB::bind_method(D_METHOD("convex_hull_2d", "points"), &_Geometry::convex_hull_2d);
15711678
ClassDB::bind_method(D_METHOD("clip_polygon", "points", "plane"), &_Geometry::clip_polygon);
15721679

1680+
ClassDB::bind_method(D_METHOD("merge_polygons_2d", "polygon_a", "polygon_b"), &_Geometry::merge_polygons_2d);
1681+
ClassDB::bind_method(D_METHOD("clip_polygons_2d", "polygon_a", "polygon_b"), &_Geometry::clip_polygons_2d);
1682+
ClassDB::bind_method(D_METHOD("intersect_polygons_2d", "polygon_a", "polygon_b"), &_Geometry::intersect_polygons_2d);
1683+
ClassDB::bind_method(D_METHOD("exclude_polygons_2d", "polygon_a", "polygon_b"), &_Geometry::exclude_polygons_2d);
1684+
1685+
ClassDB::bind_method(D_METHOD("clip_polyline_with_polygon_2d", "polyline", "polygon"), &_Geometry::clip_polyline_with_polygon_2d);
1686+
ClassDB::bind_method(D_METHOD("intersect_polyline_with_polygon_2d", "polyline", "polygon"), &_Geometry::intersect_polyline_with_polygon_2d);
1687+
1688+
ClassDB::bind_method(D_METHOD("offset_polygon_2d", "polygon", "delta", "join_type"), &_Geometry::offset_polygon_2d, DEFVAL(JOIN_SQUARE));
1689+
ClassDB::bind_method(D_METHOD("offset_polyline_2d", "polyline", "delta", "join_type", "end_type"), &_Geometry::offset_polyline_2d, DEFVAL(JOIN_SQUARE), DEFVAL(END_SQUARE));
1690+
1691+
ClassDB::bind_method(D_METHOD("transform_points_2d", "points", "transform"), &_Geometry::transform_points_2d);
1692+
15731693
ClassDB::bind_method(D_METHOD("make_atlas", "sizes"), &_Geometry::make_atlas);
1694+
1695+
BIND_ENUM_CONSTANT(OPERATION_UNION);
1696+
BIND_ENUM_CONSTANT(OPERATION_DIFFERENCE);
1697+
BIND_ENUM_CONSTANT(OPERATION_INTERSECTION);
1698+
BIND_ENUM_CONSTANT(OPERATION_XOR);
1699+
1700+
BIND_ENUM_CONSTANT(JOIN_SQUARE);
1701+
BIND_ENUM_CONSTANT(JOIN_ROUND);
1702+
BIND_ENUM_CONSTANT(JOIN_MITER);
1703+
1704+
BIND_ENUM_CONSTANT(END_POLYGON);
1705+
BIND_ENUM_CONSTANT(END_JOINED);
1706+
BIND_ENUM_CONSTANT(END_BUTT);
1707+
BIND_ENUM_CONSTANT(END_SQUARE);
1708+
BIND_ENUM_CONSTANT(END_ROUND);
15741709
}
15751710

15761711
_Geometry::_Geometry() {

core/bind/core_bind.h

+39
Original file line numberDiff line numberDiff line change
@@ -402,15 +402,54 @@ class _Geometry : public Object {
402402
real_t segment_intersects_circle(const Vector2 &p_from, const Vector2 &p_to, const Vector2 &p_circle_pos, real_t p_circle_radius);
403403
int get_uv84_normal_bit(const Vector3 &p_vector);
404404

405+
bool is_polygon_clockwise(const Vector<Vector2> &p_polygon);
405406
Vector<int> triangulate_polygon(const Vector<Vector2> &p_polygon);
406407
Vector<Point2> convex_hull_2d(const Vector<Point2> &p_points);
407408
Vector<Vector3> clip_polygon(const Vector<Vector3> &p_points, const Plane &p_plane);
408409

410+
enum PolyBooleanOperation {
411+
OPERATION_UNION,
412+
OPERATION_DIFFERENCE,
413+
OPERATION_INTERSECTION,
414+
OPERATION_XOR
415+
};
416+
// 2D polygon boolean operations
417+
Array merge_polygons_2d(const Vector<Vector2> &p_polygon_a, const Vector<Vector2> &p_polygon_b); // union (add)
418+
Array clip_polygons_2d(const Vector<Vector2> &p_polygon_a, const Vector<Vector2> &p_polygon_b); // difference (subtract)
419+
Array intersect_polygons_2d(const Vector<Vector2> &p_polygon_a, const Vector<Vector2> &p_polygon_b); // common area (multiply)
420+
Array exclude_polygons_2d(const Vector<Vector2> &p_polygon_a, const Vector<Vector2> &p_polygon_b); // all but common area (xor)
421+
422+
// 2D polyline vs polygon operations
423+
Array clip_polyline_with_polygon_2d(const Vector<Vector2> &p_polyline, const Vector<Vector2> &p_polygon); // cut
424+
Array intersect_polyline_with_polygon_2d(const Vector<Vector2> &p_polyline, const Vector<Vector2> &p_polygon); // chop
425+
426+
// 2D offset polygons/polylines
427+
enum PolyJoinType {
428+
JOIN_SQUARE,
429+
JOIN_ROUND,
430+
JOIN_MITER
431+
};
432+
enum PolyEndType {
433+
END_POLYGON,
434+
END_JOINED,
435+
END_BUTT,
436+
END_SQUARE,
437+
END_ROUND
438+
};
439+
Array offset_polygon_2d(const Vector<Vector2> &p_polygon, real_t p_delta, PolyJoinType p_join_type = JOIN_SQUARE);
440+
Array offset_polyline_2d(const Vector<Vector2> &p_polygon, real_t p_delta, PolyJoinType p_join_type = JOIN_SQUARE, PolyEndType p_end_type = END_SQUARE);
441+
442+
Vector<Point2> transform_points_2d(const Vector<Point2> &p_points, const Transform2D &p_mat);
443+
409444
Dictionary make_atlas(const Vector<Size2> &p_rects);
410445

411446
_Geometry();
412447
};
413448

449+
VARIANT_ENUM_CAST(_Geometry::PolyBooleanOperation);
450+
VARIANT_ENUM_CAST(_Geometry::PolyJoinType);
451+
VARIANT_ENUM_CAST(_Geometry::PolyEndType);
452+
414453
class _File : public Reference {
415454

416455
GDCLASS(_File, Reference);

core/math/geometry.cpp

+106
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,11 @@
3131
#include "geometry.h"
3232

3333
#include "core/print_string.h"
34+
#include "thirdparty/misc/clipper.hpp"
3435
#include "thirdparty/misc/triangulator.h"
3536

37+
#define SCALE_FACTOR 100000.0 // based on CMP_EPSILON
38+
3639
/* this implementation is very inefficient, commenting unless bugs happen. See the other one.
3740
bool Geometry::is_point_in_polygon(const Vector2 &p_point, const Vector<Vector2> &p_polygon) {
3841
@@ -1134,3 +1137,106 @@ void Geometry::make_atlas(const Vector<Size2i> &p_rects, Vector<Point2i> &r_resu
11341137

11351138
r_size = Size2(results[best].max_w, results[best].max_h);
11361139
}
1140+
1141+
Vector<Vector<Point2> > Geometry::_polypaths_do_operation(PolyBooleanOperation p_op, const Vector<Point2> &p_polypath_a, const Vector<Point2> &p_polypath_b, bool is_a_open) {
1142+
1143+
using namespace ClipperLib;
1144+
1145+
ClipType op = ctUnion;
1146+
1147+
switch (p_op) {
1148+
case OPERATION_UNION: op = ctUnion; break;
1149+
case OPERATION_DIFFERENCE: op = ctDifference; break;
1150+
case OPERATION_INTERSECTION: op = ctIntersection; break;
1151+
case OPERATION_XOR: op = ctXor; break;
1152+
}
1153+
Path path_a, path_b;
1154+
1155+
// Need to scale points (Clipper's requirement for robust computation)
1156+
for (int i = 0; i != p_polypath_a.size(); ++i) {
1157+
path_a << IntPoint(p_polypath_a[i].x * SCALE_FACTOR, p_polypath_a[i].y * SCALE_FACTOR);
1158+
}
1159+
for (int i = 0; i != p_polypath_b.size(); ++i) {
1160+
path_b << IntPoint(p_polypath_b[i].x * SCALE_FACTOR, p_polypath_b[i].y * SCALE_FACTOR);
1161+
}
1162+
Clipper clp;
1163+
clp.AddPath(path_a, ptSubject, !is_a_open); // forward compatible with Clipper 10.0.0
1164+
clp.AddPath(path_b, ptClip, true); // polylines cannot be set as clip
1165+
1166+
Paths paths;
1167+
1168+
if (is_a_open) {
1169+
PolyTree tree; // needed to populate polylines
1170+
clp.Execute(op, tree);
1171+
OpenPathsFromPolyTree(tree, paths);
1172+
} else {
1173+
clp.Execute(op, paths); // works on closed polygons only
1174+
}
1175+
// Have to scale points down now
1176+
Vector<Vector<Point2> > polypaths;
1177+
1178+
for (Paths::size_type i = 0; i < paths.size(); ++i) {
1179+
Vector<Vector2> polypath;
1180+
1181+
const Path &scaled_path = paths[i];
1182+
1183+
for (Paths::size_type j = 0; j < scaled_path.size(); ++j) {
1184+
polypath.push_back(Point2(
1185+
static_cast<real_t>(scaled_path[j].X) / SCALE_FACTOR,
1186+
static_cast<real_t>(scaled_path[j].Y) / SCALE_FACTOR));
1187+
}
1188+
polypaths.push_back(polypath);
1189+
}
1190+
return polypaths;
1191+
}
1192+
1193+
Vector<Vector<Point2> > Geometry::_polypath_offset(const Vector<Point2> &p_polypath, real_t p_delta, PolyJoinType p_join_type, PolyEndType p_end_type) {
1194+
1195+
using namespace ClipperLib;
1196+
1197+
JoinType jt = jtSquare;
1198+
1199+
switch (p_join_type) {
1200+
case JOIN_SQUARE: jt = jtSquare; break;
1201+
case JOIN_ROUND: jt = jtRound; break;
1202+
case JOIN_MITER: jt = jtMiter; break;
1203+
}
1204+
1205+
EndType et = etClosedPolygon;
1206+
1207+
switch (p_end_type) {
1208+
case END_POLYGON: et = etClosedPolygon; break;
1209+
case END_JOINED: et = etClosedLine; break;
1210+
case END_BUTT: et = etOpenButt; break;
1211+
case END_SQUARE: et = etOpenSquare; break;
1212+
case END_ROUND: et = etOpenRound; break;
1213+
}
1214+
ClipperOffset co;
1215+
Path path;
1216+
1217+
// Need to scale points (Clipper's requirement for robust computation)
1218+
for (int i = 0; i != p_polypath.size(); ++i) {
1219+
path << IntPoint(p_polypath[i].x * SCALE_FACTOR, p_polypath[i].y * SCALE_FACTOR);
1220+
}
1221+
co.AddPath(path, jt, et);
1222+
1223+
Paths paths;
1224+
co.Execute(paths, p_delta * SCALE_FACTOR); // inflate/deflate
1225+
1226+
// Have to scale points down now
1227+
Vector<Vector<Point2> > polypaths;
1228+
1229+
for (Paths::size_type i = 0; i < paths.size(); ++i) {
1230+
Vector<Vector2> polypath;
1231+
1232+
const Path &scaled_path = paths[i];
1233+
1234+
for (Paths::size_type j = 0; j < scaled_path.size(); ++j) {
1235+
polypath.push_back(Point2(
1236+
static_cast<real_t>(scaled_path[j].X) / SCALE_FACTOR,
1237+
static_cast<real_t>(scaled_path[j].Y) / SCALE_FACTOR));
1238+
}
1239+
polypaths.push_back(polypath);
1240+
}
1241+
return polypaths;
1242+
}

0 commit comments

Comments
 (0)