Skip to content

Commit

Permalink
Merge pull request #90153 from rburing/clipper2_electric_boogaloo
Browse files Browse the repository at this point in the history
Replace Clipper1 library by Clipper2 library
  • Loading branch information
akien-mga committed Apr 22, 2024
2 parents 1a6e4ce + 8a28f81 commit fb3c3ac
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 5,330 deletions.
6 changes: 0 additions & 6 deletions COPYRIGHT.txt
Original file line number Diff line number Diff line change
Expand Up @@ -360,12 +360,6 @@ Copyright: 1998-2010, Gilles Vollant
2009-2010, Mathias Svensson
License: Zlib

Files: ./thirdparty/misc/clipper.cpp
./thirdparty/misc/clipper.hpp
Comment: Clipper
Copyright: 2010-2017, Angus Johnson
License: BSL-1.0

Files: ./thirdparty/misc/cubemap_coeffs.h
Comment: Fast Filtering of Reflection Probes
Copyright: 2016, Activision Publishing, Inc.
Expand Down
1 change: 0 additions & 1 deletion core/SCsub
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ thirdparty_misc_sources = [
# C++ sources
"pcg.cpp",
"polypartition.cpp",
"clipper.cpp",
"smolv.cpp",
]
thirdparty_misc_sources = [thirdparty_misc_dir + file for file in thirdparty_misc_sources]
Expand Down
111 changes: 53 additions & 58 deletions core/math/geometry_2d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@

#include "geometry_2d.h"

#include "thirdparty/misc/clipper.hpp"
#include "thirdparty/clipper2/include/clipper2/clipper.h"
#include "thirdparty/misc/polypartition.h"
#define STB_RECT_PACK_IMPLEMENTATION
#include "thirdparty/misc/stb_rect_pack.h"

#define SCALE_FACTOR 100000.0 // Based on CMP_EPSILON.
#define PRECISION 5 // Based on CMP_EPSILON.

