diff --git a/core/math/geometry.cpp b/core/math/geometry.cpp index 2d32088ed..4d650c279 100644 --- a/core/math/geometry.cpp +++ b/core/math/geometry.cpp @@ -1278,6 +1278,316 @@ Vector> Geometry::_polypath_offset(const Vector &p_polypa return polypaths; } +Vector> Geometry::_polypaths_do_operations(PolyBooleanOperation p_op, const Vector> &p_polypaths, const Vector &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 &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> polypaths; + + for (Paths::size_type i = 0; i < paths.size(); ++i) { + Vector polypath; + + const Path &scaled_path = paths[i]; + + for (Paths::size_type j = 0; j < scaled_path.size(); ++j) { + polypath.push_back(Point2( + static_cast(scaled_path[j].X) / (real_t)SCALE_FACTOR, + static_cast(scaled_path[j].Y) / (real_t)SCALE_FACTOR)); + } + polypaths.push_back(polypath); + } + return polypaths; +} + +Vector> Geometry::_polypaths2_do_operations(PolyBooleanOperation p_op, const Vector> &p_polypaths, const Vector> &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 &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 &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> polypaths; + + for (Paths::size_type i = 0; i < paths.size(); ++i) { + Vector polypath; + + const Path &scaled_path = paths[i]; + + for (Paths::size_type j = 0; j < scaled_path.size(); ++j) { + polypath.push_back(Point2( + static_cast(scaled_path[j].X) / (real_t)SCALE_FACTOR, + static_cast(scaled_path[j].Y) / (real_t)SCALE_FACTOR)); + } + polypaths.push_back(polypath); + } + return polypaths; +} + +static void _recursive_process_polytree_items(List &p_tppl_in_polygon, const ClipperLib::PolyNode *p_polypath_item) { + using namespace ClipperLib; + + Vector 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(polypath_point.X / (real_t)SCALE_FACTOR), static_cast(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> &p_polygons, PoolVector &r_new_vertices, Vector> &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 &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 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 points; + for (List::Element *I = tppl_out_polygon.front(); I; I = I->next()) { + TriangulatorPoly &tp = I->get(); + + Vector new_polygon; + + for (int64_t i = 0; i < tp.GetNumPoints(); i++) { + HashMap::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; diff --git a/core/math/geometry.h b/core/math/geometry.h index 854ae2749..cdec0b3f0 100644 --- a/core/math/geometry.h +++ b/core/math/geometry.h @@ -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> merge_polygons_2d(const Vector &p_polygon_a, const Vector &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> merge_all_polygons_2d(const Vector> &p_polygons, const Vector &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> clip_all2_polygons_2d(const Vector> &p_polygons, const Vector> &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> &p_polygons, PoolVector &r_new_vertices, Vector> &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> offset_polyline_2d(const Vector &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>(), "Attempt to offset a polyline like a polygon (use offset_polygon_2d instead)."); @@ -1110,6 +1128,9 @@ public: private: static Vector> _polypaths_do_operation(PolyBooleanOperation p_op, const Vector &p_polypath_a, const Vector &p_polypath_b, bool is_a_open = false); static Vector> _polypath_offset(const Vector &p_polypath, real_t p_delta, PolyJoinType p_join_type, PolyEndType p_end_type); + static Vector> _polypaths_do_operations(PolyBooleanOperation p_op, const Vector> &p_polypaths, const Vector &p_polypath_clip, PolygonFillType fill_type, bool is_a_open = false); + static Vector> _polypaths2_do_operations(PolyBooleanOperation p_op, const Vector> &p_polypaths, const Vector> &p_polypath_clip, PolygonFillType fill_type, bool is_a_open = false); + static bool _merge_convex_decompose_polygon_2d(PolyBooleanOperation p_op, const Vector> &p_polygons, PoolVector &r_new_vertices, Vector> &r_new_polygons, PolygonFillType fill_type = POLYGON_FILL_TYPE_EVEN_ODD); }; #endif diff --git a/modules/navigation_mesh_generator/pandemonium_navigation_mesh_generator.cpp b/modules/navigation_mesh_generator/pandemonium_navigation_mesh_generator.cpp index 17feae299..edaa1de68 100644 --- a/modules/navigation_mesh_generator/pandemonium_navigation_mesh_generator.cpp +++ b/modules/navigation_mesh_generator/pandemonium_navigation_mesh_generator.cpp @@ -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 #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 &p_tppl_in_polygon, const Clipper2Lib::PolyPath64 *p_polypath_item) { - using namespace Clipper2Lib; - - Vector polygon_vertices; - - for (const Point64 &polypath_point : p_polypath_item->Polygon()) { - polygon_vertices.push_back(Vector2(static_cast(polypath_point.x), static_cast(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 p_navigation_polygon, Ref 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> &traversable_outlines = p_source_geometry_data->_get_traversable_outlines(); const Vector> &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> s_traversable_polygon_paths; + for (int i = 0; i < outline_count; i++) { PoolVector traversable_outline = p_navigation_polygon->get_outline(i); - Path64 subject_path; - + Vector 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> s_traversable_outlines; + for (int i = 0; i < traversable_outlines.size(); i++) { Vector traversable_outline = traversable_outlines[i]; - Path64 subject_path; + Vector 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> s_obstruction_polygon_paths; + for (int i = 0; i < obstruction_outlines.size(); i++) { - const Vector &obstruction_outline = obstruction_outlines[i]; + Vector obstruction_outline = obstruction_outlines[i]; - Path64 clip_path; + Vector 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(), 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(), Geometry::POLYGON_FILL_TYPE_NON_ZERO); - path_solution = Difference(traversable_polygon_paths, obstruction_polygon_paths, clipper_fillrule); + Vector> 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> 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 &scaled_path = s_path_solution[i]; PoolVector 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(scaled_point.x), static_cast(scaled_point.y))); } @@ -461,38 +413,23 @@ void PandemoniumNavigationMeshGenerator::_static_bake_2d_from_source_geometry_da return; } - Paths64 polygon_paths; + Vector> s_polygon_paths; for (int i = 0; i < new_baked_outlines.size(); i++) { const PoolVector &baked_outline = new_baked_outlines[i]; - Path64 polygon_path; + Vector 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 s_new_vertices; + Vector> s_new_polygons; - List 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()); p_navigation_polygon->set_polygons(Vector>()); @@ -500,30 +437,8 @@ void PandemoniumNavigationMeshGenerator::_static_bake_2d_from_source_geometry_da return; } - PoolVector new_vertices; - Vector> new_polygons; - - HashMap points; - for (List::Element *I = tppl_out_polygon.front(); I; I = I->next()) { - TPPLPoly &tp = I->get(); - - Vector new_polygon; - - for (int64_t i = 0; i < tp.GetNumPoints(); i++) { - HashMap::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(); }