From 0ee7e3102b6072d2f5a9d157c8afdb99e13624e6 Mon Sep 17 00:00:00 2001 From: smix8 <52464204+smix8@users.noreply.github.com> Date: Thu, 17 Aug 2023 18:32:30 +0200 Subject: [PATCH] Add 2D navigation mesh baking Adds 2D navigation mesh baking. --- COPYRIGHT.txt | 5 + SConstruct | 1 + core/SCsub | 18 + .../NavigationMeshSourceGeometryData2D.xml | 65 + doc/classes/NavigationPolygon.xml | 118 +- doc/classes/NavigationRegion2D.xml | 19 + doc/classes/NavigationServer2D.xml | 30 + .../navigation_polygon_editor_plugin.cpp | 88 +- .../navigation_polygon_editor_plugin.h | 26 +- main/main.cpp | 13 +- modules/navigation/config.py | 2 +- .../navigation/godot_navigation_server.cpp | 18 +- modules/navigation/godot_navigation_server.h | 1 + .../navigation/godot_navigation_server_2d.cpp | 369 ++ .../navigation/godot_navigation_server_2d.h | 234 ++ modules/navigation/nav_mesh_generator_2d.cpp | 830 +++++ modules/navigation/nav_mesh_generator_2d.h | 100 + modules/navigation/register_types.cpp | 7 + scene/2d/navigation_region_2d.cpp | 35 +- scene/2d/navigation_region_2d.h | 3 + scene/register_scene_types.cpp | 2 + ...avigation_mesh_source_geometry_data_2d.cpp | 138 + .../navigation_mesh_source_geometry_data_2d.h | 78 + scene/resources/navigation_polygon.cpp | 132 +- scene/resources/navigation_polygon.h | 51 +- servers/navigation_server_2d.cpp | 516 +-- servers/navigation_server_2d.h | 218 +- servers/navigation_server_2d_dummy.h | 155 + servers/navigation_server_3d.h | 1 + servers/navigation_server_3d_dummy.h | 1 + tests/test_main.cpp | 4 +- thirdparty/README.md | 12 + thirdparty/clipper2/LICENSE | 23 + thirdparty/clipper2/clipper2-exceptions.patch | 35 + .../clipper2/include/clipper2/clipper.core.h | 846 +++++ .../include/clipper2/clipper.engine.h | 610 ++++ .../include/clipper2/clipper.export.h | 774 +++++ .../clipper2/include/clipper2/clipper.h | 776 +++++ .../include/clipper2/clipper.minkowski.h | 120 + .../include/clipper2/clipper.offset.h | 114 + .../include/clipper2/clipper.rectclip.h | 82 + thirdparty/clipper2/src/clipper.engine.cpp | 2979 +++++++++++++++++ thirdparty/clipper2/src/clipper.offset.cpp | 618 ++++ thirdparty/clipper2/src/clipper.rectclip.cpp | 976 ++++++ 44 files changed, 10706 insertions(+), 537 deletions(-) create mode 100644 doc/classes/NavigationMeshSourceGeometryData2D.xml create mode 100644 modules/navigation/godot_navigation_server_2d.cpp create mode 100644 modules/navigation/godot_navigation_server_2d.h create mode 100644 modules/navigation/nav_mesh_generator_2d.cpp create mode 100644 modules/navigation/nav_mesh_generator_2d.h create mode 100644 scene/resources/navigation_mesh_source_geometry_data_2d.cpp create mode 100644 scene/resources/navigation_mesh_source_geometry_data_2d.h create mode 100644 servers/navigation_server_2d_dummy.h create mode 100644 thirdparty/clipper2/LICENSE create mode 100644 thirdparty/clipper2/clipper2-exceptions.patch create mode 100644 thirdparty/clipper2/include/clipper2/clipper.core.h create mode 100644 thirdparty/clipper2/include/clipper2/clipper.engine.h create mode 100644 thirdparty/clipper2/include/clipper2/clipper.export.h create mode 100644 thirdparty/clipper2/include/clipper2/clipper.h create mode 100644 thirdparty/clipper2/include/clipper2/clipper.minkowski.h create mode 100644 thirdparty/clipper2/include/clipper2/clipper.offset.h create mode 100644 thirdparty/clipper2/include/clipper2/clipper.rectclip.h create mode 100644 thirdparty/clipper2/src/clipper.engine.cpp create mode 100644 thirdparty/clipper2/src/clipper.offset.cpp create mode 100644 thirdparty/clipper2/src/clipper.rectclip.cpp diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index 3200eafa00d..ce9be3dd5e2 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -171,6 +171,11 @@ Comment: CA certificates Copyright: Mozilla Contributors License: MPL-2.0 +Files: ./thirdparty/clipper2/ +Comment: Clipper2 +Copyright: 2010-2013, Angus Johnson +License: BSL-1.0 + Files: ./thirdparty/cvtt/ Comment: Convection Texture Tools Stand-Alone Kernels Copyright: 2018, Eric Lasota diff --git a/SConstruct b/SConstruct index d01062db8d3..a06dfc1bde5 100644 --- a/SConstruct +++ b/SConstruct @@ -226,6 +226,7 @@ opts.Add("scu_limit", "Max includes per SCU file when using scu_build (determine # Thirdparty libraries opts.Add(BoolVariable("builtin_brotli", "Use the built-in Brotli library", True)) opts.Add(BoolVariable("builtin_certs", "Use the built-in SSL certificates bundles", True)) +opts.Add(BoolVariable("builtin_clipper2", "Use the built-in Clipper2 library", True)) opts.Add(BoolVariable("builtin_embree", "Use the built-in Embree library", True)) opts.Add(BoolVariable("builtin_enet", "Use the built-in ENet library", True)) opts.Add(BoolVariable("builtin_freetype", "Use the built-in FreeType library", True)) diff --git a/core/SCsub b/core/SCsub index ab78eeedc71..3b1a7ca79a1 100644 --- a/core/SCsub +++ b/core/SCsub @@ -89,6 +89,24 @@ if env["brotli"] and env["builtin_brotli"]: env_thirdparty.add_source_files(thirdparty_obj, thirdparty_brotli_sources) +# Clipper2 Thirdparty source files used for polygon and polyline boolean operations. +if env["builtin_clipper2"]: + thirdparty_clipper_dir = "#thirdparty/clipper2/" + thirdparty_clipper_sources = [ + "src/clipper.engine.cpp", + "src/clipper.offset.cpp", + "src/clipper.rectclip.cpp", + ] + thirdparty_clipper_sources = [thirdparty_clipper_dir + file for file in thirdparty_clipper_sources] + + env_thirdparty.Prepend(CPPPATH=[thirdparty_clipper_dir + "include"]) + env.Prepend(CPPPATH=[thirdparty_clipper_dir + "include"]) + + env_thirdparty.Append(CPPDEFINES=["CLIPPER2_ENABLED"]) + env.Append(CPPDEFINES=["CLIPPER2_ENABLED"]) + + env_thirdparty.add_source_files(thirdparty_obj, thirdparty_clipper_sources) + # Zlib library, can be unbundled if env["builtin_zlib"]: thirdparty_zlib_dir = "#thirdparty/zlib/" diff --git a/doc/classes/NavigationMeshSourceGeometryData2D.xml b/doc/classes/NavigationMeshSourceGeometryData2D.xml new file mode 100644 index 00000000000..4bf5213da7f --- /dev/null +++ b/doc/classes/NavigationMeshSourceGeometryData2D.xml @@ -0,0 +1,65 @@ + + + + Container for parsed source geometry data used in navigation mesh baking. + + + Container for parsed source geometry data used in navigation mesh baking. + + + + + + + + + Adds the outline points of a shape as obstructed area. + + + + + + + Adds the outline points of a shape as traversable area. + + + + + + Clears the internal data. + + + + + + Returns all the obstructed area outlines arrays. + + + + + + Returns all the traversable area outlines arrays. + + + + + + Returns [code]true[/code] when parsed source geometry data exists. + + + + + + + Sets all the obstructed area outlines arrays. + + + + + + + Sets all the traversable area outlines arrays. + + + + diff --git a/doc/classes/NavigationPolygon.xml b/doc/classes/NavigationPolygon.xml index fe28c5a468b..e0ad6032def 100644 --- a/doc/classes/NavigationPolygon.xml +++ b/doc/classes/NavigationPolygon.xml @@ -1,44 +1,44 @@ - A navigation polygon that defines traversable areas and obstacles. + A 2D navigation mesh that describes a traversable surface for pathfinding. - There are two ways to create polygons. Either by using the [method add_outline] method, or using the [method add_polygon] method. - Using [method add_outline]: + A navigation mesh can be created either by baking it with the help of the [NavigationServer2D], or by adding vertices and convex polygon indices arrays manually. + To bake a navigation mesh at least one outline needs to be added that defines the outer bounds of the baked area. [codeblocks] [gdscript] - var polygon = NavigationPolygon.new() - var outline = PackedVector2Array([Vector2(0, 0), Vector2(0, 50), Vector2(50, 50), Vector2(50, 0)]) - polygon.add_outline(outline) - polygon.make_polygons_from_outlines() - $NavigationRegion2D.navigation_polygon = polygon + var new_navigation_mesh = NavigationPolygon.new() + var bounding_outline = PackedVector2Array([Vector2(0, 0), Vector2(0, 50), Vector2(50, 50), Vector2(50, 0)]) + new_navigation_mesh.add_outline(bounding_outline) + NavigationServer2D.bake_from_source_geometry_data(new_navigation_mesh, NavigationMeshSourceGeometryData2D.new()); + $NavigationRegion2D.navigation_polygon = new_navigation_mesh [/gdscript] [csharp] - var polygon = new NavigationPolygon(); - var outline = new Vector2[] { new Vector2(0, 0), new Vector2(0, 50), new Vector2(50, 50), new Vector2(50, 0) }; - polygon.AddOutline(outline); - polygon.MakePolygonsFromOutlines(); - GetNode<NavigationRegion2D>("NavigationRegion2D").NavigationPolygon = polygon; + var newNavigationMesh = new NavigationPolygon(); + var boundingOutline = new Vector2[] { new Vector2(0, 0), new Vector2(0, 50), new Vector2(50, 50), new Vector2(50, 0) }; + newNavigationMesh.AddOutline(boundingOutline); + NavigationServer2D.BakeFromSourceGeometryData(newNavigationMesh, new NavigationMeshSourceGeometryData2D()); + GetNode<NavigationRegion2D>("NavigationRegion2D").NavigationPolygon = newNavigationMesh; [/csharp] [/codeblocks] - Using [method add_polygon] and indices of the vertices array. + Adding vertices and polygon indices manually. [codeblocks] [gdscript] - var polygon = NavigationPolygon.new() - var vertices = PackedVector2Array([Vector2(0, 0), Vector2(0, 50), Vector2(50, 50), Vector2(50, 0)]) - polygon.vertices = vertices - var indices = PackedInt32Array([0, 1, 2, 3]) - polygon.add_polygon(indices) - $NavigationRegion2D.navigation_polygon = polygon + var new_navigation_mesh = NavigationPolygon.new() + var new_vertices = PackedVector2Array([Vector2(0, 0), Vector2(0, 50), Vector2(50, 50), Vector2(50, 0)]) + new_navigation_mesh.vertices = new_vertices + var new_polygon_indices = PackedInt32Array([0, 1, 2, 3]) + new_navigation_mesh.add_polygon(new_polygon_indices) + $NavigationRegion2D.navigation_polygon = new_navigation_mesh [/gdscript] [csharp] - var polygon = new NavigationPolygon(); - var vertices = new Vector2[] { new Vector2(0, 0), new Vector2(0, 50), new Vector2(50, 50), new Vector2(50, 0) }; - polygon.Vertices = vertices; - var indices = new int[] { 0, 1, 2, 3 }; - polygon.AddPolygon(indices); - GetNode<NavigationRegion2D>("NavigationRegion2D").NavigationPolygon = polygon; + var newNavigationMesh = new NavigationPolygon(); + var newVertices = new Vector2[] { new Vector2(0, 0), new Vector2(0, 50), new Vector2(50, 50), new Vector2(50, 0) }; + newNavigationMesh.Vertices = newVertices; + var newPolygonIndices = new int[] { 0, 1, 2, 3 }; + newNavigationMesh.AddPolygon(newPolygonIndices); + GetNode<NavigationRegion2D>("NavigationRegion2D").NavigationPolygon = newNavigationMesh; [/csharp] [/codeblocks] @@ -51,7 +51,7 @@ - Appends a [PackedVector2Array] that contains the vertices of an outline to the internal array that contains all the outlines. You have to call [method make_polygons_from_outlines] in order for this array to be converted to polygons that the engine will use. + Appends a [PackedVector2Array] that contains the vertices of an outline to the internal array that contains all the outlines. @@ -59,7 +59,7 @@ - Adds a [PackedVector2Array] that contains the vertices of an outline to the internal array that contains all the outlines at a fixed position. You have to call [method make_polygons_from_outlines] in order for this array to be converted to polygons that the engine will use. + Adds a [PackedVector2Array] that contains the vertices of an outline to the internal array that contains all the outlines at a fixed position. @@ -106,6 +106,13 @@ Returns the number of outlines that were created in the editor or by script. + + + + + Returns whether or not the specified layer of the [member parsed_collision_mask] is enabled, given a [param layer_number] between 1 and 32. + + @@ -125,10 +132,11 @@ Returns a [PackedVector2Array] containing all the vertices being used to create the polygons. - + Creates polygons from the outlines added in the editor or by script. + [i]Deprecated.[/i] This function is deprecated, and might be removed in a future release. Use [method NavigationServer2D.parse_source_geometry_data] and [method NavigationServer2D.bake_from_source_geometry_data] instead. @@ -146,6 +154,14 @@ Changes an outline created in the editor or by script. You have to call [method make_polygons_from_outlines] for the polygons to update. + + + + + + Based on [param value], enables or disables the specified layer in the [member parsed_collision_mask], given a [param layer_number] between 1 and 32. + + @@ -155,8 +171,52 @@ + + The distance to erode/shrink the walkable surface when baking the navigation mesh. + The cell size used to rasterize the navigation mesh vertices. Must match with the cell size on the navigation map. + + The physics layers to scan for static colliders. + Only used when [member parsed_geometry_type] is [constant PARSED_GEOMETRY_STATIC_COLLIDERS] or [constant PARSED_GEOMETRY_BOTH]. + + + Determines which type of nodes will be parsed as geometry. See [enum ParsedGeometryType] for possible values. + + + The group name of nodes that should be parsed for baking source geometry. + Only used when [member source_geometry_mode] is [constant SOURCE_GEOMETRY_GROUPS_WITH_CHILDREN] or [constant SOURCE_GEOMETRY_GROUPS_EXPLICIT]. + + + The source of the geometry used when baking. See [enum SourceGeometryMode] for possible values. + + + + Parses mesh instances as obstruction geometry. This includes [Polygon2D], [MeshInstance2D], [MultiMeshInstance2D], and [TileMap] nodes. + Meshes are only parsed when they use a 2D vertices surface format. + + + Parses [StaticBody2D] and [TileMap] colliders as obstruction geometry. The collider should be in any of the layers specified by [member parsed_collision_mask]. + + + Both [constant PARSED_GEOMETRY_MESH_INSTANCES] and [constant PARSED_GEOMETRY_STATIC_COLLIDERS]. + + + Represents the size of the [enum ParsedGeometryType] enum. + + + Scans the child nodes of the root node recursively for geometry. + + + Scans nodes in a group and their child nodes recursively for geometry. The group is specified by [member source_geometry_group_name]. + + + Uses nodes in a group for geometry. The group is specified by [member source_geometry_group_name]. + + + Represents the size of the [enum SourceGeometryMode] enum. + + diff --git a/doc/classes/NavigationRegion2D.xml b/doc/classes/NavigationRegion2D.xml index acd789cc22d..e023d8dd7fb 100644 --- a/doc/classes/NavigationRegion2D.xml +++ b/doc/classes/NavigationRegion2D.xml @@ -16,6 +16,13 @@ $DOCS_URL/tutorials/navigation/navigation_using_navigationregions.html + + + + + Bakes the [NavigationPolygon]. If [param on_thread] is set to [code]true[/code] (default), the baking is done on a separate thread. + + @@ -93,4 +100,16 @@ If enabled the navigation region will use edge connections to connect with other navigation regions within proximity of the navigation map edge connection margin. + + + + Emitted when a navigation polygon bake operation is completed. + + + + + Emitted when the used navigation polygon is replaced or changes to the internals of the current navigation polygon are committed. + + + diff --git a/doc/classes/NavigationServer2D.xml b/doc/classes/NavigationServer2D.xml index a52c9abefdb..a25f048df3f 100644 --- a/doc/classes/NavigationServer2D.xml +++ b/doc/classes/NavigationServer2D.xml @@ -182,6 +182,24 @@ Replaces the internal velocity in the collision avoidance simulation with [param velocity] for the specified [param agent]. When an agent is teleported to a new position far away this function should be used in the same frame. If called frequently this function can get agents stuck. + + + + + + + Bakes the provided [param navigation_polygon] with the data from the provided [param source_geometry_data]. After the process is finished the optional [param callback] will be called. + + + + + + + + + Bakes the provided [param navigation_polygon] with the data from the provided [param source_geometry_data] as an async task running on a background thread. After the process is finished the optional [param callback] will be called. + + @@ -579,6 +597,18 @@ Sets the outline vertices for the obstacle. If the vertices are winded in clockwise order agents will be pushed in by the obstacle, else they will be pushed out. + + + + + + + + Parses the [SceneTree] for source geometry according to the properties of [param navigation_polygon]. Updates the provided [param source_geometry_data] resource with the resulting data. The resource can then be used to bake a navigation mesh with [method bake_from_source_geometry_data]. After the process is finished the optional [param callback] will be called. + [b]Note:[/b] This function needs to run on the main thread or with a deferred call as the SceneTree is not thread-safe. + [b]Performance:[/b] While convenient, reading data arrays from [Mesh] resources can affect the frame rate negatively. The data needs to be received from the GPU, stalling the [RenderingServer] in the process. For performance prefer the use of e.g. collision shapes or creating the data arrays entirely in code. + + diff --git a/editor/plugins/navigation_polygon_editor_plugin.cpp b/editor/plugins/navigation_polygon_editor_plugin.cpp index 957a520d8ac..48335f3b948 100644 --- a/editor/plugins/navigation_polygon_editor_plugin.cpp +++ b/editor/plugins/navigation_polygon_editor_plugin.cpp @@ -32,6 +32,8 @@ #include "editor/editor_node.h" #include "editor/editor_undo_redo_manager.h" +#include "scene/2d/navigation_region_2d.h" +#include "scene/gui/dialogs.h" Ref NavigationPolygonEditor::_ensure_navpoly() const { Ref navpoly = node->get_navigation_polygon(); @@ -71,7 +73,6 @@ Variant NavigationPolygonEditor::_get_polygon(int p_idx) const { void NavigationPolygonEditor::_set_polygon(int p_idx, const Variant &p_polygon) const { Ref navpoly = _ensure_navpoly(); navpoly->set_outline(p_idx, p_polygon); - navpoly->make_polygons_from_outlines(); } void NavigationPolygonEditor::_action_add_polygon(const Variant &p_polygon) { @@ -79,8 +80,6 @@ void NavigationPolygonEditor::_action_add_polygon(const Variant &p_polygon) { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->add_do_method(navpoly.ptr(), "add_outline", p_polygon); undo_redo->add_undo_method(navpoly.ptr(), "remove_outline", navpoly->get_outline_count()); - undo_redo->add_do_method(navpoly.ptr(), "make_polygons_from_outlines"); - undo_redo->add_undo_method(navpoly.ptr(), "make_polygons_from_outlines"); } void NavigationPolygonEditor::_action_remove_polygon(int p_idx) { @@ -88,8 +87,6 @@ void NavigationPolygonEditor::_action_remove_polygon(int p_idx) { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->add_do_method(navpoly.ptr(), "remove_outline", p_idx); undo_redo->add_undo_method(navpoly.ptr(), "add_outline_at_index", navpoly->get_outline(p_idx), p_idx); - undo_redo->add_do_method(navpoly.ptr(), "make_polygons_from_outlines"); - undo_redo->add_undo_method(navpoly.ptr(), "make_polygons_from_outlines"); } void NavigationPolygonEditor::_action_set_polygon(int p_idx, const Variant &p_previous, const Variant &p_polygon) { @@ -97,8 +94,6 @@ void NavigationPolygonEditor::_action_set_polygon(int p_idx, const Variant &p_pr EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->add_do_method(navpoly.ptr(), "set_outline", p_idx, p_polygon); undo_redo->add_undo_method(navpoly.ptr(), "set_outline", p_idx, p_previous); - undo_redo->add_do_method(navpoly.ptr(), "make_polygons_from_outlines"); - undo_redo->add_undo_method(navpoly.ptr(), "make_polygons_from_outlines"); } bool NavigationPolygonEditor::_has_resource() const { @@ -119,7 +114,84 @@ void NavigationPolygonEditor::_create_resource() { _menu_option(MODE_CREATE); } -NavigationPolygonEditor::NavigationPolygonEditor() {} +NavigationPolygonEditor::NavigationPolygonEditor() { + bake_hbox = memnew(HBoxContainer); + add_child(bake_hbox); + + button_bake = memnew(Button); + button_bake->set_flat(true); + bake_hbox->add_child(button_bake); + button_bake->set_toggle_mode(true); + button_bake->set_text(TTR("Bake NavigationPolygon")); + button_bake->set_tooltip_text(TTR("Bakes the NavigationPolygon by first parsing the scene for source geometry and then creating the navigation polygon vertices and polygons.")); + button_bake->connect("pressed", callable_mp(this, &NavigationPolygonEditor::_bake_pressed)); + + button_reset = memnew(Button); + button_reset->set_flat(true); + bake_hbox->add_child(button_reset); + button_reset->set_text(TTR("Clear NavigationPolygon")); + button_reset->set_tooltip_text(TTR("Clears the internal NavigationPolygon outlines, vertices and polygons.")); + button_reset->connect("pressed", callable_mp(this, &NavigationPolygonEditor::_clear_pressed)); + + bake_info = memnew(Label); + bake_hbox->add_child(bake_info); + + err_dialog = memnew(AcceptDialog); + add_child(err_dialog); + node = nullptr; +} + +void NavigationPolygonEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + button_bake->set_icon(get_theme_icon(SNAME("Bake"), SNAME("EditorIcons"))); + button_reset->set_icon(get_theme_icon(SNAME("Reload"), SNAME("EditorIcons"))); + } break; + } +} + +void NavigationPolygonEditor::_bake_pressed() { + button_bake->set_pressed(false); + + ERR_FAIL_NULL(node); + Ref navigation_polygon = node->get_navigation_polygon(); + if (!navigation_polygon.is_valid()) { + err_dialog->set_text(TTR("A NavigationPolygon resource must be set or created for this node to work.")); + err_dialog->popup_centered(); + return; + } + + node->bake_navigation_polygon(true); + + node->queue_redraw(); +} + +void NavigationPolygonEditor::_clear_pressed() { + if (node) { + if (node->get_navigation_polygon().is_valid()) { + node->get_navigation_polygon()->clear(); + } + } + + button_bake->set_pressed(false); + bake_info->set_text(""); + + if (node) { + node->queue_redraw(); + } +} + +void NavigationPolygonEditor::_update_polygon_editing_state() { + if (!_get_node()) { + return; + } + + if (node != nullptr && node->get_navigation_polygon().is_valid()) { + bake_hbox->show(); + } else { + bake_hbox->hide(); + } +} NavigationPolygonEditorPlugin::NavigationPolygonEditorPlugin() : AbstractPolygon2DEditorPlugin(memnew(NavigationPolygonEditor), "NavigationRegion2D") { diff --git a/editor/plugins/navigation_polygon_editor_plugin.h b/editor/plugins/navigation_polygon_editor_plugin.h index f43c052dd36..f1d0cd87517 100644 --- a/editor/plugins/navigation_polygon_editor_plugin.h +++ b/editor/plugins/navigation_polygon_editor_plugin.h @@ -32,16 +32,38 @@ #define NAVIGATION_POLYGON_EDITOR_PLUGIN_H #include "editor/plugins/abstract_polygon_2d_editor.h" -#include "scene/2d/navigation_region_2d.h" + +#include "editor/editor_plugin.h" + +class AcceptDialog; +class HBoxContainer; +class NavigationPolygon; +class NavigationRegion2D; class NavigationPolygonEditor : public AbstractPolygon2DEditor { + friend class NavigationPolygonEditorPlugin; + GDCLASS(NavigationPolygonEditor, AbstractPolygon2DEditor); NavigationRegion2D *node = nullptr; Ref _ensure_navpoly() const; + AcceptDialog *err_dialog = nullptr; + + HBoxContainer *bake_hbox = nullptr; + Button *button_bake = nullptr; + Button *button_reset = nullptr; + Label *bake_info = nullptr; + + void _bake_pressed(); + void _clear_pressed(); + + void _update_polygon_editing_state(); + protected: + void _notification(int p_what); + virtual Node2D *_get_node() const override; virtual void _set_node(Node *p_polygon) override; @@ -63,6 +85,8 @@ public: class NavigationPolygonEditorPlugin : public AbstractPolygon2DEditorPlugin { GDCLASS(NavigationPolygonEditorPlugin, AbstractPolygon2DEditorPlugin); + NavigationPolygonEditor *navigation_polygon_editor = nullptr; + public: NavigationPolygonEditorPlugin(); }; diff --git a/main/main.cpp b/main/main.cpp index cd9bd6d1d34..2b2ed3d6ece 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -70,6 +70,7 @@ #include "servers/movie_writer/movie_writer.h" #include "servers/movie_writer/movie_writer_mjpeg.h" #include "servers/navigation_server_2d.h" +#include "servers/navigation_server_2d_dummy.h" #include "servers/navigation_server_3d.h" #include "servers/navigation_server_3d_dummy.h" #include "servers/physics_server_2d.h" @@ -339,8 +340,14 @@ void initialize_navigation_server() { navigation_server_3d->init(); // Init 2D Navigation Server - navigation_server_2d = memnew(NavigationServer2D); + navigation_server_2d = NavigationServer2DManager::new_default_server(); + if (!navigation_server_2d) { + WARN_PRINT_ONCE("No NavigationServer2D implementation has been registered! Falling back to a dummy implementation: navigation features will be unavailable."); + navigation_server_2d = memnew(NavigationServer2DDummy); + } + ERR_FAIL_NULL_MSG(navigation_server_2d, "Failed to initialize NavigationServer2D."); + navigation_server_2d->init(); } void finalize_navigation_server() { @@ -350,6 +357,7 @@ void finalize_navigation_server() { navigation_server_3d = nullptr; ERR_FAIL_NULL(navigation_server_2d); + navigation_server_2d->finish(); memdelete(navigation_server_2d); navigation_server_2d = nullptr; } @@ -3490,6 +3498,9 @@ bool Main::iteration() { // process all our active interfaces XRServer::get_singleton()->_process(); + NavigationServer2D::get_singleton()->sync(); + NavigationServer3D::get_singleton()->sync(); + for (int iters = 0; iters < advance.physics_steps; ++iters) { if (Input::get_singleton()->is_using_input_buffering() && agile_input_event_flushing) { Input::get_singleton()->flush_buffered_events(); diff --git a/modules/navigation/config.py b/modules/navigation/config.py index d22f9454ed2..a42f27fbe12 100644 --- a/modules/navigation/config.py +++ b/modules/navigation/config.py @@ -1,5 +1,5 @@ def can_build(env, platform): - return True + return not env["disable_3d"] def configure(env): diff --git a/modules/navigation/godot_navigation_server.cpp b/modules/navigation/godot_navigation_server.cpp index 9162fcf171a..32482e0c9d0 100644 --- a/modules/navigation/godot_navigation_server.cpp +++ b/modules/navigation/godot_navigation_server.cpp @@ -1086,6 +1086,14 @@ void GodotNavigationServer::map_force_update(RID p_map) { map->sync(); } +void GodotNavigationServer::sync() { +#ifndef _3D_DISABLED + if (navmesh_generator_3d) { + navmesh_generator_3d->sync(); + } +#endif // _3D_DISABLED +} + void GodotNavigationServer::process(real_t p_delta_time) { flush_queries(); @@ -1093,16 +1101,6 @@ void GodotNavigationServer::process(real_t p_delta_time) { return; } -#ifndef _3D_DISABLED - // Sync finished navmesh bakes before doing NavMap updates. - if (navmesh_generator_3d) { - navmesh_generator_3d->sync(); - // Finished bakes emit callbacks and users might have reacted to those. - // Flush queue again so users do not have to wait for the next sync. - flush_queries(); - } -#endif // _3D_DISABLED - int _new_pm_region_count = 0; int _new_pm_agent_count = 0; int _new_pm_link_count = 0; diff --git a/modules/navigation/godot_navigation_server.h b/modules/navigation/godot_navigation_server.h index c12605bc7af..4ead4fc398b 100644 --- a/modules/navigation/godot_navigation_server.h +++ b/modules/navigation/godot_navigation_server.h @@ -243,6 +243,7 @@ public: void flush_queries(); virtual void process(real_t p_delta_time) override; virtual void init() override; + virtual void sync() override; virtual void finish() override; virtual NavigationUtilities::PathQueryResult _query_path(const NavigationUtilities::PathQueryParameters &p_parameters) const override; diff --git a/modules/navigation/godot_navigation_server_2d.cpp b/modules/navigation/godot_navigation_server_2d.cpp new file mode 100644 index 00000000000..b54729e06fc --- /dev/null +++ b/modules/navigation/godot_navigation_server_2d.cpp @@ -0,0 +1,369 @@ +/**************************************************************************/ +/* godot_navigation_server_2d.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "godot_navigation_server_2d.h" + +#ifdef CLIPPER2_ENABLED +#include "nav_mesh_generator_2d.h" +#endif // CLIPPER2_ENABLED + +#include "servers/navigation_server_3d.h" + +#define FORWARD_0(FUNC_NAME) \ + GodotNavigationServer2D::FUNC_NAME() { \ + return NavigationServer3D::get_singleton()->FUNC_NAME(); \ + } + +#define FORWARD_0_C(FUNC_NAME) \ + GodotNavigationServer2D::FUNC_NAME() \ + const { \ + return NavigationServer3D::get_singleton()->FUNC_NAME(); \ + } + +#define FORWARD_1(FUNC_NAME, T_0, D_0, CONV_0) \ + GodotNavigationServer2D::FUNC_NAME(T_0 D_0) { \ + return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0)); \ + } + +#define FORWARD_1_C(FUNC_NAME, T_0, D_0, CONV_0) \ + GodotNavigationServer2D::FUNC_NAME(T_0 D_0) \ + const { \ + return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0)); \ + } + +#define FORWARD_1_R_C(CONV_R, FUNC_NAME, T_0, D_0, CONV_0) \ + GodotNavigationServer2D::FUNC_NAME(T_0 D_0) \ + const { \ + return CONV_R(NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0))); \ + } + +#define FORWARD_2(FUNC_NAME, T_0, D_0, T_1, D_1, CONV_0, CONV_1) \ + GodotNavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1) { \ + return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1)); \ + } + +#define FORWARD_2_C(FUNC_NAME, T_0, D_0, T_1, D_1, CONV_0, CONV_1) \ + GodotNavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1) \ + const { \ + return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1)); \ + } + +#define FORWARD_2_R_C(CONV_R, FUNC_NAME, T_0, D_0, T_1, D_1, CONV_0, CONV_1) \ + GodotNavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1) \ + const { \ + return CONV_R(NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1))); \ + } + +#define FORWARD_5_R_C(CONV_R, FUNC_NAME, T_0, D_0, T_1, D_1, T_2, D_2, T_3, D_3, T_4, D_4, CONV_0, CONV_1, CONV_2, CONV_3, CONV_4) \ + GodotNavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3, T_4 D_4) \ + const { \ + return CONV_R(NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1), CONV_2(D_2), CONV_3(D_3), CONV_4(D_4))); \ + } + +static RID rid_to_rid(const RID d) { + return d; +} + +static bool bool_to_bool(const bool d) { + return d; +} + +static int int_to_int(const int d) { + return d; +} + +static uint32_t uint32_to_uint32(const uint32_t d) { + return d; +} + +static real_t real_to_real(const real_t d) { + return d; +} + +static Vector3 v2_to_v3(const Vector2 d) { + return Vector3(d.x, 0.0, d.y); +} + +static Vector2 v3_to_v2(const Vector3 &d) { + return Vector2(d.x, d.z); +} + +static Vector vector_v2_to_v3(const Vector &d) { + Vector nd; + nd.resize(d.size()); + for (int i(0); i < nd.size(); i++) { + nd.write[i] = v2_to_v3(d[i]); + } + return nd; +} + +static Vector vector_v3_to_v2(const Vector &d) { + Vector nd; + nd.resize(d.size()); + for (int i(0); i < nd.size(); i++) { + nd.write[i] = v3_to_v2(d[i]); + } + return nd; +} + +static Transform3D trf2_to_trf3(const Transform2D &d) { + Vector3 o(v2_to_v3(d.get_origin())); + Basis b; + b.rotate(Vector3(0, -1, 0), d.get_rotation()); + b.scale(v2_to_v3(d.get_scale())); + return Transform3D(b, o); +} + +static ObjectID id_to_id(const ObjectID &id) { + return id; +} + +static Callable callable_to_callable(const Callable &c) { + return c; +} + +static Ref poly_to_mesh(Ref d) { + if (d.is_valid()) { + return d->get_navigation_mesh(); + } else { + return Ref(); + } +} + +void GodotNavigationServer2D::init() { +#ifdef CLIPPER2_ENABLED + navmesh_generator_2d = memnew(NavMeshGenerator2D); + ERR_FAIL_NULL_MSG(navmesh_generator_2d, "Failed to init NavMeshGenerator2D."); +#endif // CLIPPER2_ENABLED +} + +void GodotNavigationServer2D::sync() { +#ifdef CLIPPER2_ENABLED + if (navmesh_generator_2d) { + navmesh_generator_2d->sync(); + } +#endif // CLIPPER2_ENABLED +} + +void GodotNavigationServer2D::finish() { +#ifdef CLIPPER2_ENABLED + if (navmesh_generator_2d) { + navmesh_generator_2d->finish(); + memdelete(navmesh_generator_2d); + navmesh_generator_2d = nullptr; + } +#endif // CLIPPER2_ENABLED +} + +void GodotNavigationServer2D::parse_source_geometry_data(const Ref &p_navigation_mesh, const Ref &p_source_geometry_data, Node *p_root_node, const Callable &p_callback) { + ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "The SceneTree can only be parsed on the main thread. Call this function from the main thread or use call_deferred()."); + ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation polygon."); + ERR_FAIL_NULL_MSG(p_root_node, "No parsing root node specified."); + ERR_FAIL_COND_MSG(!p_root_node->is_inside_tree(), "The root node needs to be inside the SceneTree."); + +#ifdef CLIPPER2_ENABLED + ERR_FAIL_NULL(NavMeshGenerator2D::get_singleton()); + NavMeshGenerator2D::get_singleton()->parse_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_root_node, p_callback); +#endif // CLIPPER2_ENABLED +} + +void GodotNavigationServer2D::bake_from_source_geometry_data(const Ref &p_navigation_mesh, const Ref &p_source_geometry_data, const Callable &p_callback) { + ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation polygon."); + ERR_FAIL_COND_MSG(!p_source_geometry_data.is_valid(), "Invalid NavigationMeshSourceGeometryData2D."); + +#ifdef CLIPPER2_ENABLED + ERR_FAIL_NULL(NavMeshGenerator2D::get_singleton()); + NavMeshGenerator2D::get_singleton()->bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_callback); +#endif // CLIPPER2_ENABLED +} + +void GodotNavigationServer2D::bake_from_source_geometry_data_async(const Ref &p_navigation_mesh, const Ref &p_source_geometry_data, const Callable &p_callback) { + ERR_FAIL_COND_MSG(!p_navigation_mesh.is_valid(), "Invalid navigation mesh."); + ERR_FAIL_COND_MSG(!p_source_geometry_data.is_valid(), "Invalid NavigationMeshSourceGeometryData2D."); + +#ifdef CLIPPER2_ENABLED + ERR_FAIL_NULL(NavMeshGenerator2D::get_singleton()); + NavMeshGenerator2D::get_singleton()->bake_from_source_geometry_data_async(p_navigation_mesh, p_source_geometry_data, p_callback); +#endif // CLIPPER2_ENABLED +} + +GodotNavigationServer2D::GodotNavigationServer2D() {} + +GodotNavigationServer2D::~GodotNavigationServer2D() {} + +TypedArray FORWARD_0_C(get_maps); + +TypedArray FORWARD_1_C(map_get_links, RID, p_map, rid_to_rid); + +TypedArray FORWARD_1_C(map_get_regions, RID, p_map, rid_to_rid); + +TypedArray FORWARD_1_C(map_get_agents, RID, p_map, rid_to_rid); + +TypedArray FORWARD_1_C(map_get_obstacles, RID, p_map, rid_to_rid); + +RID FORWARD_1_C(region_get_map, RID, p_region, rid_to_rid); + +RID FORWARD_1_C(agent_get_map, RID, p_agent, rid_to_rid); + +RID FORWARD_0(map_create); + +void FORWARD_2(map_set_active, RID, p_map, bool, p_active, rid_to_rid, bool_to_bool); + +bool FORWARD_1_C(map_is_active, RID, p_map, rid_to_rid); + +void GodotNavigationServer2D::map_force_update(RID p_map) { + NavigationServer3D::get_singleton()->map_force_update(p_map); +} + +void FORWARD_2(map_set_cell_size, RID, p_map, real_t, p_cell_size, rid_to_rid, real_to_real); +real_t FORWARD_1_C(map_get_cell_size, RID, p_map, rid_to_rid); + +void FORWARD_2(map_set_use_edge_connections, RID, p_map, bool, p_enabled, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(map_get_use_edge_connections, RID, p_map, rid_to_rid); + +void FORWARD_2(map_set_edge_connection_margin, RID, p_map, real_t, p_connection_margin, rid_to_rid, real_to_real); +real_t FORWARD_1_C(map_get_edge_connection_margin, RID, p_map, rid_to_rid); + +void FORWARD_2(map_set_link_connection_radius, RID, p_map, real_t, p_connection_radius, rid_to_rid, real_to_real); +real_t FORWARD_1_C(map_get_link_connection_radius, RID, p_map, rid_to_rid); + +Vector FORWARD_5_R_C(vector_v3_to_v2, map_get_path, RID, p_map, Vector2, p_origin, Vector2, p_destination, bool, p_optimize, uint32_t, p_layers, rid_to_rid, v2_to_v3, v2_to_v3, bool_to_bool, uint32_to_uint32); + +Vector2 FORWARD_2_R_C(v3_to_v2, map_get_closest_point, RID, p_map, const Vector2 &, p_point, rid_to_rid, v2_to_v3); +RID FORWARD_2_C(map_get_closest_point_owner, RID, p_map, const Vector2 &, p_point, rid_to_rid, v2_to_v3); + +RID FORWARD_0(region_create); + +void FORWARD_2(region_set_enabled, RID, p_region, bool, p_enabled, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(region_get_enabled, RID, p_region, rid_to_rid); +void FORWARD_2(region_set_use_edge_connections, RID, p_region, bool, p_enabled, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(region_get_use_edge_connections, RID, p_region, rid_to_rid); + +void FORWARD_2(region_set_enter_cost, RID, p_region, real_t, p_enter_cost, rid_to_rid, real_to_real); +real_t FORWARD_1_C(region_get_enter_cost, RID, p_region, rid_to_rid); +void FORWARD_2(region_set_travel_cost, RID, p_region, real_t, p_travel_cost, rid_to_rid, real_to_real); +real_t FORWARD_1_C(region_get_travel_cost, RID, p_region, rid_to_rid); +void FORWARD_2(region_set_owner_id, RID, p_region, ObjectID, p_owner_id, rid_to_rid, id_to_id); +ObjectID FORWARD_1_C(region_get_owner_id, RID, p_region, rid_to_rid); +bool FORWARD_2_C(region_owns_point, RID, p_region, const Vector2 &, p_point, rid_to_rid, v2_to_v3); + +void FORWARD_2(region_set_map, RID, p_region, RID, p_map, rid_to_rid, rid_to_rid); +void FORWARD_2(region_set_navigation_layers, RID, p_region, uint32_t, p_navigation_layers, rid_to_rid, uint32_to_uint32); +uint32_t FORWARD_1_C(region_get_navigation_layers, RID, p_region, rid_to_rid); +void FORWARD_2(region_set_transform, RID, p_region, Transform2D, p_transform, rid_to_rid, trf2_to_trf3); + +void GodotNavigationServer2D::region_set_navigation_polygon(RID p_region, Ref p_navigation_polygon) { + NavigationServer3D::get_singleton()->region_set_navigation_mesh(p_region, poly_to_mesh(p_navigation_polygon)); +} + +int FORWARD_1_C(region_get_connections_count, RID, p_region, rid_to_rid); +Vector2 FORWARD_2_R_C(v3_to_v2, region_get_connection_pathway_start, RID, p_region, int, p_connection_id, rid_to_rid, int_to_int); +Vector2 FORWARD_2_R_C(v3_to_v2, region_get_connection_pathway_end, RID, p_region, int, p_connection_id, rid_to_rid, int_to_int); + +RID FORWARD_0(link_create); + +void FORWARD_2(link_set_map, RID, p_link, RID, p_map, rid_to_rid, rid_to_rid); +RID FORWARD_1_C(link_get_map, RID, p_link, rid_to_rid); +void FORWARD_2(link_set_enabled, RID, p_link, bool, p_enabled, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(link_get_enabled, RID, p_link, rid_to_rid); +void FORWARD_2(link_set_bidirectional, RID, p_link, bool, p_bidirectional, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(link_is_bidirectional, RID, p_link, rid_to_rid); +void FORWARD_2(link_set_navigation_layers, RID, p_link, uint32_t, p_navigation_layers, rid_to_rid, uint32_to_uint32); +uint32_t FORWARD_1_C(link_get_navigation_layers, RID, p_link, rid_to_rid); +void FORWARD_2(link_set_start_position, RID, p_link, Vector2, p_position, rid_to_rid, v2_to_v3); +Vector2 FORWARD_1_R_C(v3_to_v2, link_get_start_position, RID, p_link, rid_to_rid); +void FORWARD_2(link_set_end_position, RID, p_link, Vector2, p_position, rid_to_rid, v2_to_v3); +Vector2 FORWARD_1_R_C(v3_to_v2, link_get_end_position, RID, p_link, rid_to_rid); +void FORWARD_2(link_set_enter_cost, RID, p_link, real_t, p_enter_cost, rid_to_rid, real_to_real); +real_t FORWARD_1_C(link_get_enter_cost, RID, p_link, rid_to_rid); +void FORWARD_2(link_set_travel_cost, RID, p_link, real_t, p_travel_cost, rid_to_rid, real_to_real); +real_t FORWARD_1_C(link_get_travel_cost, RID, p_link, rid_to_rid); +void FORWARD_2(link_set_owner_id, RID, p_link, ObjectID, p_owner_id, rid_to_rid, id_to_id); +ObjectID FORWARD_1_C(link_get_owner_id, RID, p_link, rid_to_rid); + +RID GodotNavigationServer2D::agent_create() { + RID agent = NavigationServer3D::get_singleton()->agent_create(); + return agent; +} + +void FORWARD_2(agent_set_avoidance_enabled, RID, p_agent, bool, p_enabled, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(agent_get_avoidance_enabled, RID, p_agent, rid_to_rid); +void FORWARD_2(agent_set_map, RID, p_agent, RID, p_map, rid_to_rid, rid_to_rid); +void FORWARD_2(agent_set_neighbor_distance, RID, p_agent, real_t, p_dist, rid_to_rid, real_to_real); +void FORWARD_2(agent_set_max_neighbors, RID, p_agent, int, p_count, rid_to_rid, int_to_int); +void FORWARD_2(agent_set_time_horizon_agents, RID, p_agent, real_t, p_time_horizon, rid_to_rid, real_to_real); +void FORWARD_2(agent_set_time_horizon_obstacles, RID, p_agent, real_t, p_time_horizon, rid_to_rid, real_to_real); +void FORWARD_2(agent_set_radius, RID, p_agent, real_t, p_radius, rid_to_rid, real_to_real); +void FORWARD_2(agent_set_max_speed, RID, p_agent, real_t, p_max_speed, rid_to_rid, real_to_real); +void FORWARD_2(agent_set_velocity_forced, RID, p_agent, Vector2, p_velocity, rid_to_rid, v2_to_v3); +void FORWARD_2(agent_set_velocity, RID, p_agent, Vector2, p_velocity, rid_to_rid, v2_to_v3); +void FORWARD_2(agent_set_position, RID, p_agent, Vector2, p_position, rid_to_rid, v2_to_v3); + +bool FORWARD_1_C(agent_is_map_changed, RID, p_agent, rid_to_rid); +void FORWARD_2(agent_set_paused, RID, p_agent, bool, p_paused, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(agent_get_paused, RID, p_agent, rid_to_rid); + +void FORWARD_1(free, RID, p_object, rid_to_rid); +void FORWARD_2(agent_set_avoidance_callback, RID, p_agent, Callable, p_callback, rid_to_rid, callable_to_callable); + +void FORWARD_2(agent_set_avoidance_layers, RID, p_agent, uint32_t, p_layers, rid_to_rid, uint32_to_uint32); +void FORWARD_2(agent_set_avoidance_mask, RID, p_agent, uint32_t, p_mask, rid_to_rid, uint32_to_uint32); +void FORWARD_2(agent_set_avoidance_priority, RID, p_agent, real_t, p_priority, rid_to_rid, real_to_real); + +RID GodotNavigationServer2D::obstacle_create() { + RID obstacle = NavigationServer3D::get_singleton()->obstacle_create(); + return obstacle; +} +void FORWARD_2(obstacle_set_avoidance_enabled, RID, p_obstacle, bool, p_enabled, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(obstacle_get_avoidance_enabled, RID, p_obstacle, rid_to_rid); +void FORWARD_2(obstacle_set_map, RID, p_obstacle, RID, p_map, rid_to_rid, rid_to_rid); +RID FORWARD_1_C(obstacle_get_map, RID, p_obstacle, rid_to_rid); +void FORWARD_2(obstacle_set_paused, RID, p_obstacle, bool, p_paused, rid_to_rid, bool_to_bool); +bool FORWARD_1_C(obstacle_get_paused, RID, p_obstacle, rid_to_rid); +void FORWARD_2(obstacle_set_radius, RID, p_obstacle, real_t, p_radius, rid_to_rid, real_to_real); +void FORWARD_2(obstacle_set_velocity, RID, p_obstacle, Vector2, p_velocity, rid_to_rid, v2_to_v3); +void FORWARD_2(obstacle_set_position, RID, p_obstacle, Vector2, p_position, rid_to_rid, v2_to_v3); +void FORWARD_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers, rid_to_rid, uint32_to_uint32); + +void GodotNavigationServer2D::obstacle_set_vertices(RID p_obstacle, const Vector &p_vertices) { + NavigationServer3D::get_singleton()->obstacle_set_vertices(p_obstacle, vector_v2_to_v3(p_vertices)); +} + +void GodotNavigationServer2D::query_path(const Ref &p_query_parameters, Ref p_query_result) const { + ERR_FAIL_COND(!p_query_parameters.is_valid()); + ERR_FAIL_COND(!p_query_result.is_valid()); + + const NavigationUtilities::PathQueryResult _query_result = NavigationServer3D::get_singleton()->_query_path(p_query_parameters->get_parameters()); + + p_query_result->set_path(vector_v3_to_v2(_query_result.path)); + p_query_result->set_path_types(_query_result.path_types); + p_query_result->set_path_rids(_query_result.path_rids); + p_query_result->set_path_owner_ids(_query_result.path_owner_ids); +} diff --git a/modules/navigation/godot_navigation_server_2d.h b/modules/navigation/godot_navigation_server_2d.h new file mode 100644 index 00000000000..337f5f40d8f --- /dev/null +++ b/modules/navigation/godot_navigation_server_2d.h @@ -0,0 +1,234 @@ +/**************************************************************************/ +/* godot_navigation_server_2d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef GODOT_NAVIGATION_SERVER_2D_H +#define GODOT_NAVIGATION_SERVER_2D_H + +#include "nav_agent.h" +#include "nav_link.h" +#include "nav_map.h" +#include "nav_obstacle.h" +#include "nav_region.h" + +#include "servers/navigation_server_2d.h" + +#ifdef CLIPPER2_ENABLED +class NavMeshGenerator2D; +#endif // CLIPPER2_ENABLED + +// This server exposes the `NavigationServer3D` features in the 2D world. +class GodotNavigationServer2D : public NavigationServer2D { + GDCLASS(GodotNavigationServer2D, NavigationServer2D); + +#ifdef CLIPPER2_ENABLED + NavMeshGenerator2D *navmesh_generator_2d = nullptr; +#endif // CLIPPER2_ENABLED + +public: + GodotNavigationServer2D(); + virtual ~GodotNavigationServer2D(); + + virtual TypedArray get_maps() const override; + + virtual RID map_create() override; + virtual void map_set_active(RID p_map, bool p_active) override; + virtual bool map_is_active(RID p_map) const override; + virtual void map_set_cell_size(RID p_map, real_t p_cell_size) override; + virtual real_t map_get_cell_size(RID p_map) const override; + virtual void map_set_use_edge_connections(RID p_map, bool p_enabled) override; + virtual bool map_get_use_edge_connections(RID p_map) const override; + virtual void map_set_edge_connection_margin(RID p_map, real_t p_connection_margin) override; + virtual real_t map_get_edge_connection_margin(RID p_map) const override; + virtual void map_set_link_connection_radius(RID p_map, real_t p_connection_radius) override; + virtual real_t map_get_link_connection_radius(RID p_map) const override; + virtual Vector map_get_path(RID p_map, Vector2 p_origin, Vector2 p_destination, bool p_optimize, uint32_t p_navigation_layers = 1) const override; + virtual Vector2 map_get_closest_point(RID p_map, const Vector2 &p_point) const override; + virtual RID map_get_closest_point_owner(RID p_map, const Vector2 &p_point) const override; + virtual TypedArray map_get_links(RID p_map) const override; + virtual TypedArray map_get_regions(RID p_map) const override; + virtual TypedArray map_get_agents(RID p_map) const override; + virtual TypedArray map_get_obstacles(RID p_map) const override; + virtual void map_force_update(RID p_map) override; + + virtual RID region_create() override; + virtual void region_set_enabled(RID p_region, bool p_enabled) override; + virtual bool region_get_enabled(RID p_region) const override; + virtual void region_set_use_edge_connections(RID p_region, bool p_enabled) override; + virtual bool region_get_use_edge_connections(RID p_region) const override; + virtual void region_set_enter_cost(RID p_region, real_t p_enter_cost) override; + virtual real_t region_get_enter_cost(RID p_region) const override; + virtual void region_set_travel_cost(RID p_region, real_t p_travel_cost) override; + virtual real_t region_get_travel_cost(RID p_region) const override; + virtual void region_set_owner_id(RID p_region, ObjectID p_owner_id) override; + virtual ObjectID region_get_owner_id(RID p_region) const override; + virtual bool region_owns_point(RID p_region, const Vector2 &p_point) const override; + virtual void region_set_map(RID p_region, RID p_map) override; + virtual RID region_get_map(RID p_region) const override; + virtual void region_set_navigation_layers(RID p_region, uint32_t p_navigation_layers) override; + virtual uint32_t region_get_navigation_layers(RID p_region) const override; + virtual void region_set_transform(RID p_region, Transform2D p_transform) override; + virtual void region_set_navigation_polygon(RID p_region, Ref p_navigation_polygon) override; + virtual int region_get_connections_count(RID p_region) const override; + virtual Vector2 region_get_connection_pathway_start(RID p_region, int p_connection_id) const override; + virtual Vector2 region_get_connection_pathway_end(RID p_region, int p_connection_id) const override; + + virtual RID link_create() override; + + /// Set the map of this link. + virtual void link_set_map(RID p_link, RID p_map) override; + virtual RID link_get_map(RID p_link) const override; + + virtual void link_set_enabled(RID p_link, bool p_enabled) override; + virtual bool link_get_enabled(RID p_link) const override; + + /// Set whether this link travels in both directions. + virtual void link_set_bidirectional(RID p_link, bool p_bidirectional) override; + virtual bool link_is_bidirectional(RID p_link) const override; + + /// Set the link's layers. + virtual void link_set_navigation_layers(RID p_link, uint32_t p_navigation_layers) override; + virtual uint32_t link_get_navigation_layers(RID p_link) const override; + + /// Set the start position of the link. + virtual void link_set_start_position(RID p_link, Vector2 p_position) override; + virtual Vector2 link_get_start_position(RID p_link) const override; + + /// Set the end position of the link. + virtual void link_set_end_position(RID p_link, Vector2 p_position) override; + virtual Vector2 link_get_end_position(RID p_link) const override; + + /// Set the enter cost of the link. + virtual void link_set_enter_cost(RID p_link, real_t p_enter_cost) override; + virtual real_t link_get_enter_cost(RID p_link) const override; + + /// Set the travel cost of the link. + virtual void link_set_travel_cost(RID p_link, real_t p_travel_cost) override; + virtual real_t link_get_travel_cost(RID p_link) const override; + + /// Set the node which manages this link. + virtual void link_set_owner_id(RID p_link, ObjectID p_owner_id) override; + virtual ObjectID link_get_owner_id(RID p_link) const override; + + /// Creates the agent. + virtual RID agent_create() override; + + /// Put the agent in the map. + virtual void agent_set_map(RID p_agent, RID p_map) override; + virtual RID agent_get_map(RID p_agent) const override; + + virtual void agent_set_paused(RID p_agent, bool p_paused) override; + virtual bool agent_get_paused(RID p_agent) const override; + + virtual void agent_set_avoidance_enabled(RID p_agent, bool p_enabled) override; + virtual bool agent_get_avoidance_enabled(RID p_agent) const override; + + /// The maximum distance (center point to + /// center point) to other agents this agent + /// takes into account in the navigation. The + /// larger this number, the longer the running + /// time of the simulation. If the number is too + /// low, the simulation will not be safe. + /// Must be non-negative. + virtual void agent_set_neighbor_distance(RID p_agent, real_t p_distance) override; + + /// The maximum number of other agents this + /// agent takes into account in the navigation. + /// The larger this number, the longer the + /// running time of the simulation. If the + /// number is too low, the simulation will not + /// be safe. + virtual void agent_set_max_neighbors(RID p_agent, int p_count) override; + + /// The minimal amount of time for which this + /// agent's velocities that are computed by the + /// simulation are safe with respect to other + /// agents. The larger this number, the sooner + /// this agent will respond to the presence of + /// other agents, but the less freedom this + /// agent has in choosing its velocities. + /// Must be positive. + + virtual void agent_set_time_horizon_agents(RID p_agent, real_t p_time_horizon) override; + virtual void agent_set_time_horizon_obstacles(RID p_agent, real_t p_time_horizon) override; + + /// The radius of this agent. + /// Must be non-negative. + virtual void agent_set_radius(RID p_agent, real_t p_radius) override; + + /// The maximum speed of this agent. + /// Must be non-negative. + virtual void agent_set_max_speed(RID p_agent, real_t p_max_speed) override; + + /// forces and agent velocity change in the avoidance simulation, adds simulation instability if done recklessly + virtual void agent_set_velocity_forced(RID p_agent, Vector2 p_velocity) override; + + /// The wanted velocity for the agent as a "suggestion" to the avoidance simulation. + /// The simulation will try to fulfill this velocity wish if possible but may change the velocity depending on other agent's and obstacles'. + virtual void agent_set_velocity(RID p_agent, Vector2 p_velocity) override; + + /// Position of the agent in world space. + virtual void agent_set_position(RID p_agent, Vector2 p_position) override; + + /// Returns true if the map got changed the previous frame. + virtual bool agent_is_map_changed(RID p_agent) const override; + + /// Callback called at the end of the RVO process + virtual void agent_set_avoidance_callback(RID p_agent, Callable p_callback) override; + + virtual void agent_set_avoidance_layers(RID p_agent, uint32_t p_layers) override; + virtual void agent_set_avoidance_mask(RID p_agent, uint32_t p_mask) override; + virtual void agent_set_avoidance_priority(RID p_agent, real_t p_priority) override; + + virtual RID obstacle_create() override; + virtual void obstacle_set_avoidance_enabled(RID p_obstacle, bool p_enabled) override; + virtual bool obstacle_get_avoidance_enabled(RID p_obstacle) const override; + virtual void obstacle_set_map(RID p_obstacle, RID p_map) override; + virtual RID obstacle_get_map(RID p_obstacle) const override; + virtual void obstacle_set_paused(RID p_obstacle, bool p_paused) override; + virtual bool obstacle_get_paused(RID p_obstacle) const override; + virtual void obstacle_set_radius(RID p_obstacle, real_t p_radius) override; + virtual void obstacle_set_velocity(RID p_obstacle, Vector2 p_velocity) override; + virtual void obstacle_set_position(RID p_obstacle, Vector2 p_position) override; + virtual void obstacle_set_vertices(RID p_obstacle, const Vector &p_vertices) override; + virtual void obstacle_set_avoidance_layers(RID p_obstacle, uint32_t p_layers) override; + + virtual void query_path(const Ref &p_query_parameters, Ref p_query_result) const override; + + virtual void init() override; + virtual void sync() override; + virtual void finish() override; + virtual void free(RID p_object) override; + + virtual void parse_source_geometry_data(const Ref &p_navigation_mesh, const Ref &p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) override; + virtual void bake_from_source_geometry_data(const Ref &p_navigation_mesh, const Ref &p_source_geometry_data, const Callable &p_callback = Callable()) override; + virtual void bake_from_source_geometry_data_async(const Ref &p_navigation_mesh, const Ref &p_source_geometry_data, const Callable &p_callback = Callable()) override; +}; + +#endif // GODOT_NAVIGATION_SERVER_2D_H diff --git a/modules/navigation/nav_mesh_generator_2d.cpp b/modules/navigation/nav_mesh_generator_2d.cpp new file mode 100644 index 00000000000..0c9f7e80fbb --- /dev/null +++ b/modules/navigation/nav_mesh_generator_2d.cpp @@ -0,0 +1,830 @@ +/**************************************************************************/ +/* nav_mesh_generator_2d.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "nav_mesh_generator_2d.h" + +#include "core/config/project_settings.h" +#include "scene/2d/mesh_instance_2d.h" +#include "scene/2d/multimesh_instance_2d.h" +#include "scene/2d/physics_body_2d.h" +#include "scene/2d/polygon_2d.h" +#include "scene/2d/tile_map.h" +#include "scene/resources/capsule_shape_2d.h" +#include "scene/resources/circle_shape_2d.h" +#include "scene/resources/concave_polygon_shape_2d.h" +#include "scene/resources/convex_polygon_shape_2d.h" +#include "scene/resources/navigation_mesh_source_geometry_data_2d.h" +#include "scene/resources/navigation_polygon.h" +#include "scene/resources/rectangle_shape_2d.h" + +#include "thirdparty/clipper2/include/clipper2/clipper.h" +#include "thirdparty/misc/polypartition.h" + +NavMeshGenerator2D *NavMeshGenerator2D::singleton = nullptr; +Mutex NavMeshGenerator2D::baking_navmesh_mutex; +Mutex NavMeshGenerator2D::generator_task_mutex; +bool NavMeshGenerator2D::use_threads = true; +bool NavMeshGenerator2D::baking_use_multiple_threads = true; +bool NavMeshGenerator2D::baking_use_high_priority_threads = true; +HashSet> NavMeshGenerator2D::baking_navmeshes; +HashMap NavMeshGenerator2D::generator_tasks; + +NavMeshGenerator2D *NavMeshGenerator2D::get_singleton() { + return singleton; +} + +NavMeshGenerator2D::NavMeshGenerator2D() { + ERR_FAIL_COND(singleton != nullptr); + singleton = this; + + baking_use_multiple_threads = GLOBAL_GET("navigation/baking/thread_model/baking_use_multiple_threads"); + baking_use_high_priority_threads = GLOBAL_GET("navigation/baking/thread_model/baking_use_high_priority_threads"); + + // Using threads might cause problems on certain exports or with the Editor on certain devices. + // This is the main switch to turn threaded navmesh baking off should the need arise. + use_threads = baking_use_multiple_threads && !Engine::get_singleton()->is_editor_hint(); +} + +NavMeshGenerator2D::~NavMeshGenerator2D() { + cleanup(); +} + +void NavMeshGenerator2D::sync() { + if (generator_tasks.size() == 0) { + return; + } + + baking_navmesh_mutex.lock(); + generator_task_mutex.lock(); + + LocalVector finished_task_ids; + + for (KeyValue &E : generator_tasks) { + if (WorkerThreadPool::get_singleton()->is_task_completed(E.key)) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); + finished_task_ids.push_back(E.key); + + NavMeshGeneratorTask2D *generator_task = E.value; + DEV_ASSERT(generator_task->status = NavMeshGeneratorTask2D::TaskStatus::BAKING_FINISHED); + + baking_navmeshes.erase(generator_task->navigation_mesh); + if (generator_task->callback.is_valid()) { + generator_emit_callback(generator_task->callback); + } + memdelete(generator_task); + } + } + + for (WorkerThreadPool::TaskID finished_task_id : finished_task_ids) { + generator_tasks.erase(finished_task_id); + } + + generator_task_mutex.unlock(); + baking_navmesh_mutex.unlock(); +} + +void NavMeshGenerator2D::cleanup() { + baking_navmesh_mutex.lock(); + generator_task_mutex.lock(); + + baking_navmeshes.clear(); + + for (KeyValue &E : generator_tasks) { + WorkerThreadPool::get_singleton()->wait_for_task_completion(E.key); + NavMeshGeneratorTask2D *generator_task = E.value; + memdelete(generator_task); + } + generator_tasks.clear(); + + generator_task_mutex.unlock(); + baking_navmesh_mutex.unlock(); +} + +void NavMeshGenerator2D::finish() { + cleanup(); +} + +void NavMeshGenerator2D::parse_source_geometry_data(Ref p_navigation_mesh, Ref p_source_geometry_data, Node *p_root_node, const Callable &p_callback) { + ERR_FAIL_COND(!Thread::is_main_thread()); + ERR_FAIL_COND(!p_navigation_mesh.is_valid()); + ERR_FAIL_NULL(p_root_node); + ERR_FAIL_COND(!p_root_node->is_inside_tree()); + ERR_FAIL_COND(!p_source_geometry_data.is_valid()); + + generator_parse_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_root_node); + + if (p_callback.is_valid()) { + generator_emit_callback(p_callback); + } +} + +void NavMeshGenerator2D::bake_from_source_geometry_data(Ref p_navigation_mesh, Ref p_source_geometry_data, const Callable &p_callback) { + ERR_FAIL_COND(!p_navigation_mesh.is_valid()); + ERR_FAIL_COND(!p_source_geometry_data.is_valid()); + + if (p_navigation_mesh->get_outline_count() == 0 && !p_source_geometry_data->has_data()) { + p_navigation_mesh->clear(); + if (p_callback.is_valid()) { + generator_emit_callback(p_callback); + } + return; + } + + baking_navmesh_mutex.lock(); + if (baking_navmeshes.has(p_navigation_mesh)) { + baking_navmesh_mutex.unlock(); + ERR_FAIL_MSG("NavigationPolygon is already baking. Wait for current bake to finish."); + } + baking_navmeshes.insert(p_navigation_mesh); + baking_navmesh_mutex.unlock(); + + generator_bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data); + + baking_navmesh_mutex.lock(); + baking_navmeshes.erase(p_navigation_mesh); + baking_navmesh_mutex.unlock(); + + if (p_callback.is_valid()) { + generator_emit_callback(p_callback); + } +} + +void NavMeshGenerator2D::bake_from_source_geometry_data_async(Ref p_navigation_mesh, Ref p_source_geometry_data, const Callable &p_callback) { + ERR_FAIL_COND(!p_navigation_mesh.is_valid()); + ERR_FAIL_COND(!p_source_geometry_data.is_valid()); + + if (p_navigation_mesh->get_outline_count() == 0 && !p_source_geometry_data->has_data()) { + p_navigation_mesh->clear(); + if (p_callback.is_valid()) { + generator_emit_callback(p_callback); + } + return; + } + + if (!use_threads) { + bake_from_source_geometry_data(p_navigation_mesh, p_source_geometry_data, p_callback); + return; + } + + baking_navmesh_mutex.lock(); + if (baking_navmeshes.has(p_navigation_mesh)) { + baking_navmesh_mutex.unlock(); + ERR_FAIL_MSG("NavigationPolygon is already baking. Wait for current bake to finish."); + } + baking_navmeshes.insert(p_navigation_mesh); + baking_navmesh_mutex.unlock(); + + generator_task_mutex.lock(); + NavMeshGeneratorTask2D *generator_task = memnew(NavMeshGeneratorTask2D); + generator_task->navigation_mesh = p_navigation_mesh; + generator_task->source_geometry_data = p_source_geometry_data; + generator_task->callback = p_callback; + generator_task->status = NavMeshGeneratorTask2D::TaskStatus::BAKING_STARTED; + generator_task->thread_task_id = WorkerThreadPool::get_singleton()->add_native_task(&NavMeshGenerator2D::generator_thread_bake, generator_task, NavMeshGenerator2D::baking_use_high_priority_threads, "NavMeshGeneratorBake2D"); + generator_tasks.insert(generator_task->thread_task_id, generator_task); + generator_task_mutex.unlock(); +} + +void NavMeshGenerator2D::generator_thread_bake(void *p_arg) { + NavMeshGeneratorTask2D *generator_task = static_cast(p_arg); + + generator_bake_from_source_geometry_data(generator_task->navigation_mesh, generator_task->source_geometry_data); + + generator_task->status = NavMeshGeneratorTask2D::TaskStatus::BAKING_FINISHED; +} + +void NavMeshGenerator2D::generator_parse_geometry_node(Ref p_navigation_mesh, Ref p_source_geometry_data, Node *p_node, bool p_recurse_children) { + generator_parse_meshinstance2d_node(p_navigation_mesh, p_source_geometry_data, p_node); + generator_parse_multimeshinstance2d_node(p_navigation_mesh, p_source_geometry_data, p_node); + generator_parse_polygon2d_node(p_navigation_mesh, p_source_geometry_data, p_node); + generator_parse_staticbody2d_node(p_navigation_mesh, p_source_geometry_data, p_node); + generator_parse_tilemap_node(p_navigation_mesh, p_source_geometry_data, p_node); + + if (p_recurse_children) { + for (int i = 0; i < p_node->get_child_count(); i++) { + generator_parse_geometry_node(p_navigation_mesh, p_source_geometry_data, p_node->get_child(i), p_recurse_children); + } + } +} + +void NavMeshGenerator2D::generator_parse_meshinstance2d_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { + MeshInstance2D *mesh_instance = Object::cast_to(p_node); + + if (mesh_instance == nullptr) { + return; + } + + NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); + + if (!(parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH)) { + return; + } + + Ref mesh = mesh_instance->get_mesh(); + if (!mesh.is_valid()) { + return; + } + + const Transform2D mesh_instance_xform = p_source_geometry_data->root_node_transform * mesh_instance->get_global_transform(); + + using namespace Clipper2Lib; + + Paths64 subject_paths, dummy_clip_paths; + + for (int i = 0; i < mesh->get_surface_count(); i++) { + if (mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { + continue; + } + + if (!(mesh->surface_get_format(i) & Mesh::ARRAY_FLAG_USE_2D_VERTICES)) { + continue; + } + + Path64 subject_path; + + int index_count = 0; + if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) { + index_count = mesh->surface_get_array_index_len(i); + } else { + index_count = mesh->surface_get_array_len(i); + } + + ERR_CONTINUE((index_count == 0 || (index_count % 3) != 0)); + + Array a = mesh->surface_get_arrays(i); + + Vector mesh_vertices = a[Mesh::ARRAY_VERTEX]; + + if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) { + Vector mesh_indices = a[Mesh::ARRAY_INDEX]; + for (int vertex_index : mesh_indices) { + const Vector2 &vertex = mesh_vertices[vertex_index]; + const Point64 &point = Point64(vertex.x, vertex.y); + subject_path.push_back(point); + } + } else { + for (const Vector2 &vertex : mesh_vertices) { + const Point64 &point = Point64(vertex.x, vertex.y); + subject_path.push_back(point); + } + } + subject_paths.push_back(subject_path); + } + + Paths64 path_solution; + + path_solution = Union(subject_paths, dummy_clip_paths, FillRule::NonZero); + + //path_solution = RamerDouglasPeucker(path_solution, 0.025); + + Vector> polypaths; + + for (const Path64 &scaled_path : path_solution) { + Vector shape_outline; + for (const Point64 &scaled_point : scaled_path) { + shape_outline.push_back(Point2(static_cast(scaled_point.x), static_cast(scaled_point.y))); + } + + for (int i = 0; i < shape_outline.size(); i++) { + shape_outline.write[i] = mesh_instance_xform.xform(shape_outline[i]); + } + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } +} + +void NavMeshGenerator2D::generator_parse_multimeshinstance2d_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { + MultiMeshInstance2D *multimesh_instance = Object::cast_to(p_node); + + if (multimesh_instance == nullptr) { + return; + } + + NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); + if (!(parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH)) { + return; + } + + Ref multimesh = multimesh_instance->get_multimesh(); + if (!(multimesh.is_valid() && multimesh->get_transform_format() == MultiMesh::TRANSFORM_2D)) { + return; + } + + Ref mesh = multimesh->get_mesh(); + if (!mesh.is_valid()) { + return; + } + + using namespace Clipper2Lib; + + Paths64 mesh_subject_paths, dummy_clip_paths; + + for (int i = 0; i < mesh->get_surface_count(); i++) { + if (mesh->surface_get_primitive_type(i) != Mesh::PRIMITIVE_TRIANGLES) { + continue; + } + + if (!(mesh->surface_get_format(i) & Mesh::ARRAY_FLAG_USE_2D_VERTICES)) { + continue; + } + + Path64 subject_path; + + int index_count = 0; + if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) { + index_count = mesh->surface_get_array_index_len(i); + } else { + index_count = mesh->surface_get_array_len(i); + } + + ERR_CONTINUE((index_count == 0 || (index_count % 3) != 0)); + + Array a = mesh->surface_get_arrays(i); + + Vector mesh_vertices = a[Mesh::ARRAY_VERTEX]; + + if (mesh->surface_get_format(i) & Mesh::ARRAY_FORMAT_INDEX) { + Vector mesh_indices = a[Mesh::ARRAY_INDEX]; + for (int vertex_index : mesh_indices) { + const Vector2 &vertex = mesh_vertices[vertex_index]; + const Point64 &point = Point64(vertex.x, vertex.y); + subject_path.push_back(point); + } + } else { + for (const Vector2 &vertex : mesh_vertices) { + const Point64 &point = Point64(vertex.x, vertex.y); + subject_path.push_back(point); + } + } + mesh_subject_paths.push_back(subject_path); + } + + Paths64 mesh_path_solution = Union(mesh_subject_paths, dummy_clip_paths, FillRule::NonZero); + + //path_solution = RamerDouglasPeucker(path_solution, 0.025); + + int multimesh_instance_count = multimesh->get_visible_instance_count(); + if (multimesh_instance_count == -1) { + multimesh_instance_count = multimesh->get_instance_count(); + } + + const Transform2D multimesh_instance_xform = p_source_geometry_data->root_node_transform * multimesh_instance->get_global_transform(); + + for (int i = 0; i < multimesh_instance_count; i++) { + const Transform2D multimesh_instance_mesh_instance_xform = multimesh_instance_xform * multimesh->get_instance_transform_2d(i); + + for (const Path64 &mesh_path : mesh_path_solution) { + Vector shape_outline; + + for (const Point64 &mesh_path_point : mesh_path) { + shape_outline.push_back(Point2(static_cast(mesh_path_point.x), static_cast(mesh_path_point.y))); + } + + for (int j = 0; j < shape_outline.size(); j++) { + shape_outline.write[j] = multimesh_instance_mesh_instance_xform.xform(shape_outline[j]); + } + p_source_geometry_data->add_obstruction_outline(shape_outline); + } + } +} + +void NavMeshGenerator2D::generator_parse_polygon2d_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { + Polygon2D *polygon_2d = Object::cast_to(p_node); + + if (polygon_2d == nullptr) { + return; + } + + NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); + + if (parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_MESH_INSTANCES || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH) { + const Transform2D polygon_2d_xform = p_source_geometry_data->root_node_transform * polygon_2d->get_global_transform(); + + Vector shape_outline = polygon_2d->get_polygon(); + for (int i = 0; i < shape_outline.size(); i++) { + shape_outline.write[i] = polygon_2d_xform.xform(shape_outline[i]); + } + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } +} + +void NavMeshGenerator2D::generator_parse_staticbody2d_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { + StaticBody2D *static_body = Object::cast_to(p_node); + + if (static_body == nullptr) { + return; + } + + NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); + if (!(parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_STATIC_COLLIDERS || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH)) { + return; + } + + uint32_t parsed_collision_mask = p_navigation_mesh->get_parsed_collision_mask(); + if (!(static_body->get_collision_layer() & parsed_collision_mask)) { + return; + } + + List shape_owners; + static_body->get_shape_owners(&shape_owners); + + for (uint32_t shape_owner : shape_owners) { + if (static_body->is_shape_owner_disabled(shape_owner)) { + continue; + } + + const int shape_count = static_body->shape_owner_get_shape_count(shape_owner); + + for (int shape_index = 0; shape_index < shape_count; shape_index++) { + Ref s = static_body->shape_owner_get_shape(shape_owner, shape_index); + + if (s.is_null()) { + continue; + } + + const Transform2D static_body_xform = p_source_geometry_data->root_node_transform * static_body->get_global_transform() * static_body->shape_owner_get_transform(shape_owner); + + RectangleShape2D *rectangle_shape = Object::cast_to(*s); + if (rectangle_shape) { + Vector shape_outline; + + const Vector2 &rectangle_size = rectangle_shape->get_size(); + + shape_outline.resize(5); + shape_outline.write[0] = static_body_xform.xform(-rectangle_size * 0.5); + shape_outline.write[1] = static_body_xform.xform(Vector2(rectangle_size.x, -rectangle_size.y) * 0.5); + shape_outline.write[2] = static_body_xform.xform(rectangle_size * 0.5); + shape_outline.write[3] = static_body_xform.xform(Vector2(-rectangle_size.x, rectangle_size.y) * 0.5); + shape_outline.write[4] = static_body_xform.xform(-rectangle_size * 0.5); + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } + + CapsuleShape2D *capsule_shape = Object::cast_to(*s); + if (capsule_shape) { + const real_t capsule_height = capsule_shape->get_height(); + const real_t capsule_radius = capsule_shape->get_radius(); + + Vector shape_outline; + const real_t turn_step = Math_TAU / 12.0; + shape_outline.resize(14); + int shape_outline_inx = 0; + for (int i = 0; i < 12; i++) { + Vector2 ofs = Vector2(0, (i > 3 && i <= 9) ? -capsule_height * 0.5 + capsule_radius : capsule_height * 0.5 - capsule_radius); + + shape_outline.write[shape_outline_inx] = static_body_xform.xform(Vector2(Math::sin(i * turn_step), Math::cos(i * turn_step)) * capsule_radius + ofs); + shape_outline_inx += 1; + if (i == 3 || i == 9) { + shape_outline.write[shape_outline_inx] = static_body_xform.xform(Vector2(Math::sin(i * turn_step), Math::cos(i * turn_step)) * capsule_radius - ofs); + shape_outline_inx += 1; + } + } + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } + + CircleShape2D *circle_shape = Object::cast_to(*s); + if (circle_shape) { + const real_t circle_radius = circle_shape->get_radius(); + + Vector shape_outline; + int circle_edge_count = 12; + shape_outline.resize(circle_edge_count); + + const real_t turn_step = Math_TAU / real_t(circle_edge_count); + for (int i = 0; i < circle_edge_count; i++) { + shape_outline.write[i] = static_body_xform.xform(Vector2(Math::cos(i * turn_step), Math::sin(i * turn_step)) * circle_radius); + } + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } + + ConcavePolygonShape2D *concave_polygon_shape = Object::cast_to(*s); + if (concave_polygon_shape) { + Vector shape_outline = concave_polygon_shape->get_segments(); + + for (int i = 0; i < shape_outline.size(); i++) { + shape_outline.write[i] = static_body_xform.xform(shape_outline[i]); + } + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } + + ConvexPolygonShape2D *convex_polygon_shape = Object::cast_to(*s); + if (convex_polygon_shape) { + Vector shape_outline = convex_polygon_shape->get_points(); + + for (int i = 0; i < shape_outline.size(); i++) { + shape_outline.write[i] = static_body_xform.xform(shape_outline[i]); + } + + p_source_geometry_data->add_obstruction_outline(shape_outline); + } + } + } +} + +void NavMeshGenerator2D::generator_parse_tilemap_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node) { + TileMap *tilemap = Object::cast_to(p_node); + + if (tilemap == nullptr) { + return; + } + + NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type(); + uint32_t parsed_collision_mask = p_navigation_mesh->get_parsed_collision_mask(); + + if (tilemap->get_layers_count() <= 0) { + return; + } + + int tilemap_layer = 0; // only main tile map layer is supported + + Ref tile_set = tilemap->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + int physics_layers_count = tile_set->get_physics_layers_count(); + int navigation_layers_count = tile_set->get_navigation_layers_count(); + + if (physics_layers_count <= 0 && navigation_layers_count <= 0) { + return; + } + + const Transform2D tilemap_xform = p_source_geometry_data->root_node_transform * tilemap->get_global_transform(); + TypedArray used_cells = tilemap->get_used_cells(tilemap_layer); + + for (int used_cell_index = 0; used_cell_index < used_cells.size(); used_cell_index++) { + const Vector2i &cell = used_cells[used_cell_index]; + + const TileData *tile_data = tilemap->get_cell_tile_data(tilemap_layer, cell, false); + + Transform2D tile_transform; + tile_transform.set_origin(tilemap->map_to_local(cell)); + + const Transform2D tile_transform_offset = tilemap_xform * tile_transform; + + if (navigation_layers_count > 0) { + Ref navigation_polygon = tile_data->get_navigation_polygon(tilemap_layer); + if (navigation_polygon.is_valid()) { + for (int outline_index = 0; outline_index < navigation_polygon->get_outline_count(); outline_index++) { + Vector traversable_outline = navigation_polygon->get_outline(outline_index); + + for (int traversable_outline_index = 0; traversable_outline_index < traversable_outline.size(); traversable_outline_index++) { + traversable_outline.write[traversable_outline_index] = tile_transform_offset.xform(traversable_outline[traversable_outline_index]); + } + + p_source_geometry_data->_add_traversable_outline(traversable_outline); + } + } + } + + if (physics_layers_count > 0 && (parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_STATIC_COLLIDERS || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH) && (tile_set->get_physics_layer_collision_layer(tilemap_layer) & parsed_collision_mask)) { + for (int collision_polygon_index = 0; collision_polygon_index < tile_data->get_collision_polygons_count(tilemap_layer); collision_polygon_index++) { + Vector obstruction_outline = tile_data->get_collision_polygon_points(tilemap_layer, collision_polygon_index); + + for (int obstruction_outline_index = 0; obstruction_outline_index < obstruction_outline.size(); obstruction_outline_index++) { + obstruction_outline.write[obstruction_outline_index] = tile_transform_offset.xform(obstruction_outline[obstruction_outline_index]); + } + + p_source_geometry_data->_add_obstruction_outline(obstruction_outline); + } + } + } +} + +void NavMeshGenerator2D::generator_parse_source_geometry_data(Ref p_navigation_mesh, Ref p_source_geometry_data, Node *p_root_node) { + List parse_nodes; + + if (p_navigation_mesh->get_source_geometry_mode() == NavigationPolygon::SOURCE_GEOMETRY_ROOT_NODE_CHILDREN) { + parse_nodes.push_back(p_root_node); + } else { + p_root_node->get_tree()->get_nodes_in_group(p_navigation_mesh->get_source_geometry_group_name(), &parse_nodes); + } + + Transform2D root_node_transform = Transform2D(); + if (Object::cast_to(p_root_node)) { + root_node_transform = Object::cast_to(p_root_node)->get_global_transform().affine_inverse(); + } + + p_source_geometry_data->clear(); + p_source_geometry_data->root_node_transform = root_node_transform; + + bool recurse_children = p_navigation_mesh->get_source_geometry_mode() != NavigationPolygon::SOURCE_GEOMETRY_GROUPS_EXPLICIT; + + for (Node *E : parse_nodes) { + generator_parse_geometry_node(p_navigation_mesh, p_source_geometry_data, E, recurse_children); + } +}; + +static void generator_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); + generator_recursive_process_polytree_items(p_tppl_in_polygon, polypath_item); + } +} + +bool NavMeshGenerator2D::generator_emit_callback(const Callable &p_callback) { + ERR_FAIL_COND_V(!p_callback.is_valid(), false); + + Callable::CallError ce; + Variant result; + p_callback.callp(nullptr, 0, result, ce); + + return ce.error == Callable::CallError::CALL_OK; +} + +void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref p_navigation_mesh, Ref p_source_geometry_data) { + if (p_navigation_mesh.is_null() || p_source_geometry_data.is_null()) { + return; + } + + if (p_navigation_mesh->get_outline_count() == 0 && !p_source_geometry_data->has_data()) { + return; + } + + int outline_count = p_navigation_mesh->get_outline_count(); + const Vector> &traversable_outlines = p_source_geometry_data->_get_traversable_outlines(); + const Vector> &obstruction_outlines = p_source_geometry_data->_get_obstruction_outlines(); + + if (outline_count == 0 && traversable_outlines.size() == 0) { + return; + } + + using namespace Clipper2Lib; + + Paths64 traversable_polygon_paths; + Paths64 obstruction_polygon_paths; + + for (int i = 0; i < outline_count; i++) { + const Vector &traversable_outline = p_navigation_mesh->get_outline(i); + Path64 subject_path; + for (const Vector2 &traversable_point : traversable_outline) { + const Point64 &point = Point64(traversable_point.x, traversable_point.y); + subject_path.push_back(point); + } + traversable_polygon_paths.push_back(subject_path); + } + + for (const Vector &traversable_outline : traversable_outlines) { + Path64 subject_path; + for (const Vector2 &traversable_point : traversable_outline) { + const Point64 &point = Point64(traversable_point.x, traversable_point.y); + subject_path.push_back(point); + } + traversable_polygon_paths.push_back(subject_path); + } + + for (const Vector &obstruction_outline : obstruction_outlines) { + Path64 clip_path; + for (const Vector2 &obstruction_point : obstruction_outline) { + const Point64 &point = Point64(obstruction_point.x, obstruction_point.y); + clip_path.push_back(point); + } + obstruction_polygon_paths.push_back(clip_path); + } + + Paths64 path_solution; + + // 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, FillRule::NonZero); + // 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); + + path_solution = Difference(traversable_polygon_paths, obstruction_polygon_paths, FillRule::NonZero); + + real_t agent_radius_offset = p_navigation_mesh->get_agent_radius(); + if (agent_radius_offset > 0.0) { + path_solution = InflatePaths(path_solution, -agent_radius_offset, JoinType::Miter, EndType::Polygon); + } + //path_solution = RamerDouglasPeucker(path_solution, 0.025); // + + Vector> new_baked_outlines; + + for (const Path64 &scaled_path : path_solution) { + Vector polypath; + for (const Point64 &scaled_point : scaled_path) { + polypath.push_back(Vector2(static_cast(scaled_point.x), static_cast(scaled_point.y))); + } + new_baked_outlines.push_back(polypath); + } + + if (new_baked_outlines.size() == 0) { + p_navigation_mesh->set_vertices(Vector()); + p_navigation_mesh->clear_polygons(); + return; + } + + Paths64 polygon_paths; + + for (const Vector &baked_outline : new_baked_outlines) { + Path64 polygon_path; + for (const Vector2 &baked_outline_point : baked_outline) { + const Point64 &point = Point64(baked_outline_point.x, baked_outline_point.y); + polygon_path.push_back(point); + } + polygon_paths.push_back(polygon_path); + } + + ClipType clipper_cliptype = ClipType::Union; + + List tppl_in_polygon, tppl_out_polygon; + + PolyTree64 polytree; + Clipper64 clipper_64; + + clipper_64.AddSubject(polygon_paths); + clipper_64.Execute(clipper_cliptype, FillRule::NonZero, polytree); + + for (size_t i = 0; i < polytree.Count(); i++) { + const PolyPath64 *polypath_item = polytree[i]; + generator_recursive_process_polytree_items(tppl_in_polygon, polypath_item); + } + + TPPLPartition tpart; + if (tpart.ConvexPartition_HM(&tppl_in_polygon, &tppl_out_polygon) == 0) { //failed! + ERR_PRINT("NavigationPolygon Convex partition failed. Unable to create a valid NavigationMesh from defined polygon outline paths."); + p_navigation_mesh->set_vertices(Vector()); + p_navigation_mesh->clear_polygons(); + return; + } + + Vector 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::Iterator 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_mesh->set_vertices(new_vertices); + p_navigation_mesh->clear_polygons(); + for (int i = 0; i < new_polygons.size(); i++) { + p_navigation_mesh->add_polygon(new_polygons[i]); + } +} diff --git a/modules/navigation/nav_mesh_generator_2d.h b/modules/navigation/nav_mesh_generator_2d.h new file mode 100644 index 00000000000..763ad246364 --- /dev/null +++ b/modules/navigation/nav_mesh_generator_2d.h @@ -0,0 +1,100 @@ +/**************************************************************************/ +/* nav_mesh_generator_2d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef NAV_MESH_GENERATOR_2D_H +#define NAV_MESH_GENERATOR_2D_H + +#include "core/object/class_db.h" +#include "core/object/worker_thread_pool.h" + +class Node; +class NavigationPolygon; +class NavigationMeshSourceGeometryData2D; + +class NavMeshGenerator2D : public Object { + static NavMeshGenerator2D *singleton; + + static Mutex baking_navmesh_mutex; + static Mutex generator_task_mutex; + + static bool use_threads; + static bool baking_use_multiple_threads; + static bool baking_use_high_priority_threads; + + struct NavMeshGeneratorTask2D { + enum TaskStatus { + BAKING_STARTED, + BAKING_FINISHED, + BAKING_FAILED, + CALLBACK_DISPATCHED, + CALLBACK_FAILED, + }; + + Ref navigation_mesh; + Ref source_geometry_data; + Callable callback; + WorkerThreadPool::TaskID thread_task_id = WorkerThreadPool::INVALID_TASK_ID; + NavMeshGeneratorTask2D::TaskStatus status = NavMeshGeneratorTask2D::TaskStatus::BAKING_STARTED; + }; + + static HashMap generator_tasks; + + static void generator_thread_bake(void *p_arg); + + static HashSet> baking_navmeshes; + + static void generator_parse_geometry_node(Ref p_navigation_mesh, Ref p_source_geometry_data, Node *p_node, bool p_recurse_children); + static void generator_parse_source_geometry_data(Ref p_navigation_mesh, Ref p_source_geometry_data, Node *p_root_node); + static void generator_bake_from_source_geometry_data(Ref p_navigation_mesh, Ref p_source_geometry_data); + + static void generator_parse_meshinstance2d_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); + static void generator_parse_multimeshinstance2d_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); + static void generator_parse_polygon2d_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); + static void generator_parse_staticbody2d_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); + static void generator_parse_tilemap_node(const Ref &p_navigation_mesh, Ref p_source_geometry_data, Node *p_node); + + static bool generator_emit_callback(const Callable &p_callback); + +public: + static NavMeshGenerator2D *get_singleton(); + + static void sync(); + static void cleanup(); + static void finish(); + + static void parse_source_geometry_data(Ref p_navigation_mesh, Ref p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()); + static void bake_from_source_geometry_data(Ref p_navigation_mesh, Ref p_source_geometry_data, const Callable &p_callback = Callable()); + static void bake_from_source_geometry_data_async(Ref p_navigation_mesh, Ref p_source_geometry_data, const Callable &p_callback = Callable()); + + NavMeshGenerator2D(); + ~NavMeshGenerator2D(); +}; + +#endif // NAV_MESH_GENERATOR_2D_H diff --git a/modules/navigation/register_types.cpp b/modules/navigation/register_types.cpp index 1548ff4b9ce..525fe71134f 100644 --- a/modules/navigation/register_types.cpp +++ b/modules/navigation/register_types.cpp @@ -31,6 +31,7 @@ #include "register_types.h" #include "godot_navigation_server.h" +#include "godot_navigation_server_2d.h" #ifndef DISABLE_DEPRECATED #ifndef _3D_DISABLED @@ -43,6 +44,7 @@ #endif #include "core/config/engine.h" +#include "servers/navigation_server_2d.h" #include "servers/navigation_server_3d.h" #ifndef DISABLE_DEPRECATED @@ -55,9 +57,14 @@ NavigationServer3D *new_server() { return memnew(GodotNavigationServer); } +NavigationServer2D *new_navigation_server_2d() { + return memnew(GodotNavigationServer2D); +} + void initialize_navigation_module(ModuleInitializationLevel p_level) { if (p_level == MODULE_INITIALIZATION_LEVEL_SERVERS) { NavigationServer3DManager::set_default_server(new_server); + NavigationServer2DManager::set_default_server(new_navigation_server_2d); #ifndef DISABLE_DEPRECATED #ifndef _3D_DISABLED diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp index 670a2c641c5..706b26bd05c 100644 --- a/scene/2d/navigation_region_2d.cpp +++ b/scene/2d/navigation_region_2d.cpp @@ -179,10 +179,6 @@ void NavigationRegion2D::_notification(int p_what) { } void NavigationRegion2D::set_navigation_polygon(const Ref &p_navigation_polygon) { - if (p_navigation_polygon == navigation_polygon) { - return; - } - if (navigation_polygon.is_valid()) { navigation_polygon->disconnect_changed(callable_mp(this, &NavigationRegion2D::_navigation_polygon_changed)); } @@ -226,6 +222,32 @@ RID NavigationRegion2D::get_navigation_map() const { return RID(); } +void NavigationRegion2D::bake_navigation_polygon(bool p_on_thread) { + ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "The SceneTree can only be parsed on the main thread. Call this function from the main thread or use call_deferred()."); + ERR_FAIL_COND_MSG(!navigation_polygon.is_valid(), "Baking the navigation polygon requires a valid `NavigationPolygon` resource."); + + Ref source_geometry_data; + source_geometry_data.instantiate(); + + NavigationServer2D::get_singleton()->parse_source_geometry_data(navigation_polygon, source_geometry_data, this); + + if (p_on_thread) { + NavigationServer2D::get_singleton()->bake_from_source_geometry_data_async(navigation_polygon, source_geometry_data, callable_mp(this, &NavigationRegion2D::_bake_finished).bind(navigation_polygon)); + } else { + NavigationServer2D::get_singleton()->bake_from_source_geometry_data(navigation_polygon, source_geometry_data, callable_mp(this, &NavigationRegion2D::_bake_finished).bind(navigation_polygon)); + } +} + +void NavigationRegion2D::_bake_finished(Ref p_navigation_polygon) { + if (!Thread::is_main_thread()) { + call_deferred(SNAME("_bake_finished"), p_navigation_polygon); + return; + } + + set_navigation_polygon(p_navigation_polygon); + emit_signal(SNAME("bake_finished")); +} + void NavigationRegion2D::_navigation_polygon_changed() { if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_navigation_hint())) { queue_redraw(); @@ -290,6 +312,8 @@ void NavigationRegion2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_travel_cost", "travel_cost"), &NavigationRegion2D::set_travel_cost); ClassDB::bind_method(D_METHOD("get_travel_cost"), &NavigationRegion2D::get_travel_cost); + ClassDB::bind_method(D_METHOD("bake_navigation_polygon", "on_thread"), &NavigationRegion2D::bake_navigation_polygon, DEFVAL(true)); + ClassDB::bind_method(D_METHOD("_navigation_polygon_changed"), &NavigationRegion2D::_navigation_polygon_changed); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "navigation_polygon", PROPERTY_HINT_RESOURCE_TYPE, "NavigationPolygon"), "set_navigation_polygon", "get_navigation_polygon"); @@ -300,6 +324,9 @@ void NavigationRegion2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "travel_cost"), "set_travel_cost", "get_travel_cost"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "constrain_avoidance"), "set_constrain_avoidance", "get_constrain_avoidance"); ADD_PROPERTY(PropertyInfo(Variant::INT, "avoidance_layers", PROPERTY_HINT_LAYERS_AVOIDANCE), "set_avoidance_layers", "get_avoidance_layers"); + + ADD_SIGNAL(MethodInfo("navigation_polygon_changed")); + ADD_SIGNAL(MethodInfo("bake_finished")); } #ifndef DISABLE_DEPRECATED diff --git a/scene/2d/navigation_region_2d.h b/scene/2d/navigation_region_2d.h index 0a48b10f470..36e889877a1 100644 --- a/scene/2d/navigation_region_2d.h +++ b/scene/2d/navigation_region_2d.h @@ -114,6 +114,9 @@ public: PackedStringArray get_configuration_warnings() const override; + void bake_navigation_polygon(bool p_on_thread); + void _bake_finished(Ref p_navigation_polygon); + NavigationRegion2D(); ~NavigationRegion2D(); diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 19c4b62eaf7..03085edb86a 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -172,6 +172,7 @@ #include "scene/resources/mesh_texture.h" #include "scene/resources/multimesh.h" #include "scene/resources/navigation_mesh.h" +#include "scene/resources/navigation_mesh_source_geometry_data_2d.h" #include "scene/resources/navigation_mesh_source_geometry_data_3d.h" #include "scene/resources/navigation_polygon.h" #include "scene/resources/packed_scene.h" @@ -956,6 +957,7 @@ void register_scene_types() { GDREGISTER_CLASS(PathFollow2D); GDREGISTER_CLASS(NavigationMesh); + GDREGISTER_CLASS(NavigationMeshSourceGeometryData2D); GDREGISTER_CLASS(NavigationMeshSourceGeometryData3D); GDREGISTER_CLASS(NavigationPolygon); GDREGISTER_CLASS(NavigationRegion2D); diff --git a/scene/resources/navigation_mesh_source_geometry_data_2d.cpp b/scene/resources/navigation_mesh_source_geometry_data_2d.cpp new file mode 100644 index 00000000000..3dde6dbff6f --- /dev/null +++ b/scene/resources/navigation_mesh_source_geometry_data_2d.cpp @@ -0,0 +1,138 @@ +/**************************************************************************/ +/* navigation_mesh_source_geometry_data_2d.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "navigation_mesh_source_geometry_data_2d.h" + +#include "scene/resources/mesh.h" + +void NavigationMeshSourceGeometryData2D::clear() { + traversable_outlines.clear(); + obstruction_outlines.clear(); +} + +void NavigationMeshSourceGeometryData2D::_set_traversable_outlines(const Vector> &p_traversable_outlines) { + traversable_outlines = p_traversable_outlines; +} + +void NavigationMeshSourceGeometryData2D::_set_obstruction_outlines(const Vector> &p_obstruction_outlines) { + obstruction_outlines = p_obstruction_outlines; +} + +void NavigationMeshSourceGeometryData2D::_add_traversable_outline(const Vector &p_shape_outline) { + if (p_shape_outline.size() > 1) { + traversable_outlines.push_back(p_shape_outline); + } +} + +void NavigationMeshSourceGeometryData2D::_add_obstruction_outline(const Vector &p_shape_outline) { + if (p_shape_outline.size() > 1) { + obstruction_outlines.push_back(p_shape_outline); + } +} + +void NavigationMeshSourceGeometryData2D::set_traversable_outlines(const TypedArray> &p_traversable_outlines) { + traversable_outlines.resize(p_traversable_outlines.size()); + for (int i = 0; i < p_traversable_outlines.size(); i++) { + traversable_outlines.write[i] = p_traversable_outlines[i]; + } +} + +TypedArray> NavigationMeshSourceGeometryData2D::get_traversable_outlines() const { + TypedArray> typed_array_traversable_outlines; + typed_array_traversable_outlines.resize(traversable_outlines.size()); + for (int i = 0; i < typed_array_traversable_outlines.size(); i++) { + typed_array_traversable_outlines[i] = traversable_outlines[i]; + } + + return typed_array_traversable_outlines; +} + +void NavigationMeshSourceGeometryData2D::set_obstruction_outlines(const TypedArray> &p_obstruction_outlines) { + obstruction_outlines.resize(p_obstruction_outlines.size()); + for (int i = 0; i < p_obstruction_outlines.size(); i++) { + obstruction_outlines.write[i] = p_obstruction_outlines[i]; + } +} + +TypedArray> NavigationMeshSourceGeometryData2D::get_obstruction_outlines() const { + TypedArray> typed_array_obstruction_outlines; + typed_array_obstruction_outlines.resize(obstruction_outlines.size()); + for (int i = 0; i < typed_array_obstruction_outlines.size(); i++) { + typed_array_obstruction_outlines[i] = obstruction_outlines[i]; + } + + return typed_array_obstruction_outlines; +} + +void NavigationMeshSourceGeometryData2D::add_traversable_outline(const PackedVector2Array &p_shape_outline) { + if (p_shape_outline.size() > 1) { + Vector traversable_outline; + traversable_outline.resize(p_shape_outline.size()); + for (int i = 0; i < p_shape_outline.size(); i++) { + traversable_outline.write[i] = p_shape_outline[i]; + } + traversable_outlines.push_back(traversable_outline); + } +} + +void NavigationMeshSourceGeometryData2D::add_obstruction_outline(const PackedVector2Array &p_shape_outline) { + if (p_shape_outline.size() > 1) { + Vector obstruction_outline; + obstruction_outline.resize(p_shape_outline.size()); + for (int i = 0; i < p_shape_outline.size(); i++) { + obstruction_outline.write[i] = p_shape_outline[i]; + } + obstruction_outlines.push_back(obstruction_outline); + } +} + +void NavigationMeshSourceGeometryData2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("clear"), &NavigationMeshSourceGeometryData2D::clear); + ClassDB::bind_method(D_METHOD("has_data"), &NavigationMeshSourceGeometryData2D::has_data); + + ClassDB::bind_method(D_METHOD("set_traversable_outlines", "traversable_outlines"), &NavigationMeshSourceGeometryData2D::set_traversable_outlines); + ClassDB::bind_method(D_METHOD("get_traversable_outlines"), &NavigationMeshSourceGeometryData2D::get_traversable_outlines); + + ClassDB::bind_method(D_METHOD("set_obstruction_outlines", "obstruction_outlines"), &NavigationMeshSourceGeometryData2D::set_obstruction_outlines); + ClassDB::bind_method(D_METHOD("get_obstruction_outlines"), &NavigationMeshSourceGeometryData2D::get_obstruction_outlines); + + ClassDB::bind_method(D_METHOD("add_traversable_outline", "shape_outline"), &NavigationMeshSourceGeometryData2D::add_traversable_outline); + ClassDB::bind_method(D_METHOD("add_obstruction_outline", "shape_outline"), &NavigationMeshSourceGeometryData2D::add_obstruction_outline); + + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "traversable_outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_traversable_outlines", "get_traversable_outlines"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "obstruction_outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_obstruction_outlines", "get_obstruction_outlines"); +} + +NavigationMeshSourceGeometryData2D::NavigationMeshSourceGeometryData2D() { +} + +NavigationMeshSourceGeometryData2D::~NavigationMeshSourceGeometryData2D() { + clear(); +} diff --git a/scene/resources/navigation_mesh_source_geometry_data_2d.h b/scene/resources/navigation_mesh_source_geometry_data_2d.h new file mode 100644 index 00000000000..f26a4e9a2e1 --- /dev/null +++ b/scene/resources/navigation_mesh_source_geometry_data_2d.h @@ -0,0 +1,78 @@ +/**************************************************************************/ +/* navigation_mesh_source_geometry_data_2d.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_2D_H +#define NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_2D_H + +#include "scene/2d/node_2d.h" +#include "scene/resources/navigation_polygon.h" + +class NavigationMeshSourceGeometryData2D : public Resource { + GDCLASS(NavigationMeshSourceGeometryData2D, Resource); + + Vector> traversable_outlines; + Vector> obstruction_outlines; + +protected: + static void _bind_methods(); + +public: + void _set_traversable_outlines(const Vector> &p_traversable_outlines); + const Vector> &_get_traversable_outlines() const { return traversable_outlines; } + + void _set_obstruction_outlines(const Vector> &p_obstruction_outlines); + const Vector> &_get_obstruction_outlines() const { return obstruction_outlines; } + + void _add_traversable_outline(const Vector &p_shape_outline); + void _add_obstruction_outline(const Vector &p_shape_outline); + + // kept root node transform here on the geometry data + // if we add this transform to all exposed functions we need to break comp on all functions later + // when navmesh changes from global transform to relative to navregion + // but if it stays here we can just remove it and change the internal functions only + Transform2D root_node_transform; + + void set_traversable_outlines(const TypedArray> &p_traversable_outlines); + TypedArray> get_traversable_outlines() const; + + void set_obstruction_outlines(const TypedArray> &p_obstruction_outlines); + TypedArray> get_obstruction_outlines() const; + + void add_traversable_outline(const PackedVector2Array &p_shape_outline); + void add_obstruction_outline(const PackedVector2Array &p_shape_outline); + + bool has_data() { return traversable_outlines.size(); }; + void clear(); + + NavigationMeshSourceGeometryData2D(); + ~NavigationMeshSourceGeometryData2D(); +}; + +#endif // NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_2D_H diff --git a/scene/resources/navigation_polygon.cpp b/scene/resources/navigation_polygon.cpp index e521bfb2e04..6c0e1343ecd 100644 --- a/scene/resources/navigation_polygon.cpp +++ b/scene/resources/navigation_polygon.cpp @@ -32,6 +32,7 @@ #include "core/math/geometry_2d.h" #include "core/os/mutex.h" +#include "servers/navigation_server_2d.h" #include "thirdparty/misc/polypartition.h" @@ -229,7 +230,11 @@ void NavigationPolygon::clear_outlines() { rect_cache_dirty = true; } +#ifndef DISABLE_DEPRECATED void NavigationPolygon::make_polygons_from_outlines() { + WARN_PRINT("Function make_polygons_from_outlines() is deprecated." + "\nUse NavigationServer2D.parse_source_geometry_data() and NavigationServer2D.bake_from_source_geometry_data() instead."); + { MutexLock lock(navigation_mesh_generation); navigation_mesh.unref(); @@ -331,6 +336,7 @@ void NavigationPolygon::make_polygons_from_outlines() { emit_changed(); } +#endif // DISABLE_DEPRECATED void NavigationPolygon::set_cell_size(real_t p_cell_size) { cell_size = p_cell_size; @@ -341,6 +347,69 @@ real_t NavigationPolygon::get_cell_size() const { return cell_size; } +void NavigationPolygon::set_parsed_geometry_type(ParsedGeometryType p_geometry_type) { + ERR_FAIL_INDEX(p_geometry_type, PARSED_GEOMETRY_MAX); + parsed_geometry_type = p_geometry_type; + notify_property_list_changed(); +} + +NavigationPolygon::ParsedGeometryType NavigationPolygon::get_parsed_geometry_type() const { + return parsed_geometry_type; +} + +void NavigationPolygon::set_parsed_collision_mask(uint32_t p_mask) { + parsed_collision_mask = p_mask; +} + +uint32_t NavigationPolygon::get_parsed_collision_mask() const { + return parsed_collision_mask; +} + +void NavigationPolygon::set_parsed_collision_mask_value(int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Collision layer number must be between 1 and 32 inclusive."); + uint32_t mask = get_parsed_collision_mask(); + if (p_value) { + mask |= 1 << (p_layer_number - 1); + } else { + mask &= ~(1 << (p_layer_number - 1)); + } + set_parsed_collision_mask(mask); +} + +bool NavigationPolygon::get_parsed_collision_mask_value(int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Collision layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Collision layer number must be between 1 and 32 inclusive."); + return get_parsed_collision_mask() & (1 << (p_layer_number - 1)); +} + +void NavigationPolygon::set_source_geometry_mode(SourceGeometryMode p_geometry_mode) { + ERR_FAIL_INDEX(p_geometry_mode, SOURCE_GEOMETRY_MAX); + source_geometry_mode = p_geometry_mode; + notify_property_list_changed(); +} + +NavigationPolygon::SourceGeometryMode NavigationPolygon::get_source_geometry_mode() const { + return source_geometry_mode; +} + +void NavigationPolygon::set_source_geometry_group_name(StringName p_group_name) { + source_geometry_group_name = p_group_name; +} + +StringName NavigationPolygon::get_source_geometry_group_name() const { + return source_geometry_group_name; +} + +void NavigationPolygon::set_agent_radius(real_t p_value) { + ERR_FAIL_COND(p_value < 0); + agent_radius = p_value; +} + +real_t NavigationPolygon::get_agent_radius() const { + return agent_radius; +} + void NavigationPolygon::_bind_methods() { ClassDB::bind_method(D_METHOD("set_vertices", "vertices"), &NavigationPolygon::set_vertices); ClassDB::bind_method(D_METHOD("get_vertices"), &NavigationPolygon::get_vertices); @@ -358,7 +427,9 @@ void NavigationPolygon::_bind_methods() { ClassDB::bind_method(D_METHOD("get_outline", "idx"), &NavigationPolygon::get_outline); ClassDB::bind_method(D_METHOD("remove_outline", "idx"), &NavigationPolygon::remove_outline); ClassDB::bind_method(D_METHOD("clear_outlines"), &NavigationPolygon::clear_outlines); +#ifndef DISABLE_DEPRECATED ClassDB::bind_method(D_METHOD("make_polygons_from_outlines"), &NavigationPolygon::make_polygons_from_outlines); +#endif // DISABLE_DEPRECATED ClassDB::bind_method(D_METHOD("_set_polygons", "polygons"), &NavigationPolygon::_set_polygons); ClassDB::bind_method(D_METHOD("_get_polygons"), &NavigationPolygon::_get_polygons); @@ -369,10 +440,69 @@ void NavigationPolygon::_bind_methods() { ClassDB::bind_method(D_METHOD("set_cell_size", "cell_size"), &NavigationPolygon::set_cell_size); ClassDB::bind_method(D_METHOD("get_cell_size"), &NavigationPolygon::get_cell_size); + ClassDB::bind_method(D_METHOD("set_parsed_geometry_type", "geometry_type"), &NavigationPolygon::set_parsed_geometry_type); + ClassDB::bind_method(D_METHOD("get_parsed_geometry_type"), &NavigationPolygon::get_parsed_geometry_type); + + ClassDB::bind_method(D_METHOD("set_parsed_collision_mask", "mask"), &NavigationPolygon::set_parsed_collision_mask); + ClassDB::bind_method(D_METHOD("get_parsed_collision_mask"), &NavigationPolygon::get_parsed_collision_mask); + + ClassDB::bind_method(D_METHOD("set_parsed_collision_mask_value", "layer_number", "value"), &NavigationPolygon::set_parsed_collision_mask_value); + ClassDB::bind_method(D_METHOD("get_parsed_collision_mask_value", "layer_number"), &NavigationPolygon::get_parsed_collision_mask_value); + + ClassDB::bind_method(D_METHOD("set_source_geometry_mode", "geometry_mode"), &NavigationPolygon::set_source_geometry_mode); + ClassDB::bind_method(D_METHOD("get_source_geometry_mode"), &NavigationPolygon::get_source_geometry_mode); + + ClassDB::bind_method(D_METHOD("set_source_geometry_group_name", "group_name"), &NavigationPolygon::set_source_geometry_group_name); + ClassDB::bind_method(D_METHOD("get_source_geometry_group_name"), &NavigationPolygon::get_source_geometry_group_name); + + ClassDB::bind_method(D_METHOD("set_agent_radius", "agent_radius"), &NavigationPolygon::set_agent_radius); + ClassDB::bind_method(D_METHOD("get_agent_radius"), &NavigationPolygon::get_agent_radius); + ClassDB::bind_method(D_METHOD("clear"), &NavigationPolygon::clear); ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_vertices", "get_vertices"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "polygons", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_polygons", "_get_polygons"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_outlines", "_get_outlines"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "cell_size", PROPERTY_HINT_RANGE, "0.01,500.0,0.01,or_greater,suffix:px"), "set_cell_size", "get_cell_size"); + + ADD_GROUP("Geometry", ""); + ADD_PROPERTY(PropertyInfo(Variant::INT, "parsed_geometry_type", PROPERTY_HINT_ENUM, "Mesh Instances,Static Colliders,Meshes and Static Colliders"), "set_parsed_geometry_type", "get_parsed_geometry_type"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "parsed_collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_parsed_collision_mask", "get_parsed_collision_mask"); + ADD_PROPERTY_DEFAULT("parsed_collision_mask", 0xFFFFFFFF); + ADD_PROPERTY(PropertyInfo(Variant::INT, "source_geometry_mode", PROPERTY_HINT_ENUM, "Root Node Children,Group With Children,Group Explicit"), "set_source_geometry_mode", "get_source_geometry_mode"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "source_geometry_group_name"), "set_source_geometry_group_name", "get_source_geometry_group_name"); + ADD_PROPERTY_DEFAULT("source_geometry_group_name", StringName("navigation_polygon_source_geometry_group")); + ADD_GROUP("Cells", ""); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "cell_size", PROPERTY_HINT_RANGE, "1.0,50.0,1.0,or_greater,suffix:px"), "set_cell_size", "get_cell_size"); + ADD_GROUP("Agents", "agent_"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "agent_radius", PROPERTY_HINT_RANGE, "0.0,500.0,0.01,or_greater,suffix:px"), "set_agent_radius", "get_agent_radius"); + + BIND_ENUM_CONSTANT(PARSED_GEOMETRY_MESH_INSTANCES); + BIND_ENUM_CONSTANT(PARSED_GEOMETRY_STATIC_COLLIDERS); + BIND_ENUM_CONSTANT(PARSED_GEOMETRY_BOTH); + BIND_ENUM_CONSTANT(PARSED_GEOMETRY_MAX); + + BIND_ENUM_CONSTANT(SOURCE_GEOMETRY_ROOT_NODE_CHILDREN); + BIND_ENUM_CONSTANT(SOURCE_GEOMETRY_GROUPS_WITH_CHILDREN); + BIND_ENUM_CONSTANT(SOURCE_GEOMETRY_GROUPS_EXPLICIT); + BIND_ENUM_CONSTANT(SOURCE_GEOMETRY_MAX); +} + +void NavigationPolygon::_validate_property(PropertyInfo &p_property) const { + if (p_property.name == "parsed_collision_mask") { + if (parsed_geometry_type == PARSED_GEOMETRY_MESH_INSTANCES) { + p_property.usage = PROPERTY_USAGE_NONE; + return; + } + } + + if (p_property.name == "parsed_source_group_name") { + if (source_geometry_mode == SOURCE_GEOMETRY_ROOT_NODE_CHILDREN) { + p_property.usage = PROPERTY_USAGE_NONE; + return; + } + } +} + +NavigationPolygon::NavigationPolygon() { + navigation_mesh.instantiate(); } diff --git a/scene/resources/navigation_polygon.h b/scene/resources/navigation_polygon.h index 7926709a9ef..4a6a97e2e72 100644 --- a/scene/resources/navigation_polygon.h +++ b/scene/resources/navigation_polygon.h @@ -43,6 +43,7 @@ class NavigationPolygon : public Resource { }; Vector polygons; Vector> outlines; + Vector> baked_outlines; mutable Rect2 item_rect; mutable bool rect_cache_dirty = true; @@ -55,6 +56,7 @@ class NavigationPolygon : public Resource { protected: static void _bind_methods(); + void _validate_property(PropertyInfo &p_property) const; void _set_polygons(const TypedArray> &p_array); TypedArray> _get_polygons() const; @@ -68,6 +70,28 @@ public: bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const; #endif + enum ParsedGeometryType { + PARSED_GEOMETRY_MESH_INSTANCES = 0, + PARSED_GEOMETRY_STATIC_COLLIDERS, + PARSED_GEOMETRY_BOTH, + PARSED_GEOMETRY_MAX + }; + + enum SourceGeometryMode { + SOURCE_GEOMETRY_ROOT_NODE_CHILDREN = 0, + SOURCE_GEOMETRY_GROUPS_WITH_CHILDREN, + SOURCE_GEOMETRY_GROUPS_EXPLICIT, + SOURCE_GEOMETRY_MAX + }; + + real_t agent_radius = 10.0f; + + ParsedGeometryType parsed_geometry_type = PARSED_GEOMETRY_BOTH; + uint32_t parsed_collision_mask = 0xFFFFFFFF; + + SourceGeometryMode source_geometry_mode = SOURCE_GEOMETRY_ROOT_NODE_CHILDREN; + StringName source_geometry_group_name = "navigation_polygon_source_group"; + void set_vertices(const Vector &p_vertices); Vector get_vertices() const; @@ -82,11 +106,33 @@ public: int get_outline_count() const; void clear_outlines(); +#ifndef DISABLE_DEPRECATED void make_polygons_from_outlines(); +#endif // DISABLE_DEPRECATED + void set_polygons(const Vector> &p_polygons); + const Vector> &get_polygons() const; Vector get_polygon(int p_idx); void clear_polygons(); + void set_parsed_geometry_type(ParsedGeometryType p_geometry_type); + ParsedGeometryType get_parsed_geometry_type() const; + + void set_parsed_collision_mask(uint32_t p_mask); + uint32_t get_parsed_collision_mask() const; + + void set_parsed_collision_mask_value(int p_layer_number, bool p_value); + bool get_parsed_collision_mask_value(int p_layer_number) const; + + void set_source_geometry_mode(SourceGeometryMode p_geometry_mode); + SourceGeometryMode get_source_geometry_mode() const; + + void set_source_geometry_group_name(StringName p_group_name); + StringName get_source_geometry_group_name() const; + + void set_agent_radius(real_t p_value); + real_t get_agent_radius() const; + Ref get_navigation_mesh(); void set_cell_size(real_t p_cell_size); @@ -94,8 +140,11 @@ public: void clear(); - NavigationPolygon() {} + NavigationPolygon(); ~NavigationPolygon() {} }; +VARIANT_ENUM_CAST(NavigationPolygon::ParsedGeometryType); +VARIANT_ENUM_CAST(NavigationPolygon::SourceGeometryMode); + #endif // NAVIGATION_POLYGON_H diff --git a/servers/navigation_server_2d.cpp b/servers/navigation_server_2d.cpp index cd92d9dd2f3..3804b45e1a9 100644 --- a/servers/navigation_server_2d.cpp +++ b/servers/navigation_server_2d.cpp @@ -28,133 +28,153 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /**************************************************************************/ -#include "servers/navigation_server_2d.h" +#include "navigation_server_2d.h" -#include "core/math/transform_2d.h" -#include "core/math/transform_3d.h" #include "servers/navigation_server_3d.h" NavigationServer2D *NavigationServer2D::singleton = nullptr; -#define FORWARD_0(FUNC_NAME) \ - NavigationServer2D::FUNC_NAME() { \ - return NavigationServer3D::get_singleton()->FUNC_NAME(); \ - } +void NavigationServer2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_maps"), &NavigationServer2D::get_maps); -#define FORWARD_0_C(FUNC_NAME) \ - NavigationServer2D::FUNC_NAME() \ - const { \ - return NavigationServer3D::get_singleton()->FUNC_NAME(); \ - } + ClassDB::bind_method(D_METHOD("map_create"), &NavigationServer2D::map_create); + ClassDB::bind_method(D_METHOD("map_set_active", "map", "active"), &NavigationServer2D::map_set_active); + ClassDB::bind_method(D_METHOD("map_is_active", "map"), &NavigationServer2D::map_is_active); + ClassDB::bind_method(D_METHOD("map_set_cell_size", "map", "cell_size"), &NavigationServer2D::map_set_cell_size); + ClassDB::bind_method(D_METHOD("map_get_cell_size", "map"), &NavigationServer2D::map_get_cell_size); + ClassDB::bind_method(D_METHOD("map_set_use_edge_connections", "map", "enabled"), &NavigationServer2D::map_set_use_edge_connections); + ClassDB::bind_method(D_METHOD("map_get_use_edge_connections", "map"), &NavigationServer2D::map_get_use_edge_connections); + ClassDB::bind_method(D_METHOD("map_set_edge_connection_margin", "map", "margin"), &NavigationServer2D::map_set_edge_connection_margin); + ClassDB::bind_method(D_METHOD("map_get_edge_connection_margin", "map"), &NavigationServer2D::map_get_edge_connection_margin); + ClassDB::bind_method(D_METHOD("map_set_link_connection_radius", "map", "radius"), &NavigationServer2D::map_set_link_connection_radius); + ClassDB::bind_method(D_METHOD("map_get_link_connection_radius", "map"), &NavigationServer2D::map_get_link_connection_radius); + ClassDB::bind_method(D_METHOD("map_get_path", "map", "origin", "destination", "optimize", "navigation_layers"), &NavigationServer2D::map_get_path, DEFVAL(1)); + ClassDB::bind_method(D_METHOD("map_get_closest_point", "map", "to_point"), &NavigationServer2D::map_get_closest_point); + ClassDB::bind_method(D_METHOD("map_get_closest_point_owner", "map", "to_point"), &NavigationServer2D::map_get_closest_point_owner); -#define FORWARD_1(FUNC_NAME, T_0, D_0, CONV_0) \ - NavigationServer2D::FUNC_NAME(T_0 D_0) { \ - return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0)); \ - } + ClassDB::bind_method(D_METHOD("map_get_links", "map"), &NavigationServer2D::map_get_links); + ClassDB::bind_method(D_METHOD("map_get_regions", "map"), &NavigationServer2D::map_get_regions); + ClassDB::bind_method(D_METHOD("map_get_agents", "map"), &NavigationServer2D::map_get_agents); + ClassDB::bind_method(D_METHOD("map_get_obstacles", "map"), &NavigationServer2D::map_get_obstacles); -#define FORWARD_1_C(FUNC_NAME, T_0, D_0, CONV_0) \ - NavigationServer2D::FUNC_NAME(T_0 D_0) \ - const { \ - return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0)); \ - } + ClassDB::bind_method(D_METHOD("map_force_update", "map"), &NavigationServer2D::map_force_update); -#define FORWARD_1_R_C(CONV_R, FUNC_NAME, T_0, D_0, CONV_0) \ - NavigationServer2D::FUNC_NAME(T_0 D_0) \ - const { \ - return CONV_R(NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0))); \ - } + ClassDB::bind_method(D_METHOD("query_path", "parameters", "result"), &NavigationServer2D::query_path); -#define FORWARD_2(FUNC_NAME, T_0, D_0, T_1, D_1, CONV_0, CONV_1) \ - NavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1) { \ - return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1)); \ - } + ClassDB::bind_method(D_METHOD("region_create"), &NavigationServer2D::region_create); + ClassDB::bind_method(D_METHOD("region_set_enabled", "region", "enabled"), &NavigationServer2D::region_set_enabled); + ClassDB::bind_method(D_METHOD("region_get_enabled", "region"), &NavigationServer2D::region_get_enabled); + ClassDB::bind_method(D_METHOD("region_set_use_edge_connections", "region", "enabled"), &NavigationServer2D::region_set_use_edge_connections); + ClassDB::bind_method(D_METHOD("region_get_use_edge_connections", "region"), &NavigationServer2D::region_get_use_edge_connections); + ClassDB::bind_method(D_METHOD("region_set_enter_cost", "region", "enter_cost"), &NavigationServer2D::region_set_enter_cost); + ClassDB::bind_method(D_METHOD("region_get_enter_cost", "region"), &NavigationServer2D::region_get_enter_cost); + ClassDB::bind_method(D_METHOD("region_set_travel_cost", "region", "travel_cost"), &NavigationServer2D::region_set_travel_cost); + ClassDB::bind_method(D_METHOD("region_get_travel_cost", "region"), &NavigationServer2D::region_get_travel_cost); + ClassDB::bind_method(D_METHOD("region_set_owner_id", "region", "owner_id"), &NavigationServer2D::region_set_owner_id); + ClassDB::bind_method(D_METHOD("region_get_owner_id", "region"), &NavigationServer2D::region_get_owner_id); + ClassDB::bind_method(D_METHOD("region_owns_point", "region", "point"), &NavigationServer2D::region_owns_point); + ClassDB::bind_method(D_METHOD("region_set_map", "region", "map"), &NavigationServer2D::region_set_map); + ClassDB::bind_method(D_METHOD("region_get_map", "region"), &NavigationServer2D::region_get_map); + ClassDB::bind_method(D_METHOD("region_set_navigation_layers", "region", "navigation_layers"), &NavigationServer2D::region_set_navigation_layers); + ClassDB::bind_method(D_METHOD("region_get_navigation_layers", "region"), &NavigationServer2D::region_get_navigation_layers); + ClassDB::bind_method(D_METHOD("region_set_transform", "region", "transform"), &NavigationServer2D::region_set_transform); + ClassDB::bind_method(D_METHOD("region_set_navigation_polygon", "region", "navigation_polygon"), &NavigationServer2D::region_set_navigation_polygon); + ClassDB::bind_method(D_METHOD("region_get_connections_count", "region"), &NavigationServer2D::region_get_connections_count); + ClassDB::bind_method(D_METHOD("region_get_connection_pathway_start", "region", "connection"), &NavigationServer2D::region_get_connection_pathway_start); + ClassDB::bind_method(D_METHOD("region_get_connection_pathway_end", "region", "connection"), &NavigationServer2D::region_get_connection_pathway_end); -#define FORWARD_2_C(FUNC_NAME, T_0, D_0, T_1, D_1, CONV_0, CONV_1) \ - NavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1) \ - const { \ - return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1)); \ - } + ClassDB::bind_method(D_METHOD("link_create"), &NavigationServer2D::link_create); + ClassDB::bind_method(D_METHOD("link_set_map", "link", "map"), &NavigationServer2D::link_set_map); + ClassDB::bind_method(D_METHOD("link_get_map", "link"), &NavigationServer2D::link_get_map); + ClassDB::bind_method(D_METHOD("link_set_enabled", "link", "enabled"), &NavigationServer2D::link_set_enabled); + ClassDB::bind_method(D_METHOD("link_get_enabled", "link"), &NavigationServer2D::link_get_enabled); + ClassDB::bind_method(D_METHOD("link_set_bidirectional", "link", "bidirectional"), &NavigationServer2D::link_set_bidirectional); + ClassDB::bind_method(D_METHOD("link_is_bidirectional", "link"), &NavigationServer2D::link_is_bidirectional); + ClassDB::bind_method(D_METHOD("link_set_navigation_layers", "link", "navigation_layers"), &NavigationServer2D::link_set_navigation_layers); + ClassDB::bind_method(D_METHOD("link_get_navigation_layers", "link"), &NavigationServer2D::link_get_navigation_layers); + ClassDB::bind_method(D_METHOD("link_set_start_position", "link", "position"), &NavigationServer2D::link_set_start_position); + ClassDB::bind_method(D_METHOD("link_get_start_position", "link"), &NavigationServer2D::link_get_start_position); + ClassDB::bind_method(D_METHOD("link_set_end_position", "link", "position"), &NavigationServer2D::link_set_end_position); + ClassDB::bind_method(D_METHOD("link_get_end_position", "link"), &NavigationServer2D::link_get_end_position); + ClassDB::bind_method(D_METHOD("link_set_enter_cost", "link", "enter_cost"), &NavigationServer2D::link_set_enter_cost); + ClassDB::bind_method(D_METHOD("link_get_enter_cost", "link"), &NavigationServer2D::link_get_enter_cost); + ClassDB::bind_method(D_METHOD("link_set_travel_cost", "link", "travel_cost"), &NavigationServer2D::link_set_travel_cost); + ClassDB::bind_method(D_METHOD("link_get_travel_cost", "link"), &NavigationServer2D::link_get_travel_cost); + ClassDB::bind_method(D_METHOD("link_set_owner_id", "link", "owner_id"), &NavigationServer2D::link_set_owner_id); + ClassDB::bind_method(D_METHOD("link_get_owner_id", "link"), &NavigationServer2D::link_get_owner_id); -#define FORWARD_2_R_C(CONV_R, FUNC_NAME, T_0, D_0, T_1, D_1, CONV_0, CONV_1) \ - NavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1) \ - const { \ - return CONV_R(NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1))); \ - } + ClassDB::bind_method(D_METHOD("agent_create"), &NavigationServer2D::agent_create); + ClassDB::bind_method(D_METHOD("agent_set_avoidance_enabled", "agent", "enabled"), &NavigationServer2D::agent_set_avoidance_enabled); + ClassDB::bind_method(D_METHOD("agent_get_avoidance_enabled", "agent"), &NavigationServer2D::agent_get_avoidance_enabled); + ClassDB::bind_method(D_METHOD("agent_set_map", "agent", "map"), &NavigationServer2D::agent_set_map); + ClassDB::bind_method(D_METHOD("agent_get_map", "agent"), &NavigationServer2D::agent_get_map); + ClassDB::bind_method(D_METHOD("agent_set_paused", "agent", "paused"), &NavigationServer2D::agent_set_paused); + ClassDB::bind_method(D_METHOD("agent_get_paused", "agent"), &NavigationServer2D::agent_get_paused); + ClassDB::bind_method(D_METHOD("agent_set_neighbor_distance", "agent", "distance"), &NavigationServer2D::agent_set_neighbor_distance); + ClassDB::bind_method(D_METHOD("agent_set_max_neighbors", "agent", "count"), &NavigationServer2D::agent_set_max_neighbors); + ClassDB::bind_method(D_METHOD("agent_set_time_horizon_agents", "agent", "time_horizon"), &NavigationServer2D::agent_set_time_horizon_agents); + ClassDB::bind_method(D_METHOD("agent_set_time_horizon_obstacles", "agent", "time_horizon"), &NavigationServer2D::agent_set_time_horizon_obstacles); + ClassDB::bind_method(D_METHOD("agent_set_radius", "agent", "radius"), &NavigationServer2D::agent_set_radius); + ClassDB::bind_method(D_METHOD("agent_set_max_speed", "agent", "max_speed"), &NavigationServer2D::agent_set_max_speed); + ClassDB::bind_method(D_METHOD("agent_set_velocity_forced", "agent", "velocity"), &NavigationServer2D::agent_set_velocity_forced); + ClassDB::bind_method(D_METHOD("agent_set_velocity", "agent", "velocity"), &NavigationServer2D::agent_set_velocity); + ClassDB::bind_method(D_METHOD("agent_set_position", "agent", "position"), &NavigationServer2D::agent_set_position); + ClassDB::bind_method(D_METHOD("agent_is_map_changed", "agent"), &NavigationServer2D::agent_is_map_changed); + ClassDB::bind_method(D_METHOD("agent_set_avoidance_callback", "agent", "callback"), &NavigationServer2D::agent_set_avoidance_callback); + ClassDB::bind_method(D_METHOD("agent_set_avoidance_layers", "agent", "layers"), &NavigationServer2D::agent_set_avoidance_layers); + ClassDB::bind_method(D_METHOD("agent_set_avoidance_mask", "agent", "mask"), &NavigationServer2D::agent_set_avoidance_mask); + ClassDB::bind_method(D_METHOD("agent_set_avoidance_priority", "agent", "priority"), &NavigationServer2D::agent_set_avoidance_priority); -#define FORWARD_5_R_C(CONV_R, FUNC_NAME, T_0, D_0, T_1, D_1, T_2, D_2, T_3, D_3, T_4, D_4, CONV_0, CONV_1, CONV_2, CONV_3, CONV_4) \ - NavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3, T_4 D_4) \ - const { \ - return CONV_R(NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1), CONV_2(D_2), CONV_3(D_3), CONV_4(D_4))); \ - } + ClassDB::bind_method(D_METHOD("obstacle_create"), &NavigationServer2D::obstacle_create); + ClassDB::bind_method(D_METHOD("obstacle_set_avoidance_enabled", "obstacle", "enabled"), &NavigationServer2D::obstacle_set_avoidance_enabled); + ClassDB::bind_method(D_METHOD("obstacle_get_avoidance_enabled", "obstacle"), &NavigationServer2D::obstacle_get_avoidance_enabled); + ClassDB::bind_method(D_METHOD("obstacle_set_map", "obstacle", "map"), &NavigationServer2D::obstacle_set_map); + ClassDB::bind_method(D_METHOD("obstacle_get_map", "obstacle"), &NavigationServer2D::obstacle_get_map); + ClassDB::bind_method(D_METHOD("obstacle_set_paused", "obstacle", "paused"), &NavigationServer2D::obstacle_set_paused); + ClassDB::bind_method(D_METHOD("obstacle_get_paused", "obstacle"), &NavigationServer2D::obstacle_get_paused); + ClassDB::bind_method(D_METHOD("obstacle_set_radius", "obstacle", "radius"), &NavigationServer2D::obstacle_set_radius); + ClassDB::bind_method(D_METHOD("obstacle_set_velocity", "obstacle", "velocity"), &NavigationServer2D::obstacle_set_velocity); + ClassDB::bind_method(D_METHOD("obstacle_set_position", "obstacle", "position"), &NavigationServer2D::obstacle_set_position); + ClassDB::bind_method(D_METHOD("obstacle_set_vertices", "obstacle", "vertices"), &NavigationServer2D::obstacle_set_vertices); + ClassDB::bind_method(D_METHOD("obstacle_set_avoidance_layers", "obstacle", "layers"), &NavigationServer2D::obstacle_set_avoidance_layers); -static RID rid_to_rid(const RID d) { - return d; + ClassDB::bind_method(D_METHOD("parse_source_geometry_data", "navigation_polygon", "source_geometry_data", "root_node", "callback"), &NavigationServer2D::parse_source_geometry_data, DEFVAL(Callable())); + ClassDB::bind_method(D_METHOD("bake_from_source_geometry_data", "navigation_polygon", "source_geometry_data", "callback"), &NavigationServer2D::bake_from_source_geometry_data, DEFVAL(Callable())); + ClassDB::bind_method(D_METHOD("bake_from_source_geometry_data_async", "navigation_polygon", "source_geometry_data", "callback"), &NavigationServer2D::bake_from_source_geometry_data_async, DEFVAL(Callable())); + + ClassDB::bind_method(D_METHOD("free_rid", "rid"), &NavigationServer2D::free); + + ClassDB::bind_method(D_METHOD("set_debug_enabled", "enabled"), &NavigationServer2D::set_debug_enabled); + ClassDB::bind_method(D_METHOD("get_debug_enabled"), &NavigationServer2D::get_debug_enabled); + + ADD_SIGNAL(MethodInfo("map_changed", PropertyInfo(Variant::RID, "map"))); + + ADD_SIGNAL(MethodInfo("navigation_debug_changed")); } -static bool bool_to_bool(const bool d) { - return d; +NavigationServer2D *NavigationServer2D::get_singleton() { + return singleton; } -static int int_to_int(const int d) { - return d; +NavigationServer2D::NavigationServer2D() { + ERR_FAIL_COND(singleton != nullptr); + singleton = this; + ERR_FAIL_NULL_MSG(NavigationServer3D::get_singleton(), "The Navigation3D singleton should be initialized before the 2D one."); + NavigationServer3D::get_singleton()->connect("map_changed", callable_mp(this, &NavigationServer2D::_emit_map_changed)); + +#ifdef DEBUG_ENABLED + NavigationServer3D::get_singleton()->connect(SNAME("navigation_debug_changed"), callable_mp(this, &NavigationServer2D::_emit_navigation_debug_changed_signal)); +#endif // DEBUG_ENABLED } -static uint32_t uint32_to_uint32(const uint32_t d) { - return d; +#ifdef DEBUG_ENABLED +void NavigationServer2D::_emit_navigation_debug_changed_signal() { + emit_signal(SNAME("navigation_debug_changed")); } +#endif // DEBUG_ENABLED -static real_t real_to_real(const real_t d) { - return d; -} - -static Vector3 v2_to_v3(const Vector2 d) { - return Vector3(d.x, 0.0, d.y); -} - -static Vector2 v3_to_v2(const Vector3 &d) { - return Vector2(d.x, d.z); -} - -static Vector vector_v2_to_v3(const Vector &d) { - Vector nd; - nd.resize(d.size()); - for (int i(0); i < nd.size(); i++) { - nd.write[i] = v2_to_v3(d[i]); - } - return nd; -} - -static Vector vector_v3_to_v2(const Vector &d) { - Vector nd; - nd.resize(d.size()); - for (int i(0); i < nd.size(); i++) { - nd.write[i] = v3_to_v2(d[i]); - } - return nd; -} - -static Transform3D trf2_to_trf3(const Transform2D &d) { - Vector3 o(v2_to_v3(d.get_origin())); - Basis b; - b.rotate(Vector3(0, -1, 0), d.get_rotation()); - b.scale(v2_to_v3(d.get_scale())); - return Transform3D(b, o); -} - -static ObjectID id_to_id(const ObjectID &id) { - return id; -} - -static Callable callable_to_callable(const Callable &c) { - return c; -} - -static Ref poly_to_mesh(Ref d) { - if (d.is_valid()) { - return d->get_navigation_mesh(); - } else { - return Ref(); - } +NavigationServer2D::~NavigationServer2D() { + singleton = nullptr; } void NavigationServer2D::_emit_map_changed(RID p_map) { @@ -363,286 +383,18 @@ bool NavigationServer2D::get_debug_navigation_avoidance_enable_obstacles_static( } #endif // DEBUG_ENABLED -void NavigationServer2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_maps"), &NavigationServer2D::get_maps); +/////////////////////////////////////////////////////// - ClassDB::bind_method(D_METHOD("map_create"), &NavigationServer2D::map_create); - ClassDB::bind_method(D_METHOD("map_set_active", "map", "active"), &NavigationServer2D::map_set_active); - ClassDB::bind_method(D_METHOD("map_is_active", "map"), &NavigationServer2D::map_is_active); - ClassDB::bind_method(D_METHOD("map_set_cell_size", "map", "cell_size"), &NavigationServer2D::map_set_cell_size); - ClassDB::bind_method(D_METHOD("map_get_cell_size", "map"), &NavigationServer2D::map_get_cell_size); - ClassDB::bind_method(D_METHOD("map_set_use_edge_connections", "map", "enabled"), &NavigationServer2D::map_set_use_edge_connections); - ClassDB::bind_method(D_METHOD("map_get_use_edge_connections", "map"), &NavigationServer2D::map_get_use_edge_connections); - ClassDB::bind_method(D_METHOD("map_set_edge_connection_margin", "map", "margin"), &NavigationServer2D::map_set_edge_connection_margin); - ClassDB::bind_method(D_METHOD("map_get_edge_connection_margin", "map"), &NavigationServer2D::map_get_edge_connection_margin); - ClassDB::bind_method(D_METHOD("map_set_link_connection_radius", "map", "radius"), &NavigationServer2D::map_set_link_connection_radius); - ClassDB::bind_method(D_METHOD("map_get_link_connection_radius", "map"), &NavigationServer2D::map_get_link_connection_radius); - ClassDB::bind_method(D_METHOD("map_get_path", "map", "origin", "destination", "optimize", "navigation_layers"), &NavigationServer2D::map_get_path, DEFVAL(1)); - ClassDB::bind_method(D_METHOD("map_get_closest_point", "map", "to_point"), &NavigationServer2D::map_get_closest_point); - ClassDB::bind_method(D_METHOD("map_get_closest_point_owner", "map", "to_point"), &NavigationServer2D::map_get_closest_point_owner); +NavigationServer2DCallback NavigationServer2DManager::create_callback = nullptr; - ClassDB::bind_method(D_METHOD("map_get_links", "map"), &NavigationServer2D::map_get_links); - ClassDB::bind_method(D_METHOD("map_get_regions", "map"), &NavigationServer2D::map_get_regions); - ClassDB::bind_method(D_METHOD("map_get_agents", "map"), &NavigationServer2D::map_get_agents); - ClassDB::bind_method(D_METHOD("map_get_obstacles", "map"), &NavigationServer2D::map_get_obstacles); - - ClassDB::bind_method(D_METHOD("map_force_update", "map"), &NavigationServer2D::map_force_update); - - ClassDB::bind_method(D_METHOD("query_path", "parameters", "result"), &NavigationServer2D::query_path); - - ClassDB::bind_method(D_METHOD("region_create"), &NavigationServer2D::region_create); - ClassDB::bind_method(D_METHOD("region_set_enabled", "region", "enabled"), &NavigationServer2D::region_set_enabled); - ClassDB::bind_method(D_METHOD("region_get_enabled", "region"), &NavigationServer2D::region_get_enabled); - ClassDB::bind_method(D_METHOD("region_set_use_edge_connections", "region", "enabled"), &NavigationServer2D::region_set_use_edge_connections); - ClassDB::bind_method(D_METHOD("region_get_use_edge_connections", "region"), &NavigationServer2D::region_get_use_edge_connections); - ClassDB::bind_method(D_METHOD("region_set_enter_cost", "region", "enter_cost"), &NavigationServer2D::region_set_enter_cost); - ClassDB::bind_method(D_METHOD("region_get_enter_cost", "region"), &NavigationServer2D::region_get_enter_cost); - ClassDB::bind_method(D_METHOD("region_set_travel_cost", "region", "travel_cost"), &NavigationServer2D::region_set_travel_cost); - ClassDB::bind_method(D_METHOD("region_get_travel_cost", "region"), &NavigationServer2D::region_get_travel_cost); - ClassDB::bind_method(D_METHOD("region_set_owner_id", "region", "owner_id"), &NavigationServer2D::region_set_owner_id); - ClassDB::bind_method(D_METHOD("region_get_owner_id", "region"), &NavigationServer2D::region_get_owner_id); - ClassDB::bind_method(D_METHOD("region_owns_point", "region", "point"), &NavigationServer2D::region_owns_point); - ClassDB::bind_method(D_METHOD("region_set_map", "region", "map"), &NavigationServer2D::region_set_map); - ClassDB::bind_method(D_METHOD("region_get_map", "region"), &NavigationServer2D::region_get_map); - ClassDB::bind_method(D_METHOD("region_set_navigation_layers", "region", "navigation_layers"), &NavigationServer2D::region_set_navigation_layers); - ClassDB::bind_method(D_METHOD("region_get_navigation_layers", "region"), &NavigationServer2D::region_get_navigation_layers); - ClassDB::bind_method(D_METHOD("region_set_transform", "region", "transform"), &NavigationServer2D::region_set_transform); - ClassDB::bind_method(D_METHOD("region_set_navigation_polygon", "region", "navigation_polygon"), &NavigationServer2D::region_set_navigation_polygon); - ClassDB::bind_method(D_METHOD("region_get_connections_count", "region"), &NavigationServer2D::region_get_connections_count); - ClassDB::bind_method(D_METHOD("region_get_connection_pathway_start", "region", "connection"), &NavigationServer2D::region_get_connection_pathway_start); - ClassDB::bind_method(D_METHOD("region_get_connection_pathway_end", "region", "connection"), &NavigationServer2D::region_get_connection_pathway_end); - - ClassDB::bind_method(D_METHOD("link_create"), &NavigationServer2D::link_create); - ClassDB::bind_method(D_METHOD("link_set_map", "link", "map"), &NavigationServer2D::link_set_map); - ClassDB::bind_method(D_METHOD("link_get_map", "link"), &NavigationServer2D::link_get_map); - ClassDB::bind_method(D_METHOD("link_set_enabled", "link", "enabled"), &NavigationServer2D::link_set_enabled); - ClassDB::bind_method(D_METHOD("link_get_enabled", "link"), &NavigationServer2D::link_get_enabled); - ClassDB::bind_method(D_METHOD("link_set_bidirectional", "link", "bidirectional"), &NavigationServer2D::link_set_bidirectional); - ClassDB::bind_method(D_METHOD("link_is_bidirectional", "link"), &NavigationServer2D::link_is_bidirectional); - ClassDB::bind_method(D_METHOD("link_set_navigation_layers", "link", "navigation_layers"), &NavigationServer2D::link_set_navigation_layers); - ClassDB::bind_method(D_METHOD("link_get_navigation_layers", "link"), &NavigationServer2D::link_get_navigation_layers); - ClassDB::bind_method(D_METHOD("link_set_start_position", "link", "position"), &NavigationServer2D::link_set_start_position); - ClassDB::bind_method(D_METHOD("link_get_start_position", "link"), &NavigationServer2D::link_get_start_position); - ClassDB::bind_method(D_METHOD("link_set_end_position", "link", "position"), &NavigationServer2D::link_set_end_position); - ClassDB::bind_method(D_METHOD("link_get_end_position", "link"), &NavigationServer2D::link_get_end_position); - ClassDB::bind_method(D_METHOD("link_set_enter_cost", "link", "enter_cost"), &NavigationServer2D::link_set_enter_cost); - ClassDB::bind_method(D_METHOD("link_get_enter_cost", "link"), &NavigationServer2D::link_get_enter_cost); - ClassDB::bind_method(D_METHOD("link_set_travel_cost", "link", "travel_cost"), &NavigationServer2D::link_set_travel_cost); - ClassDB::bind_method(D_METHOD("link_get_travel_cost", "link"), &NavigationServer2D::link_get_travel_cost); - ClassDB::bind_method(D_METHOD("link_set_owner_id", "link", "owner_id"), &NavigationServer2D::link_set_owner_id); - ClassDB::bind_method(D_METHOD("link_get_owner_id", "link"), &NavigationServer2D::link_get_owner_id); - - ClassDB::bind_method(D_METHOD("agent_create"), &NavigationServer2D::agent_create); - ClassDB::bind_method(D_METHOD("agent_set_avoidance_enabled", "agent", "enabled"), &NavigationServer2D::agent_set_avoidance_enabled); - ClassDB::bind_method(D_METHOD("agent_get_avoidance_enabled", "agent"), &NavigationServer2D::agent_get_avoidance_enabled); - ClassDB::bind_method(D_METHOD("agent_set_map", "agent", "map"), &NavigationServer2D::agent_set_map); - ClassDB::bind_method(D_METHOD("agent_get_map", "agent"), &NavigationServer2D::agent_get_map); - ClassDB::bind_method(D_METHOD("agent_set_paused", "agent", "paused"), &NavigationServer2D::agent_set_paused); - ClassDB::bind_method(D_METHOD("agent_get_paused", "agent"), &NavigationServer2D::agent_get_paused); - ClassDB::bind_method(D_METHOD("agent_set_neighbor_distance", "agent", "distance"), &NavigationServer2D::agent_set_neighbor_distance); - ClassDB::bind_method(D_METHOD("agent_set_max_neighbors", "agent", "count"), &NavigationServer2D::agent_set_max_neighbors); - ClassDB::bind_method(D_METHOD("agent_set_time_horizon_agents", "agent", "time_horizon"), &NavigationServer2D::agent_set_time_horizon_agents); - ClassDB::bind_method(D_METHOD("agent_set_time_horizon_obstacles", "agent", "time_horizon"), &NavigationServer2D::agent_set_time_horizon_obstacles); - ClassDB::bind_method(D_METHOD("agent_set_radius", "agent", "radius"), &NavigationServer2D::agent_set_radius); - ClassDB::bind_method(D_METHOD("agent_set_max_speed", "agent", "max_speed"), &NavigationServer2D::agent_set_max_speed); - ClassDB::bind_method(D_METHOD("agent_set_velocity_forced", "agent", "velocity"), &NavigationServer2D::agent_set_velocity_forced); - ClassDB::bind_method(D_METHOD("agent_set_velocity", "agent", "velocity"), &NavigationServer2D::agent_set_velocity); - ClassDB::bind_method(D_METHOD("agent_set_position", "agent", "position"), &NavigationServer2D::agent_set_position); - ClassDB::bind_method(D_METHOD("agent_is_map_changed", "agent"), &NavigationServer2D::agent_is_map_changed); - ClassDB::bind_method(D_METHOD("agent_set_avoidance_callback", "agent", "callback"), &NavigationServer2D::agent_set_avoidance_callback); - ClassDB::bind_method(D_METHOD("agent_set_avoidance_layers", "agent", "layers"), &NavigationServer2D::agent_set_avoidance_layers); - ClassDB::bind_method(D_METHOD("agent_set_avoidance_mask", "agent", "mask"), &NavigationServer2D::agent_set_avoidance_mask); - ClassDB::bind_method(D_METHOD("agent_set_avoidance_priority", "agent", "priority"), &NavigationServer2D::agent_set_avoidance_priority); - - ClassDB::bind_method(D_METHOD("obstacle_create"), &NavigationServer2D::obstacle_create); - ClassDB::bind_method(D_METHOD("obstacle_set_avoidance_enabled", "obstacle", "enabled"), &NavigationServer2D::obstacle_set_avoidance_enabled); - ClassDB::bind_method(D_METHOD("obstacle_get_avoidance_enabled", "obstacle"), &NavigationServer2D::obstacle_get_avoidance_enabled); - ClassDB::bind_method(D_METHOD("obstacle_set_map", "obstacle", "map"), &NavigationServer2D::obstacle_set_map); - ClassDB::bind_method(D_METHOD("obstacle_get_map", "obstacle"), &NavigationServer2D::obstacle_get_map); - ClassDB::bind_method(D_METHOD("obstacle_set_paused", "obstacle", "paused"), &NavigationServer2D::obstacle_set_paused); - ClassDB::bind_method(D_METHOD("obstacle_get_paused", "obstacle"), &NavigationServer2D::obstacle_get_paused); - ClassDB::bind_method(D_METHOD("obstacle_set_radius", "obstacle", "radius"), &NavigationServer2D::obstacle_set_radius); - ClassDB::bind_method(D_METHOD("obstacle_set_velocity", "obstacle", "velocity"), &NavigationServer2D::obstacle_set_velocity); - ClassDB::bind_method(D_METHOD("obstacle_set_position", "obstacle", "position"), &NavigationServer2D::obstacle_set_position); - ClassDB::bind_method(D_METHOD("obstacle_set_vertices", "obstacle", "vertices"), &NavigationServer2D::obstacle_set_vertices); - ClassDB::bind_method(D_METHOD("obstacle_set_avoidance_layers", "obstacle", "layers"), &NavigationServer2D::obstacle_set_avoidance_layers); - - ClassDB::bind_method(D_METHOD("free_rid", "rid"), &NavigationServer2D::free); - - ClassDB::bind_method(D_METHOD("set_debug_enabled", "enabled"), &NavigationServer2D::set_debug_enabled); - ClassDB::bind_method(D_METHOD("get_debug_enabled"), &NavigationServer2D::get_debug_enabled); - - ADD_SIGNAL(MethodInfo("map_changed", PropertyInfo(Variant::RID, "map"))); - - ADD_SIGNAL(MethodInfo("navigation_debug_changed")); +void NavigationServer2DManager::set_default_server(NavigationServer2DCallback p_callback) { + create_callback = p_callback; } -NavigationServer2D::NavigationServer2D() { - singleton = this; - ERR_FAIL_COND_MSG(!NavigationServer3D::get_singleton(), "The Navigation3D singleton should be initialized before the 2D one."); - NavigationServer3D::get_singleton()->connect("map_changed", callable_mp(this, &NavigationServer2D::_emit_map_changed)); +NavigationServer2D *NavigationServer2DManager::new_default_server() { + if (create_callback == nullptr) { + return nullptr; + } -#ifdef DEBUG_ENABLED - NavigationServer3D::get_singleton()->connect(SNAME("navigation_debug_changed"), callable_mp(this, &NavigationServer2D::_emit_navigation_debug_changed_signal)); -#endif // DEBUG_ENABLED -} - -#ifdef DEBUG_ENABLED -void NavigationServer2D::_emit_navigation_debug_changed_signal() { - emit_signal(SNAME("navigation_debug_changed")); -} -#endif // DEBUG_ENABLED - -NavigationServer2D::~NavigationServer2D() { - singleton = nullptr; -} - -TypedArray FORWARD_0_C(get_maps); - -TypedArray FORWARD_1_C(map_get_links, RID, p_map, rid_to_rid); - -TypedArray FORWARD_1_C(map_get_regions, RID, p_map, rid_to_rid); - -TypedArray FORWARD_1_C(map_get_agents, RID, p_map, rid_to_rid); - -TypedArray FORWARD_1_C(map_get_obstacles, RID, p_map, rid_to_rid); - -RID FORWARD_1_C(region_get_map, RID, p_region, rid_to_rid); - -RID FORWARD_1_C(agent_get_map, RID, p_agent, rid_to_rid); - -RID FORWARD_0(map_create); - -void FORWARD_2(map_set_active, RID, p_map, bool, p_active, rid_to_rid, bool_to_bool); - -bool FORWARD_1_C(map_is_active, RID, p_map, rid_to_rid); - -void NavigationServer2D::map_force_update(RID p_map) { - NavigationServer3D::get_singleton()->map_force_update(p_map); -} - -void FORWARD_2(map_set_cell_size, RID, p_map, real_t, p_cell_size, rid_to_rid, real_to_real); -real_t FORWARD_1_C(map_get_cell_size, RID, p_map, rid_to_rid); - -void FORWARD_2(map_set_use_edge_connections, RID, p_map, bool, p_enabled, rid_to_rid, bool_to_bool); -bool FORWARD_1_C(map_get_use_edge_connections, RID, p_map, rid_to_rid); - -void FORWARD_2(map_set_edge_connection_margin, RID, p_map, real_t, p_connection_margin, rid_to_rid, real_to_real); -real_t FORWARD_1_C(map_get_edge_connection_margin, RID, p_map, rid_to_rid); - -void FORWARD_2(map_set_link_connection_radius, RID, p_map, real_t, p_connection_radius, rid_to_rid, real_to_real); -real_t FORWARD_1_C(map_get_link_connection_radius, RID, p_map, rid_to_rid); - -Vector FORWARD_5_R_C(vector_v3_to_v2, map_get_path, RID, p_map, Vector2, p_origin, Vector2, p_destination, bool, p_optimize, uint32_t, p_layers, rid_to_rid, v2_to_v3, v2_to_v3, bool_to_bool, uint32_to_uint32); - -Vector2 FORWARD_2_R_C(v3_to_v2, map_get_closest_point, RID, p_map, const Vector2 &, p_point, rid_to_rid, v2_to_v3); -RID FORWARD_2_C(map_get_closest_point_owner, RID, p_map, const Vector2 &, p_point, rid_to_rid, v2_to_v3); - -RID FORWARD_0(region_create); - -void FORWARD_2(region_set_enabled, RID, p_region, bool, p_enabled, rid_to_rid, bool_to_bool); -bool FORWARD_1_C(region_get_enabled, RID, p_region, rid_to_rid); -void FORWARD_2(region_set_use_edge_connections, RID, p_region, bool, p_enabled, rid_to_rid, bool_to_bool); -bool FORWARD_1_C(region_get_use_edge_connections, RID, p_region, rid_to_rid); - -void FORWARD_2(region_set_enter_cost, RID, p_region, real_t, p_enter_cost, rid_to_rid, real_to_real); -real_t FORWARD_1_C(region_get_enter_cost, RID, p_region, rid_to_rid); -void FORWARD_2(region_set_travel_cost, RID, p_region, real_t, p_travel_cost, rid_to_rid, real_to_real); -real_t FORWARD_1_C(region_get_travel_cost, RID, p_region, rid_to_rid); -void FORWARD_2(region_set_owner_id, RID, p_region, ObjectID, p_owner_id, rid_to_rid, id_to_id); -ObjectID FORWARD_1_C(region_get_owner_id, RID, p_region, rid_to_rid); -bool FORWARD_2_C(region_owns_point, RID, p_region, const Vector2 &, p_point, rid_to_rid, v2_to_v3); - -void FORWARD_2(region_set_map, RID, p_region, RID, p_map, rid_to_rid, rid_to_rid); -void FORWARD_2(region_set_navigation_layers, RID, p_region, uint32_t, p_navigation_layers, rid_to_rid, uint32_to_uint32); -uint32_t FORWARD_1_C(region_get_navigation_layers, RID, p_region, rid_to_rid); -void FORWARD_2(region_set_transform, RID, p_region, Transform2D, p_transform, rid_to_rid, trf2_to_trf3); - -void NavigationServer2D::region_set_navigation_polygon(RID p_region, Ref p_navigation_polygon) { - NavigationServer3D::get_singleton()->region_set_navigation_mesh(p_region, poly_to_mesh(p_navigation_polygon)); -} - -int FORWARD_1_C(region_get_connections_count, RID, p_region, rid_to_rid); -Vector2 FORWARD_2_R_C(v3_to_v2, region_get_connection_pathway_start, RID, p_region, int, p_connection_id, rid_to_rid, int_to_int); -Vector2 FORWARD_2_R_C(v3_to_v2, region_get_connection_pathway_end, RID, p_region, int, p_connection_id, rid_to_rid, int_to_int); - -RID FORWARD_0(link_create); - -void FORWARD_2(link_set_map, RID, p_link, RID, p_map, rid_to_rid, rid_to_rid); -RID FORWARD_1_C(link_get_map, RID, p_link, rid_to_rid); -void FORWARD_2(link_set_enabled, RID, p_link, bool, p_enabled, rid_to_rid, bool_to_bool); -bool FORWARD_1_C(link_get_enabled, RID, p_link, rid_to_rid); -void FORWARD_2(link_set_bidirectional, RID, p_link, bool, p_bidirectional, rid_to_rid, bool_to_bool); -bool FORWARD_1_C(link_is_bidirectional, RID, p_link, rid_to_rid); -void FORWARD_2(link_set_navigation_layers, RID, p_link, uint32_t, p_navigation_layers, rid_to_rid, uint32_to_uint32); -uint32_t FORWARD_1_C(link_get_navigation_layers, RID, p_link, rid_to_rid); -void FORWARD_2(link_set_start_position, RID, p_link, Vector2, p_position, rid_to_rid, v2_to_v3); -Vector2 FORWARD_1_R_C(v3_to_v2, link_get_start_position, RID, p_link, rid_to_rid); -void FORWARD_2(link_set_end_position, RID, p_link, Vector2, p_position, rid_to_rid, v2_to_v3); -Vector2 FORWARD_1_R_C(v3_to_v2, link_get_end_position, RID, p_link, rid_to_rid); -void FORWARD_2(link_set_enter_cost, RID, p_link, real_t, p_enter_cost, rid_to_rid, real_to_real); -real_t FORWARD_1_C(link_get_enter_cost, RID, p_link, rid_to_rid); -void FORWARD_2(link_set_travel_cost, RID, p_link, real_t, p_travel_cost, rid_to_rid, real_to_real); -real_t FORWARD_1_C(link_get_travel_cost, RID, p_link, rid_to_rid); -void FORWARD_2(link_set_owner_id, RID, p_link, ObjectID, p_owner_id, rid_to_rid, id_to_id); -ObjectID FORWARD_1_C(link_get_owner_id, RID, p_link, rid_to_rid); - -RID NavigationServer2D::agent_create() { - RID agent = NavigationServer3D::get_singleton()->agent_create(); - return agent; -} - -void FORWARD_2(agent_set_avoidance_enabled, RID, p_agent, bool, p_enabled, rid_to_rid, bool_to_bool); -bool FORWARD_1_C(agent_get_avoidance_enabled, RID, p_agent, rid_to_rid); -void FORWARD_2(agent_set_map, RID, p_agent, RID, p_map, rid_to_rid, rid_to_rid); -void FORWARD_2(agent_set_neighbor_distance, RID, p_agent, real_t, p_dist, rid_to_rid, real_to_real); -void FORWARD_2(agent_set_max_neighbors, RID, p_agent, int, p_count, rid_to_rid, int_to_int); -void FORWARD_2(agent_set_time_horizon_agents, RID, p_agent, real_t, p_time_horizon, rid_to_rid, real_to_real); -void FORWARD_2(agent_set_time_horizon_obstacles, RID, p_agent, real_t, p_time_horizon, rid_to_rid, real_to_real); -void FORWARD_2(agent_set_radius, RID, p_agent, real_t, p_radius, rid_to_rid, real_to_real); -void FORWARD_2(agent_set_max_speed, RID, p_agent, real_t, p_max_speed, rid_to_rid, real_to_real); -void FORWARD_2(agent_set_velocity_forced, RID, p_agent, Vector2, p_velocity, rid_to_rid, v2_to_v3); -void FORWARD_2(agent_set_velocity, RID, p_agent, Vector2, p_velocity, rid_to_rid, v2_to_v3); -void FORWARD_2(agent_set_position, RID, p_agent, Vector2, p_position, rid_to_rid, v2_to_v3); - -bool FORWARD_1_C(agent_is_map_changed, RID, p_agent, rid_to_rid); -void FORWARD_2(agent_set_paused, RID, p_agent, bool, p_paused, rid_to_rid, bool_to_bool); -bool FORWARD_1_C(agent_get_paused, RID, p_agent, rid_to_rid); - -void FORWARD_1(free, RID, p_object, rid_to_rid); -void FORWARD_2(agent_set_avoidance_callback, RID, p_agent, Callable, p_callback, rid_to_rid, callable_to_callable); - -void FORWARD_2(agent_set_avoidance_layers, RID, p_agent, uint32_t, p_layers, rid_to_rid, uint32_to_uint32); -void FORWARD_2(agent_set_avoidance_mask, RID, p_agent, uint32_t, p_mask, rid_to_rid, uint32_to_uint32); -void FORWARD_2(agent_set_avoidance_priority, RID, p_agent, real_t, p_priority, rid_to_rid, real_to_real); - -RID NavigationServer2D::obstacle_create() { - RID obstacle = NavigationServer3D::get_singleton()->obstacle_create(); - return obstacle; -} -void FORWARD_2(obstacle_set_avoidance_enabled, RID, p_obstacle, bool, p_enabled, rid_to_rid, bool_to_bool); -bool FORWARD_1_C(obstacle_get_avoidance_enabled, RID, p_obstacle, rid_to_rid); -void FORWARD_2(obstacle_set_map, RID, p_obstacle, RID, p_map, rid_to_rid, rid_to_rid); -RID FORWARD_1_C(obstacle_get_map, RID, p_obstacle, rid_to_rid); -void FORWARD_2(obstacle_set_paused, RID, p_obstacle, bool, p_paused, rid_to_rid, bool_to_bool); -bool FORWARD_1_C(obstacle_get_paused, RID, p_obstacle, rid_to_rid); -void FORWARD_2(obstacle_set_radius, RID, p_obstacle, real_t, p_radius, rid_to_rid, real_to_real); -void FORWARD_2(obstacle_set_velocity, RID, p_obstacle, Vector2, p_velocity, rid_to_rid, v2_to_v3); -void FORWARD_2(obstacle_set_position, RID, p_obstacle, Vector2, p_position, rid_to_rid, v2_to_v3); -void FORWARD_2(obstacle_set_avoidance_layers, RID, p_obstacle, uint32_t, p_layers, rid_to_rid, uint32_to_uint32); - -void NavigationServer2D::obstacle_set_vertices(RID p_obstacle, const Vector &p_vertices) { - NavigationServer3D::get_singleton()->obstacle_set_vertices(p_obstacle, vector_v2_to_v3(p_vertices)); -} - -void NavigationServer2D::query_path(const Ref &p_query_parameters, Ref p_query_result) const { - ERR_FAIL_COND(!p_query_parameters.is_valid()); - ERR_FAIL_COND(!p_query_result.is_valid()); - - const NavigationUtilities::PathQueryResult _query_result = NavigationServer3D::get_singleton()->_query_path(p_query_parameters->get_parameters()); - - p_query_result->set_path(vector_v3_to_v2(_query_result.path)); - p_query_result->set_path_types(_query_result.path_types); - p_query_result->set_path_rids(_query_result.path_rids); - p_query_result->set_path_owner_ids(_query_result.path_owner_ids); + return create_callback(); } diff --git a/servers/navigation_server_2d.h b/servers/navigation_server_2d.h index c78edaf8788..0afd794c26c 100644 --- a/servers/navigation_server_2d.h +++ b/servers/navigation_server_2d.h @@ -34,10 +34,15 @@ #include "core/object/class_db.h" #include "core/templates/rid.h" +#include "scene/resources/navigation_mesh_source_geometry_data_2d.h" #include "scene/resources/navigation_polygon.h" #include "servers/navigation/navigation_path_query_parameters_2d.h" #include "servers/navigation/navigation_path_query_result_2d.h" +#ifdef CLIPPER2_ENABLED +class NavMeshGenerator2D; +#endif // CLIPPER2_ENABLED + // This server exposes the `NavigationServer3D` features in the 2D world. class NavigationServer2D : public Object { GDCLASS(NavigationServer2D, Object); @@ -51,145 +56,145 @@ protected: public: /// Thread safe, can be used across many threads. - static NavigationServer2D *get_singleton() { return singleton; } + static NavigationServer2D *get_singleton(); - virtual TypedArray get_maps() const; + virtual TypedArray get_maps() const = 0; /// Create a new map. - virtual RID map_create(); + virtual RID map_create() = 0; /// Set map active. - virtual void map_set_active(RID p_map, bool p_active); + virtual void map_set_active(RID p_map, bool p_active) = 0; /// Returns true if the map is active. - virtual bool map_is_active(RID p_map) const; + virtual bool map_is_active(RID p_map) const = 0; /// Set the map cell size used to weld the navigation mesh polygons. - virtual void map_set_cell_size(RID p_map, real_t p_cell_size); + virtual void map_set_cell_size(RID p_map, real_t p_cell_size) = 0; /// Returns the map cell size. - virtual real_t map_get_cell_size(RID p_map) const; + virtual real_t map_get_cell_size(RID p_map) const = 0; - virtual void map_set_use_edge_connections(RID p_map, bool p_enabled); - virtual bool map_get_use_edge_connections(RID p_map) const; + virtual void map_set_use_edge_connections(RID p_map, bool p_enabled) = 0; + virtual bool map_get_use_edge_connections(RID p_map) const = 0; /// Set the map edge connection margin used to weld the compatible region edges. - virtual void map_set_edge_connection_margin(RID p_map, real_t p_connection_margin); + virtual void map_set_edge_connection_margin(RID p_map, real_t p_connection_margin) = 0; /// Returns the edge connection margin of this map. - virtual real_t map_get_edge_connection_margin(RID p_map) const; + virtual real_t map_get_edge_connection_margin(RID p_map) const = 0; /// Set the map link connection radius used to attach links to the nav mesh. - virtual void map_set_link_connection_radius(RID p_map, real_t p_connection_radius); + virtual void map_set_link_connection_radius(RID p_map, real_t p_connection_radius) = 0; /// Returns the link connection radius of this map. - virtual real_t map_get_link_connection_radius(RID p_map) const; + virtual real_t map_get_link_connection_radius(RID p_map) const = 0; /// Returns the navigation path to reach the destination from the origin. - virtual Vector map_get_path(RID p_map, Vector2 p_origin, Vector2 p_destination, bool p_optimize, uint32_t p_navigation_layers = 1) const; + virtual Vector map_get_path(RID p_map, Vector2 p_origin, Vector2 p_destination, bool p_optimize, uint32_t p_navigation_layers = 1) const = 0; - virtual Vector2 map_get_closest_point(RID p_map, const Vector2 &p_point) const; - virtual RID map_get_closest_point_owner(RID p_map, const Vector2 &p_point) const; + virtual Vector2 map_get_closest_point(RID p_map, const Vector2 &p_point) const = 0; + virtual RID map_get_closest_point_owner(RID p_map, const Vector2 &p_point) const = 0; - virtual TypedArray map_get_links(RID p_map) const; - virtual TypedArray map_get_regions(RID p_map) const; - virtual TypedArray map_get_agents(RID p_map) const; - virtual TypedArray map_get_obstacles(RID p_map) const; + virtual TypedArray map_get_links(RID p_map) const = 0; + virtual TypedArray map_get_regions(RID p_map) const = 0; + virtual TypedArray map_get_agents(RID p_map) const = 0; + virtual TypedArray map_get_obstacles(RID p_map) const = 0; - virtual void map_force_update(RID p_map); + virtual void map_force_update(RID p_map) = 0; /// Creates a new region. - virtual RID region_create(); + virtual RID region_create() = 0; - virtual void region_set_enabled(RID p_region, bool p_enabled); - virtual bool region_get_enabled(RID p_region) const; + virtual void region_set_enabled(RID p_region, bool p_enabled) = 0; + virtual bool region_get_enabled(RID p_region) const = 0; - virtual void region_set_use_edge_connections(RID p_region, bool p_enabled); - virtual bool region_get_use_edge_connections(RID p_region) const; + virtual void region_set_use_edge_connections(RID p_region, bool p_enabled) = 0; + virtual bool region_get_use_edge_connections(RID p_region) const = 0; /// Set the enter_cost of a region - virtual void region_set_enter_cost(RID p_region, real_t p_enter_cost); - virtual real_t region_get_enter_cost(RID p_region) const; + virtual void region_set_enter_cost(RID p_region, real_t p_enter_cost) = 0; + virtual real_t region_get_enter_cost(RID p_region) const = 0; /// Set the travel_cost of a region - virtual void region_set_travel_cost(RID p_region, real_t p_travel_cost); - virtual real_t region_get_travel_cost(RID p_region) const; + virtual void region_set_travel_cost(RID p_region, real_t p_travel_cost) = 0; + virtual real_t region_get_travel_cost(RID p_region) const = 0; /// Set the node which manages this region. - virtual void region_set_owner_id(RID p_region, ObjectID p_owner_id); - virtual ObjectID region_get_owner_id(RID p_region) const; + virtual void region_set_owner_id(RID p_region, ObjectID p_owner_id) = 0; + virtual ObjectID region_get_owner_id(RID p_region) const = 0; - virtual bool region_owns_point(RID p_region, const Vector2 &p_point) const; + virtual bool region_owns_point(RID p_region, const Vector2 &p_point) const = 0; /// Set the map of this region. - virtual void region_set_map(RID p_region, RID p_map); - virtual RID region_get_map(RID p_region) const; + virtual void region_set_map(RID p_region, RID p_map) = 0; + virtual RID region_get_map(RID p_region) const = 0; /// Set the region's layers - virtual void region_set_navigation_layers(RID p_region, uint32_t p_navigation_layers); - virtual uint32_t region_get_navigation_layers(RID p_region) const; + virtual void region_set_navigation_layers(RID p_region, uint32_t p_navigation_layers) = 0; + virtual uint32_t region_get_navigation_layers(RID p_region) const = 0; /// Set the global transformation of this region. - virtual void region_set_transform(RID p_region, Transform2D p_transform); + virtual void region_set_transform(RID p_region, Transform2D p_transform) = 0; /// Set the navigation poly of this region. - virtual void region_set_navigation_polygon(RID p_region, Ref p_navigation_polygon); + virtual void region_set_navigation_polygon(RID p_region, Ref p_navigation_polygon) = 0; /// Get a list of a region's connection to other regions. - virtual int region_get_connections_count(RID p_region) const; - virtual Vector2 region_get_connection_pathway_start(RID p_region, int p_connection_id) const; - virtual Vector2 region_get_connection_pathway_end(RID p_region, int p_connection_id) const; + virtual int region_get_connections_count(RID p_region) const = 0; + virtual Vector2 region_get_connection_pathway_start(RID p_region, int p_connection_id) const = 0; + virtual Vector2 region_get_connection_pathway_end(RID p_region, int p_connection_id) const = 0; /// Creates a new link between positions in the nav map. - virtual RID link_create(); + virtual RID link_create() = 0; /// Set the map of this link. - virtual void link_set_map(RID p_link, RID p_map); - virtual RID link_get_map(RID p_link) const; + virtual void link_set_map(RID p_link, RID p_map) = 0; + virtual RID link_get_map(RID p_link) const = 0; - virtual void link_set_enabled(RID p_link, bool p_enabled); - virtual bool link_get_enabled(RID p_link) const; + virtual void link_set_enabled(RID p_link, bool p_enabled) = 0; + virtual bool link_get_enabled(RID p_link) const = 0; /// Set whether this link travels in both directions. - virtual void link_set_bidirectional(RID p_link, bool p_bidirectional); - virtual bool link_is_bidirectional(RID p_link) const; + virtual void link_set_bidirectional(RID p_link, bool p_bidirectional) = 0; + virtual bool link_is_bidirectional(RID p_link) const = 0; /// Set the link's layers. - virtual void link_set_navigation_layers(RID p_link, uint32_t p_navigation_layers); - virtual uint32_t link_get_navigation_layers(RID p_link) const; + virtual void link_set_navigation_layers(RID p_link, uint32_t p_navigation_layers) = 0; + virtual uint32_t link_get_navigation_layers(RID p_link) const = 0; /// Set the start position of the link. - virtual void link_set_start_position(RID p_link, Vector2 p_position); - virtual Vector2 link_get_start_position(RID p_link) const; + virtual void link_set_start_position(RID p_link, Vector2 p_position) = 0; + virtual Vector2 link_get_start_position(RID p_link) const = 0; /// Set the end position of the link. - virtual void link_set_end_position(RID p_link, Vector2 p_position); - virtual Vector2 link_get_end_position(RID p_link) const; + virtual void link_set_end_position(RID p_link, Vector2 p_position) = 0; + virtual Vector2 link_get_end_position(RID p_link) const = 0; /// Set the enter cost of the link. - virtual void link_set_enter_cost(RID p_link, real_t p_enter_cost); - virtual real_t link_get_enter_cost(RID p_link) const; + virtual void link_set_enter_cost(RID p_link, real_t p_enter_cost) = 0; + virtual real_t link_get_enter_cost(RID p_link) const = 0; /// Set the travel cost of the link. - virtual void link_set_travel_cost(RID p_link, real_t p_travel_cost); - virtual real_t link_get_travel_cost(RID p_link) const; + virtual void link_set_travel_cost(RID p_link, real_t p_travel_cost) = 0; + virtual real_t link_get_travel_cost(RID p_link) const = 0; /// Set the node which manages this link. - virtual void link_set_owner_id(RID p_link, ObjectID p_owner_id); - virtual ObjectID link_get_owner_id(RID p_link) const; + virtual void link_set_owner_id(RID p_link, ObjectID p_owner_id) = 0; + virtual ObjectID link_get_owner_id(RID p_link) const = 0; /// Creates the agent. - virtual RID agent_create(); + virtual RID agent_create() = 0; /// Put the agent in the map. - virtual void agent_set_map(RID p_agent, RID p_map); - virtual RID agent_get_map(RID p_agent) const; + virtual void agent_set_map(RID p_agent, RID p_map) = 0; + virtual RID agent_get_map(RID p_agent) const = 0; - virtual void agent_set_paused(RID p_agent, bool p_paused); - virtual bool agent_get_paused(RID p_agent) const; + virtual void agent_set_paused(RID p_agent, bool p_paused) = 0; + virtual bool agent_get_paused(RID p_agent) const = 0; - virtual void agent_set_avoidance_enabled(RID p_agent, bool p_enabled); - virtual bool agent_get_avoidance_enabled(RID p_agent) const; + virtual void agent_set_avoidance_enabled(RID p_agent, bool p_enabled) = 0; + virtual bool agent_get_avoidance_enabled(RID p_agent) const = 0; /// The maximum distance (center point to /// center point) to other agents this agent @@ -198,7 +203,7 @@ public: /// time of the simulation. If the number is too /// low, the simulation will not be safe. /// Must be non-negative. - virtual void agent_set_neighbor_distance(RID p_agent, real_t p_distance); + virtual void agent_set_neighbor_distance(RID p_agent, real_t p_distance) = 0; /// The maximum number of other agents this /// agent takes into account in the navigation. @@ -206,7 +211,7 @@ public: /// running time of the simulation. If the /// number is too low, the simulation will not /// be safe. - virtual void agent_set_max_neighbors(RID p_agent, int p_count); + virtual void agent_set_max_neighbors(RID p_agent, int p_count) = 0; /// The minimal amount of time for which this /// agent's velocities that are computed by the @@ -217,59 +222,67 @@ public: /// agent has in choosing its velocities. /// Must be positive. - virtual void agent_set_time_horizon_agents(RID p_agent, real_t p_time_horizon); - virtual void agent_set_time_horizon_obstacles(RID p_agent, real_t p_time_horizon); + virtual void agent_set_time_horizon_agents(RID p_agent, real_t p_time_horizon) = 0; + virtual void agent_set_time_horizon_obstacles(RID p_agent, real_t p_time_horizon) = 0; /// The radius of this agent. /// Must be non-negative. - virtual void agent_set_radius(RID p_agent, real_t p_radius); + virtual void agent_set_radius(RID p_agent, real_t p_radius) = 0; /// The maximum speed of this agent. /// Must be non-negative. - virtual void agent_set_max_speed(RID p_agent, real_t p_max_speed); + virtual void agent_set_max_speed(RID p_agent, real_t p_max_speed) = 0; /// forces and agent velocity change in the avoidance simulation, adds simulation instability if done recklessly - virtual void agent_set_velocity_forced(RID p_agent, Vector2 p_velocity); + virtual void agent_set_velocity_forced(RID p_agent, Vector2 p_velocity) = 0; /// The wanted velocity for the agent as a "suggestion" to the avoidance simulation. /// The simulation will try to fulfill this velocity wish if possible but may change the velocity depending on other agent's and obstacles'. - virtual void agent_set_velocity(RID p_agent, Vector2 p_velocity); + virtual void agent_set_velocity(RID p_agent, Vector2 p_velocity) = 0; /// Position of the agent in world space. - virtual void agent_set_position(RID p_agent, Vector2 p_position); + virtual void agent_set_position(RID p_agent, Vector2 p_position) = 0; /// Returns true if the map got changed the previous frame. - virtual bool agent_is_map_changed(RID p_agent) const; + virtual bool agent_is_map_changed(RID p_agent) const = 0; /// Callback called at the end of the RVO process - virtual void agent_set_avoidance_callback(RID p_agent, Callable p_callback); + virtual void agent_set_avoidance_callback(RID p_agent, Callable p_callback) = 0; - virtual void agent_set_avoidance_layers(RID p_agent, uint32_t p_layers); - virtual void agent_set_avoidance_mask(RID p_agent, uint32_t p_mask); - virtual void agent_set_avoidance_priority(RID p_agent, real_t p_priority); + virtual void agent_set_avoidance_layers(RID p_agent, uint32_t p_layers) = 0; + virtual void agent_set_avoidance_mask(RID p_agent, uint32_t p_mask) = 0; + virtual void agent_set_avoidance_priority(RID p_agent, real_t p_priority) = 0; /// Creates the obstacle. - virtual RID obstacle_create(); - virtual void obstacle_set_avoidance_enabled(RID p_obstacle, bool p_enabled); - virtual bool obstacle_get_avoidance_enabled(RID p_obstacle) const; - virtual void obstacle_set_map(RID p_obstacle, RID p_map); - virtual RID obstacle_get_map(RID p_obstacle) const; - virtual void obstacle_set_paused(RID p_obstacle, bool p_paused); - virtual bool obstacle_get_paused(RID p_obstacle) const; - virtual void obstacle_set_radius(RID p_obstacle, real_t p_radius); - virtual void obstacle_set_velocity(RID p_obstacle, Vector2 p_velocity); - virtual void obstacle_set_position(RID p_obstacle, Vector2 p_position); - virtual void obstacle_set_vertices(RID p_obstacle, const Vector &p_vertices); - virtual void obstacle_set_avoidance_layers(RID p_obstacle, uint32_t p_layers); + virtual RID obstacle_create() = 0; + virtual void obstacle_set_avoidance_enabled(RID p_obstacle, bool p_enabled) = 0; + virtual bool obstacle_get_avoidance_enabled(RID p_obstacle) const = 0; + virtual void obstacle_set_map(RID p_obstacle, RID p_map) = 0; + virtual RID obstacle_get_map(RID p_obstacle) const = 0; + virtual void obstacle_set_paused(RID p_obstacle, bool p_paused) = 0; + virtual bool obstacle_get_paused(RID p_obstacle) const = 0; + virtual void obstacle_set_radius(RID p_obstacle, real_t p_radius) = 0; + virtual void obstacle_set_velocity(RID p_obstacle, Vector2 p_velocity) = 0; + virtual void obstacle_set_position(RID p_obstacle, Vector2 p_position) = 0; + virtual void obstacle_set_vertices(RID p_obstacle, const Vector &p_vertices) = 0; + virtual void obstacle_set_avoidance_layers(RID p_obstacle, uint32_t p_layers) = 0; /// Returns a customized navigation path using a query parameters object - virtual void query_path(const Ref &p_query_parameters, Ref p_query_result) const; + virtual void query_path(const Ref &p_query_parameters, Ref p_query_result) const = 0; + + virtual void init() = 0; + virtual void sync() = 0; + virtual void finish() = 0; /// Destroy the `RID` - virtual void free(RID p_object); + virtual void free(RID p_object) = 0; + + virtual void parse_source_geometry_data(const Ref &p_navigation_mesh, const Ref &p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) = 0; + virtual void bake_from_source_geometry_data(const Ref &p_navigation_mesh, const Ref &p_source_geometry_data, const Callable &p_callback = Callable()) = 0; + virtual void bake_from_source_geometry_data_async(const Ref &p_navigation_mesh, const Ref &p_source_geometry_data, const Callable &p_callback = Callable()) = 0; NavigationServer2D(); - virtual ~NavigationServer2D(); + ~NavigationServer2D() override; void set_debug_enabled(bool p_enabled); bool get_debug_enabled() const; @@ -354,4 +367,15 @@ private: #endif // DEBUG_ENABLED }; +typedef NavigationServer2D *(*NavigationServer2DCallback)(); + +/// Manager used for the server singleton registration +class NavigationServer2DManager { + static NavigationServer2DCallback create_callback; + +public: + static void set_default_server(NavigationServer2DCallback p_callback); + static NavigationServer2D *new_default_server(); +}; + #endif // NAVIGATION_SERVER_2D_H diff --git a/servers/navigation_server_2d_dummy.h b/servers/navigation_server_2d_dummy.h new file mode 100644 index 00000000000..d64a9454aac --- /dev/null +++ b/servers/navigation_server_2d_dummy.h @@ -0,0 +1,155 @@ +/**************************************************************************/ +/* navigation_server_2d_dummy.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef NAVIGATION_SERVER_2D_DUMMY_H +#define NAVIGATION_SERVER_2D_DUMMY_H + +#include "servers/navigation_server_2d.h" + +class NavigationServer2DDummy : public NavigationServer2D { + GDCLASS(NavigationServer2DDummy, NavigationServer2D); + +public: + TypedArray get_maps() const override { return TypedArray(); } + + RID map_create() override { return RID(); } + void map_set_active(RID p_map, bool p_active) override {} + bool map_is_active(RID p_map) const override { return false; } + void map_set_cell_size(RID p_map, real_t p_cell_size) override {} + real_t map_get_cell_size(RID p_map) const override { return 0; } + void map_set_use_edge_connections(RID p_map, bool p_enabled) override {} + bool map_get_use_edge_connections(RID p_map) const override { return false; } + void map_set_edge_connection_margin(RID p_map, real_t p_connection_margin) override {} + real_t map_get_edge_connection_margin(RID p_map) const override { return 0; } + void map_set_link_connection_radius(RID p_map, real_t p_connection_radius) override {} + real_t map_get_link_connection_radius(RID p_map) const override { return 0; } + Vector map_get_path(RID p_map, Vector2 p_origin, Vector2 p_destination, bool p_optimize, uint32_t p_navigation_layers = 1) const override { return Vector(); } + Vector2 map_get_closest_point(RID p_map, const Vector2 &p_point) const override { return Vector2(); } + RID map_get_closest_point_owner(RID p_map, const Vector2 &p_point) const override { return RID(); } + TypedArray map_get_links(RID p_map) const override { return TypedArray(); } + TypedArray map_get_regions(RID p_map) const override { return TypedArray(); } + TypedArray map_get_agents(RID p_map) const override { return TypedArray(); } + TypedArray map_get_obstacles(RID p_map) const override { return TypedArray(); } + void map_force_update(RID p_map) override {} + + RID region_create() override { return RID(); } + void region_set_enabled(RID p_region, bool p_enabled) override {} + bool region_get_enabled(RID p_region) const override { return false; } + void region_set_use_edge_connections(RID p_region, bool p_enabled) override {} + bool region_get_use_edge_connections(RID p_region) const override { return false; } + void region_set_enter_cost(RID p_region, real_t p_enter_cost) override {} + real_t region_get_enter_cost(RID p_region) const override { return 0; } + void region_set_travel_cost(RID p_region, real_t p_travel_cost) override {} + real_t region_get_travel_cost(RID p_region) const override { return 0; } + void region_set_owner_id(RID p_region, ObjectID p_owner_id) override {} + ObjectID region_get_owner_id(RID p_region) const override { return ObjectID(); } + bool region_owns_point(RID p_region, const Vector2 &p_point) const override { return false; } + void region_set_map(RID p_region, RID p_map) override {} + RID region_get_map(RID p_region) const override { return RID(); } + void region_set_navigation_layers(RID p_region, uint32_t p_navigation_layers) override {} + uint32_t region_get_navigation_layers(RID p_region) const override { return 0; } + void region_set_transform(RID p_region, Transform2D p_transform) override {} + void region_set_navigation_polygon(RID p_region, Ref p_navigation_polygon) override {} + int region_get_connections_count(RID p_region) const override { return 0; } + Vector2 region_get_connection_pathway_start(RID p_region, int p_connection_id) const override { return Vector2(); } + Vector2 region_get_connection_pathway_end(RID p_region, int p_connection_id) const override { return Vector2(); } + + RID link_create() override { return RID(); } + void link_set_map(RID p_link, RID p_map) override {} + RID link_get_map(RID p_link) const override { return RID(); } + void link_set_enabled(RID p_link, bool p_enabled) override {} + bool link_get_enabled(RID p_link) const override { return false; } + void link_set_bidirectional(RID p_link, bool p_bidirectional) override {} + bool link_is_bidirectional(RID p_link) const override { return false; } + void link_set_navigation_layers(RID p_link, uint32_t p_navigation_layers) override {} + uint32_t link_get_navigation_layers(RID p_link) const override { return 0; } + void link_set_start_position(RID p_link, Vector2 p_position) override {} + Vector2 link_get_start_position(RID p_link) const override { return Vector2(); } + void link_set_end_position(RID p_link, Vector2 p_position) override {} + Vector2 link_get_end_position(RID p_link) const override { return Vector2(); } + void link_set_enter_cost(RID p_link, real_t p_enter_cost) override {} + real_t link_get_enter_cost(RID p_link) const override { return 0; } + void link_set_travel_cost(RID p_link, real_t p_travel_cost) override {} + real_t link_get_travel_cost(RID p_link) const override { return 0; } + void link_set_owner_id(RID p_link, ObjectID p_owner_id) override {} + ObjectID link_get_owner_id(RID p_link) const override { return ObjectID(); } + + RID agent_create() override { return RID(); } + void agent_set_map(RID p_agent, RID p_map) override {} + RID agent_get_map(RID p_agent) const override { return RID(); } + void agent_set_paused(RID p_agent, bool p_paused) override {} + bool agent_get_paused(RID p_agent) const override { return false; } + void agent_set_avoidance_enabled(RID p_agent, bool p_enabled) override {} + bool agent_get_avoidance_enabled(RID p_agent) const override { return false; } + void agent_set_neighbor_distance(RID p_agent, real_t p_distance) override {} + void agent_set_max_neighbors(RID p_agent, int p_count) override {} + void agent_set_time_horizon_agents(RID p_agent, real_t p_time_horizon) override {} + void agent_set_time_horizon_obstacles(RID p_agent, real_t p_time_horizon) override {} + void agent_set_radius(RID p_agent, real_t p_radius) override {} + void agent_set_max_speed(RID p_agent, real_t p_max_speed) override {} + void agent_set_velocity_forced(RID p_agent, Vector2 p_velocity) override {} + void agent_set_velocity(RID p_agent, Vector2 p_velocity) override {} + void agent_set_position(RID p_agent, Vector2 p_position) override {} + bool agent_is_map_changed(RID p_agent) const override { return false; } + void agent_set_avoidance_callback(RID p_agent, Callable p_callback) override {} + void agent_set_avoidance_layers(RID p_agent, uint32_t p_layers) override {} + void agent_set_avoidance_mask(RID p_agent, uint32_t p_mask) override {} + void agent_set_avoidance_priority(RID p_agent, real_t p_priority) override {} + + RID obstacle_create() override { return RID(); } + void obstacle_set_avoidance_enabled(RID p_obstacle, bool p_enabled) override {} + bool obstacle_get_avoidance_enabled(RID p_obstacle) const override { return false; } + void obstacle_set_map(RID p_obstacle, RID p_map) override {} + RID obstacle_get_map(RID p_obstacle) const override { return RID(); } + void obstacle_set_paused(RID p_obstacle, bool p_paused) override {} + bool obstacle_get_paused(RID p_obstacle) const override { return false; } + void obstacle_set_radius(RID p_obstacle, real_t p_radius) override {} + void obstacle_set_velocity(RID p_obstacle, Vector2 p_velocity) override {} + void obstacle_set_position(RID p_obstacle, Vector2 p_position) override {} + void obstacle_set_vertices(RID p_obstacle, const Vector &p_vertices) override {} + void obstacle_set_avoidance_layers(RID p_obstacle, uint32_t p_layers) override {} + + void query_path(const Ref &p_query_parameters, Ref p_query_result) const override {} + + void init() override {} + void sync() override {} + void finish() override {} + + void free(RID p_object) override {} + + void parse_source_geometry_data(const Ref &p_navigation_mesh, const Ref &p_source_geometry_data, Node *p_root_node, const Callable &p_callback = Callable()) override {} + void bake_from_source_geometry_data(const Ref &p_navigation_mesh, const Ref &p_source_geometry_data, const Callable &p_callback = Callable()) override {} + void bake_from_source_geometry_data_async(const Ref &p_navigation_mesh, const Ref &p_source_geometry_data, const Callable &p_callback = Callable()) override {} + + void set_debug_enabled(bool p_enabled) {} + bool get_debug_enabled() const { return false; } +}; + +#endif // NAVIGATION_SERVER_2D_DUMMY_H diff --git a/servers/navigation_server_3d.h b/servers/navigation_server_3d.h index 39f147357ad..53d33d0058d 100644 --- a/servers/navigation_server_3d.h +++ b/servers/navigation_server_3d.h @@ -302,6 +302,7 @@ public: /// Note: This function is not thread safe. virtual void process(real_t delta_time) = 0; virtual void init() = 0; + virtual void sync() = 0; virtual void finish() = 0; /// Returns a customized navigation path using a query parameters object diff --git a/servers/navigation_server_3d_dummy.h b/servers/navigation_server_3d_dummy.h index b1ec214bb0b..52580223280 100644 --- a/servers/navigation_server_3d_dummy.h +++ b/servers/navigation_server_3d_dummy.h @@ -152,6 +152,7 @@ public: void set_active(bool p_active) override {} void process(real_t delta_time) override {} void init() override {} + void sync() override {} void finish() override {} NavigationUtilities::PathQueryResult _query_path(const NavigationUtilities::PathQueryParameters &p_parameters) const override { return NavigationUtilities::PathQueryResult(); } int get_process_info(ProcessInfo p_info) const override { return 0; } diff --git a/tests/test_main.cpp b/tests/test_main.cpp index a43dfaa57f2..8c120f6d3af 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -252,7 +252,7 @@ struct GodotTestCaseListener : public doctest::IReporter { ERR_PRINT_OFF; navigation_server_3d = NavigationServer3DManager::new_default_server(); - navigation_server_2d = memnew(NavigationServer2D); + navigation_server_2d = NavigationServer2DManager::new_default_server(); ERR_PRINT_ON; memnew(InputMap); @@ -278,7 +278,7 @@ struct GodotTestCaseListener : public doctest::IReporter { if (suite_name.find("[Navigation]") != -1 && navigation_server_2d == nullptr && navigation_server_3d == nullptr) { ERR_PRINT_OFF; navigation_server_3d = NavigationServer3DManager::new_default_server(); - navigation_server_2d = memnew(NavigationServer2D); + navigation_server_2d = NavigationServer2DManager::new_default_server(); ERR_PRINT_ON; return; } diff --git a/thirdparty/README.md b/thirdparty/README.md index 1eb95a1a7c2..77ec1a26111 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -91,6 +91,18 @@ Files extracted from upstream source: - License: MPL 2.0 +## clipper2 + +- Upstream: https://github.com/AngusJohnson/Clipper2 +- Version: 1.2.2 (756c5079aacab5837e812a143c59dc48a09f22e7, 2023) +- License: Boost Software License 1.0 + +Files extracted from upstream source: + +- `CPP/Clipper2Lib` folder +- `LICENSE` + + ## cvtt - Upstream: https://github.com/elasota/ConvectionKernels diff --git a/thirdparty/clipper2/LICENSE b/thirdparty/clipper2/LICENSE new file mode 100644 index 00000000000..36b7cd93cdf --- /dev/null +++ b/thirdparty/clipper2/LICENSE @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/thirdparty/clipper2/clipper2-exceptions.patch b/thirdparty/clipper2/clipper2-exceptions.patch new file mode 100644 index 00000000000..8a6acc124e4 --- /dev/null +++ b/thirdparty/clipper2/clipper2-exceptions.patch @@ -0,0 +1,35 @@ +diff --git a/thirdparty/clipper2/include/clipper2/clipper.core.h b/thirdparty/clipper2/include/clipper2/clipper.core.h +index c7522cb900..086d1b659c 100644 +--- a/thirdparty/clipper2/include/clipper2/clipper.core.h ++++ b/thirdparty/clipper2/include/clipper2/clipper.core.h +@@ -20,6 +20,8 @@ + #include + #include + ++#define CLIPPER2_THROW(exception) std::abort() ++ + namespace Clipper2Lib + { + +@@ -65,16 +67,16 @@ namespace Clipper2Lib + switch (error_code) + { + case precision_error_i: +- throw Clipper2Exception(precision_error); ++ CLIPPER2_THROW(Clipper2Exception(precision_error)); + case scale_error_i: +- throw Clipper2Exception(scale_error); ++ CLIPPER2_THROW(Clipper2Exception(scale_error)); + case non_pair_error_i: +- throw Clipper2Exception(non_pair_error); ++ CLIPPER2_THROW(Clipper2Exception(non_pair_error)); + case range_error_i: +- throw Clipper2Exception(range_error); ++ CLIPPER2_THROW(Clipper2Exception(range_error)); + } + #else +- ++error_code; // only to stop compiler warning ++ if(error_code) {}; // only to stop compiler 'parameter not used' warning + #endif + } + diff --git a/thirdparty/clipper2/include/clipper2/clipper.core.h b/thirdparty/clipper2/include/clipper2/clipper.core.h new file mode 100644 index 00000000000..086d1b659c9 --- /dev/null +++ b/thirdparty/clipper2/include/clipper2/clipper.core.h @@ -0,0 +1,846 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 22 March 2023 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2023 * +* Purpose : Core Clipper Library structures and functions * +* License : http://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +#ifndef CLIPPER_CORE_H +#define CLIPPER_CORE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CLIPPER2_THROW(exception) std::abort() + +namespace Clipper2Lib +{ + +#if (defined(__cpp_exceptions) && __cpp_exceptions) || (defined(__EXCEPTIONS) && __EXCEPTIONS) + + class Clipper2Exception : public std::exception { + public: + explicit Clipper2Exception(const char* description) : + m_descr(description) {} + virtual const char* what() const throw() override { return m_descr.c_str(); } + private: + std::string m_descr; + }; + + static const char* precision_error = + "Precision exceeds the permitted range"; + static const char* range_error = + "Values exceed permitted range"; + static const char* scale_error = + "Invalid scale (either 0 or too large)"; + static const char* non_pair_error = + "There must be 2 values for each coordinate"; +#endif + + // error codes (2^n) + const int precision_error_i = 1; // non-fatal + const int scale_error_i = 2; // non-fatal + const int non_pair_error_i = 4; // non-fatal + const int range_error_i = 64; + + static const double PI = 3.141592653589793238; + static const int64_t MAX_COORD = INT64_MAX >> 2; + static const int64_t MIN_COORD = -MAX_COORD; + static const int64_t INVALID = INT64_MAX; + const double max_coord = static_cast(MAX_COORD); + const double min_coord = static_cast(MIN_COORD); + + static const double MAX_DBL = (std::numeric_limits::max)(); + + static void DoError(int error_code) + { +#if (defined(__cpp_exceptions) && __cpp_exceptions) || (defined(__EXCEPTIONS) && __EXCEPTIONS) + switch (error_code) + { + case precision_error_i: + CLIPPER2_THROW(Clipper2Exception(precision_error)); + case scale_error_i: + CLIPPER2_THROW(Clipper2Exception(scale_error)); + case non_pair_error_i: + CLIPPER2_THROW(Clipper2Exception(non_pair_error)); + case range_error_i: + CLIPPER2_THROW(Clipper2Exception(range_error)); + } +#else + if(error_code) {}; // only to stop compiler 'parameter not used' warning +#endif + } + + //By far the most widely used filling rules for polygons are EvenOdd + //and NonZero, sometimes called Alternate and Winding respectively. + //https://en.wikipedia.org/wiki/Nonzero-rule + enum class FillRule { EvenOdd, NonZero, Positive, Negative }; + + // Point ------------------------------------------------------------------------ + + template + struct Point { + T x; + T y; +#ifdef USINGZ + int64_t z; + + template + inline void Init(const T2 x_ = 0, const T2 y_ = 0, const int64_t z_ = 0) + { + if constexpr (std::numeric_limits::is_integer && + !std::numeric_limits::is_integer) + { + x = static_cast(std::round(x_)); + y = static_cast(std::round(y_)); + z = z_; + } + else + { + x = static_cast(x_); + y = static_cast(y_); + z = z_; + } + } + + explicit Point() : x(0), y(0), z(0) {}; + + template + Point(const T2 x_, const T2 y_, const int64_t z_ = 0) + { + Init(x_, y_); + z = z_; + } + + template + explicit Point(const Point& p) + { + Init(p.x, p.y, p.z); + } + + Point operator * (const double scale) const + { + return Point(x * scale, y * scale, z); + } + + + friend std::ostream& operator<<(std::ostream& os, const Point& point) + { + os << point.x << "," << point.y << "," << point.z << " "; + return os; + } + +#else + + template + inline void Init(const T2 x_ = 0, const T2 y_ = 0) + { + if constexpr (std::numeric_limits::is_integer && + !std::numeric_limits::is_integer) + { + x = static_cast(std::round(x_)); + y = static_cast(std::round(y_)); + } + else + { + x = static_cast(x_); + y = static_cast(y_); + } + } + + explicit Point() : x(0), y(0) {}; + + template + Point(const T2 x_, const T2 y_) { Init(x_, y_); } + + template + explicit Point(const Point& p) { Init(p.x, p.y); } + + Point operator * (const double scale) const + { + return Point(x * scale, y * scale); + } + + friend std::ostream& operator<<(std::ostream& os, const Point& point) + { + os << point.x << "," << point.y << " "; + return os; + } +#endif + + friend bool operator==(const Point& a, const Point& b) + { + return a.x == b.x && a.y == b.y; + } + + friend bool operator!=(const Point& a, const Point& b) + { + return !(a == b); + } + + inline Point operator-() const + { + return Point(-x, -y); + } + + inline Point operator+(const Point& b) const + { + return Point(x + b.x, y + b.y); + } + + inline Point operator-(const Point& b) const + { + return Point(x - b.x, y - b.y); + } + + inline void Negate() { x = -x; y = -y; } + + }; + + //nb: using 'using' here (instead of typedef) as they can be used in templates + using Point64 = Point; + using PointD = Point; + + template + using Path = std::vector>; + template + using Paths = std::vector>; + + using Path64 = Path; + using PathD = Path; + using Paths64 = std::vector< Path64>; + using PathsD = std::vector< PathD>; + + // Rect ------------------------------------------------------------------------ + + template + struct Rect; + + using Rect64 = Rect; + using RectD = Rect; + + template + struct Rect { + T left; + T top; + T right; + T bottom; + + Rect() : + left(0), + top(0), + right(0), + bottom(0) {} + + Rect(T l, T t, T r, T b) : + left(l), + top(t), + right(r), + bottom(b) {} + + Rect(bool is_valid) + { + if (is_valid) + { + left = right = top = bottom = 0; + } + else + { + left = top = std::numeric_limits::max(); + right = bottom = -std::numeric_limits::max(); + } + } + + T Width() const { return right - left; } + T Height() const { return bottom - top; } + void Width(T width) { right = left + width; } + void Height(T height) { bottom = top + height; } + + Point MidPoint() const + { + return Point((left + right) / 2, (top + bottom) / 2); + } + + Path AsPath() const + { + Path result; + result.reserve(4); + result.push_back(Point(left, top)); + result.push_back(Point(right, top)); + result.push_back(Point(right, bottom)); + result.push_back(Point(left, bottom)); + return result; + } + + bool Contains(const Point& pt) const + { + return pt.x > left && pt.x < right&& pt.y > top && pt.y < bottom; + } + + bool Contains(const Rect& rec) const + { + return rec.left >= left && rec.right <= right && + rec.top >= top && rec.bottom <= bottom; + } + + void Scale(double scale) { + left *= scale; + top *= scale; + right *= scale; + bottom *= scale; + } + + bool IsEmpty() const { return bottom <= top || right <= left; }; + + bool Intersects(const Rect& rec) const + { + return ((std::max)(left, rec.left) <= (std::min)(right, rec.right)) && + ((std::max)(top, rec.top) <= (std::min)(bottom, rec.bottom)); + }; + + friend std::ostream& operator<<(std::ostream& os, const Rect& rect) { + os << "(" + << rect.left << "," << rect.top << "," << rect.right << "," << rect.bottom + << ")"; + return os; + } + }; + + template + inline Rect ScaleRect(const Rect& rect, double scale) + { + Rect result; + + if constexpr (std::numeric_limits::is_integer && + !std::numeric_limits::is_integer) + { + result.left = static_cast(std::round(rect.left * scale)); + result.top = static_cast(std::round(rect.top * scale)); + result.right = static_cast(std::round(rect.right * scale)); + result.bottom = static_cast(std::round(rect.bottom * scale)); + } + else + { + result.left = rect.left * scale; + result.top = rect.top * scale; + result.right = rect.right * scale; + result.bottom = rect.bottom * scale; + } + return result; + } + + static const Rect64 MaxInvalidRect64 = Rect64( + INT64_MAX, INT64_MAX, INT64_MIN, INT64_MIN); + static const RectD MaxInvalidRectD = RectD( + MAX_DBL, MAX_DBL, -MAX_DBL, -MAX_DBL); + + template + Rect GetBounds(const Path& path) + { + auto xmin = std::numeric_limits::max(); + auto ymin = std::numeric_limits::max(); + auto xmax = std::numeric_limits::lowest(); + auto ymax = std::numeric_limits::lowest(); + for (const auto& p : path) + { + if (p.x < xmin) xmin = p.x; + if (p.x > xmax) xmax = p.x; + if (p.y < ymin) ymin = p.y; + if (p.y > ymax) ymax = p.y; + } + return Rect(xmin, ymin, xmax, ymax); + } + + template + Rect GetBounds(const Paths& paths) + { + auto xmin = std::numeric_limits::max(); + auto ymin = std::numeric_limits::max(); + auto xmax = std::numeric_limits::lowest(); + auto ymax = std::numeric_limits::lowest(); + for (const Path& path : paths) + for (const Point& p : path) + { + if (p.x < xmin) xmin = p.x; + if (p.x > xmax) xmax = p.x; + if (p.y < ymin) ymin = p.y; + if (p.y > ymax) ymax = p.y; + } + return Rect(xmin, ymin, xmax, ymax); + } + + template + std::ostream& operator << (std::ostream& outstream, const Path& path) + { + if (!path.empty()) + { + auto pt = path.cbegin(), last = path.cend() - 1; + while (pt != last) + outstream << *pt++ << ", "; + outstream << *last << std::endl; + } + return outstream; + } + + template + std::ostream& operator << (std::ostream& outstream, const Paths& paths) + { + for (auto p : paths) + outstream << p; + return outstream; + } + + + template + inline Path ScalePath(const Path& path, + double scale_x, double scale_y, int& error_code) + { + Path result; + if (scale_x == 0 || scale_y == 0) + { + error_code |= scale_error_i; + DoError(scale_error_i); + // if no exception, treat as non-fatal error + if (scale_x == 0) scale_x = 1.0; + if (scale_y == 0) scale_y = 1.0; + } + + result.reserve(path.size()); +#ifdef USINGZ + std::transform(path.begin(), path.end(), back_inserter(result), + [scale_x, scale_y](const auto& pt) + { return Point(pt.x * scale_x, pt.y * scale_y, pt.z); }); +#else + std::transform(path.begin(), path.end(), back_inserter(result), + [scale_x, scale_y](const auto& pt) + { return Point(pt.x * scale_x, pt.y * scale_y); }); +#endif + return result; + } + + template + inline Path ScalePath(const Path& path, + double scale, int& error_code) + { + return ScalePath(path, scale, scale, error_code); + } + + template + inline Paths ScalePaths(const Paths& paths, + double scale_x, double scale_y, int& error_code) + { + Paths result; + + if constexpr (std::numeric_limits::is_integer && + !std::numeric_limits::is_integer) + { + RectD r = GetBounds(paths); + if ((r.left * scale_x) < min_coord || + (r.right * scale_x) > max_coord || + (r.top * scale_y) < min_coord || + (r.bottom * scale_y) > max_coord) + { + error_code |= range_error_i; + DoError(range_error_i); + return result; // empty path + } + } + + result.reserve(paths.size()); + std::transform(paths.begin(), paths.end(), back_inserter(result), + [=, &error_code](const auto& path) + { return ScalePath(path, scale_x, scale_y, error_code); }); + return result; + } + + template + inline Paths ScalePaths(const Paths& paths, + double scale, int& error_code) + { + return ScalePaths(paths, scale, scale, error_code); + } + + template + inline Path TransformPath(const Path& path) + { + Path result; + result.reserve(path.size()); + std::transform(path.cbegin(), path.cend(), std::back_inserter(result), + [](const Point& pt) {return Point(pt); }); + return result; + } + + template + inline Paths TransformPaths(const Paths& paths) + { + Paths result; + std::transform(paths.cbegin(), paths.cend(), std::back_inserter(result), + [](const Path& path) {return TransformPath(path); }); + return result; + } + + inline PathD Path64ToPathD(const Path64& path) + { + return TransformPath(path); + } + + inline PathsD Paths64ToPathsD(const Paths64& paths) + { + return TransformPaths(paths); + } + + inline Path64 PathDToPath64(const PathD& path) + { + return TransformPath(path); + } + + inline Paths64 PathsDToPaths64(const PathsD& paths) + { + return TransformPaths(paths); + } + + template + inline double Sqr(T val) + { + return static_cast(val) * static_cast(val); + } + + template + inline bool NearEqual(const Point& p1, + const Point& p2, double max_dist_sqrd) + { + return Sqr(p1.x - p2.x) + Sqr(p1.y - p2.y) < max_dist_sqrd; + } + + template + inline Path StripNearEqual(const Path& path, + double max_dist_sqrd, bool is_closed_path) + { + if (path.size() == 0) return Path(); + Path result; + result.reserve(path.size()); + typename Path::const_iterator path_iter = path.cbegin(); + Point first_pt = *path_iter++, last_pt = first_pt; + result.push_back(first_pt); + for (; path_iter != path.cend(); ++path_iter) + { + if (!NearEqual(*path_iter, last_pt, max_dist_sqrd)) + { + last_pt = *path_iter; + result.push_back(last_pt); + } + } + if (!is_closed_path) return result; + while (result.size() > 1 && + NearEqual(result.back(), first_pt, max_dist_sqrd)) result.pop_back(); + return result; + } + + template + inline Paths StripNearEqual(const Paths& paths, + double max_dist_sqrd, bool is_closed_path) + { + Paths result; + result.reserve(paths.size()); + for (typename Paths::const_iterator paths_citer = paths.cbegin(); + paths_citer != paths.cend(); ++paths_citer) + { + result.push_back(StripNearEqual(*paths_citer, max_dist_sqrd, is_closed_path)); + } + return result; + } + + template + inline Path StripDuplicates(const Path& path, bool is_closed_path) + { + if (path.size() == 0) return Path(); + Path result; + result.reserve(path.size()); + typename Path::const_iterator path_iter = path.cbegin(); + Point first_pt = *path_iter++, last_pt = first_pt; + result.push_back(first_pt); + for (; path_iter != path.cend(); ++path_iter) + { + if (*path_iter != last_pt) + { + last_pt = *path_iter; + result.push_back(last_pt); + } + } + if (!is_closed_path) return result; + while (result.size() > 1 && result.back() == first_pt) result.pop_back(); + return result; + } + + template + inline Paths StripDuplicates(const Paths& paths, bool is_closed_path) + { + Paths result; + result.reserve(paths.size()); + for (typename Paths::const_iterator paths_citer = paths.cbegin(); + paths_citer != paths.cend(); ++paths_citer) + { + result.push_back(StripDuplicates(*paths_citer, is_closed_path)); + } + return result; + } + + // Miscellaneous ------------------------------------------------------------ + + inline void CheckPrecision(int& precision, int& error_code) + { + if (precision >= -8 && precision <= 8) return; + error_code |= precision_error_i; // non-fatal error + DoError(precision_error_i); // unless exceptions enabled + precision = precision > 8 ? 8 : -8; + } + + inline void CheckPrecision(int& precision) + { + int error_code = 0; + CheckPrecision(precision, error_code); + } + + template + inline double CrossProduct(const Point& pt1, const Point& pt2, const Point& pt3) { + return (static_cast(pt2.x - pt1.x) * static_cast(pt3.y - + pt2.y) - static_cast(pt2.y - pt1.y) * static_cast(pt3.x - pt2.x)); + } + + template + inline double CrossProduct(const Point& vec1, const Point& vec2) + { + return static_cast(vec1.y * vec2.x) - static_cast(vec2.y * vec1.x); + } + + template + inline double DotProduct(const Point& pt1, const Point& pt2, const Point& pt3) { + return (static_cast(pt2.x - pt1.x) * static_cast(pt3.x - pt2.x) + + static_cast(pt2.y - pt1.y) * static_cast(pt3.y - pt2.y)); + } + + template + inline double DotProduct(const Point& vec1, const Point& vec2) + { + return static_cast(vec1.x * vec2.x) + static_cast(vec1.y * vec2.y); + } + + template + inline double DistanceSqr(const Point pt1, const Point pt2) + { + return Sqr(pt1.x - pt2.x) + Sqr(pt1.y - pt2.y); + } + + template + inline double DistanceFromLineSqrd(const Point& pt, const Point& ln1, const Point& ln2) + { + //perpendicular distance of point (x³,y³) = (Ax³ + By³ + C)/Sqrt(A² + B²) + //see http://en.wikipedia.org/wiki/Perpendicular_distance + double A = static_cast(ln1.y - ln2.y); + double B = static_cast(ln2.x - ln1.x); + double C = A * ln1.x + B * ln1.y; + C = A * pt.x + B * pt.y - C; + return (C * C) / (A * A + B * B); + } + + template + inline double Area(const Path& path) + { + size_t cnt = path.size(); + if (cnt < 3) return 0.0; + double a = 0.0; + typename Path::const_iterator it1, it2 = path.cend() - 1, stop = it2; + if (!(cnt & 1)) ++stop; + for (it1 = path.cbegin(); it1 != stop;) + { + a += static_cast(it2->y + it1->y) * (it2->x - it1->x); + it2 = it1 + 1; + a += static_cast(it1->y + it2->y) * (it1->x - it2->x); + it1 += 2; + } + if (cnt & 1) + a += static_cast(it2->y + it1->y) * (it2->x - it1->x); + return a * 0.5; + } + + template + inline double Area(const Paths& paths) + { + double a = 0.0; + for (typename Paths::const_iterator paths_iter = paths.cbegin(); + paths_iter != paths.cend(); ++paths_iter) + { + a += Area(*paths_iter); + } + return a; + } + + template + inline bool IsPositive(const Path& poly) + { + // A curve has positive orientation [and area] if a region 'R' + // is on the left when traveling around the outside of 'R'. + //https://mathworld.wolfram.com/CurveOrientation.html + //nb: This statement is premised on using Cartesian coordinates + return Area(poly) >= 0; + } + + inline int64_t CheckCastInt64(double val) + { + if ((val >= max_coord) || (val <= min_coord)) return INVALID; + else return static_cast(val); + } + + inline bool GetIntersectPoint(const Point64& ln1a, const Point64& ln1b, + const Point64& ln2a, const Point64& ln2b, Point64& ip) + { + // https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection + + double dx1 = static_cast(ln1b.x - ln1a.x); + double dy1 = static_cast(ln1b.y - ln1a.y); + double dx2 = static_cast(ln2b.x - ln2a.x); + double dy2 = static_cast(ln2b.y - ln2a.y); + double det = dy1 * dx2 - dy2 * dx1; + if (det == 0.0) return 0; + double qx = dx1 * ln1a.y - dy1 * ln1a.x; + double qy = dx2 * ln2a.y - dy2 * ln2a.x; + ip.x = CheckCastInt64((dx1 * qy - dx2 * qx) / det); + ip.y = CheckCastInt64((dy1 * qy - dy2 * qx) / det); + return (ip.x != INVALID && ip.y != INVALID); + } + + inline bool SegmentsIntersect(const Point64& seg1a, const Point64& seg1b, + const Point64& seg2a, const Point64& seg2b, bool inclusive = false) + { + if (inclusive) + { + double res1 = CrossProduct(seg1a, seg2a, seg2b); + double res2 = CrossProduct(seg1b, seg2a, seg2b); + if (res1 * res2 > 0) return false; + double res3 = CrossProduct(seg2a, seg1a, seg1b); + double res4 = CrossProduct(seg2b, seg1a, seg1b); + if (res3 * res4 > 0) return false; + return (res1 || res2 || res3 || res4); // ensures not collinear + } + else { + return (CrossProduct(seg1a, seg2a, seg2b) * + CrossProduct(seg1b, seg2a, seg2b) < 0) && + (CrossProduct(seg2a, seg1a, seg1b) * + CrossProduct(seg2b, seg1a, seg1b) < 0); + } + } + + inline Point64 GetClosestPointOnSegment(const Point64& offPt, + const Point64& seg1, const Point64& seg2) + { + if (seg1.x == seg2.x && seg1.y == seg2.y) return seg1; + double dx = static_cast(seg2.x - seg1.x); + double dy = static_cast(seg2.y - seg1.y); + double q = + (static_cast(offPt.x - seg1.x) * dx + + static_cast(offPt.y - seg1.y) * dy) / + (Sqr(dx) + Sqr(dy)); + if (q < 0) q = 0; else if (q > 1) q = 1; + return Point64( + seg1.x + static_cast(nearbyint(q * dx)), + seg1.y + static_cast(nearbyint(q * dy))); + } + + enum class PointInPolygonResult { IsOn, IsInside, IsOutside }; + + template + inline PointInPolygonResult PointInPolygon(const Point& pt, const Path& polygon) + { + if (polygon.size() < 3) + return PointInPolygonResult::IsOutside; + + int val = 0; + typename Path::const_iterator cbegin = polygon.cbegin(), first = cbegin, curr, prev; + typename Path::const_iterator cend = polygon.cend(); + + while (first != cend && first->y == pt.y) ++first; + if (first == cend) // not a proper polygon + return PointInPolygonResult::IsOutside; + + bool is_above = first->y < pt.y, starting_above = is_above; + curr = first +1; + while (true) + { + if (curr == cend) + { + if (cend == first || first == cbegin) break; + cend = first; + curr = cbegin; + } + + if (is_above) + { + while (curr != cend && curr->y < pt.y) ++curr; + if (curr == cend) continue; + } + else + { + while (curr != cend && curr->y > pt.y) ++curr; + if (curr == cend) continue; + } + + if (curr == cbegin) + prev = polygon.cend() - 1; //nb: NOT cend (since might equal first) + else + prev = curr - 1; + + if (curr->y == pt.y) + { + if (curr->x == pt.x || + (curr->y == prev->y && + ((pt.x < prev->x) != (pt.x < curr->x)))) + return PointInPolygonResult::IsOn; + ++curr; + if (curr == first) break; + continue; + } + + if (pt.x < curr->x && pt.x < prev->x) + { + // we're only interested in edges crossing on the left + } + else if (pt.x > prev->x && pt.x > curr->x) + val = 1 - val; // toggle val + else + { + double d = CrossProduct(*prev, *curr, pt); + if (d == 0) return PointInPolygonResult::IsOn; + if ((d < 0) == is_above) val = 1 - val; + } + is_above = !is_above; + ++curr; + } + + if (is_above != starting_above) + { + cend = polygon.cend(); + if (curr == cend) curr = cbegin; + if (curr == cbegin) prev = cend - 1; + else prev = curr - 1; + double d = CrossProduct(*prev, *curr, pt); + if (d == 0) return PointInPolygonResult::IsOn; + if ((d < 0) == is_above) val = 1 - val; + } + + return (val == 0) ? + PointInPolygonResult::IsOutside : + PointInPolygonResult::IsInside; + } + +} // namespace + +#endif // CLIPPER_CORE_H diff --git a/thirdparty/clipper2/include/clipper2/clipper.engine.h b/thirdparty/clipper2/include/clipper2/clipper.engine.h new file mode 100644 index 00000000000..30dc6c86ff3 --- /dev/null +++ b/thirdparty/clipper2/include/clipper2/clipper.engine.h @@ -0,0 +1,610 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 26 March 2023 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2023 * +* Purpose : This is the main polygon clipping module * +* License : http://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +#ifndef CLIPPER_ENGINE_H +#define CLIPPER_ENGINE_H + +constexpr auto CLIPPER2_VERSION = "1.2.2"; + +#include +#include +#include +#include +#include +#include +#include + +#include "clipper.core.h" + +namespace Clipper2Lib { + + struct Scanline; + struct IntersectNode; + struct Active; + struct Vertex; + struct LocalMinima; + struct OutRec; + struct HorzSegment; + + //Note: all clipping operations except for Difference are commutative. + enum class ClipType { None, Intersection, Union, Difference, Xor }; + + enum class PathType { Subject, Clip }; + enum class JoinWith { None, Left, Right }; + + enum class VertexFlags : uint32_t { + None = 0, OpenStart = 1, OpenEnd = 2, LocalMax = 4, LocalMin = 8 + }; + + constexpr enum VertexFlags operator &(enum VertexFlags a, enum VertexFlags b) + { + return (enum VertexFlags)(uint32_t(a) & uint32_t(b)); + } + + constexpr enum VertexFlags operator |(enum VertexFlags a, enum VertexFlags b) + { + return (enum VertexFlags)(uint32_t(a) | uint32_t(b)); + } + + struct Vertex { + Point64 pt; + Vertex* next = nullptr; + Vertex* prev = nullptr; + VertexFlags flags = VertexFlags::None; + }; + + struct OutPt { + Point64 pt; + OutPt* next = nullptr; + OutPt* prev = nullptr; + OutRec* outrec; + HorzSegment* horz = nullptr; + + OutPt(const Point64& pt_, OutRec* outrec_): pt(pt_), outrec(outrec_) { + next = this; + prev = this; + } + }; + + class PolyPath; + class PolyPath64; + class PolyPathD; + using PolyTree64 = PolyPath64; + using PolyTreeD = PolyPathD; + + struct OutRec; + typedef std::vector OutRecList; + + //OutRec: contains a path in the clipping solution. Edges in the AEL will + //have OutRec pointers assigned when they form part of the clipping solution. + struct OutRec { + size_t idx = 0; + OutRec* owner = nullptr; + Active* front_edge = nullptr; + Active* back_edge = nullptr; + OutPt* pts = nullptr; + PolyPath* polypath = nullptr; + OutRecList* splits = nullptr; + Rect64 bounds = {}; + Path64 path; + bool is_open = false; + bool horz_done = false; + ~OutRec() { + if (splits) delete splits; + // nb: don't delete the split pointers + // as these are owned by ClipperBase's outrec_list_ + }; + }; + + /////////////////////////////////////////////////////////////////// + //Important: UP and DOWN here are premised on Y-axis positive down + //displays, which is the orientation used in Clipper's development. + /////////////////////////////////////////////////////////////////// + + struct Active { + Point64 bot; + Point64 top; + int64_t curr_x = 0; //current (updated at every new scanline) + double dx = 0.0; + int wind_dx = 1; //1 or -1 depending on winding direction + int wind_cnt = 0; + int wind_cnt2 = 0; //winding count of the opposite polytype + OutRec* outrec = nullptr; + //AEL: 'active edge list' (Vatti's AET - active edge table) + // a linked list of all edges (from left to right) that are present + // (or 'active') within the current scanbeam (a horizontal 'beam' that + // sweeps from bottom to top over the paths in the clipping operation). + Active* prev_in_ael = nullptr; + Active* next_in_ael = nullptr; + //SEL: 'sorted edge list' (Vatti's ST - sorted table) + // linked list used when sorting edges into their new positions at the + // top of scanbeams, but also (re)used to process horizontals. + Active* prev_in_sel = nullptr; + Active* next_in_sel = nullptr; + Active* jump = nullptr; + Vertex* vertex_top = nullptr; + LocalMinima* local_min = nullptr; // the bottom of an edge 'bound' (also Vatti) + bool is_left_bound = false; + JoinWith join_with = JoinWith::None; + }; + + struct LocalMinima { + Vertex* vertex; + PathType polytype; + bool is_open; + LocalMinima(Vertex* v, PathType pt, bool open) : + vertex(v), polytype(pt), is_open(open){} + }; + + struct IntersectNode { + Point64 pt; + Active* edge1; + Active* edge2; + IntersectNode() : pt(Point64(0,0)), edge1(NULL), edge2(NULL) {} + IntersectNode(Active* e1, Active* e2, Point64& pt_) : + pt(pt_), edge1(e1), edge2(e2) {} + }; + + struct HorzSegment { + OutPt* left_op; + OutPt* right_op = nullptr; + bool left_to_right = true; + HorzSegment() : left_op(nullptr) { } + explicit HorzSegment(OutPt* op) : left_op(op) { } + }; + + struct HorzJoin { + OutPt* op1 = nullptr; + OutPt* op2 = nullptr; + HorzJoin() {}; + explicit HorzJoin(OutPt* ltr, OutPt* rtl) : op1(ltr), op2(rtl) { } + }; + +#ifdef USINGZ + typedef std::function ZCallback64; + + typedef std::function ZCallbackD; +#endif + + typedef std::vector HorzSegmentList; + typedef std::unique_ptr LocalMinima_ptr; + typedef std::vector LocalMinimaList; + typedef std::vector IntersectNodeList; + + // ClipperBase ------------------------------------------------------------- + + class ClipperBase { + private: + ClipType cliptype_ = ClipType::None; + FillRule fillrule_ = FillRule::EvenOdd; + FillRule fillpos = FillRule::Positive; + int64_t bot_y_ = 0; + bool minima_list_sorted_ = false; + bool using_polytree_ = false; + Active* actives_ = nullptr; + Active *sel_ = nullptr; + LocalMinimaList minima_list_; //pointers in case of memory reallocs + LocalMinimaList::iterator current_locmin_iter_; + std::vector vertex_lists_; + std::priority_queue scanline_list_; + IntersectNodeList intersect_nodes_; + HorzSegmentList horz_seg_list_; + std::vector horz_join_list_; + void Reset(); + inline void InsertScanline(int64_t y); + inline bool PopScanline(int64_t &y); + inline bool PopLocalMinima(int64_t y, LocalMinima*& local_minima); + void DisposeAllOutRecs(); + void DisposeVerticesAndLocalMinima(); + void DeleteEdges(Active*& e); + inline void AddLocMin(Vertex &vert, PathType polytype, bool is_open); + bool IsContributingClosed(const Active &e) const; + inline bool IsContributingOpen(const Active &e) const; + void SetWindCountForClosedPathEdge(Active &edge); + void SetWindCountForOpenPathEdge(Active &e); + void InsertLocalMinimaIntoAEL(int64_t bot_y); + void InsertLeftEdge(Active &e); + inline void PushHorz(Active &e); + inline bool PopHorz(Active *&e); + inline OutPt* StartOpenPath(Active &e, const Point64& pt); + inline void UpdateEdgeIntoAEL(Active *e); + OutPt* IntersectEdges(Active &e1, Active &e2, const Point64& pt); + inline void DeleteFromAEL(Active &e); + inline void AdjustCurrXAndCopyToSEL(const int64_t top_y); + void DoIntersections(const int64_t top_y); + void AddNewIntersectNode(Active &e1, Active &e2, const int64_t top_y); + bool BuildIntersectList(const int64_t top_y); + void ProcessIntersectList(); + void SwapPositionsInAEL(Active& edge1, Active& edge2); + OutRec* NewOutRec(); + OutPt* AddOutPt(const Active &e, const Point64& pt); + OutPt* AddLocalMinPoly(Active &e1, Active &e2, + const Point64& pt, bool is_new = false); + OutPt* AddLocalMaxPoly(Active &e1, Active &e2, const Point64& pt); + void DoHorizontal(Active &horz); + bool ResetHorzDirection(const Active &horz, const Vertex* max_vertex, + int64_t &horz_left, int64_t &horz_right); + void DoTopOfScanbeam(const int64_t top_y); + Active *DoMaxima(Active &e); + void JoinOutrecPaths(Active &e1, Active &e2); + void CompleteSplit(OutPt* op1, OutPt* op2, OutRec& outrec); + void FixSelfIntersects(OutRec* outrec); + void DoSplitOp(OutRec* outRec, OutPt* splitOp); + + inline void AddTrialHorzJoin(OutPt* op); + void ConvertHorzSegsToJoins(); + void ProcessHorzJoins(); + + void Split(Active& e, const Point64& pt); + inline void CheckJoinLeft(Active& e, + const Point64& pt, bool check_curr_x = false); + inline void CheckJoinRight(Active& e, + const Point64& pt, bool check_curr_x = false); + protected: + int error_code_ = 0; + bool has_open_paths_ = false; + bool succeeded_ = true; + OutRecList outrec_list_; //pointers in case list memory reallocated + bool ExecuteInternal(ClipType ct, FillRule ft, bool use_polytrees); + void CleanCollinear(OutRec* outrec); + bool CheckBounds(OutRec* outrec); + void RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath); + void DeepCheckOwners(OutRec* outrec, PolyPath* polypath); +#ifdef USINGZ + ZCallback64 zCallback_ = nullptr; + void SetZ(const Active& e1, const Active& e2, Point64& pt); +#endif + void CleanUp(); // unlike Clear, CleanUp preserves added paths + void AddPath(const Path64& path, PathType polytype, bool is_open); + void AddPaths(const Paths64& paths, PathType polytype, bool is_open); + public: + virtual ~ClipperBase(); + int ErrorCode() { return error_code_; }; + bool PreserveCollinear = true; + bool ReverseSolution = false; + void Clear(); +#ifdef USINGZ + int64_t DefaultZ = 0; +#endif + }; + + // PolyPath / PolyTree -------------------------------------------------------- + + //PolyTree: is intended as a READ-ONLY data structure for CLOSED paths returned + //by clipping operations. While this structure is more complex than the + //alternative Paths structure, it does preserve path 'ownership' - ie those + //paths that contain (or own) other paths. This will be useful to some users. + + class PolyPath { + protected: + PolyPath* parent_; + public: + PolyPath(PolyPath* parent = nullptr): parent_(parent){} + virtual ~PolyPath() {}; + //https://en.cppreference.com/w/cpp/language/rule_of_three + PolyPath(const PolyPath&) = delete; + PolyPath& operator=(const PolyPath&) = delete; + + unsigned Level() const + { + unsigned result = 0; + const PolyPath* p = parent_; + while (p) { ++result; p = p->parent_; } + return result; + } + + virtual PolyPath* AddChild(const Path64& path) = 0; + + virtual void Clear() = 0; + virtual size_t Count() const { return 0; } + + const PolyPath* Parent() const { return parent_; } + + bool IsHole() const + { + unsigned lvl = Level(); + //Even levels except level 0 + return lvl && !(lvl & 1); + } + }; + + typedef typename std::vector> PolyPath64List; + typedef typename std::vector> PolyPathDList; + + class PolyPath64 : public PolyPath { + private: + PolyPath64List childs_; + Path64 polygon_; + public: + explicit PolyPath64(PolyPath64* parent = nullptr) : PolyPath(parent) {} + + ~PolyPath64() { + childs_.resize(0); + } + + const PolyPath64* operator [] (size_t index) const + { + return childs_[index].get(); + } + + const PolyPath64* Child(size_t index) const + { + return childs_[index].get(); + } + + PolyPath64List::const_iterator begin() const { return childs_.cbegin(); } + PolyPath64List::const_iterator end() const { return childs_.cend(); } + + PolyPath64* AddChild(const Path64& path) override + { + auto p = std::make_unique(this); + auto* result = childs_.emplace_back(std::move(p)).get(); + result->polygon_ = path; + return result; + } + + void Clear() override + { + childs_.resize(0); + } + + size_t Count() const override + { + return childs_.size(); + } + + const Path64& Polygon() const { return polygon_; }; + + double Area() const + { + return std::accumulate(childs_.cbegin(), childs_.cend(), + Clipper2Lib::Area(polygon_), + [](double a, const auto& child) {return a + child->Area(); }); + } + + }; + + class PolyPathD : public PolyPath { + private: + PolyPathDList childs_; + double inv_scale_; + PathD polygon_; + public: + explicit PolyPathD(PolyPathD* parent = nullptr) : PolyPath(parent) + { + inv_scale_ = parent ? parent->inv_scale_ : 1.0; + } + + ~PolyPathD() { + childs_.resize(0); + } + + const PolyPathD* operator [] (size_t index) const + { + return childs_[index].get(); + } + + const PolyPathD* Child(size_t index) const + { + return childs_[index].get(); + } + + PolyPathDList::const_iterator begin() const { return childs_.cbegin(); } + PolyPathDList::const_iterator end() const { return childs_.cend(); } + + void SetInvScale(double value) { inv_scale_ = value; } + double InvScale() { return inv_scale_; } + PolyPathD* AddChild(const Path64& path) override + { + int error_code = 0; + auto p = std::make_unique(this); + PolyPathD* result = childs_.emplace_back(std::move(p)).get(); + result->polygon_ = ScalePath(path, inv_scale_, error_code); + return result; + } + + void Clear() override + { + childs_.resize(0); + } + + size_t Count() const override + { + return childs_.size(); + } + + const PathD& Polygon() const { return polygon_; }; + + double Area() const + { + return std::accumulate(childs_.begin(), childs_.end(), + Clipper2Lib::Area(polygon_), + [](double a, const auto& child) {return a + child->Area(); }); + } + }; + + class Clipper64 : public ClipperBase + { + private: + void BuildPaths64(Paths64& solutionClosed, Paths64* solutionOpen); + void BuildTree64(PolyPath64& polytree, Paths64& open_paths); + public: +#ifdef USINGZ + void SetZCallback(ZCallback64 cb) { zCallback_ = cb; } +#endif + + void AddSubject(const Paths64& subjects) + { + AddPaths(subjects, PathType::Subject, false); + } + void AddOpenSubject(const Paths64& open_subjects) + { + AddPaths(open_subjects, PathType::Subject, true); + } + void AddClip(const Paths64& clips) + { + AddPaths(clips, PathType::Clip, false); + } + + bool Execute(ClipType clip_type, + FillRule fill_rule, Paths64& closed_paths) + { + Paths64 dummy; + return Execute(clip_type, fill_rule, closed_paths, dummy); + } + + bool Execute(ClipType clip_type, FillRule fill_rule, + Paths64& closed_paths, Paths64& open_paths) + { + closed_paths.clear(); + open_paths.clear(); + if (ExecuteInternal(clip_type, fill_rule, false)) + BuildPaths64(closed_paths, &open_paths); + CleanUp(); + return succeeded_; + } + + bool Execute(ClipType clip_type, FillRule fill_rule, PolyTree64& polytree) + { + Paths64 dummy; + return Execute(clip_type, fill_rule, polytree, dummy); + } + + bool Execute(ClipType clip_type, + FillRule fill_rule, PolyTree64& polytree, Paths64& open_paths) + { + if (ExecuteInternal(clip_type, fill_rule, true)) + { + open_paths.clear(); + polytree.Clear(); + BuildTree64(polytree, open_paths); + } + CleanUp(); + return succeeded_; + } + }; + + class ClipperD : public ClipperBase { + private: + double scale_ = 1.0, invScale_ = 1.0; +#ifdef USINGZ + ZCallbackD zCallbackD_ = nullptr; +#endif + void BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen); + void BuildTreeD(PolyPathD& polytree, PathsD& open_paths); + public: + explicit ClipperD(int precision = 2) : ClipperBase() + { + CheckPrecision(precision, error_code_); + // to optimize scaling / descaling precision + // set the scale to a power of double's radix (2) (#25) + scale_ = std::pow(std::numeric_limits::radix, + std::ilogb(std::pow(10, precision)) + 1); + invScale_ = 1 / scale_; + } + +#ifdef USINGZ + void SetZCallback(ZCallbackD cb) { zCallbackD_ = cb; }; + + void ZCB(const Point64& e1bot, const Point64& e1top, + const Point64& e2bot, const Point64& e2top, Point64& pt) + { + // de-scale (x & y) + // temporarily convert integers to their initial float values + // this will slow clipping marginally but will make it much easier + // to understand the coordinates passed to the callback function + PointD tmp = PointD(pt) * invScale_; + PointD e1b = PointD(e1bot) * invScale_; + PointD e1t = PointD(e1top) * invScale_; + PointD e2b = PointD(e2bot) * invScale_; + PointD e2t = PointD(e2top) * invScale_; + zCallbackD_(e1b,e1t, e2b, e2t, tmp); + pt.z = tmp.z; // only update 'z' + }; + + void CheckCallback() + { + if(zCallbackD_) + // if the user defined float point callback has been assigned + // then assign the proxy callback function + ClipperBase::zCallback_ = + std::bind(&ClipperD::ZCB, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, + std::placeholders::_4, std::placeholders::_5); + else + ClipperBase::zCallback_ = nullptr; + } + +#endif + + void AddSubject(const PathsD& subjects) + { + AddPaths(ScalePaths(subjects, scale_, error_code_), PathType::Subject, false); + } + + void AddOpenSubject(const PathsD& open_subjects) + { + AddPaths(ScalePaths(open_subjects, scale_, error_code_), PathType::Subject, true); + } + + void AddClip(const PathsD& clips) + { + AddPaths(ScalePaths(clips, scale_, error_code_), PathType::Clip, false); + } + + bool Execute(ClipType clip_type, FillRule fill_rule, PathsD& closed_paths) + { + PathsD dummy; + return Execute(clip_type, fill_rule, closed_paths, dummy); + } + + bool Execute(ClipType clip_type, + FillRule fill_rule, PathsD& closed_paths, PathsD& open_paths) + { +#ifdef USINGZ + CheckCallback(); +#endif + if (ExecuteInternal(clip_type, fill_rule, false)) + { + BuildPathsD(closed_paths, &open_paths); + } + CleanUp(); + return succeeded_; + } + + bool Execute(ClipType clip_type, FillRule fill_rule, PolyTreeD& polytree) + { + PathsD dummy; + return Execute(clip_type, fill_rule, polytree, dummy); + } + + bool Execute(ClipType clip_type, + FillRule fill_rule, PolyTreeD& polytree, PathsD& open_paths) + { +#ifdef USINGZ + CheckCallback(); +#endif + if (ExecuteInternal(clip_type, fill_rule, true)) + { + polytree.Clear(); + polytree.SetInvScale(invScale_); + open_paths.clear(); + BuildTreeD(polytree, open_paths); + } + CleanUp(); + return succeeded_; + } + + }; + +} // namespace + +#endif // CLIPPER_ENGINE_H diff --git a/thirdparty/clipper2/include/clipper2/clipper.export.h b/thirdparty/clipper2/include/clipper2/clipper.export.h new file mode 100644 index 00000000000..e8d678a41d5 --- /dev/null +++ b/thirdparty/clipper2/include/clipper2/clipper.export.h @@ -0,0 +1,774 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 23 March 2023 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2023 * +* Purpose : This module exports the Clipper2 Library (ie DLL/so) * +* License : http://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +// The exported functions below refer to simple structures that +// can be understood across multiple languages. Consequently +// Path64, PathD, Polytree64 etc are converted from C++ classes +// (std::vector<> etc) into the following data structures: +// +// CPath64 (int64_t*) & CPathD (double_t*): +// Path64 and PathD are converted into arrays of x,y coordinates. +// However in these arrays the first x,y coordinate pair is a +// counter with 'x' containing the number of following coordinate +// pairs. ('y' should be 0, with one exception explained below.) +// __________________________________ +// |counter|coord1|coord2|...|coordN| +// |N ,0 |x1, y1|x2, y2|...|xN, yN| +// __________________________________ +// +// CPaths64 (int64_t**) & CPathsD (double_t**): +// These are arrays of pointers to CPath64 and CPathD where +// the first pointer is to a 'counter path'. This 'counter +// path' has a single x,y coord pair with 'y' (not 'x') +// containing the number of paths that follow. ('x' = 0). +// _______________________________ +// |counter|path1|path2|...|pathN| +// |addr0 |addr1|addr2|...|addrN| (*addr0[0]=0; *addr0[1]=N) +// _______________________________ +// +// The structures of CPolytree64 and CPolytreeD are defined +// below and these structures don't need to be explained here. + +#ifndef CLIPPER2_EXPORT_H +#define CLIPPER2_EXPORT_H + +#include +#include + +#include "clipper2/clipper.core.h" +#include "clipper2/clipper.engine.h" +#include "clipper2/clipper.offset.h" +#include "clipper2/clipper.rectclip.h" + +namespace Clipper2Lib { + +typedef int64_t* CPath64; +typedef int64_t** CPaths64; +typedef double* CPathD; +typedef double** CPathsD; + +typedef struct CPolyPath64 { + CPath64 polygon; + uint32_t is_hole; + uint32_t child_count; + CPolyPath64* childs; +} +CPolyTree64; + +typedef struct CPolyPathD { + CPathD polygon; + uint32_t is_hole; + uint32_t child_count; + CPolyPathD* childs; +} +CPolyTreeD; + +template +struct CRect { + T left; + T top; + T right; + T bottom; +}; + +typedef CRect CRect64; +typedef CRect CRectD; + +template +inline bool CRectIsEmpty(const CRect& rect) +{ + return (rect.right <= rect.left) || (rect.bottom <= rect.top); +} + +template +inline Rect CRectToRect(const CRect& rect) +{ + Rect result; + result.left = rect.left; + result.top = rect.top; + result.right = rect.right; + result.bottom = rect.bottom; + return result; +} + +#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport) + +////////////////////////////////////////////////////// +// EXPORTED FUNCTION DEFINITIONS +////////////////////////////////////////////////////// + +EXTERN_DLL_EXPORT const char* Version(); + +// Some of the functions below will return data in the various CPath +// and CPolyTree structures which are pointers to heap allocated +// memory. Eventually this memory will need to be released with one +// of the following 'DisposeExported' functions. (This may be the +// only safe way to release this memory since the executable +// accessing these exported functions may use a memory manager that +// allocates and releases heap memory in a different way. Also, +// CPath structures that have been constructed by the executable +// should not be destroyed using these 'DisposeExported' functions.) +EXTERN_DLL_EXPORT void DisposeExportedCPath64(CPath64 p); +EXTERN_DLL_EXPORT void DisposeExportedCPaths64(CPaths64& pp); +EXTERN_DLL_EXPORT void DisposeExportedCPathD(CPathD p); +EXTERN_DLL_EXPORT void DisposeExportedCPathsD(CPathsD& pp); +EXTERN_DLL_EXPORT void DisposeExportedCPolyTree64(CPolyTree64*& cpt); +EXTERN_DLL_EXPORT void DisposeExportedCPolyTreeD(CPolyTreeD*& cpt); + +// Boolean clipping: +// cliptype: None=0, Intersection=1, Union=2, Difference=3, Xor=4 +// fillrule: EvenOdd=0, NonZero=1, Positive=2, Negative=3 +EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype, + uint8_t fillrule, const CPaths64 subjects, + const CPaths64 subjects_open, const CPaths64 clips, + CPaths64& solution, CPaths64& solution_open, + bool preserve_collinear = true, bool reverse_solution = false); +EXTERN_DLL_EXPORT int BooleanOpPt64(uint8_t cliptype, + uint8_t fillrule, const CPaths64 subjects, + const CPaths64 subjects_open, const CPaths64 clips, + CPolyTree64*& solution, CPaths64& solution_open, + bool preserve_collinear = true, bool reverse_solution = false); +EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype, + uint8_t fillrule, const CPathsD subjects, + const CPathsD subjects_open, const CPathsD clips, + CPathsD& solution, CPathsD& solution_open, int precision = 2, + bool preserve_collinear = true, bool reverse_solution = false); +EXTERN_DLL_EXPORT int BooleanOpPtD(uint8_t cliptype, + uint8_t fillrule, const CPathsD subjects, + const CPathsD subjects_open, const CPathsD clips, + CPolyTreeD*& solution, CPathsD& solution_open, int precision = 2, + bool preserve_collinear = true, bool reverse_solution = false); + +// Polygon offsetting (inflate/deflate): +// jointype: Square=0, Round=1, Miter=2 +// endtype: Polygon=0, Joined=1, Butt=2, Square=3, Round=4 +EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths, + double delta, uint8_t jointype, uint8_t endtype, + double miter_limit = 2.0, double arc_tolerance = 0.0, + bool reverse_solution = false); +EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths, + double delta, uint8_t jointype, uint8_t endtype, + int precision = 2, double miter_limit = 2.0, + double arc_tolerance = 0.0, bool reverse_solution = false); + +// ExecuteRectClip & ExecuteRectClipLines: +EXTERN_DLL_EXPORT CPaths64 ExecuteRectClip64(const CRect64& rect, + const CPaths64 paths, bool convex_only = false); +EXTERN_DLL_EXPORT CPathsD ExecuteRectClipD(const CRectD& rect, + const CPathsD paths, int precision = 2, bool convex_only = false); +EXTERN_DLL_EXPORT CPaths64 ExecuteRectClipLines64(const CRect64& rect, + const CPaths64 paths); +EXTERN_DLL_EXPORT CPathsD ExecuteRectClipLinesD(const CRectD& rect, + const CPathsD paths, int precision = 2); + +////////////////////////////////////////////////////// +// INTERNAL FUNCTIONS +////////////////////////////////////////////////////// + +inline CPath64 CreateCPath64(size_t cnt1, size_t cnt2); +inline CPath64 CreateCPath64(const Path64& p); +inline CPaths64 CreateCPaths64(const Paths64& pp); +inline Path64 ConvertCPath64(const CPath64& p); +inline Paths64 ConvertCPaths64(const CPaths64& pp); + +inline CPathD CreateCPathD(size_t cnt1, size_t cnt2); +inline CPathD CreateCPathD(const PathD& p); +inline CPathsD CreateCPathsD(const PathsD& pp); +inline PathD ConvertCPathD(const CPathD& p); +inline PathsD ConvertCPathsD(const CPathsD& pp); + +// the following function avoid multiple conversions +inline CPathD CreateCPathD(const Path64& p, double scale); +inline CPathsD CreateCPathsD(const Paths64& pp, double scale); +inline Path64 ConvertCPathD(const CPathD& p, double scale); +inline Paths64 ConvertCPathsD(const CPathsD& pp, double scale); + +inline CPolyTree64* CreateCPolyTree64(const PolyTree64& pt); +inline CPolyTreeD* CreateCPolyTreeD(const PolyTree64& pt, double scale); + +EXTERN_DLL_EXPORT const char* Version() +{ + return CLIPPER2_VERSION; +} + +EXTERN_DLL_EXPORT void DisposeExportedCPath64(CPath64 p) +{ + if (p) delete[] p; +} + +EXTERN_DLL_EXPORT void DisposeExportedCPaths64(CPaths64& pp) +{ + if (!pp) return; + CPaths64 v = pp; + CPath64 cnts = *v; + const size_t cnt = static_cast(cnts[1]); + for (size_t i = 0; i <= cnt; ++i) //nb: cnt +1 + DisposeExportedCPath64(*v++); + delete[] pp; + pp = nullptr; +} + +EXTERN_DLL_EXPORT void DisposeExportedCPathD(CPathD p) +{ + if (p) delete[] p; +} + +EXTERN_DLL_EXPORT void DisposeExportedCPathsD(CPathsD& pp) +{ + if (!pp) return; + CPathsD v = pp; + CPathD cnts = *v; + size_t cnt = static_cast(cnts[1]); + for (size_t i = 0; i <= cnt; ++i) //nb: cnt +1 + DisposeExportedCPathD(*v++); + delete[] pp; + pp = nullptr; +} + +EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype, + uint8_t fillrule, const CPaths64 subjects, + const CPaths64 subjects_open, const CPaths64 clips, + CPaths64& solution, CPaths64& solution_open, + bool preserve_collinear, bool reverse_solution) +{ + if (cliptype > static_cast(ClipType::Xor)) return -4; + if (fillrule > static_cast(FillRule::Negative)) return -3; + + Paths64 sub, sub_open, clp, sol, sol_open; + sub = ConvertCPaths64(subjects); + sub_open = ConvertCPaths64(subjects_open); + clp = ConvertCPaths64(clips); + + Clipper64 clipper; + clipper.PreserveCollinear = preserve_collinear; + clipper.ReverseSolution = reverse_solution; + if (sub.size() > 0) clipper.AddSubject(sub); + if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open); + if (clp.size() > 0) clipper.AddClip(clp); + if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), sol, sol_open)) + return -1; // clipping bug - should never happen :) + solution = CreateCPaths64(sol); + solution_open = CreateCPaths64(sol_open); + return 0; //success !! +} + +EXTERN_DLL_EXPORT int BooleanOpPt64(uint8_t cliptype, + uint8_t fillrule, const CPaths64 subjects, + const CPaths64 subjects_open, const CPaths64 clips, + CPolyTree64*& solution, CPaths64& solution_open, + bool preserve_collinear, bool reverse_solution) +{ + if (cliptype > static_cast(ClipType::Xor)) return -4; + if (fillrule > static_cast(FillRule::Negative)) return -3; + Paths64 sub, sub_open, clp, sol_open; + sub = ConvertCPaths64(subjects); + sub_open = ConvertCPaths64(subjects_open); + clp = ConvertCPaths64(clips); + + PolyTree64 pt; + Clipper64 clipper; + clipper.PreserveCollinear = preserve_collinear; + clipper.ReverseSolution = reverse_solution; + if (sub.size() > 0) clipper.AddSubject(sub); + if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open); + if (clp.size() > 0) clipper.AddClip(clp); + if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), pt, sol_open)) + return -1; // clipping bug - should never happen :) + + solution = CreateCPolyTree64(pt); + solution_open = CreateCPaths64(sol_open); + return 0; //success !! +} + +EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype, + uint8_t fillrule, const CPathsD subjects, + const CPathsD subjects_open, const CPathsD clips, + CPathsD& solution, CPathsD& solution_open, int precision, + bool preserve_collinear, bool reverse_solution) +{ + if (precision < -8 || precision > 8) return -5; + if (cliptype > static_cast(ClipType::Xor)) return -4; + if (fillrule > static_cast(FillRule::Negative)) return -3; + const double scale = std::pow(10, precision); + + Paths64 sub, sub_open, clp, sol, sol_open; + sub = ConvertCPathsD(subjects, scale); + sub_open = ConvertCPathsD(subjects_open, scale); + clp = ConvertCPathsD(clips, scale); + + Clipper64 clipper; + clipper.PreserveCollinear = preserve_collinear; + clipper.ReverseSolution = reverse_solution; + if (sub.size() > 0) clipper.AddSubject(sub); + if (sub_open.size() > 0) + clipper.AddOpenSubject(sub_open); + if (clp.size() > 0) clipper.AddClip(clp); + if (!clipper.Execute(ClipType(cliptype), + FillRule(fillrule), sol, sol_open)) return -1; + + if (sol.size() > 0) solution = CreateCPathsD(sol, 1 / scale); + if (sol_open.size() > 0) + solution_open = CreateCPathsD(sol_open, 1 / scale); + return 0; +} + +EXTERN_DLL_EXPORT int BooleanOpPtD(uint8_t cliptype, + uint8_t fillrule, const CPathsD subjects, + const CPathsD subjects_open, const CPathsD clips, + CPolyTreeD*& solution, CPathsD& solution_open, int precision, + bool preserve_collinear, bool reverse_solution) +{ + if (precision < -8 || precision > 8) return -5; + if (cliptype > static_cast(ClipType::Xor)) return -4; + if (fillrule > static_cast(FillRule::Negative)) return -3; + + const double scale = std::pow(10, precision); + Paths64 sub, sub_open, clp, sol_open; + sub = ConvertCPathsD(subjects, scale); + sub_open = ConvertCPathsD(subjects_open, scale); + clp = ConvertCPathsD(clips, scale); + + PolyTree64 sol; + Clipper64 clipper; + clipper.PreserveCollinear = preserve_collinear; + clipper.ReverseSolution = reverse_solution; + if (sub.size() > 0) clipper.AddSubject(sub); + if (sub_open.size() > 0) + clipper.AddOpenSubject(sub_open); + if (clp.size() > 0) clipper.AddClip(clp); + if (!clipper.Execute(ClipType(cliptype), + FillRule(fillrule), sol, sol_open)) return -1; + + solution = CreateCPolyTreeD(sol, 1 / scale); + if (sol_open.size() > 0) + solution_open = CreateCPathsD(sol_open, 1 / scale); + return 0; +} + +EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths, + double delta, uint8_t jointype, uint8_t endtype, double miter_limit, + double arc_tolerance, bool reverse_solution) +{ + Paths64 pp; + pp = ConvertCPaths64(paths); + + ClipperOffset clip_offset( miter_limit, + arc_tolerance, reverse_solution); + clip_offset.AddPaths(pp, JoinType(jointype), EndType(endtype)); + Paths64 result; + clip_offset.Execute(delta, result); + return CreateCPaths64(result); +} + +EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths, + double delta, uint8_t jointype, uint8_t endtype, + int precision, double miter_limit, + double arc_tolerance, bool reverse_solution) +{ + if (precision < -8 || precision > 8 || !paths) return nullptr; + const double scale = std::pow(10, precision); + ClipperOffset clip_offset(miter_limit, arc_tolerance, reverse_solution); + Paths64 pp = ConvertCPathsD(paths, scale); + clip_offset.AddPaths(pp, JoinType(jointype), EndType(endtype)); + Paths64 result; + clip_offset.Execute(delta * scale, result); + return CreateCPathsD(result, 1/scale); +} + +EXTERN_DLL_EXPORT CPaths64 ExecuteRectClip64(const CRect64& rect, + const CPaths64 paths, bool convex_only) +{ + if (CRectIsEmpty(rect) || !paths) return nullptr; + Rect64 r64 = CRectToRect(rect); + class RectClip rc(r64); + Paths64 pp = ConvertCPaths64(paths); + Paths64 result = rc.Execute(pp, convex_only); + return CreateCPaths64(result); +} + +EXTERN_DLL_EXPORT CPathsD ExecuteRectClipD(const CRectD& rect, + const CPathsD paths, int precision, bool convex_only) +{ + if (CRectIsEmpty(rect) || !paths) return nullptr; + if (precision < -8 || precision > 8) return nullptr; + const double scale = std::pow(10, precision); + + RectD r = CRectToRect(rect); + Rect64 rec = ScaleRect(r, scale); + Paths64 pp = ConvertCPathsD(paths, scale); + class RectClip rc(rec); + Paths64 result = rc.Execute(pp, convex_only); + return CreateCPathsD(result, 1/scale); +} + +EXTERN_DLL_EXPORT CPaths64 ExecuteRectClipLines64(const CRect64& rect, + const CPaths64 paths) +{ + if (CRectIsEmpty(rect) || !paths) return nullptr; + Rect64 r = CRectToRect(rect); + class RectClipLines rcl (r); + Paths64 pp = ConvertCPaths64(paths); + Paths64 result = rcl.Execute(pp); + return CreateCPaths64(result); +} + +EXTERN_DLL_EXPORT CPathsD ExecuteRectClipLinesD(const CRectD& rect, + const CPathsD paths, int precision) +{ + if (CRectIsEmpty(rect) || !paths) return nullptr; + if (precision < -8 || precision > 8) return nullptr; + const double scale = std::pow(10, precision); + Rect64 r = ScaleRect(CRectToRect(rect), scale); + class RectClipLines rcl(r); + Paths64 pp = ConvertCPathsD(paths, scale); + Paths64 result = rcl.Execute(pp); + return CreateCPathsD(result, 1/scale); +} + +inline CPath64 CreateCPath64(size_t cnt1, size_t cnt2) +{ + // allocates memory for CPath64, fills in the counter, and + // returns the structure ready to be filled with path data + CPath64 result = new int64_t[2 + cnt1 *2]; + result[0] = cnt1; + result[1] = cnt2; + return result; +} + +inline CPath64 CreateCPath64(const Path64& p) +{ + // allocates memory for CPath64, fills the counter + // and returns the memory filled with path data + size_t cnt = p.size(); + if (!cnt) return nullptr; + CPath64 result = CreateCPath64(cnt, 0); + CPath64 v = result; + v += 2; // skip counters + for (const Point64& pt : p) + { + *v++ = pt.x; + *v++ = pt.y; + } + return result; +} + +inline Path64 ConvertCPath64(const CPath64& p) +{ + Path64 result; + if (p && *p) + { + CPath64 v = p; + const size_t cnt = static_cast(p[0]); + v += 2; // skip counters + result.reserve(cnt); + for (size_t i = 0; i < cnt; ++i) + { + // x,y here avoids right to left function evaluation + // result.push_back(Point64(*v++, *v++)); + int64_t x = *v++; + int64_t y = *v++; + result.push_back(Point64(x, y)); + } + } + return result; +} + +inline CPaths64 CreateCPaths64(const Paths64& pp) +{ + // allocates memory for multiple CPath64 and + // and returns this memory filled with path data + size_t cnt = pp.size(), cnt2 = cnt; + + // don't allocate space for empty paths + for (size_t i = 0; i < cnt; ++i) + if (!pp[i].size()) --cnt2; + if (!cnt2) return nullptr; + + CPaths64 result = new int64_t* [cnt2 + 1]; + CPaths64 v = result; + *v++ = CreateCPath64(0, cnt2); // assign a counter path + for (const Path64& p : pp) + { + *v = CreateCPath64(p); + if (*v) ++v; + } + return result; +} + +inline Paths64 ConvertCPaths64(const CPaths64& pp) +{ + Paths64 result; + if (pp) + { + CPaths64 v = pp; + CPath64 cnts = pp[0]; + const size_t cnt = static_cast(cnts[1]); // nb 2nd cnt + ++v; // skip cnts + result.reserve(cnt); + for (size_t i = 0; i < cnt; ++i) + result.push_back(ConvertCPath64(*v++)); + } + return result; +} + +inline CPathD CreateCPathD(size_t cnt1, size_t cnt2) +{ + // allocates memory for CPathD, fills in the counter, and + // returns the structure ready to be filled with path data + CPathD result = new double[2 + cnt1 * 2]; + result[0] = static_cast(cnt1); + result[1] = static_cast(cnt2); + return result; +} + +inline CPathD CreateCPathD(const PathD& p) +{ + // allocates memory for CPath, fills the counter + // and returns the memory fills with path data + size_t cnt = p.size(); + if (!cnt) return nullptr; + CPathD result = CreateCPathD(cnt, 0); + CPathD v = result; + v += 2; // skip counters + for (const PointD& pt : p) + { + *v++ = pt.x; + *v++ = pt.y; + } + return result; +} + +inline PathD ConvertCPathD(const CPathD& p) +{ + PathD result; + if (p) + { + CPathD v = p; + size_t cnt = static_cast(v[0]); + v += 2; // skip counters + result.reserve(cnt); + for (size_t i = 0; i < cnt; ++i) + { + // x,y here avoids right to left function evaluation + // result.push_back(PointD(*v++, *v++)); + double x = *v++; + double y = *v++; + result.push_back(PointD(x, y)); + } + } + return result; +} + +inline CPathsD CreateCPathsD(const PathsD& pp) +{ + size_t cnt = pp.size(), cnt2 = cnt; + // don't allocate space for empty paths + for (size_t i = 0; i < cnt; ++i) + if (!pp[i].size()) --cnt2; + if (!cnt2) return nullptr; + CPathsD result = new double * [cnt2 + 1]; + CPathsD v = result; + *v++ = CreateCPathD(0, cnt2); // assign counter path + for (const PathD& p : pp) + { + *v = CreateCPathD(p); + if (*v) { ++v; } + } + return result; +} + +inline PathsD ConvertCPathsD(const CPathsD& pp) +{ + PathsD result; + if (pp) + { + CPathsD v = pp; + CPathD cnts = v[0]; + size_t cnt = static_cast(cnts[1]); + ++v; // skip cnts path + result.reserve(cnt); + for (size_t i = 0; i < cnt; ++i) + result.push_back(ConvertCPathD(*v++)); + } + return result; +} + +inline Path64 ConvertCPathD(const CPathD& p, double scale) +{ + Path64 result; + if (p) + { + CPathD v = p; + size_t cnt = static_cast(*v); + v += 2; // skip counters + result.reserve(cnt); + for (size_t i = 0; i < cnt; ++i) + { + // x,y here avoids right to left function evaluation + // result.push_back(PointD(*v++, *v++)); + double x = *v++ * scale; + double y = *v++ * scale; + result.push_back(Point64(x, y)); + } + } + return result; +} + +inline Paths64 ConvertCPathsD(const CPathsD& pp, double scale) +{ + Paths64 result; + if (pp) + { + CPathsD v = pp; + CPathD cnts = v[0]; + size_t cnt = static_cast(cnts[1]); + result.reserve(cnt); + ++v; // skip cnts path + for (size_t i = 0; i < cnt; ++i) + result.push_back(ConvertCPathD(*v++, scale)); + } + return result; +} + +inline CPathD CreateCPathD(const Path64& p, double scale) +{ + // allocates memory for CPathD, fills in the counter, and + // returns the structure filled with *scaled* path data + size_t cnt = p.size(); + if (!cnt) return nullptr; + CPathD result = CreateCPathD(cnt, 0); + CPathD v = result; + v += 2; // skip cnts + for (const Point64& pt : p) + { + *v++ = pt.x * scale; + *v++ = pt.y * scale; + } + return result; +} + +inline CPathsD CreateCPathsD(const Paths64& pp, double scale) +{ + // allocates memory for *multiple* CPathD, and + // returns the structure filled with scaled path data + size_t cnt = pp.size(), cnt2 = cnt; + // don't allocate space for empty paths + for (size_t i = 0; i < cnt; ++i) + if (!pp[i].size()) --cnt2; + if (!cnt2) return nullptr; + CPathsD result = new double* [cnt2 + 1]; + CPathsD v = result; + *v++ = CreateCPathD(0, cnt2); + for (const Path64& p : pp) + { + *v = CreateCPathD(p, scale); + if (*v) ++v; + } + return result; +} + +inline void InitCPolyPath64(CPolyTree64* cpt, + bool is_hole, const std::unique_ptr & pp) +{ + cpt->polygon = CreateCPath64(pp->Polygon()); + cpt->is_hole = is_hole; + size_t child_cnt = pp->Count(); + cpt->child_count = static_cast(child_cnt); + cpt->childs = nullptr; + if (!child_cnt) return; + cpt->childs = new CPolyPath64[child_cnt]; + CPolyPath64* child = cpt->childs; + for (const std::unique_ptr & pp_child : *pp) + InitCPolyPath64(child++, !is_hole, pp_child); +} + +inline CPolyTree64* CreateCPolyTree64(const PolyTree64& pt) +{ + CPolyTree64* result = new CPolyTree64(); + result->polygon = nullptr; + result->is_hole = false; + size_t child_cnt = pt.Count(); + result->childs = nullptr; + result->child_count = static_cast(child_cnt); + if (!child_cnt) return result; + result->childs = new CPolyPath64[child_cnt]; + CPolyPath64* child = result->childs; + for (const std::unique_ptr & pp : pt) + InitCPolyPath64(child++, true, pp); + return result; +} + +inline void DisposeCPolyPath64(CPolyPath64* cpp) +{ + if (!cpp->child_count) return; + CPolyPath64* child = cpp->childs; + for (size_t i = 0; i < cpp->child_count; ++i) + DisposeCPolyPath64(child); + delete[] cpp->childs; +} + +EXTERN_DLL_EXPORT void DisposeExportedCPolyTree64(CPolyTree64*& cpt) +{ + if (!cpt) return; + DisposeCPolyPath64(cpt); + delete cpt; + cpt = nullptr; +} + +inline void InitCPolyPathD(CPolyTreeD* cpt, + bool is_hole, const std::unique_ptr & pp, double scale) +{ + cpt->polygon = CreateCPathD(pp->Polygon(), scale); + cpt->is_hole = is_hole; + size_t child_cnt = pp->Count(); + cpt->child_count = static_cast(child_cnt); + cpt->childs = nullptr; + if (!child_cnt) return; + cpt->childs = new CPolyPathD[child_cnt]; + CPolyPathD* child = cpt->childs; + for (const std::unique_ptr & pp_child : *pp) + InitCPolyPathD(child++, !is_hole, pp_child, scale); +} + +inline CPolyTreeD* CreateCPolyTreeD(const PolyTree64& pt, double scale) +{ + CPolyTreeD* result = new CPolyTreeD(); + result->polygon = nullptr; + result->is_hole = false; + size_t child_cnt = pt.Count(); + result->child_count = static_cast(child_cnt); + result->childs = nullptr; + if (!child_cnt) return result; + result->childs = new CPolyPathD[child_cnt]; + CPolyPathD* child = result->childs; + for (const std::unique_ptr & pp : pt) + InitCPolyPathD(child++, true, pp, scale); + return result; +} + +inline void DisposeCPolyPathD(CPolyPathD* cpp) +{ + if (!cpp->child_count) return; + CPolyPathD* child = cpp->childs; + for (size_t i = 0; i < cpp->child_count; ++i) + DisposeCPolyPathD(child++); + delete[] cpp->childs; +} + +EXTERN_DLL_EXPORT void DisposeExportedCPolyTreeD(CPolyTreeD*& cpt) +{ + if (!cpt) return; + DisposeCPolyPathD(cpt); + delete cpt; + cpt = nullptr; +} + +} // end Clipper2Lib namespace + +#endif // CLIPPER2_EXPORT_H diff --git a/thirdparty/clipper2/include/clipper2/clipper.h b/thirdparty/clipper2/include/clipper2/clipper.h new file mode 100644 index 00000000000..6579f59c18a --- /dev/null +++ b/thirdparty/clipper2/include/clipper2/clipper.h @@ -0,0 +1,776 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 23 March 2023 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2023 * +* Purpose : This module provides a simple interface to the Clipper Library * +* License : http://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +#ifndef CLIPPER_H +#define CLIPPER_H + +#include +#include +#include + +#include "clipper.core.h" +#include "clipper.engine.h" +#include "clipper.offset.h" +#include "clipper.minkowski.h" +#include "clipper.rectclip.h" + +namespace Clipper2Lib { + + inline Paths64 BooleanOp(ClipType cliptype, FillRule fillrule, + const Paths64& subjects, const Paths64& clips) + { + Paths64 result; + Clipper64 clipper; + clipper.AddSubject(subjects); + clipper.AddClip(clips); + clipper.Execute(cliptype, fillrule, result); + return result; + } + + inline void BooleanOp(ClipType cliptype, FillRule fillrule, + const Paths64& subjects, const Paths64& clips, PolyTree64& solution) + { + Paths64 sol_open; + Clipper64 clipper; + clipper.AddSubject(subjects); + clipper.AddClip(clips); + clipper.Execute(cliptype, fillrule, solution, sol_open); + } + + inline PathsD BooleanOp(ClipType cliptype, FillRule fillrule, + const PathsD& subjects, const PathsD& clips, int precision = 2) + { + int error_code = 0; + CheckPrecision(precision, error_code); + PathsD result; + if (error_code) return result; + ClipperD clipper(precision); + clipper.AddSubject(subjects); + clipper.AddClip(clips); + clipper.Execute(cliptype, fillrule, result); + return result; + } + + inline void BooleanOp(ClipType cliptype, FillRule fillrule, + const PathsD& subjects, const PathsD& clips, + PolyTreeD& polytree, int precision = 2) + { + polytree.Clear(); + int error_code = 0; + CheckPrecision(precision, error_code); + if (error_code) return; + ClipperD clipper(precision); + clipper.AddSubject(subjects); + clipper.AddClip(clips); + clipper.Execute(cliptype, fillrule, polytree); + } + + inline Paths64 Intersect(const Paths64& subjects, const Paths64& clips, FillRule fillrule) + { + return BooleanOp(ClipType::Intersection, fillrule, subjects, clips); + } + + inline PathsD Intersect(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2) + { + return BooleanOp(ClipType::Intersection, fillrule, subjects, clips, decimal_prec); + } + + inline Paths64 Union(const Paths64& subjects, const Paths64& clips, FillRule fillrule) + { + return BooleanOp(ClipType::Union, fillrule, subjects, clips); + } + + inline PathsD Union(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2) + { + return BooleanOp(ClipType::Union, fillrule, subjects, clips, decimal_prec); + } + + inline Paths64 Union(const Paths64& subjects, FillRule fillrule) + { + Paths64 result; + Clipper64 clipper; + clipper.AddSubject(subjects); + clipper.Execute(ClipType::Union, fillrule, result); + return result; + } + + inline PathsD Union(const PathsD& subjects, FillRule fillrule, int precision = 2) + { + PathsD result; + int error_code = 0; + CheckPrecision(precision, error_code); + if (error_code) return result; + ClipperD clipper(precision); + clipper.AddSubject(subjects); + clipper.Execute(ClipType::Union, fillrule, result); + return result; + } + + inline Paths64 Difference(const Paths64& subjects, const Paths64& clips, FillRule fillrule) + { + return BooleanOp(ClipType::Difference, fillrule, subjects, clips); + } + + inline PathsD Difference(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2) + { + return BooleanOp(ClipType::Difference, fillrule, subjects, clips, decimal_prec); + } + + inline Paths64 Xor(const Paths64& subjects, const Paths64& clips, FillRule fillrule) + { + return BooleanOp(ClipType::Xor, fillrule, subjects, clips); + } + + inline PathsD Xor(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2) + { + return BooleanOp(ClipType::Xor, fillrule, subjects, clips, decimal_prec); + } + + inline Paths64 InflatePaths(const Paths64& paths, double delta, + JoinType jt, EndType et, double miter_limit = 2.0, + double arc_tolerance = 0.0) + { + if (!delta) return paths; + ClipperOffset clip_offset(miter_limit, arc_tolerance); + clip_offset.AddPaths(paths, jt, et); + Paths64 solution; + clip_offset.Execute(delta, solution); + return solution; + } + + inline PathsD InflatePaths(const PathsD& paths, double delta, + JoinType jt, EndType et, double miter_limit = 2.0, + int precision = 2, double arc_tolerance = 0.0) + { + int error_code = 0; + CheckPrecision(precision, error_code); + if (!delta) return paths; + if (error_code) return PathsD(); + const double scale = std::pow(10, precision); + ClipperOffset clip_offset(miter_limit, arc_tolerance); + clip_offset.AddPaths(ScalePaths(paths, scale, error_code), jt, et); + if (error_code) return PathsD(); + Paths64 solution; + clip_offset.Execute(delta * scale, solution); + return ScalePaths(solution, 1 / scale, error_code); + } + + inline Path64 TranslatePath(const Path64& path, int64_t dx, int64_t dy) + { + Path64 result; + result.reserve(path.size()); + std::transform(path.begin(), path.end(), back_inserter(result), + [dx, dy](const auto& pt) { return Point64(pt.x + dx, pt.y +dy); }); + return result; + } + + inline PathD TranslatePath(const PathD& path, double dx, double dy) + { + PathD result; + result.reserve(path.size()); + std::transform(path.begin(), path.end(), back_inserter(result), + [dx, dy](const auto& pt) { return PointD(pt.x + dx, pt.y + dy); }); + return result; + } + + inline Paths64 TranslatePaths(const Paths64& paths, int64_t dx, int64_t dy) + { + Paths64 result; + result.reserve(paths.size()); + std::transform(paths.begin(), paths.end(), back_inserter(result), + [dx, dy](const auto& path) { return TranslatePath(path, dx, dy); }); + return result; + } + + inline PathsD TranslatePaths(const PathsD& paths, double dx, double dy) + { + PathsD result; + result.reserve(paths.size()); + std::transform(paths.begin(), paths.end(), back_inserter(result), + [dx, dy](const auto& path) { return TranslatePath(path, dx, dy); }); + return result; + } + + inline Paths64 ExecuteRectClip(const Rect64& rect, + const Paths64& paths, bool convex_only = false) + { + if (rect.IsEmpty() || paths.empty()) return Paths64(); + RectClip rc(rect); + return rc.Execute(paths, convex_only); + } + + inline Paths64 ExecuteRectClip(const Rect64& rect, + const Path64& path, bool convex_only = false) + { + if (rect.IsEmpty() || path.empty()) return Paths64(); + RectClip rc(rect); + return rc.Execute(Paths64{ path }, convex_only); + } + + inline PathsD ExecuteRectClip(const RectD& rect, + const PathsD& paths, bool convex_only = false, int precision = 2) + { + if (rect.IsEmpty() || paths.empty()) return PathsD(); + int error_code = 0; + CheckPrecision(precision, error_code); + if (error_code) return PathsD(); + const double scale = std::pow(10, precision); + Rect64 r = ScaleRect(rect, scale); + RectClip rc(r); + Paths64 pp = ScalePaths(paths, scale, error_code); + if (error_code) return PathsD(); // ie: error_code result is lost + return ScalePaths( + rc.Execute(pp, convex_only), 1 / scale, error_code); + } + + inline PathsD ExecuteRectClip(const RectD& rect, + const PathD& path, bool convex_only = false, int precision = 2) + { + return ExecuteRectClip(rect, PathsD{ path }, convex_only, precision); + } + + inline Paths64 ExecuteRectClipLines(const Rect64& rect, const Paths64& lines) + { + if (rect.IsEmpty() || lines.empty()) return Paths64(); + RectClipLines rcl(rect); + return rcl.Execute(lines); + } + + inline Paths64 ExecuteRectClipLines(const Rect64& rect, const Path64& line) + { + return ExecuteRectClipLines(rect, Paths64{ line }); + } + + inline PathsD ExecuteRectClipLines(const RectD& rect, const PathD& line, int precision = 2) + { + return ExecuteRectClip(rect, PathsD{ line }, precision); + } + + inline PathsD ExecuteRectClipLines(const RectD& rect, const PathsD& lines, int precision = 2) + { + if (rect.IsEmpty() || lines.empty()) return PathsD(); + int error_code = 0; + CheckPrecision(precision, error_code); + if (error_code) return PathsD(); + const double scale = std::pow(10, precision); + Rect64 r = ScaleRect(rect, scale); + RectClipLines rcl(r); + Paths64 p = ScalePaths(lines, scale, error_code); + if (error_code) return PathsD(); + p = rcl.Execute(p); + return ScalePaths(p, 1 / scale, error_code); + } + + namespace details + { + + inline void PolyPathToPaths64(const PolyPath64& polypath, Paths64& paths) + { + paths.push_back(polypath.Polygon()); + for (const auto& child : polypath) + PolyPathToPaths64(*child, paths); + } + + inline void PolyPathToPathsD(const PolyPathD& polypath, PathsD& paths) + { + paths.push_back(polypath.Polygon()); + for (const auto& child : polypath) + PolyPathToPathsD(*child, paths); + } + + inline bool PolyPath64ContainsChildren(const PolyPath64& pp) + { + for (const auto& child : pp) + { + // return false if this child isn't fully contained by its parent + + // the following algorithm is a bit too crude, and doesn't account + // for rounding errors. A better algorithm is to return false when + // consecutive vertices are found outside the parent's polygon. + + //const Path64& path = pp.Polygon(); + //if (std::any_of(child->Polygon().cbegin(), child->Polygon().cend(), + // [path](const auto& pt) {return (PointInPolygon(pt, path) == + // PointInPolygonResult::IsOutside); })) return false; + + int outsideCnt = 0; + for (const Point64& pt : child->Polygon()) + { + PointInPolygonResult result = PointInPolygon(pt, pp.Polygon()); + if (result == PointInPolygonResult::IsInside) --outsideCnt; + else if (result == PointInPolygonResult::IsOutside) ++outsideCnt; + if (outsideCnt > 1) return false; + else if (outsideCnt < -1) break; + } + + // now check any nested children too + if (child->Count() > 0 && !PolyPath64ContainsChildren(*child)) + return false; + } + return true; + } + + static void OutlinePolyPath(std::ostream& os, + bool isHole, size_t count, const std::string& preamble) + { + std::string plural = (count == 1) ? "." : "s."; + if (isHole) + { + if (count) + os << preamble << "+- Hole with " << count << + " nested polygon" << plural << std::endl; + else + os << preamble << "+- Hole" << std::endl; + } + else + { + if (count) + os << preamble << "+- Polygon with " << count << + " hole" << plural << std::endl; + else + os << preamble << "+- Polygon" << std::endl; + } + } + + static void OutlinePolyPath64(std::ostream& os, const PolyPath64& pp, + std::string preamble, bool last_child) + { + OutlinePolyPath(os, pp.IsHole(), pp.Count(), preamble); + preamble += (!last_child) ? "| " : " "; + if (pp.Count()) + { + PolyPath64List::const_iterator it = pp.begin(); + for (; it < pp.end() - 1; ++it) + OutlinePolyPath64(os, **it, preamble, false); + OutlinePolyPath64(os, **it, preamble, true); + } + } + + static void OutlinePolyPathD(std::ostream& os, const PolyPathD& pp, + std::string preamble, bool last_child) + { + OutlinePolyPath(os, pp.IsHole(), pp.Count(), preamble); + preamble += (!last_child) ? "| " : " "; + if (pp.Count()) + { + PolyPathDList::const_iterator it = pp.begin(); + for (; it < pp.end() - 1; ++it) + OutlinePolyPathD(os, **it, preamble, false); + OutlinePolyPathD(os, **it, preamble, true); + } + } + + } // end details namespace + + inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp) + { + PolyPath64List::const_iterator it = pp.begin(); + for (; it < pp.end() - 1; ++it) + details::OutlinePolyPath64(os, **it, " ", false); + details::OutlinePolyPath64(os, **it, " ", true); + os << std::endl << std::endl; + if (!pp.Level()) os << std::endl; + return os; + } + + inline std::ostream& operator<< (std::ostream& os, const PolyTreeD& pp) + { + PolyPathDList::const_iterator it = pp.begin(); + for (; it < pp.end() - 1; ++it) + details::OutlinePolyPathD(os, **it, " ", false); + details::OutlinePolyPathD(os, **it, " ", true); + os << std::endl << std::endl; + if (!pp.Level()) os << std::endl; + return os; + } + + inline Paths64 PolyTreeToPaths64(const PolyTree64& polytree) + { + Paths64 result; + for (const auto& child : polytree) + details::PolyPathToPaths64(*child, result); + return result; + } + + inline PathsD PolyTreeToPathsD(const PolyTreeD& polytree) + { + PathsD result; + for (const auto& child : polytree) + details::PolyPathToPathsD(*child, result); + return result; + } + + inline bool CheckPolytreeFullyContainsChildren(const PolyTree64& polytree) + { + for (const auto& child : polytree) + if (child->Count() > 0 && + !details::PolyPath64ContainsChildren(*child)) + return false; + return true; + } + + namespace details { + + template + inline constexpr void MakePathGeneric(const T list, size_t size, + std::vector& result) + { + for (size_t i = 0; i < size; ++i) +#ifdef USINGZ + result[i / 2] = U{list[i], list[++i], 0}; +#else + result[i / 2] = U{list[i], list[++i]}; +#endif + } + + } // end details namespace + + template::value && + !std::is_same::value, bool + >::type = true> + inline Path64 MakePath(const std::vector& list) + { + const auto size = list.size() - list.size() % 2; + if (list.size() != size) + DoError(non_pair_error_i); // non-fatal without exception handling + Path64 result(size / 2); // else ignores unpaired value + details::MakePathGeneric(list, size, result); + return result; + } + + template::value && + !std::is_same::value, bool + >::type = true> + inline Path64 MakePath(const T(&list)[N]) + { + // Make the compiler error on unpaired value (i.e. no runtime effects). + static_assert(N % 2 == 0, "MakePath requires an even number of arguments"); + Path64 result(N / 2); + details::MakePathGeneric(list, N, result); + return result; + } + + template::value && + !std::is_same::value, bool + >::type = true> + inline PathD MakePathD(const std::vector& list) + { + const auto size = list.size() - list.size() % 2; + if (list.size() != size) + DoError(non_pair_error_i); // non-fatal without exception handling + PathD result(size / 2); // else ignores unpaired value + details::MakePathGeneric(list, size, result); + return result; + } + + template::value && + !std::is_same::value, bool + >::type = true> + inline PathD MakePathD(const T(&list)[N]) + { + // Make the compiler error on unpaired value (i.e. no runtime effects). + static_assert(N % 2 == 0, "MakePath requires an even number of arguments"); + PathD result(N / 2); + details::MakePathGeneric(list, N, result); + return result; + } + + inline Path64 TrimCollinear(const Path64& p, bool is_open_path = false) + { + size_t len = p.size(); + if (len < 3) + { + if (!is_open_path || len < 2 || p[0] == p[1]) return Path64(); + else return p; + } + + Path64 dst; + dst.reserve(len); + Path64::const_iterator srcIt = p.cbegin(), prevIt, stop = p.cend() - 1; + + if (!is_open_path) + { + while (srcIt != stop && !CrossProduct(*stop, *srcIt, *(srcIt + 1))) + ++srcIt; + while (srcIt != stop && !CrossProduct(*(stop - 1), *stop, *srcIt)) + --stop; + if (srcIt == stop) return Path64(); + } + + prevIt = srcIt++; + dst.push_back(*prevIt); + for (; srcIt != stop; ++srcIt) + { + if (CrossProduct(*prevIt, *srcIt, *(srcIt + 1))) + { + prevIt = srcIt; + dst.push_back(*prevIt); + } + } + + if (is_open_path) + dst.push_back(*srcIt); + else if (CrossProduct(*prevIt, *stop, dst[0])) + dst.push_back(*stop); + else + { + while (dst.size() > 2 && + !CrossProduct(dst[dst.size() - 1], dst[dst.size() - 2], dst[0])) + dst.pop_back(); + if (dst.size() < 3) return Path64(); + } + return dst; + } + + inline PathD TrimCollinear(const PathD& path, int precision, bool is_open_path = false) + { + int error_code = 0; + CheckPrecision(precision, error_code); + if (error_code) return PathD(); + const double scale = std::pow(10, precision); + Path64 p = ScalePath(path, scale, error_code); + if (error_code) return PathD(); + p = TrimCollinear(p, is_open_path); + return ScalePath(p, 1/scale, error_code); + } + + template + inline double Distance(const Point pt1, const Point pt2) + { + return std::sqrt(DistanceSqr(pt1, pt2)); + } + + template + inline double Length(const Path& path, bool is_closed_path = false) + { + double result = 0.0; + if (path.size() < 2) return result; + auto it = path.cbegin(), stop = path.end() - 1; + for (; it != stop; ++it) + result += Distance(*it, *(it + 1)); + if (is_closed_path) + result += Distance(*stop, *path.cbegin()); + return result; + } + + + template + inline bool NearCollinear(const Point& pt1, const Point& pt2, const Point& pt3, double sin_sqrd_min_angle_rads) + { + double cp = std::abs(CrossProduct(pt1, pt2, pt3)); + return (cp * cp) / (DistanceSqr(pt1, pt2) * DistanceSqr(pt2, pt3)) < sin_sqrd_min_angle_rads; + } + + template + inline Path Ellipse(const Rect& rect, int steps = 0) + { + return Ellipse(rect.MidPoint(), + static_cast(rect.Width()) *0.5, + static_cast(rect.Height()) * 0.5, steps); + } + + template + inline Path Ellipse(const Point& center, + double radiusX, double radiusY = 0, int steps = 0) + { + if (radiusX <= 0) return Path(); + if (radiusY <= 0) radiusY = radiusX; + if (steps <= 2) + steps = static_cast(PI * sqrt((radiusX + radiusY) / 2)); + + double si = std::sin(2 * PI / steps); + double co = std::cos(2 * PI / steps); + double dx = co, dy = si; + Path result; + result.reserve(steps); + result.push_back(Point(center.x + radiusX, static_cast(center.y))); + for (int i = 1; i < steps; ++i) + { + result.push_back(Point(center.x + radiusX * dx, center.y + radiusY * dy)); + double x = dx * co - dy * si; + dy = dy * co + dx * si; + dx = x; + } + return result; + } + + template + inline double PerpendicDistFromLineSqrd(const Point& pt, + const Point& line1, const Point& line2) + { + double a = static_cast(pt.x - line1.x); + double b = static_cast(pt.y - line1.y); + double c = static_cast(line2.x - line1.x); + double d = static_cast(line2.y - line1.y); + if (c == 0 && d == 0) return 0; + return Sqr(a * d - c * b) / (c * c + d * d); + } + + inline size_t GetNext(size_t current, size_t high, + const std::vector& flags) + { + ++current; + while (current <= high && flags[current]) ++current; + if (current <= high) return current; + current = 0; + while (flags[current]) ++current; + return current; + } + + inline size_t GetPrior(size_t current, size_t high, + const std::vector& flags) + { + if (current == 0) current = high; + else --current; + while (current > 0 && flags[current]) --current; + if (!flags[current]) return current; + current = high; + while (flags[current]) --current; + return current; + } + + template + inline Path SimplifyPath(const Path path, + double epsilon, bool isOpenPath = false) + { + const size_t len = path.size(), high = len -1; + const double epsSqr = Sqr(epsilon); + if (len < 4) return Path(path); + + std::vector flags(len); + std::vector distSqr(len); + size_t prior = high, curr = 0, start, next, prior2, next2; + if (isOpenPath) + { + distSqr[0] = MAX_DBL; + distSqr[high] = MAX_DBL; + } + else + { + distSqr[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]); + distSqr[high] = PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]); + } + for (size_t i = 1; i < high; ++i) + distSqr[i] = PerpendicDistFromLineSqrd(path[i], path[i - 1], path[i + 1]); + + for (;;) + { + if (distSqr[curr] > epsSqr) + { + start = curr; + do + { + curr = GetNext(curr, high, flags); + } while (curr != start && distSqr[curr] > epsSqr); + if (curr == start) break; + } + + prior = GetPrior(curr, high, flags); + next = GetNext(curr, high, flags); + if (next == prior) break; + + if (distSqr[next] < distSqr[curr]) + { + flags[next] = true; + next = GetNext(next, high, flags); + next2 = GetNext(next, high, flags); + distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]); + if (next != high || !isOpenPath) + distSqr[next] = PerpendicDistFromLineSqrd(path[next], path[curr], path[next2]); + curr = next; + } + else + { + flags[curr] = true; + curr = next; + next = GetNext(next, high, flags); + prior2 = GetPrior(prior, high, flags); + distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]); + if (prior != 0 || !isOpenPath) + distSqr[prior] = PerpendicDistFromLineSqrd(path[prior], path[prior2], path[curr]); + } + } + Path result; + result.reserve(len); + for (typename Path::size_type i = 0; i < len; ++i) + if (!flags[i]) result.push_back(path[i]); + return result; + } + + template + inline Paths SimplifyPaths(const Paths paths, + double epsilon, bool isOpenPath = false) + { + Paths result; + result.reserve(paths.size()); + for (const auto& path : paths) + result.push_back(SimplifyPath(path, epsilon, isOpenPath)); + return result; + } + + template + inline void RDP(const Path path, std::size_t begin, + std::size_t end, double epsSqrd, std::vector& flags) + { + typename Path::size_type idx = 0; + double max_d = 0; + while (end > begin && path[begin] == path[end]) flags[end--] = false; + for (typename Path::size_type i = begin + 1; i < end; ++i) + { + // PerpendicDistFromLineSqrd - avoids expensive Sqrt() + double d = PerpendicDistFromLineSqrd(path[i], path[begin], path[end]); + if (d <= max_d) continue; + max_d = d; + idx = i; + } + if (max_d <= epsSqrd) return; + flags[idx] = true; + if (idx > begin + 1) RDP(path, begin, idx, epsSqrd, flags); + if (idx < end - 1) RDP(path, idx, end, epsSqrd, flags); + } + + template + inline Path RamerDouglasPeucker(const Path& path, double epsilon) + { + const typename Path::size_type len = path.size(); + if (len < 5) return Path(path); + std::vector flags(len); + flags[0] = true; + flags[len - 1] = true; + RDP(path, 0, len - 1, Sqr(epsilon), flags); + Path result; + result.reserve(len); + for (typename Path::size_type i = 0; i < len; ++i) + if (flags[i]) + result.push_back(path[i]); + return result; + } + + template + inline Paths RamerDouglasPeucker(const Paths& paths, double epsilon) + { + Paths result; + result.reserve(paths.size()); + std::transform(paths.begin(), paths.end(), back_inserter(result), + [epsilon](const auto& path) + { return RamerDouglasPeucker(path, epsilon); }); + return result; + } + +} // end Clipper2Lib namespace + +#endif // CLIPPER_H diff --git a/thirdparty/clipper2/include/clipper2/clipper.minkowski.h b/thirdparty/clipper2/include/clipper2/clipper.minkowski.h new file mode 100644 index 00000000000..71c221bb50a --- /dev/null +++ b/thirdparty/clipper2/include/clipper2/clipper.minkowski.h @@ -0,0 +1,120 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 28 January 2023 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2023 * +* Purpose : Minkowski Sum and Difference * +* License : http://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +#ifndef CLIPPER_MINKOWSKI_H +#define CLIPPER_MINKOWSKI_H + +#include +#include +#include +#include "clipper.core.h" + +namespace Clipper2Lib +{ + + namespace detail + { + inline Paths64 Minkowski(const Path64& pattern, const Path64& path, bool isSum, bool isClosed) + { + size_t delta = isClosed ? 0 : 1; + size_t patLen = pattern.size(), pathLen = path.size(); + if (patLen == 0 || pathLen == 0) return Paths64(); + Paths64 tmp; + tmp.reserve(pathLen); + + if (isSum) + { + for (const Point64& p : path) + { + Path64 path2(pattern.size()); + std::transform(pattern.cbegin(), pattern.cend(), + path2.begin(), [p](const Point64& pt2) {return p + pt2; }); + tmp.push_back(path2); + } + } + else + { + for (const Point64& p : path) + { + Path64 path2(pattern.size()); + std::transform(pattern.cbegin(), pattern.cend(), + path2.begin(), [p](const Point64& pt2) {return p - pt2; }); + tmp.push_back(path2); + } + } + + Paths64 result; + result.reserve((pathLen - delta) * patLen); + size_t g = isClosed ? pathLen - 1 : 0; + for (size_t h = patLen - 1, i = delta; i < pathLen; ++i) + { + for (size_t j = 0; j < patLen; j++) + { + Path64 quad; + quad.reserve(4); + { + quad.push_back(tmp[g][h]); + quad.push_back(tmp[i][h]); + quad.push_back(tmp[i][j]); + quad.push_back(tmp[g][j]); + }; + if (!IsPositive(quad)) + std::reverse(quad.begin(), quad.end()); + result.push_back(quad); + h = j; + } + g = i; + } + return result; + } + + inline Paths64 Union(const Paths64& subjects, FillRule fillrule) + { + Paths64 result; + Clipper64 clipper; + clipper.AddSubject(subjects); + clipper.Execute(ClipType::Union, fillrule, result); + return result; + } + + } // namespace internal + + inline Paths64 MinkowskiSum(const Path64& pattern, const Path64& path, bool isClosed) + { + return detail::Union(detail::Minkowski(pattern, path, true, isClosed), FillRule::NonZero); + } + + inline PathsD MinkowskiSum(const PathD& pattern, const PathD& path, bool isClosed, int decimalPlaces = 2) + { + int error_code = 0; + double scale = pow(10, decimalPlaces); + Path64 pat64 = ScalePath(pattern, scale, error_code); + Path64 path64 = ScalePath(path, scale, error_code); + Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, true, isClosed), FillRule::NonZero); + return ScalePaths(tmp, 1 / scale, error_code); + } + + inline Paths64 MinkowskiDiff(const Path64& pattern, const Path64& path, bool isClosed) + { + return detail::Union(detail::Minkowski(pattern, path, false, isClosed), FillRule::NonZero); + } + + inline PathsD MinkowskiDiff(const PathD& pattern, const PathD& path, bool isClosed, int decimalPlaces = 2) + { + int error_code = 0; + double scale = pow(10, decimalPlaces); + Path64 pat64 = ScalePath(pattern, scale, error_code); + Path64 path64 = ScalePath(path, scale, error_code); + Paths64 tmp = detail::Union(detail::Minkowski(pat64, path64, false, isClosed), FillRule::NonZero); + return ScalePaths(tmp, 1 / scale, error_code); + } + +} // Clipper2Lib namespace + +#endif // CLIPPER_MINKOWSKI_H diff --git a/thirdparty/clipper2/include/clipper2/clipper.offset.h b/thirdparty/clipper2/include/clipper2/clipper.offset.h new file mode 100644 index 00000000000..f5d47e07ee2 --- /dev/null +++ b/thirdparty/clipper2/include/clipper2/clipper.offset.h @@ -0,0 +1,114 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 22 March 2023 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2023 * +* Purpose : Path Offset (Inflate/Shrink) * +* License : http://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +#ifndef CLIPPER_OFFSET_H_ +#define CLIPPER_OFFSET_H_ + +#include "clipper.core.h" +#include "clipper.engine.h" + +namespace Clipper2Lib { + +enum class JoinType { Square, Round, Miter }; + +enum class EndType {Polygon, Joined, Butt, Square, Round}; +//Butt : offsets both sides of a path, with square blunt ends +//Square : offsets both sides of a path, with square extended ends +//Round : offsets both sides of a path, with round extended ends +//Joined : offsets both sides of a path, with joined ends +//Polygon: offsets only one side of a closed path + + +class ClipperOffset { +private: + + class Group { + public: + Paths64 paths_in; + Paths64 paths_out; + Path64 path; + bool is_reversed = false; + JoinType join_type; + EndType end_type; + Group(const Paths64& _paths, JoinType _join_type, EndType _end_type) : + paths_in(_paths), join_type(_join_type), end_type(_end_type) {} + }; + + int error_code_ = 0; + double delta_ = 0.0; + double group_delta_ = 0.0; + double abs_group_delta_ = 0.0; + double temp_lim_ = 0.0; + double steps_per_rad_ = 0.0; + double step_sin_ = 0.0; + double step_cos_ = 0.0; + PathD norms; + Paths64 solution; + std::vector groups_; + JoinType join_type_ = JoinType::Square; + EndType end_type_ = EndType::Polygon; + + double miter_limit_ = 0.0; + double arc_tolerance_ = 0.0; + bool preserve_collinear_ = false; + bool reverse_solution_ = false; + +#ifdef USINGZ + ZCallback64 zCallback64_ = nullptr; +#endif + + void DoSquare(Group& group, const Path64& path, size_t j, size_t k); + void DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a); + void DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle); + void BuildNormals(const Path64& path); + void OffsetPolygon(Group& group, Path64& path); + void OffsetOpenJoined(Group& group, Path64& path); + void OffsetOpenPath(Group& group, Path64& path); + void OffsetPoint(Group& group, Path64& path, size_t j, size_t& k); + void DoGroupOffset(Group &group); + void ExecuteInternal(double delta); +public: + explicit ClipperOffset(double miter_limit = 2.0, + double arc_tolerance = 0.0, + bool preserve_collinear = false, + bool reverse_solution = false) : + miter_limit_(miter_limit), arc_tolerance_(arc_tolerance), + preserve_collinear_(preserve_collinear), + reverse_solution_(reverse_solution) { }; + + ~ClipperOffset() { Clear(); }; + + int ErrorCode() { return error_code_; }; + void AddPath(const Path64& path, JoinType jt_, EndType et_); + void AddPaths(const Paths64& paths, JoinType jt_, EndType et_); + void Clear() { groups_.clear(); norms.clear(); }; + + void Execute(double delta, Paths64& paths); + void Execute(double delta, PolyTree64& polytree); + + double MiterLimit() const { return miter_limit_; } + void MiterLimit(double miter_limit) { miter_limit_ = miter_limit; } + + //ArcTolerance: needed for rounded offsets (See offset_triginometry2.svg) + double ArcTolerance() const { return arc_tolerance_; } + void ArcTolerance(double arc_tolerance) { arc_tolerance_ = arc_tolerance; } + + bool PreserveCollinear() const { return preserve_collinear_; } + void PreserveCollinear(bool preserve_collinear){preserve_collinear_ = preserve_collinear;} + + bool ReverseSolution() const { return reverse_solution_; } + void ReverseSolution(bool reverse_solution) {reverse_solution_ = reverse_solution;} + +#ifdef USINGZ + void SetZCallback(ZCallback64 cb) { zCallback64_ = cb; } +#endif +}; + +} +#endif /* CLIPPER_OFFSET_H_ */ diff --git a/thirdparty/clipper2/include/clipper2/clipper.rectclip.h b/thirdparty/clipper2/include/clipper2/clipper.rectclip.h new file mode 100644 index 00000000000..2a9bb35d08e --- /dev/null +++ b/thirdparty/clipper2/include/clipper2/clipper.rectclip.h @@ -0,0 +1,82 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 9 February 2023 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2023 * +* Purpose : FAST rectangular clipping * +* License : http://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +#ifndef CLIPPER_RECTCLIP_H +#define CLIPPER_RECTCLIP_H + +#include +#include +#include +#include "clipper.h" +#include "clipper.core.h" + +namespace Clipper2Lib +{ + + enum class Location { Left, Top, Right, Bottom, Inside }; + + class OutPt2; + typedef std::vector OutPt2List; + + class OutPt2 { + public: + Point64 pt; + size_t owner_idx; + OutPt2List* edge; + OutPt2* next; + OutPt2* prev; + }; + + //------------------------------------------------------------------------------ + // RectClip + //------------------------------------------------------------------------------ + + class RectClip { + private: + void ExecuteInternal(const Path64& path); + Path64 GetPath(OutPt2*& op); + protected: + const Rect64 rect_; + const Path64 rect_as_path_; + const Point64 rect_mp_; + Rect64 path_bounds_; + std::deque op_container_; + OutPt2List results_; // each path can be broken into multiples + OutPt2List edges_[8]; // clockwise and counter-clockwise + std::vector start_locs_; + void CheckEdges(); + void TidyEdges(int idx, OutPt2List& cw, OutPt2List& ccw); + void GetNextLocation(const Path64& path, + Location& loc, int& i, int highI); + OutPt2* Add(Point64 pt, bool start_new = false); + void AddCorner(Location prev, Location curr); + void AddCorner(Location& loc, bool isClockwise); + public: + explicit RectClip(const Rect64& rect) : + rect_(rect), + rect_as_path_(rect.AsPath()), + rect_mp_(rect.MidPoint()) {} + Paths64 Execute(const Paths64& paths, bool convex_only = false); + }; + + //------------------------------------------------------------------------------ + // RectClipLines + //------------------------------------------------------------------------------ + + class RectClipLines : public RectClip { + private: + void ExecuteInternal(const Path64& path); + Path64 GetPath(OutPt2*& op); + public: + explicit RectClipLines(const Rect64& rect) : RectClip(rect) {}; + Paths64 Execute(const Paths64& paths); + }; + +} // Clipper2Lib namespace +#endif // CLIPPER_RECTCLIP_H diff --git a/thirdparty/clipper2/src/clipper.engine.cpp b/thirdparty/clipper2/src/clipper.engine.cpp new file mode 100644 index 00000000000..2d61b8aafae --- /dev/null +++ b/thirdparty/clipper2/src/clipper.engine.cpp @@ -0,0 +1,2979 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 19 March 2023 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2023 * +* Purpose : This is the main polygon clipping module * +* License : http://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include "clipper2/clipper.engine.h" + +// https://github.com/AngusJohnson/Clipper2/discussions/334 +// #discussioncomment-4248602 +#if defined(_MSC_VER) && ( defined(_M_AMD64) || defined(_M_X64) ) +#include +#include +#define fmin(a,b) _mm_cvtsd_f64(_mm_min_sd(_mm_set_sd(a),_mm_set_sd(b))) +#define fmax(a,b) _mm_cvtsd_f64(_mm_max_sd(_mm_set_sd(a),_mm_set_sd(b))) +#define nearbyint(a) _mm_cvtsd_si64(_mm_set_sd(a)) /* Note: expression type is (int64_t) */ +#endif + +namespace Clipper2Lib { + + static const Rect64 invalid_rect = Rect64(false); + + // Every closed path (or polygon) is made up of a series of vertices forming + // edges that alternate between going up (relative to the Y-axis) and going + // down. Edges consecutively going up or consecutively going down are called + // 'bounds' (ie sides if they're simple polygons). 'Local Minima' refer to + // vertices where descending bounds become ascending ones. + + struct Scanline { + int64_t y = 0; + Scanline* next = nullptr; + + explicit Scanline(int64_t y_) : y(y_) {} + }; + + struct HorzSegSorter { + inline bool operator()(const HorzSegment& hs1, const HorzSegment& hs2) + { + if (!hs1.right_op || !hs2.right_op) return (hs1.right_op); + return hs2.left_op->pt.x > hs1.left_op->pt.x; + } + }; + + struct LocMinSorter { + inline bool operator()(const LocalMinima_ptr& locMin1, + const LocalMinima_ptr& locMin2) + { + if (locMin2->vertex->pt.y != locMin1->vertex->pt.y) + return locMin2->vertex->pt.y < locMin1->vertex->pt.y; + else + return locMin2->vertex->pt.x > locMin1->vertex->pt.x; + } + }; + + inline bool IsOdd(int val) + { + return (val & 1) ? true : false; + } + + + inline bool IsHotEdge(const Active& e) + { + return (e.outrec); + } + + + inline bool IsOpen(const Active& e) + { + return (e.local_min->is_open); + } + + + inline bool IsOpenEnd(const Vertex& v) + { + return (v.flags & (VertexFlags::OpenStart | VertexFlags::OpenEnd)) != + VertexFlags::None; + } + + + inline bool IsOpenEnd(const Active& ae) + { + return IsOpenEnd(*ae.vertex_top); + } + + + inline Active* GetPrevHotEdge(const Active& e) + { + Active* prev = e.prev_in_ael; + while (prev && (IsOpen(*prev) || !IsHotEdge(*prev))) + prev = prev->prev_in_ael; + return prev; + } + + inline bool IsFront(const Active& e) + { + return (&e == e.outrec->front_edge); + } + + inline bool IsInvalidPath(OutPt* op) + { + return (!op || op->next == op); + } + + /******************************************************************************* + * Dx: 0(90deg) * + * | * + * +inf (180deg) <--- o ---> -inf (0deg) * + *******************************************************************************/ + + inline double GetDx(const Point64& pt1, const Point64& pt2) + { + double dy = double(pt2.y - pt1.y); + if (dy != 0) + return double(pt2.x - pt1.x) / dy; + else if (pt2.x > pt1.x) + return -std::numeric_limits::max(); + else + return std::numeric_limits::max(); + } + + inline int64_t TopX(const Active& ae, const int64_t currentY) + { + if ((currentY == ae.top.y) || (ae.top.x == ae.bot.x)) return ae.top.x; + else if (currentY == ae.bot.y) return ae.bot.x; + else return ae.bot.x + static_cast(nearbyint(ae.dx * (currentY - ae.bot.y))); + // nb: std::nearbyint (or std::round) substantially *improves* performance here + // as it greatly improves the likelihood of edge adjacency in ProcessIntersectList(). + } + + + inline bool IsHorizontal(const Active& e) + { + return (e.top.y == e.bot.y); + } + + + inline bool IsHeadingRightHorz(const Active& e) + { + return e.dx == -std::numeric_limits::max(); + } + + + inline bool IsHeadingLeftHorz(const Active& e) + { + return e.dx == std::numeric_limits::max(); + } + + + inline void SwapActives(Active*& e1, Active*& e2) + { + Active* e = e1; + e1 = e2; + e2 = e; + } + + inline PathType GetPolyType(const Active& e) + { + return e.local_min->polytype; + } + + inline bool IsSamePolyType(const Active& e1, const Active& e2) + { + return e1.local_min->polytype == e2.local_min->polytype; + } + + inline void SetDx(Active& e) + { + e.dx = GetDx(e.bot, e.top); + } + + inline Vertex* NextVertex(const Active& e) + { + if (e.wind_dx > 0) + return e.vertex_top->next; + else + return e.vertex_top->prev; + } + + //PrevPrevVertex: useful to get the (inverted Y-axis) top of the + //alternate edge (ie left or right bound) during edge insertion. + inline Vertex* PrevPrevVertex(const Active& ae) + { + if (ae.wind_dx > 0) + return ae.vertex_top->prev->prev; + else + return ae.vertex_top->next->next; + } + + + inline Active* ExtractFromSEL(Active* ae) + { + Active* res = ae->next_in_sel; + if (res) + res->prev_in_sel = ae->prev_in_sel; + ae->prev_in_sel->next_in_sel = res; + return res; + } + + + inline void Insert1Before2InSEL(Active* ae1, Active* ae2) + { + ae1->prev_in_sel = ae2->prev_in_sel; + if (ae1->prev_in_sel) + ae1->prev_in_sel->next_in_sel = ae1; + ae1->next_in_sel = ae2; + ae2->prev_in_sel = ae1; + } + + inline bool IsMaxima(const Vertex& v) + { + return ((v.flags & VertexFlags::LocalMax) != VertexFlags::None); + } + + + inline bool IsMaxima(const Active& e) + { + return IsMaxima(*e.vertex_top); + } + + inline Vertex* GetCurrYMaximaVertex_Open(const Active& e) + { + Vertex* result = e.vertex_top; + if (e.wind_dx > 0) + while ((result->next->pt.y == result->pt.y) && + ((result->flags & (VertexFlags::OpenEnd | + VertexFlags::LocalMax)) == VertexFlags::None)) + result = result->next; + else + while (result->prev->pt.y == result->pt.y && + ((result->flags & (VertexFlags::OpenEnd | + VertexFlags::LocalMax)) == VertexFlags::None)) + result = result->prev; + if (!IsMaxima(*result)) result = nullptr; // not a maxima + return result; + } + + inline Vertex* GetCurrYMaximaVertex(const Active& e) + { + Vertex* result = e.vertex_top; + if (e.wind_dx > 0) + while (result->next->pt.y == result->pt.y) result = result->next; + else + while (result->prev->pt.y == result->pt.y) result = result->prev; + if (!IsMaxima(*result)) result = nullptr; // not a maxima + return result; + } + + Active* GetMaximaPair(const Active& e) + { + Active* e2; + e2 = e.next_in_ael; + while (e2) + { + if (e2->vertex_top == e.vertex_top) return e2; // Found! + e2 = e2->next_in_ael; + } + return nullptr; + } + + inline int PointCount(OutPt* op) + { + OutPt* op2 = op; + int cnt = 0; + do + { + op2 = op2->next; + ++cnt; + } while (op2 != op); + return cnt; + } + + inline OutPt* DuplicateOp(OutPt* op, bool insert_after) + { + OutPt* result = new OutPt(op->pt, op->outrec); + if (insert_after) + { + result->next = op->next; + result->next->prev = result; + result->prev = op; + op->next = result; + } + else + { + result->prev = op->prev; + result->prev->next = result; + result->next = op; + op->prev = result; + } + return result; + } + + inline OutPt* DisposeOutPt(OutPt* op) + { + OutPt* result = op->next; + op->prev->next = op->next; + op->next->prev = op->prev; + delete op; + return result; + } + + + inline void DisposeOutPts(OutRec* outrec) + { + OutPt* op = outrec->pts; + op->prev->next = nullptr; + while (op) + { + OutPt* tmp = op; + op = op->next; + delete tmp; + }; + outrec->pts = nullptr; + } + + + bool IntersectListSort(const IntersectNode& a, const IntersectNode& b) + { + //note different inequality tests ... + return (a.pt.y == b.pt.y) ? (a.pt.x < b.pt.x) : (a.pt.y > b.pt.y); + } + + + inline void SetSides(OutRec& outrec, Active& start_edge, Active& end_edge) + { + outrec.front_edge = &start_edge; + outrec.back_edge = &end_edge; + } + + + void SwapOutrecs(Active& e1, Active& e2) + { + OutRec* or1 = e1.outrec; + OutRec* or2 = e2.outrec; + if (or1 == or2) + { + Active* e = or1->front_edge; + or1->front_edge = or1->back_edge; + or1->back_edge = e; + return; + } + if (or1) + { + if (&e1 == or1->front_edge) + or1->front_edge = &e2; + else + or1->back_edge = &e2; + } + if (or2) + { + if (&e2 == or2->front_edge) + or2->front_edge = &e1; + else + or2->back_edge = &e1; + } + e1.outrec = or2; + e2.outrec = or1; + } + + + double Area(OutPt* op) + { + //https://en.wikipedia.org/wiki/Shoelace_formula + double result = 0.0; + OutPt* op2 = op; + do + { + result += static_cast(op2->prev->pt.y + op2->pt.y) * + static_cast(op2->prev->pt.x - op2->pt.x); + op2 = op2->next; + } while (op2 != op); + return result * 0.5; + } + + inline double AreaTriangle(const Point64& pt1, + const Point64& pt2, const Point64& pt3) + { + return (static_cast(pt3.y + pt1.y) * static_cast(pt3.x - pt1.x) + + static_cast(pt1.y + pt2.y) * static_cast(pt1.x - pt2.x) + + static_cast(pt2.y + pt3.y) * static_cast(pt2.x - pt3.x)); + } + + void ReverseOutPts(OutPt* op) + { + if (!op) return; + + OutPt* op1 = op; + OutPt* op2; + + do + { + op2 = op1->next; + op1->next = op1->prev; + op1->prev = op2; + op1 = op2; + } while (op1 != op); + } + + inline void SwapSides(OutRec& outrec) + { + Active* e2 = outrec.front_edge; + outrec.front_edge = outrec.back_edge; + outrec.back_edge = e2; + outrec.pts = outrec.pts->next; + } + + inline OutRec* GetRealOutRec(OutRec* outrec) + { + while (outrec && !outrec->pts) outrec = outrec->owner; + return outrec; + } + + + inline void UncoupleOutRec(Active ae) + { + OutRec* outrec = ae.outrec; + if (!outrec) return; + outrec->front_edge->outrec = nullptr; + outrec->back_edge->outrec = nullptr; + outrec->front_edge = nullptr; + outrec->back_edge = nullptr; + } + + + inline bool PtsReallyClose(const Point64& pt1, const Point64& pt2) + { + return (std::llabs(pt1.x - pt2.x) < 2) && (std::llabs(pt1.y - pt2.y) < 2); + } + + inline bool IsVerySmallTriangle(const OutPt& op) + { + return op.next->next == op.prev && + (PtsReallyClose(op.prev->pt, op.next->pt) || + PtsReallyClose(op.pt, op.next->pt) || + PtsReallyClose(op.pt, op.prev->pt)); + } + + inline bool IsValidClosedPath(const OutPt* op) + { + return op && (op->next != op) && (op->next != op->prev) && + !IsVerySmallTriangle(*op); + } + + inline bool OutrecIsAscending(const Active* hotEdge) + { + return (hotEdge == hotEdge->outrec->front_edge); + } + + inline void SwapFrontBackSides(OutRec& outrec) + { + Active* tmp = outrec.front_edge; + outrec.front_edge = outrec.back_edge; + outrec.back_edge = tmp; + outrec.pts = outrec.pts->next; + } + + inline bool EdgesAdjacentInAEL(const IntersectNode& inode) + { + return (inode.edge1->next_in_ael == inode.edge2) || (inode.edge1->prev_in_ael == inode.edge2); + } + + inline bool IsJoined(const Active& e) + { + return e.join_with != JoinWith::None; + } + + inline void SetOwner(OutRec* outrec, OutRec* new_owner) + { + //precondition1: new_owner is never null + while (new_owner->owner && !new_owner->owner->pts) + new_owner->owner = new_owner->owner->owner; + OutRec* tmp = new_owner; + while (tmp && tmp != outrec) tmp = tmp->owner; + if (tmp) new_owner->owner = outrec->owner; + outrec->owner = new_owner; + } + + //------------------------------------------------------------------------------ + // ClipperBase methods ... + //------------------------------------------------------------------------------ + + ClipperBase::~ClipperBase() + { + Clear(); + } + + void ClipperBase::DeleteEdges(Active*& e) + { + while (e) + { + Active* e2 = e; + e = e->next_in_ael; + delete e2; + } + } + + void ClipperBase::CleanUp() + { + DeleteEdges(actives_); + scanline_list_ = std::priority_queue(); + intersect_nodes_.clear(); + DisposeAllOutRecs(); + horz_seg_list_.clear(); + horz_join_list_.clear(); + } + + + void ClipperBase::Clear() + { + CleanUp(); + DisposeVerticesAndLocalMinima(); + current_locmin_iter_ = minima_list_.begin(); + minima_list_sorted_ = false; + has_open_paths_ = false; + } + + + void ClipperBase::Reset() + { + if (!minima_list_sorted_) + { + std::sort(minima_list_.begin(), minima_list_.end(), LocMinSorter()); + minima_list_sorted_ = true; + } + LocalMinimaList::const_reverse_iterator i; + for (i = minima_list_.rbegin(); i != minima_list_.rend(); ++i) + InsertScanline((*i)->vertex->pt.y); + + current_locmin_iter_ = minima_list_.begin(); + actives_ = nullptr; + sel_ = nullptr; + succeeded_ = true; + } + + +#ifdef USINGZ + void ClipperBase::SetZ(const Active& e1, const Active& e2, Point64& ip) + { + if (!zCallback_) return; + // prioritize subject over clip vertices by passing + // subject vertices before clip vertices in the callback + if (GetPolyType(e1) == PathType::Subject) + { + if (ip == e1.bot) ip.z = e1.bot.z; + else if (ip == e1.top) ip.z = e1.top.z; + else if (ip == e2.bot) ip.z = e2.bot.z; + else if (ip == e2.top) ip.z = e2.top.z; + else ip.z = DefaultZ; + zCallback_(e1.bot, e1.top, e2.bot, e2.top, ip); + } + else + { + if (ip == e2.bot) ip.z = e2.bot.z; + else if (ip == e2.top) ip.z = e2.top.z; + else if (ip == e1.bot) ip.z = e1.bot.z; + else if (ip == e1.top) ip.z = e1.top.z; + else ip.z = DefaultZ; + zCallback_(e2.bot, e2.top, e1.bot, e1.top, ip); + } + } +#endif + + void ClipperBase::AddPath(const Path64& path, PathType polytype, bool is_open) + { + Paths64 tmp; + tmp.push_back(path); + AddPaths(tmp, polytype, is_open); + } + + + void ClipperBase::AddPaths(const Paths64& paths, PathType polytype, bool is_open) + { + if (is_open) has_open_paths_ = true; + minima_list_sorted_ = false; + + const auto total_vertex_count = + std::accumulate(paths.begin(), paths.end(), 0, + [](const auto& a, const Path64& path) + {return a + static_cast(path.size());}); + if (total_vertex_count == 0) return; + + Vertex* vertices = new Vertex[total_vertex_count], * v = vertices; + for (const Path64& path : paths) + { + //for each path create a circular double linked list of vertices + Vertex* v0 = v, * curr_v = v, * prev_v = nullptr; + + if (path.empty()) + continue; + + v->prev = nullptr; + int cnt = 0; + for (const Point64& pt : path) + { + if (prev_v) + { + if (prev_v->pt == pt) continue; // ie skips duplicates + prev_v->next = curr_v; + } + curr_v->prev = prev_v; + curr_v->pt = pt; + curr_v->flags = VertexFlags::None; + prev_v = curr_v++; + cnt++; + } + if (!prev_v || !prev_v->prev) continue; + if (!is_open && prev_v->pt == v0->pt) + prev_v = prev_v->prev; + prev_v->next = v0; + v0->prev = prev_v; + v = curr_v; // ie get ready for next path + if (cnt < 2 || (cnt == 2 && !is_open)) continue; + + //now find and assign local minima + bool going_up, going_up0; + if (is_open) + { + curr_v = v0->next; + while (curr_v != v0 && curr_v->pt.y == v0->pt.y) + curr_v = curr_v->next; + going_up = curr_v->pt.y <= v0->pt.y; + if (going_up) + { + v0->flags = VertexFlags::OpenStart; + AddLocMin(*v0, polytype, true); + } + else + v0->flags = VertexFlags::OpenStart | VertexFlags::LocalMax; + } + else // closed path + { + prev_v = v0->prev; + while (prev_v != v0 && prev_v->pt.y == v0->pt.y) + prev_v = prev_v->prev; + if (prev_v == v0) + continue; // only open paths can be completely flat + going_up = prev_v->pt.y > v0->pt.y; + } + + going_up0 = going_up; + prev_v = v0; + curr_v = v0->next; + while (curr_v != v0) + { + if (curr_v->pt.y > prev_v->pt.y && going_up) + { + prev_v->flags = (prev_v->flags | VertexFlags::LocalMax); + going_up = false; + } + else if (curr_v->pt.y < prev_v->pt.y && !going_up) + { + going_up = true; + AddLocMin(*prev_v, polytype, is_open); + } + prev_v = curr_v; + curr_v = curr_v->next; + } + + if (is_open) + { + prev_v->flags = prev_v->flags | VertexFlags::OpenEnd; + if (going_up) + prev_v->flags = prev_v->flags | VertexFlags::LocalMax; + else + AddLocMin(*prev_v, polytype, is_open); + } + else if (going_up != going_up0) + { + if (going_up0) AddLocMin(*prev_v, polytype, false); + else prev_v->flags = prev_v->flags | VertexFlags::LocalMax; + } + } // end processing current path + + vertex_lists_.emplace_back(vertices); + } // end AddPaths + + + void ClipperBase::InsertScanline(int64_t y) + { + scanline_list_.push(y); + } + + + bool ClipperBase::PopScanline(int64_t& y) + { + if (scanline_list_.empty()) return false; + y = scanline_list_.top(); + scanline_list_.pop(); + while (!scanline_list_.empty() && y == scanline_list_.top()) + scanline_list_.pop(); // Pop duplicates. + return true; + } + + + bool ClipperBase::PopLocalMinima(int64_t y, LocalMinima*& local_minima) + { + if (current_locmin_iter_ == minima_list_.end() || (*current_locmin_iter_)->vertex->pt.y != y) return false; + local_minima = (current_locmin_iter_++)->get(); + return true; + } + + void ClipperBase::DisposeAllOutRecs() + { + for (auto outrec : outrec_list_) + { + if (outrec->pts) DisposeOutPts(outrec); + delete outrec; + } + outrec_list_.resize(0); + } + + void ClipperBase::DisposeVerticesAndLocalMinima() + { + minima_list_.clear(); + for (auto v : vertex_lists_) delete[] v; + vertex_lists_.clear(); + } + + + void ClipperBase::AddLocMin(Vertex& vert, PathType polytype, bool is_open) + { + //make sure the vertex is added only once ... + if ((VertexFlags::LocalMin & vert.flags) != VertexFlags::None) return; + + vert.flags = (vert.flags | VertexFlags::LocalMin); + minima_list_.push_back(std::make_unique (&vert, polytype, is_open)); + } + + bool ClipperBase::IsContributingClosed(const Active& e) const + { + switch (fillrule_) + { + case FillRule::EvenOdd: + break; + case FillRule::NonZero: + if (abs(e.wind_cnt) != 1) return false; + break; + case FillRule::Positive: + if (e.wind_cnt != 1) return false; + break; + case FillRule::Negative: + if (e.wind_cnt != -1) return false; + break; + } + + switch (cliptype_) + { + case ClipType::None: + return false; + case ClipType::Intersection: + switch (fillrule_) + { + case FillRule::Positive: + return (e.wind_cnt2 > 0); + case FillRule::Negative: + return (e.wind_cnt2 < 0); + default: + return (e.wind_cnt2 != 0); + } + break; + + case ClipType::Union: + switch (fillrule_) + { + case FillRule::Positive: + return (e.wind_cnt2 <= 0); + case FillRule::Negative: + return (e.wind_cnt2 >= 0); + default: + return (e.wind_cnt2 == 0); + } + break; + + case ClipType::Difference: + bool result; + switch (fillrule_) + { + case FillRule::Positive: + result = (e.wind_cnt2 <= 0); + break; + case FillRule::Negative: + result = (e.wind_cnt2 >= 0); + break; + default: + result = (e.wind_cnt2 == 0); + } + if (GetPolyType(e) == PathType::Subject) + return result; + else + return !result; + break; + + case ClipType::Xor: return true; break; + } + return false; // we should never get here + } + + + inline bool ClipperBase::IsContributingOpen(const Active& e) const + { + bool is_in_clip, is_in_subj; + switch (fillrule_) + { + case FillRule::Positive: + is_in_clip = e.wind_cnt2 > 0; + is_in_subj = e.wind_cnt > 0; + break; + case FillRule::Negative: + is_in_clip = e.wind_cnt2 < 0; + is_in_subj = e.wind_cnt < 0; + break; + default: + is_in_clip = e.wind_cnt2 != 0; + is_in_subj = e.wind_cnt != 0; + } + + switch (cliptype_) + { + case ClipType::Intersection: return is_in_clip; + case ClipType::Union: return (!is_in_subj && !is_in_clip); + default: return !is_in_clip; + } + } + + + void ClipperBase::SetWindCountForClosedPathEdge(Active& e) + { + //Wind counts refer to polygon regions not edges, so here an edge's WindCnt + //indicates the higher of the wind counts for the two regions touching the + //edge. (NB Adjacent regions can only ever have their wind counts differ by + //one. Also, open paths have no meaningful wind directions or counts.) + + Active* e2 = e.prev_in_ael; + //find the nearest closed path edge of the same PolyType in AEL (heading left) + PathType pt = GetPolyType(e); + while (e2 && (GetPolyType(*e2) != pt || IsOpen(*e2))) e2 = e2->prev_in_ael; + + if (!e2) + { + e.wind_cnt = e.wind_dx; + e2 = actives_; + } + else if (fillrule_ == FillRule::EvenOdd) + { + e.wind_cnt = e.wind_dx; + e.wind_cnt2 = e2->wind_cnt2; + e2 = e2->next_in_ael; + } + else + { + //NonZero, positive, or negative filling here ... + //if e's WindCnt is in the SAME direction as its WindDx, then polygon + //filling will be on the right of 'e'. + //NB neither e2.WindCnt nor e2.WindDx should ever be 0. + if (e2->wind_cnt * e2->wind_dx < 0) + { + //opposite directions so 'e' is outside 'e2' ... + if (abs(e2->wind_cnt) > 1) + { + //outside prev poly but still inside another. + if (e2->wind_dx * e.wind_dx < 0) + //reversing direction so use the same WC + e.wind_cnt = e2->wind_cnt; + else + //otherwise keep 'reducing' the WC by 1 (ie towards 0) ... + e.wind_cnt = e2->wind_cnt + e.wind_dx; + } + else + //now outside all polys of same polytype so set own WC ... + e.wind_cnt = (IsOpen(e) ? 1 : e.wind_dx); + } + else + { + //'e' must be inside 'e2' + if (e2->wind_dx * e.wind_dx < 0) + //reversing direction so use the same WC + e.wind_cnt = e2->wind_cnt; + else + //otherwise keep 'increasing' the WC by 1 (ie away from 0) ... + e.wind_cnt = e2->wind_cnt + e.wind_dx; + } + e.wind_cnt2 = e2->wind_cnt2; + e2 = e2->next_in_ael; // ie get ready to calc WindCnt2 + } + + //update wind_cnt2 ... + if (fillrule_ == FillRule::EvenOdd) + while (e2 != &e) + { + if (GetPolyType(*e2) != pt && !IsOpen(*e2)) + e.wind_cnt2 = (e.wind_cnt2 == 0 ? 1 : 0); + e2 = e2->next_in_ael; + } + else + while (e2 != &e) + { + if (GetPolyType(*e2) != pt && !IsOpen(*e2)) + e.wind_cnt2 += e2->wind_dx; + e2 = e2->next_in_ael; + } + } + + + void ClipperBase::SetWindCountForOpenPathEdge(Active& e) + { + Active* e2 = actives_; + if (fillrule_ == FillRule::EvenOdd) + { + int cnt1 = 0, cnt2 = 0; + while (e2 != &e) + { + if (GetPolyType(*e2) == PathType::Clip) + cnt2++; + else if (!IsOpen(*e2)) + cnt1++; + e2 = e2->next_in_ael; + } + e.wind_cnt = (IsOdd(cnt1) ? 1 : 0); + e.wind_cnt2 = (IsOdd(cnt2) ? 1 : 0); + } + else + { + while (e2 != &e) + { + if (GetPolyType(*e2) == PathType::Clip) + e.wind_cnt2 += e2->wind_dx; + else if (!IsOpen(*e2)) + e.wind_cnt += e2->wind_dx; + e2 = e2->next_in_ael; + } + } + } + + + bool IsValidAelOrder(const Active& resident, const Active& newcomer) + { + if (newcomer.curr_x != resident.curr_x) + return newcomer.curr_x > resident.curr_x; + + //get the turning direction a1.top, a2.bot, a2.top + double d = CrossProduct(resident.top, newcomer.bot, newcomer.top); + if (d != 0) return d < 0; + + //edges must be collinear to get here + //for starting open paths, place them according to + //the direction they're about to turn + if (!IsMaxima(resident) && (resident.top.y > newcomer.top.y)) + { + return CrossProduct(newcomer.bot, + resident.top, NextVertex(resident)->pt) <= 0; + } + else if (!IsMaxima(newcomer) && (newcomer.top.y > resident.top.y)) + { + return CrossProduct(newcomer.bot, + newcomer.top, NextVertex(newcomer)->pt) >= 0; + } + + int64_t y = newcomer.bot.y; + bool newcomerIsLeft = newcomer.is_left_bound; + + if (resident.bot.y != y || resident.local_min->vertex->pt.y != y) + return newcomer.is_left_bound; + //resident must also have just been inserted + else if (resident.is_left_bound != newcomerIsLeft) + return newcomerIsLeft; + else if (CrossProduct(PrevPrevVertex(resident)->pt, + resident.bot, resident.top) == 0) return true; + else + //compare turning direction of the alternate bound + return (CrossProduct(PrevPrevVertex(resident)->pt, + newcomer.bot, PrevPrevVertex(newcomer)->pt) > 0) == newcomerIsLeft; + } + + + void ClipperBase::InsertLeftEdge(Active& e) + { + Active* e2; + if (!actives_) + { + e.prev_in_ael = nullptr; + e.next_in_ael = nullptr; + actives_ = &e; + } + else if (!IsValidAelOrder(*actives_, e)) + { + e.prev_in_ael = nullptr; + e.next_in_ael = actives_; + actives_->prev_in_ael = &e; + actives_ = &e; + } + else + { + e2 = actives_; + while (e2->next_in_ael && IsValidAelOrder(*e2->next_in_ael, e)) + e2 = e2->next_in_ael; + if (e2->join_with == JoinWith::Right) + e2 = e2->next_in_ael; + if (!e2) return; // should never happen and stops compiler warning :) + e.next_in_ael = e2->next_in_ael; + if (e2->next_in_ael) e2->next_in_ael->prev_in_ael = &e; + e.prev_in_ael = e2; + e2->next_in_ael = &e; + } + } + + + void InsertRightEdge(Active& e, Active& e2) + { + e2.next_in_ael = e.next_in_ael; + if (e.next_in_ael) e.next_in_ael->prev_in_ael = &e2; + e2.prev_in_ael = &e; + e.next_in_ael = &e2; + } + + + void ClipperBase::InsertLocalMinimaIntoAEL(int64_t bot_y) + { + LocalMinima* local_minima; + Active* left_bound, * right_bound; + //Add any local minima (if any) at BotY ... + //nb: horizontal local minima edges should contain locMin.vertex.prev + + while (PopLocalMinima(bot_y, local_minima)) + { + if ((local_minima->vertex->flags & VertexFlags::OpenStart) != VertexFlags::None) + { + left_bound = nullptr; + } + else + { + left_bound = new Active(); + left_bound->bot = local_minima->vertex->pt; + left_bound->curr_x = left_bound->bot.x; + left_bound->wind_dx = -1; + left_bound->vertex_top = local_minima->vertex->prev; // ie descending + left_bound->top = left_bound->vertex_top->pt; + left_bound->local_min = local_minima; + SetDx(*left_bound); + } + + if ((local_minima->vertex->flags & VertexFlags::OpenEnd) != VertexFlags::None) + { + right_bound = nullptr; + } + else + { + right_bound = new Active(); + right_bound->bot = local_minima->vertex->pt; + right_bound->curr_x = right_bound->bot.x; + right_bound->wind_dx = 1; + right_bound->vertex_top = local_minima->vertex->next; // ie ascending + right_bound->top = right_bound->vertex_top->pt; + right_bound->local_min = local_minima; + SetDx(*right_bound); + } + + //Currently LeftB is just the descending bound and RightB is the ascending. + //Now if the LeftB isn't on the left of RightB then we need swap them. + if (left_bound && right_bound) + { + if (IsHorizontal(*left_bound)) + { + if (IsHeadingRightHorz(*left_bound)) SwapActives(left_bound, right_bound); + } + else if (IsHorizontal(*right_bound)) + { + if (IsHeadingLeftHorz(*right_bound)) SwapActives(left_bound, right_bound); + } + else if (left_bound->dx < right_bound->dx) + SwapActives(left_bound, right_bound); + } + else if (!left_bound) + { + left_bound = right_bound; + right_bound = nullptr; + } + + bool contributing; + left_bound->is_left_bound = true; + InsertLeftEdge(*left_bound); + + if (IsOpen(*left_bound)) + { + SetWindCountForOpenPathEdge(*left_bound); + contributing = IsContributingOpen(*left_bound); + } + else + { + SetWindCountForClosedPathEdge(*left_bound); + contributing = IsContributingClosed(*left_bound); + } + + if (right_bound) + { + right_bound->is_left_bound = false; + right_bound->wind_cnt = left_bound->wind_cnt; + right_bound->wind_cnt2 = left_bound->wind_cnt2; + InsertRightEdge(*left_bound, *right_bound); /////// + if (contributing) + { + AddLocalMinPoly(*left_bound, *right_bound, left_bound->bot, true); + if (!IsHorizontal(*left_bound)) + CheckJoinLeft(*left_bound, left_bound->bot); + } + + while (right_bound->next_in_ael && + IsValidAelOrder(*right_bound->next_in_ael, *right_bound)) + { + IntersectEdges(*right_bound, *right_bound->next_in_ael, right_bound->bot); + SwapPositionsInAEL(*right_bound, *right_bound->next_in_ael); + } + + if (IsHorizontal(*right_bound)) + PushHorz(*right_bound); + else + { + CheckJoinRight(*right_bound, right_bound->bot); + InsertScanline(right_bound->top.y); + } + } + else if (contributing) + { + StartOpenPath(*left_bound, left_bound->bot); + } + + if (IsHorizontal(*left_bound)) + PushHorz(*left_bound); + else + InsertScanline(left_bound->top.y); + } // while (PopLocalMinima()) + } + + + inline void ClipperBase::PushHorz(Active& e) + { + e.next_in_sel = (sel_ ? sel_ : nullptr); + sel_ = &e; + } + + + inline bool ClipperBase::PopHorz(Active*& e) + { + e = sel_; + if (!e) return false; + sel_ = sel_->next_in_sel; + return true; + } + + + OutPt* ClipperBase::AddLocalMinPoly(Active& e1, Active& e2, + const Point64& pt, bool is_new) + { + OutRec* outrec = NewOutRec(); + e1.outrec = outrec; + e2.outrec = outrec; + + if (IsOpen(e1)) + { + outrec->owner = nullptr; + outrec->is_open = true; + if (e1.wind_dx > 0) + SetSides(*outrec, e1, e2); + else + SetSides(*outrec, e2, e1); + } + else + { + Active* prevHotEdge = GetPrevHotEdge(e1); + //e.windDx is the winding direction of the **input** paths + //and unrelated to the winding direction of output polygons. + //Output orientation is determined by e.outrec.frontE which is + //the ascending edge (see AddLocalMinPoly). + if (prevHotEdge) + { + if (using_polytree_) + SetOwner(outrec, prevHotEdge->outrec); + if (OutrecIsAscending(prevHotEdge) == is_new) + SetSides(*outrec, e2, e1); + else + SetSides(*outrec, e1, e2); + } + else + { + outrec->owner = nullptr; + if (is_new) + SetSides(*outrec, e1, e2); + else + SetSides(*outrec, e2, e1); + } + } + + OutPt* op = new OutPt(pt, outrec); + outrec->pts = op; + return op; + } + + + OutPt* ClipperBase::AddLocalMaxPoly(Active& e1, Active& e2, const Point64& pt) + { + if (IsJoined(e1)) Split(e1, pt); + if (IsJoined(e2)) Split(e2, pt); + + if (IsFront(e1) == IsFront(e2)) + { + if (IsOpenEnd(e1)) + SwapFrontBackSides(*e1.outrec); + else if (IsOpenEnd(e2)) + SwapFrontBackSides(*e2.outrec); + else + { + succeeded_ = false; + return nullptr; + } + } + + OutPt* result = AddOutPt(e1, pt); + if (e1.outrec == e2.outrec) + { + OutRec& outrec = *e1.outrec; + outrec.pts = result; + + if (using_polytree_) + { + Active* e = GetPrevHotEdge(e1); + if (!e) + outrec.owner = nullptr; + else + SetOwner(&outrec, e->outrec); + // nb: outRec.owner here is likely NOT the real + // owner but this will be checked in DeepCheckOwner() + } + + UncoupleOutRec(e1); + result = outrec.pts; + if (outrec.owner && !outrec.owner->front_edge) + outrec.owner = GetRealOutRec(outrec.owner); + } + //and to preserve the winding orientation of outrec ... + else if (IsOpen(e1)) + { + if (e1.wind_dx < 0) + JoinOutrecPaths(e1, e2); + else + JoinOutrecPaths(e2, e1); + } + else if (e1.outrec->idx < e2.outrec->idx) + JoinOutrecPaths(e1, e2); + else + JoinOutrecPaths(e2, e1); + return result; + } + + void ClipperBase::JoinOutrecPaths(Active& e1, Active& e2) + { + //join e2 outrec path onto e1 outrec path and then delete e2 outrec path + //pointers. (NB Only very rarely do the joining ends share the same coords.) + OutPt* p1_st = e1.outrec->pts; + OutPt* p2_st = e2.outrec->pts; + OutPt* p1_end = p1_st->next; + OutPt* p2_end = p2_st->next; + if (IsFront(e1)) + { + p2_end->prev = p1_st; + p1_st->next = p2_end; + p2_st->next = p1_end; + p1_end->prev = p2_st; + e1.outrec->pts = p2_st; + e1.outrec->front_edge = e2.outrec->front_edge; + if (e1.outrec->front_edge) + e1.outrec->front_edge->outrec = e1.outrec; + } + else + { + p1_end->prev = p2_st; + p2_st->next = p1_end; + p1_st->next = p2_end; + p2_end->prev = p1_st; + e1.outrec->back_edge = e2.outrec->back_edge; + if (e1.outrec->back_edge) + e1.outrec->back_edge->outrec = e1.outrec; + } + + //after joining, the e2.OutRec must contains no vertices ... + e2.outrec->front_edge = nullptr; + e2.outrec->back_edge = nullptr; + e2.outrec->pts = nullptr; + SetOwner(e2.outrec, e1.outrec); + + if (IsOpenEnd(e1)) + { + e2.outrec->pts = e1.outrec->pts; + e1.outrec->pts = nullptr; + } + + //and e1 and e2 are maxima and are about to be dropped from the Actives list. + e1.outrec = nullptr; + e2.outrec = nullptr; + } + + OutRec* ClipperBase::NewOutRec() + { + OutRec* result = new OutRec(); + result->idx = outrec_list_.size(); + outrec_list_.push_back(result); + result->pts = nullptr; + result->owner = nullptr; + result->polypath = nullptr; + result->is_open = false; + return result; + } + + + OutPt* ClipperBase::AddOutPt(const Active& e, const Point64& pt) + { + OutPt* new_op = nullptr; + + //Outrec.OutPts: a circular doubly-linked-list of POutPt where ... + //op_front[.Prev]* ~~~> op_back & op_back == op_front.Next + OutRec* outrec = e.outrec; + bool to_front = IsFront(e); + OutPt* op_front = outrec->pts; + OutPt* op_back = op_front->next; + + if (to_front) + { + if (pt == op_front->pt) + return op_front; + } + else if (pt == op_back->pt) + return op_back; + + new_op = new OutPt(pt, outrec); + op_back->prev = new_op; + new_op->prev = op_front; + new_op->next = op_back; + op_front->next = new_op; + if (to_front) outrec->pts = new_op; + return new_op; + } + + + void ClipperBase::CleanCollinear(OutRec* outrec) + { + outrec = GetRealOutRec(outrec); + if (!outrec || outrec->is_open) return; + if (!IsValidClosedPath(outrec->pts)) + { + DisposeOutPts(outrec); + return; + } + + OutPt* startOp = outrec->pts, * op2 = startOp; + for (; ; ) + { + //NB if preserveCollinear == true, then only remove 180 deg. spikes + if ((CrossProduct(op2->prev->pt, op2->pt, op2->next->pt) == 0) && + (op2->pt == op2->prev->pt || + op2->pt == op2->next->pt || !PreserveCollinear || + DotProduct(op2->prev->pt, op2->pt, op2->next->pt) < 0)) + { + + if (op2 == outrec->pts) outrec->pts = op2->prev; + + op2 = DisposeOutPt(op2); + if (!IsValidClosedPath(op2)) + { + DisposeOutPts(outrec); + return; + } + startOp = op2; + continue; + } + op2 = op2->next; + if (op2 == startOp) break; + } + FixSelfIntersects(outrec); + } + + void ClipperBase::DoSplitOp(OutRec* outrec, OutPt* splitOp) + { + // splitOp.prev -> splitOp && + // splitOp.next -> splitOp.next.next are intersecting + OutPt* prevOp = splitOp->prev; + OutPt* nextNextOp = splitOp->next->next; + outrec->pts = prevOp; + + Point64 ip; + GetIntersectPoint(prevOp->pt, splitOp->pt, + splitOp->next->pt, nextNextOp->pt, ip); + +#ifdef USINGZ + if (zCallback_) zCallback_(prevOp->pt, splitOp->pt, + splitOp->next->pt, nextNextOp->pt, ip); +#endif + double area1 = Area(outrec->pts); + double absArea1 = std::fabs(area1); + if (absArea1 < 2) + { + DisposeOutPts(outrec); + return; + } + + // nb: area1 is the path's area *before* splitting, whereas area2 is + // the area of the triangle containing splitOp & splitOp.next. + // So the only way for these areas to have the same sign is if + // the split triangle is larger than the path containing prevOp or + // if there's more than one self=intersection. + double area2 = AreaTriangle(ip, splitOp->pt, splitOp->next->pt); + double absArea2 = std::fabs(area2); + + // de-link splitOp and splitOp.next from the path + // while inserting the intersection point + if (ip == prevOp->pt || ip == nextNextOp->pt) + { + nextNextOp->prev = prevOp; + prevOp->next = nextNextOp; + } + else + { + OutPt* newOp2 = new OutPt(ip, prevOp->outrec); + newOp2->prev = prevOp; + newOp2->next = nextNextOp; + nextNextOp->prev = newOp2; + prevOp->next = newOp2; + } + + if (absArea2 >= 1 && + (absArea2 > absArea1 || (area2 > 0) == (area1 > 0))) + { + OutRec* newOr = NewOutRec(); + newOr->owner = outrec->owner; + + if (using_polytree_) + { + if (!outrec->splits) outrec->splits = new OutRecList(); + outrec->splits->push_back(newOr); + } + + splitOp->outrec = newOr; + splitOp->next->outrec = newOr; + OutPt* newOp = new OutPt(ip, newOr); + newOp->prev = splitOp->next; + newOp->next = splitOp; + newOr->pts = newOp; + splitOp->prev = newOp; + splitOp->next->next = newOp; + } + else + { + delete splitOp->next; + delete splitOp; + } + } + + void ClipperBase::FixSelfIntersects(OutRec* outrec) + { + OutPt* op2 = outrec->pts; + for (; ; ) + { + // triangles can't self-intersect + if (op2->prev == op2->next->next) break; + if (SegmentsIntersect(op2->prev->pt, + op2->pt, op2->next->pt, op2->next->next->pt)) + { + if (op2 == outrec->pts || op2->next == outrec->pts) + outrec->pts = outrec->pts->prev; + DoSplitOp(outrec, op2); + if (!outrec->pts) break; + op2 = outrec->pts; + continue; + } + else + op2 = op2->next; + + if (op2 == outrec->pts) break; + } + } + + + inline void UpdateOutrecOwner(OutRec* outrec) + { + OutPt* opCurr = outrec->pts; + for (; ; ) + { + opCurr->outrec = outrec; + opCurr = opCurr->next; + if (opCurr == outrec->pts) return; + } + } + + + OutPt* ClipperBase::StartOpenPath(Active& e, const Point64& pt) + { + OutRec* outrec = NewOutRec(); + outrec->is_open = true; + + if (e.wind_dx > 0) + { + outrec->front_edge = &e; + outrec->back_edge = nullptr; + } + else + { + outrec->front_edge = nullptr; + outrec->back_edge = &e; + } + + e.outrec = outrec; + + OutPt* op = new OutPt(pt, outrec); + outrec->pts = op; + return op; + } + + + inline void ClipperBase::UpdateEdgeIntoAEL(Active* e) + { + e->bot = e->top; + e->vertex_top = NextVertex(*e); + e->top = e->vertex_top->pt; + e->curr_x = e->bot.x; + SetDx(*e); + + if (IsJoined(*e)) Split(*e, e->bot); + + if (IsHorizontal(*e)) return; + InsertScanline(e->top.y); + + CheckJoinLeft(*e, e->bot); + CheckJoinRight(*e, e->bot); + } + + Active* FindEdgeWithMatchingLocMin(Active* e) + { + Active* result = e->next_in_ael; + while (result) + { + if (result->local_min == e->local_min) return result; + else if (!IsHorizontal(*result) && e->bot != result->bot) result = nullptr; + else result = result->next_in_ael; + } + result = e->prev_in_ael; + while (result) + { + if (result->local_min == e->local_min) return result; + else if (!IsHorizontal(*result) && e->bot != result->bot) return nullptr; + else result = result->prev_in_ael; + } + return result; + } + + + OutPt* ClipperBase::IntersectEdges(Active& e1, Active& e2, const Point64& pt) + { + //MANAGE OPEN PATH INTERSECTIONS SEPARATELY ... + if (has_open_paths_ && (IsOpen(e1) || IsOpen(e2))) + { + if (IsOpen(e1) && IsOpen(e2)) return nullptr; + Active* edge_o, * edge_c; + if (IsOpen(e1)) + { + edge_o = &e1; + edge_c = &e2; + } + else + { + edge_o = &e2; + edge_c = &e1; + } + if (IsJoined(*edge_c)) Split(*edge_c, pt); // needed for safety + + if (abs(edge_c->wind_cnt) != 1) return nullptr; + switch (cliptype_) + { + case ClipType::Union: + if (!IsHotEdge(*edge_c)) return nullptr; + break; + default: + if (edge_c->local_min->polytype == PathType::Subject) + return nullptr; + } + + switch (fillrule_) + { + case FillRule::Positive: if (edge_c->wind_cnt != 1) return nullptr; break; + case FillRule::Negative: if (edge_c->wind_cnt != -1) return nullptr; break; + default: if (std::abs(edge_c->wind_cnt) != 1) return nullptr; break; + } + + //toggle contribution ... + if (IsHotEdge(*edge_o)) + { + OutPt* resultOp = AddOutPt(*edge_o, pt); +#ifdef USINGZ + if (zCallback_) SetZ(e1, e2, resultOp->pt); +#endif + if (IsFront(*edge_o)) edge_o->outrec->front_edge = nullptr; + else edge_o->outrec->back_edge = nullptr; + edge_o->outrec = nullptr; + return resultOp; + } + + //horizontal edges can pass under open paths at a LocMins + else if (pt == edge_o->local_min->vertex->pt && + !IsOpenEnd(*edge_o->local_min->vertex)) + { + //find the other side of the LocMin and + //if it's 'hot' join up with it ... + Active* e3 = FindEdgeWithMatchingLocMin(edge_o); + if (e3 && IsHotEdge(*e3)) + { + edge_o->outrec = e3->outrec; + if (edge_o->wind_dx > 0) + SetSides(*e3->outrec, *edge_o, *e3); + else + SetSides(*e3->outrec, *e3, *edge_o); + return e3->outrec->pts; + } + else + return StartOpenPath(*edge_o, pt); + } + else + return StartOpenPath(*edge_o, pt); + } + + //MANAGING CLOSED PATHS FROM HERE ON + + if (IsJoined(e1)) Split(e1, pt); + if (IsJoined(e2)) Split(e2, pt); + + //UPDATE WINDING COUNTS... + + int old_e1_windcnt, old_e2_windcnt; + if (e1.local_min->polytype == e2.local_min->polytype) + { + if (fillrule_ == FillRule::EvenOdd) + { + old_e1_windcnt = e1.wind_cnt; + e1.wind_cnt = e2.wind_cnt; + e2.wind_cnt = old_e1_windcnt; + } + else + { + if (e1.wind_cnt + e2.wind_dx == 0) + e1.wind_cnt = -e1.wind_cnt; + else + e1.wind_cnt += e2.wind_dx; + if (e2.wind_cnt - e1.wind_dx == 0) + e2.wind_cnt = -e2.wind_cnt; + else + e2.wind_cnt -= e1.wind_dx; + } + } + else + { + if (fillrule_ != FillRule::EvenOdd) + { + e1.wind_cnt2 += e2.wind_dx; + e2.wind_cnt2 -= e1.wind_dx; + } + else + { + e1.wind_cnt2 = (e1.wind_cnt2 == 0 ? 1 : 0); + e2.wind_cnt2 = (e2.wind_cnt2 == 0 ? 1 : 0); + } + } + + switch (fillrule_) + { + case FillRule::EvenOdd: + case FillRule::NonZero: + old_e1_windcnt = abs(e1.wind_cnt); + old_e2_windcnt = abs(e2.wind_cnt); + break; + default: + if (fillrule_ == fillpos) + { + old_e1_windcnt = e1.wind_cnt; + old_e2_windcnt = e2.wind_cnt; + } + else + { + old_e1_windcnt = -e1.wind_cnt; + old_e2_windcnt = -e2.wind_cnt; + } + break; + } + + const bool e1_windcnt_in_01 = old_e1_windcnt == 0 || old_e1_windcnt == 1; + const bool e2_windcnt_in_01 = old_e2_windcnt == 0 || old_e2_windcnt == 1; + + if ((!IsHotEdge(e1) && !e1_windcnt_in_01) || (!IsHotEdge(e2) && !e2_windcnt_in_01)) + { + return nullptr; + } + + //NOW PROCESS THE INTERSECTION ... + OutPt* resultOp = nullptr; + //if both edges are 'hot' ... + if (IsHotEdge(e1) && IsHotEdge(e2)) + { + if ((old_e1_windcnt != 0 && old_e1_windcnt != 1) || (old_e2_windcnt != 0 && old_e2_windcnt != 1) || + (e1.local_min->polytype != e2.local_min->polytype && cliptype_ != ClipType::Xor)) + { + resultOp = AddLocalMaxPoly(e1, e2, pt); +#ifdef USINGZ + if (zCallback_ && resultOp) SetZ(e1, e2, resultOp->pt); +#endif + } + else if (IsFront(e1) || (e1.outrec == e2.outrec)) + { + //this 'else if' condition isn't strictly needed but + //it's sensible to split polygons that ony touch at + //a common vertex (not at common edges). + + resultOp = AddLocalMaxPoly(e1, e2, pt); +#ifdef USINGZ + OutPt* op2 = AddLocalMinPoly(e1, e2, pt); + if (zCallback_ && resultOp) SetZ(e1, e2, resultOp->pt); + if (zCallback_) SetZ(e1, e2, op2->pt); +#else + AddLocalMinPoly(e1, e2, pt); +#endif + } + else + { + resultOp = AddOutPt(e1, pt); +#ifdef USINGZ + OutPt* op2 = AddOutPt(e2, pt); + if (zCallback_) + { + SetZ(e1, e2, resultOp->pt); + SetZ(e1, e2, op2->pt); + } +#else + AddOutPt(e2, pt); +#endif + SwapOutrecs(e1, e2); + } + } + else if (IsHotEdge(e1)) + { + resultOp = AddOutPt(e1, pt); +#ifdef USINGZ + if (zCallback_) SetZ(e1, e2, resultOp->pt); +#endif + SwapOutrecs(e1, e2); + } + else if (IsHotEdge(e2)) + { + resultOp = AddOutPt(e2, pt); +#ifdef USINGZ + if (zCallback_) SetZ(e1, e2, resultOp->pt); +#endif + SwapOutrecs(e1, e2); + } + else + { + int64_t e1Wc2, e2Wc2; + switch (fillrule_) + { + case FillRule::EvenOdd: + case FillRule::NonZero: + e1Wc2 = abs(e1.wind_cnt2); + e2Wc2 = abs(e2.wind_cnt2); + break; + default: + if (fillrule_ == fillpos) + { + e1Wc2 = e1.wind_cnt2; + e2Wc2 = e2.wind_cnt2; + } + else + { + e1Wc2 = -e1.wind_cnt2; + e2Wc2 = -e2.wind_cnt2; + } + break; + } + + if (!IsSamePolyType(e1, e2)) + { + resultOp = AddLocalMinPoly(e1, e2, pt, false); +#ifdef USINGZ + if (zCallback_) SetZ(e1, e2, resultOp->pt); +#endif + } + else if (old_e1_windcnt == 1 && old_e2_windcnt == 1) + { + resultOp = nullptr; + switch (cliptype_) + { + case ClipType::Union: + if (e1Wc2 <= 0 && e2Wc2 <= 0) + resultOp = AddLocalMinPoly(e1, e2, pt, false); + break; + case ClipType::Difference: + if (((GetPolyType(e1) == PathType::Clip) && (e1Wc2 > 0) && (e2Wc2 > 0)) || + ((GetPolyType(e1) == PathType::Subject) && (e1Wc2 <= 0) && (e2Wc2 <= 0))) + { + resultOp = AddLocalMinPoly(e1, e2, pt, false); + } + break; + case ClipType::Xor: + resultOp = AddLocalMinPoly(e1, e2, pt, false); + break; + default: + if (e1Wc2 > 0 && e2Wc2 > 0) + resultOp = AddLocalMinPoly(e1, e2, pt, false); + break; + } +#ifdef USINGZ + if (resultOp && zCallback_) SetZ(e1, e2, resultOp->pt); +#endif + } + } + return resultOp; + } + + inline void ClipperBase::DeleteFromAEL(Active& e) + { + Active* prev = e.prev_in_ael; + Active* next = e.next_in_ael; + if (!prev && !next && (&e != actives_)) return; // already deleted + if (prev) + prev->next_in_ael = next; + else + actives_ = next; + if (next) next->prev_in_ael = prev; + delete& e; + } + + + inline void ClipperBase::AdjustCurrXAndCopyToSEL(const int64_t top_y) + { + Active* e = actives_; + sel_ = e; + while (e) + { + e->prev_in_sel = e->prev_in_ael; + e->next_in_sel = e->next_in_ael; + e->jump = e->next_in_sel; + if (e->join_with == JoinWith::Left) + e->curr_x = e->prev_in_ael->curr_x; // also avoids complications + else + e->curr_x = TopX(*e, top_y); + e = e->next_in_ael; + } + } + + bool ClipperBase::ExecuteInternal(ClipType ct, FillRule fillrule, bool use_polytrees) + { + cliptype_ = ct; + fillrule_ = fillrule; + using_polytree_ = use_polytrees; + Reset(); + int64_t y; + if (ct == ClipType::None || !PopScanline(y)) return true; + + while (succeeded_) + { + InsertLocalMinimaIntoAEL(y); + Active* e; + while (PopHorz(e)) DoHorizontal(*e); + if (horz_seg_list_.size() > 0) + { + ConvertHorzSegsToJoins(); + horz_seg_list_.clear(); + } + bot_y_ = y; // bot_y_ == bottom of scanbeam + if (!PopScanline(y)) break; // y new top of scanbeam + DoIntersections(y); + DoTopOfScanbeam(y); + while (PopHorz(e)) DoHorizontal(*e); + } + if (succeeded_) ProcessHorzJoins(); + return succeeded_; + } + + inline void FixOutRecPts(OutRec* outrec) + { + OutPt* op = outrec->pts; + do { + op->outrec = outrec; + op = op->next; + } while (op != outrec->pts); + } + + inline Rect64 GetBounds(OutPt* op) + { + Rect64 result(op->pt.x, op->pt.y, op->pt.x, op->pt.y); + OutPt* op2 = op->next; + while (op2 != op) + { + if (op2->pt.x < result.left) result.left = op2->pt.x; + else if (op2->pt.x > result.right) result.right = op2->pt.x; + if (op2->pt.y < result.top) result.top = op2->pt.y; + else if (op2->pt.y > result.bottom) result.bottom = op2->pt.y; + op2 = op2->next; + } + return result; + } + + static PointInPolygonResult PointInOpPolygon(const Point64& pt, OutPt* op) + { + if (op == op->next || op->prev == op->next) + return PointInPolygonResult::IsOutside; + + OutPt* op2 = op; + do + { + if (op->pt.y != pt.y) break; + op = op->next; + } while (op != op2); + if (op->pt.y == pt.y) // not a proper polygon + return PointInPolygonResult::IsOutside; + + bool is_above = op->pt.y < pt.y, starting_above = is_above; + int val = 0; + op2 = op->next; + while (op2 != op) + { + if (is_above) + while (op2 != op && op2->pt.y < pt.y) op2 = op2->next; + else + while (op2 != op && op2->pt.y > pt.y) op2 = op2->next; + if (op2 == op) break; + + // must have touched or crossed the pt.Y horizonal + // and this must happen an even number of times + + if (op2->pt.y == pt.y) // touching the horizontal + { + if (op2->pt.x == pt.x || (op2->pt.y == op2->prev->pt.y && + (pt.x < op2->prev->pt.x) != (pt.x < op2->pt.x))) + return PointInPolygonResult::IsOn; + + op2 = op2->next; + if (op2 == op) break; + continue; + } + + if (pt.x < op2->pt.x && pt.x < op2->prev->pt.x); + // do nothing because + // we're only interested in edges crossing on the left + else if ((pt.x > op2->prev->pt.x && pt.x > op2->pt.x)) + val = 1 - val; // toggle val + else + { + double d = CrossProduct(op2->prev->pt, op2->pt, pt); + if (d == 0) return PointInPolygonResult::IsOn; + if ((d < 0) == is_above) val = 1 - val; + } + is_above = !is_above; + op2 = op2->next; + } + + if (is_above != starting_above) + { + double d = CrossProduct(op2->prev->pt, op2->pt, pt); + if (d == 0) return PointInPolygonResult::IsOn; + if ((d < 0) == is_above) val = 1 - val; + } + + if (val == 0) return PointInPolygonResult::IsOutside; + else return PointInPolygonResult::IsInside; + } + + inline bool Path1InsidePath2(OutPt* op1, OutPt* op2) + { + // we need to make some accommodation for rounding errors + // so we won't jump if the first vertex is found outside + int outside_cnt = 0; + OutPt* op = op1; + do + { + PointInPolygonResult result = PointInOpPolygon(op->pt, op2); + if (result == PointInPolygonResult::IsOutside) ++outside_cnt; + else if (result == PointInPolygonResult::IsInside) --outside_cnt; + op = op->next; + } while (op != op1 && std::abs(outside_cnt) < 2); + if (std::abs(outside_cnt) > 1) return (outside_cnt < 0); + // since path1's location is still equivocal, check its midpoint + Point64 mp = GetBounds(op).MidPoint(); + return PointInOpPolygon(mp, op2) == PointInPolygonResult::IsInside; + } + + inline bool SetHorzSegHeadingForward(HorzSegment& hs, OutPt* opP, OutPt* opN) + { + if (opP->pt.x == opN->pt.x) return false; + if (opP->pt.x < opN->pt.x) + { + hs.left_op = opP; + hs.right_op = opN; + hs.left_to_right = true; + } + else + { + hs.left_op = opN; + hs.right_op = opP; + hs.left_to_right = false; + } + return true; + } + + inline bool UpdateHorzSegment(HorzSegment& hs) + { + OutPt* op = hs.left_op; + OutRec* outrec = GetRealOutRec(op->outrec); + bool outrecHasEdges = outrec->front_edge; + int64_t curr_y = op->pt.y; + OutPt* opP = op, * opN = op; + if (outrecHasEdges) + { + OutPt* opA = outrec->pts, * opZ = opA->next; + while (opP != opZ && opP->prev->pt.y == curr_y) + opP = opP->prev; + while (opN != opA && opN->next->pt.y == curr_y) + opN = opN->next; + } + else + { + while (opP->prev != opN && opP->prev->pt.y == curr_y) + opP = opP->prev; + while (opN->next != opP && opN->next->pt.y == curr_y) + opN = opN->next; + } + bool result = + SetHorzSegHeadingForward(hs, opP, opN) && + !hs.left_op->horz; + + if (result) + hs.left_op->horz = &hs; + else + hs.right_op = nullptr; // (for sorting) + return result; + } + + void ClipperBase::ConvertHorzSegsToJoins() + { + auto j = std::count_if(horz_seg_list_.begin(), + horz_seg_list_.end(), + [](HorzSegment& hs) { return UpdateHorzSegment(hs); }); + if (j < 2) return; + std::sort(horz_seg_list_.begin(), horz_seg_list_.end(), HorzSegSorter()); + + HorzSegmentList::iterator hs1 = horz_seg_list_.begin(), hs2; + HorzSegmentList::iterator hs_end = hs1 +j; + HorzSegmentList::iterator hs_end1 = hs_end - 1; + + for (; hs1 != hs_end1; ++hs1) + { + for (hs2 = hs1 + 1; hs2 != hs_end; ++hs2) + { + if (hs2->left_op->pt.x >= hs1->right_op->pt.x) break; + if (hs2->left_to_right == hs1->left_to_right || + (hs2->right_op->pt.x <= hs1->left_op->pt.x)) continue; + int64_t curr_y = hs1->left_op->pt.y; + if (hs1->left_to_right) + { + while (hs1->left_op->next->pt.y == curr_y && + hs1->left_op->next->pt.x <= hs2->left_op->pt.x) + hs1->left_op = hs1->left_op->next; + while (hs2->left_op->prev->pt.y == curr_y && + hs2->left_op->prev->pt.x <= hs1->left_op->pt.x) + hs2->left_op = hs2->left_op->prev; + HorzJoin join = HorzJoin( + DuplicateOp(hs1->left_op, true), + DuplicateOp(hs2->left_op, false)); + horz_join_list_.push_back(join); + } + else + { + while (hs1->left_op->prev->pt.y == curr_y && + hs1->left_op->prev->pt.x <= hs2->left_op->pt.x) + hs1->left_op = hs1->left_op->prev; + while (hs2->left_op->next->pt.y == curr_y && + hs2->left_op->next->pt.x <= hs1->left_op->pt.x) + hs2->left_op = hs2->left_op->next; + HorzJoin join = HorzJoin( + DuplicateOp(hs2->left_op, true), + DuplicateOp(hs1->left_op, false)); + horz_join_list_.push_back(join); + } + } + } + } + + void ClipperBase::ProcessHorzJoins() + { + for (const HorzJoin& j : horz_join_list_) + { + OutRec* or1 = GetRealOutRec(j.op1->outrec); + OutRec* or2 = GetRealOutRec(j.op2->outrec); + + OutPt* op1b = j.op1->next; + OutPt* op2b = j.op2->prev; + j.op1->next = j.op2; + j.op2->prev = j.op1; + op1b->prev = op2b; + op2b->next = op1b; + + if (or1 == or2) + { + or2 = new OutRec(); + or2->pts = op1b; + FixOutRecPts(or2); + if (or1->pts->outrec == or2) + { + or1->pts = j.op1; + or1->pts->outrec = or1; + } + + if (using_polytree_) + { + if (Path1InsidePath2(or2->pts, or1->pts)) + SetOwner(or2, or1); + else if (Path1InsidePath2(or1->pts, or2->pts)) + SetOwner(or1, or2); + else + or2->owner = or1; + } + else + or2->owner = or1; + + outrec_list_.push_back(or2); + } + else + { + or2->pts = nullptr; + if (using_polytree_) + SetOwner(or2, or1); + else + or2->owner = or1; + } + } + } + + void ClipperBase::DoIntersections(const int64_t top_y) + { + if (BuildIntersectList(top_y)) + { + ProcessIntersectList(); + intersect_nodes_.clear(); + } + } + + void ClipperBase::AddNewIntersectNode(Active& e1, Active& e2, int64_t top_y) + { + Point64 ip; + if (!GetIntersectPoint(e1.bot, e1.top, e2.bot, e2.top, ip)) + ip = Point64(e1.curr_x, top_y); //parallel edges + + //rounding errors can occasionally place the calculated intersection + //point either below or above the scanbeam, so check and correct ... + if (ip.y > bot_y_ || ip.y < top_y) + { + double abs_dx1 = std::fabs(e1.dx); + double abs_dx2 = std::fabs(e2.dx); + if (abs_dx1 > 100 && abs_dx2 > 100) + { + if (abs_dx1 > abs_dx2) + ip = GetClosestPointOnSegment(ip, e1.bot, e1.top); + else + ip = GetClosestPointOnSegment(ip, e2.bot, e2.top); + } + else if (abs_dx1 > 100) + ip = GetClosestPointOnSegment(ip, e1.bot, e1.top); + else if (abs_dx2 > 100) + ip = GetClosestPointOnSegment(ip, e2.bot, e2.top); + else + { + if (ip.y < top_y) ip.y = top_y; + else ip.y = bot_y_; + if (abs_dx1 < abs_dx2) ip.x = TopX(e1, ip.y); + else ip.x = TopX(e2, ip.y); + } + } + intersect_nodes_.push_back(IntersectNode(&e1, &e2, ip)); + } + + bool ClipperBase::BuildIntersectList(const int64_t top_y) + { + if (!actives_ || !actives_->next_in_ael) return false; + + //Calculate edge positions at the top of the current scanbeam, and from this + //we will determine the intersections required to reach these new positions. + AdjustCurrXAndCopyToSEL(top_y); + //Find all edge intersections in the current scanbeam using a stable merge + //sort that ensures only adjacent edges are intersecting. Intersect info is + //stored in FIntersectList ready to be processed in ProcessIntersectList. + //Re merge sorts see https://stackoverflow.com/a/46319131/359538 + + Active* left = sel_, * right, * l_end, * r_end, * curr_base, * tmp; + + while (left && left->jump) + { + Active* prev_base = nullptr; + while (left && left->jump) + { + curr_base = left; + right = left->jump; + l_end = right; + r_end = right->jump; + left->jump = r_end; + while (left != l_end && right != r_end) + { + if (right->curr_x < left->curr_x) + { + tmp = right->prev_in_sel; + for (; ; ) + { + AddNewIntersectNode(*tmp, *right, top_y); + if (tmp == left) break; + tmp = tmp->prev_in_sel; + } + + tmp = right; + right = ExtractFromSEL(tmp); + l_end = right; + Insert1Before2InSEL(tmp, left); + if (left == curr_base) + { + curr_base = tmp; + curr_base->jump = r_end; + if (!prev_base) sel_ = curr_base; + else prev_base->jump = curr_base; + } + } + else left = left->next_in_sel; + } + prev_base = curr_base; + left = r_end; + } + left = sel_; + } + return intersect_nodes_.size() > 0; + } + + void ClipperBase::ProcessIntersectList() + { + //We now have a list of intersections required so that edges will be + //correctly positioned at the top of the scanbeam. However, it's important + //that edge intersections are processed from the bottom up, but it's also + //crucial that intersections only occur between adjacent edges. + + //First we do a quicksort so intersections proceed in a bottom up order ... + std::sort(intersect_nodes_.begin(), intersect_nodes_.end(), IntersectListSort); + //Now as we process these intersections, we must sometimes adjust the order + //to ensure that intersecting edges are always adjacent ... + + IntersectNodeList::iterator node_iter, node_iter2; + for (node_iter = intersect_nodes_.begin(); + node_iter != intersect_nodes_.end(); ++node_iter) + { + if (!EdgesAdjacentInAEL(*node_iter)) + { + node_iter2 = node_iter + 1; + while (!EdgesAdjacentInAEL(*node_iter2)) ++node_iter2; + std::swap(*node_iter, *node_iter2); + } + + IntersectNode& node = *node_iter; + IntersectEdges(*node.edge1, *node.edge2, node.pt); + SwapPositionsInAEL(*node.edge1, *node.edge2); + + node.edge1->curr_x = node.pt.x; + node.edge2->curr_x = node.pt.x; + CheckJoinLeft(*node.edge2, node.pt, true); + CheckJoinRight(*node.edge1, node.pt, true); + } + } + + void ClipperBase::SwapPositionsInAEL(Active& e1, Active& e2) + { + //preconditon: e1 must be immediately to the left of e2 + Active* next = e2.next_in_ael; + if (next) next->prev_in_ael = &e1; + Active* prev = e1.prev_in_ael; + if (prev) prev->next_in_ael = &e2; + e2.prev_in_ael = prev; + e2.next_in_ael = &e1; + e1.prev_in_ael = &e2; + e1.next_in_ael = next; + if (!e2.prev_in_ael) actives_ = &e2; + } + + inline OutPt* GetLastOp(const Active& hot_edge) + { + OutRec* outrec = hot_edge.outrec; + OutPt* result = outrec->pts; + if (&hot_edge != outrec->front_edge) + result = result->next; + return result; + } + + void ClipperBase::AddTrialHorzJoin(OutPt* op) + { + if (op->outrec->is_open) return; + horz_seg_list_.push_back(HorzSegment(op)); + } + + bool ClipperBase::ResetHorzDirection(const Active& horz, + const Vertex* max_vertex, int64_t& horz_left, int64_t& horz_right) + { + if (horz.bot.x == horz.top.x) + { + //the horizontal edge is going nowhere ... + horz_left = horz.curr_x; + horz_right = horz.curr_x; + Active* e = horz.next_in_ael; + while (e && e->vertex_top != max_vertex) e = e->next_in_ael; + return e != nullptr; + } + else if (horz.curr_x < horz.top.x) + { + horz_left = horz.curr_x; + horz_right = horz.top.x; + return true; + } + else + { + horz_left = horz.top.x; + horz_right = horz.curr_x; + return false; // right to left + } + } + + inline bool HorzIsSpike(const Active& horzEdge) + { + Point64 nextPt = NextVertex(horzEdge)->pt; + return (nextPt.y == horzEdge.bot.y) && + (horzEdge.bot.x < horzEdge.top.x) != (horzEdge.top.x < nextPt.x); + } + + inline void TrimHorz(Active& horzEdge, bool preserveCollinear) + { + bool wasTrimmed = false; + Point64 pt = NextVertex(horzEdge)->pt; + while (pt.y == horzEdge.top.y) + { + //always trim 180 deg. spikes (in closed paths) + //but otherwise break if preserveCollinear = true + if (preserveCollinear && + ((pt.x < horzEdge.top.x) != (horzEdge.bot.x < horzEdge.top.x))) + break; + + horzEdge.vertex_top = NextVertex(horzEdge); + horzEdge.top = pt; + wasTrimmed = true; + if (IsMaxima(horzEdge)) break; + pt = NextVertex(horzEdge)->pt; + } + + if (wasTrimmed) SetDx(horzEdge); // +/-infinity + } + + void ClipperBase::DoHorizontal(Active& horz) + /******************************************************************************* + * Notes: Horizontal edges (HEs) at scanline intersections (ie at the top or * + * bottom of a scanbeam) are processed as if layered.The order in which HEs * + * are processed doesn't matter. HEs intersect with the bottom vertices of * + * other HEs[#] and with non-horizontal edges [*]. Once these intersections * + * are completed, intermediate HEs are 'promoted' to the next edge in their * + * bounds, and they in turn may be intersected[%] by other HEs. * + * * + * eg: 3 horizontals at a scanline: / | / / * + * | / | (HE3)o ========%========== o * + * o ======= o(HE2) / | / / * + * o ============#=========*======*========#=========o (HE1) * + * / | / | / * + *******************************************************************************/ + { + Point64 pt; + bool horzIsOpen = IsOpen(horz); + int64_t y = horz.bot.y; + Vertex* vertex_max; + if (horzIsOpen) + vertex_max = GetCurrYMaximaVertex_Open(horz); + else + vertex_max = GetCurrYMaximaVertex(horz); + + // remove 180 deg.spikes and also simplify + // consecutive horizontals when PreserveCollinear = true + if (vertex_max && !horzIsOpen && vertex_max != horz.vertex_top) + TrimHorz(horz, PreserveCollinear); + + int64_t horz_left, horz_right; + bool is_left_to_right = + ResetHorzDirection(horz, vertex_max, horz_left, horz_right); + + if (IsHotEdge(horz)) + { +#ifdef USINGZ + OutPt* op = AddOutPt(horz, Point64(horz.curr_x, y, horz.bot.z)); +#else + OutPt* op = AddOutPt(horz, Point64(horz.curr_x, y)); +#endif + AddTrialHorzJoin(op); + } + OutRec* currHorzOutrec = horz.outrec; + + while (true) // loop through consec. horizontal edges + { + Active* e; + if (is_left_to_right) e = horz.next_in_ael; + else e = horz.prev_in_ael; + + while (e) + { + if (e->vertex_top == vertex_max) + { + if (IsHotEdge(horz) && IsJoined(*e)) + Split(*e, e->top); + + if (IsHotEdge(horz)) + { + while (horz.vertex_top != vertex_max) + { + AddOutPt(horz, horz.top); + UpdateEdgeIntoAEL(&horz); + } + if (is_left_to_right) + AddLocalMaxPoly(horz, *e, horz.top); + else + AddLocalMaxPoly(*e, horz, horz.top); + } + DeleteFromAEL(*e); + DeleteFromAEL(horz); + return; + } + + //if horzEdge is a maxima, keep going until we reach + //its maxima pair, otherwise check for break conditions + if (vertex_max != horz.vertex_top || IsOpenEnd(horz)) + { + //otherwise stop when 'ae' is beyond the end of the horizontal line + if ((is_left_to_right && e->curr_x > horz_right) || + (!is_left_to_right && e->curr_x < horz_left)) break; + + if (e->curr_x == horz.top.x && !IsHorizontal(*e)) + { + pt = NextVertex(horz)->pt; + if (is_left_to_right) + { + //with open paths we'll only break once past horz's end + if (IsOpen(*e) && !IsSamePolyType(*e, horz) && !IsHotEdge(*e)) + { + if (TopX(*e, pt.y) > pt.x) break; + } + //otherwise we'll only break when horz's outslope is greater than e's + else if (TopX(*e, pt.y) >= pt.x) break; + } + else + { + if (IsOpen(*e) && !IsSamePolyType(*e, horz) && !IsHotEdge(*e)) + { + if (TopX(*e, pt.y) < pt.x) break; + } + else if (TopX(*e, pt.y) <= pt.x) break; + } + } + } + + pt = Point64(e->curr_x, horz.bot.y); + if (is_left_to_right) + { + IntersectEdges(horz, *e, pt); + SwapPositionsInAEL(horz, *e); + horz.curr_x = e->curr_x; + e = horz.next_in_ael; + } + else + { + IntersectEdges(*e, horz, pt); + SwapPositionsInAEL(*e, horz); + horz.curr_x = e->curr_x; + e = horz.prev_in_ael; + } + + if (horz.outrec && horz.outrec != currHorzOutrec) + { + currHorzOutrec = horz.outrec; + //nb: The outrec containining the op returned by IntersectEdges + //above may no longer be associated with horzEdge. + AddTrialHorzJoin(GetLastOp(horz)); + } + } + + //check if we've finished with (consecutive) horizontals ... + if (horzIsOpen && IsOpenEnd(horz)) // ie open at top + { + if (IsHotEdge(horz)) + { + AddOutPt(horz, horz.top); + if (IsFront(horz)) + horz.outrec->front_edge = nullptr; + else + horz.outrec->back_edge = nullptr; + horz.outrec = nullptr; + } + DeleteFromAEL(horz); + return; + } + else if (NextVertex(horz)->pt.y != horz.top.y) + break; + + //still more horizontals in bound to process ... + if (IsHotEdge(horz)) + AddOutPt(horz, horz.top); + UpdateEdgeIntoAEL(&horz); + + if (PreserveCollinear && !horzIsOpen && HorzIsSpike(horz)) + TrimHorz(horz, true); + + is_left_to_right = + ResetHorzDirection(horz, vertex_max, horz_left, horz_right); + } + + if (IsHotEdge(horz)) AddOutPt(horz, horz.top); + UpdateEdgeIntoAEL(&horz); // end of an intermediate horiz. + } + + void ClipperBase::DoTopOfScanbeam(const int64_t y) + { + sel_ = nullptr; // sel_ is reused to flag horizontals (see PushHorz below) + Active* e = actives_; + while (e) + { + //nb: 'e' will never be horizontal here + if (e->top.y == y) + { + e->curr_x = e->top.x; + if (IsMaxima(*e)) + { + e = DoMaxima(*e); // TOP OF BOUND (MAXIMA) + continue; + } + else + { + //INTERMEDIATE VERTEX ... + if (IsHotEdge(*e)) AddOutPt(*e, e->top); + UpdateEdgeIntoAEL(e); + if (IsHorizontal(*e)) + PushHorz(*e); // horizontals are processed later + } + } + else // i.e. not the top of the edge + e->curr_x = TopX(*e, y); + + e = e->next_in_ael; + } + } + + + Active* ClipperBase::DoMaxima(Active& e) + { + Active* next_e, * prev_e, * max_pair; + prev_e = e.prev_in_ael; + next_e = e.next_in_ael; + if (IsOpenEnd(e)) + { + if (IsHotEdge(e)) AddOutPt(e, e.top); + if (!IsHorizontal(e)) + { + if (IsHotEdge(e)) + { + if (IsFront(e)) + e.outrec->front_edge = nullptr; + else + e.outrec->back_edge = nullptr; + e.outrec = nullptr; + } + DeleteFromAEL(e); + } + return next_e; + } + + max_pair = GetMaximaPair(e); + if (!max_pair) return next_e; // eMaxPair is horizontal + + if (IsJoined(e)) Split(e, e.top); + if (IsJoined(*max_pair)) Split(*max_pair, max_pair->top); + + //only non-horizontal maxima here. + //process any edges between maxima pair ... + while (next_e != max_pair) + { + IntersectEdges(e, *next_e, e.top); + SwapPositionsInAEL(e, *next_e); + next_e = e.next_in_ael; + } + + if (IsOpen(e)) + { + if (IsHotEdge(e)) + AddLocalMaxPoly(e, *max_pair, e.top); + DeleteFromAEL(*max_pair); + DeleteFromAEL(e); + return (prev_e ? prev_e->next_in_ael : actives_); + } + + // e.next_in_ael== max_pair ... + if (IsHotEdge(e)) + AddLocalMaxPoly(e, *max_pair, e.top); + + DeleteFromAEL(e); + DeleteFromAEL(*max_pair); + return (prev_e ? prev_e->next_in_ael : actives_); + } + + void ClipperBase::Split(Active& e, const Point64& pt) + { + if (e.join_with == JoinWith::Right) + { + e.join_with = JoinWith::None; + e.next_in_ael->join_with = JoinWith::None; + AddLocalMinPoly(e, *e.next_in_ael, pt, true); + } + else + { + e.join_with = JoinWith::None; + e.prev_in_ael->join_with = JoinWith::None; + AddLocalMinPoly(*e.prev_in_ael, e, pt, true); + } + } + + void ClipperBase::CheckJoinLeft(Active& e, + const Point64& pt, bool check_curr_x) + { + Active* prev = e.prev_in_ael; + if (IsOpen(e) || !IsHotEdge(e) || !prev || + IsOpen(*prev) || !IsHotEdge(*prev) || + pt.y < e.top.y + 2 || pt.y < prev->top.y + 2) // avoid trivial joins + return; + + if (check_curr_x) + { + if (DistanceFromLineSqrd(pt, prev->bot, prev->top) > 0.25) return; + } + else if (e.curr_x != prev->curr_x) return; + if (CrossProduct(e.top, pt, prev->top)) return; + + if (e.outrec->idx == prev->outrec->idx) + AddLocalMaxPoly(*prev, e, pt); + else if (e.outrec->idx < prev->outrec->idx) + JoinOutrecPaths(e, *prev); + else + JoinOutrecPaths(*prev, e); + prev->join_with = JoinWith::Right; + e.join_with = JoinWith::Left; + } + + void ClipperBase::CheckJoinRight(Active& e, + const Point64& pt, bool check_curr_x) + { + Active* next = e.next_in_ael; + if (IsOpen(e) || !IsHotEdge(e) || + !next || IsOpen(*next) || !IsHotEdge(*next) || + pt.y < e.top.y +2 || pt.y < next->top.y +2) // avoids trivial joins + return; + + if (check_curr_x) + { + if (DistanceFromLineSqrd(pt, next->bot, next->top) > 0.35) return; + } + else if (e.curr_x != next->curr_x) return; + if (CrossProduct(e.top, pt, next->top)) return; + + if (e.outrec->idx == next->outrec->idx) + AddLocalMaxPoly(e, *next, pt); + else if (e.outrec->idx < next->outrec->idx) + JoinOutrecPaths(e, *next); + else + JoinOutrecPaths(*next, e); + e.join_with = JoinWith::Right; + next->join_with = JoinWith::Left; + } + + inline bool GetHorzExtendedHorzSeg(OutPt*& op, OutPt*& op2) + { + OutRec* outrec = GetRealOutRec(op->outrec); + op2 = op; + if (outrec->front_edge) + { + while (op->prev != outrec->pts && + op->prev->pt.y == op->pt.y) op = op->prev; + while (op2 != outrec->pts && + op2->next->pt.y == op2->pt.y) op2 = op2->next; + return op2 != op; + } + else + { + while (op->prev != op2 && op->prev->pt.y == op->pt.y) + op = op->prev; + while (op2->next != op && op2->next->pt.y == op2->pt.y) + op2 = op2->next; + return op2 != op && op2->next != op; + } + } + + bool BuildPath64(OutPt* op, bool reverse, bool isOpen, Path64& path) + { + if (!op || op->next == op || (!isOpen && op->next == op->prev)) + return false; + + path.resize(0); + Point64 lastPt; + OutPt* op2; + if (reverse) + { + lastPt = op->pt; + op2 = op->prev; + } + else + { + op = op->next; + lastPt = op->pt; + op2 = op->next; + } + path.push_back(lastPt); + + while (op2 != op) + { + if (op2->pt != lastPt) + { + lastPt = op2->pt; + path.push_back(lastPt); + } + if (reverse) + op2 = op2->prev; + else + op2 = op2->next; + } + + if (path.size() == 3 && IsVerySmallTriangle(*op2)) return false; + else return true; + } + + bool ClipperBase::CheckBounds(OutRec* outrec) + { + if (!outrec->pts) return false; + if (!outrec->bounds.IsEmpty()) return true; + CleanCollinear(outrec); + if (!outrec->pts || + !BuildPath64(outrec->pts, ReverseSolution, false, outrec->path)) + return false; + outrec->bounds = GetBounds(outrec->path); + return true; + } + + void ClipperBase::RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath) + { + // pre-condition: outrec will have valid bounds + // post-condition: if a valid path, outrec will have a polypath + + if (outrec->polypath || outrec->bounds.IsEmpty()) return; + + while (outrec->owner && + (!outrec->owner->pts || !CheckBounds(outrec->owner))) + outrec->owner = outrec->owner->owner; + + if (outrec->owner && !outrec->owner->polypath) + RecursiveCheckOwners(outrec->owner, polypath); + + while (outrec->owner) + if (outrec->owner->bounds.Contains(outrec->bounds) && + Path1InsidePath2(outrec->pts, outrec->owner->pts)) + break; // found - owner contain outrec! + else + outrec->owner = outrec->owner->owner; + + if (outrec->owner) + outrec->polypath = outrec->owner->polypath->AddChild(outrec->path); + else + outrec->polypath = polypath->AddChild(outrec->path); + } + + void ClipperBase::DeepCheckOwners(OutRec* outrec, PolyPath* polypath) + { + RecursiveCheckOwners(outrec, polypath); + + while (outrec->owner && outrec->owner->splits) + { + OutRec* split = nullptr; + for (auto s : *outrec->owner->splits) + { + split = GetRealOutRec(s); + if (split && split != outrec && + split != outrec->owner && CheckBounds(split) && + split->bounds.Contains(outrec->bounds) && + Path1InsidePath2(outrec->pts, split->pts)) + { + RecursiveCheckOwners(split, polypath); + outrec->owner = split; //found in split + break; // inner 'for' loop + } + else + split = nullptr; + } + if (!split) break; + } + } + + void Clipper64::BuildPaths64(Paths64& solutionClosed, Paths64* solutionOpen) + { + solutionClosed.resize(0); + solutionClosed.reserve(outrec_list_.size()); + if (solutionOpen) + { + solutionOpen->resize(0); + solutionOpen->reserve(outrec_list_.size()); + } + + // nb: outrec_list_.size() may change in the following + // while loop because polygons may be split during + // calls to CleanCollinear which calls FixSelfIntersects + for (size_t i = 0; i < outrec_list_.size(); ++i) + { + OutRec* outrec = outrec_list_[i]; + if (outrec->pts == nullptr) continue; + + Path64 path; + if (solutionOpen && outrec->is_open) + { + if (BuildPath64(outrec->pts, ReverseSolution, true, path)) + solutionOpen->emplace_back(std::move(path)); + } + else + { + // nb: CleanCollinear can add to outrec_list_ + CleanCollinear(outrec); + //closed paths should always return a Positive orientation + if (BuildPath64(outrec->pts, ReverseSolution, false, path)) + solutionClosed.emplace_back(std::move(path)); + } + } + } + + void Clipper64::BuildTree64(PolyPath64& polytree, Paths64& open_paths) + { + polytree.Clear(); + open_paths.resize(0); + if (has_open_paths_) + open_paths.reserve(outrec_list_.size()); + + // outrec_list_.size() is not static here because + // CheckBounds below can indirectly add additional + // OutRec (via FixOutRecPts & CleanCollinear) + for (size_t i = 0; i < outrec_list_.size(); ++i) + { + OutRec* outrec = outrec_list_[i]; + if (!outrec || !outrec->pts) continue; + if (outrec->is_open) + { + Path64 path; + if (BuildPath64(outrec->pts, ReverseSolution, true, path)) + open_paths.push_back(path); + continue; + } + + if (CheckBounds(outrec)) + DeepCheckOwners(outrec, &polytree); + } + } + + bool BuildPathD(OutPt* op, bool reverse, bool isOpen, PathD& path, double inv_scale) + { + if (!op || op->next == op || (!isOpen && op->next == op->prev)) + return false; + + path.resize(0); + Point64 lastPt; + OutPt* op2; + if (reverse) + { + lastPt = op->pt; + op2 = op->prev; + } + else + { + op = op->next; + lastPt = op->pt; + op2 = op->next; + } +#ifdef USINGZ + path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale, lastPt.z)); +#else + path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale)); +#endif + + while (op2 != op) + { + if (op2->pt != lastPt) + { + lastPt = op2->pt; +#ifdef USINGZ + path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale, lastPt.z)); +#else + path.push_back(PointD(lastPt.x * inv_scale, lastPt.y * inv_scale)); +#endif + + } + if (reverse) + op2 = op2->prev; + else + op2 = op2->next; + } + if (path.size() == 3 && IsVerySmallTriangle(*op2)) return false; + return true; + } + + void ClipperD::BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen) + { + solutionClosed.resize(0); + solutionClosed.reserve(outrec_list_.size()); + if (solutionOpen) + { + solutionOpen->resize(0); + solutionOpen->reserve(outrec_list_.size()); + } + + // outrec_list_.size() is not static here because + // CleanCollinear below can indirectly add additional + // OutRec (via FixOutRecPts) + for (std::size_t i = 0; i < outrec_list_.size(); ++i) + { + OutRec* outrec = outrec_list_[i]; + if (outrec->pts == nullptr) continue; + + PathD path; + if (solutionOpen && outrec->is_open) + { + if (BuildPathD(outrec->pts, ReverseSolution, true, path, invScale_)) + solutionOpen->emplace_back(std::move(path)); + } + else + { + CleanCollinear(outrec); + //closed paths should always return a Positive orientation + if (BuildPathD(outrec->pts, ReverseSolution, false, path, invScale_)) + solutionClosed.emplace_back(std::move(path)); + } + } + } + + void ClipperD::BuildTreeD(PolyPathD& polytree, PathsD& open_paths) + { + polytree.Clear(); + open_paths.resize(0); + if (has_open_paths_) + open_paths.reserve(outrec_list_.size()); + + for (OutRec* outrec : outrec_list_) + { + if (!outrec || !outrec->pts) continue; + if (outrec->is_open) + { + PathD path; + if (BuildPathD(outrec->pts, ReverseSolution, true, path, invScale_)) + open_paths.push_back(path); + continue; + } + + if (CheckBounds(outrec)) + DeepCheckOwners(outrec, &polytree); + } + } + +} // namespace clipper2lib diff --git a/thirdparty/clipper2/src/clipper.offset.cpp b/thirdparty/clipper2/src/clipper.offset.cpp new file mode 100644 index 00000000000..78cd82376ae --- /dev/null +++ b/thirdparty/clipper2/src/clipper.offset.cpp @@ -0,0 +1,618 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 22 March 2023 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2023 * +* Purpose : Path Offset (Inflate/Shrink) * +* License : http://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +#include +#include "clipper2/clipper.h" +#include "clipper2/clipper.offset.h" + +namespace Clipper2Lib { + +const double default_arc_tolerance = 0.25; +const double floating_point_tolerance = 1e-12; + +//------------------------------------------------------------------------------ +// Miscellaneous methods +//------------------------------------------------------------------------------ + +void GetBoundsAndLowestPolyIdx(const Paths64& paths, Rect64& r, int & idx) +{ + idx = -1; + r = MaxInvalidRect64; + int64_t lpx = 0; + for (int i = 0; i < static_cast(paths.size()); ++i) + for (const Point64& p : paths[i]) + { + if (p.y >= r.bottom) + { + if (p.y > r.bottom || p.x < lpx) + { + idx = i; + lpx = p.x; + r.bottom = p.y; + } + } + else if (p.y < r.top) r.top = p.y; + if (p.x > r.right) r.right = p.x; + else if (p.x < r.left) r.left = p.x; + } + //if (idx < 0) r = Rect64(0, 0, 0, 0); + //if (r.top == INT64_MIN) r.bottom = r.top; + //if (r.left == INT64_MIN) r.left = r.right; +} + +bool IsSafeOffset(const Rect64& r, double abs_delta) +{ + return r.left > min_coord + abs_delta && + r.right < max_coord - abs_delta && + r.top > min_coord + abs_delta && + r.bottom < max_coord - abs_delta; +} + +PointD GetUnitNormal(const Point64& pt1, const Point64& pt2) +{ + double dx, dy, inverse_hypot; + if (pt1 == pt2) return PointD(0.0, 0.0); + dx = static_cast(pt2.x - pt1.x); + dy = static_cast(pt2.y - pt1.y); + inverse_hypot = 1.0 / hypot(dx, dy); + dx *= inverse_hypot; + dy *= inverse_hypot; + return PointD(dy, -dx); +} + +inline bool AlmostZero(double value, double epsilon = 0.001) +{ + return std::fabs(value) < epsilon; +} + +inline double Hypot(double x, double y) +{ + //see https://stackoverflow.com/a/32436148/359538 + return std::sqrt(x * x + y * y); +} + +inline PointD NormalizeVector(const PointD& vec) +{ + + double h = Hypot(vec.x, vec.y); + if (AlmostZero(h)) return PointD(0,0); + double inverseHypot = 1 / h; + return PointD(vec.x * inverseHypot, vec.y * inverseHypot); +} + +inline PointD GetAvgUnitVector(const PointD& vec1, const PointD& vec2) +{ + return NormalizeVector(PointD(vec1.x + vec2.x, vec1.y + vec2.y)); +} + +inline bool IsClosedPath(EndType et) +{ + return et == EndType::Polygon || et == EndType::Joined; +} + +inline Point64 GetPerpendic(const Point64& pt, const PointD& norm, double delta) +{ +#ifdef USINGZ + return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta, pt.z); +#else + return Point64(pt.x + norm.x * delta, pt.y + norm.y * delta); +#endif +} + +inline PointD GetPerpendicD(const Point64& pt, const PointD& norm, double delta) +{ +#ifdef USINGZ + return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta, pt.z); +#else + return PointD(pt.x + norm.x * delta, pt.y + norm.y * delta); +#endif +} + +inline void NegatePath(PathD& path) +{ + for (PointD& pt : path) + { + pt.x = -pt.x; + pt.y = -pt.y; +#ifdef USINGZ + pt.z = pt.z; +#endif + } +} + +//------------------------------------------------------------------------------ +// ClipperOffset methods +//------------------------------------------------------------------------------ + +void ClipperOffset::AddPath(const Path64& path, JoinType jt_, EndType et_) +{ + Paths64 paths; + paths.push_back(path); + AddPaths(paths, jt_, et_); +} + +void ClipperOffset::AddPaths(const Paths64 &paths, JoinType jt_, EndType et_) +{ + if (paths.size() == 0) return; + groups_.push_back(Group(paths, jt_, et_)); +} + +void ClipperOffset::BuildNormals(const Path64& path) +{ + norms.clear(); + norms.reserve(path.size()); + if (path.size() == 0) return; + Path64::const_iterator path_iter, path_last_iter = --path.cend(); + for (path_iter = path.cbegin(); path_iter != path_last_iter; ++path_iter) + norms.push_back(GetUnitNormal(*path_iter,*(path_iter +1))); + norms.push_back(GetUnitNormal(*path_last_iter, *(path.cbegin()))); +} + +inline PointD TranslatePoint(const PointD& pt, double dx, double dy) +{ +#ifdef USINGZ + return PointD(pt.x + dx, pt.y + dy, pt.z); +#else + return PointD(pt.x + dx, pt.y + dy); +#endif +} + +inline PointD ReflectPoint(const PointD& pt, const PointD& pivot) +{ +#ifdef USINGZ + return PointD(pivot.x + (pivot.x - pt.x), pivot.y + (pivot.y - pt.y), pt.z); +#else + return PointD(pivot.x + (pivot.x - pt.x), pivot.y + (pivot.y - pt.y)); +#endif +} + +PointD IntersectPoint(const PointD& pt1a, const PointD& pt1b, + const PointD& pt2a, const PointD& pt2b) +{ + if (pt1a.x == pt1b.x) //vertical + { + if (pt2a.x == pt2b.x) return PointD(0, 0); + + double m2 = (pt2b.y - pt2a.y) / (pt2b.x - pt2a.x); + double b2 = pt2a.y - m2 * pt2a.x; + return PointD(pt1a.x, m2 * pt1a.x + b2); + } + else if (pt2a.x == pt2b.x) //vertical + { + double m1 = (pt1b.y - pt1a.y) / (pt1b.x - pt1a.x); + double b1 = pt1a.y - m1 * pt1a.x; + return PointD(pt2a.x, m1 * pt2a.x + b1); + } + else + { + double m1 = (pt1b.y - pt1a.y) / (pt1b.x - pt1a.x); + double b1 = pt1a.y - m1 * pt1a.x; + double m2 = (pt2b.y - pt2a.y) / (pt2b.x - pt2a.x); + double b2 = pt2a.y - m2 * pt2a.x; + if (m1 == m2) return PointD(0, 0); + double x = (b2 - b1) / (m1 - m2); + return PointD(x, m1 * x + b1); + } +} + +void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t k) +{ + PointD vec; + if (j == k) + vec = PointD(norms[0].y, -norms[0].x); + else + vec = GetAvgUnitVector( + PointD(-norms[k].y, norms[k].x), + PointD(norms[j].y, -norms[j].x)); + + // now offset the original vertex delta units along unit vector + PointD ptQ = PointD(path[j]); + ptQ = TranslatePoint(ptQ, abs_group_delta_ * vec.x, abs_group_delta_ * vec.y); + // get perpendicular vertices + PointD pt1 = TranslatePoint(ptQ, group_delta_ * vec.y, group_delta_ * -vec.x); + PointD pt2 = TranslatePoint(ptQ, group_delta_ * -vec.y, group_delta_ * vec.x); + // get 2 vertices along one edge offset + PointD pt3 = GetPerpendicD(path[k], norms[k], group_delta_); + if (j == k) + { + PointD pt4 = PointD(pt3.x + vec.x * group_delta_, pt3.y + vec.y * group_delta_); + PointD pt = IntersectPoint(pt1, pt2, pt3, pt4); +#ifdef USINGZ + pt.z = ptQ.z; +#endif + //get the second intersect point through reflecion + group.path.push_back(Point64(ReflectPoint(pt, ptQ))); + group.path.push_back(Point64(pt)); + } + else + { + PointD pt4 = GetPerpendicD(path[j], norms[k], group_delta_); + PointD pt = IntersectPoint(pt1, pt2, pt3, pt4); +#ifdef USINGZ + pt.z = ptQ.z; +#endif + group.path.push_back(Point64(pt)); + //get the second intersect point through reflecion + group.path.push_back(Point64(ReflectPoint(pt, ptQ))); + } +} + +void ClipperOffset::DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a) +{ + double q = group_delta_ / (cos_a + 1); +#ifdef USINGZ + group.path.push_back(Point64( + path[j].x + (norms[k].x + norms[j].x) * q, + path[j].y + (norms[k].y + norms[j].y) * q, + path[j].z)); +#else + group.path.push_back(Point64( + path[j].x + (norms[k].x + norms[j].x) * q, + path[j].y + (norms[k].y + norms[j].y) * q)); +#endif +} + +void ClipperOffset::DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle) +{ + Point64 pt = path[j]; + PointD offsetVec = PointD(norms[k].x * group_delta_, norms[k].y * group_delta_); + + if (j == k) offsetVec.Negate(); +#ifdef USINGZ + group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z)); +#else + group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y)); +#endif + if (angle > -PI + 0.01) // avoid 180deg concave + { + int steps = static_cast(std::ceil(steps_per_rad_ * std::abs(angle))); // #448, #456 + for (int i = 1; i < steps; ++i) // ie 1 less than steps + { + offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y, + offsetVec.x * step_sin_ + offsetVec.y * step_cos_); +#ifdef USINGZ + group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z)); +#else + group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y)); +#endif + + } + } + group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_)); +} + +void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t& k) +{ + // Let A = change in angle where edges join + // A == 0: ie no change in angle (flat join) + // A == PI: edges 'spike' + // sin(A) < 0: right turning + // cos(A) < 0: change in angle is more than 90 degree + + if (path[j] == path[k]) { k = j; return; } + + double sin_a = CrossProduct(norms[j], norms[k]); + double cos_a = DotProduct(norms[j], norms[k]); + if (sin_a > 1.0) sin_a = 1.0; + else if (sin_a < -1.0) sin_a = -1.0; + + if (cos_a > 0.99) // almost straight - less than 8 degrees + { + group.path.push_back(GetPerpendic(path[j], norms[k], group_delta_)); + if (cos_a < 0.9998) // greater than 1 degree (#424) + group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_)); // (#418) + } + else if (cos_a > -0.99 && (sin_a * group_delta_ < 0)) + { + // is concave + group.path.push_back(GetPerpendic(path[j], norms[k], group_delta_)); + // this extra point is the only (simple) way to ensure that + // path reversals are fully cleaned with the trailing clipper + group.path.push_back(path[j]); // (#405) + group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_)); + } + else if (join_type_ == JoinType::Round) + DoRound(group, path, j, k, std::atan2(sin_a, cos_a)); + else if (join_type_ == JoinType::Miter) + { + // miter unless the angle is so acute the miter would exceeds ML + if (cos_a > temp_lim_ - 1) DoMiter(group, path, j, k, cos_a); + else DoSquare(group, path, j, k); + } + // don't bother squaring angles that deviate < ~20 degrees because + // squaring will be indistinguishable from mitering and just be a lot slower + else if (cos_a > 0.9) + DoMiter(group, path, j, k, cos_a); + else + DoSquare(group, path, j, k); + + k = j; +} + +void ClipperOffset::OffsetPolygon(Group& group, Path64& path) +{ + for (Path64::size_type i = 0, j = path.size() -1; i < path.size(); j = i, ++i) + OffsetPoint(group, path, i, j); + group.paths_out.push_back(group.path); +} + +void ClipperOffset::OffsetOpenJoined(Group& group, Path64& path) +{ + OffsetPolygon(group, path); + std::reverse(path.begin(), path.end()); + + //rebuild normals // BuildNormals(path); + std::reverse(norms.begin(), norms.end()); + norms.push_back(norms[0]); + norms.erase(norms.begin()); + NegatePath(norms); + + group.path.clear(); + OffsetPolygon(group, path); +} + +void ClipperOffset::OffsetOpenPath(Group& group, Path64& path) +{ + // do the line start cap + switch (end_type_) + { + case EndType::Butt: +#ifdef USINGZ + group.path.push_back(Point64( + path[0].x - norms[0].x * group_delta_, + path[0].y - norms[0].y * group_delta_, + path[0].z)); +#else + group.path.push_back(Point64( + path[0].x - norms[0].x * group_delta_, + path[0].y - norms[0].y * group_delta_)); +#endif + group.path.push_back(GetPerpendic(path[0], norms[0], group_delta_)); + break; + case EndType::Round: + DoRound(group, path, 0,0, PI); + break; + default: + DoSquare(group, path, 0, 0); + break; + } + + size_t highI = path.size() - 1; + + // offset the left side going forward + for (Path64::size_type i = 1, k = 0; i < highI; ++i) + OffsetPoint(group, path, i, k); + + // reverse normals + for (size_t i = highI; i > 0; --i) + norms[i] = PointD(-norms[i - 1].x, -norms[i - 1].y); + norms[0] = norms[highI]; + + // do the line end cap + switch (end_type_) + { + case EndType::Butt: +#ifdef USINGZ + group.path.push_back(Point64( + path[highI].x - norms[highI].x * group_delta_, + path[highI].y - norms[highI].y * group_delta_, + path[highI].z)); +#else + group.path.push_back(Point64( + path[highI].x - norms[highI].x * group_delta_, + path[highI].y - norms[highI].y * group_delta_)); +#endif + group.path.push_back(GetPerpendic(path[highI], norms[highI], group_delta_)); + break; + case EndType::Round: + DoRound(group, path, highI, highI, PI); + break; + default: + DoSquare(group, path, highI, highI); + break; + } + + for (size_t i = highI, k = 0; i > 0; --i) + OffsetPoint(group, path, i, k); + group.paths_out.push_back(group.path); +} + +void ClipperOffset::DoGroupOffset(Group& group) +{ + Rect64 r; + int idx = -1; + //the lowermost polygon must be an outer polygon. So we can use that as the + //designated orientation for outer polygons (needed for tidy-up clipping) + GetBoundsAndLowestPolyIdx(group.paths_in, r, idx); + if (idx < 0) return; + + if (group.end_type == EndType::Polygon) + { + double area = Area(group.paths_in[idx]); + //if (area == 0) return; // probably unhelpful (#430) + group.is_reversed = (area < 0); + if (group.is_reversed) group_delta_ = -delta_; + else group_delta_ = delta_; + } + else + { + group.is_reversed = false; + group_delta_ = std::abs(delta_) * 0.5; + } + abs_group_delta_ = std::fabs(group_delta_); + + // do range checking + if (!IsSafeOffset(r, abs_group_delta_)) + { + DoError(range_error_i); + error_code_ |= range_error_i; + return; + } + + join_type_ = group.join_type; + end_type_ = group.end_type; + + //calculate a sensible number of steps (for 360 deg for the given offset + if (group.join_type == JoinType::Round || group.end_type == EndType::Round) + { + // arcTol - when arc_tolerance_ is undefined (0), the amount of + // curve imprecision that's allowed is based on the size of the + // offset (delta). Obviously very large offsets will almost always + // require much less precision. See also offset_triginometry2.svg + double arcTol = (arc_tolerance_ > floating_point_tolerance ? + std::min(abs_group_delta_, arc_tolerance_) : + std::log10(2 + abs_group_delta_) * default_arc_tolerance); + double steps_per_360 = PI / std::acos(1 - arcTol / abs_group_delta_); + if (steps_per_360 > abs_group_delta_ * PI) + steps_per_360 = abs_group_delta_ * PI; //ie avoids excessive precision + + step_sin_ = std::sin(2 * PI / steps_per_360); + step_cos_ = std::cos(2 * PI / steps_per_360); + if (group_delta_ < 0.0) step_sin_ = -step_sin_; + steps_per_rad_ = steps_per_360 / (2 *PI); + } + + bool is_joined = + (end_type_ == EndType::Polygon) || + (end_type_ == EndType::Joined); + Paths64::const_iterator path_iter; + for(path_iter = group.paths_in.cbegin(); path_iter != group.paths_in.cend(); ++path_iter) + { + Path64 path = StripDuplicates(*path_iter, is_joined); + Path64::size_type cnt = path.size(); + if (cnt == 0 || ((cnt < 3) && group.end_type == EndType::Polygon)) + continue; + + group.path.clear(); + if (cnt == 1) // single point - only valid with open paths + { + if (group_delta_ < 1) continue; + //single vertex so build a circle or square ... + if (group.join_type == JoinType::Round) + { + double radius = abs_group_delta_; + group.path = Ellipse(path[0], radius, radius); +#ifdef USINGZ + for (auto& p : group.path) p.z = path[0].z; +#endif + } + else + { + int d = (int)std::ceil(abs_group_delta_); + r = Rect64(path[0].x - d, path[0].y - d, path[0].x + d, path[0].y + d); + group.path = r.AsPath(); +#ifdef USINGZ + for (auto& p : group.path) p.z = path[0].z; +#endif + } + group.paths_out.push_back(group.path); + } + else + { + if ((cnt == 2) && (group.end_type == EndType::Joined)) + { + if (group.join_type == JoinType::Round) + end_type_ = EndType::Round; + else + end_type_ = EndType::Square; + } + + BuildNormals(path); + if (end_type_ == EndType::Polygon) OffsetPolygon(group, path); + else if (end_type_ == EndType::Joined) OffsetOpenJoined(group, path); + else OffsetOpenPath(group, path); + } + } + solution.reserve(solution.size() + group.paths_out.size()); + copy(group.paths_out.begin(), group.paths_out.end(), back_inserter(solution)); + group.paths_out.clear(); +} + +void ClipperOffset::ExecuteInternal(double delta) +{ + error_code_ = 0; + solution.clear(); + if (groups_.size() == 0) return; + + if (std::abs(delta) < 0.5) + { + for (const Group& group : groups_) + { + solution.reserve(solution.size() + group.paths_in.size()); + copy(group.paths_in.begin(), group.paths_in.end(), back_inserter(solution)); + } + } + else + { + temp_lim_ = (miter_limit_ <= 1) ? + 2.0 : + 2.0 / (miter_limit_ * miter_limit_); + + delta_ = delta; + std::vector::iterator git; + for (git = groups_.begin(); git != groups_.end(); ++git) + { + DoGroupOffset(*git); + if (!error_code_) continue; // all OK + solution.clear(); + } + } +} + +void ClipperOffset::Execute(double delta, Paths64& paths) +{ + paths.clear(); + + ExecuteInternal(delta); + if (!solution.size()) return; + + paths = solution; + //clean up self-intersections ... + Clipper64 c; + c.PreserveCollinear = false; + //the solution should retain the orientation of the input + c.ReverseSolution = reverse_solution_ != groups_[0].is_reversed; +#ifdef USINGZ + if (zCallback64_) { + c.SetZCallback(zCallback64_); + } +#endif + c.AddSubject(solution); + if (groups_[0].is_reversed) + c.Execute(ClipType::Union, FillRule::Negative, paths); + else + c.Execute(ClipType::Union, FillRule::Positive, paths); +} + + +void ClipperOffset::Execute(double delta, PolyTree64& polytree) +{ + polytree.Clear(); + + ExecuteInternal(delta); + if (!solution.size()) return; + + //clean up self-intersections ... + Clipper64 c; + c.PreserveCollinear = false; + //the solution should retain the orientation of the input + c.ReverseSolution = reverse_solution_ != groups_[0].is_reversed; +#ifdef USINGZ + if (zCallback64_) { + c.SetZCallback(zCallback64_); + } +#endif + c.AddSubject(solution); + if (groups_[0].is_reversed) + c.Execute(ClipType::Union, FillRule::Negative, polytree); + else + c.Execute(ClipType::Union, FillRule::Positive, polytree); +} + +} // namespace diff --git a/thirdparty/clipper2/src/clipper.rectclip.cpp b/thirdparty/clipper2/src/clipper.rectclip.cpp new file mode 100644 index 00000000000..959972b4404 --- /dev/null +++ b/thirdparty/clipper2/src/clipper.rectclip.cpp @@ -0,0 +1,976 @@ +/******************************************************************************* +* Author : Angus Johnson * +* Date : 14 February 2023 * +* Website : http://www.angusj.com * +* Copyright : Angus Johnson 2010-2023 * +* Purpose : FAST rectangular clipping * +* License : http://www.boost.org/LICENSE_1_0.txt * +*******************************************************************************/ + +#include +#include "clipper2/clipper.h" +#include "clipper2/clipper.rectclip.h" + +namespace Clipper2Lib { + + //------------------------------------------------------------------------------ + // Miscellaneous methods + //------------------------------------------------------------------------------ + + inline bool Path1ContainsPath2(const Path64& path1, const Path64& path2) + { + int io_count = 0; + // precondition: no (significant) overlap + for (const Point64& pt : path2) + { + PointInPolygonResult pip = PointInPolygon(pt, path1); + switch (pip) + { + case PointInPolygonResult::IsOutside: ++io_count; break; + case PointInPolygonResult::IsInside: --io_count; break; + default: continue; + } + if (std::abs(io_count) > 1) break; + } + return io_count <= 0; + } + + inline bool GetLocation(const Rect64& rec, + const Point64& pt, Location& loc) + { + if (pt.x == rec.left && pt.y >= rec.top && pt.y <= rec.bottom) + { + loc = Location::Left; + return false; + } + else if (pt.x == rec.right && pt.y >= rec.top && pt.y <= rec.bottom) + { + loc = Location::Right; + return false; + } + else if (pt.y == rec.top && pt.x >= rec.left && pt.x <= rec.right) + { + loc = Location::Top; + return false; + } + else if (pt.y == rec.bottom && pt.x >= rec.left && pt.x <= rec.right) + { + loc = Location::Bottom; + return false; + } + else if (pt.x < rec.left) loc = Location::Left; + else if (pt.x > rec.right) loc = Location::Right; + else if (pt.y < rec.top) loc = Location::Top; + else if (pt.y > rec.bottom) loc = Location::Bottom; + else loc = Location::Inside; + return true; + } + + inline bool GetIntersection(const Path64& rectPath, + const Point64& p, const Point64& p2, Location& loc, Point64& ip) + { + // gets the intersection closest to 'p' + // when Result = false, loc will remain unchanged + switch (loc) + { + case Location::Left: + if (SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true)) + GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip); + else if (p.y < rectPath[0].y && + SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true)) + { + GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip); + loc = Location::Top; + } + else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true)) + { + GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip); + loc = Location::Bottom; + } + else return false; + break; + + case Location::Top: + if (SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true)) + GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip); + else if (p.x < rectPath[0].x && + SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true)) + { + GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip); + loc = Location::Left; + } + else if (p.x > rectPath[1].x && + SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true)) + { + GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip); + loc = Location::Right; + } + else return false; + break; + + case Location::Right: + if (SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true)) + GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip); + else if (p.y < rectPath[0].y && + SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true)) + { + GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip); + loc = Location::Top; + } + else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true)) + { + GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip); + loc = Location::Bottom; + } + else return false; + break; + + case Location::Bottom: + if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true)) + GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip); + else if (p.x < rectPath[3].x && + SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true)) + { + GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip); + loc = Location::Left; + } + else if (p.x > rectPath[2].x && + SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true)) + { + GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip); + loc = Location::Right; + } + else return false; + break; + + default: // loc == rInside + if (SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true)) + { + GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip); + loc = Location::Left; + } + else if (SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true)) + { + GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip); + loc = Location::Top; + } + else if (SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true)) + { + GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip); + loc = Location::Right; + } + else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true)) + { + GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip); + loc = Location::Bottom; + } + else return false; + break; + } + return true; + } + + inline Location GetAdjacentLocation(Location loc, bool isClockwise) + { + int delta = (isClockwise) ? 1 : 3; + return static_cast((static_cast(loc) + delta) % 4); + } + + inline bool HeadingClockwise(Location prev, Location curr) + { + return (static_cast(prev) + 1) % 4 == static_cast(curr); + } + + inline bool AreOpposites(Location prev, Location curr) + { + return abs(static_cast(prev) - static_cast(curr)) == 2; + } + + inline bool IsClockwise(Location prev, Location curr, + const Point64& prev_pt, const Point64& curr_pt, const Point64& rect_mp) + { + if (AreOpposites(prev, curr)) + return CrossProduct(prev_pt, rect_mp, curr_pt) < 0; + else + return HeadingClockwise(prev, curr); + } + + inline OutPt2* UnlinkOp(OutPt2* op) + { + if (op->next == op) return nullptr; + op->prev->next = op->next; + op->next->prev = op->prev; + return op->next; + } + + inline OutPt2* UnlinkOpBack(OutPt2* op) + { + if (op->next == op) return nullptr; + op->prev->next = op->next; + op->next->prev = op->prev; + return op->prev; + } + + inline uint32_t GetEdgesForPt(const Point64& pt, const Rect64& rec) + { + uint32_t result = 0; + if (pt.x == rec.left) result = 1; + else if (pt.x == rec.right) result = 4; + if (pt.y == rec.top) result += 2; + else if (pt.y == rec.bottom) result += 8; + return result; + } + + inline bool IsHeadingClockwise(const Point64& pt1, const Point64& pt2, int edgeIdx) + { + switch (edgeIdx) + { + case 0: return pt2.y < pt1.y; + case 1: return pt2.x > pt1.x; + case 2: return pt2.y > pt1.y; + default: return pt2.x < pt1.x; + } + } + + inline bool HasHorzOverlap(const Point64& left1, const Point64& right1, + const Point64& left2, const Point64& right2) + { + return (left1.x < right2.x) && (right1.x > left2.x); + } + + inline bool HasVertOverlap(const Point64& top1, const Point64& bottom1, + const Point64& top2, const Point64& bottom2) + { + return (top1.y < bottom2.y) && (bottom1.y > top2.y); + } + + inline void AddToEdge(OutPt2List& edge, OutPt2* op) + { + if (op->edge) return; + op->edge = &edge; + edge.push_back(op); + } + + inline void UncoupleEdge(OutPt2* op) + { + if (!op->edge) return; + for (size_t i = 0; i < op->edge->size(); ++i) + { + OutPt2* op2 = (*op->edge)[i]; + if (op2 == op) + { + (*op->edge)[i] = nullptr; + break; + } + } + op->edge = nullptr; + } + + inline void SetNewOwner(OutPt2* op, size_t new_idx) + { + op->owner_idx = new_idx; + OutPt2* op2 = op->next; + while (op2 != op) + { + op2->owner_idx = new_idx; + op2 = op2->next; + } + } + + //---------------------------------------------------------------------------- + // RectClip64 + //---------------------------------------------------------------------------- + + OutPt2* RectClip::Add(Point64 pt, bool start_new) + { + // this method is only called by InternalExecute. + // Later splitting & rejoining won't create additional op's, + // though they will change the (non-storage) results_ count. + int curr_idx = static_cast(results_.size()) - 1; + OutPt2* result; + if (curr_idx < 0 || start_new) + { + result = &op_container_.emplace_back(OutPt2()); + result->pt = pt; + result->next = result; + result->prev = result; + results_.push_back(result); + } + else + { + OutPt2* prevOp = results_[curr_idx]; + if (prevOp->pt == pt) return prevOp; + result = &op_container_.emplace_back(OutPt2()); + result->owner_idx = curr_idx; + result->pt = pt; + result->next = prevOp->next; + prevOp->next->prev = result; + prevOp->next = result; + result->prev = prevOp; + results_[curr_idx] = result; + } + return result; + } + + void RectClip::AddCorner(Location prev, Location curr) + { + if (HeadingClockwise(prev, curr)) + Add(rect_as_path_[static_cast(prev)]); + else + Add(rect_as_path_[static_cast(curr)]); + } + + void RectClip::AddCorner(Location& loc, bool isClockwise) + { + if (isClockwise) + { + Add(rect_as_path_[static_cast(loc)]); + loc = GetAdjacentLocation(loc, true); + } + else + { + loc = GetAdjacentLocation(loc, false); + Add(rect_as_path_[static_cast(loc)]); + } + } + + void RectClip::GetNextLocation(const Path64& path, + Location& loc, int& i, int highI) + { + switch (loc) + { + case Location::Left: + while (i <= highI && path[i].x <= rect_.left) ++i; + if (i > highI) break; + else if (path[i].x >= rect_.right) loc = Location::Right; + else if (path[i].y <= rect_.top) loc = Location::Top; + else if (path[i].y >= rect_.bottom) loc = Location::Bottom; + else loc = Location::Inside; + break; + + case Location::Top: + while (i <= highI && path[i].y <= rect_.top) ++i; + if (i > highI) break; + else if (path[i].y >= rect_.bottom) loc = Location::Bottom; + else if (path[i].x <= rect_.left) loc = Location::Left; + else if (path[i].x >= rect_.right) loc = Location::Right; + else loc = Location::Inside; + break; + + case Location::Right: + while (i <= highI && path[i].x >= rect_.right) ++i; + if (i > highI) break; + else if (path[i].x <= rect_.left) loc = Location::Left; + else if (path[i].y <= rect_.top) loc = Location::Top; + else if (path[i].y >= rect_.bottom) loc = Location::Bottom; + else loc = Location::Inside; + break; + + case Location::Bottom: + while (i <= highI && path[i].y >= rect_.bottom) ++i; + if (i > highI) break; + else if (path[i].y <= rect_.top) loc = Location::Top; + else if (path[i].x <= rect_.left) loc = Location::Left; + else if (path[i].x >= rect_.right) loc = Location::Right; + else loc = Location::Inside; + break; + + case Location::Inside: + while (i <= highI) + { + if (path[i].x < rect_.left) loc = Location::Left; + else if (path[i].x > rect_.right) loc = Location::Right; + else if (path[i].y > rect_.bottom) loc = Location::Bottom; + else if (path[i].y < rect_.top) loc = Location::Top; + else { Add(path[i]); ++i; continue; } + break; //inner loop + } + break; + } //switch + } + + void RectClip::ExecuteInternal(const Path64& path) + { + int i = 0, highI = static_cast(path.size()) - 1; + Location prev = Location::Inside, loc; + Location crossing_loc = Location::Inside; + Location first_cross_ = Location::Inside; + if (!GetLocation(rect_, path[highI], loc)) + { + i = highI - 1; + while (i >= 0 && !GetLocation(rect_, path[i], prev)) --i; + if (i < 0) + { + // all of path must be inside fRect + for (const auto& pt : path) Add(pt); + return; + } + if (prev == Location::Inside) loc = Location::Inside; + i = 0; + } + Location startingLoc = loc; + + /////////////////////////////////////////////////// + while (i <= highI) + { + prev = loc; + Location crossing_prev = crossing_loc; + + GetNextLocation(path, loc, i, highI); + + if (i > highI) break; + Point64 ip, ip2; + Point64 prev_pt = (i) ? + path[static_cast(i - 1)] : + path[highI]; + + crossing_loc = loc; + if (!GetIntersection(rect_as_path_, + path[i], prev_pt, crossing_loc, ip)) + { + // ie remaining outside + if (crossing_prev == Location::Inside) + { + bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], rect_mp_); + do { + start_locs_.push_back(prev); + prev = GetAdjacentLocation(prev, isClockw); + } while (prev != loc); + crossing_loc = crossing_prev; // still not crossed + } + else if (prev != Location::Inside && prev != loc) + { + bool isClockw = IsClockwise(prev, loc, prev_pt, path[i], rect_mp_); + do { + AddCorner(prev, isClockw); + } while (prev != loc); + } + ++i; + continue; + } + + //////////////////////////////////////////////////// + // we must be crossing the rect boundary to get here + //////////////////////////////////////////////////// + + if (loc == Location::Inside) // path must be entering rect + { + if (first_cross_ == Location::Inside) + { + first_cross_ = crossing_loc; + start_locs_.push_back(prev); + } + else if (prev != crossing_loc) + { + bool isClockw = IsClockwise(prev, crossing_loc, prev_pt, path[i], rect_mp_); + do { + AddCorner(prev, isClockw); + } while (prev != crossing_loc); + } + } + else if (prev != Location::Inside) + { + // passing right through rect. 'ip' here will be the second + // intersect pt but we'll also need the first intersect pt (ip2) + loc = prev; + GetIntersection(rect_as_path_, prev_pt, path[i], loc, ip2); + if (crossing_prev != Location::Inside) + AddCorner(crossing_prev, loc); + + if (first_cross_ == Location::Inside) + { + first_cross_ = loc; + start_locs_.push_back(prev); + } + + loc = crossing_loc; + Add(ip2); + if (ip == ip2) + { + // it's very likely that path[i] is on rect + GetLocation(rect_, path[i], loc); + AddCorner(crossing_loc, loc); + crossing_loc = loc; + continue; + } + } + else // path must be exiting rect + { + loc = crossing_loc; + if (first_cross_ == Location::Inside) + first_cross_ = crossing_loc; + } + + Add(ip); + + } //while i <= highI + /////////////////////////////////////////////////// + + if (first_cross_ == Location::Inside) + { + // path never intersects + if (startingLoc != Location::Inside) + { + // path is outside rect + // but being outside, it still may not contain rect + if (path_bounds_.Contains(rect_) && + Path1ContainsPath2(path, rect_as_path_)) + { + // yep, the path does fully contain rect + // so add rect to the solution + for (size_t j = 0; j < 4; ++j) + { + Add(rect_as_path_[j]); + // we may well need to do some splitting later, so + AddToEdge(edges_[j * 2], results_[0]); + } + } + } + } + else if (loc != Location::Inside && + (loc != first_cross_ || start_locs_.size() > 2)) + { + if (start_locs_.size() > 0) + { + prev = loc; + for (auto loc2 : start_locs_) + { + if (prev == loc2) continue; + AddCorner(prev, HeadingClockwise(prev, loc2)); + prev = loc2; + } + loc = prev; + } + if (loc != first_cross_) + AddCorner(loc, HeadingClockwise(loc, first_cross_)); + } + } + + void RectClip::CheckEdges() + { + for (size_t i = 0; i < results_.size(); ++i) + { + OutPt2* op = results_[i]; + if (!op) continue; + OutPt2* op2 = op; + do + { + if (!CrossProduct(op2->prev->pt, + op2->pt, op2->next->pt)) + { + if (op2 == op) + { + op2 = UnlinkOpBack(op2); + if (!op2) break; + op = op2->prev; + } + else + { + op2 = UnlinkOpBack(op2); + if (!op2) break; + } + } + else + op2 = op2->next; + } while (op2 != op); + + if (!op2) + { + results_[i] = nullptr; + continue; + } + results_[i] = op; // safety first + + uint32_t edgeSet1 = GetEdgesForPt(op->prev->pt, rect_); + op2 = op; + do + { + uint32_t edgeSet2 = GetEdgesForPt(op2->pt, rect_); + if (edgeSet2 && !op2->edge) + { + uint32_t combinedSet = (edgeSet1 & edgeSet2); + for (int j = 0; j < 4; ++j) + { + if (combinedSet & (1 << j)) + { + if (IsHeadingClockwise(op2->prev->pt, op2->pt, j)) + AddToEdge(edges_[j * 2], op2); + else + AddToEdge(edges_[j * 2 + 1], op2); + } + } + } + edgeSet1 = edgeSet2; + op2 = op2->next; + } while (op2 != op); + } + } + + void RectClip::TidyEdges(int idx, OutPt2List& cw, OutPt2List& ccw) + { + if (ccw.empty()) return; + bool isHorz = ((idx == 1) || (idx == 3)); + bool cwIsTowardLarger = ((idx == 1) || (idx == 2)); + size_t i = 0, j = 0; + OutPt2* p1, * p2, * p1a, * p2a, * op, * op2; + + while (i < cw.size()) + { + p1 = cw[i]; + if (!p1 || p1->next == p1->prev) + { + cw[i++]->edge = nullptr; + j = 0; + continue; + } + + size_t jLim = ccw.size(); + while (j < jLim && + (!ccw[j] || ccw[j]->next == ccw[j]->prev)) ++j; + + if (j == jLim) + { + ++i; + j = 0; + continue; + } + + if (cwIsTowardLarger) + { + // p1 >>>> p1a; + // p2 <<<< p2a; + p1 = cw[i]->prev; + p1a = cw[i]; + p2 = ccw[j]; + p2a = ccw[j]->prev; + } + else + { + // p1 <<<< p1a; + // p2 >>>> p2a; + p1 = cw[i]; + p1a = cw[i]->prev; + p2 = ccw[j]->prev; + p2a = ccw[j]; + } + + if ((isHorz && !HasHorzOverlap(p1->pt, p1a->pt, p2->pt, p2a->pt)) || + (!isHorz && !HasVertOverlap(p1->pt, p1a->pt, p2->pt, p2a->pt))) + { + ++j; + continue; + } + + // to get here we're either splitting or rejoining + bool isRejoining = cw[i]->owner_idx != ccw[j]->owner_idx; + + if (isRejoining) + { + results_[p2->owner_idx] = nullptr; + SetNewOwner(p2, p1->owner_idx); + } + + // do the split or re-join + if (cwIsTowardLarger) + { + // p1 >> | >> p1a; + // p2 << | << p2a; + p1->next = p2; + p2->prev = p1; + p1a->prev = p2a; + p2a->next = p1a; + } + else + { + // p1 << | << p1a; + // p2 >> | >> p2a; + p1->prev = p2; + p2->next = p1; + p1a->next = p2a; + p2a->prev = p1a; + } + + if (!isRejoining) + { + size_t new_idx = results_.size(); + results_.push_back(p1a); + SetNewOwner(p1a, new_idx); + } + + if (cwIsTowardLarger) + { + op = p2; + op2 = p1a; + } + else + { + op = p1; + op2 = p2a; + } + results_[op->owner_idx] = op; + results_[op2->owner_idx] = op2; + + // and now lots of work to get ready for the next loop + + bool opIsLarger, op2IsLarger; + if (isHorz) // X + { + opIsLarger = op->pt.x > op->prev->pt.x; + op2IsLarger = op2->pt.x > op2->prev->pt.x; + } + else // Y + { + opIsLarger = op->pt.y > op->prev->pt.y; + op2IsLarger = op2->pt.y > op2->prev->pt.y; + } + + if ((op->next == op->prev) || + (op->pt == op->prev->pt)) + { + if (op2IsLarger == cwIsTowardLarger) + { + cw[i] = op2; + ccw[j++] = nullptr; + } + else + { + ccw[j] = op2; + cw[i++] = nullptr; + } + } + else if ((op2->next == op2->prev) || + (op2->pt == op2->prev->pt)) + { + if (opIsLarger == cwIsTowardLarger) + { + cw[i] = op; + ccw[j++] = nullptr; + } + else + { + ccw[j] = op; + cw[i++] = nullptr; + } + } + else if (opIsLarger == op2IsLarger) + { + if (opIsLarger == cwIsTowardLarger) + { + cw[i] = op; + UncoupleEdge(op2); + AddToEdge(cw, op2); + ccw[j++] = nullptr; + } + else + { + cw[i++] = nullptr; + ccw[j] = op2; + UncoupleEdge(op); + AddToEdge(ccw, op); + j = 0; + } + } + else + { + if (opIsLarger == cwIsTowardLarger) + cw[i] = op; + else + ccw[j] = op; + if (op2IsLarger == cwIsTowardLarger) + cw[i] = op2; + else + ccw[j] = op2; + } + } + } + + Path64 RectClip::GetPath(OutPt2*& op) + { + if (!op || op->next == op->prev) return Path64(); + + OutPt2* op2 = op->next; + while (op2 && op2 != op) + { + if (CrossProduct(op2->prev->pt, + op2->pt, op2->next->pt) == 0) + { + op = op2->prev; + op2 = UnlinkOp(op2); + } + else + op2 = op2->next; + } + op = op2; // needed for op cleanup + if (!op2) return Path64(); + + Path64 result; + result.push_back(op->pt); + op2 = op->next; + while (op2 != op) + { + result.push_back(op2->pt); + op2 = op2->next; + } + return result; + } + + Paths64 RectClip::Execute(const Paths64& paths, bool convex_only) + { + Paths64 result; + if (rect_.IsEmpty()) return result; + + for (const auto& path : paths) + { + if (path.size() < 3) continue; + path_bounds_ = GetBounds(path); + if (!rect_.Intersects(path_bounds_)) + continue; // the path must be completely outside rect_ + else if (rect_.Contains(path_bounds_)) + { + // the path must be completely inside rect_ + result.push_back(path); + continue; + } + + ExecuteInternal(path); + if (!convex_only) + { + CheckEdges(); + for (int i = 0; i < 4; ++i) + TidyEdges(i, edges_[i * 2], edges_[i * 2 + 1]); + } + + for (OutPt2*& op : results_) + { + Path64 tmp = GetPath(op); + if (!tmp.empty()) + result.emplace_back(tmp); + } + + //clean up after every loop + op_container_ = std::deque(); + results_.clear(); + for (OutPt2List edge : edges_) edge.clear(); + start_locs_.clear(); + } + return result; + } + + //------------------------------------------------------------------------------ + // RectClipLines + //------------------------------------------------------------------------------ + + Paths64 RectClipLines::Execute(const Paths64& paths) + { + Paths64 result; + if (rect_.IsEmpty()) return result; + + for (const auto& path : paths) + { + if (path.size() < 2) continue; + Rect64 pathrec = GetBounds(path); + + if (!rect_.Intersects(pathrec)) continue; + + ExecuteInternal(path); + + for (OutPt2*& op : results_) + { + Path64 tmp = GetPath(op); + if (!tmp.empty()) + result.emplace_back(tmp); + } + results_.clear(); + + op_container_ = std::deque(); + start_locs_.clear(); + } + return result; + } + + void RectClipLines::ExecuteInternal(const Path64& path) + { + if (rect_.IsEmpty() || path.size() < 2) return; + + results_.clear(); + op_container_ = std::deque(); + start_locs_.clear(); + + int i = 1, highI = static_cast(path.size()) - 1; + + Location prev = Location::Inside, loc; + Location crossing_loc; + if (!GetLocation(rect_, path[0], loc)) + { + while (i <= highI && !GetLocation(rect_, path[i], prev)) ++i; + if (i > highI) + { + // all of path must be inside fRect + for (const auto& pt : path) Add(pt); + return; + } + if (prev == Location::Inside) loc = Location::Inside; + i = 1; + } + if (loc == Location::Inside) Add(path[0]); + + /////////////////////////////////////////////////// + while (i <= highI) + { + prev = loc; + GetNextLocation(path, loc, i, highI); + if (i > highI) break; + Point64 ip, ip2; + Point64 prev_pt = path[static_cast(i - 1)]; + + crossing_loc = loc; + if (!GetIntersection(rect_as_path_, + path[i], prev_pt, crossing_loc, ip)) + { + // ie remaining outside + ++i; + continue; + } + + //////////////////////////////////////////////////// + // we must be crossing the rect boundary to get here + //////////////////////////////////////////////////// + + if (loc == Location::Inside) // path must be entering rect + { + Add(ip, true); + } + else if (prev != Location::Inside) + { + // passing right through rect. 'ip' here will be the second + // intersect pt but we'll also need the first intersect pt (ip2) + crossing_loc = prev; + GetIntersection(rect_as_path_, + prev_pt, path[i], crossing_loc, ip2); + Add(ip2, true); + Add(ip); + } + else // path must be exiting rect + { + Add(ip); + } + } //while i <= highI + /////////////////////////////////////////////////// + } + + Path64 RectClipLines::GetPath(OutPt2*& op) + { + Path64 result; + if (!op || op == op->next) return result; + op = op->next; // starting at path beginning + result.push_back(op->pt); + OutPt2 *op2 = op->next; + while (op2 != op) + { + result.push_back(op2->pt); + op2 = op2->next; + } + return result; + } + +} // namespace