Ported clipper2 usage to clipper1 in PandemoniumNavigationMeshGenerator. Also moved them to the Geometry singleton.

This commit is contained in:
Relintai 2023-06-07 09:20:36 +02:00
parent 91f365b183
commit 6eb37ff901
3 changed files with 373 additions and 127 deletions

View File

@ -1278,6 +1278,316 @@ Vector<Vector<Point2>> Geometry::_polypath_offset(const Vector<Point2> &p_polypa
return polypaths;
}
Vector<Vector<Point2>> Geometry::_polypaths_do_operations(PolyBooleanOperation p_op, const Vector<Vector<Point2>> &p_polypaths, const Vector<Point2> &p_polypath_clip, PolygonFillType fill_type, bool is_a_open) {
using namespace ClipperLib;
ClipType op = ctUnion;
switch (p_op) {
case OPERATION_UNION:
op = ctUnion;
break;
case OPERATION_DIFFERENCE:
op = ctDifference;
break;
case OPERATION_INTERSECTION:
op = ctIntersection;
break;
case OPERATION_XOR:
op = ctXor;
break;
}
Paths in_paths;
// Need to scale points (Clipper's requirement for robust computation).
for (int j = 0; j < p_polypaths.size(); ++j) {
const Vector<Point2> &polypath = p_polypaths[j];
Path path_a;
for (int i = 0; i != polypath.size(); ++i) {
path_a << IntPoint(polypath[i].x * (real_t)SCALE_FACTOR, polypath[i].y * (real_t)SCALE_FACTOR);
}
in_paths << path_a;
}
Path path_clip;
for (int i = 0; i != p_polypath_clip.size(); ++i) {
path_clip << IntPoint(p_polypath_clip[i].x * (real_t)SCALE_FACTOR, p_polypath_clip[i].y * (real_t)SCALE_FACTOR);
}
Clipper clp;
clp.AddPaths(in_paths, ptSubject, !is_a_open);
clp.AddPath(path_clip, ptClip, true); // Polylines cannot be set as clip.
Paths paths;
PolyFillType pft;
switch (fill_type) {
case POLYGON_FILL_TYPE_EVEN_ODD:
pft = pftEvenOdd;
break;
case POLYGON_FILL_TYPE_NON_ZERO:
pft = pftNonZero;
break;
case POLYGON_FILL_TYPE_POSITIVE:
pft = pftPositive;
break;
case POLYGON_FILL_TYPE_NEGATIVE:
pft = pftNegative;
break;
default:
pft = pftEvenOdd;
break;
}
if (is_a_open) {
PolyTree tree; // Needed to populate polylines.
clp.Execute(op, tree, pft);
OpenPathsFromPolyTree(tree, paths);
} else {
clp.Execute(op, paths, pft); // Works on closed polygons only.
}
// Have to scale points down now.
Vector<Vector<Point2>> polypaths;
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));
}
polypaths.push_back(polypath);
}
return polypaths;
}
Vector<Vector<Point2>> Geometry::_polypaths2_do_operations(PolyBooleanOperation p_op, const Vector<Vector<Point2>> &p_polypaths, const Vector<Vector<Point2>> &p_polypath_clip, PolygonFillType fill_type, bool is_a_open) {
using namespace ClipperLib;
ClipType op = ctUnion;
switch (p_op) {
case OPERATION_UNION:
op = ctUnion;
break;
case OPERATION_DIFFERENCE:
op = ctDifference;
break;
case OPERATION_INTERSECTION:
op = ctIntersection;
break;
case OPERATION_XOR:
op = ctXor;
break;
}
Paths in_paths;
// Need to scale points (Clipper's requirement for robust computation).
for (int j = 0; j < p_polypaths.size(); ++j) {
const Vector<Point2> &polypath = p_polypaths[j];
Path path_a;
for (int i = 0; i != polypath.size(); ++i) {
path_a << IntPoint(polypath[i].x * (real_t)SCALE_FACTOR, polypath[i].y * (real_t)SCALE_FACTOR);
}
in_paths << path_a;
}
Paths paths_clip;
for (int j = 0; j < p_polypath_clip.size(); ++j) {
const Vector<Point2> &polypath = p_polypath_clip[j];
Path path_clip;
for (int i = 0; i != polypath.size(); ++i) {
path_clip << IntPoint(polypath[i].x * (real_t)SCALE_FACTOR, polypath[i].y * (real_t)SCALE_FACTOR);
}
paths_clip << path_clip;
}
Clipper clp;
clp.AddPaths(in_paths, ptSubject, !is_a_open);
clp.AddPaths(paths_clip, ptClip, true); // Polylines cannot be set as clip.
Paths paths;
PolyFillType pft;
switch (fill_type) {
case POLYGON_FILL_TYPE_EVEN_ODD:
pft = pftEvenOdd;
break;
case POLYGON_FILL_TYPE_NON_ZERO:
pft = pftNonZero;
break;
case POLYGON_FILL_TYPE_POSITIVE:
pft = pftPositive;
break;
case POLYGON_FILL_TYPE_NEGATIVE:
pft = pftNegative;
break;
default:
pft = pftEvenOdd;
break;
}
if (is_a_open) {
PolyTree tree; // Needed to populate polylines.
clp.Execute(op, tree, pft);
OpenPathsFromPolyTree(tree, paths);
} else {
clp.Execute(op, paths, pft); // Works on closed polygons only.
}
// Have to scale points down now.
Vector<Vector<Point2>> polypaths;
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));
}
polypaths.push_back(polypath);
}
return polypaths;
}
static void _recursive_process_polytree_items(List<TriangulatorPoly> &p_tppl_in_polygon, const ClipperLib::PolyNode *p_polypath_item) {
using namespace ClipperLib;
Vector<Vector2> polygon_vertices;
for (uint32_t i = 0; i < p_polypath_item->Contour.size(); ++i) {
const IntPoint &polypath_point = p_polypath_item->Contour[i];
// Have to scale points down now.
polygon_vertices.push_back(Vector2(static_cast<real_t>(polypath_point.X / (real_t)SCALE_FACTOR), static_cast<real_t>(polypath_point.Y / (real_t)SCALE_FACTOR)));
}
TriangulatorPoly tp;
tp.Init(polygon_vertices.size());
for (int j = 0; j < polygon_vertices.size(); j++) {
tp[j] = polygon_vertices[j];
}
if (p_polypath_item->IsHole()) {
tp.SetOrientation(TRIANGULATOR_CW);
tp.SetHole(true);
} else {
tp.SetOrientation(TRIANGULATOR_CCW);
}
p_tppl_in_polygon.push_back(tp);
for (int i = 0; i < p_polypath_item->ChildCount(); i++) {
const ClipperLib::PolyNode *polypath_item = p_polypath_item->Childs[i];
_recursive_process_polytree_items(p_tppl_in_polygon, polypath_item);
}
}
bool Geometry::_merge_convex_decompose_polygon_2d(Geometry::PolyBooleanOperation p_op, const Vector<Vector<Point2>> &p_polygons, PoolVector<Vector2> &r_new_vertices, Vector<Vector<int>> &r_new_polygons, Geometry::PolygonFillType fill_type) {
using namespace ClipperLib;
ClipType op = ctUnion;
switch (p_op) {
case OPERATION_UNION:
op = ctUnion;
break;
case OPERATION_DIFFERENCE:
op = ctDifference;
break;
case OPERATION_INTERSECTION:
op = ctIntersection;
break;
case OPERATION_XOR:
op = ctXor;
break;
}
PolyFillType pft;
switch (fill_type) {
case POLYGON_FILL_TYPE_EVEN_ODD:
pft = pftEvenOdd;
break;
case POLYGON_FILL_TYPE_NON_ZERO:
pft = pftNonZero;
break;
case POLYGON_FILL_TYPE_POSITIVE:
pft = pftPositive;
break;
case POLYGON_FILL_TYPE_NEGATIVE:
pft = pftNegative;
break;
default:
pft = pftEvenOdd;
break;
}
Paths polygon_paths_scaled;
for (int i = 0; i < p_polygons.size(); i++) {
const Vector<Vector2> &baked_outline = p_polygons[i];
Path polygon_path;
for (int j = 0; j < baked_outline.size(); ++j) {
const Vector2 &baked_outline_point = baked_outline[j];
polygon_path << IntPoint(baked_outline_point.x * (real_t)SCALE_FACTOR, baked_outline_point.y * (real_t)SCALE_FACTOR);
}
polygon_paths_scaled.push_back(polygon_path);
}
PolyTree polytree;
Clipper clp;
clp.AddPaths(polygon_paths_scaled, ptSubject, true);
clp.Execute(op, polytree, pft);
List<TriangulatorPoly> tppl_in_polygon, tppl_out_polygon;
for (int i = 0; i < polytree.ChildCount(); i++) {
const ClipperLib::PolyNode *polypath_item = polytree.Childs[i];
_recursive_process_polytree_items(tppl_in_polygon, polypath_item);
}
TriangulatorPartition tpart;
if (tpart.ConvexPartition_HM(&tppl_in_polygon, &tppl_out_polygon) == 0) { //failed!
return false;
}
HashMap<Vector2, int> points;
for (List<TriangulatorPoly>::Element *I = tppl_out_polygon.front(); I; I = I->next()) {
TriangulatorPoly &tp = I->get();
Vector<int> new_polygon;
for (int64_t i = 0; i < tp.GetNumPoints(); i++) {
HashMap<Vector2, int>::Element *E = points.find(tp[i]);
if (!E) {
E = points.insert(tp[i], r_new_vertices.size());
r_new_vertices.push_back(tp[i]);
}
new_polygon.push_back(E->value());
}
r_new_polygons.push_back(new_polygon);
}
return true;
}
real_t Geometry::calculate_convex_hull_volume(const Geometry::MeshData &p_md) {
if (!p_md.vertices.size()) {
return 0;

View File

@ -771,6 +771,12 @@ public:
END_SQUARE,
END_ROUND
};
enum PolygonFillType {
POLYGON_FILL_TYPE_EVEN_ODD,
POLYGON_FILL_TYPE_NON_ZERO,
POLYGON_FILL_TYPE_POSITIVE,
POLYGON_FILL_TYPE_NEGATIVE,
};
static Vector<Vector<Point2>> merge_polygons_2d(const Vector<Point2> &p_polygon_a, const Vector<Point2> &p_polygon_b) {
return _polypaths_do_operation(OPERATION_UNION, p_polygon_a, p_polygon_b);
@ -800,6 +806,18 @@ public:
return _polypath_offset(p_polygon, p_delta, p_join_type, END_POLYGON);
}
static Vector<Vector<Point2>> merge_all_polygons_2d(const Vector<Vector<Point2>> &p_polygons, const Vector<Point2> &p_polypath_clip, PolygonFillType fill_type = POLYGON_FILL_TYPE_EVEN_ODD) {
return _polypaths_do_operations(OPERATION_UNION, p_polygons, p_polypath_clip, fill_type);
}
static Vector<Vector<Point2>> clip_all2_polygons_2d(const Vector<Vector<Point2>> &p_polygons, const Vector<Vector<Point2>> &p_polypath_clip, PolygonFillType fill_type = POLYGON_FILL_TYPE_EVEN_ODD) {
return _polypaths2_do_operations(OPERATION_DIFFERENCE, p_polygons, p_polypath_clip, fill_type);
}
static bool merge_convex_decompose_polygon_2d(const Vector<Vector<Point2>> &p_polygons, PoolVector<Vector2> &r_new_vertices, Vector<Vector<int>> &r_new_polygons, PolygonFillType fill_type = POLYGON_FILL_TYPE_EVEN_ODD) {
return _merge_convex_decompose_polygon_2d(OPERATION_UNION, p_polygons, r_new_vertices, r_new_polygons, fill_type);
}
static Vector<Vector<Point2>> offset_polyline_2d(const Vector<Vector2> &p_polygon, real_t p_delta, PolyJoinType p_join_type, PolyEndType p_end_type) {
ERR_FAIL_COND_V_MSG(p_end_type == END_POLYGON, Vector<Vector<Point2>>(), "Attempt to offset a polyline like a polygon (use offset_polygon_2d instead).");
@ -1110,6 +1128,9 @@ public:
private:
static Vector<Vector<Point2>> _polypaths_do_operation(PolyBooleanOperation p_op, const Vector<Point2> &p_polypath_a, const Vector<Point2> &p_polypath_b, bool is_a_open = false);
static Vector<Vector<Point2>> _polypath_offset(const Vector<Point2> &p_polypath, real_t p_delta, PolyJoinType p_join_type, PolyEndType p_end_type);
static Vector<Vector<Point2>> _polypaths_do_operations(PolyBooleanOperation p_op, const Vector<Vector<Point2>> &p_polypaths, const Vector<Point2> &p_polypath_clip, PolygonFillType fill_type, bool is_a_open = false);
static Vector<Vector<Point2>> _polypaths2_do_operations(PolyBooleanOperation p_op, const Vector<Vector<Point2>> &p_polypaths, const Vector<Vector<Point2>> &p_polypath_clip, PolygonFillType fill_type, bool is_a_open = false);
static bool _merge_convex_decompose_polygon_2d(PolyBooleanOperation p_op, const Vector<Vector<Point2>> &p_polygons, PoolVector<Vector2> &r_new_vertices, Vector<Vector<int>> &r_new_polygons, PolygonFillType fill_type = POLYGON_FILL_TYPE_EVEN_ODD);
};
#endif

View File

@ -44,13 +44,6 @@
#include "scene/3d/mesh_instance.h"
#endif // _3D_DISABLED
#include "modules/modules_enabled.gen.h"
#ifdef MODULE_CLIPPER2_ENABLED
#include "modules/clipper2/lib/include/clipper2/clipper.h"
#include "modules/clipper2/polypartition.h"
#endif
#ifndef _3D_DISABLED
#include <Recast.h>
#endif // _3D_DISABLED
@ -291,38 +284,6 @@ void PandemoniumNavigationMeshGenerator::_static_parse_2d_source_geometry_data(R
}
}
// rewrite this to use clipper 1
#ifdef MODULE_CLIPPER2_ENABLED
static void _recursive_process_polytree_items(List<TPPLPoly> &p_tppl_in_polygon, const Clipper2Lib::PolyPath64 *p_polypath_item) {
using namespace Clipper2Lib;
Vector<Vector2> polygon_vertices;
for (const Point64 &polypath_point : p_polypath_item->Polygon()) {
polygon_vertices.push_back(Vector2(static_cast<real_t>(polypath_point.x), static_cast<real_t>(polypath_point.y)));
}
TPPLPoly tp;
tp.Init(polygon_vertices.size());
for (int j = 0; j < polygon_vertices.size(); j++) {
tp[j] = polygon_vertices[j];
}
if (p_polypath_item->IsHole()) {
tp.SetOrientation(TPPL_ORIENTATION_CW);
tp.SetHole(true);
} else {
tp.SetOrientation(TPPL_ORIENTATION_CCW);
}
p_tppl_in_polygon.push_back(tp);
for (size_t i = 0; i < p_polypath_item->Count(); i++) {
const PolyPath64 *polypath_item = p_polypath_item->Child(i);
_recursive_process_polytree_items(p_tppl_in_polygon, polypath_item);
}
}
#endif // MODULE_CLIPPER2_ENABLED
void PandemoniumNavigationMeshGenerator::_static_bake_2d_from_source_geometry_data(Ref<NavigationPolygon> p_navigation_polygon, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data) {
ERR_FAIL_COND_MSG(!p_navigation_polygon.is_valid(), "Invalid navigation polygon.");
ERR_FAIL_COND_MSG(!p_source_geometry_data.is_valid(), "Invalid source geometry data.");
@ -331,86 +292,77 @@ void PandemoniumNavigationMeshGenerator::_static_bake_2d_from_source_geometry_da
const Vector<Vector<Vector2>> &traversable_outlines = p_source_geometry_data->_get_traversable_outlines();
const Vector<Vector<Vector2>> &obstruction_outlines = p_source_geometry_data->_get_obstruction_outlines();
// rewrite this to use clipper 1
#ifdef MODULE_CLIPPER2_ENABLED
using namespace Clipper2Lib;
Paths64 traversable_polygon_paths;
Paths64 obstruction_polygon_paths;
int outline_count = p_navigation_polygon->get_outline_count();
Vector<Vector<Point2>> s_traversable_polygon_paths;
for (int i = 0; i < outline_count; i++) {
PoolVector<Vector2> traversable_outline = p_navigation_polygon->get_outline(i);
Path64 subject_path;
Vector<Point2> subject_path;
for (int j = 0; j < traversable_outline.size(); ++j) {
Vector2 traversable_point = traversable_outline[j];
const Point64 &point = Point64(traversable_point.x, traversable_point.y);
subject_path.push_back(point);
subject_path.push_back(traversable_point);
}
traversable_polygon_paths.push_back(subject_path);
s_traversable_polygon_paths.push_back(subject_path);
}
Vector<Vector<Point2>> s_traversable_outlines;
for (int i = 0; i < traversable_outlines.size(); i++) {
Vector<Vector2> traversable_outline = traversable_outlines[i];
Path64 subject_path;
Vector<Point2> subject_path;
for (int j = 0; j < traversable_outline.size(); ++j) {
Vector2 traversable_point = traversable_outline[j];
const Point64 &point = Point64(traversable_point.x, traversable_point.y);
subject_path.push_back(point);
subject_path.push_back(traversable_point);
}
traversable_polygon_paths.push_back(subject_path);
s_traversable_outlines.push_back(subject_path);
}
Vector<Vector<Point2>> s_obstruction_polygon_paths;
for (int i = 0; i < obstruction_outlines.size(); i++) {
const Vector<Vector2> &obstruction_outline = obstruction_outlines[i];
Vector<Vector2> obstruction_outline = obstruction_outlines[i];
Path64 clip_path;
Vector<Point2> clip_path;
for (int j = 0; j < obstruction_outline.size(); ++j) {
const Vector2 &obstruction_point = obstruction_outline[j];
const Point64 &point = Point64(obstruction_point.x, obstruction_point.y);
clip_path.push_back(point);
Vector2 traversable_point = obstruction_outline[j];
clip_path.push_back(traversable_point);
}
obstruction_polygon_paths.push_back(clip_path);
s_obstruction_polygon_paths.push_back(clip_path);
}
Paths64 path_solution;
FillRule clipper_fillrule = FillRule::EvenOdd;
Geometry::PolygonFillType geom_fillrule = Geometry::POLYGON_FILL_TYPE_EVEN_ODD;
switch (p_navigation_polygon->get_polygon_bake_fillrule()) {
case NavigationPolygon::POLYGON_FILLRULE_EVENODD: {
clipper_fillrule = FillRule::EvenOdd;
geom_fillrule = Geometry::POLYGON_FILL_TYPE_EVEN_ODD;
} break;
case NavigationPolygon::POLYGON_FILLRULE_NONZERO: {
clipper_fillrule = FillRule::NonZero;
geom_fillrule = Geometry::POLYGON_FILL_TYPE_NON_ZERO;
} break;
case NavigationPolygon::POLYGON_FILLRULE_POSITIVE: {
clipper_fillrule = FillRule::Positive;
geom_fillrule = Geometry::POLYGON_FILL_TYPE_POSITIVE;
} break;
case NavigationPolygon::POLYGON_FILLRULE_NEGATIVE: {
clipper_fillrule = FillRule::Negative;
geom_fillrule = Geometry::POLYGON_FILL_TYPE_NEGATIVE;
} break;
default: {
WARN_PRINT_ONCE("No match for used NavigationPolygon::POLYGON_FILLRULE - fallback to default");
clipper_fillrule = FillRule::EvenOdd;
geom_fillrule = Geometry::POLYGON_FILL_TYPE_EVEN_ODD;
} break;
}
// first merge all traversable polygons according to user specified fill rule
Paths64 dummy_clip_path;
traversable_polygon_paths = Union(traversable_polygon_paths, dummy_clip_path, clipper_fillrule);
s_traversable_polygon_paths = Geometry::merge_all_polygons_2d(s_traversable_polygon_paths, Vector<Point2>(), geom_fillrule);
// merge all obstruction polygons, don't allow holes for what is considered "solid" 2D geometry
obstruction_polygon_paths = Union(obstruction_polygon_paths, dummy_clip_path, FillRule::NonZero);
s_obstruction_polygon_paths = Geometry::merge_all_polygons_2d(s_obstruction_polygon_paths, Vector<Point2>(), Geometry::POLYGON_FILL_TYPE_NON_ZERO);
path_solution = Difference(traversable_polygon_paths, obstruction_polygon_paths, clipper_fillrule);
Vector<Vector<Point2>> s_path_solution;
s_path_solution = Geometry::clip_all2_polygons_2d(s_traversable_polygon_paths, s_obstruction_polygon_paths, geom_fillrule);
// Seems to be bugged when dealing with 2d (likely because of the scales)
// Seems to be bugged when dealing with 2d
/*
JoinType clipper_jointype = JoinType::Square;
@ -439,12 +391,12 @@ void PandemoniumNavigationMeshGenerator::_static_bake_2d_from_source_geometry_da
Vector<PoolVector<Vector2>> new_baked_outlines;
for (uint32_t i = 0; i < path_solution.size(); i++) {
const Path64 &scaled_path = path_solution[i];
for (int i = 0; i < s_path_solution.size(); i++) {
const Vector<Point2> &scaled_path = s_path_solution[i];
PoolVector<Vector2> polypath;
for (uint32_t j = 0; j < scaled_path.size(); ++j) {
const Point64 &scaled_point = scaled_path[j];
for (int j = 0; j < scaled_path.size(); ++j) {
const Vector2 &scaled_point = scaled_path[j];
polypath.push_back(Vector2(static_cast<real_t>(scaled_point.x), static_cast<real_t>(scaled_point.y)));
}
@ -461,38 +413,23 @@ void PandemoniumNavigationMeshGenerator::_static_bake_2d_from_source_geometry_da
return;
}
Paths64 polygon_paths;
Vector<Vector<Point2>> s_polygon_paths;
for (int i = 0; i < new_baked_outlines.size(); i++) {
const PoolVector<Vector2> &baked_outline = new_baked_outlines[i];
Path64 polygon_path;
Vector<Point2> polygon_path;
for (int j = 0; j < baked_outline.size(); ++j) {
const Vector2 &baked_outline_point = baked_outline[j];
const Point64 &point = Point64(baked_outline_point.x, baked_outline_point.y);
polygon_path.push_back(point);
polygon_path.push_back(baked_outline_point);
}
polygon_paths.push_back(polygon_path);
s_polygon_paths.push_back(polygon_path);
}
ClipType clipper_cliptype = ClipType::Union;
PoolVector<Vector2> s_new_vertices;
Vector<Vector<int>> s_new_polygons;
List<TPPLPoly> tppl_in_polygon, tppl_out_polygon;
PolyTree64 polytree;
Clipper64 clipper_64;
clipper_64.AddSubject(polygon_paths);
clipper_64.Execute(clipper_cliptype, clipper_fillrule, polytree);
for (size_t i = 0; i < polytree.Count(); i++) {
const PolyPath64 *polypath_item = polytree[i];
_recursive_process_polytree_items(tppl_in_polygon, polypath_item);
}
TPPLPartition tpart;
if (tpart.ConvexPartition_HM(&tppl_in_polygon, &tppl_out_polygon) == 0) { //failed!
if (!Geometry::merge_convex_decompose_polygon_2d(s_polygon_paths, s_new_vertices, s_new_polygons)) {
ERR_PRINT("NavigationPolygon Convex partition failed. Unable to create a valid NavigationMesh from defined polygon outline paths.");
p_navigation_polygon->set_vertices(PoolVector<Vector2>());
p_navigation_polygon->set_polygons(Vector<Vector<int>>());
@ -500,30 +437,8 @@ void PandemoniumNavigationMeshGenerator::_static_bake_2d_from_source_geometry_da
return;
}
PoolVector<Vector2> new_vertices;
Vector<Vector<int>> new_polygons;
HashMap<Vector2, int> points;
for (List<TPPLPoly>::Element *I = tppl_out_polygon.front(); I; I = I->next()) {
TPPLPoly &tp = I->get();
Vector<int> new_polygon;
for (int64_t i = 0; i < tp.GetNumPoints(); i++) {
HashMap<Vector2, int>::Element *E = points.find(tp[i]);
if (!E) {
E = points.insert(tp[i], new_vertices.size());
new_vertices.push_back(tp[i]);
}
new_polygon.push_back(E->value());
}
new_polygons.push_back(new_polygon);
}
p_navigation_polygon->set_vertices(new_vertices);
p_navigation_polygon->set_polygons(new_polygons);
#endif // MODULE_CLIPPER2_ENABLED
p_navigation_polygon->set_vertices(s_new_vertices);
p_navigation_polygon->set_polygons(s_new_polygons);
p_navigation_polygon->commit_changes();
}