Vector<Vector<Vector2>> Geometry2D::decompose_polygon_in_convex(const Vector<Point2> &polygon) {
Vector<Vector<Vector2>> decomp;
Expand Down Expand Up @@ -196,126 +196,121 @@ void Geometry2D::make_atlas(const Vector<Size2i> &p_rects, Vector<Point2i> &r_re
}

Vector<Vector<Point2>> Geometry2D::_polypaths_do_operation(PolyBooleanOperation p_op, const Vector<Point2> &p_polypath_a, const Vector<Point2> &p_polypath_b, bool is_a_open) {
using namespace ClipperLib;
using namespace Clipper2Lib;

ClipType op = ctUnion;
ClipType op = ClipType::Union;

switch (p_op) {
case OPERATION_UNION:
op = ctUnion;
op = ClipType::Union;
break;
case OPERATION_DIFFERENCE:
op = ctDifference;
op = ClipType::Difference;
break;
case OPERATION_INTERSECTION:
op = ctIntersection;
op = ClipType::Intersection;
break;
case OPERATION_XOR:
op = ctXor;
op = ClipType::Xor;
break;
}
Path path_a, path_b;

// Need to scale points (Clipper's requirement for robust computation).
PathD path_a(p_polypath_a.size());
for (int i = 0; i != p_polypath_a.size(); ++i) {
path_a << IntPoint(p_polypath_a[i].x * (real_t)SCALE_FACTOR, p_polypath_a[i].y * (real_t)SCALE_FACTOR);
path_a[i] = PointD(p_polypath_a[i].x, p_polypath_a[i].y);
}
PathD path_b(p_polypath_b.size());
for (int i = 0; i != p_polypath_b.size(); ++i) {
path_b << IntPoint(p_polypath_b[i].x * (real_t)SCALE_FACTOR, p_polypath_b[i].y * (real_t)SCALE_FACTOR);
path_b[i] = PointD(p_polypath_b[i].x, p_polypath_b[i].y);
}
Clipper clp;
clp.AddPath(path_a, ptSubject, !is_a_open); // Forward compatible with Clipper 10.0.0.
clp.AddPath(path_b, ptClip, true); // Polylines cannot be set as clip.

Paths paths;
ClipperD clp(PRECISION); // Scale points up internally to attain the desired precision.
clp.PreserveCollinear(false); // Remove redundant vertices.
if (is_a_open) {
clp.AddOpenSubject({ path_a });
} else {
clp.AddSubject({ path_a });
}
clp.AddClip({ path_b });

PathsD paths;

if (is_a_open) {
PolyTree tree; // Needed to populate polylines.
clp.Execute(op, tree);
OpenPathsFromPolyTree(tree, paths);
PolyTreeD tree; // Needed to populate polylines.
clp.Execute(op, FillRule::EvenOdd, tree, paths);
} else {
clp.Execute(op, paths); // Works on closed polygons only.
clp.Execute(op, FillRule::EvenOdd, paths); // Works on closed polygons only.
}
// Have to scale points down now.

Vector<Vector<Point2>> polypaths;
for (PathsD::size_type i = 0; i < paths.size(); ++i) {
const PathD &path = paths[i];

for (Paths::size_type i = 0; i < paths.size(); ++i) {
Vector<Vector2> polypath;

const Path &scaled_path = paths[i];

for (Paths::size_type j = 0; j < scaled_path.size(); ++j) {
polypath.push_back(Point2(
static_cast<real_t>(scaled_path[j].X) / (real_t)SCALE_FACTOR,
static_cast<real_t>(scaled_path[j].Y) / (real_t)SCALE_FACTOR));
for (PathsD::size_type j = 0; j < path.size(); ++j) {
polypath.push_back(Point2(static_cast<real_t>(path[j].x), static_cast<real_t>(path[j].y)));
}
polypaths.push_back(polypath);
}
return polypaths;
}

Vector<Vector<Point2>> Geometry2D::_polypath_offset(const Vector<Point2> &p_polypath, real_t p_delta, PolyJoinType p_join_type, PolyEndType p_end_type) {
using namespace ClipperLib;
using namespace Clipper2Lib;

JoinType jt = jtSquare;
JoinType jt = JoinType::Square;

switch (p_join_type) {
case JOIN_SQUARE:
jt = jtSquare;
jt = JoinType::Square;
break;
case JOIN_ROUND:
jt = jtRound;
jt = JoinType::Round;
break;
case JOIN_MITER:
jt = jtMiter;
jt = JoinType::Miter;
break;
}

EndType et = etClosedPolygon;
EndType et = EndType::Polygon;

switch (p_end_type) {
case END_POLYGON:
et = etClosedPolygon;
et = EndType::Polygon;
break;
case END_JOINED:
et = etClosedLine;
et = EndType::Joined;
break;
case END_BUTT:
et = etOpenButt;
et = EndType::Butt;
break;
case END_SQUARE:
et = etOpenSquare;
et = EndType::Square;
break;
case END_ROUND:
et = etOpenRound;
et = EndType::Round;
break;
}
ClipperOffset co(2.0, 0.25f * (real_t)SCALE_FACTOR); // Defaults from ClipperOffset.
Path path;

// Need to scale points (Clipper's requirement for robust computation).
PathD polypath(p_polypath.size());
for (int i = 0; i != p_polypath.size(); ++i) {
path << IntPoint(p_polypath[i].x * (real_t)SCALE_FACTOR, p_polypath[i].y * (real_t)SCALE_FACTOR);
polypath[i] = PointD(p_polypath[i].x, p_polypath[i].y);
}
co.AddPath(path, jt, et);

Paths paths;
co.Execute(paths, p_delta * (real_t)SCALE_FACTOR); // Inflate/deflate.
// Inflate/deflate.
PathsD paths = InflatePaths({ polypath }, p_delta, jt, et, 2.0, PRECISION, 0.0);
// Here the miter_limit = 2.0 and arc_tolerance = 0.0 are Clipper2 defaults,
// and the PRECISION is used to scale points up internally, to attain the desired precision.

// Have to scale points down now.
Vector<Vector<Point2>> polypaths;
for (PathsD::size_type i = 0; i < paths.size(); ++i) {
const PathD &path = paths[i];

for (Paths::size_type i = 0; i < paths.size(); ++i) {
Vector<Vector2> polypath;

const Path &scaled_path = paths[i];

for (Paths::size_type j = 0; j < scaled_path.size(); ++j) {
polypath.push_back(Point2(
static_cast<real_t>(scaled_path[j].X) / (real_t)SCALE_FACTOR,
static_cast<real_t>(scaled_path[j].Y) / (real_t)SCALE_FACTOR));
Vector<Vector2> polypath2;
for (PathsD::size_type j = 0; j < path.size(); ++j) {
polypath2.push_back(Point2(static_cast<real_t>(path[j].x), static_cast<real_t>(path[j].y)));
}
polypaths.push_back(polypath);
polypaths.push_back(polypath2);
}
return polypaths;
}
Expand Down
63 changes: 23 additions & 40 deletions editor/plugins/sprite_2d_editor_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@
#include "scene/gui/menu_button.h"
#include "scene/gui/panel.h"
#include "scene/gui/view_panner.h"
#include "thirdparty/misc/clipper.hpp"
#include "thirdparty/clipper2/include/clipper2/clipper.h"

#define PRECISION 1

void Sprite2DEditor::_node_removed(Node *p_node) {
if (p_node == node) {
Expand All @@ -59,58 +61,39 @@ void Sprite2DEditor::edit(Sprite2D *p_sprite) {
node = p_sprite;
}

#define PRECISION 10.0

Vector<Vector2> expand(const Vector<Vector2> &points, const Rect2i &rect, float epsilon = 2.0) {
int size = points.size();
ERR_FAIL_COND_V(size < 2, Vector<Vector2>());

ClipperLib::Path subj;
ClipperLib::PolyTree solution;
ClipperLib::PolyTree out;

Clipper2Lib::PathD subj(points.size());
for (int i = 0; i < points.size(); i++) {
subj << ClipperLib::IntPoint(points[i].x * PRECISION, points[i].y * PRECISION);
subj[i] = Clipper2Lib::PointD(points[i].x, points[i].y);
}
ClipperLib::ClipperOffset co;
co.AddPath(subj, ClipperLib::jtMiter, ClipperLib::etClosedPolygon);
co.Execute(solution, epsilon * PRECISION);

ClipperLib::PolyNode *p = solution.GetFirst();
Clipper2Lib::PathsD solution = Clipper2Lib::InflatePaths({ subj }, epsilon, Clipper2Lib::JoinType::Miter, Clipper2Lib::EndType::Polygon, 2.0, PRECISION, 0.0);
// Here the miter_limit = 2.0 and arc_tolerance = 0.0 are Clipper2 defaults,
// and PRECISION is used to scale points up internally, to attain the desired precision.

ERR_FAIL_NULL_V(p, points);
ERR_FAIL_COND_V(solution.size() == 0, points);

while (p->IsHole()) {
p = p->GetNext();
}
// Clamp into the specified rect.
Clipper2Lib::RectD clamp(rect.position.x,
rect.position.y,
rect.position.x + rect.size.width,
rect.position.y + rect.size.height);
Clipper2Lib::PathsD out = Clipper2Lib::RectClip(clamp, solution[0], PRECISION);
// Here PRECISION is used to scale points up internally, to attain the desired precision.

//turn the result into simply polygon (AKA, fix overlap)

//clamp into the specified rect
ClipperLib::Clipper cl;
cl.StrictlySimple(true);
cl.AddPath(p->Contour, ClipperLib::ptSubject, true);
//create the clipping rect
ClipperLib::Path clamp;
clamp.push_back(ClipperLib::IntPoint(0, 0));
clamp.push_back(ClipperLib::IntPoint(rect.size.width * PRECISION, 0));
clamp.push_back(ClipperLib::IntPoint(rect.size.width * PRECISION, rect.size.height * PRECISION));
clamp.push_back(ClipperLib::IntPoint(0, rect.size.height * PRECISION));
cl.AddPath(clamp, ClipperLib::ptClip, true);
cl.Execute(ClipperLib::ctIntersection, out);
ERR_FAIL_COND_V(out.size() == 0, points);

Vector<Vector2> outPoints;
ClipperLib::PolyNode *p2 = out.GetFirst();
ERR_FAIL_NULL_V(p2, points);
const Clipper2Lib::PathD &p2 = out[0];

while (p2->IsHole()) {
p2 = p2->GetNext();
}
Vector<Vector2> outPoints;

int lasti = p2->Contour.size() - 1;
Vector2 prev = Vector2(p2->Contour[lasti].X / PRECISION, p2->Contour[lasti].Y / PRECISION);
for (uint64_t i = 0; i < p2->Contour.size(); i++) {
Vector2 cur = Vector2(p2->Contour[i].X / PRECISION, p2->Contour[i].Y / PRECISION);
int lasti = p2.size() - 1;
Vector2 prev = Vector2(p2[lasti].x, p2[lasti].y);
for (uint64_t i = 0; i < p2.size(); i++) {
Vector2 cur = Vector2(p2[i].x, p2[i].y);
if (cur.distance_to(prev) > 0.5) {
outPoints.push_back(cur);
prev = cur;
Expand Down
8 changes: 4 additions & 4 deletions tests/core/math/test_geometry_2d.h
Original file line number Diff line number Diff line change
Expand Up @@ -711,12 +711,12 @@ TEST_CASE("[Geometry2D] Clip polyline with polygon") {
r = Geometry2D::clip_polyline_with_polygon(l, p);
REQUIRE_MESSAGE(r.size() == 2, "There should be 2 resulting clipped lines.");
REQUIRE_MESSAGE(r[0].size() == 3, "The resulting clipped line should have 3 vertices.");
CHECK(r[0][0].is_equal_approx(Vector2(160, 320)));
CHECK(r[0][0].is_equal_approx(Vector2(121.412682, 225.038757)));
CHECK(r[0][1].is_equal_approx(Vector2(122, 250)));
CHECK(r[0][2].is_equal_approx(Vector2(121.412682, 225.038757)));
CHECK(r[0][2].is_equal_approx(Vector2(160, 320)));
REQUIRE_MESSAGE(r[1].size() == 2, "The resulting clipped line should have 2 vertices.");
CHECK(r[1][0].is_equal_approx(Vector2(53.07737, 116.143021)));
CHECK(r[1][1].is_equal_approx(Vector2(55, 70)));
CHECK(r[1][0].is_equal_approx(Vector2(55, 70)));
CHECK(r[1][1].is_equal_approx(Vector2(53.07737, 116.143021)));
}
}

Expand Down
Loading

0 comments on commit fb3c3ac

Please sign in to comment.