From 1cb531ddb6fedb5bed432eeaa17793a9e07f61ac Mon Sep 17 00:00:00 2001 From: Aaron Franke Date: Tue, 16 Mar 2021 02:27:53 -0400 Subject: Move 2D-only resources to their own folder --- .../plugins/collision_shape_2d_editor_plugin.cpp | 16 +- editor/plugins/tiles/atlas_merging_dialog.h | 2 +- editor/plugins/tiles/tile_atlas_view.h | 2 +- .../plugins/tiles/tile_set_atlas_source_editor.h | 2 +- editor/plugins/tiles/tile_set_editor.h | 2 +- .../tile_set_scenes_collection_source_editor.h | 2 +- editor/plugins/tiles/tiles_editor_plugin.cpp | 2 +- modules/navigation/2d/nav_mesh_generator_2d.cpp | 10 +- scene/2d/collision_object_2d.h | 2 +- scene/2d/collision_polygon_2d.cpp | 4 +- scene/2d/collision_shape_2d.cpp | 4 +- scene/2d/collision_shape_2d.h | 2 +- scene/2d/shape_cast_2d.cpp | 2 +- scene/2d/shape_cast_2d.h | 2 +- scene/2d/skeleton_2d.h | 2 +- scene/2d/tile_map.h | 2 +- scene/2d/tile_map_layer.h | 2 +- scene/2d/tile_map_layer_group.cpp | 2 +- scene/2d/touch_screen_button.h | 2 +- scene/register_scene_types.cpp | 38 +- scene/resources/2d/SCsub | 6 + scene/resources/2d/capsule_shape_2d.cpp | 124 + scene/resources/2d/capsule_shape_2d.h | 64 + scene/resources/2d/circle_shape_2d.cpp | 95 + scene/resources/2d/circle_shape_2d.h | 58 + scene/resources/2d/concave_polygon_shape_2d.cpp | 119 + scene/resources/2d/concave_polygon_shape_2d.h | 55 + scene/resources/2d/convex_polygon_shape_2d.cpp | 143 + scene/resources/2d/convex_polygon_shape_2d.h | 59 + scene/resources/2d/polygon_path_finder.cpp | 563 ++ scene/resources/2d/polygon_path_finder.h | 98 + scene/resources/2d/rectangle_shape_2d.cpp | 107 + scene/resources/2d/rectangle_shape_2d.h | 60 + scene/resources/2d/segment_shape_2d.cpp | 100 + scene/resources/2d/segment_shape_2d.h | 63 + scene/resources/2d/separation_ray_shape_2d.cpp | 117 + scene/resources/2d/separation_ray_shape_2d.h | 61 + scene/resources/2d/shape_2d.cpp | 129 + scene/resources/2d/shape_2d.h | 70 + .../2d/skeleton/skeleton_modification_2d.cpp | 239 + .../2d/skeleton/skeleton_modification_2d.h | 89 + .../2d/skeleton/skeleton_modification_2d_ccdik.cpp | 549 ++ .../2d/skeleton/skeleton_modification_2d_ccdik.h | 116 + .../skeleton/skeleton_modification_2d_fabrik.cpp | 452 ++ .../2d/skeleton/skeleton_modification_2d_fabrik.h | 108 + .../skeleton/skeleton_modification_2d_jiggle.cpp | 572 ++ .../2d/skeleton/skeleton_modification_2d_jiggle.h | 139 + .../skeleton/skeleton_modification_2d_lookat.cpp | 411 ++ .../2d/skeleton/skeleton_modification_2d_lookat.h | 100 + .../skeleton_modification_2d_physicalbones.cpp | 297 + .../skeleton_modification_2d_physicalbones.h | 82 + .../skeleton_modification_2d_stackholder.cpp | 135 + .../skeleton_modification_2d_stackholder.h | 64 + .../skeleton_modification_2d_twoboneik.cpp | 485 ++ .../skeleton/skeleton_modification_2d_twoboneik.h | 107 + .../2d/skeleton/skeleton_modification_stack_2d.cpp | 270 + .../2d/skeleton/skeleton_modification_stack_2d.h | 99 + scene/resources/2d/tile_set.compat.inc | 48 + scene/resources/2d/tile_set.cpp | 6927 +++++++++++++++++++ scene/resources/2d/tile_set.h | 987 +++ scene/resources/2d/world_boundary_shape_2d.cpp | 126 + scene/resources/2d/world_boundary_shape_2d.h | 64 + scene/resources/SCsub | 2 + scene/resources/capsule_shape_2d.cpp | 124 - scene/resources/capsule_shape_2d.h | 64 - scene/resources/circle_shape_2d.cpp | 95 - scene/resources/circle_shape_2d.h | 58 - scene/resources/concave_polygon_shape_2d.cpp | 119 - scene/resources/concave_polygon_shape_2d.h | 55 - scene/resources/convex_polygon_shape_2d.cpp | 143 - scene/resources/convex_polygon_shape_2d.h | 59 - scene/resources/polygon_path_finder.cpp | 563 -- scene/resources/polygon_path_finder.h | 98 - scene/resources/rectangle_shape_2d.cpp | 107 - scene/resources/rectangle_shape_2d.h | 60 - scene/resources/segment_shape_2d.cpp | 100 - scene/resources/segment_shape_2d.h | 63 - scene/resources/separation_ray_shape_2d.cpp | 117 - scene/resources/separation_ray_shape_2d.h | 61 - scene/resources/shape_2d.cpp | 129 - scene/resources/shape_2d.h | 70 - scene/resources/skeleton_modification_2d.cpp | 239 - scene/resources/skeleton_modification_2d.h | 89 - scene/resources/skeleton_modification_2d_ccdik.cpp | 549 -- scene/resources/skeleton_modification_2d_ccdik.h | 116 - .../resources/skeleton_modification_2d_fabrik.cpp | 452 -- scene/resources/skeleton_modification_2d_fabrik.h | 108 - .../resources/skeleton_modification_2d_jiggle.cpp | 572 -- scene/resources/skeleton_modification_2d_jiggle.h | 139 - .../resources/skeleton_modification_2d_lookat.cpp | 411 -- scene/resources/skeleton_modification_2d_lookat.h | 100 - .../skeleton_modification_2d_physicalbones.cpp | 297 - .../skeleton_modification_2d_physicalbones.h | 82 - .../skeleton_modification_2d_stackholder.cpp | 135 - .../skeleton_modification_2d_stackholder.h | 64 - .../skeleton_modification_2d_twoboneik.cpp | 485 -- .../resources/skeleton_modification_2d_twoboneik.h | 107 - scene/resources/skeleton_modification_stack_2d.cpp | 270 - scene/resources/skeleton_modification_stack_2d.h | 99 - scene/resources/tile_set.compat.inc | 48 - scene/resources/tile_set.cpp | 6928 -------------------- scene/resources/tile_set.h | 989 --- scene/resources/world_boundary_shape_2d.cpp | 126 - scene/resources/world_boundary_shape_2d.h | 64 - scu_builders.py | 1 + tests/scene/test_viewport.h | 2 +- 106 files changed, 14612 insertions(+), 14606 deletions(-) create mode 100644 scene/resources/2d/SCsub create mode 100644 scene/resources/2d/capsule_shape_2d.cpp create mode 100644 scene/resources/2d/capsule_shape_2d.h create mode 100644 scene/resources/2d/circle_shape_2d.cpp create mode 100644 scene/resources/2d/circle_shape_2d.h create mode 100644 scene/resources/2d/concave_polygon_shape_2d.cpp create mode 100644 scene/resources/2d/concave_polygon_shape_2d.h create mode 100644 scene/resources/2d/convex_polygon_shape_2d.cpp create mode 100644 scene/resources/2d/convex_polygon_shape_2d.h create mode 100644 scene/resources/2d/polygon_path_finder.cpp create mode 100644 scene/resources/2d/polygon_path_finder.h create mode 100644 scene/resources/2d/rectangle_shape_2d.cpp create mode 100644 scene/resources/2d/rectangle_shape_2d.h create mode 100644 scene/resources/2d/segment_shape_2d.cpp create mode 100644 scene/resources/2d/segment_shape_2d.h create mode 100644 scene/resources/2d/separation_ray_shape_2d.cpp create mode 100644 scene/resources/2d/separation_ray_shape_2d.h create mode 100644 scene/resources/2d/shape_2d.cpp create mode 100644 scene/resources/2d/shape_2d.h create mode 100644 scene/resources/2d/skeleton/skeleton_modification_2d.cpp create mode 100644 scene/resources/2d/skeleton/skeleton_modification_2d.h create mode 100644 scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.cpp create mode 100644 scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.h create mode 100644 scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.cpp create mode 100644 scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.h create mode 100644 scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.cpp create mode 100644 scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.h create mode 100644 scene/resources/2d/skeleton/skeleton_modification_2d_lookat.cpp create mode 100644 scene/resources/2d/skeleton/skeleton_modification_2d_lookat.h create mode 100644 scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp create mode 100644 scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.h create mode 100644 scene/resources/2d/skeleton/skeleton_modification_2d_stackholder.cpp create mode 100644 scene/resources/2d/skeleton/skeleton_modification_2d_stackholder.h create mode 100644 scene/resources/2d/skeleton/skeleton_modification_2d_twoboneik.cpp create mode 100644 scene/resources/2d/skeleton/skeleton_modification_2d_twoboneik.h create mode 100644 scene/resources/2d/skeleton/skeleton_modification_stack_2d.cpp create mode 100644 scene/resources/2d/skeleton/skeleton_modification_stack_2d.h create mode 100644 scene/resources/2d/tile_set.compat.inc create mode 100644 scene/resources/2d/tile_set.cpp create mode 100644 scene/resources/2d/tile_set.h create mode 100644 scene/resources/2d/world_boundary_shape_2d.cpp create mode 100644 scene/resources/2d/world_boundary_shape_2d.h delete mode 100644 scene/resources/capsule_shape_2d.cpp delete mode 100644 scene/resources/capsule_shape_2d.h delete mode 100644 scene/resources/circle_shape_2d.cpp delete mode 100644 scene/resources/circle_shape_2d.h delete mode 100644 scene/resources/concave_polygon_shape_2d.cpp delete mode 100644 scene/resources/concave_polygon_shape_2d.h delete mode 100644 scene/resources/convex_polygon_shape_2d.cpp delete mode 100644 scene/resources/convex_polygon_shape_2d.h delete mode 100644 scene/resources/polygon_path_finder.cpp delete mode 100644 scene/resources/polygon_path_finder.h delete mode 100644 scene/resources/rectangle_shape_2d.cpp delete mode 100644 scene/resources/rectangle_shape_2d.h delete mode 100644 scene/resources/segment_shape_2d.cpp delete mode 100644 scene/resources/segment_shape_2d.h delete mode 100644 scene/resources/separation_ray_shape_2d.cpp delete mode 100644 scene/resources/separation_ray_shape_2d.h delete mode 100644 scene/resources/shape_2d.cpp delete mode 100644 scene/resources/shape_2d.h delete mode 100644 scene/resources/skeleton_modification_2d.cpp delete mode 100644 scene/resources/skeleton_modification_2d.h delete mode 100644 scene/resources/skeleton_modification_2d_ccdik.cpp delete mode 100644 scene/resources/skeleton_modification_2d_ccdik.h delete mode 100644 scene/resources/skeleton_modification_2d_fabrik.cpp delete mode 100644 scene/resources/skeleton_modification_2d_fabrik.h delete mode 100644 scene/resources/skeleton_modification_2d_jiggle.cpp delete mode 100644 scene/resources/skeleton_modification_2d_jiggle.h delete mode 100644 scene/resources/skeleton_modification_2d_lookat.cpp delete mode 100644 scene/resources/skeleton_modification_2d_lookat.h delete mode 100644 scene/resources/skeleton_modification_2d_physicalbones.cpp delete mode 100644 scene/resources/skeleton_modification_2d_physicalbones.h delete mode 100644 scene/resources/skeleton_modification_2d_stackholder.cpp delete mode 100644 scene/resources/skeleton_modification_2d_stackholder.h delete mode 100644 scene/resources/skeleton_modification_2d_twoboneik.cpp delete mode 100644 scene/resources/skeleton_modification_2d_twoboneik.h delete mode 100644 scene/resources/skeleton_modification_stack_2d.cpp delete mode 100644 scene/resources/skeleton_modification_stack_2d.h delete mode 100644 scene/resources/tile_set.compat.inc delete mode 100644 scene/resources/tile_set.cpp delete mode 100644 scene/resources/tile_set.h delete mode 100644 scene/resources/world_boundary_shape_2d.cpp delete mode 100644 scene/resources/world_boundary_shape_2d.h diff --git a/editor/plugins/collision_shape_2d_editor_plugin.cpp b/editor/plugins/collision_shape_2d_editor_plugin.cpp index 3191858657..12dde46193 100644 --- a/editor/plugins/collision_shape_2d_editor_plugin.cpp +++ b/editor/plugins/collision_shape_2d_editor_plugin.cpp @@ -35,14 +35,14 @@ #include "editor/editor_node.h" #include "editor/editor_settings.h" #include "editor/editor_undo_redo_manager.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/rectangle_shape_2d.h" -#include "scene/resources/segment_shape_2d.h" -#include "scene/resources/separation_ray_shape_2d.h" -#include "scene/resources/world_boundary_shape_2d.h" +#include "scene/resources/2d/capsule_shape_2d.h" +#include "scene/resources/2d/circle_shape_2d.h" +#include "scene/resources/2d/concave_polygon_shape_2d.h" +#include "scene/resources/2d/convex_polygon_shape_2d.h" +#include "scene/resources/2d/rectangle_shape_2d.h" +#include "scene/resources/2d/segment_shape_2d.h" +#include "scene/resources/2d/separation_ray_shape_2d.h" +#include "scene/resources/2d/world_boundary_shape_2d.h" #include "scene/scene_string_names.h" CollisionShape2DEditor::CollisionShape2DEditor() { diff --git a/editor/plugins/tiles/atlas_merging_dialog.h b/editor/plugins/tiles/atlas_merging_dialog.h index 324cc82490..2517db2ccd 100644 --- a/editor/plugins/tiles/atlas_merging_dialog.h +++ b/editor/plugins/tiles/atlas_merging_dialog.h @@ -35,7 +35,7 @@ #include "scene/gui/dialogs.h" #include "scene/gui/item_list.h" #include "scene/gui/texture_rect.h" -#include "scene/resources/tile_set.h" +#include "scene/resources/2d/tile_set.h" class EditorFileDialog; class EditorPropertyVector2i; diff --git a/editor/plugins/tiles/tile_atlas_view.h b/editor/plugins/tiles/tile_atlas_view.h index e5b088af61..e5b4863b05 100644 --- a/editor/plugins/tiles/tile_atlas_view.h +++ b/editor/plugins/tiles/tile_atlas_view.h @@ -37,7 +37,7 @@ #include "scene/gui/center_container.h" #include "scene/gui/label.h" #include "scene/gui/margin_container.h" -#include "scene/resources/tile_set.h" +#include "scene/resources/2d/tile_set.h" class ViewPanner; diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.h b/editor/plugins/tiles/tile_set_atlas_source_editor.h index b98705baf1..322de81045 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.h +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.h @@ -35,7 +35,7 @@ #include "tile_data_editors.h" #include "scene/gui/split_container.h" -#include "scene/resources/tile_set.h" +#include "scene/resources/2d/tile_set.h" class Popup; class TileSet; diff --git a/editor/plugins/tiles/tile_set_editor.h b/editor/plugins/tiles/tile_set_editor.h index 755b940db2..68ab046ecf 100644 --- a/editor/plugins/tiles/tile_set_editor.h +++ b/editor/plugins/tiles/tile_set_editor.h @@ -33,7 +33,7 @@ #include "atlas_merging_dialog.h" #include "scene/gui/tab_bar.h" -#include "scene/resources/tile_set.h" +#include "scene/resources/2d/tile_set.h" #include "tile_proxies_manager_dialog.h" #include "tile_set_atlas_source_editor.h" #include "tile_set_scenes_collection_source_editor.h" diff --git a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h index 82de063a07..5527685b24 100644 --- a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h +++ b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h @@ -33,7 +33,7 @@ #include "editor/editor_inspector.h" #include "scene/gui/box_container.h" -#include "scene/resources/tile_set.h" +#include "scene/resources/2d/tile_set.h" class Button; class ItemList; diff --git a/editor/plugins/tiles/tiles_editor_plugin.cpp b/editor/plugins/tiles/tiles_editor_plugin.cpp index 46bc072c18..e7bf812a6c 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.cpp +++ b/editor/plugins/tiles/tiles_editor_plugin.cpp @@ -47,8 +47,8 @@ #include "scene/gui/button.h" #include "scene/gui/control.h" #include "scene/gui/separator.h" +#include "scene/resources/2d/tile_set.h" #include "scene/resources/image_texture.h" -#include "scene/resources/tile_set.h" TilesEditorUtils *TilesEditorUtils::singleton = nullptr; TileMapEditorPlugin *tile_map_plugin_singleton = nullptr; diff --git a/modules/navigation/2d/nav_mesh_generator_2d.cpp b/modules/navigation/2d/nav_mesh_generator_2d.cpp index 836dee8178..9fdfb20842 100644 --- a/modules/navigation/2d/nav_mesh_generator_2d.cpp +++ b/modules/navigation/2d/nav_mesh_generator_2d.cpp @@ -38,13 +38,13 @@ #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/2d/capsule_shape_2d.h" +#include "scene/resources/2d/circle_shape_2d.h" +#include "scene/resources/2d/concave_polygon_shape_2d.h" +#include "scene/resources/2d/convex_polygon_shape_2d.h" +#include "scene/resources/2d/rectangle_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" diff --git a/scene/2d/collision_object_2d.h b/scene/2d/collision_object_2d.h index 780793f289..2c8534665c 100644 --- a/scene/2d/collision_object_2d.h +++ b/scene/2d/collision_object_2d.h @@ -33,7 +33,7 @@ #include "scene/2d/node_2d.h" #include "scene/main/viewport.h" -#include "scene/resources/shape_2d.h" +#include "scene/resources/2d/shape_2d.h" #include "servers/physics_server_2d.h" class CollisionObject2D : public Node2D { diff --git a/scene/2d/collision_polygon_2d.cpp b/scene/2d/collision_polygon_2d.cpp index d2f71eca9d..3bc5bce8f0 100644 --- a/scene/2d/collision_polygon_2d.cpp +++ b/scene/2d/collision_polygon_2d.cpp @@ -33,8 +33,8 @@ #include "collision_object_2d.h" #include "core/math/geometry_2d.h" #include "scene/2d/area_2d.h" -#include "scene/resources/concave_polygon_shape_2d.h" -#include "scene/resources/convex_polygon_shape_2d.h" +#include "scene/resources/2d/concave_polygon_shape_2d.h" +#include "scene/resources/2d/convex_polygon_shape_2d.h" #include "thirdparty/misc/polypartition.h" diff --git a/scene/2d/collision_shape_2d.cpp b/scene/2d/collision_shape_2d.cpp index ee413c7bc2..5539e49135 100644 --- a/scene/2d/collision_shape_2d.cpp +++ b/scene/2d/collision_shape_2d.cpp @@ -32,8 +32,8 @@ #include "collision_object_2d.h" #include "scene/2d/area_2d.h" -#include "scene/resources/concave_polygon_shape_2d.h" -#include "scene/resources/convex_polygon_shape_2d.h" +#include "scene/resources/2d/concave_polygon_shape_2d.h" +#include "scene/resources/2d/convex_polygon_shape_2d.h" void CollisionShape2D::_shape_changed() { queue_redraw(); diff --git a/scene/2d/collision_shape_2d.h b/scene/2d/collision_shape_2d.h index 3e13dd698c..65436f1539 100644 --- a/scene/2d/collision_shape_2d.h +++ b/scene/2d/collision_shape_2d.h @@ -32,7 +32,7 @@ #define COLLISION_SHAPE_2D_H #include "scene/2d/node_2d.h" -#include "scene/resources/shape_2d.h" +#include "scene/resources/2d/shape_2d.h" class CollisionObject2D; diff --git a/scene/2d/shape_cast_2d.cpp b/scene/2d/shape_cast_2d.cpp index 90d80d7549..38e3b3180b 100644 --- a/scene/2d/shape_cast_2d.cpp +++ b/scene/2d/shape_cast_2d.cpp @@ -33,7 +33,7 @@ #include "core/config/engine.h" #include "scene/2d/collision_object_2d.h" #include "scene/2d/physics_body_2d.h" -#include "scene/resources/circle_shape_2d.h" +#include "scene/resources/2d/circle_shape_2d.h" #include "servers/physics_2d/godot_physics_server_2d.h" void ShapeCast2D::set_target_position(const Vector2 &p_point) { diff --git a/scene/2d/shape_cast_2d.h b/scene/2d/shape_cast_2d.h index a577c351fd..6b8fd5b798 100644 --- a/scene/2d/shape_cast_2d.h +++ b/scene/2d/shape_cast_2d.h @@ -32,7 +32,7 @@ #define SHAPE_CAST_2D_H #include "scene/2d/node_2d.h" -#include "scene/resources/shape_2d.h" +#include "scene/resources/2d/shape_2d.h" #include "scene/resources/world_2d.h" class CollisionObject2D; diff --git a/scene/2d/skeleton_2d.h b/scene/2d/skeleton_2d.h index 6a36a31552..ad6a47bf43 100644 --- a/scene/2d/skeleton_2d.h +++ b/scene/2d/skeleton_2d.h @@ -32,7 +32,7 @@ #define SKELETON_2D_H #include "scene/2d/node_2d.h" -#include "scene/resources/skeleton_modification_2d.h" +#include "scene/resources/2d/skeleton/skeleton_modification_2d.h" class Skeleton2D; diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 16750625e5..c9844f29af 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -32,7 +32,7 @@ #define TILE_MAP_H #include "scene/2d/tile_map_layer_group.h" -#include "scene/resources/tile_set.h" +#include "scene/resources/2d/tile_set.h" class Control; class TileMapLayer; diff --git a/scene/2d/tile_map_layer.h b/scene/2d/tile_map_layer.h index ac03f3155f..5a0c51d7e5 100644 --- a/scene/2d/tile_map_layer.h +++ b/scene/2d/tile_map_layer.h @@ -32,7 +32,7 @@ #define TILE_MAP_LAYER_H #include "scene/2d/tile_map.h" -#include "scene/resources/tile_set.h" +#include "scene/resources/2d/tile_set.h" class TileSetAtlasSource; diff --git a/scene/2d/tile_map_layer_group.cpp b/scene/2d/tile_map_layer_group.cpp index ac0de380a3..c596534474 100644 --- a/scene/2d/tile_map_layer_group.cpp +++ b/scene/2d/tile_map_layer_group.cpp @@ -32,7 +32,7 @@ #include "core/core_string_names.h" #include "scene/2d/tile_map_layer.h" -#include "scene/resources/tile_set.h" +#include "scene/resources/2d/tile_set.h" #ifdef TOOLS_ENABLED diff --git a/scene/2d/touch_screen_button.h b/scene/2d/touch_screen_button.h index c7dc0a323e..4467604e2b 100644 --- a/scene/2d/touch_screen_button.h +++ b/scene/2d/touch_screen_button.h @@ -32,8 +32,8 @@ #define TOUCH_SCREEN_BUTTON_H #include "scene/2d/node_2d.h" +#include "scene/resources/2d/rectangle_shape_2d.h" #include "scene/resources/bit_map.h" -#include "scene/resources/rectangle_shape_2d.h" #include "scene/resources/texture.h" class TouchScreenButton : public Node2D { diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 035c6c0695..77a331f52c 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -139,6 +139,25 @@ #include "scene/main/timer.h" #include "scene/main/viewport.h" #include "scene/main/window.h" +#include "scene/resources/2d/capsule_shape_2d.h" +#include "scene/resources/2d/circle_shape_2d.h" +#include "scene/resources/2d/concave_polygon_shape_2d.h" +#include "scene/resources/2d/convex_polygon_shape_2d.h" +#include "scene/resources/2d/polygon_path_finder.h" +#include "scene/resources/2d/rectangle_shape_2d.h" +#include "scene/resources/2d/segment_shape_2d.h" +#include "scene/resources/2d/separation_ray_shape_2d.h" +#include "scene/resources/2d/skeleton/skeleton_modification_2d.h" +#include "scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.h" +#include "scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.h" +#include "scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.h" +#include "scene/resources/2d/skeleton/skeleton_modification_2d_lookat.h" +#include "scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.h" +#include "scene/resources/2d/skeleton/skeleton_modification_2d_stackholder.h" +#include "scene/resources/2d/skeleton/skeleton_modification_2d_twoboneik.h" +#include "scene/resources/2d/skeleton/skeleton_modification_stack_2d.h" +#include "scene/resources/2d/tile_set.h" +#include "scene/resources/2d/world_boundary_shape_2d.h" #include "scene/resources/animated_texture.h" #include "scene/resources/animation_library.h" #include "scene/resources/atlas_texture.h" @@ -148,12 +167,8 @@ #include "scene/resources/bone_map.h" #include "scene/resources/camera_attributes.h" #include "scene/resources/camera_texture.h" -#include "scene/resources/capsule_shape_2d.h" -#include "scene/resources/circle_shape_2d.h" #include "scene/resources/compositor.h" #include "scene/resources/compressed_texture.h" -#include "scene/resources/concave_polygon_shape_2d.h" -#include "scene/resources/convex_polygon_shape_2d.h" #include "scene/resources/curve_texture.h" #include "scene/resources/environment.h" #include "scene/resources/font.h" @@ -174,22 +189,9 @@ #include "scene/resources/particle_process_material.h" #include "scene/resources/physics_material.h" #include "scene/resources/placeholder_textures.h" -#include "scene/resources/polygon_path_finder.h" #include "scene/resources/portable_compressed_texture.h" -#include "scene/resources/rectangle_shape_2d.h" #include "scene/resources/resource_format_text.h" -#include "scene/resources/segment_shape_2d.h" -#include "scene/resources/separation_ray_shape_2d.h" #include "scene/resources/shader_include.h" -#include "scene/resources/skeleton_modification_2d.h" -#include "scene/resources/skeleton_modification_2d_ccdik.h" -#include "scene/resources/skeleton_modification_2d_fabrik.h" -#include "scene/resources/skeleton_modification_2d_jiggle.h" -#include "scene/resources/skeleton_modification_2d_lookat.h" -#include "scene/resources/skeleton_modification_2d_physicalbones.h" -#include "scene/resources/skeleton_modification_2d_stackholder.h" -#include "scene/resources/skeleton_modification_2d_twoboneik.h" -#include "scene/resources/skeleton_modification_stack_2d.h" #include "scene/resources/skeleton_profile.h" #include "scene/resources/sky.h" #include "scene/resources/sky_material.h" @@ -205,14 +207,12 @@ #include "scene/resources/texture.h" #include "scene/resources/texture_rd.h" #include "scene/resources/theme.h" -#include "scene/resources/tile_set.h" #include "scene/resources/video_stream.h" #include "scene/resources/visual_shader.h" #include "scene/resources/visual_shader_nodes.h" #include "scene/resources/visual_shader_particle_nodes.h" #include "scene/resources/visual_shader_sdf_nodes.h" #include "scene/resources/world_2d.h" -#include "scene/resources/world_boundary_shape_2d.h" #include "scene/scene_string_names.h" #include "scene/theme/theme_db.h" diff --git a/scene/resources/2d/SCsub b/scene/resources/2d/SCsub new file mode 100644 index 0000000000..fdf20e0bde --- /dev/null +++ b/scene/resources/2d/SCsub @@ -0,0 +1,6 @@ +#!/usr/bin/env python + +Import("env") + +env.add_source_files(env.scene_sources, "*.cpp") +env.add_source_files(env.scene_sources, "skeleton/*.cpp") diff --git a/scene/resources/2d/capsule_shape_2d.cpp b/scene/resources/2d/capsule_shape_2d.cpp new file mode 100644 index 0000000000..8268040ed9 --- /dev/null +++ b/scene/resources/2d/capsule_shape_2d.cpp @@ -0,0 +1,124 @@ +/**************************************************************************/ +/* capsule_shape_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 "capsule_shape_2d.h" + +#include "core/math/geometry_2d.h" +#include "servers/physics_server_2d.h" +#include "servers/rendering_server.h" + +Vector CapsuleShape2D::_get_points() const { + Vector points; + const real_t turn_step = Math_TAU / 24.0; + for (int i = 0; i < 24; i++) { + Vector2 ofs = Vector2(0, (i > 6 && i <= 18) ? -height * 0.5 + radius : height * 0.5 - radius); + + points.push_back(Vector2(Math::sin(i * turn_step), Math::cos(i * turn_step)) * radius + ofs); + if (i == 6 || i == 18) { + points.push_back(Vector2(Math::sin(i * turn_step), Math::cos(i * turn_step)) * radius - ofs); + } + } + + return points; +} + +bool CapsuleShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { + return Geometry2D::is_point_in_polygon(p_point, _get_points()); +} + +void CapsuleShape2D::_update_shape() { + PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), Vector2(radius, height)); + emit_changed(); +} + +void CapsuleShape2D::set_radius(real_t p_radius) { + ERR_FAIL_COND_MSG(p_radius < 0, "CapsuleShape2D radius cannot be negative."); + radius = p_radius; + if (radius > height * 0.5) { + height = radius * 2.0; + } + _update_shape(); +} + +real_t CapsuleShape2D::get_radius() const { + return radius; +} + +void CapsuleShape2D::set_height(real_t p_height) { + ERR_FAIL_COND_MSG(p_height < 0, "CapsuleShape2D height cannot be negative."); + height = p_height; + if (radius > height * 0.5) { + radius = height * 0.5; + } + _update_shape(); +} + +real_t CapsuleShape2D::get_height() const { + return height; +} + +void CapsuleShape2D::draw(const RID &p_to_rid, const Color &p_color) { + Vector points = _get_points(); + Vector col = { p_color }; + RenderingServer::get_singleton()->canvas_item_add_polygon(p_to_rid, points, col); + + if (is_collision_outline_enabled()) { + points.push_back(points[0]); + col = { Color(p_color, 1.0) }; + RenderingServer::get_singleton()->canvas_item_add_polyline(p_to_rid, points, col); + } +} + +Rect2 CapsuleShape2D::get_rect() const { + const Vector2 half_size = Vector2(radius, height * 0.5); + return Rect2(-half_size, half_size * 2.0); +} + +real_t CapsuleShape2D::get_enclosing_radius() const { + return height * 0.5; +} + +void CapsuleShape2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CapsuleShape2D::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &CapsuleShape2D::get_radius); + + ClassDB::bind_method(D_METHOD("set_height", "height"), &CapsuleShape2D::set_height); + ClassDB::bind_method(D_METHOD("get_height"), &CapsuleShape2D::get_height); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:px"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:px"), "set_height", "get_height"); + ADD_LINKED_PROPERTY("radius", "height"); + ADD_LINKED_PROPERTY("height", "radius"); +} + +CapsuleShape2D::CapsuleShape2D() : + Shape2D(PhysicsServer2D::get_singleton()->capsule_shape_create()) { + _update_shape(); +} diff --git a/scene/resources/2d/capsule_shape_2d.h b/scene/resources/2d/capsule_shape_2d.h new file mode 100644 index 0000000000..cfd3fc6b26 --- /dev/null +++ b/scene/resources/2d/capsule_shape_2d.h @@ -0,0 +1,64 @@ +/**************************************************************************/ +/* capsule_shape_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 CAPSULE_SHAPE_2D_H +#define CAPSULE_SHAPE_2D_H + +#include "scene/resources/2d/shape_2d.h" + +class CapsuleShape2D : public Shape2D { + GDCLASS(CapsuleShape2D, Shape2D); + + real_t height = 30.0; + real_t radius = 10.0; + + void _update_shape(); + Vector _get_points() const; + +protected: + static void _bind_methods(); + +public: + virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override; + + void set_height(real_t p_height); + real_t get_height() const; + + void set_radius(real_t p_radius); + real_t get_radius() const; + + virtual void draw(const RID &p_to_rid, const Color &p_color) override; + virtual Rect2 get_rect() const override; + virtual real_t get_enclosing_radius() const override; + + CapsuleShape2D(); +}; + +#endif // CAPSULE_SHAPE_2D_H diff --git a/scene/resources/2d/circle_shape_2d.cpp b/scene/resources/2d/circle_shape_2d.cpp new file mode 100644 index 0000000000..0b207c33ca --- /dev/null +++ b/scene/resources/2d/circle_shape_2d.cpp @@ -0,0 +1,95 @@ +/**************************************************************************/ +/* circle_shape_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 "circle_shape_2d.h" + +#include "servers/physics_server_2d.h" +#include "servers/rendering_server.h" + +bool CircleShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { + return p_point.length() < get_radius() + p_tolerance; +} + +void CircleShape2D::_update_shape() { + PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), radius); + emit_changed(); +} + +void CircleShape2D::set_radius(real_t p_radius) { + ERR_FAIL_COND_MSG(p_radius < 0, "CircleShape2D radius cannot be negative."); + radius = p_radius; + _update_shape(); +} + +real_t CircleShape2D::get_radius() const { + return radius; +} + +void CircleShape2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CircleShape2D::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &CircleShape2D::get_radius); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:px"), "set_radius", "get_radius"); +} + +Rect2 CircleShape2D::get_rect() const { + Rect2 rect; + rect.position = -Point2(get_radius(), get_radius()); + rect.size = Point2(get_radius(), get_radius()) * 2.0; + return rect; +} + +real_t CircleShape2D::get_enclosing_radius() const { + return radius; +} + +void CircleShape2D::draw(const RID &p_to_rid, const Color &p_color) { + Vector points; + points.resize(24); + + const real_t turn_step = Math_TAU / 24.0; + for (int i = 0; i < 24; i++) { + points.write[i] = Vector2(Math::cos(i * turn_step), Math::sin(i * turn_step)) * get_radius(); + } + + Vector col = { p_color }; + RenderingServer::get_singleton()->canvas_item_add_polygon(p_to_rid, points, col); + + if (is_collision_outline_enabled()) { + points.push_back(points[0]); + col = { Color(p_color, 1.0) }; + RenderingServer::get_singleton()->canvas_item_add_polyline(p_to_rid, points, col); + } +} + +CircleShape2D::CircleShape2D() : + Shape2D(PhysicsServer2D::get_singleton()->circle_shape_create()) { + _update_shape(); +} diff --git a/scene/resources/2d/circle_shape_2d.h b/scene/resources/2d/circle_shape_2d.h new file mode 100644 index 0000000000..203a8240eb --- /dev/null +++ b/scene/resources/2d/circle_shape_2d.h @@ -0,0 +1,58 @@ +/**************************************************************************/ +/* circle_shape_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 CIRCLE_SHAPE_2D_H +#define CIRCLE_SHAPE_2D_H + +#include "scene/resources/2d/shape_2d.h" + +class CircleShape2D : public Shape2D { + GDCLASS(CircleShape2D, Shape2D); + + real_t radius = 10.0; + void _update_shape(); + +protected: + static void _bind_methods(); + +public: + virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override; + + void set_radius(real_t p_radius); + real_t get_radius() const; + + virtual void draw(const RID &p_to_rid, const Color &p_color) override; + virtual Rect2 get_rect() const override; + virtual real_t get_enclosing_radius() const override; + + CircleShape2D(); +}; + +#endif // CIRCLE_SHAPE_2D_H diff --git a/scene/resources/2d/concave_polygon_shape_2d.cpp b/scene/resources/2d/concave_polygon_shape_2d.cpp new file mode 100644 index 0000000000..56734a8f0b --- /dev/null +++ b/scene/resources/2d/concave_polygon_shape_2d.cpp @@ -0,0 +1,119 @@ +/**************************************************************************/ +/* concave_polygon_shape_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 "concave_polygon_shape_2d.h" + +#include "core/math/geometry_2d.h" +#include "servers/physics_server_2d.h" +#include "servers/rendering_server.h" + +bool ConcavePolygonShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { + Vector s = get_segments(); + int len = s.size(); + if (len == 0 || (len % 2) == 1) { + return false; + } + + const Vector2 *r = s.ptr(); + for (int i = 0; i < len; i += 2) { + Vector2 closest = Geometry2D::get_closest_point_to_segment(p_point, &r[i]); + if (p_point.distance_to(closest) < p_tolerance) { + return true; + } + } + + return false; +} + +void ConcavePolygonShape2D::set_segments(const Vector &p_segments) { + PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), p_segments); + emit_changed(); +} + +Vector ConcavePolygonShape2D::get_segments() const { + return PhysicsServer2D::get_singleton()->shape_get_data(get_rid()); +} + +void ConcavePolygonShape2D::draw(const RID &p_to_rid, const Color &p_color) { + Vector s = get_segments(); + int len = s.size(); + if (len == 0 || (len % 2) == 1) { + return; + } + + const Vector2 *r = s.ptr(); + for (int i = 0; i < len; i += 2) { + RenderingServer::get_singleton()->canvas_item_add_line(p_to_rid, r[i], r[i + 1], p_color, 2); + } +} + +Rect2 ConcavePolygonShape2D::get_rect() const { + Vector s = get_segments(); + int len = s.size(); + if (len == 0) { + return Rect2(); + } + + Rect2 rect; + + const Vector2 *r = s.ptr(); + for (int i = 0; i < len; i++) { + if (i == 0) { + rect.position = r[i]; + } else { + rect.expand_to(r[i]); + } + } + + return rect; +} + +real_t ConcavePolygonShape2D::get_enclosing_radius() const { + Vector data = get_segments(); + const Vector2 *read = data.ptr(); + real_t r = 0.0; + for (int i(0); i < data.size(); i++) { + r = MAX(read[i].length_squared(), r); + } + return Math::sqrt(r); +} + +void ConcavePolygonShape2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_segments", "segments"), &ConcavePolygonShape2D::set_segments); + ClassDB::bind_method(D_METHOD("get_segments"), &ConcavePolygonShape2D::get_segments); + + ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "segments"), "set_segments", "get_segments"); +} + +ConcavePolygonShape2D::ConcavePolygonShape2D() : + Shape2D(PhysicsServer2D::get_singleton()->concave_polygon_shape_create()) { + Vector empty; + set_segments(empty); +} diff --git a/scene/resources/2d/concave_polygon_shape_2d.h b/scene/resources/2d/concave_polygon_shape_2d.h new file mode 100644 index 0000000000..8b0b77acaa --- /dev/null +++ b/scene/resources/2d/concave_polygon_shape_2d.h @@ -0,0 +1,55 @@ +/**************************************************************************/ +/* concave_polygon_shape_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 CONCAVE_POLYGON_SHAPE_2D_H +#define CONCAVE_POLYGON_SHAPE_2D_H + +#include "scene/resources/2d/shape_2d.h" + +class ConcavePolygonShape2D : public Shape2D { + GDCLASS(ConcavePolygonShape2D, Shape2D); + +protected: + static void _bind_methods(); + +public: + virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override; + + void set_segments(const Vector &p_segments); + Vector get_segments() const; + + virtual void draw(const RID &p_to_rid, const Color &p_color) override; + virtual Rect2 get_rect() const override; + virtual real_t get_enclosing_radius() const override; + + ConcavePolygonShape2D(); +}; + +#endif // CONCAVE_POLYGON_SHAPE_2D_H diff --git a/scene/resources/2d/convex_polygon_shape_2d.cpp b/scene/resources/2d/convex_polygon_shape_2d.cpp new file mode 100644 index 0000000000..0d9e570149 --- /dev/null +++ b/scene/resources/2d/convex_polygon_shape_2d.cpp @@ -0,0 +1,143 @@ +/**************************************************************************/ +/* convex_polygon_shape_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 "convex_polygon_shape_2d.h" + +#include "core/math/geometry_2d.h" +#include "servers/physics_server_2d.h" +#include "servers/rendering_server.h" + +bool ConvexPolygonShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { + return Geometry2D::is_point_in_polygon(p_point, points); +} + +#ifdef DEBUG_ENABLED +// Check if point p3 is to the left of [p1, p2] segment or on it. +bool left_test(const Vector2 &p1, const Vector2 &p2, const Vector2 &p3) { + Vector2 p12 = p2 - p1; + Vector2 p13 = p3 - p1; + // If the value of the cross product is positive or zero; p3 is to the left or on the segment, respectively. + return p12.cross(p13) >= 0; +} + +bool is_convex(const Vector &p_points) { + // Pre-condition: Polygon is in counter-clockwise order. + int polygon_size = p_points.size(); + for (int i = 0; i < polygon_size && polygon_size > 3; i++) { + int j = (i + 1) % polygon_size; + int k = (j + 1) % polygon_size; + // If any consecutive three points fail left-test, then there is a concavity. + if (!left_test(p_points[i], p_points[j], p_points[k])) { + return false; + } + } + + return true; +} +#endif + +void ConvexPolygonShape2D::_update_shape() { + Vector final_points = points; + if (Geometry2D::is_polygon_clockwise(final_points)) { //needs to be counter clockwise + final_points.reverse(); + } +#ifdef DEBUG_ENABLED + if (!is_convex(final_points)) { + WARN_PRINT("Concave polygon is assigned to ConvexPolygonShape2D."); + } +#endif + PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), final_points); + emit_changed(); +} + +void ConvexPolygonShape2D::set_point_cloud(const Vector &p_points) { + Vector hull = Geometry2D::convex_hull(p_points); + ERR_FAIL_COND(hull.size() < 3); + set_points(hull); +} + +void ConvexPolygonShape2D::set_points(const Vector &p_points) { + points = p_points; + + _update_shape(); +} + +Vector ConvexPolygonShape2D::get_points() const { + return points; +} + +void ConvexPolygonShape2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_point_cloud", "point_cloud"), &ConvexPolygonShape2D::set_point_cloud); + ClassDB::bind_method(D_METHOD("set_points", "points"), &ConvexPolygonShape2D::set_points); + ClassDB::bind_method(D_METHOD("get_points"), &ConvexPolygonShape2D::get_points); + + ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "points"), "set_points", "get_points"); +} + +void ConvexPolygonShape2D::draw(const RID &p_to_rid, const Color &p_color) { + if (points.size() < 3) { + return; + } + + Vector col = { p_color }; + RenderingServer::get_singleton()->canvas_item_add_polygon(p_to_rid, points, col); + + if (is_collision_outline_enabled()) { + col = { Color(p_color, 1.0) }; + RenderingServer::get_singleton()->canvas_item_add_polyline(p_to_rid, points, col); + // Draw the last segment. + RenderingServer::get_singleton()->canvas_item_add_line(p_to_rid, points[points.size() - 1], points[0], Color(p_color, 1.0)); + } +} + +Rect2 ConvexPolygonShape2D::get_rect() const { + Rect2 rect; + for (int i = 0; i < points.size(); i++) { + if (i == 0) { + rect.position = points[i]; + } else { + rect.expand_to(points[i]); + } + } + + return rect; +} + +real_t ConvexPolygonShape2D::get_enclosing_radius() const { + real_t r = 0.0; + for (int i(0); i < get_points().size(); i++) { + r = MAX(get_points()[i].length_squared(), r); + } + return Math::sqrt(r); +} + +ConvexPolygonShape2D::ConvexPolygonShape2D() : + Shape2D(PhysicsServer2D::get_singleton()->convex_polygon_shape_create()) { +} diff --git a/scene/resources/2d/convex_polygon_shape_2d.h b/scene/resources/2d/convex_polygon_shape_2d.h new file mode 100644 index 0000000000..2e448f1133 --- /dev/null +++ b/scene/resources/2d/convex_polygon_shape_2d.h @@ -0,0 +1,59 @@ +/**************************************************************************/ +/* convex_polygon_shape_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 CONVEX_POLYGON_SHAPE_2D_H +#define CONVEX_POLYGON_SHAPE_2D_H + +#include "scene/resources/2d/shape_2d.h" + +class ConvexPolygonShape2D : public Shape2D { + GDCLASS(ConvexPolygonShape2D, Shape2D); + + Vector points; + void _update_shape(); + +protected: + static void _bind_methods(); + +public: + virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override; + + void set_point_cloud(const Vector &p_points); + void set_points(const Vector &p_points); + Vector get_points() const; + + virtual void draw(const RID &p_to_rid, const Color &p_color) override; + virtual Rect2 get_rect() const override; + virtual real_t get_enclosing_radius() const override; + + ConvexPolygonShape2D(); +}; + +#endif // CONVEX_POLYGON_SHAPE_2D_H diff --git a/scene/resources/2d/polygon_path_finder.cpp b/scene/resources/2d/polygon_path_finder.cpp new file mode 100644 index 0000000000..617a53f0a3 --- /dev/null +++ b/scene/resources/2d/polygon_path_finder.cpp @@ -0,0 +1,563 @@ +/**************************************************************************/ +/* polygon_path_finder.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 "polygon_path_finder.h" +#include "core/math/geometry_2d.h" + +bool PolygonPathFinder::_is_point_inside(const Vector2 &p_point) const { + int crosses = 0; + + for (const Edge &E : edges) { + const Edge &e = E; + + Vector2 a = points[e.points[0]].pos; + Vector2 b = points[e.points[1]].pos; + + if (Geometry2D::segment_intersects_segment(a, b, p_point, outside_point, nullptr)) { + crosses++; + } + } + + return crosses & 1; +} + +void PolygonPathFinder::setup(const Vector &p_points, const Vector &p_connections) { + ERR_FAIL_COND(p_connections.size() & 1); + + points.clear(); + edges.clear(); + + //insert points + + int point_count = p_points.size(); + points.resize(point_count + 2); + bounds = Rect2(); + + for (int i = 0; i < p_points.size(); i++) { + points.write[i].pos = p_points[i]; + points.write[i].penalty = 0; + + outside_point.x = i == 0 ? p_points[0].x : (MAX(p_points[i].x, outside_point.x)); + outside_point.y = i == 0 ? p_points[0].y : (MAX(p_points[i].y, outside_point.y)); + + if (i == 0) { + bounds.position = points[i].pos; + } else { + bounds.expand_to(points[i].pos); + } + } + + outside_point.x += 20.451 + Math::randf() * 10.2039; + outside_point.y += 21.193 + Math::randf() * 12.5412; + + //insert edges (which are also connections) + + for (int i = 0; i < p_connections.size(); i += 2) { + Edge e(p_connections[i], p_connections[i + 1]); + ERR_FAIL_INDEX(e.points[0], point_count); + ERR_FAIL_INDEX(e.points[1], point_count); + points.write[p_connections[i]].connections.insert(p_connections[i + 1]); + points.write[p_connections[i + 1]].connections.insert(p_connections[i]); + edges.insert(e); + } + + //fill the remaining connections based on visibility + + for (int i = 0; i < point_count; i++) { + for (int j = i + 1; j < point_count; j++) { + if (edges.has(Edge(i, j))) { + continue; //if in edge ignore + } + + Vector2 from = points[i].pos; + Vector2 to = points[j].pos; + + if (!_is_point_inside(from * 0.5 + to * 0.5)) { //connection between points in inside space + continue; + } + + bool valid = true; + + for (const Edge &E : edges) { + const Edge &e = E; + if (e.points[0] == i || e.points[1] == i || e.points[0] == j || e.points[1] == j) { + continue; + } + + Vector2 a = points[e.points[0]].pos; + Vector2 b = points[e.points[1]].pos; + + if (Geometry2D::segment_intersects_segment(a, b, from, to, nullptr)) { + valid = false; + break; + } + } + + if (valid) { + points.write[i].connections.insert(j); + points.write[j].connections.insert(i); + } + } + } +} + +Vector PolygonPathFinder::find_path(const Vector2 &p_from, const Vector2 &p_to) { + Vector path; + + Vector2 from = p_from; + Vector2 to = p_to; + Edge ignore_from_edge(-1, -1); + Edge ignore_to_edge(-1, -1); + + if (!_is_point_inside(from)) { + float closest_dist = 1e20f; + Vector2 closest_point; + + for (const Edge &E : edges) { + const Edge &e = E; + Vector2 seg[2] = { + points[e.points[0]].pos, + points[e.points[1]].pos + }; + + Vector2 closest = Geometry2D::get_closest_point_to_segment(from, seg); + float d = from.distance_squared_to(closest); + + if (d < closest_dist) { + ignore_from_edge = E; + closest_dist = d; + closest_point = closest; + } + } + + from = closest_point; + }; + + if (!_is_point_inside(to)) { + float closest_dist = 1e20f; + Vector2 closest_point; + + for (const Edge &E : edges) { + const Edge &e = E; + Vector2 seg[2] = { + points[e.points[0]].pos, + points[e.points[1]].pos + }; + + Vector2 closest = Geometry2D::get_closest_point_to_segment(to, seg); + float d = to.distance_squared_to(closest); + + if (d < closest_dist) { + ignore_to_edge = E; + closest_dist = d; + closest_point = closest; + } + } + + to = closest_point; + }; + + //test direct connection + { + bool can_see_eachother = true; + + for (const Edge &E : edges) { + const Edge &e = E; + if (e.points[0] == ignore_from_edge.points[0] && e.points[1] == ignore_from_edge.points[1]) { + continue; + } + if (e.points[0] == ignore_to_edge.points[0] && e.points[1] == ignore_to_edge.points[1]) { + continue; + } + + Vector2 a = points[e.points[0]].pos; + Vector2 b = points[e.points[1]].pos; + + if (Geometry2D::segment_intersects_segment(a, b, from, to, nullptr)) { + can_see_eachother = false; + break; + } + } + + if (can_see_eachother) { + path.push_back(from); + path.push_back(to); + return path; + } + } + + //add to graph + + int aidx = points.size() - 2; + int bidx = points.size() - 1; + points.write[aidx].pos = from; + points.write[bidx].pos = to; + points.write[aidx].distance = 0; + points.write[bidx].distance = 0; + points.write[aidx].prev = -1; + points.write[bidx].prev = -1; + points.write[aidx].penalty = 0; + points.write[bidx].penalty = 0; + + for (int i = 0; i < points.size() - 2; i++) { + bool valid_a = true; + bool valid_b = true; + points.write[i].prev = -1; + points.write[i].distance = 0; + + if (!_is_point_inside(from * 0.5 + points[i].pos * 0.5)) { + valid_a = false; + } + + if (!_is_point_inside(to * 0.5 + points[i].pos * 0.5)) { + valid_b = false; + } + + for (const Edge &E : edges) { + const Edge &e = E; + + if (e.points[0] == i || e.points[1] == i) { + continue; + } + + Vector2 a = points[e.points[0]].pos; + Vector2 b = points[e.points[1]].pos; + + if (valid_a) { + if (e.points[0] != ignore_from_edge.points[1] && + e.points[1] != ignore_from_edge.points[1] && + e.points[0] != ignore_from_edge.points[0] && + e.points[1] != ignore_from_edge.points[0]) { + if (Geometry2D::segment_intersects_segment(a, b, from, points[i].pos, nullptr)) { + valid_a = false; + } + } + } + + if (valid_b) { + if (e.points[0] != ignore_to_edge.points[1] && + e.points[1] != ignore_to_edge.points[1] && + e.points[0] != ignore_to_edge.points[0] && + e.points[1] != ignore_to_edge.points[0]) { + if (Geometry2D::segment_intersects_segment(a, b, to, points[i].pos, nullptr)) { + valid_b = false; + } + } + } + + if (!valid_a && !valid_b) { + break; + } + } + + if (valid_a) { + points.write[i].connections.insert(aidx); + points.write[aidx].connections.insert(i); + } + + if (valid_b) { + points.write[i].connections.insert(bidx); + points.write[bidx].connections.insert(i); + } + } + //solve graph + + HashSet open_list; + + points.write[aidx].distance = 0; + points.write[aidx].prev = aidx; + for (const int &E : points[aidx].connections) { + open_list.insert(E); + points.write[E].distance = from.distance_to(points[E].pos); + points.write[E].prev = aidx; + } + + bool found_route = false; + + while (true) { + if (open_list.size() == 0) { + print_verbose("Open list empty."); + break; + } + //check open list + + int least_cost_point = -1; + float least_cost = 1e30; + + //this could be faster (cache previous results) + for (const int &E : open_list) { + const Point &p = points[E]; + float cost = p.distance; + cost += p.pos.distance_to(to); + cost += p.penalty; + + if (cost < least_cost) { + least_cost_point = E; + least_cost = cost; + } + } + + const Point &np = points[least_cost_point]; + //open the neighbors for search + + for (const int &E : np.connections) { + Point &p = points.write[E]; + float distance = np.pos.distance_to(p.pos) + np.distance; + + if (p.prev != -1) { + //oh this was visited already, can we win the cost? + + if (p.distance > distance) { + p.prev = least_cost_point; //reassign previous + p.distance = distance; + } + } else { + //add to open neighbors + + p.prev = least_cost_point; + p.distance = distance; + open_list.insert(E); + + if (E == bidx) { + //oh my reached end! stop algorithm + found_route = true; + break; + } + } + } + + if (found_route) { + break; + } + + open_list.erase(least_cost_point); + } + + if (found_route) { + int at = bidx; + path.push_back(points[at].pos); + do { + at = points[at].prev; + path.push_back(points[at].pos); + } while (at != aidx); + + path.reverse(); + } + + for (int i = 0; i < points.size() - 2; i++) { + points.write[i].connections.erase(aidx); + points.write[i].connections.erase(bidx); + points.write[i].prev = -1; + points.write[i].distance = 0; + } + + points.write[aidx].connections.clear(); + points.write[aidx].prev = -1; + points.write[aidx].distance = 0; + points.write[bidx].connections.clear(); + points.write[bidx].prev = -1; + points.write[bidx].distance = 0; + + return path; +} + +void PolygonPathFinder::_set_data(const Dictionary &p_data) { + ERR_FAIL_COND(!p_data.has("points")); + ERR_FAIL_COND(!p_data.has("connections")); + ERR_FAIL_COND(!p_data.has("segments")); + ERR_FAIL_COND(!p_data.has("bounds")); + + Vector p = p_data["points"]; + Array c = p_data["connections"]; + + ERR_FAIL_COND(c.size() != p.size()); + if (c.size()) { + return; + } + + int pc = p.size(); + points.resize(pc + 2); + + const Vector2 *pr = p.ptr(); + for (int i = 0; i < pc; i++) { + points.write[i].pos = pr[i]; + Vector con = c[i]; + const int *cr = con.ptr(); + int cc = con.size(); + for (int j = 0; j < cc; j++) { + points.write[i].connections.insert(cr[j]); + } + } + + if (p_data.has("penalties")) { + Vector penalties = p_data["penalties"]; + if (penalties.size() == pc) { + const real_t *pr2 = penalties.ptr(); + for (int i = 0; i < pc; i++) { + points.write[i].penalty = pr2[i]; + } + } + } + + Vector segs = p_data["segments"]; + int sc = segs.size(); + ERR_FAIL_COND(sc & 1); + const int *sr = segs.ptr(); + for (int i = 0; i < sc; i += 2) { + Edge e(sr[i], sr[i + 1]); + edges.insert(e); + } + bounds = p_data["bounds"]; +} + +Dictionary PolygonPathFinder::_get_data() const { + Dictionary d; + Vector p; + Vector ind; + Array path_connections; + p.resize(MAX(0, points.size() - 2)); + path_connections.resize(MAX(0, points.size() - 2)); + ind.resize(edges.size() * 2); + Vector penalties; + penalties.resize(MAX(0, points.size() - 2)); + { + Vector2 *wp = p.ptrw(); + real_t *pw = penalties.ptrw(); + + for (int i = 0; i < points.size() - 2; i++) { + wp[i] = points[i].pos; + pw[i] = points[i].penalty; + Vector c; + c.resize(points[i].connections.size()); + { + int *cw = c.ptrw(); + int idx = 0; + for (const int &E : points[i].connections) { + cw[idx++] = E; + } + } + path_connections[i] = c; + } + } + { + int *iw = ind.ptrw(); + int idx = 0; + for (const Edge &E : edges) { + iw[idx++] = E.points[0]; + iw[idx++] = E.points[1]; + } + } + + d["bounds"] = bounds; + d["points"] = p; + d["penalties"] = penalties; + d["connections"] = path_connections; + d["segments"] = ind; + + return d; +} + +bool PolygonPathFinder::is_point_inside(const Vector2 &p_point) const { + return _is_point_inside(p_point); +} + +Vector2 PolygonPathFinder::get_closest_point(const Vector2 &p_point) const { + float closest_dist = 1e20f; + Vector2 closest_point; + + for (const Edge &E : edges) { + const Edge &e = E; + Vector2 seg[2] = { + points[e.points[0]].pos, + points[e.points[1]].pos + }; + + Vector2 closest = Geometry2D::get_closest_point_to_segment(p_point, seg); + float d = p_point.distance_squared_to(closest); + + if (d < closest_dist) { + closest_dist = d; + closest_point = closest; + } + } + + ERR_FAIL_COND_V(Math::is_equal_approx(closest_dist, 1e20f), Vector2()); + + return closest_point; +} + +Vector PolygonPathFinder::get_intersections(const Vector2 &p_from, const Vector2 &p_to) const { + Vector inters; + + for (const Edge &E : edges) { + Vector2 a = points[E.points[0]].pos; + Vector2 b = points[E.points[1]].pos; + + Vector2 res; + if (Geometry2D::segment_intersects_segment(a, b, p_from, p_to, &res)) { + inters.push_back(res); + } + } + + return inters; +} + +Rect2 PolygonPathFinder::get_bounds() const { + return bounds; +} + +void PolygonPathFinder::set_point_penalty(int p_point, float p_penalty) { + ERR_FAIL_INDEX(p_point, points.size() - 2); + points.write[p_point].penalty = p_penalty; +} + +float PolygonPathFinder::get_point_penalty(int p_point) const { + ERR_FAIL_INDEX_V(p_point, points.size() - 2, 0); + return points[p_point].penalty; +} + +void PolygonPathFinder::_bind_methods() { + ClassDB::bind_method(D_METHOD("setup", "points", "connections"), &PolygonPathFinder::setup); + ClassDB::bind_method(D_METHOD("find_path", "from", "to"), &PolygonPathFinder::find_path); + ClassDB::bind_method(D_METHOD("get_intersections", "from", "to"), &PolygonPathFinder::get_intersections); + ClassDB::bind_method(D_METHOD("get_closest_point", "point"), &PolygonPathFinder::get_closest_point); + ClassDB::bind_method(D_METHOD("is_point_inside", "point"), &PolygonPathFinder::is_point_inside); + ClassDB::bind_method(D_METHOD("set_point_penalty", "idx", "penalty"), &PolygonPathFinder::set_point_penalty); + ClassDB::bind_method(D_METHOD("get_point_penalty", "idx"), &PolygonPathFinder::get_point_penalty); + + ClassDB::bind_method(D_METHOD("get_bounds"), &PolygonPathFinder::get_bounds); + ClassDB::bind_method(D_METHOD("_set_data", "data"), &PolygonPathFinder::_set_data); + ClassDB::bind_method(D_METHOD("_get_data"), &PolygonPathFinder::_get_data); + + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data"); +} + +PolygonPathFinder::PolygonPathFinder() { +} diff --git a/scene/resources/2d/polygon_path_finder.h b/scene/resources/2d/polygon_path_finder.h new file mode 100644 index 0000000000..a6cf6cd584 --- /dev/null +++ b/scene/resources/2d/polygon_path_finder.h @@ -0,0 +1,98 @@ +/**************************************************************************/ +/* polygon_path_finder.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 POLYGON_PATH_FINDER_H +#define POLYGON_PATH_FINDER_H + +#include "core/io/resource.h" + +class PolygonPathFinder : public Resource { + GDCLASS(PolygonPathFinder, Resource); + + struct Point { + Vector2 pos; + HashSet connections; + float distance = 0.0; + float penalty = 0.0; + int prev = 0; + }; + + union Edge { + struct { + int32_t points[2]; + }; + uint64_t key = 0; + + _FORCE_INLINE_ bool operator==(const Edge &p_edge) const { + return key == p_edge.key; + } + _FORCE_INLINE_ static uint32_t hash(const Edge &p_edge) { + return hash_one_uint64(p_edge.key); + } + + Edge(int a = 0, int b = 0) { + if (a > b) { + SWAP(a, b); + } + points[0] = a; + points[1] = b; + } + }; + + Vector2 outside_point; + Rect2 bounds; + + Vector points; + HashSet edges; + + bool _is_point_inside(const Vector2 &p_point) const; + + void _set_data(const Dictionary &p_data); + Dictionary _get_data() const; + +protected: + static void _bind_methods(); + +public: + void setup(const Vector &p_points, const Vector &p_connections); + Vector find_path(const Vector2 &p_from, const Vector2 &p_to); + + void set_point_penalty(int p_point, float p_penalty); + float get_point_penalty(int p_point) const; + + bool is_point_inside(const Vector2 &p_point) const; + Vector2 get_closest_point(const Vector2 &p_point) const; + Vector get_intersections(const Vector2 &p_from, const Vector2 &p_to) const; + Rect2 get_bounds() const; + + PolygonPathFinder(); +}; + +#endif // POLYGON_PATH_FINDER_H diff --git a/scene/resources/2d/rectangle_shape_2d.cpp b/scene/resources/2d/rectangle_shape_2d.cpp new file mode 100644 index 0000000000..65b1653293 --- /dev/null +++ b/scene/resources/2d/rectangle_shape_2d.cpp @@ -0,0 +1,107 @@ +/**************************************************************************/ +/* rectangle_shape_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 "rectangle_shape_2d.h" + +#include "servers/physics_server_2d.h" +#include "servers/rendering_server.h" +void RectangleShape2D::_update_shape() { + PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), size * 0.5); + emit_changed(); +} + +#ifndef DISABLE_DEPRECATED +bool RectangleShape2D::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "extents") { // Compatibility with Godot 3.x. + // Convert to `size`, twice as big. + set_size((Size2)p_value * 2); + return true; + } + return false; +} + +bool RectangleShape2D::_get(const StringName &p_name, Variant &r_property) const { + if (p_name == "extents") { // Compatibility with Godot 3.x. + // Convert to `extents`, half as big. + r_property = size / 2; + return true; + } + return false; +} +#endif // DISABLE_DEPRECATED + +void RectangleShape2D::set_size(const Size2 &p_size) { + ERR_FAIL_COND_MSG(p_size.x < 0 || p_size.y < 0, "RectangleShape2D size cannot be negative."); + size = p_size; + _update_shape(); +} + +Size2 RectangleShape2D::get_size() const { + return size; +} + +void RectangleShape2D::draw(const RID &p_to_rid, const Color &p_color) { + RenderingServer::get_singleton()->canvas_item_add_rect(p_to_rid, Rect2(-size * 0.5, size), p_color); + if (is_collision_outline_enabled()) { + // Draw an outlined rectangle to make individual shapes easier to distinguish. + Vector stroke_points; + stroke_points.resize(5); + stroke_points.write[0] = -size * 0.5; + stroke_points.write[1] = Vector2(size.x, -size.y) * 0.5; + stroke_points.write[2] = size * 0.5; + stroke_points.write[3] = Vector2(-size.x, size.y) * 0.5; + stroke_points.write[4] = -size * 0.5; + + Vector stroke_colors = { Color(p_color, 1.0) }; + + RenderingServer::get_singleton()->canvas_item_add_polyline(p_to_rid, stroke_points, stroke_colors); + } +} + +Rect2 RectangleShape2D::get_rect() const { + return Rect2(-size * 0.5, size); +} + +real_t RectangleShape2D::get_enclosing_radius() const { + return size.length() / 2; +} + +void RectangleShape2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_size", "size"), &RectangleShape2D::set_size); + ClassDB::bind_method(D_METHOD("get_size"), &RectangleShape2D::get_size); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size", PROPERTY_HINT_NONE, "suffix:px"), "set_size", "get_size"); +} + +RectangleShape2D::RectangleShape2D() : + Shape2D(PhysicsServer2D::get_singleton()->rectangle_shape_create()) { + size = Size2(20, 20); + _update_shape(); +} diff --git a/scene/resources/2d/rectangle_shape_2d.h b/scene/resources/2d/rectangle_shape_2d.h new file mode 100644 index 0000000000..80fbc51b5c --- /dev/null +++ b/scene/resources/2d/rectangle_shape_2d.h @@ -0,0 +1,60 @@ +/**************************************************************************/ +/* rectangle_shape_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 RECTANGLE_SHAPE_2D_H +#define RECTANGLE_SHAPE_2D_H + +#include "scene/resources/2d/shape_2d.h" + +class RectangleShape2D : public Shape2D { + GDCLASS(RectangleShape2D, Shape2D); + + Size2 size; + void _update_shape(); + +protected: + static void _bind_methods(); +#ifndef DISABLE_DEPRECATED + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_property) const; +#endif // DISABLE_DEPRECATED + +public: + void set_size(const Size2 &p_size); + Size2 get_size() const; + + virtual void draw(const RID &p_to_rid, const Color &p_color) override; + virtual Rect2 get_rect() const override; + virtual real_t get_enclosing_radius() const override; + + RectangleShape2D(); +}; + +#endif // RECTANGLE_SHAPE_2D_H diff --git a/scene/resources/2d/segment_shape_2d.cpp b/scene/resources/2d/segment_shape_2d.cpp new file mode 100644 index 0000000000..864b4c952b --- /dev/null +++ b/scene/resources/2d/segment_shape_2d.cpp @@ -0,0 +1,100 @@ +/**************************************************************************/ +/* segment_shape_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 "segment_shape_2d.h" + +#include "core/math/geometry_2d.h" +#include "servers/physics_server_2d.h" +#include "servers/rendering_server.h" + +bool SegmentShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { + Vector2 l[2] = { a, b }; + Vector2 closest = Geometry2D::get_closest_point_to_segment(p_point, l); + return p_point.distance_to(closest) < p_tolerance; +} + +void SegmentShape2D::_update_shape() { + Rect2 r; + r.position = a; + r.size = b; + PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), r); + emit_changed(); +} + +void SegmentShape2D::set_a(const Vector2 &p_a) { + a = p_a; + _update_shape(); +} + +Vector2 SegmentShape2D::get_a() const { + return a; +} + +void SegmentShape2D::set_b(const Vector2 &p_b) { + b = p_b; + _update_shape(); +} + +Vector2 SegmentShape2D::get_b() const { + return b; +} + +void SegmentShape2D::draw(const RID &p_to_rid, const Color &p_color) { + RenderingServer::get_singleton()->canvas_item_add_line(p_to_rid, a, b, p_color, 3); +} + +Rect2 SegmentShape2D::get_rect() const { + Rect2 rect; + rect.position = a; + rect.expand_to(b); + return rect; +} + +real_t SegmentShape2D::get_enclosing_radius() const { + return (a + b).length(); +} + +void SegmentShape2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_a", "a"), &SegmentShape2D::set_a); + ClassDB::bind_method(D_METHOD("get_a"), &SegmentShape2D::get_a); + + ClassDB::bind_method(D_METHOD("set_b", "b"), &SegmentShape2D::set_b); + ClassDB::bind_method(D_METHOD("get_b"), &SegmentShape2D::get_b); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "a", PROPERTY_HINT_NONE, "suffix:px"), "set_a", "get_a"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "b", PROPERTY_HINT_NONE, "suffix:px"), "set_b", "get_b"); +} + +SegmentShape2D::SegmentShape2D() : + Shape2D(PhysicsServer2D::get_singleton()->segment_shape_create()) { + a = Vector2(); + b = Vector2(0, 10); + _update_shape(); +} diff --git a/scene/resources/2d/segment_shape_2d.h b/scene/resources/2d/segment_shape_2d.h new file mode 100644 index 0000000000..ad2ece883d --- /dev/null +++ b/scene/resources/2d/segment_shape_2d.h @@ -0,0 +1,63 @@ +/**************************************************************************/ +/* segment_shape_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 SEGMENT_SHAPE_2D_H +#define SEGMENT_SHAPE_2D_H + +#include "scene/resources/2d/shape_2d.h" + +class SegmentShape2D : public Shape2D { + GDCLASS(SegmentShape2D, Shape2D); + + Vector2 a; + Vector2 b; + + void _update_shape(); + +protected: + static void _bind_methods(); + +public: + virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override; + + void set_a(const Vector2 &p_a); + void set_b(const Vector2 &p_b); + + Vector2 get_a() const; + Vector2 get_b() const; + + virtual void draw(const RID &p_to_rid, const Color &p_color) override; + virtual Rect2 get_rect() const override; + virtual real_t get_enclosing_radius() const override; + + SegmentShape2D(); +}; + +#endif // SEGMENT_SHAPE_2D_H diff --git a/scene/resources/2d/separation_ray_shape_2d.cpp b/scene/resources/2d/separation_ray_shape_2d.cpp new file mode 100644 index 0000000000..83d526626d --- /dev/null +++ b/scene/resources/2d/separation_ray_shape_2d.cpp @@ -0,0 +1,117 @@ +/**************************************************************************/ +/* separation_ray_shape_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 "separation_ray_shape_2d.h" + +#include "servers/physics_server_2d.h" +#include "servers/rendering_server.h" + +void SeparationRayShape2D::_update_shape() { + Dictionary d; + d["length"] = length; + d["slide_on_slope"] = slide_on_slope; + PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), d); + emit_changed(); +} + +void SeparationRayShape2D::draw(const RID &p_to_rid, const Color &p_color) { + const Vector2 target_position = Vector2(0, get_length()); + + const float max_arrow_size = 6; + const float line_width = 1.4; + bool no_line = target_position.length() < line_width; + float arrow_size = CLAMP(target_position.length() * 2 / 3, line_width, max_arrow_size); + + if (no_line) { + arrow_size = target_position.length(); + } else { + RS::get_singleton()->canvas_item_add_line(p_to_rid, Vector2(), target_position - target_position.normalized() * arrow_size, p_color, line_width); + } + + Transform2D xf; + xf.rotate(target_position.angle()); + xf.translate_local(Vector2(no_line ? 0 : target_position.length() - arrow_size, 0)); + + Vector pts = { + xf.xform(Vector2(arrow_size, 0)), + xf.xform(Vector2(0, 0.5 * arrow_size)), + xf.xform(Vector2(0, -0.5 * arrow_size)) + }; + + Vector cols = { p_color, p_color, p_color }; + + RS::get_singleton()->canvas_item_add_primitive(p_to_rid, pts, cols, Vector(), RID()); +} + +Rect2 SeparationRayShape2D::get_rect() const { + Rect2 rect; + rect.position = Vector2(); + rect.expand_to(Vector2(0, length)); + rect = rect.grow(Math_SQRT12 * 4); + return rect; +} + +real_t SeparationRayShape2D::get_enclosing_radius() const { + return length; +} + +void SeparationRayShape2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_length", "length"), &SeparationRayShape2D::set_length); + ClassDB::bind_method(D_METHOD("get_length"), &SeparationRayShape2D::get_length); + + ClassDB::bind_method(D_METHOD("set_slide_on_slope", "active"), &SeparationRayShape2D::set_slide_on_slope); + ClassDB::bind_method(D_METHOD("get_slide_on_slope"), &SeparationRayShape2D::get_slide_on_slope); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:px"), "set_length", "get_length"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_slope"), "set_slide_on_slope", "get_slide_on_slope"); +} + +void SeparationRayShape2D::set_length(real_t p_length) { + length = p_length; + _update_shape(); +} + +real_t SeparationRayShape2D::get_length() const { + return length; +} + +void SeparationRayShape2D::set_slide_on_slope(bool p_active) { + slide_on_slope = p_active; + _update_shape(); +} + +bool SeparationRayShape2D::get_slide_on_slope() const { + return slide_on_slope; +} + +SeparationRayShape2D::SeparationRayShape2D() : + Shape2D(PhysicsServer2D::get_singleton()->separation_ray_shape_create()) { + _update_shape(); +} diff --git a/scene/resources/2d/separation_ray_shape_2d.h b/scene/resources/2d/separation_ray_shape_2d.h new file mode 100644 index 0000000000..0359b41b6e --- /dev/null +++ b/scene/resources/2d/separation_ray_shape_2d.h @@ -0,0 +1,61 @@ +/**************************************************************************/ +/* separation_ray_shape_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 SEPARATION_RAY_SHAPE_2D_H +#define SEPARATION_RAY_SHAPE_2D_H + +#include "scene/resources/2d/shape_2d.h" + +class SeparationRayShape2D : public Shape2D { + GDCLASS(SeparationRayShape2D, Shape2D); + + real_t length = 20.0; + bool slide_on_slope = false; + + void _update_shape(); + +protected: + static void _bind_methods(); + +public: + void set_length(real_t p_length); + real_t get_length() const; + + void set_slide_on_slope(bool p_active); + bool get_slide_on_slope() const; + + virtual void draw(const RID &p_to_rid, const Color &p_color) override; + virtual Rect2 get_rect() const override; + virtual real_t get_enclosing_radius() const override; + + SeparationRayShape2D(); +}; + +#endif // SEPARATION_RAY_SHAPE_2D_H diff --git a/scene/resources/2d/shape_2d.cpp b/scene/resources/2d/shape_2d.cpp new file mode 100644 index 0000000000..2de9b70f53 --- /dev/null +++ b/scene/resources/2d/shape_2d.cpp @@ -0,0 +1,129 @@ +/**************************************************************************/ +/* shape_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 "shape_2d.h" + +#include "core/config/engine.h" +#include "core/config/project_settings.h" +#include "servers/physics_server_2d.h" + +RID Shape2D::get_rid() const { + return shape; +} + +void Shape2D::set_custom_solver_bias(real_t p_bias) { + custom_bias = p_bias; + PhysicsServer2D::get_singleton()->shape_set_custom_solver_bias(shape, custom_bias); +} + +real_t Shape2D::get_custom_solver_bias() const { + return custom_bias; +} + +bool Shape2D::collide_with_motion(const Transform2D &p_local_xform, const Vector2 &p_local_motion, const Ref &p_shape, const Transform2D &p_shape_xform, const Vector2 &p_shape_motion) { + ERR_FAIL_COND_V(p_shape.is_null(), false); + int r; + return PhysicsServer2D::get_singleton()->shape_collide(get_rid(), p_local_xform, p_local_motion, p_shape->get_rid(), p_shape_xform, p_shape_motion, nullptr, 0, r); +} + +bool Shape2D::collide(const Transform2D &p_local_xform, const Ref &p_shape, const Transform2D &p_shape_xform) { + ERR_FAIL_COND_V(p_shape.is_null(), false); + int r; + return PhysicsServer2D::get_singleton()->shape_collide(get_rid(), p_local_xform, Vector2(), p_shape->get_rid(), p_shape_xform, Vector2(), nullptr, 0, r); +} + +PackedVector2Array Shape2D::collide_with_motion_and_get_contacts(const Transform2D &p_local_xform, const Vector2 &p_local_motion, const Ref &p_shape, const Transform2D &p_shape_xform, const Vector2 &p_shape_motion) { + ERR_FAIL_COND_V(p_shape.is_null(), PackedVector2Array()); + const int max_contacts = 16; + Vector2 result[max_contacts * 2]; + int contacts = 0; + + if (!PhysicsServer2D::get_singleton()->shape_collide(get_rid(), p_local_xform, p_local_motion, p_shape->get_rid(), p_shape_xform, p_shape_motion, result, max_contacts, contacts)) { + return PackedVector2Array(); + } + + PackedVector2Array results; + results.resize(contacts * 2); + for (int i = 0; i < contacts * 2; i++) { + results.write[i] = result[i]; + } + + return results; +} + +PackedVector2Array Shape2D::collide_and_get_contacts(const Transform2D &p_local_xform, const Ref &p_shape, const Transform2D &p_shape_xform) { + ERR_FAIL_COND_V(p_shape.is_null(), PackedVector2Array()); + const int max_contacts = 16; + Vector2 result[max_contacts * 2]; + int contacts = 0; + + if (!PhysicsServer2D::get_singleton()->shape_collide(get_rid(), p_local_xform, Vector2(), p_shape->get_rid(), p_shape_xform, Vector2(), result, max_contacts, contacts)) { + return PackedVector2Array(); + } + + PackedVector2Array results; + results.resize(contacts * 2); + for (int i = 0; i < contacts * 2; i++) { + results.write[i] = result[i]; + } + + return results; +} + +void Shape2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_custom_solver_bias", "bias"), &Shape2D::set_custom_solver_bias); + ClassDB::bind_method(D_METHOD("get_custom_solver_bias"), &Shape2D::get_custom_solver_bias); + ClassDB::bind_method(D_METHOD("collide", "local_xform", "with_shape", "shape_xform"), &Shape2D::collide); + ClassDB::bind_method(D_METHOD("collide_with_motion", "local_xform", "local_motion", "with_shape", "shape_xform", "shape_motion"), &Shape2D::collide_with_motion); + ClassDB::bind_method(D_METHOD("collide_and_get_contacts", "local_xform", "with_shape", "shape_xform"), &Shape2D::collide_and_get_contacts); + ClassDB::bind_method(D_METHOD("collide_with_motion_and_get_contacts", "local_xform", "local_motion", "with_shape", "shape_xform", "shape_motion"), &Shape2D::collide_with_motion_and_get_contacts); + ClassDB::bind_method(D_METHOD("draw", "canvas_item", "color"), &Shape2D::draw); + ClassDB::bind_method(D_METHOD("get_rect"), &Shape2D::get_rect); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "custom_solver_bias", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_custom_solver_bias", "get_custom_solver_bias"); +} + +bool Shape2D::is_collision_outline_enabled() { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + return true; + } +#endif + return GLOBAL_GET("debug/shapes/collision/draw_2d_outlines"); +} + +Shape2D::Shape2D(const RID &p_rid) { + shape = p_rid; +} + +Shape2D::~Shape2D() { + ERR_FAIL_NULL(PhysicsServer2D::get_singleton()); + PhysicsServer2D::get_singleton()->free(shape); +} diff --git a/scene/resources/2d/shape_2d.h b/scene/resources/2d/shape_2d.h new file mode 100644 index 0000000000..b1a5b24d05 --- /dev/null +++ b/scene/resources/2d/shape_2d.h @@ -0,0 +1,70 @@ +/**************************************************************************/ +/* shape_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 SHAPE_2D_H +#define SHAPE_2D_H + +#include "core/io/resource.h" + +class Shape2D : public Resource { + GDCLASS(Shape2D, Resource); + OBJ_SAVE_TYPE(Shape2D); + + RID shape; + real_t custom_bias = 0.0; + +protected: + static void _bind_methods(); + Shape2D(const RID &p_rid); + +public: + virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { return get_rect().has_point(p_point); } + + void set_custom_solver_bias(real_t p_bias); + real_t get_custom_solver_bias() const; + + bool collide_with_motion(const Transform2D &p_local_xform, const Vector2 &p_local_motion, const Ref &p_shape, const Transform2D &p_shape_xform, const Vector2 &p_shape_motion); + bool collide(const Transform2D &p_local_xform, const Ref &p_shape, const Transform2D &p_shape_xform); + + PackedVector2Array collide_with_motion_and_get_contacts(const Transform2D &p_local_xform, const Vector2 &p_local_motion, const Ref &p_shape, const Transform2D &p_shape_xform, const Vector2 &p_shape_motion); + PackedVector2Array collide_and_get_contacts(const Transform2D &p_local_xform, const Ref &p_shape, const Transform2D &p_shape_xform); + + virtual void draw(const RID &p_to_rid, const Color &p_color) {} + virtual Rect2 get_rect() const { return Rect2(); } + /// Returns the radius of a circle that fully enclose this shape + virtual real_t get_enclosing_radius() const = 0; + virtual RID get_rid() const override; + + static bool is_collision_outline_enabled(); + + ~Shape2D(); +}; + +#endif // SHAPE_2D_H diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d.cpp new file mode 100644 index 0000000000..88a0f7b92d --- /dev/null +++ b/scene/resources/2d/skeleton/skeleton_modification_2d.cpp @@ -0,0 +1,239 @@ +/**************************************************************************/ +/* skeleton_modification_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 "skeleton_modification_2d.h" +#include "scene/2d/skeleton_2d.h" + +#include "scene/2d/collision_object_2d.h" +#include "scene/2d/collision_shape_2d.h" +#include "scene/2d/physical_bone_2d.h" + +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#endif // TOOLS_ENABLED + +/////////////////////////////////////// +// Modification2D +/////////////////////////////////////// + +void SkeletonModification2D::_execute(float p_delta) { + GDVIRTUAL_CALL(_execute, p_delta); + + if (!enabled) { + return; + } +} + +void SkeletonModification2D::_setup_modification(SkeletonModificationStack2D *p_stack) { + stack = p_stack; + if (stack) { + is_setup = true; + } else { + WARN_PRINT("Could not setup modification with name " + get_name()); + } + + GDVIRTUAL_CALL(_setup_modification, Ref(p_stack)); +} + +void SkeletonModification2D::_draw_editor_gizmo() { + GDVIRTUAL_CALL(_draw_editor_gizmo); +} + +void SkeletonModification2D::set_enabled(bool p_enabled) { + enabled = p_enabled; + +#ifdef TOOLS_ENABLED + if (editor_draw_gizmo) { + if (stack) { + stack->set_editor_gizmos_dirty(true); + } + } +#endif // TOOLS_ENABLED +} + +bool SkeletonModification2D::get_enabled() { + return enabled; +} + +float SkeletonModification2D::clamp_angle(float p_angle, float p_min_bound, float p_max_bound, bool p_invert) { + // Map to the 0 to 360 range (in radians though) instead of the -180 to 180 range. + if (p_angle < 0) { + p_angle = Math_TAU + p_angle; + } + + // Make min and max in the range of 0 to 360 (in radians), and make sure they are in the right order + if (p_min_bound < 0) { + p_min_bound = Math_TAU + p_min_bound; + } + if (p_max_bound < 0) { + p_max_bound = Math_TAU + p_max_bound; + } + if (p_min_bound > p_max_bound) { + SWAP(p_min_bound, p_max_bound); + } + + bool is_beyond_bounds = (p_angle < p_min_bound || p_angle > p_max_bound); + bool is_within_bounds = (p_angle > p_min_bound && p_angle < p_max_bound); + + // Note: May not be the most optimal way to clamp, but it always constraints to the nearest angle. + if ((!p_invert && is_beyond_bounds) || (p_invert && is_within_bounds)) { + Vector2 min_bound_vec = Vector2(Math::cos(p_min_bound), Math::sin(p_min_bound)); + Vector2 max_bound_vec = Vector2(Math::cos(p_max_bound), Math::sin(p_max_bound)); + Vector2 angle_vec = Vector2(Math::cos(p_angle), Math::sin(p_angle)); + + if (angle_vec.distance_squared_to(min_bound_vec) <= angle_vec.distance_squared_to(max_bound_vec)) { + p_angle = p_min_bound; + } else { + p_angle = p_max_bound; + } + } + + return p_angle; +} + +void SkeletonModification2D::editor_draw_angle_constraints(Bone2D *p_operation_bone, float p_min_bound, float p_max_bound, + bool p_constraint_enabled, bool p_constraint_in_localspace, bool p_constraint_inverted) { + if (!p_operation_bone) { + return; + } + + Color bone_ik_color = Color(1.0, 0.65, 0.0, 0.4); +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + bone_ik_color = EDITOR_GET("editors/2d/bone_ik_color"); + } +#endif // TOOLS_ENABLED + + float arc_angle_min = p_min_bound; + float arc_angle_max = p_max_bound; + if (arc_angle_min < 0) { + arc_angle_min = (Math_PI * 2) + arc_angle_min; + } + if (arc_angle_max < 0) { + arc_angle_max = (Math_PI * 2) + arc_angle_max; + } + if (arc_angle_min > arc_angle_max) { + SWAP(arc_angle_min, arc_angle_max); + } + arc_angle_min += p_operation_bone->get_bone_angle(); + arc_angle_max += p_operation_bone->get_bone_angle(); + + if (p_constraint_enabled) { + if (p_constraint_in_localspace) { + Node *operation_bone_parent = p_operation_bone->get_parent(); + Bone2D *operation_bone_parent_bone = Object::cast_to(operation_bone_parent); + + if (operation_bone_parent_bone) { + stack->skeleton->draw_set_transform( + stack->skeleton->to_local(p_operation_bone->get_global_position()), + operation_bone_parent_bone->get_global_rotation() - stack->skeleton->get_global_rotation()); + } else { + stack->skeleton->draw_set_transform(stack->skeleton->to_local(p_operation_bone->get_global_position())); + } + } else { + stack->skeleton->draw_set_transform(stack->skeleton->to_local(p_operation_bone->get_global_position())); + } + + if (p_constraint_inverted) { + stack->skeleton->draw_arc(Vector2(0, 0), p_operation_bone->get_length(), + arc_angle_min + (Math_PI * 2), arc_angle_max, 32, bone_ik_color, 1.0); + } else { + stack->skeleton->draw_arc(Vector2(0, 0), p_operation_bone->get_length(), + arc_angle_min, arc_angle_max, 32, bone_ik_color, 1.0); + } + stack->skeleton->draw_line(Vector2(0, 0), Vector2(Math::cos(arc_angle_min), Math::sin(arc_angle_min)) * p_operation_bone->get_length(), bone_ik_color, 1.0); + stack->skeleton->draw_line(Vector2(0, 0), Vector2(Math::cos(arc_angle_max), Math::sin(arc_angle_max)) * p_operation_bone->get_length(), bone_ik_color, 1.0); + + } else { + stack->skeleton->draw_set_transform(stack->skeleton->to_local(p_operation_bone->get_global_position())); + stack->skeleton->draw_arc(Vector2(0, 0), p_operation_bone->get_length(), 0, Math_PI * 2, 32, bone_ik_color, 1.0); + stack->skeleton->draw_line(Vector2(0, 0), Vector2(1, 0) * p_operation_bone->get_length(), bone_ik_color, 1.0); + } +} + +Ref SkeletonModification2D::get_modification_stack() { + return stack; +} + +void SkeletonModification2D::set_is_setup(bool p_setup) { + is_setup = p_setup; +} + +bool SkeletonModification2D::get_is_setup() const { + return is_setup; +} + +void SkeletonModification2D::set_execution_mode(int p_mode) { + execution_mode = p_mode; +} + +int SkeletonModification2D::get_execution_mode() const { + return execution_mode; +} + +void SkeletonModification2D::set_editor_draw_gizmo(bool p_draw_gizmo) { + editor_draw_gizmo = p_draw_gizmo; +#ifdef TOOLS_ENABLED + if (is_setup) { + if (stack) { + stack->set_editor_gizmos_dirty(true); + } + } +#endif // TOOLS_ENABLED +} + +bool SkeletonModification2D::get_editor_draw_gizmo() const { + return editor_draw_gizmo; +} + +void SkeletonModification2D::_bind_methods() { + GDVIRTUAL_BIND(_execute, "delta"); + GDVIRTUAL_BIND(_setup_modification, "modification_stack") + GDVIRTUAL_BIND(_draw_editor_gizmo) + + ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &SkeletonModification2D::set_enabled); + ClassDB::bind_method(D_METHOD("get_enabled"), &SkeletonModification2D::get_enabled); + ClassDB::bind_method(D_METHOD("get_modification_stack"), &SkeletonModification2D::get_modification_stack); + ClassDB::bind_method(D_METHOD("set_is_setup", "is_setup"), &SkeletonModification2D::set_is_setup); + ClassDB::bind_method(D_METHOD("get_is_setup"), &SkeletonModification2D::get_is_setup); + ClassDB::bind_method(D_METHOD("set_execution_mode", "execution_mode"), &SkeletonModification2D::set_execution_mode); + ClassDB::bind_method(D_METHOD("get_execution_mode"), &SkeletonModification2D::get_execution_mode); + ClassDB::bind_method(D_METHOD("clamp_angle", "angle", "min", "max", "invert"), &SkeletonModification2D::clamp_angle); + ClassDB::bind_method(D_METHOD("set_editor_draw_gizmo", "draw_gizmo"), &SkeletonModification2D::set_editor_draw_gizmo); + ClassDB::bind_method(D_METHOD("get_editor_draw_gizmo"), &SkeletonModification2D::get_editor_draw_gizmo); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "get_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "execution_mode", PROPERTY_HINT_ENUM, "process,physics_process"), "set_execution_mode", "get_execution_mode"); +} + +SkeletonModification2D::SkeletonModification2D() { + stack = nullptr; + is_setup = false; +} diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d.h b/scene/resources/2d/skeleton/skeleton_modification_2d.h new file mode 100644 index 0000000000..413b860a99 --- /dev/null +++ b/scene/resources/2d/skeleton/skeleton_modification_2d.h @@ -0,0 +1,89 @@ +/**************************************************************************/ +/* skeleton_modification_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 SKELETON_MODIFICATION_2D_H +#define SKELETON_MODIFICATION_2D_H + +#include "scene/2d/skeleton_2d.h" +#include "scene/resources/2d/skeleton/skeleton_modification_stack_2d.h" + +/////////////////////////////////////// +// SkeletonModification2D +/////////////////////////////////////// + +class SkeletonModificationStack2D; +class Bone2D; + +class SkeletonModification2D : public Resource { + GDCLASS(SkeletonModification2D, Resource); + friend class Skeleton2D; + friend class Bone2D; + +protected: + static void _bind_methods(); + + SkeletonModificationStack2D *stack = nullptr; + int execution_mode = 0; // 0 = process + + bool enabled = true; + bool is_setup = false; + + bool _print_execution_error(bool p_condition, String p_message); + + GDVIRTUAL1(_execute, double) + GDVIRTUAL1(_setup_modification, Ref) + GDVIRTUAL0(_draw_editor_gizmo) + +public: + virtual void _execute(float _delta); + virtual void _setup_modification(SkeletonModificationStack2D *p_stack); + virtual void _draw_editor_gizmo(); + + bool editor_draw_gizmo = false; + void set_editor_draw_gizmo(bool p_draw_gizmo); + bool get_editor_draw_gizmo() const; + + void set_enabled(bool p_enabled); + bool get_enabled(); + + Ref get_modification_stack(); + void set_is_setup(bool p_setup); + bool get_is_setup() const; + + void set_execution_mode(int p_mode); + int get_execution_mode() const; + + float clamp_angle(float p_angle, float p_min_bound, float p_max_bound, bool p_invert_clamp = false); + void editor_draw_angle_constraints(Bone2D *p_operation_bone, float p_min_bound, float p_max_bound, bool p_constraint_enabled, bool p_constraint_in_localspace, bool p_constraint_inverted); + + SkeletonModification2D(); +}; + +#endif // SKELETON_MODIFICATION_2D_H diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.cpp new file mode 100644 index 0000000000..1ad8d0eccc --- /dev/null +++ b/scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.cpp @@ -0,0 +1,549 @@ +/**************************************************************************/ +/* skeleton_modification_2d_ccdik.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 "skeleton_modification_2d_ccdik.h" +#include "scene/2d/skeleton_2d.h" + +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#endif // TOOLS_ENABLED + +bool SkeletonModification2DCCDIK::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.begins_with("joint_data/")) { + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, ccdik_data_chain.size(), false); + + if (what == "bone2d_node") { + set_ccdik_joint_bone2d_node(which, p_value); + } else if (what == "bone_index") { + set_ccdik_joint_bone_index(which, p_value); + } else if (what == "rotate_from_joint") { + set_ccdik_joint_rotate_from_joint(which, p_value); + } else if (what == "enable_constraint") { + set_ccdik_joint_enable_constraint(which, p_value); + } else if (what == "constraint_angle_min") { + set_ccdik_joint_constraint_angle_min(which, Math::deg_to_rad(float(p_value))); + } else if (what == "constraint_angle_max") { + set_ccdik_joint_constraint_angle_max(which, Math::deg_to_rad(float(p_value))); + } else if (what == "constraint_angle_invert") { + set_ccdik_joint_constraint_angle_invert(which, p_value); + } else if (what == "constraint_in_localspace") { + set_ccdik_joint_constraint_in_localspace(which, p_value); + } +#ifdef TOOLS_ENABLED + else if (what.begins_with("editor_draw_gizmo")) { + set_ccdik_joint_editor_draw_gizmo(which, p_value); + } +#endif // TOOLS_ENABLED + else { + return false; + } + } +#ifdef TOOLS_ENABLED + else if (path.begins_with("editor/draw_gizmo")) { + set_editor_draw_gizmo(p_value); + } +#endif // TOOLS_ENABLED + else { + return false; + } + + return true; +} + +bool SkeletonModification2DCCDIK::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.begins_with("joint_data/")) { + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, ccdik_data_chain.size(), false); + + if (what == "bone2d_node") { + r_ret = get_ccdik_joint_bone2d_node(which); + } else if (what == "bone_index") { + r_ret = get_ccdik_joint_bone_index(which); + } else if (what == "rotate_from_joint") { + r_ret = get_ccdik_joint_rotate_from_joint(which); + } else if (what == "enable_constraint") { + r_ret = get_ccdik_joint_enable_constraint(which); + } else if (what == "constraint_angle_min") { + r_ret = Math::rad_to_deg(get_ccdik_joint_constraint_angle_min(which)); + } else if (what == "constraint_angle_max") { + r_ret = Math::rad_to_deg(get_ccdik_joint_constraint_angle_max(which)); + } else if (what == "constraint_angle_invert") { + r_ret = get_ccdik_joint_constraint_angle_invert(which); + } else if (what == "constraint_in_localspace") { + r_ret = get_ccdik_joint_constraint_in_localspace(which); + } +#ifdef TOOLS_ENABLED + else if (what.begins_with("editor_draw_gizmo")) { + r_ret = get_ccdik_joint_editor_draw_gizmo(which); + } +#endif // TOOLS_ENABLED + else { + return false; + } + } +#ifdef TOOLS_ENABLED + else if (path.begins_with("editor/draw_gizmo")) { + r_ret = get_editor_draw_gizmo(); + } +#endif // TOOLS_ENABLED + else { + return false; + } + + return true; +} + +void SkeletonModification2DCCDIK::_get_property_list(List *p_list) const { + for (int i = 0; i < ccdik_data_chain.size(); i++) { + String base_string = "joint_data/" + itos(i) + "/"; + + p_list->push_back(PropertyInfo(Variant::INT, base_string + "bone_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "rotate_from_joint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "enable_constraint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + if (ccdik_data_chain[i].enable_constraint) { + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "constraint_angle_min", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "constraint_angle_max", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "constraint_angle_invert", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "constraint_in_localspace", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "editor_draw_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } +#endif // TOOLS_ENABLED + } + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } +#endif // TOOLS_ENABLED +} + +void SkeletonModification2DCCDIK::_execute(float p_delta) { + ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot execute!"); + if (!enabled) { + return; + } + + if (target_node_cache.is_null()) { + WARN_PRINT_ONCE("Target cache is out of date. Attempting to update..."); + update_target_cache(); + return; + } + if (tip_node_cache.is_null()) { + WARN_PRINT_ONCE("Tip cache is out of date. Attempting to update..."); + update_tip_cache(); + return; + } + + Node2D *target = Object::cast_to(ObjectDB::get_instance(target_node_cache)); + if (!target || !target->is_inside_tree()) { + ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!"); + return; + } + + Node2D *tip = Object::cast_to(ObjectDB::get_instance(tip_node_cache)); + if (!tip || !tip->is_inside_tree()) { + ERR_PRINT_ONCE("Tip node is not in the scene tree. Cannot execute modification!"); + return; + } + + for (int i = 0; i < ccdik_data_chain.size(); i++) { + _execute_ccdik_joint(i, target, tip); + } +} + +void SkeletonModification2DCCDIK::_execute_ccdik_joint(int p_joint_idx, Node2D *p_target, Node2D *p_tip) { + CCDIK_Joint_Data2D ccdik_data = ccdik_data_chain[p_joint_idx]; + if (ccdik_data.bone_idx < 0 || ccdik_data.bone_idx > stack->skeleton->get_bone_count()) { + ERR_PRINT_ONCE("2D CCDIK joint: bone index not found!"); + return; + } + + Bone2D *operation_bone = stack->skeleton->get_bone(ccdik_data.bone_idx); + Transform2D operation_transform = operation_bone->get_global_transform(); + + if (ccdik_data.rotate_from_joint) { + // To rotate from the joint, simply look at the target! + operation_transform.set_rotation( + operation_transform.looking_at(p_target->get_global_position()).get_rotation() - operation_bone->get_bone_angle()); + } else { + // How to rotate from the tip: get the difference of rotation needed from the tip to the target, from the perspective of the joint. + // Because we are only using the offset, we do not need to account for the bone angle of the Bone2D node. + float joint_to_tip = p_tip->get_global_position().angle_to_point(operation_transform.get_origin()); + float joint_to_target = p_target->get_global_position().angle_to_point(operation_transform.get_origin()); + operation_transform.set_rotation( + operation_transform.get_rotation() + (joint_to_target - joint_to_tip)); + } + + // Reset scale + operation_transform.set_scale(operation_bone->get_global_scale()); + + // Apply constraints in globalspace: + if (ccdik_data.enable_constraint && !ccdik_data.constraint_in_localspace) { + operation_transform.set_rotation(clamp_angle(operation_transform.get_rotation(), ccdik_data.constraint_angle_min, ccdik_data.constraint_angle_max, ccdik_data.constraint_angle_invert)); + } + + // Convert from a global transform to a delta and then apply the delta to the local transform. + operation_bone->set_global_transform(operation_transform); + operation_transform = operation_bone->get_transform(); + + // Apply constraints in localspace: + if (ccdik_data.enable_constraint && ccdik_data.constraint_in_localspace) { + operation_transform.set_rotation(clamp_angle(operation_transform.get_rotation(), ccdik_data.constraint_angle_min, ccdik_data.constraint_angle_max, ccdik_data.constraint_angle_invert)); + } + + // Set the local pose override, and to make sure child bones are also updated, set the transform of the bone. + stack->skeleton->set_bone_local_pose_override(ccdik_data.bone_idx, operation_transform, stack->strength, true); + operation_bone->set_transform(operation_transform); + operation_bone->notification(operation_bone->NOTIFICATION_TRANSFORM_CHANGED); +} + +void SkeletonModification2DCCDIK::_setup_modification(SkeletonModificationStack2D *p_stack) { + stack = p_stack; + + if (stack != nullptr) { + is_setup = true; + update_target_cache(); + update_tip_cache(); + } +} + +void SkeletonModification2DCCDIK::_draw_editor_gizmo() { + if (!enabled || !is_setup) { + return; + } + + for (int i = 0; i < ccdik_data_chain.size(); i++) { + if (!ccdik_data_chain[i].editor_draw_gizmo) { + continue; + } + + Bone2D *operation_bone = stack->skeleton->get_bone(ccdik_data_chain[i].bone_idx); + editor_draw_angle_constraints(operation_bone, ccdik_data_chain[i].constraint_angle_min, ccdik_data_chain[i].constraint_angle_max, + ccdik_data_chain[i].enable_constraint, ccdik_data_chain[i].constraint_in_localspace, ccdik_data_chain[i].constraint_angle_invert); + } +} + +void SkeletonModification2DCCDIK::update_target_cache() { + if (!is_setup || !stack) { + ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + return; + } + + target_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(target_node)) { + Node *node = stack->skeleton->get_node(target_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update target cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update target cache: node is not in the scene tree!"); + target_node_cache = node->get_instance_id(); + } + } + } +} + +void SkeletonModification2DCCDIK::update_tip_cache() { + if (!is_setup || !stack) { + ERR_PRINT_ONCE("Cannot update tip cache: modification is not properly setup!"); + return; + } + + tip_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(tip_node)) { + Node *node = stack->skeleton->get_node(tip_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update tip cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update tip cache: node is not in the scene tree!"); + tip_node_cache = node->get_instance_id(); + } + } + } +} + +void SkeletonModification2DCCDIK::ccdik_joint_update_bone2d_cache(int p_joint_idx) { + ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "Cannot update bone2d cache: joint index out of range!"); + if (!is_setup || !stack) { + ERR_PRINT_ONCE("Cannot update CCDIK Bone2D cache: modification is not properly setup!"); + return; + } + + ccdik_data_chain.write[p_joint_idx].bone2d_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(ccdik_data_chain[p_joint_idx].bone2d_node)) { + Node *node = stack->skeleton->get_node(ccdik_data_chain[p_joint_idx].bone2d_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update CCDIK joint " + itos(p_joint_idx) + " Bone2D cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update CCDIK joint " + itos(p_joint_idx) + " Bone2D cache: node is not in the scene tree!"); + ccdik_data_chain.write[p_joint_idx].bone2d_node_cache = node->get_instance_id(); + + Bone2D *bone = Object::cast_to(node); + if (bone) { + ccdik_data_chain.write[p_joint_idx].bone_idx = bone->get_index_in_skeleton(); + } else { + ERR_FAIL_MSG("CCDIK joint " + itos(p_joint_idx) + " Bone2D cache: Nodepath to Bone2D is not a Bone2D node!"); + } + } + } + } +} + +void SkeletonModification2DCCDIK::set_target_node(const NodePath &p_target_node) { + target_node = p_target_node; + update_target_cache(); +} + +NodePath SkeletonModification2DCCDIK::get_target_node() const { + return target_node; +} + +void SkeletonModification2DCCDIK::set_tip_node(const NodePath &p_tip_node) { + tip_node = p_tip_node; + update_tip_cache(); +} + +NodePath SkeletonModification2DCCDIK::get_tip_node() const { + return tip_node; +} + +void SkeletonModification2DCCDIK::set_ccdik_data_chain_length(int p_length) { + ccdik_data_chain.resize(p_length); + notify_property_list_changed(); +} + +int SkeletonModification2DCCDIK::get_ccdik_data_chain_length() { + return ccdik_data_chain.size(); +} + +void SkeletonModification2DCCDIK::set_ccdik_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node) { + ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); + ccdik_data_chain.write[p_joint_idx].bone2d_node = p_target_node; + ccdik_joint_update_bone2d_cache(p_joint_idx); + + notify_property_list_changed(); +} + +NodePath SkeletonModification2DCCDIK::get_ccdik_joint_bone2d_node(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), NodePath(), "CCDIK joint out of range!"); + return ccdik_data_chain[p_joint_idx].bone2d_node; +} + +void SkeletonModification2DCCDIK::set_ccdik_joint_bone_index(int p_joint_idx, int p_bone_idx) { + ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCCDIK joint out of range!"); + ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); + + if (is_setup) { + if (stack->skeleton) { + ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); + ccdik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; + ccdik_data_chain.write[p_joint_idx].bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id(); + ccdik_data_chain.write[p_joint_idx].bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx)); + } else { + WARN_PRINT("Cannot verify the CCDIK joint " + itos(p_joint_idx) + " bone index for this modification..."); + ccdik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; + } + } else { + WARN_PRINT("Cannot verify the CCDIK joint " + itos(p_joint_idx) + " bone index for this modification..."); + ccdik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; + } + + notify_property_list_changed(); +} + +int SkeletonModification2DCCDIK::get_ccdik_joint_bone_index(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), -1, "CCDIK joint out of range!"); + return ccdik_data_chain[p_joint_idx].bone_idx; +} + +void SkeletonModification2DCCDIK::set_ccdik_joint_rotate_from_joint(int p_joint_idx, bool p_rotate_from_joint) { + ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); + ccdik_data_chain.write[p_joint_idx].rotate_from_joint = p_rotate_from_joint; +} + +bool SkeletonModification2DCCDIK::get_ccdik_joint_rotate_from_joint(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), false, "CCDIK joint out of range!"); + return ccdik_data_chain[p_joint_idx].rotate_from_joint; +} + +void SkeletonModification2DCCDIK::set_ccdik_joint_enable_constraint(int p_joint_idx, bool p_constraint) { + ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); + ccdik_data_chain.write[p_joint_idx].enable_constraint = p_constraint; + notify_property_list_changed(); + +#ifdef TOOLS_ENABLED + if (stack && is_setup) { + stack->set_editor_gizmos_dirty(true); + } +#endif // TOOLS_ENABLED +} + +bool SkeletonModification2DCCDIK::get_ccdik_joint_enable_constraint(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), false, "CCDIK joint out of range!"); + return ccdik_data_chain[p_joint_idx].enable_constraint; +} + +void SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_min(int p_joint_idx, float p_angle_min) { + ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); + ccdik_data_chain.write[p_joint_idx].constraint_angle_min = p_angle_min; + +#ifdef TOOLS_ENABLED + if (stack && is_setup) { + stack->set_editor_gizmos_dirty(true); + } +#endif // TOOLS_ENABLED +} + +float SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_min(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), 0.0, "CCDIK joint out of range!"); + return ccdik_data_chain[p_joint_idx].constraint_angle_min; +} + +void SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_max(int p_joint_idx, float p_angle_max) { + ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); + ccdik_data_chain.write[p_joint_idx].constraint_angle_max = p_angle_max; + +#ifdef TOOLS_ENABLED + if (stack && is_setup) { + stack->set_editor_gizmos_dirty(true); + } +#endif // TOOLS_ENABLED +} + +float SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_max(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), 0.0, "CCDIK joint out of range!"); + return ccdik_data_chain[p_joint_idx].constraint_angle_max; +} + +void SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_invert(int p_joint_idx, bool p_invert) { + ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); + ccdik_data_chain.write[p_joint_idx].constraint_angle_invert = p_invert; + +#ifdef TOOLS_ENABLED + if (stack && is_setup) { + stack->set_editor_gizmos_dirty(true); + } +#endif // TOOLS_ENABLED +} + +bool SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_invert(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), false, "CCDIK joint out of range!"); + return ccdik_data_chain[p_joint_idx].constraint_angle_invert; +} + +void SkeletonModification2DCCDIK::set_ccdik_joint_constraint_in_localspace(int p_joint_idx, bool p_constraint_in_localspace) { + ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); + ccdik_data_chain.write[p_joint_idx].constraint_in_localspace = p_constraint_in_localspace; + +#ifdef TOOLS_ENABLED + if (stack && is_setup) { + stack->set_editor_gizmos_dirty(true); + } +#endif // TOOLS_ENABLED +} + +bool SkeletonModification2DCCDIK::get_ccdik_joint_constraint_in_localspace(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), false, "CCDIK joint out of range!"); + return ccdik_data_chain[p_joint_idx].constraint_in_localspace; +} + +void SkeletonModification2DCCDIK::set_ccdik_joint_editor_draw_gizmo(int p_joint_idx, bool p_draw_gizmo) { + ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); + ccdik_data_chain.write[p_joint_idx].editor_draw_gizmo = p_draw_gizmo; + +#ifdef TOOLS_ENABLED + if (stack && is_setup) { + stack->set_editor_gizmos_dirty(true); + } +#endif // TOOLS_ENABLED +} + +bool SkeletonModification2DCCDIK::get_ccdik_joint_editor_draw_gizmo(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), false, "CCDIK joint out of range!"); + return ccdik_data_chain[p_joint_idx].editor_draw_gizmo; +} + +void SkeletonModification2DCCDIK::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DCCDIK::set_target_node); + ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DCCDIK::get_target_node); + ClassDB::bind_method(D_METHOD("set_tip_node", "tip_nodepath"), &SkeletonModification2DCCDIK::set_tip_node); + ClassDB::bind_method(D_METHOD("get_tip_node"), &SkeletonModification2DCCDIK::get_tip_node); + + ClassDB::bind_method(D_METHOD("set_ccdik_data_chain_length", "length"), &SkeletonModification2DCCDIK::set_ccdik_data_chain_length); + ClassDB::bind_method(D_METHOD("get_ccdik_data_chain_length"), &SkeletonModification2DCCDIK::get_ccdik_data_chain_length); + + ClassDB::bind_method(D_METHOD("set_ccdik_joint_bone2d_node", "joint_idx", "bone2d_nodepath"), &SkeletonModification2DCCDIK::set_ccdik_joint_bone2d_node); + ClassDB::bind_method(D_METHOD("get_ccdik_joint_bone2d_node", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_bone2d_node); + ClassDB::bind_method(D_METHOD("set_ccdik_joint_bone_index", "joint_idx", "bone_idx"), &SkeletonModification2DCCDIK::set_ccdik_joint_bone_index); + ClassDB::bind_method(D_METHOD("get_ccdik_joint_bone_index", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_bone_index); + ClassDB::bind_method(D_METHOD("set_ccdik_joint_rotate_from_joint", "joint_idx", "rotate_from_joint"), &SkeletonModification2DCCDIK::set_ccdik_joint_rotate_from_joint); + ClassDB::bind_method(D_METHOD("get_ccdik_joint_rotate_from_joint", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_rotate_from_joint); + ClassDB::bind_method(D_METHOD("set_ccdik_joint_enable_constraint", "joint_idx", "enable_constraint"), &SkeletonModification2DCCDIK::set_ccdik_joint_enable_constraint); + ClassDB::bind_method(D_METHOD("get_ccdik_joint_enable_constraint", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_enable_constraint); + ClassDB::bind_method(D_METHOD("set_ccdik_joint_constraint_angle_min", "joint_idx", "angle_min"), &SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_min); + ClassDB::bind_method(D_METHOD("get_ccdik_joint_constraint_angle_min", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_min); + ClassDB::bind_method(D_METHOD("set_ccdik_joint_constraint_angle_max", "joint_idx", "angle_max"), &SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_max); + ClassDB::bind_method(D_METHOD("get_ccdik_joint_constraint_angle_max", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_max); + ClassDB::bind_method(D_METHOD("set_ccdik_joint_constraint_angle_invert", "joint_idx", "invert"), &SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_invert); + ClassDB::bind_method(D_METHOD("get_ccdik_joint_constraint_angle_invert", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_invert); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "tip_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_tip_node", "get_tip_node"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "ccdik_data_chain_length", PROPERTY_HINT_RANGE, "0, 100, 1"), "set_ccdik_data_chain_length", "get_ccdik_data_chain_length"); +} + +SkeletonModification2DCCDIK::SkeletonModification2DCCDIK() { + stack = nullptr; + is_setup = false; + enabled = true; + editor_draw_gizmo = true; +} + +SkeletonModification2DCCDIK::~SkeletonModification2DCCDIK() { +} diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.h b/scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.h new file mode 100644 index 0000000000..34da89c31c --- /dev/null +++ b/scene/resources/2d/skeleton/skeleton_modification_2d_ccdik.h @@ -0,0 +1,116 @@ +/**************************************************************************/ +/* skeleton_modification_2d_ccdik.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 SKELETON_MODIFICATION_2D_CCDIK_H +#define SKELETON_MODIFICATION_2D_CCDIK_H + +#include "scene/2d/skeleton_2d.h" +#include "scene/resources/2d/skeleton/skeleton_modification_2d.h" + +/////////////////////////////////////// +// SkeletonModification2DCCDIK +/////////////////////////////////////// + +class SkeletonModification2DCCDIK : public SkeletonModification2D { + GDCLASS(SkeletonModification2DCCDIK, SkeletonModification2D); + +private: + struct CCDIK_Joint_Data2D { + int bone_idx = -1; + NodePath bone2d_node; + ObjectID bone2d_node_cache; + bool rotate_from_joint = false; + + bool enable_constraint = false; + float constraint_angle_min = 0; + float constraint_angle_max = (2.0 * Math_PI); + bool constraint_angle_invert = false; + bool constraint_in_localspace = true; + + bool editor_draw_gizmo = true; + }; + + Vector ccdik_data_chain; + + NodePath target_node; + ObjectID target_node_cache; + void update_target_cache(); + + NodePath tip_node; + ObjectID tip_node_cache; + void update_tip_cache(); + + void ccdik_joint_update_bone2d_cache(int p_joint_idx); + void _execute_ccdik_joint(int p_joint_idx, Node2D *p_target, Node2D *p_tip); + +protected: + static void _bind_methods(); + bool _set(const StringName &p_path, const Variant &p_value); + bool _get(const StringName &p_path, Variant &r_ret) const; + void _get_property_list(List *p_list) const; + +public: + void _execute(float p_delta) override; + void _setup_modification(SkeletonModificationStack2D *p_stack) override; + void _draw_editor_gizmo() override; + + void set_target_node(const NodePath &p_target_node); + NodePath get_target_node() const; + void set_tip_node(const NodePath &p_tip_node); + NodePath get_tip_node() const; + + int get_ccdik_data_chain_length(); + void set_ccdik_data_chain_length(int p_new_length); + + void set_ccdik_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node); + NodePath get_ccdik_joint_bone2d_node(int p_joint_idx) const; + void set_ccdik_joint_bone_index(int p_joint_idx, int p_bone_idx); + int get_ccdik_joint_bone_index(int p_joint_idx) const; + + void set_ccdik_joint_rotate_from_joint(int p_joint_idx, bool p_rotate_from_joint); + bool get_ccdik_joint_rotate_from_joint(int p_joint_idx) const; + void set_ccdik_joint_enable_constraint(int p_joint_idx, bool p_constraint); + bool get_ccdik_joint_enable_constraint(int p_joint_idx) const; + void set_ccdik_joint_constraint_angle_min(int p_joint_idx, float p_angle_min); + float get_ccdik_joint_constraint_angle_min(int p_joint_idx) const; + void set_ccdik_joint_constraint_angle_max(int p_joint_idx, float p_angle_max); + float get_ccdik_joint_constraint_angle_max(int p_joint_idx) const; + void set_ccdik_joint_constraint_angle_invert(int p_joint_idx, bool p_invert); + bool get_ccdik_joint_constraint_angle_invert(int p_joint_idx) const; + void set_ccdik_joint_constraint_in_localspace(int p_joint_idx, bool p_constraint_in_localspace); + bool get_ccdik_joint_constraint_in_localspace(int p_joint_idx) const; + void set_ccdik_joint_editor_draw_gizmo(int p_joint_idx, bool p_draw_gizmo); + bool get_ccdik_joint_editor_draw_gizmo(int p_joint_idx) const; + + SkeletonModification2DCCDIK(); + ~SkeletonModification2DCCDIK(); +}; + +#endif // SKELETON_MODIFICATION_2D_CCDIK_H diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.cpp new file mode 100644 index 0000000000..dd1c4a91d5 --- /dev/null +++ b/scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.cpp @@ -0,0 +1,452 @@ +/**************************************************************************/ +/* skeleton_modification_2d_fabrik.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 "skeleton_modification_2d_fabrik.h" +#include "scene/2d/skeleton_2d.h" + +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#endif // TOOLS_ENABLED + +bool SkeletonModification2DFABRIK::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.begins_with("joint_data/")) { + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, fabrik_data_chain.size(), false); + + if (what == "bone2d_node") { + set_fabrik_joint_bone2d_node(which, p_value); + } else if (what == "bone_index") { + set_fabrik_joint_bone_index(which, p_value); + } else if (what == "magnet_position") { + set_fabrik_joint_magnet_position(which, p_value); + } else if (what == "use_target_rotation") { + set_fabrik_joint_use_target_rotation(which, p_value); + } else { + return false; + } + } else { + return false; + } + + return true; +} + +bool SkeletonModification2DFABRIK::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.begins_with("joint_data/")) { + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, fabrik_data_chain.size(), false); + + if (what == "bone2d_node") { + r_ret = get_fabrik_joint_bone2d_node(which); + } else if (what == "bone_index") { + r_ret = get_fabrik_joint_bone_index(which); + } else if (what == "magnet_position") { + r_ret = get_fabrik_joint_magnet_position(which); + } else if (what == "use_target_rotation") { + r_ret = get_fabrik_joint_use_target_rotation(which); + } else { + return false; + } + } else { + return false; + } + return true; +} + +void SkeletonModification2DFABRIK::_get_property_list(List *p_list) const { + for (int i = 0; i < fabrik_data_chain.size(); i++) { + String base_string = "joint_data/" + itos(i) + "/"; + + p_list->push_back(PropertyInfo(Variant::INT, base_string + "bone_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT)); + + if (i > 0) { + p_list->push_back(PropertyInfo(Variant::VECTOR2, base_string + "magnet_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } + if (i == fabrik_data_chain.size() - 1) { + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "use_target_rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } + } +} + +void SkeletonModification2DFABRIK::_execute(float p_delta) { + ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot execute!"); + if (!enabled) { + return; + } + + if (target_node_cache.is_null()) { + WARN_PRINT_ONCE("Target cache is out of date. Attempting to update..."); + update_target_cache(); + return; + } + + if (fabrik_data_chain.size() <= 1) { + ERR_PRINT_ONCE("FABRIK requires at least two joints to operate! Cannot execute modification!"); + return; + } + + Node2D *target = Object::cast_to(ObjectDB::get_instance(target_node_cache)); + if (!target || !target->is_inside_tree()) { + ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!"); + return; + } + target_global_pose = target->get_global_transform(); + + if (fabrik_data_chain[0].bone2d_node_cache.is_null() && !fabrik_data_chain[0].bone2d_node.is_empty()) { + fabrik_joint_update_bone2d_cache(0); + WARN_PRINT("Bone2D cache for origin joint is out of date. Updating..."); + } + + Bone2D *origin_bone2d_node = Object::cast_to(ObjectDB::get_instance(fabrik_data_chain[0].bone2d_node_cache)); + if (!origin_bone2d_node || !origin_bone2d_node->is_inside_tree()) { + ERR_PRINT_ONCE("Origin joint's Bone2D node is not in the scene tree. Cannot execute modification!"); + return; + } + + origin_global_pose = origin_bone2d_node->get_global_transform(); + + if (fabrik_transform_chain.size() != fabrik_data_chain.size()) { + fabrik_transform_chain.resize(fabrik_data_chain.size()); + } + + for (int i = 0; i < fabrik_data_chain.size(); i++) { + // Update the transform chain + if (fabrik_data_chain[i].bone2d_node_cache.is_null() && !fabrik_data_chain[i].bone2d_node.is_empty()) { + WARN_PRINT_ONCE("Bone2D cache for joint " + itos(i) + " is out of date.. Attempting to update..."); + fabrik_joint_update_bone2d_cache(i); + } + Bone2D *joint_bone2d_node = Object::cast_to(ObjectDB::get_instance(fabrik_data_chain[i].bone2d_node_cache)); + if (!joint_bone2d_node) { + ERR_PRINT_ONCE("FABRIK Joint " + itos(i) + " does not have a Bone2D node set! Cannot execute modification!"); + return; + } + fabrik_transform_chain.write[i] = joint_bone2d_node->get_global_transform(); + } + + Bone2D *final_bone2d_node = Object::cast_to(ObjectDB::get_instance(fabrik_data_chain[fabrik_data_chain.size() - 1].bone2d_node_cache)); + float final_bone2d_angle = final_bone2d_node->get_global_rotation(); + if (fabrik_data_chain[fabrik_data_chain.size() - 1].use_target_rotation) { + final_bone2d_angle = target_global_pose.get_rotation(); + } + Vector2 final_bone2d_direction = Vector2(Math::cos(final_bone2d_angle), Math::sin(final_bone2d_angle)); + float final_bone2d_length = final_bone2d_node->get_length() * MIN(final_bone2d_node->get_global_scale().x, final_bone2d_node->get_global_scale().y); + float target_distance = (final_bone2d_node->get_global_position() + (final_bone2d_direction * final_bone2d_length)).distance_to(target->get_global_position()); + chain_iterations = 0; + + while (target_distance > chain_tolarance) { + chain_backwards(); + chain_forwards(); + + final_bone2d_angle = final_bone2d_node->get_global_rotation(); + if (fabrik_data_chain[fabrik_data_chain.size() - 1].use_target_rotation) { + final_bone2d_angle = target_global_pose.get_rotation(); + } + final_bone2d_direction = Vector2(Math::cos(final_bone2d_angle), Math::sin(final_bone2d_angle)); + target_distance = (final_bone2d_node->get_global_position() + (final_bone2d_direction * final_bone2d_length)).distance_to(target->get_global_position()); + + chain_iterations += 1; + if (chain_iterations >= chain_max_iterations) { + break; + } + } + + // Apply all of the saved transforms to the Bone2D nodes + for (int i = 0; i < fabrik_data_chain.size(); i++) { + Bone2D *joint_bone2d_node = Object::cast_to(ObjectDB::get_instance(fabrik_data_chain[i].bone2d_node_cache)); + if (!joint_bone2d_node) { + ERR_PRINT_ONCE("FABRIK Joint " + itos(i) + " does not have a Bone2D node set!"); + continue; + } + Transform2D chain_trans = fabrik_transform_chain[i]; + + // Apply rotation + if (i + 1 < fabrik_data_chain.size()) { + chain_trans = chain_trans.looking_at(fabrik_transform_chain[i + 1].get_origin()); + } else { + if (fabrik_data_chain[i].use_target_rotation) { + chain_trans.set_rotation(target_global_pose.get_rotation()); + } else { + chain_trans = chain_trans.looking_at(target_global_pose.get_origin()); + } + } + // Adjust for the bone angle + chain_trans.set_rotation(chain_trans.get_rotation() - joint_bone2d_node->get_bone_angle()); + + // Reset scale + chain_trans.set_scale(joint_bone2d_node->get_global_scale()); + + // Apply to the bone, and to the override + joint_bone2d_node->set_global_transform(chain_trans); + stack->skeleton->set_bone_local_pose_override(fabrik_data_chain[i].bone_idx, joint_bone2d_node->get_transform(), stack->strength, true); + } +} + +void SkeletonModification2DFABRIK::chain_backwards() { + int final_joint_index = fabrik_data_chain.size() - 1; + Bone2D *final_bone2d_node = Object::cast_to(ObjectDB::get_instance(fabrik_data_chain[final_joint_index].bone2d_node_cache)); + Transform2D final_bone2d_trans = fabrik_transform_chain[final_joint_index]; + + // Apply magnet position + if (final_joint_index != 0) { + final_bone2d_trans.set_origin(final_bone2d_trans.get_origin() + fabrik_data_chain[final_joint_index].magnet_position); + } + + // Set the rotation of the tip bone + final_bone2d_trans = final_bone2d_trans.looking_at(target_global_pose.get_origin()); + + // Set the position of the tip bone + float final_bone2d_angle = final_bone2d_trans.get_rotation(); + if (fabrik_data_chain[final_joint_index].use_target_rotation) { + final_bone2d_angle = target_global_pose.get_rotation(); + } + Vector2 final_bone2d_direction = Vector2(Math::cos(final_bone2d_angle), Math::sin(final_bone2d_angle)); + float final_bone2d_length = final_bone2d_node->get_length() * MIN(final_bone2d_node->get_global_scale().x, final_bone2d_node->get_global_scale().y); + final_bone2d_trans.set_origin(target_global_pose.get_origin() - (final_bone2d_direction * final_bone2d_length)); + + // Save the transform + fabrik_transform_chain.write[final_joint_index] = final_bone2d_trans; + + int i = final_joint_index; + while (i >= 1) { + Transform2D previous_pose = fabrik_transform_chain[i]; + i -= 1; + Bone2D *current_bone2d_node = Object::cast_to(ObjectDB::get_instance(fabrik_data_chain[i].bone2d_node_cache)); + Transform2D current_pose = fabrik_transform_chain[i]; + + // Apply magnet position + if (i != 0) { + current_pose.set_origin(current_pose.get_origin() + fabrik_data_chain[i].magnet_position); + } + + float current_bone2d_node_length = current_bone2d_node->get_length() * MIN(current_bone2d_node->get_global_scale().x, current_bone2d_node->get_global_scale().y); + float length = current_bone2d_node_length / (current_pose.get_origin().distance_to(previous_pose.get_origin())); + Vector2 finish_position = previous_pose.get_origin().lerp(current_pose.get_origin(), length); + current_pose.set_origin(finish_position); + + // Save the transform + fabrik_transform_chain.write[i] = current_pose; + } +} + +void SkeletonModification2DFABRIK::chain_forwards() { + Transform2D origin_bone2d_trans = fabrik_transform_chain[0]; + origin_bone2d_trans.set_origin(origin_global_pose.get_origin()); + // Save the position + fabrik_transform_chain.write[0] = origin_bone2d_trans; + + for (int i = 0; i < fabrik_data_chain.size() - 1; i++) { + Bone2D *current_bone2d_node = Object::cast_to(ObjectDB::get_instance(fabrik_data_chain[i].bone2d_node_cache)); + Transform2D current_pose = fabrik_transform_chain[i]; + Transform2D next_pose = fabrik_transform_chain[i + 1]; + + float current_bone2d_node_length = current_bone2d_node->get_length() * MIN(current_bone2d_node->get_global_scale().x, current_bone2d_node->get_global_scale().y); + float length = current_bone2d_node_length / (next_pose.get_origin().distance_to(current_pose.get_origin())); + Vector2 finish_position = current_pose.get_origin().lerp(next_pose.get_origin(), length); + current_pose.set_origin(finish_position); + + // Apply to the bone + fabrik_transform_chain.write[i + 1] = current_pose; + } +} + +void SkeletonModification2DFABRIK::_setup_modification(SkeletonModificationStack2D *p_stack) { + stack = p_stack; + + if (stack != nullptr) { + is_setup = true; + update_target_cache(); + } +} + +void SkeletonModification2DFABRIK::update_target_cache() { + if (!is_setup || !stack) { + ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + return; + } + + target_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(target_node)) { + Node *node = stack->skeleton->get_node(target_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update target cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update target cache: node is not in scene tree!"); + target_node_cache = node->get_instance_id(); + } + } + } +} + +void SkeletonModification2DFABRIK::fabrik_joint_update_bone2d_cache(int p_joint_idx) { + ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "Cannot update bone2d cache: joint index out of range!"); + if (!is_setup || !stack) { + ERR_PRINT_ONCE("Cannot update FABRIK Bone2D cache: modification is not properly setup!"); + return; + } + + fabrik_data_chain.write[p_joint_idx].bone2d_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(fabrik_data_chain[p_joint_idx].bone2d_node)) { + Node *node = stack->skeleton->get_node(fabrik_data_chain[p_joint_idx].bone2d_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update FABRIK joint " + itos(p_joint_idx) + " Bone2D cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update FABRIK joint " + itos(p_joint_idx) + " Bone2D cache: node is not in scene tree!"); + fabrik_data_chain.write[p_joint_idx].bone2d_node_cache = node->get_instance_id(); + + Bone2D *bone = Object::cast_to(node); + if (bone) { + fabrik_data_chain.write[p_joint_idx].bone_idx = bone->get_index_in_skeleton(); + } else { + ERR_FAIL_MSG("FABRIK joint " + itos(p_joint_idx) + " Bone2D cache: Nodepath to Bone2D is not a Bone2D node!"); + } + } + } + } +} + +void SkeletonModification2DFABRIK::set_target_node(const NodePath &p_target_node) { + target_node = p_target_node; + update_target_cache(); +} + +NodePath SkeletonModification2DFABRIK::get_target_node() const { + return target_node; +} + +void SkeletonModification2DFABRIK::set_fabrik_data_chain_length(int p_length) { + fabrik_data_chain.resize(p_length); + notify_property_list_changed(); +} + +int SkeletonModification2DFABRIK::get_fabrik_data_chain_length() { + return fabrik_data_chain.size(); +} + +void SkeletonModification2DFABRIK::set_fabrik_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node) { + ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "FABRIK joint out of range!"); + fabrik_data_chain.write[p_joint_idx].bone2d_node = p_target_node; + fabrik_joint_update_bone2d_cache(p_joint_idx); + + notify_property_list_changed(); +} + +NodePath SkeletonModification2DFABRIK::get_fabrik_joint_bone2d_node(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, fabrik_data_chain.size(), NodePath(), "FABRIK joint out of range!"); + return fabrik_data_chain[p_joint_idx].bone2d_node; +} + +void SkeletonModification2DFABRIK::set_fabrik_joint_bone_index(int p_joint_idx, int p_bone_idx) { + ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "FABRIK joint out of range!"); + ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); + + if (is_setup) { + if (stack->skeleton) { + ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); + fabrik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; + fabrik_data_chain.write[p_joint_idx].bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id(); + fabrik_data_chain.write[p_joint_idx].bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx)); + } else { + WARN_PRINT("Cannot verify the FABRIK joint " + itos(p_joint_idx) + " bone index for this modification..."); + fabrik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; + } + } else { + WARN_PRINT("Cannot verify the FABRIK joint " + itos(p_joint_idx) + " bone index for this modification..."); + fabrik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; + } + + notify_property_list_changed(); +} + +int SkeletonModification2DFABRIK::get_fabrik_joint_bone_index(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, fabrik_data_chain.size(), -1, "FABRIK joint out of range!"); + return fabrik_data_chain[p_joint_idx].bone_idx; +} + +void SkeletonModification2DFABRIK::set_fabrik_joint_magnet_position(int p_joint_idx, Vector2 p_magnet_position) { + ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "FABRIK joint out of range!"); + fabrik_data_chain.write[p_joint_idx].magnet_position = p_magnet_position; +} + +Vector2 SkeletonModification2DFABRIK::get_fabrik_joint_magnet_position(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, fabrik_data_chain.size(), Vector2(), "FABRIK joint out of range!"); + return fabrik_data_chain[p_joint_idx].magnet_position; +} + +void SkeletonModification2DFABRIK::set_fabrik_joint_use_target_rotation(int p_joint_idx, bool p_use_target_rotation) { + ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "FABRIK joint out of range!"); + fabrik_data_chain.write[p_joint_idx].use_target_rotation = p_use_target_rotation; +} + +bool SkeletonModification2DFABRIK::get_fabrik_joint_use_target_rotation(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, fabrik_data_chain.size(), false, "FABRIK joint out of range!"); + return fabrik_data_chain[p_joint_idx].use_target_rotation; +} + +void SkeletonModification2DFABRIK::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DFABRIK::set_target_node); + ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DFABRIK::get_target_node); + + ClassDB::bind_method(D_METHOD("set_fabrik_data_chain_length", "length"), &SkeletonModification2DFABRIK::set_fabrik_data_chain_length); + ClassDB::bind_method(D_METHOD("get_fabrik_data_chain_length"), &SkeletonModification2DFABRIK::get_fabrik_data_chain_length); + + ClassDB::bind_method(D_METHOD("set_fabrik_joint_bone2d_node", "joint_idx", "bone2d_nodepath"), &SkeletonModification2DFABRIK::set_fabrik_joint_bone2d_node); + ClassDB::bind_method(D_METHOD("get_fabrik_joint_bone2d_node", "joint_idx"), &SkeletonModification2DFABRIK::get_fabrik_joint_bone2d_node); + ClassDB::bind_method(D_METHOD("set_fabrik_joint_bone_index", "joint_idx", "bone_idx"), &SkeletonModification2DFABRIK::set_fabrik_joint_bone_index); + ClassDB::bind_method(D_METHOD("get_fabrik_joint_bone_index", "joint_idx"), &SkeletonModification2DFABRIK::get_fabrik_joint_bone_index); + ClassDB::bind_method(D_METHOD("set_fabrik_joint_magnet_position", "joint_idx", "magnet_position"), &SkeletonModification2DFABRIK::set_fabrik_joint_magnet_position); + ClassDB::bind_method(D_METHOD("get_fabrik_joint_magnet_position", "joint_idx"), &SkeletonModification2DFABRIK::get_fabrik_joint_magnet_position); + ClassDB::bind_method(D_METHOD("set_fabrik_joint_use_target_rotation", "joint_idx", "use_target_rotation"), &SkeletonModification2DFABRIK::set_fabrik_joint_use_target_rotation); + ClassDB::bind_method(D_METHOD("get_fabrik_joint_use_target_rotation", "joint_idx"), &SkeletonModification2DFABRIK::get_fabrik_joint_use_target_rotation); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "fabrik_data_chain_length", PROPERTY_HINT_RANGE, "0, 100, 1"), "set_fabrik_data_chain_length", "get_fabrik_data_chain_length"); +} + +SkeletonModification2DFABRIK::SkeletonModification2DFABRIK() { + stack = nullptr; + is_setup = false; + enabled = true; + editor_draw_gizmo = false; +} + +SkeletonModification2DFABRIK::~SkeletonModification2DFABRIK() { +} diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.h b/scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.h new file mode 100644 index 0000000000..edc29ecf12 --- /dev/null +++ b/scene/resources/2d/skeleton/skeleton_modification_2d_fabrik.h @@ -0,0 +1,108 @@ +/**************************************************************************/ +/* skeleton_modification_2d_fabrik.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 SKELETON_MODIFICATION_2D_FABRIK_H +#define SKELETON_MODIFICATION_2D_FABRIK_H + +#include "scene/2d/skeleton_2d.h" +#include "scene/resources/2d/skeleton/skeleton_modification_2d.h" + +/////////////////////////////////////// +// SkeletonModification2DFABRIK +/////////////////////////////////////// + +class SkeletonModification2DFABRIK : public SkeletonModification2D { + GDCLASS(SkeletonModification2DFABRIK, SkeletonModification2D); + +private: + struct FABRIK_Joint_Data2D { + int bone_idx = -1; + NodePath bone2d_node; + ObjectID bone2d_node_cache; + + Vector2 magnet_position = Vector2(0, 0); + bool use_target_rotation = false; + + bool editor_draw_gizmo = true; + }; + + Vector fabrik_data_chain; + + // Unlike in 3D, we need a vector of Transform2D objects to perform FABRIK. + // This is because FABRIK (unlike CCDIK) needs to operate on transforms that are NOT + // affected by each other, making the transforms stored in Bone2D unusable, as well as those in Skeleton2D. + // For this reason, this modification stores a vector of Transform2Ds used for the calculations, which are then applied at the end. + Vector fabrik_transform_chain; + + NodePath target_node; + ObjectID target_node_cache; + void update_target_cache(); + + float chain_tolarance = 0.01; + int chain_max_iterations = 10; + int chain_iterations = 0; + Transform2D target_global_pose; + Transform2D origin_global_pose; + + void fabrik_joint_update_bone2d_cache(int p_joint_idx); + void chain_backwards(); + void chain_forwards(); + +protected: + static void _bind_methods(); + bool _set(const StringName &p_path, const Variant &p_value); + bool _get(const StringName &p_path, Variant &r_ret) const; + void _get_property_list(List *p_list) const; + +public: + void _execute(float p_delta) override; + void _setup_modification(SkeletonModificationStack2D *p_stack) override; + + void set_target_node(const NodePath &p_target_node); + NodePath get_target_node() const; + + int get_fabrik_data_chain_length(); + void set_fabrik_data_chain_length(int p_new_length); + + void set_fabrik_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node); + NodePath get_fabrik_joint_bone2d_node(int p_joint_idx) const; + void set_fabrik_joint_bone_index(int p_joint_idx, int p_bone_idx); + int get_fabrik_joint_bone_index(int p_joint_idx) const; + + void set_fabrik_joint_magnet_position(int p_joint_idx, Vector2 p_magnet_position); + Vector2 get_fabrik_joint_magnet_position(int p_joint_idx) const; + void set_fabrik_joint_use_target_rotation(int p_joint_idx, bool p_use_target_rotation); + bool get_fabrik_joint_use_target_rotation(int p_joint_idx) const; + + SkeletonModification2DFABRIK(); + ~SkeletonModification2DFABRIK(); +}; + +#endif // SKELETON_MODIFICATION_2D_FABRIK_H diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.cpp new file mode 100644 index 0000000000..2ace9577e4 --- /dev/null +++ b/scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.cpp @@ -0,0 +1,572 @@ +/**************************************************************************/ +/* skeleton_modification_2d_jiggle.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 "skeleton_modification_2d_jiggle.h" + +#include "scene/2d/skeleton_2d.h" +#include "scene/resources/world_2d.h" + +bool SkeletonModification2DJiggle::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.begins_with("joint_data/")) { + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, jiggle_data_chain.size(), false); + + if (what == "bone2d_node") { + set_jiggle_joint_bone2d_node(which, p_value); + } else if (what == "bone_index") { + set_jiggle_joint_bone_index(which, p_value); + } else if (what == "override_defaults") { + set_jiggle_joint_override(which, p_value); + } else if (what == "stiffness") { + set_jiggle_joint_stiffness(which, p_value); + } else if (what == "mass") { + set_jiggle_joint_mass(which, p_value); + } else if (what == "damping") { + set_jiggle_joint_damping(which, p_value); + } else if (what == "use_gravity") { + set_jiggle_joint_use_gravity(which, p_value); + } else if (what == "gravity") { + set_jiggle_joint_gravity(which, p_value); + } else { + return false; + } + } else if (path == "use_colliders") { + set_use_colliders(p_value); + } else if (path == "collision_mask") { + set_collision_mask(p_value); + } else { + return false; + } + return true; +} + +bool SkeletonModification2DJiggle::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.begins_with("joint_data/")) { + int which = path.get_slicec('/', 1).to_int(); + String what = path.get_slicec('/', 2); + ERR_FAIL_INDEX_V(which, jiggle_data_chain.size(), false); + + if (what == "bone2d_node") { + r_ret = get_jiggle_joint_bone2d_node(which); + } else if (what == "bone_index") { + r_ret = get_jiggle_joint_bone_index(which); + } else if (what == "override_defaults") { + r_ret = get_jiggle_joint_override(which); + } else if (what == "stiffness") { + r_ret = get_jiggle_joint_stiffness(which); + } else if (what == "mass") { + r_ret = get_jiggle_joint_mass(which); + } else if (what == "damping") { + r_ret = get_jiggle_joint_damping(which); + } else if (what == "use_gravity") { + r_ret = get_jiggle_joint_use_gravity(which); + } else if (what == "gravity") { + r_ret = get_jiggle_joint_gravity(which); + } else { + return false; + } + } else if (path == "use_colliders") { + r_ret = get_use_colliders(); + } else if (path == "collision_mask") { + r_ret = get_collision_mask(); + } else { + return false; + } + return true; +} + +void SkeletonModification2DJiggle::_get_property_list(List *p_list) const { + p_list->push_back(PropertyInfo(Variant::BOOL, "use_colliders", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + if (use_colliders) { + p_list->push_back(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS, "", PROPERTY_USAGE_DEFAULT)); + } + + for (int i = 0; i < jiggle_data_chain.size(); i++) { + String base_string = "joint_data/" + itos(i) + "/"; + + p_list->push_back(PropertyInfo(Variant::INT, base_string + "bone_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "override_defaults", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + + if (jiggle_data_chain[i].override_defaults) { + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "stiffness", PROPERTY_HINT_RANGE, "0, 1000, 0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "mass", PROPERTY_HINT_RANGE, "0, 1000, 0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "damping", PROPERTY_HINT_RANGE, "0, 1, 0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "use_gravity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + if (jiggle_data_chain[i].use_gravity) { + p_list->push_back(PropertyInfo(Variant::VECTOR2, base_string + "gravity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } + } + } +} + +void SkeletonModification2DJiggle::_execute(float p_delta) { + ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot execute!"); + if (!enabled) { + return; + } + if (target_node_cache.is_null()) { + WARN_PRINT_ONCE("Target cache is out of date. Attempting to update..."); + update_target_cache(); + return; + } + Node2D *target = Object::cast_to(ObjectDB::get_instance(target_node_cache)); + if (!target || !target->is_inside_tree()) { + ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!"); + return; + } + + for (int i = 0; i < jiggle_data_chain.size(); i++) { + _execute_jiggle_joint(i, target, p_delta); + } +} + +void SkeletonModification2DJiggle::_execute_jiggle_joint(int p_joint_idx, Node2D *p_target, float p_delta) { + // Adopted from: https://wiki.unity3d.com/index.php/JiggleBone + // With modifications by TwistedTwigleg. + + if (jiggle_data_chain[p_joint_idx].bone_idx <= -1 || jiggle_data_chain[p_joint_idx].bone_idx > stack->skeleton->get_bone_count()) { + ERR_PRINT_ONCE("Jiggle joint " + itos(p_joint_idx) + " bone index is invalid. Cannot execute modification on joint..."); + return; + } + + if (jiggle_data_chain[p_joint_idx].bone2d_node_cache.is_null() && !jiggle_data_chain[p_joint_idx].bone2d_node.is_empty()) { + WARN_PRINT_ONCE("Bone2D cache for joint " + itos(p_joint_idx) + " is out of date. Updating..."); + jiggle_joint_update_bone2d_cache(p_joint_idx); + } + + Bone2D *operation_bone = stack->skeleton->get_bone(jiggle_data_chain[p_joint_idx].bone_idx); + if (!operation_bone) { + ERR_PRINT_ONCE("Jiggle joint " + itos(p_joint_idx) + " does not have a Bone2D node or it cannot be found!"); + return; + } + + Transform2D operation_bone_trans = operation_bone->get_global_transform(); + Vector2 target_position = p_target->get_global_position(); + + jiggle_data_chain.write[p_joint_idx].force = (target_position - jiggle_data_chain[p_joint_idx].dynamic_position) * jiggle_data_chain[p_joint_idx].stiffness * p_delta; + + if (jiggle_data_chain[p_joint_idx].use_gravity) { + jiggle_data_chain.write[p_joint_idx].force += jiggle_data_chain[p_joint_idx].gravity * p_delta; + } + + jiggle_data_chain.write[p_joint_idx].acceleration = jiggle_data_chain[p_joint_idx].force / jiggle_data_chain[p_joint_idx].mass; + jiggle_data_chain.write[p_joint_idx].velocity += jiggle_data_chain[p_joint_idx].acceleration * (1 - jiggle_data_chain[p_joint_idx].damping); + + jiggle_data_chain.write[p_joint_idx].dynamic_position += jiggle_data_chain[p_joint_idx].velocity + jiggle_data_chain[p_joint_idx].force; + jiggle_data_chain.write[p_joint_idx].dynamic_position += operation_bone_trans.get_origin() - jiggle_data_chain[p_joint_idx].last_position; + jiggle_data_chain.write[p_joint_idx].last_position = operation_bone_trans.get_origin(); + + // Collision detection/response + if (use_colliders) { + if (execution_mode == SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_physics_process) { + Ref world_2d = stack->skeleton->get_world_2d(); + ERR_FAIL_COND(world_2d.is_null()); + PhysicsDirectSpaceState2D *space_state = PhysicsServer2D::get_singleton()->space_get_direct_state(world_2d->get_space()); + PhysicsDirectSpaceState2D::RayResult ray_result; + + PhysicsDirectSpaceState2D::RayParameters ray_params; + ray_params.from = operation_bone_trans.get_origin(); + ray_params.to = jiggle_data_chain[p_joint_idx].dynamic_position; + ray_params.collision_mask = collision_mask; + + // Add exception support? + bool ray_hit = space_state->intersect_ray(ray_params, ray_result); + + if (ray_hit) { + jiggle_data_chain.write[p_joint_idx].dynamic_position = jiggle_data_chain[p_joint_idx].last_noncollision_position; + jiggle_data_chain.write[p_joint_idx].acceleration = Vector2(0, 0); + jiggle_data_chain.write[p_joint_idx].velocity = Vector2(0, 0); + } else { + jiggle_data_chain.write[p_joint_idx].last_noncollision_position = jiggle_data_chain[p_joint_idx].dynamic_position; + } + } else { + WARN_PRINT_ONCE("Jiggle 2D modifier: You cannot detect colliders without the stack mode being set to _physics_process!"); + } + } + + // Rotate the bone using the dynamic position! + operation_bone_trans = operation_bone_trans.looking_at(jiggle_data_chain[p_joint_idx].dynamic_position); + operation_bone_trans.set_rotation(operation_bone_trans.get_rotation() - operation_bone->get_bone_angle()); + + // Reset scale + operation_bone_trans.set_scale(operation_bone->get_global_scale()); + + operation_bone->set_global_transform(operation_bone_trans); + stack->skeleton->set_bone_local_pose_override(jiggle_data_chain[p_joint_idx].bone_idx, operation_bone->get_transform(), stack->strength, true); +} + +void SkeletonModification2DJiggle::_update_jiggle_joint_data() { + for (int i = 0; i < jiggle_data_chain.size(); i++) { + if (!jiggle_data_chain[i].override_defaults) { + set_jiggle_joint_stiffness(i, stiffness); + set_jiggle_joint_mass(i, mass); + set_jiggle_joint_damping(i, damping); + set_jiggle_joint_use_gravity(i, use_gravity); + set_jiggle_joint_gravity(i, gravity); + } + } +} + +void SkeletonModification2DJiggle::_setup_modification(SkeletonModificationStack2D *p_stack) { + stack = p_stack; + + if (stack) { + is_setup = true; + + if (stack->skeleton) { + for (int i = 0; i < jiggle_data_chain.size(); i++) { + int bone_idx = jiggle_data_chain[i].bone_idx; + if (bone_idx > 0 && bone_idx < stack->skeleton->get_bone_count()) { + Bone2D *bone2d_node = stack->skeleton->get_bone(bone_idx); + jiggle_data_chain.write[i].dynamic_position = bone2d_node->get_global_position(); + } + } + } + + update_target_cache(); + } +} + +void SkeletonModification2DJiggle::update_target_cache() { + if (!is_setup || !stack) { + ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + return; + } + + target_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(target_node)) { + Node *node = stack->skeleton->get_node(target_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update target cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update target cache: node is not in scene tree!"); + target_node_cache = node->get_instance_id(); + } + } + } +} + +void SkeletonModification2DJiggle::jiggle_joint_update_bone2d_cache(int p_joint_idx) { + ERR_FAIL_INDEX_MSG(p_joint_idx, jiggle_data_chain.size(), "Cannot update bone2d cache: joint index out of range!"); + if (!is_setup || !stack) { + ERR_PRINT_ONCE("Cannot update Jiggle " + itos(p_joint_idx) + " Bone2D cache: modification is not properly setup!"); + return; + } + + jiggle_data_chain.write[p_joint_idx].bone2d_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(jiggle_data_chain[p_joint_idx].bone2d_node)) { + Node *node = stack->skeleton->get_node(jiggle_data_chain[p_joint_idx].bone2d_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update Jiggle joint " + itos(p_joint_idx) + " Bone2D cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update Jiggle joint " + itos(p_joint_idx) + " Bone2D cache: node is not in scene tree!"); + jiggle_data_chain.write[p_joint_idx].bone2d_node_cache = node->get_instance_id(); + + Bone2D *bone = Object::cast_to(node); + if (bone) { + jiggle_data_chain.write[p_joint_idx].bone_idx = bone->get_index_in_skeleton(); + } else { + ERR_FAIL_MSG("Jiggle joint " + itos(p_joint_idx) + " Bone2D cache: Nodepath to Bone2D is not a Bone2D node!"); + } + } + } + } +} + +void SkeletonModification2DJiggle::set_target_node(const NodePath &p_target_node) { + target_node = p_target_node; + update_target_cache(); +} + +NodePath SkeletonModification2DJiggle::get_target_node() const { + return target_node; +} + +void SkeletonModification2DJiggle::set_stiffness(float p_stiffness) { + ERR_FAIL_COND_MSG(p_stiffness < 0, "Stiffness cannot be set to a negative value!"); + stiffness = p_stiffness; + _update_jiggle_joint_data(); +} + +float SkeletonModification2DJiggle::get_stiffness() const { + return stiffness; +} + +void SkeletonModification2DJiggle::set_mass(float p_mass) { + ERR_FAIL_COND_MSG(p_mass < 0, "Mass cannot be set to a negative value!"); + mass = p_mass; + _update_jiggle_joint_data(); +} + +float SkeletonModification2DJiggle::get_mass() const { + return mass; +} + +void SkeletonModification2DJiggle::set_damping(float p_damping) { + ERR_FAIL_COND_MSG(p_damping < 0, "Damping cannot be set to a negative value!"); + ERR_FAIL_COND_MSG(p_damping > 1, "Damping cannot be more than one!"); + damping = p_damping; + _update_jiggle_joint_data(); +} + +float SkeletonModification2DJiggle::get_damping() const { + return damping; +} + +void SkeletonModification2DJiggle::set_use_gravity(bool p_use_gravity) { + use_gravity = p_use_gravity; + _update_jiggle_joint_data(); +} + +bool SkeletonModification2DJiggle::get_use_gravity() const { + return use_gravity; +} + +void SkeletonModification2DJiggle::set_gravity(Vector2 p_gravity) { + gravity = p_gravity; + _update_jiggle_joint_data(); +} + +Vector2 SkeletonModification2DJiggle::get_gravity() const { + return gravity; +} + +void SkeletonModification2DJiggle::set_use_colliders(bool p_use_colliders) { + use_colliders = p_use_colliders; + notify_property_list_changed(); +} + +bool SkeletonModification2DJiggle::get_use_colliders() const { + return use_colliders; +} + +void SkeletonModification2DJiggle::set_collision_mask(int p_mask) { + collision_mask = p_mask; +} + +int SkeletonModification2DJiggle::get_collision_mask() const { + return collision_mask; +} + +// Jiggle joint data functions +int SkeletonModification2DJiggle::get_jiggle_data_chain_length() { + return jiggle_data_chain.size(); +} + +void SkeletonModification2DJiggle::set_jiggle_data_chain_length(int p_length) { + ERR_FAIL_COND(p_length < 0); + jiggle_data_chain.resize(p_length); + notify_property_list_changed(); +} + +void SkeletonModification2DJiggle::set_jiggle_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node) { + ERR_FAIL_INDEX_MSG(p_joint_idx, jiggle_data_chain.size(), "Jiggle joint out of range!"); + jiggle_data_chain.write[p_joint_idx].bone2d_node = p_target_node; + jiggle_joint_update_bone2d_cache(p_joint_idx); + + notify_property_list_changed(); +} + +NodePath SkeletonModification2DJiggle::get_jiggle_joint_bone2d_node(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, jiggle_data_chain.size(), NodePath(), "Jiggle joint out of range!"); + return jiggle_data_chain[p_joint_idx].bone2d_node; +} + +void SkeletonModification2DJiggle::set_jiggle_joint_bone_index(int p_joint_idx, int p_bone_idx) { + ERR_FAIL_INDEX_MSG(p_joint_idx, jiggle_data_chain.size(), "Jiggle joint out of range!"); + ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); + + if (is_setup) { + if (stack->skeleton) { + ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); + jiggle_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; + jiggle_data_chain.write[p_joint_idx].bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id(); + jiggle_data_chain.write[p_joint_idx].bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx)); + } else { + WARN_PRINT("Cannot verify the Jiggle joint " + itos(p_joint_idx) + " bone index for this modification..."); + jiggle_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; + } + } else { + WARN_PRINT("Cannot verify the Jiggle joint " + itos(p_joint_idx) + " bone index for this modification..."); + jiggle_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; + } + + notify_property_list_changed(); +} + +int SkeletonModification2DJiggle::get_jiggle_joint_bone_index(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, jiggle_data_chain.size(), -1, "Jiggle joint out of range!"); + return jiggle_data_chain[p_joint_idx].bone_idx; +} + +void SkeletonModification2DJiggle::set_jiggle_joint_override(int p_joint_idx, bool p_override) { + ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size()); + jiggle_data_chain.write[p_joint_idx].override_defaults = p_override; + _update_jiggle_joint_data(); + notify_property_list_changed(); +} + +bool SkeletonModification2DJiggle::get_jiggle_joint_override(int p_joint_idx) const { + ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), false); + return jiggle_data_chain[p_joint_idx].override_defaults; +} + +void SkeletonModification2DJiggle::set_jiggle_joint_stiffness(int p_joint_idx, float p_stiffness) { + ERR_FAIL_COND_MSG(p_stiffness < 0, "Stiffness cannot be set to a negative value!"); + ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size()); + jiggle_data_chain.write[p_joint_idx].stiffness = p_stiffness; +} + +float SkeletonModification2DJiggle::get_jiggle_joint_stiffness(int p_joint_idx) const { + ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), -1); + return jiggle_data_chain[p_joint_idx].stiffness; +} + +void SkeletonModification2DJiggle::set_jiggle_joint_mass(int p_joint_idx, float p_mass) { + ERR_FAIL_COND_MSG(p_mass < 0, "Mass cannot be set to a negative value!"); + ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size()); + jiggle_data_chain.write[p_joint_idx].mass = p_mass; +} + +float SkeletonModification2DJiggle::get_jiggle_joint_mass(int p_joint_idx) const { + ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), -1); + return jiggle_data_chain[p_joint_idx].mass; +} + +void SkeletonModification2DJiggle::set_jiggle_joint_damping(int p_joint_idx, float p_damping) { + ERR_FAIL_COND_MSG(p_damping < 0, "Damping cannot be set to a negative value!"); + ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size()); + jiggle_data_chain.write[p_joint_idx].damping = p_damping; +} + +float SkeletonModification2DJiggle::get_jiggle_joint_damping(int p_joint_idx) const { + ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), -1); + return jiggle_data_chain[p_joint_idx].damping; +} + +void SkeletonModification2DJiggle::set_jiggle_joint_use_gravity(int p_joint_idx, bool p_use_gravity) { + ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size()); + jiggle_data_chain.write[p_joint_idx].use_gravity = p_use_gravity; + notify_property_list_changed(); +} + +bool SkeletonModification2DJiggle::get_jiggle_joint_use_gravity(int p_joint_idx) const { + ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), false); + return jiggle_data_chain[p_joint_idx].use_gravity; +} + +void SkeletonModification2DJiggle::set_jiggle_joint_gravity(int p_joint_idx, Vector2 p_gravity) { + ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size()); + jiggle_data_chain.write[p_joint_idx].gravity = p_gravity; +} + +Vector2 SkeletonModification2DJiggle::get_jiggle_joint_gravity(int p_joint_idx) const { + ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), Vector2(0, 0)); + return jiggle_data_chain[p_joint_idx].gravity; +} + +void SkeletonModification2DJiggle::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DJiggle::set_target_node); + ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DJiggle::get_target_node); + + ClassDB::bind_method(D_METHOD("set_jiggle_data_chain_length", "length"), &SkeletonModification2DJiggle::set_jiggle_data_chain_length); + ClassDB::bind_method(D_METHOD("get_jiggle_data_chain_length"), &SkeletonModification2DJiggle::get_jiggle_data_chain_length); + + ClassDB::bind_method(D_METHOD("set_stiffness", "stiffness"), &SkeletonModification2DJiggle::set_stiffness); + ClassDB::bind_method(D_METHOD("get_stiffness"), &SkeletonModification2DJiggle::get_stiffness); + ClassDB::bind_method(D_METHOD("set_mass", "mass"), &SkeletonModification2DJiggle::set_mass); + ClassDB::bind_method(D_METHOD("get_mass"), &SkeletonModification2DJiggle::get_mass); + ClassDB::bind_method(D_METHOD("set_damping", "damping"), &SkeletonModification2DJiggle::set_damping); + ClassDB::bind_method(D_METHOD("get_damping"), &SkeletonModification2DJiggle::get_damping); + ClassDB::bind_method(D_METHOD("set_use_gravity", "use_gravity"), &SkeletonModification2DJiggle::set_use_gravity); + ClassDB::bind_method(D_METHOD("get_use_gravity"), &SkeletonModification2DJiggle::get_use_gravity); + ClassDB::bind_method(D_METHOD("set_gravity", "gravity"), &SkeletonModification2DJiggle::set_gravity); + ClassDB::bind_method(D_METHOD("get_gravity"), &SkeletonModification2DJiggle::get_gravity); + + ClassDB::bind_method(D_METHOD("set_use_colliders", "use_colliders"), &SkeletonModification2DJiggle::set_use_colliders); + ClassDB::bind_method(D_METHOD("get_use_colliders"), &SkeletonModification2DJiggle::get_use_colliders); + ClassDB::bind_method(D_METHOD("set_collision_mask", "collision_mask"), &SkeletonModification2DJiggle::set_collision_mask); + ClassDB::bind_method(D_METHOD("get_collision_mask"), &SkeletonModification2DJiggle::get_collision_mask); + + // Jiggle joint data functions + ClassDB::bind_method(D_METHOD("set_jiggle_joint_bone2d_node", "joint_idx", "bone2d_node"), &SkeletonModification2DJiggle::set_jiggle_joint_bone2d_node); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_bone2d_node", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_bone2d_node); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_bone_index", "joint_idx", "bone_idx"), &SkeletonModification2DJiggle::set_jiggle_joint_bone_index); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_bone_index", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_bone_index); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_override", "joint_idx", "override"), &SkeletonModification2DJiggle::set_jiggle_joint_override); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_override", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_override); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_stiffness", "joint_idx", "stiffness"), &SkeletonModification2DJiggle::set_jiggle_joint_stiffness); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_stiffness", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_stiffness); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_mass", "joint_idx", "mass"), &SkeletonModification2DJiggle::set_jiggle_joint_mass); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_mass", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_mass); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_damping", "joint_idx", "damping"), &SkeletonModification2DJiggle::set_jiggle_joint_damping); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_damping", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_damping); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_use_gravity", "joint_idx", "use_gravity"), &SkeletonModification2DJiggle::set_jiggle_joint_use_gravity); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_use_gravity", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_use_gravity); + ClassDB::bind_method(D_METHOD("set_jiggle_joint_gravity", "joint_idx", "gravity"), &SkeletonModification2DJiggle::set_jiggle_joint_gravity); + ClassDB::bind_method(D_METHOD("get_jiggle_joint_gravity", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_gravity); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "jiggle_data_chain_length", PROPERTY_HINT_RANGE, "0,100,1"), "set_jiggle_data_chain_length", "get_jiggle_data_chain_length"); + ADD_GROUP("Default Joint Settings", ""); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "stiffness"), "set_stiffness", "get_stiffness"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass"), "set_mass", "get_mass"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping", PROPERTY_HINT_RANGE, "0, 1, 0.01"), "set_damping", "get_damping"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_gravity"), "set_use_gravity", "get_use_gravity"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "gravity"), "set_gravity", "get_gravity"); + ADD_GROUP("", ""); +} + +SkeletonModification2DJiggle::SkeletonModification2DJiggle() { + stack = nullptr; + is_setup = false; + jiggle_data_chain = Vector(); + stiffness = 3; + mass = 0.75; + damping = 0.75; + use_gravity = false; + gravity = Vector2(0, 6.0); + enabled = true; + editor_draw_gizmo = false; // Nothing to really show in a gizmo right now. +} + +SkeletonModification2DJiggle::~SkeletonModification2DJiggle() { +} diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.h b/scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.h new file mode 100644 index 0000000000..7e9855839c --- /dev/null +++ b/scene/resources/2d/skeleton/skeleton_modification_2d_jiggle.h @@ -0,0 +1,139 @@ +/**************************************************************************/ +/* skeleton_modification_2d_jiggle.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 SKELETON_MODIFICATION_2D_JIGGLE_H +#define SKELETON_MODIFICATION_2D_JIGGLE_H + +#include "scene/2d/skeleton_2d.h" +#include "scene/resources/2d/skeleton/skeleton_modification_2d.h" + +/////////////////////////////////////// +// SkeletonModification2DJIGGLE +/////////////////////////////////////// + +class SkeletonModification2DJiggle : public SkeletonModification2D { + GDCLASS(SkeletonModification2DJiggle, SkeletonModification2D); + +private: + struct Jiggle_Joint_Data2D { + int bone_idx = -1; + NodePath bone2d_node; + ObjectID bone2d_node_cache; + + bool override_defaults = false; + float stiffness = 3; + float mass = 0.75; + float damping = 0.75; + bool use_gravity = false; + Vector2 gravity = Vector2(0, 6.0); + + Vector2 force = Vector2(0, 0); + Vector2 acceleration = Vector2(0, 0); + Vector2 velocity = Vector2(0, 0); + Vector2 last_position = Vector2(0, 0); + Vector2 dynamic_position = Vector2(0, 0); + + Vector2 last_noncollision_position = Vector2(0, 0); + }; + + Vector jiggle_data_chain; + + NodePath target_node; + ObjectID target_node_cache; + void update_target_cache(); + + float stiffness = 3; + float mass = 0.75; + float damping = 0.75; + bool use_gravity = false; + Vector2 gravity = Vector2(0, 6); + + bool use_colliders = false; + uint32_t collision_mask = 1; + + void jiggle_joint_update_bone2d_cache(int p_joint_idx); + void _execute_jiggle_joint(int p_joint_idx, Node2D *p_target, float p_delta); + void _update_jiggle_joint_data(); + +protected: + static void _bind_methods(); + bool _set(const StringName &p_path, const Variant &p_value); + bool _get(const StringName &p_path, Variant &r_ret) const; + void _get_property_list(List *p_list) const; + +public: + void _execute(float p_delta) override; + void _setup_modification(SkeletonModificationStack2D *p_stack) override; + + void set_target_node(const NodePath &p_target_node); + NodePath get_target_node() const; + + void set_stiffness(float p_stiffness); + float get_stiffness() const; + void set_mass(float p_mass); + float get_mass() const; + void set_damping(float p_damping); + float get_damping() const; + void set_use_gravity(bool p_use_gravity); + bool get_use_gravity() const; + void set_gravity(Vector2 p_gravity); + Vector2 get_gravity() const; + + void set_use_colliders(bool p_use_colliders); + bool get_use_colliders() const; + void set_collision_mask(int p_mask); + int get_collision_mask() const; + + int get_jiggle_data_chain_length(); + void set_jiggle_data_chain_length(int p_new_length); + + void set_jiggle_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node); + NodePath get_jiggle_joint_bone2d_node(int p_joint_idx) const; + void set_jiggle_joint_bone_index(int p_joint_idx, int p_bone_idx); + int get_jiggle_joint_bone_index(int p_joint_idx) const; + + void set_jiggle_joint_override(int p_joint_idx, bool p_override); + bool get_jiggle_joint_override(int p_joint_idx) const; + void set_jiggle_joint_stiffness(int p_joint_idx, float p_stiffness); + float get_jiggle_joint_stiffness(int p_joint_idx) const; + void set_jiggle_joint_mass(int p_joint_idx, float p_mass); + float get_jiggle_joint_mass(int p_joint_idx) const; + void set_jiggle_joint_damping(int p_joint_idx, float p_damping); + float get_jiggle_joint_damping(int p_joint_idx) const; + void set_jiggle_joint_use_gravity(int p_joint_idx, bool p_use_gravity); + bool get_jiggle_joint_use_gravity(int p_joint_idx) const; + void set_jiggle_joint_gravity(int p_joint_idx, Vector2 p_gravity); + Vector2 get_jiggle_joint_gravity(int p_joint_idx) const; + + SkeletonModification2DJiggle(); + ~SkeletonModification2DJiggle(); +}; + +#endif // SKELETON_MODIFICATION_2D_JIGGLE_H diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_lookat.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d_lookat.cpp new file mode 100644 index 0000000000..8f6f6bc4ae --- /dev/null +++ b/scene/resources/2d/skeleton/skeleton_modification_2d_lookat.cpp @@ -0,0 +1,411 @@ +/**************************************************************************/ +/* skeleton_modification_2d_lookat.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 "skeleton_modification_2d_lookat.h" +#include "scene/2d/skeleton_2d.h" + +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#endif // TOOLS_ENABLED + +bool SkeletonModification2DLookAt::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.begins_with("enable_constraint")) { + set_enable_constraint(p_value); + } else if (path.begins_with("constraint_angle_min")) { + set_constraint_angle_min(Math::deg_to_rad(float(p_value))); + } else if (path.begins_with("constraint_angle_max")) { + set_constraint_angle_max(Math::deg_to_rad(float(p_value))); + } else if (path.begins_with("constraint_angle_invert")) { + set_constraint_angle_invert(p_value); + } else if (path.begins_with("constraint_in_localspace")) { + set_constraint_in_localspace(p_value); + } else if (path.begins_with("additional_rotation")) { + set_additional_rotation(Math::deg_to_rad(float(p_value))); + } +#ifdef TOOLS_ENABLED + else if (path.begins_with("editor/draw_gizmo")) { + set_editor_draw_gizmo(p_value); + } +#endif // TOOLS_ENABLED + else { + return false; + } + + return true; +} + +bool SkeletonModification2DLookAt::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.begins_with("enable_constraint")) { + r_ret = get_enable_constraint(); + } else if (path.begins_with("constraint_angle_min")) { + r_ret = Math::rad_to_deg(get_constraint_angle_min()); + } else if (path.begins_with("constraint_angle_max")) { + r_ret = Math::rad_to_deg(get_constraint_angle_max()); + } else if (path.begins_with("constraint_angle_invert")) { + r_ret = get_constraint_angle_invert(); + } else if (path.begins_with("constraint_in_localspace")) { + r_ret = get_constraint_in_localspace(); + } else if (path.begins_with("additional_rotation")) { + r_ret = Math::rad_to_deg(get_additional_rotation()); + } +#ifdef TOOLS_ENABLED + else if (path.begins_with("editor/draw_gizmo")) { + r_ret = get_editor_draw_gizmo(); + } +#endif // TOOLS_ENABLED + else { + return false; + } + + return true; +} + +void SkeletonModification2DLookAt::_get_property_list(List *p_list) const { + p_list->push_back(PropertyInfo(Variant::BOOL, "enable_constraint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + if (enable_constraint) { + p_list->push_back(PropertyInfo(Variant::FLOAT, "constraint_angle_min", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::FLOAT, "constraint_angle_max", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, "constraint_angle_invert", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, "constraint_in_localspace", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } + p_list->push_back(PropertyInfo(Variant::FLOAT, "additional_rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } +#endif // TOOLS_ENABLED +} + +void SkeletonModification2DLookAt::_execute(float p_delta) { + ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot execute!"); + if (!enabled) { + return; + } + + if (target_node_cache.is_null()) { + WARN_PRINT_ONCE("Target cache is out of date. Attempting to update..."); + update_target_cache(); + return; + } + + if (bone2d_node_cache.is_null() && !bone2d_node.is_empty()) { + update_bone2d_cache(); + WARN_PRINT_ONCE("Bone2D node cache is out of date. Attempting to update..."); + return; + } + + if (target_node_reference == nullptr) { + target_node_reference = Object::cast_to(ObjectDB::get_instance(target_node_cache)); + } + if (!target_node_reference || !target_node_reference->is_inside_tree()) { + ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!"); + return; + } + if (bone_idx <= -1) { + ERR_PRINT_ONCE("Bone index is invalid. Cannot execute modification!"); + return; + } + + Bone2D *operation_bone = stack->skeleton->get_bone(bone_idx); + if (operation_bone == nullptr) { + ERR_PRINT_ONCE("bone_idx for modification does not point to a valid bone! Cannot execute modification"); + return; + } + + Transform2D operation_transform = operation_bone->get_global_transform(); + Transform2D target_trans = target_node_reference->get_global_transform(); + + // Look at the target! + operation_transform = operation_transform.looking_at(target_trans.get_origin()); + // Apply whatever scale it had prior to looking_at + operation_transform.set_scale(operation_bone->get_global_scale()); + + // Account for the direction the bone faces in: + operation_transform.set_rotation(operation_transform.get_rotation() - operation_bone->get_bone_angle()); + + // Apply additional rotation + operation_transform.set_rotation(operation_transform.get_rotation() + additional_rotation); + + // Apply constraints in globalspace: + if (enable_constraint && !constraint_in_localspace) { + operation_transform.set_rotation(clamp_angle(operation_transform.get_rotation(), constraint_angle_min, constraint_angle_max, constraint_angle_invert)); + } + + // Convert from a global transform to a local transform via the Bone2D node + operation_bone->set_global_transform(operation_transform); + operation_transform = operation_bone->get_transform(); + + // Apply constraints in localspace: + if (enable_constraint && constraint_in_localspace) { + operation_transform.set_rotation(clamp_angle(operation_transform.get_rotation(), constraint_angle_min, constraint_angle_max, constraint_angle_invert)); + } + + // Set the local pose override, and to make sure child bones are also updated, set the transform of the bone. + stack->skeleton->set_bone_local_pose_override(bone_idx, operation_transform, stack->strength, true); + operation_bone->set_transform(operation_transform); +} + +void SkeletonModification2DLookAt::_setup_modification(SkeletonModificationStack2D *p_stack) { + stack = p_stack; + + if (stack != nullptr) { + is_setup = true; + update_target_cache(); + update_bone2d_cache(); + } +} + +void SkeletonModification2DLookAt::_draw_editor_gizmo() { + if (!enabled || !is_setup) { + return; + } + + Bone2D *operation_bone = stack->skeleton->get_bone(bone_idx); + editor_draw_angle_constraints(operation_bone, constraint_angle_min, constraint_angle_max, + enable_constraint, constraint_in_localspace, constraint_angle_invert); +} + +void SkeletonModification2DLookAt::update_bone2d_cache() { + if (!is_setup || !stack) { + ERR_PRINT_ONCE("Cannot update Bone2D cache: modification is not properly setup!"); + return; + } + + bone2d_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(bone2d_node)) { + Node *node = stack->skeleton->get_node(bone2d_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update Bone2D cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update Bone2D cache: node is not in the scene tree!"); + bone2d_node_cache = node->get_instance_id(); + + Bone2D *bone = Object::cast_to(node); + if (bone) { + bone_idx = bone->get_index_in_skeleton(); + } else { + ERR_FAIL_MSG("Error Bone2D cache: Nodepath to Bone2D is not a Bone2D node!"); + } + + // Set this to null so we update it + target_node_reference = nullptr; + } + } + } +} + +void SkeletonModification2DLookAt::set_bone2d_node(const NodePath &p_target_node) { + bone2d_node = p_target_node; + update_bone2d_cache(); +} + +NodePath SkeletonModification2DLookAt::get_bone2d_node() const { + return bone2d_node; +} + +int SkeletonModification2DLookAt::get_bone_index() const { + return bone_idx; +} + +void SkeletonModification2DLookAt::set_bone_index(int p_bone_idx) { + ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); + + if (is_setup && stack) { + if (stack->skeleton) { + ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); + bone_idx = p_bone_idx; + bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id(); + bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx)); + } else { + WARN_PRINT("Cannot verify the bone index for this modification..."); + bone_idx = p_bone_idx; + } + } else { + WARN_PRINT("Cannot verify the bone index for this modification..."); + bone_idx = p_bone_idx; + } + + notify_property_list_changed(); +} + +void SkeletonModification2DLookAt::update_target_cache() { + if (!is_setup || !stack) { + ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + return; + } + + target_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(target_node)) { + Node *node = stack->skeleton->get_node(target_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update target cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update target cache: node is not in the scene tree!"); + target_node_cache = node->get_instance_id(); + } + } + } +} + +void SkeletonModification2DLookAt::set_target_node(const NodePath &p_target_node) { + target_node = p_target_node; + update_target_cache(); +} + +NodePath SkeletonModification2DLookAt::get_target_node() const { + return target_node; +} + +float SkeletonModification2DLookAt::get_additional_rotation() const { + return additional_rotation; +} + +void SkeletonModification2DLookAt::set_additional_rotation(float p_rotation) { + additional_rotation = p_rotation; +} + +void SkeletonModification2DLookAt::set_enable_constraint(bool p_constraint) { + enable_constraint = p_constraint; + notify_property_list_changed(); +#ifdef TOOLS_ENABLED + if (stack && is_setup) { + stack->set_editor_gizmos_dirty(true); + } +#endif // TOOLS_ENABLED +} + +bool SkeletonModification2DLookAt::get_enable_constraint() const { + return enable_constraint; +} + +void SkeletonModification2DLookAt::set_constraint_angle_min(float p_angle_min) { + constraint_angle_min = p_angle_min; +#ifdef TOOLS_ENABLED + if (stack && is_setup) { + stack->set_editor_gizmos_dirty(true); + } +#endif // TOOLS_ENABLED +} + +float SkeletonModification2DLookAt::get_constraint_angle_min() const { + return constraint_angle_min; +} + +void SkeletonModification2DLookAt::set_constraint_angle_max(float p_angle_max) { + constraint_angle_max = p_angle_max; +#ifdef TOOLS_ENABLED + if (stack && is_setup) { + stack->set_editor_gizmos_dirty(true); + } +#endif // TOOLS_ENABLED +} + +float SkeletonModification2DLookAt::get_constraint_angle_max() const { + return constraint_angle_max; +} + +void SkeletonModification2DLookAt::set_constraint_angle_invert(bool p_invert) { + constraint_angle_invert = p_invert; +#ifdef TOOLS_ENABLED + if (stack && is_setup) { + stack->set_editor_gizmos_dirty(true); + } +#endif // TOOLS_ENABLED +} + +bool SkeletonModification2DLookAt::get_constraint_angle_invert() const { + return constraint_angle_invert; +} + +void SkeletonModification2DLookAt::set_constraint_in_localspace(bool p_constraint_in_localspace) { + constraint_in_localspace = p_constraint_in_localspace; +#ifdef TOOLS_ENABLED + if (stack && is_setup) { + stack->set_editor_gizmos_dirty(true); + } +#endif // TOOLS_ENABLED +} + +bool SkeletonModification2DLookAt::get_constraint_in_localspace() const { + return constraint_in_localspace; +} + +void SkeletonModification2DLookAt::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_bone2d_node", "bone2d_nodepath"), &SkeletonModification2DLookAt::set_bone2d_node); + ClassDB::bind_method(D_METHOD("get_bone2d_node"), &SkeletonModification2DLookAt::get_bone2d_node); + ClassDB::bind_method(D_METHOD("set_bone_index", "bone_idx"), &SkeletonModification2DLookAt::set_bone_index); + ClassDB::bind_method(D_METHOD("get_bone_index"), &SkeletonModification2DLookAt::get_bone_index); + + ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DLookAt::set_target_node); + ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DLookAt::get_target_node); + + ClassDB::bind_method(D_METHOD("set_additional_rotation", "rotation"), &SkeletonModification2DLookAt::set_additional_rotation); + ClassDB::bind_method(D_METHOD("get_additional_rotation"), &SkeletonModification2DLookAt::get_additional_rotation); + + ClassDB::bind_method(D_METHOD("set_enable_constraint", "enable_constraint"), &SkeletonModification2DLookAt::set_enable_constraint); + ClassDB::bind_method(D_METHOD("get_enable_constraint"), &SkeletonModification2DLookAt::get_enable_constraint); + ClassDB::bind_method(D_METHOD("set_constraint_angle_min", "angle_min"), &SkeletonModification2DLookAt::set_constraint_angle_min); + ClassDB::bind_method(D_METHOD("get_constraint_angle_min"), &SkeletonModification2DLookAt::get_constraint_angle_min); + ClassDB::bind_method(D_METHOD("set_constraint_angle_max", "angle_max"), &SkeletonModification2DLookAt::set_constraint_angle_max); + ClassDB::bind_method(D_METHOD("get_constraint_angle_max"), &SkeletonModification2DLookAt::get_constraint_angle_max); + ClassDB::bind_method(D_METHOD("set_constraint_angle_invert", "invert"), &SkeletonModification2DLookAt::set_constraint_angle_invert); + ClassDB::bind_method(D_METHOD("get_constraint_angle_invert"), &SkeletonModification2DLookAt::get_constraint_angle_invert); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_index"), "set_bone_index", "get_bone_index"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D"), "set_bone2d_node", "get_bone2d_node"); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node"); +} + +SkeletonModification2DLookAt::SkeletonModification2DLookAt() { + stack = nullptr; + is_setup = false; + bone_idx = -1; + additional_rotation = 0; + enable_constraint = false; + constraint_angle_min = 0; + constraint_angle_max = Math_PI * 2; + constraint_angle_invert = false; + enabled = true; + + editor_draw_gizmo = true; +} + +SkeletonModification2DLookAt::~SkeletonModification2DLookAt() { +} diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_lookat.h b/scene/resources/2d/skeleton/skeleton_modification_2d_lookat.h new file mode 100644 index 0000000000..fbce45b83a --- /dev/null +++ b/scene/resources/2d/skeleton/skeleton_modification_2d_lookat.h @@ -0,0 +1,100 @@ +/**************************************************************************/ +/* skeleton_modification_2d_lookat.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 SKELETON_MODIFICATION_2D_LOOKAT_H +#define SKELETON_MODIFICATION_2D_LOOKAT_H + +#include "scene/2d/skeleton_2d.h" +#include "scene/resources/2d/skeleton/skeleton_modification_2d.h" + +/////////////////////////////////////// +// SkeletonModification2DLookAt +/////////////////////////////////////// + +class SkeletonModification2DLookAt : public SkeletonModification2D { + GDCLASS(SkeletonModification2DLookAt, SkeletonModification2D); + +private: + int bone_idx = -1; + NodePath bone2d_node; + ObjectID bone2d_node_cache; + + NodePath target_node; + ObjectID target_node_cache; + Node2D *target_node_reference = nullptr; + + float additional_rotation = 0; + bool enable_constraint = false; + float constraint_angle_min = 0; + float constraint_angle_max = (2.0 * Math_PI); + bool constraint_angle_invert = false; + bool constraint_in_localspace = true; + + void update_bone2d_cache(); + void update_target_cache(); + +protected: + static void _bind_methods(); + bool _set(const StringName &p_path, const Variant &p_value); + bool _get(const StringName &p_path, Variant &r_ret) const; + void _get_property_list(List *p_list) const; + +public: + void _execute(float p_delta) override; + void _setup_modification(SkeletonModificationStack2D *p_stack) override; + void _draw_editor_gizmo() override; + + void set_bone2d_node(const NodePath &p_target_node); + NodePath get_bone2d_node() const; + void set_bone_index(int p_idx); + int get_bone_index() const; + + void set_target_node(const NodePath &p_target_node); + NodePath get_target_node() const; + + void set_additional_rotation(float p_rotation); + float get_additional_rotation() const; + + void set_enable_constraint(bool p_constraint); + bool get_enable_constraint() const; + void set_constraint_angle_min(float p_angle_min); + float get_constraint_angle_min() const; + void set_constraint_angle_max(float p_angle_max); + float get_constraint_angle_max() const; + void set_constraint_angle_invert(bool p_invert); + bool get_constraint_angle_invert() const; + void set_constraint_in_localspace(bool p_constraint_in_localspace); + bool get_constraint_in_localspace() const; + + SkeletonModification2DLookAt(); + ~SkeletonModification2DLookAt(); +}; + +#endif // SKELETON_MODIFICATION_2D_LOOKAT_H diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp new file mode 100644 index 0000000000..e000e947cd --- /dev/null +++ b/scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.cpp @@ -0,0 +1,297 @@ +/**************************************************************************/ +/* skeleton_modification_2d_physicalbones.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 "skeleton_modification_2d_physicalbones.h" +#include "scene/2d/physical_bone_2d.h" +#include "scene/2d/skeleton_2d.h" + +bool SkeletonModification2DPhysicalBones::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + +#ifdef TOOLS_ENABLED + // Exposes a way to fetch the PhysicalBone2D nodes from the Godot editor. + if (is_setup) { + if (Engine::get_singleton()->is_editor_hint()) { + if (path.begins_with("fetch_bones")) { + fetch_physical_bones(); + notify_property_list_changed(); + return true; + } + } + } +#endif //TOOLS_ENABLED + + if (path.begins_with("joint_")) { + int which = path.get_slicec('_', 1).to_int(); + String what = path.get_slicec('_', 2); + ERR_FAIL_INDEX_V(which, physical_bone_chain.size(), false); + + if (what == "nodepath") { + set_physical_bone_node(which, p_value); + return true; + } + } + return false; +} + +bool SkeletonModification2DPhysicalBones::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + if (path.begins_with("fetch_bones")) { + return true; // Do nothing! + } + } +#endif //TOOLS_ENABLED + + if (path.begins_with("joint_")) { + int which = path.get_slicec('_', 1).to_int(); + String what = path.get_slicec('_', 2); + ERR_FAIL_INDEX_V(which, physical_bone_chain.size(), false); + + if (what == "nodepath") { + r_ret = get_physical_bone_node(which); + return true; + } + } + return false; +} + +void SkeletonModification2DPhysicalBones::_get_property_list(List *p_list) const { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + p_list->push_back(PropertyInfo(Variant::BOOL, "fetch_bones", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } +#endif //TOOLS_ENABLED + + for (int i = 0; i < physical_bone_chain.size(); i++) { + String base_string = "joint_" + itos(i) + "_"; + + p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "PhysicalBone2D", PROPERTY_USAGE_DEFAULT)); + } +} + +void SkeletonModification2DPhysicalBones::_execute(float p_delta) { + ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot execute!"); + if (!enabled) { + return; + } + + if (_simulation_state_dirty) { + _update_simulation_state(); + } + + for (int i = 0; i < physical_bone_chain.size(); i++) { + PhysicalBone_Data2D bone_data = physical_bone_chain[i]; + if (bone_data.physical_bone_node_cache.is_null()) { + WARN_PRINT_ONCE("PhysicalBone2D cache " + itos(i) + " is out of date. Attempting to update..."); + _physical_bone_update_cache(i); + continue; + } + + PhysicalBone2D *physical_bone = Object::cast_to(ObjectDB::get_instance(bone_data.physical_bone_node_cache)); + if (!physical_bone) { + ERR_PRINT_ONCE("PhysicalBone2D not found at index " + itos(i) + "!"); + return; + } + if (physical_bone->get_bone2d_index() < 0 || physical_bone->get_bone2d_index() > stack->skeleton->get_bone_count()) { + ERR_PRINT_ONCE("PhysicalBone2D at index " + itos(i) + " has invalid Bone2D!"); + return; + } + Bone2D *bone_2d = stack->skeleton->get_bone(physical_bone->get_bone2d_index()); + + if (physical_bone->get_simulate_physics() && !physical_bone->get_follow_bone_when_simulating()) { + bone_2d->set_global_transform(physical_bone->get_global_transform()); + stack->skeleton->set_bone_local_pose_override(physical_bone->get_bone2d_index(), bone_2d->get_transform(), stack->strength, true); + } + } +} + +void SkeletonModification2DPhysicalBones::_setup_modification(SkeletonModificationStack2D *p_stack) { + stack = p_stack; + + if (stack) { + is_setup = true; + + if (stack->skeleton) { + for (int i = 0; i < physical_bone_chain.size(); i++) { + _physical_bone_update_cache(i); + } + } + } +} + +void SkeletonModification2DPhysicalBones::_physical_bone_update_cache(int p_joint_idx) { + ERR_FAIL_INDEX_MSG(p_joint_idx, physical_bone_chain.size(), "Cannot update PhysicalBone2D cache: joint index out of range!"); + if (!is_setup || !stack) { + if (!stack) { + ERR_PRINT_ONCE("Cannot update PhysicalBone2D cache: modification is not properly setup!"); + } + return; + } + + physical_bone_chain.write[p_joint_idx].physical_bone_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(physical_bone_chain[p_joint_idx].physical_bone_node)) { + Node *node = stack->skeleton->get_node(physical_bone_chain[p_joint_idx].physical_bone_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update Physical Bone2D " + itos(p_joint_idx) + " cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update Physical Bone2D " + itos(p_joint_idx) + " cache: node is not in scene tree!"); + physical_bone_chain.write[p_joint_idx].physical_bone_node_cache = node->get_instance_id(); + } + } + } +} + +int SkeletonModification2DPhysicalBones::get_physical_bone_chain_length() { + return physical_bone_chain.size(); +} + +void SkeletonModification2DPhysicalBones::set_physical_bone_chain_length(int p_length) { + ERR_FAIL_COND(p_length < 0); + physical_bone_chain.resize(p_length); + notify_property_list_changed(); +} + +void SkeletonModification2DPhysicalBones::fetch_physical_bones() { + ERR_FAIL_NULL_MSG(stack, "No modification stack found! Cannot fetch physical bones!"); + ERR_FAIL_NULL_MSG(stack->skeleton, "No skeleton found! Cannot fetch physical bones!"); + + physical_bone_chain.clear(); + + List node_queue = List(); + node_queue.push_back(stack->skeleton); + + while (node_queue.size() > 0) { + Node *node_to_process = node_queue[0]; + node_queue.pop_front(); + + if (node_to_process != nullptr) { + PhysicalBone2D *potential_bone = Object::cast_to(node_to_process); + if (potential_bone) { + PhysicalBone_Data2D new_data = PhysicalBone_Data2D(); + new_data.physical_bone_node = stack->skeleton->get_path_to(potential_bone); + new_data.physical_bone_node_cache = potential_bone->get_instance_id(); + physical_bone_chain.push_back(new_data); + } + for (int i = 0; i < node_to_process->get_child_count(); i++) { + node_queue.push_back(node_to_process->get_child(i)); + } + } + } +} + +void SkeletonModification2DPhysicalBones::start_simulation(const TypedArray &p_bones) { + _simulation_state_dirty = true; + _simulation_state_dirty_names = p_bones; + _simulation_state_dirty_process = true; + + if (is_setup) { + _update_simulation_state(); + } +} + +void SkeletonModification2DPhysicalBones::stop_simulation(const TypedArray &p_bones) { + _simulation_state_dirty = true; + _simulation_state_dirty_names = p_bones; + _simulation_state_dirty_process = false; + + if (is_setup) { + _update_simulation_state(); + } +} + +void SkeletonModification2DPhysicalBones::_update_simulation_state() { + if (!_simulation_state_dirty) { + return; + } + _simulation_state_dirty = false; + + if (_simulation_state_dirty_names.size() <= 0) { + for (int i = 0; i < physical_bone_chain.size(); i++) { + PhysicalBone2D *physical_bone = Object::cast_to(stack->skeleton->get_node(physical_bone_chain[i].physical_bone_node)); + if (!physical_bone) { + continue; + } + + physical_bone->set_simulate_physics(_simulation_state_dirty_process); + } + } else { + for (int i = 0; i < physical_bone_chain.size(); i++) { + PhysicalBone2D *physical_bone = Object::cast_to(ObjectDB::get_instance(physical_bone_chain[i].physical_bone_node_cache)); + if (!physical_bone) { + continue; + } + if (_simulation_state_dirty_names.has(physical_bone->get_name())) { + physical_bone->set_simulate_physics(_simulation_state_dirty_process); + } + } + } +} + +void SkeletonModification2DPhysicalBones::set_physical_bone_node(int p_joint_idx, const NodePath &p_nodepath) { + ERR_FAIL_INDEX_MSG(p_joint_idx, physical_bone_chain.size(), "Joint index out of range!"); + physical_bone_chain.write[p_joint_idx].physical_bone_node = p_nodepath; + _physical_bone_update_cache(p_joint_idx); +} + +NodePath SkeletonModification2DPhysicalBones::get_physical_bone_node(int p_joint_idx) const { + ERR_FAIL_INDEX_V_MSG(p_joint_idx, physical_bone_chain.size(), NodePath(), "Joint index out of range!"); + return physical_bone_chain[p_joint_idx].physical_bone_node; +} + +void SkeletonModification2DPhysicalBones::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_physical_bone_chain_length", "length"), &SkeletonModification2DPhysicalBones::set_physical_bone_chain_length); + ClassDB::bind_method(D_METHOD("get_physical_bone_chain_length"), &SkeletonModification2DPhysicalBones::get_physical_bone_chain_length); + + ClassDB::bind_method(D_METHOD("set_physical_bone_node", "joint_idx", "physicalbone2d_node"), &SkeletonModification2DPhysicalBones::set_physical_bone_node); + ClassDB::bind_method(D_METHOD("get_physical_bone_node", "joint_idx"), &SkeletonModification2DPhysicalBones::get_physical_bone_node); + + ClassDB::bind_method(D_METHOD("fetch_physical_bones"), &SkeletonModification2DPhysicalBones::fetch_physical_bones); + ClassDB::bind_method(D_METHOD("start_simulation", "bones"), &SkeletonModification2DPhysicalBones::start_simulation, DEFVAL(Array())); + ClassDB::bind_method(D_METHOD("stop_simulation", "bones"), &SkeletonModification2DPhysicalBones::stop_simulation, DEFVAL(Array())); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "physical_bone_chain_length", PROPERTY_HINT_RANGE, "0,100,1"), "set_physical_bone_chain_length", "get_physical_bone_chain_length"); +} + +SkeletonModification2DPhysicalBones::SkeletonModification2DPhysicalBones() { + stack = nullptr; + is_setup = false; + physical_bone_chain = Vector(); + enabled = true; + editor_draw_gizmo = false; // Nothing to really show in a gizmo right now. +} + +SkeletonModification2DPhysicalBones::~SkeletonModification2DPhysicalBones() { +} diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.h b/scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.h new file mode 100644 index 0000000000..09bf00782c --- /dev/null +++ b/scene/resources/2d/skeleton/skeleton_modification_2d_physicalbones.h @@ -0,0 +1,82 @@ +/**************************************************************************/ +/* skeleton_modification_2d_physicalbones.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 SKELETON_MODIFICATION_2D_PHYSICALBONES_H +#define SKELETON_MODIFICATION_2D_PHYSICALBONES_H + +#include "scene/2d/skeleton_2d.h" +#include "scene/resources/2d/skeleton/skeleton_modification_2d.h" + +/////////////////////////////////////// +// SkeletonModification2DJIGGLE +/////////////////////////////////////// + +class SkeletonModification2DPhysicalBones : public SkeletonModification2D { + GDCLASS(SkeletonModification2DPhysicalBones, SkeletonModification2D); + +private: + struct PhysicalBone_Data2D { + NodePath physical_bone_node; + ObjectID physical_bone_node_cache; + }; + Vector physical_bone_chain; + + void _physical_bone_update_cache(int p_joint_idx); + + bool _simulation_state_dirty = false; + TypedArray _simulation_state_dirty_names; + bool _simulation_state_dirty_process = false; + void _update_simulation_state(); + +protected: + static void _bind_methods(); + bool _get(const StringName &p_path, Variant &r_ret) const; + bool _set(const StringName &p_path, const Variant &p_value); + void _get_property_list(List *p_list) const; + +public: + void _execute(float p_delta) override; + void _setup_modification(SkeletonModificationStack2D *p_stack) override; + + int get_physical_bone_chain_length(); + void set_physical_bone_chain_length(int p_new_length); + + void set_physical_bone_node(int p_joint_idx, const NodePath &p_path); + NodePath get_physical_bone_node(int p_joint_idx) const; + + void fetch_physical_bones(); + void start_simulation(const TypedArray &p_bones); + void stop_simulation(const TypedArray &p_bones); + + SkeletonModification2DPhysicalBones(); + ~SkeletonModification2DPhysicalBones(); +}; + +#endif // SKELETON_MODIFICATION_2D_PHYSICALBONES_H diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_stackholder.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d_stackholder.cpp new file mode 100644 index 0000000000..6d4cd290f1 --- /dev/null +++ b/scene/resources/2d/skeleton/skeleton_modification_2d_stackholder.cpp @@ -0,0 +1,135 @@ +/**************************************************************************/ +/* skeleton_modification_2d_stackholder.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 "skeleton_modification_2d_stackholder.h" +#include "scene/2d/skeleton_2d.h" + +bool SkeletonModification2DStackHolder::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path == "held_modification_stack") { + set_held_modification_stack(p_value); + } +#ifdef TOOLS_ENABLED + else if (path == "editor/draw_gizmo") { + set_editor_draw_gizmo(p_value); + } +#endif // TOOLS_ENABLED + else { + return false; + } + + return true; +} + +bool SkeletonModification2DStackHolder::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path == "held_modification_stack") { + r_ret = get_held_modification_stack(); + } +#ifdef TOOLS_ENABLED + else if (path == "editor/draw_gizmo") { + r_ret = get_editor_draw_gizmo(); + } +#endif // TOOLS_ENABLED + else { + return false; + } + + return true; +} + +void SkeletonModification2DStackHolder::_get_property_list(List *p_list) const { + p_list->push_back(PropertyInfo(Variant::OBJECT, "held_modification_stack", PROPERTY_HINT_RESOURCE_TYPE, "SkeletonModificationStack2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ALWAYS_DUPLICATE)); + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } +#endif // TOOLS_ENABLED +} + +void SkeletonModification2DStackHolder::_execute(float p_delta) { + ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot execute!"); + + if (held_modification_stack.is_valid()) { + held_modification_stack->execute(p_delta, execution_mode); + } +} + +void SkeletonModification2DStackHolder::_setup_modification(SkeletonModificationStack2D *p_stack) { + stack = p_stack; + + if (stack != nullptr) { + is_setup = true; + + if (held_modification_stack.is_valid()) { + held_modification_stack->set_skeleton(stack->get_skeleton()); + held_modification_stack->setup(); + } + } +} + +void SkeletonModification2DStackHolder::_draw_editor_gizmo() { + if (stack) { + if (held_modification_stack.is_valid()) { + held_modification_stack->draw_editor_gizmos(); + } + } +} + +void SkeletonModification2DStackHolder::set_held_modification_stack(Ref p_held_stack) { + held_modification_stack = p_held_stack; + + if (is_setup && held_modification_stack.is_valid()) { + held_modification_stack->set_skeleton(stack->get_skeleton()); + held_modification_stack->setup(); + } +} + +Ref SkeletonModification2DStackHolder::get_held_modification_stack() const { + return held_modification_stack; +} + +void SkeletonModification2DStackHolder::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_held_modification_stack", "held_modification_stack"), &SkeletonModification2DStackHolder::set_held_modification_stack); + ClassDB::bind_method(D_METHOD("get_held_modification_stack"), &SkeletonModification2DStackHolder::get_held_modification_stack); +} + +SkeletonModification2DStackHolder::SkeletonModification2DStackHolder() { + stack = nullptr; + is_setup = false; + enabled = true; +} + +SkeletonModification2DStackHolder::~SkeletonModification2DStackHolder() { +} diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_stackholder.h b/scene/resources/2d/skeleton/skeleton_modification_2d_stackholder.h new file mode 100644 index 0000000000..b1054f6429 --- /dev/null +++ b/scene/resources/2d/skeleton/skeleton_modification_2d_stackholder.h @@ -0,0 +1,64 @@ +/**************************************************************************/ +/* skeleton_modification_2d_stackholder.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 SKELETON_MODIFICATION_2D_STACKHOLDER_H +#define SKELETON_MODIFICATION_2D_STACKHOLDER_H + +#include "scene/2d/skeleton_2d.h" +#include "scene/resources/2d/skeleton/skeleton_modification_2d.h" + +/////////////////////////////////////// +// SkeletonModification2DJIGGLE +/////////////////////////////////////// + +class SkeletonModification2DStackHolder : public SkeletonModification2D { + GDCLASS(SkeletonModification2DStackHolder, SkeletonModification2D); + +protected: + static void _bind_methods(); + bool _get(const StringName &p_path, Variant &r_ret) const; + bool _set(const StringName &p_path, const Variant &p_value); + void _get_property_list(List *p_list) const; + +public: + Ref held_modification_stack; + + void _execute(float p_delta) override; + void _setup_modification(SkeletonModificationStack2D *p_stack) override; + void _draw_editor_gizmo() override; + + void set_held_modification_stack(Ref p_held_stack); + Ref get_held_modification_stack() const; + + SkeletonModification2DStackHolder(); + ~SkeletonModification2DStackHolder(); +}; + +#endif // SKELETON_MODIFICATION_2D_STACKHOLDER_H diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_twoboneik.cpp b/scene/resources/2d/skeleton/skeleton_modification_2d_twoboneik.cpp new file mode 100644 index 0000000000..c3366d5c36 --- /dev/null +++ b/scene/resources/2d/skeleton/skeleton_modification_2d_twoboneik.cpp @@ -0,0 +1,485 @@ +/**************************************************************************/ +/* skeleton_modification_2d_twoboneik.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 "skeleton_modification_2d_twoboneik.h" +#include "scene/2d/skeleton_2d.h" + +#ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" +#endif // TOOLS_ENABLED + +bool SkeletonModification2DTwoBoneIK::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path == "joint_one_bone_idx") { + set_joint_one_bone_idx(p_value); + } else if (path == "joint_one_bone2d_node") { + set_joint_one_bone2d_node(p_value); + } else if (path == "joint_two_bone_idx") { + set_joint_two_bone_idx(p_value); + } else if (path == "joint_two_bone2d_node") { + set_joint_two_bone2d_node(p_value); + } +#ifdef TOOLS_ENABLED + else if (path.begins_with("editor/draw_gizmo")) { + set_editor_draw_gizmo(p_value); + } else if (path.begins_with("editor/draw_min_max")) { + set_editor_draw_min_max(p_value); + } +#endif // TOOLS_ENABLED + else { + return false; + } + + return true; +} + +bool SkeletonModification2DTwoBoneIK::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path == "joint_one_bone_idx") { + r_ret = get_joint_one_bone_idx(); + } else if (path == "joint_one_bone2d_node") { + r_ret = get_joint_one_bone2d_node(); + } else if (path == "joint_two_bone_idx") { + r_ret = get_joint_two_bone_idx(); + } else if (path == "joint_two_bone2d_node") { + r_ret = get_joint_two_bone2d_node(); + } +#ifdef TOOLS_ENABLED + else if (path.begins_with("editor/draw_gizmo")) { + r_ret = get_editor_draw_gizmo(); + } else if (path.begins_with("editor/draw_min_max")) { + r_ret = get_editor_draw_min_max(); + } +#endif // TOOLS_ENABLED + else { + return false; + } + + return true; +} + +void SkeletonModification2DTwoBoneIK::_get_property_list(List *p_list) const { + p_list->push_back(PropertyInfo(Variant::INT, "joint_one_bone_idx", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::NODE_PATH, "joint_one_bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT)); + + p_list->push_back(PropertyInfo(Variant::INT, "joint_two_bone_idx", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::NODE_PATH, "joint_two_bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT)); + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_min_max", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); + } +#endif // TOOLS_ENABLED +} + +void SkeletonModification2DTwoBoneIK::_execute(float p_delta) { + ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, + "Modification is not setup and therefore cannot execute!"); + if (!enabled) { + return; + } + + if (target_node_cache.is_null()) { + WARN_PRINT_ONCE("Target cache is out of date. Attempting to update..."); + update_target_cache(); + return; + } + + if (joint_one_bone2d_node_cache.is_null() && !joint_one_bone2d_node.is_empty()) { + WARN_PRINT_ONCE("Joint one Bone2D node cache is out of date. Attempting to update..."); + update_joint_one_bone2d_cache(); + } + if (joint_two_bone2d_node_cache.is_null() && !joint_two_bone2d_node.is_empty()) { + WARN_PRINT_ONCE("Joint two Bone2D node cache is out of date. Attempting to update..."); + update_joint_two_bone2d_cache(); + } + + Node2D *target = Object::cast_to(ObjectDB::get_instance(target_node_cache)); + if (!target || !target->is_inside_tree()) { + ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!"); + return; + } + + Bone2D *joint_one_bone = stack->skeleton->get_bone(joint_one_bone_idx); + if (joint_one_bone == nullptr) { + ERR_PRINT_ONCE("Joint one bone_idx does not point to a valid bone! Cannot execute modification!"); + return; + } + + Bone2D *joint_two_bone = stack->skeleton->get_bone(joint_two_bone_idx); + if (joint_two_bone == nullptr) { + ERR_PRINT_ONCE("Joint two bone_idx does not point to a valid bone! Cannot execute modification!"); + return; + } + + // Adopted from the links below: + // http://theorangeduck.com/page/simple-two-joint + // https://www.alanzucconi.com/2018/05/02/ik-2d-2/ + // With modifications by TwistedTwigleg + Vector2 target_difference = target->get_global_position() - joint_one_bone->get_global_position(); + float joint_one_to_target = target_difference.length(); + float angle_atan = target_difference.angle(); + + float bone_one_length = joint_one_bone->get_length() * MIN(joint_one_bone->get_global_scale().x, joint_one_bone->get_global_scale().y); + float bone_two_length = joint_two_bone->get_length() * MIN(joint_two_bone->get_global_scale().x, joint_two_bone->get_global_scale().y); + bool override_angles_due_to_out_of_range = false; + + if (joint_one_to_target < target_minimum_distance) { + joint_one_to_target = target_minimum_distance; + } + if (joint_one_to_target > target_maximum_distance && target_maximum_distance > 0.0) { + joint_one_to_target = target_maximum_distance; + } + + if (bone_one_length + bone_two_length < joint_one_to_target) { + override_angles_due_to_out_of_range = true; + } + + if (!override_angles_due_to_out_of_range) { + float angle_0 = Math::acos(((joint_one_to_target * joint_one_to_target) + (bone_one_length * bone_one_length) - (bone_two_length * bone_two_length)) / (2.0 * joint_one_to_target * bone_one_length)); + float angle_1 = Math::acos(((bone_two_length * bone_two_length) + (bone_one_length * bone_one_length) - (joint_one_to_target * joint_one_to_target)) / (2.0 * bone_two_length * bone_one_length)); + + if (flip_bend_direction) { + angle_0 = -angle_0; + angle_1 = -angle_1; + } + + if (isnan(angle_0) || isnan(angle_1)) { + // We cannot solve for this angle! Do nothing to avoid setting the rotation (and scale) to NaN. + } else { + joint_one_bone->set_global_rotation(angle_atan - angle_0 - joint_one_bone->get_bone_angle()); + joint_two_bone->set_rotation(-Math_PI - angle_1 - joint_two_bone->get_bone_angle() + joint_one_bone->get_bone_angle()); + } + } else { + joint_one_bone->set_global_rotation(angle_atan - joint_one_bone->get_bone_angle()); + joint_two_bone->set_global_rotation(angle_atan - joint_two_bone->get_bone_angle()); + } + + stack->skeleton->set_bone_local_pose_override(joint_one_bone_idx, joint_one_bone->get_transform(), stack->strength, true); + stack->skeleton->set_bone_local_pose_override(joint_two_bone_idx, joint_two_bone->get_transform(), stack->strength, true); +} + +void SkeletonModification2DTwoBoneIK::_setup_modification(SkeletonModificationStack2D *p_stack) { + stack = p_stack; + + if (stack) { + is_setup = true; + update_target_cache(); + update_joint_one_bone2d_cache(); + update_joint_two_bone2d_cache(); + } +} + +void SkeletonModification2DTwoBoneIK::_draw_editor_gizmo() { + if (!enabled || !is_setup) { + return; + } + + Bone2D *operation_bone_one = stack->skeleton->get_bone(joint_one_bone_idx); + if (!operation_bone_one) { + return; + } + stack->skeleton->draw_set_transform( + stack->skeleton->to_local(operation_bone_one->get_global_position()), + operation_bone_one->get_global_rotation() - stack->skeleton->get_global_rotation()); + + Color bone_ik_color = Color(1.0, 0.65, 0.0, 0.4); +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + bone_ik_color = EDITOR_GET("editors/2d/bone_ik_color"); + } +#endif // TOOLS_ENABLED + + if (flip_bend_direction) { + float angle = -(Math_PI * 0.5) + operation_bone_one->get_bone_angle(); + stack->skeleton->draw_line(Vector2(0, 0), Vector2(Math::cos(angle), sin(angle)) * (operation_bone_one->get_length() * 0.5), bone_ik_color, 2.0); + } else { + float angle = (Math_PI * 0.5) + operation_bone_one->get_bone_angle(); + stack->skeleton->draw_line(Vector2(0, 0), Vector2(Math::cos(angle), sin(angle)) * (operation_bone_one->get_length() * 0.5), bone_ik_color, 2.0); + } + +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + if (editor_draw_min_max) { + if (target_maximum_distance != 0.0 || target_minimum_distance != 0.0) { + Vector2 target_direction = Vector2(0, 1); + if (target_node_cache.is_valid()) { + stack->skeleton->draw_set_transform(Vector2(0, 0), 0.0); + Node2D *target = Object::cast_to(ObjectDB::get_instance(target_node_cache)); + target_direction = operation_bone_one->get_global_position().direction_to(target->get_global_position()); + } + + stack->skeleton->draw_circle(target_direction * target_minimum_distance, 8, bone_ik_color); + stack->skeleton->draw_circle(target_direction * target_maximum_distance, 8, bone_ik_color); + stack->skeleton->draw_line(target_direction * target_minimum_distance, target_direction * target_maximum_distance, bone_ik_color, 2.0); + } + } + } +#endif // TOOLS_ENABLED +} + +void SkeletonModification2DTwoBoneIK::update_target_cache() { + if (!is_setup || !stack) { + ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); + return; + } + + target_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(target_node)) { + Node *node = stack->skeleton->get_node(target_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update target cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update target cache: node is not in the scene tree!"); + target_node_cache = node->get_instance_id(); + } + } + } +} + +void SkeletonModification2DTwoBoneIK::update_joint_one_bone2d_cache() { + if (!is_setup || !stack) { + ERR_PRINT_ONCE("Cannot update joint one Bone2D cache: modification is not properly setup!"); + return; + } + + joint_one_bone2d_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(joint_one_bone2d_node)) { + Node *node = stack->skeleton->get_node(joint_one_bone2d_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update joint one Bone2D cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update joint one Bone2D cache: node is not in the scene tree!"); + joint_one_bone2d_node_cache = node->get_instance_id(); + + Bone2D *bone = Object::cast_to(node); + if (bone) { + joint_one_bone_idx = bone->get_index_in_skeleton(); + } else { + ERR_FAIL_MSG("Update joint one Bone2D cache: Nodepath to Bone2D is not a Bone2D node!"); + } + } + } + } +} + +void SkeletonModification2DTwoBoneIK::update_joint_two_bone2d_cache() { + if (!is_setup || !stack) { + ERR_PRINT_ONCE("Cannot update joint two Bone2D cache: modification is not properly setup!"); + return; + } + + joint_two_bone2d_node_cache = ObjectID(); + if (stack->skeleton) { + if (stack->skeleton->is_inside_tree()) { + if (stack->skeleton->has_node(joint_two_bone2d_node)) { + Node *node = stack->skeleton->get_node(joint_two_bone2d_node); + ERR_FAIL_COND_MSG(!node || stack->skeleton == node, + "Cannot update joint two Bone2D cache: node is this modification's skeleton or cannot be found!"); + ERR_FAIL_COND_MSG(!node->is_inside_tree(), + "Cannot update joint two Bone2D cache: node is not in scene tree!"); + joint_two_bone2d_node_cache = node->get_instance_id(); + + Bone2D *bone = Object::cast_to(node); + if (bone) { + joint_two_bone_idx = bone->get_index_in_skeleton(); + } else { + ERR_FAIL_MSG("Update joint two Bone2D cache: Nodepath to Bone2D is not a Bone2D node!"); + } + } + } + } +} + +void SkeletonModification2DTwoBoneIK::set_target_node(const NodePath &p_target_node) { + target_node = p_target_node; + update_target_cache(); +} + +NodePath SkeletonModification2DTwoBoneIK::get_target_node() const { + return target_node; +} + +void SkeletonModification2DTwoBoneIK::set_joint_one_bone2d_node(const NodePath &p_target_node) { + joint_one_bone2d_node = p_target_node; + update_joint_one_bone2d_cache(); + notify_property_list_changed(); +} + +void SkeletonModification2DTwoBoneIK::set_target_minimum_distance(float p_distance) { + ERR_FAIL_COND_MSG(p_distance < 0, "Target minimum distance cannot be less than zero!"); + target_minimum_distance = p_distance; +} + +float SkeletonModification2DTwoBoneIK::get_target_minimum_distance() const { + return target_minimum_distance; +} + +void SkeletonModification2DTwoBoneIK::set_target_maximum_distance(float p_distance) { + ERR_FAIL_COND_MSG(p_distance < 0, "Target maximum distance cannot be less than zero!"); + target_maximum_distance = p_distance; +} + +float SkeletonModification2DTwoBoneIK::get_target_maximum_distance() const { + return target_maximum_distance; +} + +void SkeletonModification2DTwoBoneIK::set_flip_bend_direction(bool p_flip_direction) { + flip_bend_direction = p_flip_direction; + +#ifdef TOOLS_ENABLED + if (stack && is_setup) { + stack->set_editor_gizmos_dirty(true); + } +#endif // TOOLS_ENABLED +} + +bool SkeletonModification2DTwoBoneIK::get_flip_bend_direction() const { + return flip_bend_direction; +} + +NodePath SkeletonModification2DTwoBoneIK::get_joint_one_bone2d_node() const { + return joint_one_bone2d_node; +} + +void SkeletonModification2DTwoBoneIK::set_joint_two_bone2d_node(const NodePath &p_target_node) { + joint_two_bone2d_node = p_target_node; + update_joint_two_bone2d_cache(); + notify_property_list_changed(); +} + +NodePath SkeletonModification2DTwoBoneIK::get_joint_two_bone2d_node() const { + return joint_two_bone2d_node; +} + +void SkeletonModification2DTwoBoneIK::set_joint_one_bone_idx(int p_bone_idx) { + ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); + + if (is_setup) { + if (stack->skeleton) { + ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); + joint_one_bone_idx = p_bone_idx; + joint_one_bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id(); + joint_one_bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx)); + } else { + WARN_PRINT("TwoBoneIK: Cannot verify the joint bone index for joint one..."); + joint_one_bone_idx = p_bone_idx; + } + } else { + WARN_PRINT("TwoBoneIK: Cannot verify the joint bone index for joint one..."); + joint_one_bone_idx = p_bone_idx; + } + + notify_property_list_changed(); +} + +int SkeletonModification2DTwoBoneIK::get_joint_one_bone_idx() const { + return joint_one_bone_idx; +} + +void SkeletonModification2DTwoBoneIK::set_joint_two_bone_idx(int p_bone_idx) { + ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); + + if (is_setup) { + if (stack->skeleton) { + ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); + joint_two_bone_idx = p_bone_idx; + joint_two_bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id(); + joint_two_bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx)); + } else { + WARN_PRINT("TwoBoneIK: Cannot verify the joint bone index for joint two..."); + joint_two_bone_idx = p_bone_idx; + } + } else { + WARN_PRINT("TwoBoneIK: Cannot verify the joint bone index for joint two..."); + joint_two_bone_idx = p_bone_idx; + } + + notify_property_list_changed(); +} + +int SkeletonModification2DTwoBoneIK::get_joint_two_bone_idx() const { + return joint_two_bone_idx; +} + +#ifdef TOOLS_ENABLED +void SkeletonModification2DTwoBoneIK::set_editor_draw_min_max(bool p_draw) { + editor_draw_min_max = p_draw; +} + +bool SkeletonModification2DTwoBoneIK::get_editor_draw_min_max() const { + return editor_draw_min_max; +} +#endif // TOOLS_ENABLED + +void SkeletonModification2DTwoBoneIK::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DTwoBoneIK::set_target_node); + ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DTwoBoneIK::get_target_node); + + ClassDB::bind_method(D_METHOD("set_target_minimum_distance", "minimum_distance"), &SkeletonModification2DTwoBoneIK::set_target_minimum_distance); + ClassDB::bind_method(D_METHOD("get_target_minimum_distance"), &SkeletonModification2DTwoBoneIK::get_target_minimum_distance); + ClassDB::bind_method(D_METHOD("set_target_maximum_distance", "maximum_distance"), &SkeletonModification2DTwoBoneIK::set_target_maximum_distance); + ClassDB::bind_method(D_METHOD("get_target_maximum_distance"), &SkeletonModification2DTwoBoneIK::get_target_maximum_distance); + ClassDB::bind_method(D_METHOD("set_flip_bend_direction", "flip_direction"), &SkeletonModification2DTwoBoneIK::set_flip_bend_direction); + ClassDB::bind_method(D_METHOD("get_flip_bend_direction"), &SkeletonModification2DTwoBoneIK::get_flip_bend_direction); + + ClassDB::bind_method(D_METHOD("set_joint_one_bone2d_node", "bone2d_node"), &SkeletonModification2DTwoBoneIK::set_joint_one_bone2d_node); + ClassDB::bind_method(D_METHOD("get_joint_one_bone2d_node"), &SkeletonModification2DTwoBoneIK::get_joint_one_bone2d_node); + ClassDB::bind_method(D_METHOD("set_joint_one_bone_idx", "bone_idx"), &SkeletonModification2DTwoBoneIK::set_joint_one_bone_idx); + ClassDB::bind_method(D_METHOD("get_joint_one_bone_idx"), &SkeletonModification2DTwoBoneIK::get_joint_one_bone_idx); + + ClassDB::bind_method(D_METHOD("set_joint_two_bone2d_node", "bone2d_node"), &SkeletonModification2DTwoBoneIK::set_joint_two_bone2d_node); + ClassDB::bind_method(D_METHOD("get_joint_two_bone2d_node"), &SkeletonModification2DTwoBoneIK::get_joint_two_bone2d_node); + ClassDB::bind_method(D_METHOD("set_joint_two_bone_idx", "bone_idx"), &SkeletonModification2DTwoBoneIK::set_joint_two_bone_idx); + ClassDB::bind_method(D_METHOD("get_joint_two_bone_idx"), &SkeletonModification2DTwoBoneIK::get_joint_two_bone_idx); + + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_minimum_distance", PROPERTY_HINT_RANGE, "0,100000000,0.01,suffix:px"), "set_target_minimum_distance", "get_target_minimum_distance"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_maximum_distance", PROPERTY_HINT_NONE, "0,100000000,0.01,suffix:px"), "set_target_maximum_distance", "get_target_maximum_distance"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_bend_direction", PROPERTY_HINT_NONE, ""), "set_flip_bend_direction", "get_flip_bend_direction"); + ADD_GROUP("", ""); +} + +SkeletonModification2DTwoBoneIK::SkeletonModification2DTwoBoneIK() { + stack = nullptr; + is_setup = false; + enabled = true; + editor_draw_gizmo = true; +} + +SkeletonModification2DTwoBoneIK::~SkeletonModification2DTwoBoneIK() { +} diff --git a/scene/resources/2d/skeleton/skeleton_modification_2d_twoboneik.h b/scene/resources/2d/skeleton/skeleton_modification_2d_twoboneik.h new file mode 100644 index 0000000000..d528ffa66b --- /dev/null +++ b/scene/resources/2d/skeleton/skeleton_modification_2d_twoboneik.h @@ -0,0 +1,107 @@ +/**************************************************************************/ +/* skeleton_modification_2d_twoboneik.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 SKELETON_MODIFICATION_2D_TWOBONEIK_H +#define SKELETON_MODIFICATION_2D_TWOBONEIK_H + +#include "scene/2d/skeleton_2d.h" +#include "scene/resources/2d/skeleton/skeleton_modification_2d.h" + +/////////////////////////////////////// +// SkeletonModification2DJIGGLE +/////////////////////////////////////// + +class SkeletonModification2DTwoBoneIK : public SkeletonModification2D { + GDCLASS(SkeletonModification2DTwoBoneIK, SkeletonModification2D); + +private: + NodePath target_node; + ObjectID target_node_cache; + float target_minimum_distance = 0; + float target_maximum_distance = 0; + bool flip_bend_direction = false; + + NodePath joint_one_bone2d_node; + ObjectID joint_one_bone2d_node_cache; + int joint_one_bone_idx = -1; + + NodePath joint_two_bone2d_node; + ObjectID joint_two_bone2d_node_cache; + int joint_two_bone_idx = -1; + +#ifdef TOOLS_ENABLED + bool editor_draw_min_max = false; +#endif // TOOLS_ENABLED + + void update_target_cache(); + void update_joint_one_bone2d_cache(); + void update_joint_two_bone2d_cache(); + +protected: + static void _bind_methods(); + bool _get(const StringName &p_path, Variant &r_ret) const; + bool _set(const StringName &p_path, const Variant &p_value); + void _get_property_list(List *p_list) const; + +public: + void _execute(float p_delta) override; + void _setup_modification(SkeletonModificationStack2D *p_stack) override; + void _draw_editor_gizmo() override; + + void set_target_node(const NodePath &p_target_node); + NodePath get_target_node() const; + + void set_target_minimum_distance(float p_minimum_distance); + float get_target_minimum_distance() const; + void set_target_maximum_distance(float p_maximum_distance); + float get_target_maximum_distance() const; + void set_flip_bend_direction(bool p_flip_direction); + bool get_flip_bend_direction() const; + + void set_joint_one_bone2d_node(const NodePath &p_node); + NodePath get_joint_one_bone2d_node() const; + void set_joint_one_bone_idx(int p_bone_idx); + int get_joint_one_bone_idx() const; + + void set_joint_two_bone2d_node(const NodePath &p_node); + NodePath get_joint_two_bone2d_node() const; + void set_joint_two_bone_idx(int p_bone_idx); + int get_joint_two_bone_idx() const; + +#ifdef TOOLS_ENABLED + void set_editor_draw_min_max(bool p_draw); + bool get_editor_draw_min_max() const; +#endif // TOOLS_ENABLED + + SkeletonModification2DTwoBoneIK(); + ~SkeletonModification2DTwoBoneIK(); +}; + +#endif // SKELETON_MODIFICATION_2D_TWOBONEIK_H diff --git a/scene/resources/2d/skeleton/skeleton_modification_stack_2d.cpp b/scene/resources/2d/skeleton/skeleton_modification_stack_2d.cpp new file mode 100644 index 0000000000..dcc69d4831 --- /dev/null +++ b/scene/resources/2d/skeleton/skeleton_modification_stack_2d.cpp @@ -0,0 +1,270 @@ +/**************************************************************************/ +/* skeleton_modification_stack_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 "skeleton_modification_stack_2d.h" +#include "scene/2d/skeleton_2d.h" + +void SkeletonModificationStack2D::_get_property_list(List *p_list) const { + for (int i = 0; i < modifications.size(); i++) { + p_list->push_back( + PropertyInfo(Variant::OBJECT, "modifications/" + itos(i), + PROPERTY_HINT_RESOURCE_TYPE, + "SkeletonModification2D", + PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ALWAYS_DUPLICATE)); + } +} + +bool SkeletonModificationStack2D::_set(const StringName &p_path, const Variant &p_value) { + String path = p_path; + + if (path.begins_with("modifications/")) { + int mod_idx = path.get_slicec('/', 1).to_int(); + set_modification(mod_idx, p_value); + return true; + } + return false; +} + +bool SkeletonModificationStack2D::_get(const StringName &p_path, Variant &r_ret) const { + String path = p_path; + + if (path.begins_with("modifications/")) { + int mod_idx = path.get_slicec('/', 1).to_int(); + r_ret = get_modification(mod_idx); + return true; + } + return false; +} + +void SkeletonModificationStack2D::setup() { + if (is_setup) { + return; + } + + if (skeleton != nullptr) { + is_setup = true; + for (int i = 0; i < modifications.size(); i++) { + if (!modifications[i].is_valid()) { + continue; + } + modifications.get(i)->_setup_modification(this); + } + +#ifdef TOOLS_ENABLED + set_editor_gizmos_dirty(true); +#endif // TOOLS_ENABLED + + } else { + WARN_PRINT("Cannot setup SkeletonModificationStack2D: no Skeleton2D set!"); + } +} + +void SkeletonModificationStack2D::execute(float p_delta, int p_execution_mode) { + ERR_FAIL_COND_MSG(!is_setup || skeleton == nullptr || is_queued_for_deletion(), + "Modification stack is not properly setup and therefore cannot execute!"); + + if (!skeleton->is_inside_tree()) { + ERR_PRINT_ONCE("Skeleton is not inside SceneTree! Cannot execute modification!"); + return; + } + + if (!enabled) { + return; + } + + for (int i = 0; i < modifications.size(); i++) { + if (!modifications[i].is_valid()) { + continue; + } + + if (modifications[i]->get_execution_mode() == p_execution_mode) { + modifications.get(i)->_execute(p_delta); + } + } +} + +void SkeletonModificationStack2D::draw_editor_gizmos() { + if (!is_setup) { + return; + } + + if (editor_gizmo_dirty) { + for (int i = 0; i < modifications.size(); i++) { + if (!modifications[i].is_valid()) { + continue; + } + + if (modifications[i]->editor_draw_gizmo) { + modifications.get(i)->_draw_editor_gizmo(); + } + } + skeleton->draw_set_transform(Vector2(0, 0)); + editor_gizmo_dirty = false; + } +} + +void SkeletonModificationStack2D::set_editor_gizmos_dirty(bool p_dirty) { + if (!is_setup) { + return; + } + + if (!editor_gizmo_dirty && p_dirty) { + editor_gizmo_dirty = p_dirty; + if (skeleton) { + skeleton->queue_redraw(); + } + } else { + editor_gizmo_dirty = p_dirty; + } +} + +void SkeletonModificationStack2D::enable_all_modifications(bool p_enabled) { + for (int i = 0; i < modifications.size(); i++) { + if (!modifications[i].is_valid()) { + continue; + } + modifications.get(i)->set_enabled(p_enabled); + } +} + +Ref SkeletonModificationStack2D::get_modification(int p_mod_idx) const { + ERR_FAIL_INDEX_V(p_mod_idx, modifications.size(), nullptr); + return modifications[p_mod_idx]; +} + +void SkeletonModificationStack2D::add_modification(Ref p_mod) { + ERR_FAIL_COND(!p_mod.is_valid()); + + p_mod->_setup_modification(this); + modifications.push_back(p_mod); + +#ifdef TOOLS_ENABLED + set_editor_gizmos_dirty(true); +#endif // TOOLS_ENABLED +} + +void SkeletonModificationStack2D::delete_modification(int p_mod_idx) { + ERR_FAIL_INDEX(p_mod_idx, modifications.size()); + modifications.remove_at(p_mod_idx); + +#ifdef TOOLS_ENABLED + set_editor_gizmos_dirty(true); +#endif // TOOLS_ENABLED +} + +void SkeletonModificationStack2D::set_modification(int p_mod_idx, Ref p_mod) { + ERR_FAIL_INDEX(p_mod_idx, modifications.size()); + + if (p_mod.is_null()) { + modifications.write[p_mod_idx] = Ref(); + } else { + modifications.write[p_mod_idx] = p_mod; + p_mod->_setup_modification(this); + } + +#ifdef TOOLS_ENABLED + set_editor_gizmos_dirty(true); +#endif // TOOLS_ENABLED +} + +void SkeletonModificationStack2D::set_modification_count(int p_count) { + ERR_FAIL_COND_MSG(p_count < 0, "Modification count cannot be less than zero."); + modifications.resize(p_count); + notify_property_list_changed(); + +#ifdef TOOLS_ENABLED + set_editor_gizmos_dirty(true); +#endif // TOOLS_ENABLED +} + +int SkeletonModificationStack2D::get_modification_count() const { + return modifications.size(); +} + +void SkeletonModificationStack2D::set_skeleton(Skeleton2D *p_skeleton) { + skeleton = p_skeleton; +} + +Skeleton2D *SkeletonModificationStack2D::get_skeleton() const { + return skeleton; +} + +bool SkeletonModificationStack2D::get_is_setup() const { + return is_setup; +} + +void SkeletonModificationStack2D::set_enabled(bool p_enabled) { + enabled = p_enabled; +} + +bool SkeletonModificationStack2D::get_enabled() const { + return enabled; +} + +void SkeletonModificationStack2D::set_strength(float p_strength) { + ERR_FAIL_COND_MSG(p_strength < 0, "Strength cannot be less than zero!"); + ERR_FAIL_COND_MSG(p_strength > 1, "Strength cannot be more than one!"); + strength = p_strength; +} + +float SkeletonModificationStack2D::get_strength() const { + return strength; +} + +void SkeletonModificationStack2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("setup"), &SkeletonModificationStack2D::setup); + ClassDB::bind_method(D_METHOD("execute", "delta", "execution_mode"), &SkeletonModificationStack2D::execute); + + ClassDB::bind_method(D_METHOD("enable_all_modifications", "enabled"), &SkeletonModificationStack2D::enable_all_modifications); + ClassDB::bind_method(D_METHOD("get_modification", "mod_idx"), &SkeletonModificationStack2D::get_modification); + ClassDB::bind_method(D_METHOD("add_modification", "modification"), &SkeletonModificationStack2D::add_modification); + ClassDB::bind_method(D_METHOD("delete_modification", "mod_idx"), &SkeletonModificationStack2D::delete_modification); + ClassDB::bind_method(D_METHOD("set_modification", "mod_idx", "modification"), &SkeletonModificationStack2D::set_modification); + + ClassDB::bind_method(D_METHOD("set_modification_count", "count"), &SkeletonModificationStack2D::set_modification_count); + ClassDB::bind_method(D_METHOD("get_modification_count"), &SkeletonModificationStack2D::get_modification_count); + + ClassDB::bind_method(D_METHOD("get_is_setup"), &SkeletonModificationStack2D::get_is_setup); + + ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &SkeletonModificationStack2D::set_enabled); + ClassDB::bind_method(D_METHOD("get_enabled"), &SkeletonModificationStack2D::get_enabled); + + ClassDB::bind_method(D_METHOD("set_strength", "strength"), &SkeletonModificationStack2D::set_strength); + ClassDB::bind_method(D_METHOD("get_strength"), &SkeletonModificationStack2D::get_strength); + + ClassDB::bind_method(D_METHOD("get_skeleton"), &SkeletonModificationStack2D::get_skeleton); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "get_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "strength", PROPERTY_HINT_RANGE, "0, 1, 0.001"), "set_strength", "get_strength"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "modification_count", PROPERTY_HINT_RANGE, "0, 100, 1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ARRAY, "Modifications,modifications/"), "set_modification_count", "get_modification_count"); +} + +SkeletonModificationStack2D::SkeletonModificationStack2D() { +} diff --git a/scene/resources/2d/skeleton/skeleton_modification_stack_2d.h b/scene/resources/2d/skeleton/skeleton_modification_stack_2d.h new file mode 100644 index 0000000000..0732153997 --- /dev/null +++ b/scene/resources/2d/skeleton/skeleton_modification_stack_2d.h @@ -0,0 +1,99 @@ +/**************************************************************************/ +/* skeleton_modification_stack_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 SKELETON_MODIFICATION_STACK_2D_H +#define SKELETON_MODIFICATION_STACK_2D_H + +#include "scene/2d/skeleton_2d.h" +#include "scene/resources/2d/skeleton/skeleton_modification_2d.h" + +/////////////////////////////////////// +// SkeletonModificationStack2D +/////////////////////////////////////// + +class Skeleton2D; +class SkeletonModification2D; +class Bone2D; + +class SkeletonModificationStack2D : public Resource { + GDCLASS(SkeletonModificationStack2D, Resource); + friend class Skeleton2D; + friend class SkeletonModification2D; + +protected: + static void _bind_methods(); + void _get_property_list(List *p_list) const; + bool _set(const StringName &p_path, const Variant &p_value); + bool _get(const StringName &p_path, Variant &r_ret) const; + +public: + Skeleton2D *skeleton = nullptr; + bool is_setup = false; + bool enabled = false; + float strength = 1.0; + + enum EXECUTION_MODE { + execution_mode_process, + execution_mode_physics_process + }; + + Vector> modifications = Vector>(); + + void setup(); + void execute(float p_delta, int p_execution_mode); + + bool editor_gizmo_dirty = false; + void draw_editor_gizmos(); + void set_editor_gizmos_dirty(bool p_dirty); + + void enable_all_modifications(bool p_enable); + Ref get_modification(int p_mod_idx) const; + void add_modification(Ref p_mod); + void delete_modification(int p_mod_idx); + void set_modification(int p_mod_idx, Ref p_mod); + + void set_modification_count(int p_count); + int get_modification_count() const; + + void set_skeleton(Skeleton2D *p_skeleton); + Skeleton2D *get_skeleton() const; + + bool get_is_setup() const; + + void set_enabled(bool p_enabled); + bool get_enabled() const; + + void set_strength(float p_strength); + float get_strength() const; + + SkeletonModificationStack2D(); +}; + +#endif // SKELETON_MODIFICATION_STACK_2D_H diff --git a/scene/resources/2d/tile_set.compat.inc b/scene/resources/2d/tile_set.compat.inc new file mode 100644 index 0000000000..873ae3aa93 --- /dev/null +++ b/scene/resources/2d/tile_set.compat.inc @@ -0,0 +1,48 @@ +/**************************************************************************/ +/* tile_set.compat.inc */ +/**************************************************************************/ +/* 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 DISABLE_DEPRECATED + +#include "tile_set.h" + +Ref TileData::_get_navigation_polygon_bind_compat_84660(int p_layer_id) const { + return get_navigation_polygon(p_layer_id, false, false, false); +} + +Ref TileData::_get_occluder_bind_compat_84660(int p_layer_id) const { + return get_occluder(p_layer_id, false, false, false); +} + +void TileData::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("get_navigation_polygon"), &TileData::_get_navigation_polygon_bind_compat_84660); + ClassDB::bind_compatibility_method(D_METHOD("get_occluder"), &TileData::_get_occluder_bind_compat_84660); +} + +#endif diff --git a/scene/resources/2d/tile_set.cpp b/scene/resources/2d/tile_set.cpp new file mode 100644 index 0000000000..5ac7f127b0 --- /dev/null +++ b/scene/resources/2d/tile_set.cpp @@ -0,0 +1,6927 @@ +/**************************************************************************/ +/* tile_set.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 "tile_set.h" +#include "tile_set.compat.inc" + +#include "core/io/marshalls.h" +#include "core/math/geometry_2d.h" +#include "core/templates/local_vector.h" +#include "core/templates/rb_set.h" +#include "scene/gui/control.h" +#include "scene/resources/image_texture.h" +#include "servers/navigation_server_2d.h" + +/////////////////////////////// TileMapPattern ////////////////////////////////////// + +void TileMapPattern::_set_tile_data(const Vector &p_data) { + int c = p_data.size(); + const int *r = p_data.ptr(); + + int offset = 3; + ERR_FAIL_COND_MSG(c % offset != 0, "Corrupted tile data."); + + clear(); + + for (int i = 0; i < c; i += offset) { + const uint8_t *ptr = (const uint8_t *)&r[i]; + uint8_t local[12]; + for (int j = 0; j < 12; j++) { + local[j] = ptr[j]; + } + +#ifdef BIG_ENDIAN_ENABLED + SWAP(local[0], local[3]); + SWAP(local[1], local[2]); + SWAP(local[4], local[7]); + SWAP(local[5], local[6]); + SWAP(local[8], local[11]); + SWAP(local[9], local[10]); +#endif + + int16_t x = decode_uint16(&local[0]); + int16_t y = decode_uint16(&local[2]); + uint16_t source_id = decode_uint16(&local[4]); + uint16_t atlas_coords_x = decode_uint16(&local[6]); + uint16_t atlas_coords_y = decode_uint16(&local[8]); + uint16_t alternative_tile = decode_uint16(&local[10]); + set_cell(Vector2i(x, y), source_id, Vector2i(atlas_coords_x, atlas_coords_y), alternative_tile); + } + emit_signal(SNAME("changed")); +} + +Vector TileMapPattern::_get_tile_data() const { + // Export tile data to raw format + Vector data; + data.resize(pattern.size() * 3); + int *w = data.ptrw(); + + // Save in highest format + + int idx = 0; + for (const KeyValue &E : pattern) { + uint8_t *ptr = (uint8_t *)&w[idx]; + encode_uint16((int16_t)(E.key.x), &ptr[0]); + encode_uint16((int16_t)(E.key.y), &ptr[2]); + encode_uint16(E.value.source_id, &ptr[4]); + encode_uint16(E.value.coord_x, &ptr[6]); + encode_uint16(E.value.coord_y, &ptr[8]); + encode_uint16(E.value.alternative_tile, &ptr[10]); + idx += 3; + } + + return data; +} + +void TileMapPattern::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) { + ERR_FAIL_COND_MSG(p_coords.x < 0 || p_coords.y < 0, vformat("Cannot set cell with negative coords in a TileMapPattern. Wrong coords: %s", p_coords)); + + size = size.max(p_coords + Vector2i(1, 1)); + pattern[p_coords] = TileMapCell(p_source_id, p_atlas_coords, p_alternative_tile); + emit_changed(); +} + +bool TileMapPattern::has_cell(const Vector2i &p_coords) const { + return pattern.has(p_coords); +} + +void TileMapPattern::remove_cell(const Vector2i &p_coords, bool p_update_size) { + ERR_FAIL_COND(!pattern.has(p_coords)); + + pattern.erase(p_coords); + if (p_update_size) { + size = Size2i(); + for (const KeyValue &E : pattern) { + size = size.max(E.key + Vector2i(1, 1)); + } + } + emit_changed(); +} + +int TileMapPattern::get_cell_source_id(const Vector2i &p_coords) const { + ERR_FAIL_COND_V(!pattern.has(p_coords), TileSet::INVALID_SOURCE); + + return pattern[p_coords].source_id; +} + +Vector2i TileMapPattern::get_cell_atlas_coords(const Vector2i &p_coords) const { + ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetSource::INVALID_ATLAS_COORDS); + + return pattern[p_coords].get_atlas_coords(); +} + +int TileMapPattern::get_cell_alternative_tile(const Vector2i &p_coords) const { + ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetSource::INVALID_TILE_ALTERNATIVE); + + return pattern[p_coords].alternative_tile; +} + +TypedArray TileMapPattern::get_used_cells() const { + // Returns the cells used in the tilemap. + TypedArray a; + a.resize(pattern.size()); + int i = 0; + for (const KeyValue &E : pattern) { + Vector2i p(E.key.x, E.key.y); + a[i++] = p; + } + + return a; +} + +Size2i TileMapPattern::get_size() const { + return size; +} + +void TileMapPattern::set_size(const Size2i &p_size) { + for (const KeyValue &E : pattern) { + Vector2i coords = E.key; + if (p_size.x <= coords.x || p_size.y <= coords.y) { + ERR_FAIL_MSG(vformat("Cannot set pattern size to %s, it contains a tile at %s. Size can only be increased.", p_size, coords)); + }; + } + + size = p_size; + emit_changed(); +} + +bool TileMapPattern::is_empty() const { + return pattern.is_empty(); +}; + +void TileMapPattern::clear() { + size = Size2i(); + pattern.clear(); + emit_changed(); +}; + +bool TileMapPattern::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "tile_data") { + if (p_value.is_array()) { + _set_tile_data(p_value); + return true; + } + return false; + } + return false; +} + +bool TileMapPattern::_get(const StringName &p_name, Variant &r_ret) const { + if (p_name == "tile_data") { + r_ret = _get_tile_data(); + return true; + } + return false; +} + +void TileMapPattern::_get_property_list(List *p_list) const { + p_list->push_back(PropertyInfo(Variant::OBJECT, "tile_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); +} + +void TileMapPattern::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMapPattern::set_cell, DEFVAL(TileSet::INVALID_SOURCE), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetSource::INVALID_TILE_ALTERNATIVE)); + ClassDB::bind_method(D_METHOD("has_cell", "coords"), &TileMapPattern::has_cell); + ClassDB::bind_method(D_METHOD("remove_cell", "coords", "update_size"), &TileMapPattern::remove_cell); + ClassDB::bind_method(D_METHOD("get_cell_source_id", "coords"), &TileMapPattern::get_cell_source_id); + ClassDB::bind_method(D_METHOD("get_cell_atlas_coords", "coords"), &TileMapPattern::get_cell_atlas_coords); + ClassDB::bind_method(D_METHOD("get_cell_alternative_tile", "coords"), &TileMapPattern::get_cell_alternative_tile); + + ClassDB::bind_method(D_METHOD("get_used_cells"), &TileMapPattern::get_used_cells); + ClassDB::bind_method(D_METHOD("get_size"), &TileMapPattern::get_size); + ClassDB::bind_method(D_METHOD("set_size", "size"), &TileMapPattern::set_size); + ClassDB::bind_method(D_METHOD("is_empty"), &TileMapPattern::is_empty); +} + +/////////////////////////////// TileSet ////////////////////////////////////// + +bool TileSet::TerrainsPattern::is_valid() const { + return valid; +} + +bool TileSet::TerrainsPattern::is_erase_pattern() const { + return not_empty_terrains_count == 0; +} + +bool TileSet::TerrainsPattern::operator<(const TerrainsPattern &p_terrains_pattern) const { + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + if (is_valid_bit[i] != p_terrains_pattern.is_valid_bit[i]) { + return is_valid_bit[i] < p_terrains_pattern.is_valid_bit[i]; + } + } + if (terrain != p_terrains_pattern.terrain) { + return terrain < p_terrains_pattern.terrain; + } + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + if (is_valid_bit[i] && bits[i] != p_terrains_pattern.bits[i]) { + return bits[i] < p_terrains_pattern.bits[i]; + } + } + return false; +} + +bool TileSet::TerrainsPattern::operator==(const TerrainsPattern &p_terrains_pattern) const { + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + if (is_valid_bit[i] != p_terrains_pattern.is_valid_bit[i]) { + return false; + } + if (is_valid_bit[i] && bits[i] != p_terrains_pattern.bits[i]) { + return false; + } + } + if (terrain != p_terrains_pattern.terrain) { + return false; + } + return true; +} + +void TileSet::TerrainsPattern::set_terrain(int p_terrain) { + ERR_FAIL_COND(p_terrain < -1); + + terrain = p_terrain; +} + +int TileSet::TerrainsPattern::get_terrain() const { + return terrain; +} + +void TileSet::TerrainsPattern::set_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit, int p_terrain) { + ERR_FAIL_COND(p_peering_bit == TileSet::CELL_NEIGHBOR_MAX); + ERR_FAIL_COND(!is_valid_bit[p_peering_bit]); + ERR_FAIL_COND(p_terrain < -1); + + // Update the "is_erase_pattern" status. + if (p_terrain >= 0 && bits[p_peering_bit] < 0) { + not_empty_terrains_count++; + } else if (p_terrain < 0 && bits[p_peering_bit] >= 0) { + not_empty_terrains_count--; + } + + bits[p_peering_bit] = p_terrain; +} + +int TileSet::TerrainsPattern::get_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit) const { + ERR_FAIL_COND_V(p_peering_bit == TileSet::CELL_NEIGHBOR_MAX, -1); + ERR_FAIL_COND_V(!is_valid_bit[p_peering_bit], -1); + return bits[p_peering_bit]; +} + +void TileSet::TerrainsPattern::from_array(Array p_terrains) { + set_terrain(p_terrains[0]); + int in_array_index = 1; + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + if (is_valid_bit[i]) { + ERR_FAIL_INDEX(in_array_index, p_terrains.size()); + set_terrain_peering_bit(TileSet::CellNeighbor(i), p_terrains[in_array_index]); + in_array_index++; + } + } +} + +Array TileSet::TerrainsPattern::as_array() const { + Array output; + output.push_back(get_terrain()); + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + if (is_valid_bit[i]) { + output.push_back(bits[i]); + } + } + return output; +} + +TileSet::TerrainsPattern::TerrainsPattern(const TileSet *p_tile_set, int p_terrain_set) { + ERR_FAIL_COND(p_terrain_set < 0); + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + is_valid_bit[i] = (p_tile_set->is_valid_terrain_peering_bit(p_terrain_set, TileSet::CellNeighbor(i))); + bits[i] = -1; + } + valid = true; +} + +const int TileSet::INVALID_SOURCE = -1; + +const char *TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[] = { + PNAME("right_side"), + PNAME("right_corner"), + PNAME("bottom_right_side"), + PNAME("bottom_right_corner"), + PNAME("bottom_side"), + PNAME("bottom_corner"), + PNAME("bottom_left_side"), + PNAME("bottom_left_corner"), + PNAME("left_side"), + PNAME("left_corner"), + PNAME("top_left_side"), + PNAME("top_left_corner"), + PNAME("top_side"), + PNAME("top_corner"), + PNAME("top_right_side"), + PNAME("top_right_corner"), +}; + +// -- Shape and layout -- +void TileSet::set_tile_shape(TileSet::TileShape p_shape) { + tile_shape = p_shape; + + for (KeyValue> &E_source : sources) { + E_source.value->notify_tile_data_properties_should_change(); + } + + terrain_bits_meshes_dirty = true; + tile_meshes_dirty = true; + notify_property_list_changed(); + emit_changed(); +} +TileSet::TileShape TileSet::get_tile_shape() const { + return tile_shape; +} + +void TileSet::set_tile_layout(TileSet::TileLayout p_layout) { + tile_layout = p_layout; + emit_changed(); +} +TileSet::TileLayout TileSet::get_tile_layout() const { + return tile_layout; +} + +void TileSet::set_tile_offset_axis(TileSet::TileOffsetAxis p_alignment) { + tile_offset_axis = p_alignment; + + for (KeyValue> &E_source : sources) { + E_source.value->notify_tile_data_properties_should_change(); + } + + terrain_bits_meshes_dirty = true; + tile_meshes_dirty = true; + emit_changed(); +} +TileSet::TileOffsetAxis TileSet::get_tile_offset_axis() const { + return tile_offset_axis; +} + +void TileSet::set_tile_size(Size2i p_size) { + ERR_FAIL_COND(p_size.x < 1 || p_size.y < 1); + tile_size = p_size; + terrain_bits_meshes_dirty = true; + tile_meshes_dirty = true; + emit_changed(); +} +Size2i TileSet::get_tile_size() const { + return tile_size; +} + +int TileSet::get_next_source_id() const { + return next_source_id; +} + +void TileSet::_update_terrains_cache() { + if (terrains_cache_dirty) { + // Organizes tiles into structures. + per_terrain_pattern_tiles.resize(terrain_sets.size()); + for (RBMap> &tiles : per_terrain_pattern_tiles) { + tiles.clear(); + } + + for (const KeyValue> &kv : sources) { + Ref source = kv.value; + Ref atlas_source = source; + if (atlas_source.is_valid()) { + for (int tile_index = 0; tile_index < source->get_tiles_count(); tile_index++) { + Vector2i tile_id = source->get_tile_id(tile_index); + for (int alternative_index = 0; alternative_index < source->get_alternative_tiles_count(tile_id); alternative_index++) { + int alternative_id = source->get_alternative_tile_id(tile_id, alternative_index); + + // Executed for each tile_data. + TileData *tile_data = atlas_source->get_tile_data(tile_id, alternative_id); + int terrain_set = tile_data->get_terrain_set(); + if (terrain_set >= 0) { + TileMapCell cell; + cell.source_id = kv.key; + cell.set_atlas_coords(tile_id); + cell.alternative_tile = alternative_id; + + TileSet::TerrainsPattern terrains_pattern = tile_data->get_terrains_pattern(); + + // Main terrain. + if (terrains_pattern.get_terrain() >= 0) { + per_terrain_pattern_tiles[terrain_set][terrains_pattern].insert(cell); + } + + // Terrain bits. + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + CellNeighbor bit = CellNeighbor(i); + if (is_valid_terrain_peering_bit(terrain_set, bit)) { + int terrain = terrains_pattern.get_terrain_peering_bit(bit); + if (terrain >= 0) { + per_terrain_pattern_tiles[terrain_set][terrains_pattern].insert(cell); + } + } + } + } + } + } + } + } + + // Add the empty cell in the possible patterns and cells. + for (int i = 0; i < terrain_sets.size(); i++) { + TileSet::TerrainsPattern empty_pattern(this, i); + + TileMapCell empty_cell; + empty_cell.source_id = TileSet::INVALID_SOURCE; + empty_cell.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); + empty_cell.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; + per_terrain_pattern_tiles[i][empty_pattern].insert(empty_cell); + } + terrains_cache_dirty = false; + } +} + +void TileSet::_compute_next_source_id() { + while (sources.has(next_source_id)) { + next_source_id = (next_source_id + 1) % 1073741824; // 2 ** 30 + }; +} + +// Sources management +int TileSet::add_source(Ref p_tile_set_source, int p_atlas_source_id_override) { + ERR_FAIL_COND_V(!p_tile_set_source.is_valid(), TileSet::INVALID_SOURCE); + ERR_FAIL_COND_V_MSG(p_atlas_source_id_override >= 0 && (sources.has(p_atlas_source_id_override)), TileSet::INVALID_SOURCE, vformat("Cannot create TileSet atlas source. Another atlas source exists with id %d.", p_atlas_source_id_override)); + ERR_FAIL_COND_V_MSG(p_atlas_source_id_override < 0 && p_atlas_source_id_override != TileSet::INVALID_SOURCE, TileSet::INVALID_SOURCE, vformat("Provided source ID %d is not valid. Negative source IDs are not allowed.", p_atlas_source_id_override)); + + int new_source_id = p_atlas_source_id_override >= 0 ? p_atlas_source_id_override : next_source_id; + sources[new_source_id] = p_tile_set_source; + source_ids.push_back(new_source_id); + source_ids.sort(); + TileSet *old_tileset = p_tile_set_source->get_tile_set(); + if (old_tileset != this && old_tileset != nullptr) { + // If the source is already in a TileSet (might happen when duplicating), remove it from the other TileSet. + old_tileset->remove_source_ptr(p_tile_set_source.ptr()); + } + p_tile_set_source->set_tile_set(this); + _compute_next_source_id(); + + sources[new_source_id]->connect_changed(callable_mp(this, &TileSet::_source_changed)); + + terrains_cache_dirty = true; + emit_changed(); + + return new_source_id; +} + +void TileSet::remove_source(int p_source_id) { + ERR_FAIL_COND_MSG(!sources.has(p_source_id), vformat("Cannot remove TileSet atlas source. No tileset atlas source with id %d.", p_source_id)); + + sources[p_source_id]->disconnect_changed(callable_mp(this, &TileSet::_source_changed)); + + sources[p_source_id]->set_tile_set(nullptr); + sources.erase(p_source_id); + source_ids.erase(p_source_id); + source_ids.sort(); + + terrains_cache_dirty = true; + emit_changed(); +} + +void TileSet::remove_source_ptr(TileSetSource *p_tile_set_source) { + for (const KeyValue> &kv : sources) { + if (kv.value.ptr() == p_tile_set_source) { + remove_source(kv.key); + return; + } + } + ERR_FAIL_MSG(vformat("Attempting to remove source from a tileset, but the tileset doesn't have it: %s", p_tile_set_source)); +} + +void TileSet::set_source_id(int p_source_id, int p_new_source_id) { + ERR_FAIL_COND(p_new_source_id < 0); + ERR_FAIL_COND_MSG(!sources.has(p_source_id), vformat("Cannot change TileSet atlas source ID. No tileset atlas source with id %d.", p_source_id)); + if (p_source_id == p_new_source_id) { + return; + } + + ERR_FAIL_COND_MSG(sources.has(p_new_source_id), vformat("Cannot change TileSet atlas source ID. Another atlas source exists with id %d.", p_new_source_id)); + + sources[p_new_source_id] = sources[p_source_id]; + sources.erase(p_source_id); + + source_ids.erase(p_source_id); + source_ids.push_back(p_new_source_id); + source_ids.sort(); + + _compute_next_source_id(); + + terrains_cache_dirty = true; + emit_changed(); +} + +bool TileSet::has_source(int p_source_id) const { + return sources.has(p_source_id); +} + +Ref TileSet::get_source(int p_source_id) const { + ERR_FAIL_COND_V_MSG(!sources.has(p_source_id), nullptr, vformat("No TileSet atlas source with id %d.", p_source_id)); + + return sources[p_source_id]; +} + +int TileSet::get_source_count() const { + return source_ids.size(); +} + +int TileSet::get_source_id(int p_index) const { + ERR_FAIL_INDEX_V(p_index, source_ids.size(), TileSet::INVALID_SOURCE); + return source_ids[p_index]; +} + +// Rendering +void TileSet::set_uv_clipping(bool p_uv_clipping) { + if (uv_clipping == p_uv_clipping) { + return; + } + uv_clipping = p_uv_clipping; + emit_changed(); +} + +bool TileSet::is_uv_clipping() const { + return uv_clipping; +}; + +int TileSet::get_occlusion_layers_count() const { + return occlusion_layers.size(); +}; + +void TileSet::add_occlusion_layer(int p_index) { + if (p_index < 0) { + p_index = occlusion_layers.size(); + } + ERR_FAIL_INDEX(p_index, occlusion_layers.size() + 1); + occlusion_layers.insert(p_index, OcclusionLayer()); + + for (KeyValue> source : sources) { + source.value->add_occlusion_layer(p_index); + } + + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::move_occlusion_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, occlusion_layers.size()); + ERR_FAIL_INDEX(p_to_pos, occlusion_layers.size() + 1); + occlusion_layers.insert(p_to_pos, occlusion_layers[p_from_index]); + occlusion_layers.remove_at(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); + for (KeyValue> source : sources) { + source.value->move_occlusion_layer(p_from_index, p_to_pos); + } + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::remove_occlusion_layer(int p_index) { + ERR_FAIL_INDEX(p_index, occlusion_layers.size()); + occlusion_layers.remove_at(p_index); + for (KeyValue> source : sources) { + source.value->remove_occlusion_layer(p_index); + } + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::set_occlusion_layer_light_mask(int p_layer_index, int p_light_mask) { + ERR_FAIL_INDEX(p_layer_index, occlusion_layers.size()); + occlusion_layers.write[p_layer_index].light_mask = p_light_mask; + emit_changed(); +} + +int TileSet::get_occlusion_layer_light_mask(int p_layer_index) const { + ERR_FAIL_INDEX_V(p_layer_index, occlusion_layers.size(), 0); + return occlusion_layers[p_layer_index].light_mask; +} + +void TileSet::set_occlusion_layer_sdf_collision(int p_layer_index, bool p_sdf_collision) { + ERR_FAIL_INDEX(p_layer_index, occlusion_layers.size()); + occlusion_layers.write[p_layer_index].sdf_collision = p_sdf_collision; + emit_changed(); +} + +bool TileSet::get_occlusion_layer_sdf_collision(int p_layer_index) const { + ERR_FAIL_INDEX_V(p_layer_index, occlusion_layers.size(), false); + return occlusion_layers[p_layer_index].sdf_collision; +} + +int TileSet::get_physics_layers_count() const { + return physics_layers.size(); +} + +void TileSet::add_physics_layer(int p_index) { + if (p_index < 0) { + p_index = physics_layers.size(); + } + ERR_FAIL_INDEX(p_index, physics_layers.size() + 1); + physics_layers.insert(p_index, PhysicsLayer()); + + for (KeyValue> source : sources) { + source.value->add_physics_layer(p_index); + } + + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::move_physics_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, physics_layers.size()); + ERR_FAIL_INDEX(p_to_pos, physics_layers.size() + 1); + physics_layers.insert(p_to_pos, physics_layers[p_from_index]); + physics_layers.remove_at(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); + for (KeyValue> source : sources) { + source.value->move_physics_layer(p_from_index, p_to_pos); + } + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::remove_physics_layer(int p_index) { + ERR_FAIL_INDEX(p_index, physics_layers.size()); + physics_layers.remove_at(p_index); + for (KeyValue> source : sources) { + source.value->remove_physics_layer(p_index); + } + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::set_physics_layer_collision_layer(int p_layer_index, uint32_t p_layer) { + ERR_FAIL_INDEX(p_layer_index, physics_layers.size()); + physics_layers.write[p_layer_index].collision_layer = p_layer; + emit_changed(); +} + +uint32_t TileSet::get_physics_layer_collision_layer(int p_layer_index) const { + ERR_FAIL_INDEX_V(p_layer_index, physics_layers.size(), 0); + return physics_layers[p_layer_index].collision_layer; +} + +void TileSet::set_physics_layer_collision_mask(int p_layer_index, uint32_t p_mask) { + ERR_FAIL_INDEX(p_layer_index, physics_layers.size()); + physics_layers.write[p_layer_index].collision_mask = p_mask; + emit_changed(); +} + +uint32_t TileSet::get_physics_layer_collision_mask(int p_layer_index) const { + ERR_FAIL_INDEX_V(p_layer_index, physics_layers.size(), 0); + return physics_layers[p_layer_index].collision_mask; +} + +void TileSet::set_physics_layer_physics_material(int p_layer_index, Ref p_physics_material) { + ERR_FAIL_INDEX(p_layer_index, physics_layers.size()); + physics_layers.write[p_layer_index].physics_material = p_physics_material; +} + +Ref TileSet::get_physics_layer_physics_material(int p_layer_index) const { + ERR_FAIL_INDEX_V(p_layer_index, physics_layers.size(), Ref()); + return physics_layers[p_layer_index].physics_material; +} + +// Terrains +int TileSet::get_terrain_sets_count() const { + return terrain_sets.size(); +} + +void TileSet::add_terrain_set(int p_index) { + if (p_index < 0) { + p_index = terrain_sets.size(); + } + ERR_FAIL_INDEX(p_index, terrain_sets.size() + 1); + terrain_sets.insert(p_index, TerrainSet()); + + for (KeyValue> source : sources) { + source.value->add_terrain_set(p_index); + } + + notify_property_list_changed(); + terrains_cache_dirty = true; + emit_changed(); +} + +void TileSet::move_terrain_set(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, terrain_sets.size()); + ERR_FAIL_INDEX(p_to_pos, terrain_sets.size() + 1); + terrain_sets.insert(p_to_pos, terrain_sets[p_from_index]); + terrain_sets.remove_at(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); + for (KeyValue> source : sources) { + source.value->move_terrain_set(p_from_index, p_to_pos); + } + notify_property_list_changed(); + terrains_cache_dirty = true; + emit_changed(); +} + +void TileSet::remove_terrain_set(int p_index) { + ERR_FAIL_INDEX(p_index, terrain_sets.size()); + terrain_sets.remove_at(p_index); + for (KeyValue> source : sources) { + source.value->remove_terrain_set(p_index); + } + notify_property_list_changed(); + terrains_cache_dirty = true; + emit_changed(); +} + +void TileSet::set_terrain_set_mode(int p_terrain_set, TerrainMode p_terrain_mode) { + ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size()); + terrain_sets.write[p_terrain_set].mode = p_terrain_mode; + for (KeyValue> &E_source : sources) { + E_source.value->notify_tile_data_properties_should_change(); + } + + notify_property_list_changed(); + terrains_cache_dirty = true; + emit_changed(); +} + +TileSet::TerrainMode TileSet::get_terrain_set_mode(int p_terrain_set) const { + ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES); + return terrain_sets[p_terrain_set].mode; +} + +int TileSet::get_terrains_count(int p_terrain_set) const { + ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), -1); + return terrain_sets[p_terrain_set].terrains.size(); +} + +void TileSet::add_terrain(int p_terrain_set, int p_index) { + ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size()); + Vector &terrains = terrain_sets.write[p_terrain_set].terrains; + if (p_index < 0) { + p_index = terrains.size(); + } + ERR_FAIL_INDEX(p_index, terrains.size() + 1); + terrains.insert(p_index, Terrain()); + + // Default name and color + float hue_rotate = (terrains.size() % 16) / 16.0; + Color c; + c.set_hsv(Math::fmod(float(hue_rotate), float(1.0)), 0.5, 0.5); + terrains.write[p_index].color = c; + terrains.write[p_index].name = String(vformat("Terrain %d", p_index)); + + for (KeyValue> source : sources) { + source.value->add_terrain(p_terrain_set, p_index); + } + + notify_property_list_changed(); + terrains_cache_dirty = true; + emit_changed(); +} + +void TileSet::move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size()); + Vector &terrains = terrain_sets.write[p_terrain_set].terrains; + + ERR_FAIL_INDEX(p_from_index, terrains.size()); + ERR_FAIL_INDEX(p_to_pos, terrains.size() + 1); + terrains.insert(p_to_pos, terrains[p_from_index]); + terrains.remove_at(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); + for (KeyValue> source : sources) { + source.value->move_terrain(p_terrain_set, p_from_index, p_to_pos); + } + notify_property_list_changed(); + terrains_cache_dirty = true; + emit_changed(); +} + +void TileSet::remove_terrain(int p_terrain_set, int p_index) { + ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size()); + Vector &terrains = terrain_sets.write[p_terrain_set].terrains; + + ERR_FAIL_INDEX(p_index, terrains.size()); + terrains.remove_at(p_index); + for (KeyValue> source : sources) { + source.value->remove_terrain(p_terrain_set, p_index); + } + notify_property_list_changed(); + terrains_cache_dirty = true; + emit_changed(); +} + +void TileSet::set_terrain_name(int p_terrain_set, int p_terrain_index, String p_name) { + ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size()); + ERR_FAIL_INDEX(p_terrain_index, terrain_sets[p_terrain_set].terrains.size()); + terrain_sets.write[p_terrain_set].terrains.write[p_terrain_index].name = p_name; + emit_changed(); +} + +String TileSet::get_terrain_name(int p_terrain_set, int p_terrain_index) const { + ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), String()); + ERR_FAIL_INDEX_V(p_terrain_index, terrain_sets[p_terrain_set].terrains.size(), String()); + return terrain_sets[p_terrain_set].terrains[p_terrain_index].name; +} + +void TileSet::set_terrain_color(int p_terrain_set, int p_terrain_index, Color p_color) { + ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size()); + ERR_FAIL_INDEX(p_terrain_index, terrain_sets[p_terrain_set].terrains.size()); + if (p_color.a != 1.0) { + WARN_PRINT("Terrain color should have alpha == 1.0"); + p_color.a = 1.0; + } + terrain_sets.write[p_terrain_set].terrains.write[p_terrain_index].color = p_color; + emit_changed(); +} + +Color TileSet::get_terrain_color(int p_terrain_set, int p_terrain_index) const { + ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), Color()); + ERR_FAIL_INDEX_V(p_terrain_index, terrain_sets[p_terrain_set].terrains.size(), Color()); + return terrain_sets[p_terrain_set].terrains[p_terrain_index].color; +} + +bool TileSet::is_valid_terrain_peering_bit_for_mode(TileSet::TerrainMode p_terrain_mode, TileSet::CellNeighbor p_peering_bit) const { + if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { + if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_SIDES) { + if (p_peering_bit == TileSet::CELL_NEIGHBOR_RIGHT_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_LEFT_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_SIDE) { + return true; + } + } + if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { + if (p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER) { + return true; + } + } + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_SIDES) { + if (p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return true; + } + } + if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { + if (p_peering_bit == TileSet::CELL_NEIGHBOR_RIGHT_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_LEFT_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_CORNER) { + return true; + } + } + } else { + if (get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_SIDES) { + if (p_peering_bit == TileSet::CELL_NEIGHBOR_RIGHT_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_LEFT_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return true; + } + } + if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { + if (p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER) { + return true; + } + } + } else { + if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_SIDES) { + if (p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_SIDE || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return true; + } + } + if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { + if (p_peering_bit == TileSet::CELL_NEIGHBOR_RIGHT_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_LEFT_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER || + p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER) { + return true; + } + } + } + } + return false; +} + +bool TileSet::is_valid_terrain_peering_bit(int p_terrain_set, TileSet::CellNeighbor p_peering_bit) const { + if (p_terrain_set < 0 || p_terrain_set >= get_terrain_sets_count()) { + return false; + } + + TileSet::TerrainMode terrain_mode = get_terrain_set_mode(p_terrain_set); + return is_valid_terrain_peering_bit_for_mode(terrain_mode, p_peering_bit); +} + +// Navigation +int TileSet::get_navigation_layers_count() const { + return navigation_layers.size(); +} + +void TileSet::add_navigation_layer(int p_index) { + if (p_index < 0) { + p_index = navigation_layers.size(); + } + ERR_FAIL_INDEX(p_index, navigation_layers.size() + 1); + navigation_layers.insert(p_index, NavigationLayer()); + + for (KeyValue> source : sources) { + source.value->add_navigation_layer(p_index); + } + + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::move_navigation_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, navigation_layers.size()); + ERR_FAIL_INDEX(p_to_pos, navigation_layers.size() + 1); + navigation_layers.insert(p_to_pos, navigation_layers[p_from_index]); + navigation_layers.remove_at(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); + for (KeyValue> source : sources) { + source.value->move_navigation_layer(p_from_index, p_to_pos); + } + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::remove_navigation_layer(int p_index) { + ERR_FAIL_INDEX(p_index, navigation_layers.size()); + navigation_layers.remove_at(p_index); + for (KeyValue> source : sources) { + source.value->remove_navigation_layer(p_index); + } + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::set_navigation_layer_layers(int p_layer_index, uint32_t p_layers) { + ERR_FAIL_INDEX(p_layer_index, navigation_layers.size()); + navigation_layers.write[p_layer_index].layers = p_layers; + emit_changed(); +} + +uint32_t TileSet::get_navigation_layer_layers(int p_layer_index) const { + ERR_FAIL_INDEX_V(p_layer_index, navigation_layers.size(), 0); + return navigation_layers[p_layer_index].layers; +} + +void TileSet::set_navigation_layer_layer_value(int p_layer_index, int p_layer_number, bool p_value) { + ERR_FAIL_COND_MSG(p_layer_number < 1, "Navigation layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_MSG(p_layer_number > 32, "Navigation layer number must be between 1 and 32 inclusive."); + + uint32_t _navigation_layers = get_navigation_layer_layers(p_layer_index); + + if (p_value) { + _navigation_layers |= 1 << (p_layer_number - 1); + } else { + _navigation_layers &= ~(1 << (p_layer_number - 1)); + } + + set_navigation_layer_layers(p_layer_index, _navigation_layers); +} + +bool TileSet::get_navigation_layer_layer_value(int p_layer_index, int p_layer_number) const { + ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Navigation layer number must be between 1 and 32 inclusive."); + ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Navigation layer number must be between 1 and 32 inclusive."); + + return get_navigation_layer_layers(p_layer_index) & (1 << (p_layer_number - 1)); +} + +// Custom data. +int TileSet::get_custom_data_layers_count() const { + return custom_data_layers.size(); +} + +void TileSet::add_custom_data_layer(int p_index) { + if (p_index < 0) { + p_index = custom_data_layers.size(); + } + ERR_FAIL_INDEX(p_index, custom_data_layers.size() + 1); + custom_data_layers.insert(p_index, CustomDataLayer()); + + for (KeyValue> source : sources) { + source.value->add_custom_data_layer(p_index); + } + + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::move_custom_data_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, custom_data_layers.size()); + ERR_FAIL_INDEX(p_to_pos, custom_data_layers.size() + 1); + custom_data_layers.insert(p_to_pos, custom_data_layers[p_from_index]); + custom_data_layers.remove_at(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); + for (KeyValue> source : sources) { + source.value->move_custom_data_layer(p_from_index, p_to_pos); + } + notify_property_list_changed(); + emit_changed(); +} + +void TileSet::remove_custom_data_layer(int p_index) { + ERR_FAIL_INDEX(p_index, custom_data_layers.size()); + custom_data_layers.remove_at(p_index); + + String to_erase; + for (KeyValue &E : custom_data_layers_by_name) { + if (E.value == p_index) { + to_erase = E.key; + } else if (E.value > p_index) { + E.value--; + } + } + custom_data_layers_by_name.erase(to_erase); + + for (KeyValue> source : sources) { + source.value->remove_custom_data_layer(p_index); + } + notify_property_list_changed(); + emit_changed(); +} + +int TileSet::get_custom_data_layer_by_name(String p_value) const { + if (custom_data_layers_by_name.has(p_value)) { + return custom_data_layers_by_name[p_value]; + } else { + return -1; + } +} + +void TileSet::set_custom_data_layer_name(int p_layer_id, String p_value) { + ERR_FAIL_INDEX(p_layer_id, custom_data_layers.size()); + + // Exit if another property has the same name. + if (!p_value.is_empty()) { + for (int other_layer_id = 0; other_layer_id < get_custom_data_layers_count(); other_layer_id++) { + if (other_layer_id != p_layer_id && get_custom_data_layer_name(other_layer_id) == p_value) { + ERR_FAIL_MSG(vformat("There is already a custom property named %s", p_value)); + } + } + } + + if (p_value.is_empty() && custom_data_layers_by_name.has(p_value)) { + custom_data_layers_by_name.erase(p_value); + } else { + custom_data_layers_by_name[p_value] = p_layer_id; + } + + custom_data_layers.write[p_layer_id].name = p_value; + emit_changed(); +} + +String TileSet::get_custom_data_layer_name(int p_layer_id) const { + ERR_FAIL_INDEX_V(p_layer_id, custom_data_layers.size(), ""); + return custom_data_layers[p_layer_id].name; +} + +void TileSet::set_custom_data_layer_type(int p_layer_id, Variant::Type p_value) { + ERR_FAIL_INDEX(p_layer_id, custom_data_layers.size()); + custom_data_layers.write[p_layer_id].type = p_value; + + for (KeyValue> &E_source : sources) { + E_source.value->notify_tile_data_properties_should_change(); + } + + emit_changed(); +} + +Variant::Type TileSet::get_custom_data_layer_type(int p_layer_id) const { + ERR_FAIL_INDEX_V(p_layer_id, custom_data_layers.size(), Variant::NIL); + return custom_data_layers[p_layer_id].type; +} + +void TileSet::set_source_level_tile_proxy(int p_source_from, int p_source_to) { + ERR_FAIL_COND(p_source_from == TileSet::INVALID_SOURCE || p_source_to == TileSet::INVALID_SOURCE); + + source_level_proxies[p_source_from] = p_source_to; + + emit_changed(); +} + +int TileSet::get_source_level_tile_proxy(int p_source_from) { + ERR_FAIL_COND_V(!source_level_proxies.has(p_source_from), TileSet::INVALID_SOURCE); + + return source_level_proxies[p_source_from]; +} + +bool TileSet::has_source_level_tile_proxy(int p_source_from) { + return source_level_proxies.has(p_source_from); +} + +void TileSet::remove_source_level_tile_proxy(int p_source_from) { + ERR_FAIL_COND(!source_level_proxies.has(p_source_from)); + + source_level_proxies.erase(p_source_from); + + emit_changed(); +} + +void TileSet::set_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_source_to, Vector2i p_coords_to) { + ERR_FAIL_COND(p_source_from == TileSet::INVALID_SOURCE || p_source_to == TileSet::INVALID_SOURCE); + ERR_FAIL_COND(p_coords_from == TileSetSource::INVALID_ATLAS_COORDS || p_coords_to == TileSetSource::INVALID_ATLAS_COORDS); + + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + + Array to; + to.push_back(p_source_to); + to.push_back(p_coords_to); + + coords_level_proxies[from] = to; + + emit_changed(); +} + +Array TileSet::get_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from) { + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + + ERR_FAIL_COND_V(!coords_level_proxies.has(from), Array()); + + return coords_level_proxies[from]; +} + +bool TileSet::has_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from) { + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + + return coords_level_proxies.has(from); +} + +void TileSet::remove_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from) { + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + + ERR_FAIL_COND(!coords_level_proxies.has(from)); + + coords_level_proxies.erase(from); + + emit_changed(); +} + +void TileSet::set_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from, int p_source_to, Vector2i p_coords_to, int p_alternative_to) { + ERR_FAIL_COND(p_source_from == TileSet::INVALID_SOURCE || p_source_to == TileSet::INVALID_SOURCE); + ERR_FAIL_COND(p_coords_from == TileSetSource::INVALID_ATLAS_COORDS || p_coords_to == TileSetSource::INVALID_ATLAS_COORDS); + + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + from.push_back(p_alternative_from); + + Array to; + to.push_back(p_source_to); + to.push_back(p_coords_to); + to.push_back(p_alternative_to); + + alternative_level_proxies[from] = to; + + emit_changed(); +} + +Array TileSet::get_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from) { + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + from.push_back(p_alternative_from); + + ERR_FAIL_COND_V(!alternative_level_proxies.has(from), Array()); + + return alternative_level_proxies[from]; +} + +bool TileSet::has_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from) { + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + from.push_back(p_alternative_from); + + return alternative_level_proxies.has(from); +} + +void TileSet::remove_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from) { + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + from.push_back(p_alternative_from); + + ERR_FAIL_COND(!alternative_level_proxies.has(from)); + + alternative_level_proxies.erase(from); + + emit_changed(); +} + +Array TileSet::get_source_level_tile_proxies() const { + Array output; + for (const KeyValue &E : source_level_proxies) { + Array proxy; + proxy.push_back(E.key); + proxy.push_back(E.value); + output.push_back(proxy); + } + return output; +} + +Array TileSet::get_coords_level_tile_proxies() const { + Array output; + for (const KeyValue &E : coords_level_proxies) { + Array proxy; + proxy.append_array(E.key); + proxy.append_array(E.value); + output.push_back(proxy); + } + return output; +} + +Array TileSet::get_alternative_level_tile_proxies() const { + Array output; + for (const KeyValue &E : alternative_level_proxies) { + Array proxy; + proxy.append_array(E.key); + proxy.append_array(E.value); + output.push_back(proxy); + } + return output; +} + +Array TileSet::map_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from) const { + Array from; + from.push_back(p_source_from); + from.push_back(p_coords_from); + from.push_back(p_alternative_from); + + // Check if the tile is valid, and if so, don't map the tile and return the input. + if (has_source(p_source_from)) { + Ref source = get_source(p_source_from); + if (source->has_tile(p_coords_from) && source->has_alternative_tile(p_coords_from, p_alternative_from)) { + return from; + } + } + + // Source, coords and alternative match. + if (alternative_level_proxies.has(from)) { + return alternative_level_proxies[from].duplicate(); + } + + // Source and coords match. + from.pop_back(); + if (coords_level_proxies.has(from)) { + Array output = coords_level_proxies[from].duplicate(); + output.push_back(p_alternative_from); + return output; + } + + // Source matches. + if (source_level_proxies.has(p_source_from)) { + Array output; + output.push_back(source_level_proxies[p_source_from]); + output.push_back(p_coords_from); + output.push_back(p_alternative_from); + return output; + } + + Array output; + output.push_back(p_source_from); + output.push_back(p_coords_from); + output.push_back(p_alternative_from); + return output; +} + +void TileSet::cleanup_invalid_tile_proxies() { + // Source level. + Vector source_to_remove; + for (const KeyValue &E : source_level_proxies) { + if (has_source(E.key)) { + source_to_remove.push_back(E.key); + } + } + for (int i = 0; i < source_to_remove.size(); i++) { + remove_source_level_tile_proxy(source_to_remove[i]); + } + + // Coords level. + Vector coords_to_remove; + for (const KeyValue &E : coords_level_proxies) { + Array a = E.key; + if (has_source(a[0]) && get_source(a[0])->has_tile(a[1])) { + coords_to_remove.push_back(a); + } + } + for (int i = 0; i < coords_to_remove.size(); i++) { + Array a = coords_to_remove[i]; + remove_coords_level_tile_proxy(a[0], a[1]); + } + + // Alternative level. + Vector alternative_to_remove; + for (const KeyValue &E : alternative_level_proxies) { + Array a = E.key; + if (has_source(a[0]) && get_source(a[0])->has_tile(a[1]) && get_source(a[0])->has_alternative_tile(a[1], a[2])) { + alternative_to_remove.push_back(a); + } + } + for (int i = 0; i < alternative_to_remove.size(); i++) { + Array a = alternative_to_remove[i]; + remove_alternative_level_tile_proxy(a[0], a[1], a[2]); + } +} + +void TileSet::clear_tile_proxies() { + source_level_proxies.clear(); + coords_level_proxies.clear(); + alternative_level_proxies.clear(); + + emit_changed(); +} + +int TileSet::add_pattern(Ref p_pattern, int p_index) { + ERR_FAIL_COND_V(!p_pattern.is_valid(), -1); + ERR_FAIL_COND_V_MSG(p_pattern->is_empty(), -1, "Cannot add an empty pattern to the TileSet."); + for (const Ref &pattern : patterns) { + ERR_FAIL_COND_V_MSG(pattern == p_pattern, -1, "TileSet has already this pattern."); + } + ERR_FAIL_COND_V(p_index > (int)patterns.size(), -1); + if (p_index < 0) { + p_index = patterns.size(); + } + patterns.insert(p_index, p_pattern); + emit_changed(); + return p_index; +} + +Ref TileSet::get_pattern(int p_index) { + ERR_FAIL_INDEX_V(p_index, (int)patterns.size(), Ref()); + return patterns[p_index]; +} + +void TileSet::remove_pattern(int p_index) { + ERR_FAIL_INDEX(p_index, (int)patterns.size()); + patterns.remove_at(p_index); + emit_changed(); +} + +int TileSet::get_patterns_count() { + return patterns.size(); +} + +RBSet TileSet::get_terrains_pattern_set(int p_terrain_set) { + ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), RBSet()); + _update_terrains_cache(); + + RBSet output; + for (KeyValue> kv : per_terrain_pattern_tiles[p_terrain_set]) { + output.insert(kv.key); + } + return output; +} + +RBSet TileSet::get_tiles_for_terrains_pattern(int p_terrain_set, TerrainsPattern p_terrain_tile_pattern) { + ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), RBSet()); + _update_terrains_cache(); + return per_terrain_pattern_tiles[p_terrain_set][p_terrain_tile_pattern]; +} + +TileMapCell TileSet::get_random_tile_from_terrains_pattern(int p_terrain_set, TileSet::TerrainsPattern p_terrain_tile_pattern) { + ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), TileMapCell()); + _update_terrains_cache(); + + // Count the sum of probabilities. + double sum = 0.0; + RBSet set = per_terrain_pattern_tiles[p_terrain_set][p_terrain_tile_pattern]; + for (const TileMapCell &E : set) { + if (E.source_id >= 0) { + Ref source = sources[E.source_id]; + Ref atlas_source = source; + if (atlas_source.is_valid()) { + TileData *tile_data = atlas_source->get_tile_data(E.get_atlas_coords(), E.alternative_tile); + sum += tile_data->get_probability(); + } else { + sum += 1.0; + } + } else { + sum += 1.0; + } + } + + // Generate a random number. + double count = 0.0; + double picked = Math::random(0.0, sum); + + // Pick the tile. + for (const TileMapCell &E : set) { + if (E.source_id >= 0) { + Ref source = sources[E.source_id]; + + Ref atlas_source = source; + if (atlas_source.is_valid()) { + TileData *tile_data = atlas_source->get_tile_data(E.get_atlas_coords(), E.alternative_tile); + count += tile_data->get_probability(); + } else { + count += 1.0; + } + } else { + count += 1.0; + } + + if (count >= picked) { + return E; + } + } + + ERR_FAIL_V(TileMapCell()); +} + +Vector TileSet::get_tile_shape_polygon() const { + Vector points; + if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { + points.push_back(Vector2(-0.5, -0.5)); + points.push_back(Vector2(0.5, -0.5)); + points.push_back(Vector2(0.5, 0.5)); + points.push_back(Vector2(-0.5, 0.5)); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + points.push_back(Vector2(0.0, -0.5)); + points.push_back(Vector2(-0.5, 0.0)); + points.push_back(Vector2(0.0, 0.5)); + points.push_back(Vector2(0.5, 0.0)); + } else { + float overlap = 0.0; + switch (tile_shape) { + case TileSet::TILE_SHAPE_HEXAGON: + overlap = 0.25; + break; + case TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE: + overlap = 0.0; + break; + default: + break; + } + + points.push_back(Vector2(0.0, -0.5)); + points.push_back(Vector2(-0.5, overlap - 0.5)); + points.push_back(Vector2(-0.5, 0.5 - overlap)); + points.push_back(Vector2(0.0, 0.5)); + points.push_back(Vector2(0.5, 0.5 - overlap)); + points.push_back(Vector2(0.5, overlap - 0.5)); + + if (get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL) { + for (int i = 0; i < points.size(); i++) { + points.write[i] = Vector2(points[i].y, points[i].x); + } + } + } + return points; +} + +void TileSet::draw_tile_shape(CanvasItem *p_canvas_item, Transform2D p_transform, Color p_color, bool p_filled, Ref p_texture) const { + if (tile_meshes_dirty) { + Vector shape = get_tile_shape_polygon(); + Vector uvs; + uvs.resize(shape.size()); + for (int i = 0; i < shape.size(); i++) { + uvs.write[i] = shape[i] + Vector2(0.5, 0.5); + } + + Vector colors; + colors.resize(shape.size()); + colors.fill(Color(1.0, 1.0, 1.0, 1.0)); + + // Filled mesh. + tile_filled_mesh->clear_surfaces(); + Array a; + a.resize(Mesh::ARRAY_MAX); + a[Mesh::ARRAY_VERTEX] = shape; + a[Mesh::ARRAY_TEX_UV] = uvs; + a[Mesh::ARRAY_COLOR] = colors; + a[Mesh::ARRAY_INDEX] = Geometry2D::triangulate_polygon(shape); + tile_filled_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a, Array(), Dictionary(), Mesh::ARRAY_FLAG_USE_2D_VERTICES); + + // Lines mesh. + tile_lines_mesh->clear_surfaces(); + a.clear(); + a.resize(Mesh::ARRAY_MAX); + // Add the first point again when drawing lines. + shape.push_back(shape[0]); + colors.push_back(colors[0]); + a[Mesh::ARRAY_VERTEX] = shape; + a[Mesh::ARRAY_COLOR] = colors; + tile_lines_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINE_STRIP, a, Array(), Dictionary(), Mesh::ARRAY_FLAG_USE_2D_VERTICES); + + tile_meshes_dirty = false; + } + + if (p_filled) { + p_canvas_item->draw_mesh(tile_filled_mesh, p_texture, p_transform, p_color); + } else { + p_canvas_item->draw_mesh(tile_lines_mesh, Ref(), p_transform, p_color); + } +} + +Vector2 TileSet::map_to_local(const Vector2i &p_pos) const { + // SHOULD RETURN THE CENTER OF THE CELL. + Vector2 ret = p_pos; + + if (tile_shape == TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE || tile_shape == TileSet::TILE_SHAPE_HEXAGON || tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + // Technically, those 3 shapes are equivalent, as they are basically half-offset, but with different levels or overlap. + // square = no overlap, hexagon = 0.25 overlap, isometric = 0.5 overlap. + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + switch (tile_layout) { + case TileSet::TILE_LAYOUT_STACKED: + ret = Vector2(ret.x + (Math::posmod(ret.y, 2) == 0 ? 0.0 : 0.5), ret.y); + break; + case TileSet::TILE_LAYOUT_STACKED_OFFSET: + ret = Vector2(ret.x + (Math::posmod(ret.y, 2) == 1 ? 0.0 : 0.5), ret.y); + break; + case TileSet::TILE_LAYOUT_STAIRS_RIGHT: + ret = Vector2(ret.x + ret.y / 2, ret.y); + break; + case TileSet::TILE_LAYOUT_STAIRS_DOWN: + ret = Vector2(ret.x / 2, ret.y * 2 + ret.x); + break; + case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: + ret = Vector2((ret.x + ret.y) / 2, ret.y - ret.x); + break; + case TileSet::TILE_LAYOUT_DIAMOND_DOWN: + ret = Vector2((ret.x - ret.y) / 2, ret.y + ret.x); + break; + } + } else { // TILE_OFFSET_AXIS_VERTICAL. + switch (tile_layout) { + case TileSet::TILE_LAYOUT_STACKED: + ret = Vector2(ret.x, ret.y + (Math::posmod(ret.x, 2) == 0 ? 0.0 : 0.5)); + break; + case TileSet::TILE_LAYOUT_STACKED_OFFSET: + ret = Vector2(ret.x, ret.y + (Math::posmod(ret.x, 2) == 1 ? 0.0 : 0.5)); + break; + case TileSet::TILE_LAYOUT_STAIRS_RIGHT: + ret = Vector2(ret.x * 2 + ret.y, ret.y / 2); + break; + case TileSet::TILE_LAYOUT_STAIRS_DOWN: + ret = Vector2(ret.x, ret.y + ret.x / 2); + break; + case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: + ret = Vector2(ret.x + ret.y, (ret.y - ret.x) / 2); + break; + case TileSet::TILE_LAYOUT_DIAMOND_DOWN: + ret = Vector2(ret.x - ret.y, (ret.y + ret.x) / 2); + break; + } + } + } + + // Multiply by the overlapping ratio. + double overlapping_ratio = 1.0; + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + overlapping_ratio = 0.5; + } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) { + overlapping_ratio = 0.75; + } + ret.y *= overlapping_ratio; + } else { // TILE_OFFSET_AXIS_VERTICAL. + if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + overlapping_ratio = 0.5; + } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) { + overlapping_ratio = 0.75; + } + ret.x *= overlapping_ratio; + } + + return (ret + Vector2(0.5, 0.5)) * tile_size; +} + +Vector2i TileSet::local_to_map(const Vector2 &p_local_position) const { + Vector2 ret = p_local_position; + ret /= tile_size; + + // Divide by the overlapping ratio. + double overlapping_ratio = 1.0; + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + overlapping_ratio = 0.5; + } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) { + overlapping_ratio = 0.75; + } + ret.y /= overlapping_ratio; + } else { // TILE_OFFSET_AXIS_VERTICAL. + if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + overlapping_ratio = 0.5; + } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) { + overlapping_ratio = 0.75; + } + ret.x /= overlapping_ratio; + } + + // For each half-offset shape, we check if we are in the corner of the tile, and thus should correct the local position accordingly. + if (tile_shape == TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE || tile_shape == TileSet::TILE_SHAPE_HEXAGON || tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + // Technically, those 3 shapes are equivalent, as they are basically half-offset, but with different levels or overlap. + // square = no overlap, hexagon = 0.25 overlap, isometric = 0.5 overlap. + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + // Smart floor of the position + Vector2 raw_pos = ret; + if (Math::posmod(Math::floor(ret.y), 2) ^ (tile_layout == TileSet::TILE_LAYOUT_STACKED_OFFSET)) { + ret = Vector2(Math::floor(ret.x + 0.5) - 0.5, Math::floor(ret.y)); + } else { + ret = ret.floor(); + } + + // Compute the tile offset, and if we might the output for a neighbor top tile. + Vector2 in_tile_pos = raw_pos - ret; + bool in_top_left_triangle = (in_tile_pos - Vector2(0.5, 0.0)).cross(Vector2(-0.5, 1.0 / overlapping_ratio - 1)) <= 0; + bool in_top_right_triangle = (in_tile_pos - Vector2(0.5, 0.0)).cross(Vector2(0.5, 1.0 / overlapping_ratio - 1)) > 0; + + switch (tile_layout) { + case TileSet::TILE_LAYOUT_STACKED: + ret = ret.floor(); + if (in_top_left_triangle) { + ret += Vector2i(Math::posmod(Math::floor(ret.y), 2) ? 0 : -1, -1); + } else if (in_top_right_triangle) { + ret += Vector2i(Math::posmod(Math::floor(ret.y), 2) ? 1 : 0, -1); + } + break; + case TileSet::TILE_LAYOUT_STACKED_OFFSET: + ret = ret.floor(); + if (in_top_left_triangle) { + ret += Vector2i(Math::posmod(Math::floor(ret.y), 2) ? -1 : 0, -1); + } else if (in_top_right_triangle) { + ret += Vector2i(Math::posmod(Math::floor(ret.y), 2) ? 0 : 1, -1); + } + break; + case TileSet::TILE_LAYOUT_STAIRS_RIGHT: + ret = Vector2(ret.x - ret.y / 2, ret.y).floor(); + if (in_top_left_triangle) { + ret += Vector2i(0, -1); + } else if (in_top_right_triangle) { + ret += Vector2i(1, -1); + } + break; + case TileSet::TILE_LAYOUT_STAIRS_DOWN: + ret = Vector2(ret.x * 2, ret.y / 2 - ret.x).floor(); + if (in_top_left_triangle) { + ret += Vector2i(-1, 0); + } else if (in_top_right_triangle) { + ret += Vector2i(1, -1); + } + break; + case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: + ret = Vector2(ret.x - ret.y / 2, ret.y / 2 + ret.x).floor(); + if (in_top_left_triangle) { + ret += Vector2i(0, -1); + } else if (in_top_right_triangle) { + ret += Vector2i(1, 0); + } + break; + case TileSet::TILE_LAYOUT_DIAMOND_DOWN: + ret = Vector2(ret.x + ret.y / 2, ret.y / 2 - ret.x).floor(); + if (in_top_left_triangle) { + ret += Vector2i(-1, 0); + } else if (in_top_right_triangle) { + ret += Vector2i(0, -1); + } + break; + } + } else { // TILE_OFFSET_AXIS_VERTICAL. + // Smart floor of the position. + Vector2 raw_pos = ret; + if (Math::posmod(Math::floor(ret.x), 2) ^ (tile_layout == TileSet::TILE_LAYOUT_STACKED_OFFSET)) { + ret = Vector2(Math::floor(ret.x), Math::floor(ret.y + 0.5) - 0.5); + } else { + ret = ret.floor(); + } + + // Compute the tile offset, and if we might the output for a neighbor top tile. + Vector2 in_tile_pos = raw_pos - ret; + bool in_top_left_triangle = (in_tile_pos - Vector2(0.0, 0.5)).cross(Vector2(1.0 / overlapping_ratio - 1, -0.5)) > 0; + bool in_bottom_left_triangle = (in_tile_pos - Vector2(0.0, 0.5)).cross(Vector2(1.0 / overlapping_ratio - 1, 0.5)) <= 0; + + switch (tile_layout) { + case TileSet::TILE_LAYOUT_STACKED: + ret = ret.floor(); + if (in_top_left_triangle) { + ret += Vector2i(-1, Math::posmod(Math::floor(ret.x), 2) ? 0 : -1); + } else if (in_bottom_left_triangle) { + ret += Vector2i(-1, Math::posmod(Math::floor(ret.x), 2) ? 1 : 0); + } + break; + case TileSet::TILE_LAYOUT_STACKED_OFFSET: + ret = ret.floor(); + if (in_top_left_triangle) { + ret += Vector2i(-1, Math::posmod(Math::floor(ret.x), 2) ? -1 : 0); + } else if (in_bottom_left_triangle) { + ret += Vector2i(-1, Math::posmod(Math::floor(ret.x), 2) ? 0 : 1); + } + break; + case TileSet::TILE_LAYOUT_STAIRS_RIGHT: + ret = Vector2(ret.x / 2 - ret.y, ret.y * 2).floor(); + if (in_top_left_triangle) { + ret += Vector2i(0, -1); + } else if (in_bottom_left_triangle) { + ret += Vector2i(-1, 1); + } + break; + case TileSet::TILE_LAYOUT_STAIRS_DOWN: + ret = Vector2(ret.x, ret.y - ret.x / 2).floor(); + if (in_top_left_triangle) { + ret += Vector2i(-1, 0); + } else if (in_bottom_left_triangle) { + ret += Vector2i(-1, 1); + } + break; + case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: + ret = Vector2(ret.x / 2 - ret.y, ret.y + ret.x / 2).floor(); + if (in_top_left_triangle) { + ret += Vector2i(0, -1); + } else if (in_bottom_left_triangle) { + ret += Vector2i(-1, 0); + } + break; + case TileSet::TILE_LAYOUT_DIAMOND_DOWN: + ret = Vector2(ret.x / 2 + ret.y, ret.y - ret.x / 2).floor(); + if (in_top_left_triangle) { + ret += Vector2i(-1, 0); + } else if (in_bottom_left_triangle) { + ret += Vector2i(0, 1); + } + break; + } + } + } else { + ret = (ret + Vector2(0.00005, 0.00005)).floor(); + } + return Vector2i(ret); +} + +bool TileSet::is_existing_neighbor(TileSet::CellNeighbor p_cell_neighbor) const { + if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { + return p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER; + + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + return p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; + } else { + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + return p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; + } else { + return p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE || + p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; + } + } +} + +Vector2i TileSet::get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeighbor p_cell_neighbor) const { + if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { + switch (p_cell_neighbor) { + case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: + return p_coords + Vector2i(1, 0); + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + return p_coords + Vector2i(1, 1); + case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: + return p_coords + Vector2i(0, 1); + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + return p_coords + Vector2i(-1, 1); + case TileSet::CELL_NEIGHBOR_LEFT_SIDE: + return p_coords + Vector2i(-1, 0); + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + return p_coords + Vector2i(-1, -1); + case TileSet::CELL_NEIGHBOR_TOP_SIDE: + return p_coords + Vector2i(0, -1); + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + return p_coords + Vector2i(1, -1); + default: + ERR_FAIL_V(p_coords); + } + } else { // Half-offset shapes (square and hexagon). + switch (tile_layout) { + case TileSet::TILE_LAYOUT_STACKED: { + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + bool is_offset = p_coords.y % 2; + if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { + return p_coords + Vector2i(1, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(is_offset ? 1 : 0, 1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { + return p_coords + Vector2i(0, 2); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(is_offset ? 0 : -1, 1); + } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { + return p_coords + Vector2i(-1, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(is_offset ? 0 : -1, -1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { + return p_coords + Vector2i(0, -2); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(is_offset ? 1 : 0, -1); + } else { + ERR_FAIL_V(p_coords); + } + } else { + bool is_offset = p_coords.x % 2; + + if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { + return p_coords + Vector2i(0, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(1, is_offset ? 1 : 0); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { + return p_coords + Vector2i(2, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(1, is_offset ? 0 : -1); + } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { + return p_coords + Vector2i(0, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(-1, is_offset ? 0 : -1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { + return p_coords + Vector2i(-2, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(-1, is_offset ? 1 : 0); + } else { + ERR_FAIL_V(p_coords); + } + } + } break; + case TileSet::TILE_LAYOUT_STACKED_OFFSET: { + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + bool is_offset = p_coords.y % 2; + + if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { + return p_coords + Vector2i(1, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(is_offset ? 0 : 1, 1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { + return p_coords + Vector2i(0, 2); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(is_offset ? -1 : 0, 1); + } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { + return p_coords + Vector2i(-1, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(is_offset ? -1 : 0, -1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { + return p_coords + Vector2i(0, -2); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(is_offset ? 0 : 1, -1); + } else { + ERR_FAIL_V(p_coords); + } + } else { + bool is_offset = p_coords.x % 2; + + if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { + return p_coords + Vector2i(0, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(1, is_offset ? 0 : 1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { + return p_coords + Vector2i(2, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(1, is_offset ? -1 : 0); + } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { + return p_coords + Vector2i(0, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(-1, is_offset ? -1 : 0); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { + return p_coords + Vector2i(-2, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(-1, is_offset ? 0 : 1); + } else { + ERR_FAIL_V(p_coords); + } + } + } break; + case TileSet::TILE_LAYOUT_STAIRS_RIGHT: + case TileSet::TILE_LAYOUT_STAIRS_DOWN: { + if ((tile_layout == TileSet::TILE_LAYOUT_STAIRS_RIGHT) ^ (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { + return p_coords + Vector2i(1, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(0, 1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { + return p_coords + Vector2i(-1, 2); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(-1, 1); + } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { + return p_coords + Vector2i(-1, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(0, -1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { + return p_coords + Vector2i(1, -2); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(1, -1); + } else { + ERR_FAIL_V(p_coords); + } + + } else { + if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { + return p_coords + Vector2i(0, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(1, 0); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { + return p_coords + Vector2i(2, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(1, -1); + } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { + return p_coords + Vector2i(0, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(-1, 0); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { + return p_coords + Vector2i(-2, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(-1, 1); + } else { + ERR_FAIL_V(p_coords); + } + } + } else { + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { + return p_coords + Vector2i(2, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(1, 0); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { + return p_coords + Vector2i(0, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(-1, 1); + } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { + return p_coords + Vector2i(-2, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(-1, 0); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { + return p_coords + Vector2i(0, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(1, -1); + } else { + ERR_FAIL_V(p_coords); + } + + } else { + if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { + return p_coords + Vector2i(-1, 2); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(0, 1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { + return p_coords + Vector2i(1, 0); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(1, -1); + } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { + return p_coords + Vector2i(1, -2); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(0, -1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { + return p_coords + Vector2i(-1, 0); + + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(-1, 1); + } else { + ERR_FAIL_V(p_coords); + } + } + } + } break; + case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: + case TileSet::TILE_LAYOUT_DIAMOND_DOWN: { + if ((tile_layout == TileSet::TILE_LAYOUT_DIAMOND_RIGHT) ^ (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { + return p_coords + Vector2i(1, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(0, 1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { + return p_coords + Vector2i(-1, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(-1, 0); + } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { + return p_coords + Vector2i(-1, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(0, -1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { + return p_coords + Vector2i(1, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(1, 0); + } else { + ERR_FAIL_V(p_coords); + } + + } else { + if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { + return p_coords + Vector2i(1, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(1, 0); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { + return p_coords + Vector2i(1, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(0, -1); + } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { + return p_coords + Vector2i(-1, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(-1, 0); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { + return p_coords + Vector2i(-1, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(0, 1); + } else { + ERR_FAIL_V(p_coords); + } + } + } else { + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { + return p_coords + Vector2i(1, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(1, 0); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { + return p_coords + Vector2i(1, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(0, 1); + } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { + return p_coords + Vector2i(-1, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(-1, 0); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { + return p_coords + Vector2i(-1, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(0, -1); + } else { + ERR_FAIL_V(p_coords); + } + + } else { + if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { + return p_coords + Vector2i(-1, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { + return p_coords + Vector2i(0, 1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { + return p_coords + Vector2i(1, 1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { + return p_coords + Vector2i(1, 0); + } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || + (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { + return p_coords + Vector2i(1, -1); + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { + return p_coords + Vector2i(0, -1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { + return p_coords + Vector2i(-1, -1); + + } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { + return p_coords + Vector2i(-1, 0); + } else { + ERR_FAIL_V(p_coords); + } + } + } + } break; + default: + ERR_FAIL_V(p_coords); + } + } +} + +TypedArray TileSet::get_surrounding_cells(const Vector2i &p_coords) const { + TypedArray around; + if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { + around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)); + around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)); + around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_LEFT_SIDE)); + around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_TOP_SIDE)); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)); + around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)); + around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE)); + around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)); + } else { + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)); + around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)); + around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)); + around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_LEFT_SIDE)); + around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE)); + around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)); + } else { + around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)); + around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)); + around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)); + around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE)); + around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_TOP_SIDE)); + around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)); + } + } + + return around; +} + +Vector2i TileSet::map_pattern(const Vector2i &p_position_in_tilemap, const Vector2i &p_coords_in_pattern, Ref p_pattern) const { + ERR_FAIL_COND_V(p_pattern.is_null(), Vector2i()); + ERR_FAIL_COND_V(!p_pattern->has_cell(p_coords_in_pattern), Vector2i()); + + Vector2i output = p_position_in_tilemap + p_coords_in_pattern; + if (tile_shape != TileSet::TILE_SHAPE_SQUARE) { + if (tile_layout == TileSet::TILE_LAYOUT_STACKED) { + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL && bool(p_position_in_tilemap.y % 2) && bool(p_coords_in_pattern.y % 2)) { + output.x += 1; + } else if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(p_position_in_tilemap.x % 2) && bool(p_coords_in_pattern.x % 2)) { + output.y += 1; + } + } else if (tile_layout == TileSet::TILE_LAYOUT_STACKED_OFFSET) { + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL && bool(p_position_in_tilemap.y % 2) && bool(p_coords_in_pattern.y % 2)) { + output.x -= 1; + } else if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(p_position_in_tilemap.x % 2) && bool(p_coords_in_pattern.x % 2)) { + output.y -= 1; + } + } + } + + return output; +} + +void TileSet::draw_cells_outline(CanvasItem *p_canvas_item, const RBSet &p_cells, Color p_color, Transform2D p_transform) const { + Vector polygon = get_tile_shape_polygon(); + for (const Vector2i &E : p_cells) { + Vector2 center = map_to_local(E); + +#define DRAW_SIDE_IF_NEEDED(side, polygon_index_from, polygon_index_to) \ + if (!p_cells.has(get_neighbor_cell(E, side))) { \ + Vector2 from = p_transform.xform(center + polygon[polygon_index_from] * tile_size); \ + Vector2 to = p_transform.xform(center + polygon[polygon_index_to] * tile_size); \ + p_canvas_item->draw_line(from, to, p_color); \ + } + + if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_RIGHT_SIDE, 1, 2); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE, 2, 3); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_LEFT_SIDE, 3, 0); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_SIDE, 0, 1); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, 2, 3); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, 1, 2); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE, 0, 1); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE, 3, 0); + } else { + if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, 3, 4); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, 2, 3); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_LEFT_SIDE, 1, 2); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE, 0, 1); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE, 5, 0); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_RIGHT_SIDE, 4, 5); + } else { + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, 3, 4); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE, 4, 5); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, 5, 0); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE, 0, 1); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_SIDE, 1, 2); + DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE, 2, 3); + } + } + } +#undef DRAW_SIDE_IF_NEEDED +} + +Vector TileSet::get_terrain_polygon(int p_terrain_set) { + if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { + return _get_square_terrain_polygon(tile_size); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + return _get_isometric_terrain_polygon(tile_size); + } else { + float overlap = 0.0; + switch (tile_shape) { + case TileSet::TILE_SHAPE_HEXAGON: + overlap = 0.25; + break; + case TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE: + overlap = 0.0; + break; + default: + break; + } + return _get_half_offset_terrain_polygon(tile_size, overlap, tile_offset_axis); + } +} + +Vector TileSet::get_terrain_peering_bit_polygon(int p_terrain_set, TileSet::CellNeighbor p_bit) { + ERR_FAIL_COND_V(p_terrain_set < 0 || p_terrain_set >= get_terrain_sets_count(), Vector()); + + TileSet::TerrainMode terrain_mode = get_terrain_set_mode(p_terrain_set); + + if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { + if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { + return _get_square_corner_or_side_terrain_peering_bit_polygon(tile_size, p_bit); + } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { + return _get_square_corner_terrain_peering_bit_polygon(tile_size, p_bit); + } else { // TileData::TERRAIN_MODE_MATCH_SIDES + return _get_square_side_terrain_peering_bit_polygon(tile_size, p_bit); + } + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { + return _get_isometric_corner_or_side_terrain_peering_bit_polygon(tile_size, p_bit); + } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { + return _get_isometric_corner_terrain_peering_bit_polygon(tile_size, p_bit); + } else { // TileData::TERRAIN_MODE_MATCH_SIDES + return _get_isometric_side_terrain_peering_bit_polygon(tile_size, p_bit); + } + } else { + float overlap = 0.0; + switch (tile_shape) { + case TileSet::TILE_SHAPE_HEXAGON: + overlap = 0.25; + break; + case TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE: + overlap = 0.0; + break; + default: + break; + } + if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { + return _get_half_offset_corner_or_side_terrain_peering_bit_polygon(tile_size, overlap, tile_offset_axis, p_bit); + } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { + return _get_half_offset_corner_terrain_peering_bit_polygon(tile_size, overlap, tile_offset_axis, p_bit); + } else { // TileData::TERRAIN_MODE_MATCH_SIDES + return _get_half_offset_side_terrain_peering_bit_polygon(tile_size, overlap, tile_offset_axis, p_bit); + } + } +} + +#define TERRAIN_ALPHA 0.6 + +void TileSet::draw_terrains(CanvasItem *p_canvas_item, Transform2D p_transform, const TileData *p_tile_data) { + ERR_FAIL_NULL(p_tile_data); + + if (terrain_bits_meshes_dirty) { + // Recompute the meshes. + terrain_peering_bits_meshes.clear(); + + for (int terrain_mode_index = 0; terrain_mode_index < 3; terrain_mode_index++) { + TerrainMode terrain_mode = TerrainMode(terrain_mode_index); + + // Center terrain + Vector polygon; + if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { + polygon = _get_square_terrain_polygon(tile_size); + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + polygon = _get_isometric_terrain_polygon(tile_size); + } else { + float overlap = 0.0; + switch (tile_shape) { + case TileSet::TILE_SHAPE_HEXAGON: + overlap = 0.25; + break; + case TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE: + overlap = 0.0; + break; + default: + break; + } + polygon = _get_half_offset_terrain_polygon(tile_size, overlap, tile_offset_axis); + } + { + Ref mesh; + mesh.instantiate(); + Vector uvs; + uvs.resize(polygon.size()); + Vector colors; + colors.resize(polygon.size()); + colors.fill(Color(1.0, 1.0, 1.0, 1.0)); + Array a; + a.resize(Mesh::ARRAY_MAX); + a[Mesh::ARRAY_VERTEX] = polygon; + a[Mesh::ARRAY_TEX_UV] = uvs; + a[Mesh::ARRAY_COLOR] = colors; + a[Mesh::ARRAY_INDEX] = Geometry2D::triangulate_polygon(polygon); + mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a, Array(), Dictionary(), Mesh::ARRAY_FLAG_USE_2D_VERTICES); + terrain_meshes[terrain_mode] = mesh; + } + // Peering bits + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + CellNeighbor bit = CellNeighbor(i); + + if (is_valid_terrain_peering_bit_for_mode(terrain_mode, bit)) { + if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { + if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { + polygon = _get_square_corner_or_side_terrain_peering_bit_polygon(tile_size, bit); + } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { + polygon = _get_square_corner_terrain_peering_bit_polygon(tile_size, bit); + } else { // TileData::TERRAIN_MODE_MATCH_SIDES + polygon = _get_square_side_terrain_peering_bit_polygon(tile_size, bit); + } + } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { + if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { + polygon = _get_isometric_corner_or_side_terrain_peering_bit_polygon(tile_size, bit); + } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { + polygon = _get_isometric_corner_terrain_peering_bit_polygon(tile_size, bit); + } else { // TileData::TERRAIN_MODE_MATCH_SIDES + polygon = _get_isometric_side_terrain_peering_bit_polygon(tile_size, bit); + } + } else { + float overlap = 0.0; + switch (tile_shape) { + case TileSet::TILE_SHAPE_HEXAGON: + overlap = 0.25; + break; + case TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE: + overlap = 0.0; + break; + default: + break; + } + if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { + polygon = _get_half_offset_corner_or_side_terrain_peering_bit_polygon(tile_size, overlap, tile_offset_axis, bit); + } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { + polygon = _get_half_offset_corner_terrain_peering_bit_polygon(tile_size, overlap, tile_offset_axis, bit); + } else { // TileData::TERRAIN_MODE_MATCH_SIDES + polygon = _get_half_offset_side_terrain_peering_bit_polygon(tile_size, overlap, tile_offset_axis, bit); + } + } + { + Ref mesh; + mesh.instantiate(); + Vector uvs; + uvs.resize(polygon.size()); + Vector colors; + colors.resize(polygon.size()); + colors.fill(Color(1.0, 1.0, 1.0, 1.0)); + Array a; + a.resize(Mesh::ARRAY_MAX); + a[Mesh::ARRAY_VERTEX] = polygon; + a[Mesh::ARRAY_TEX_UV] = uvs; + a[Mesh::ARRAY_COLOR] = colors; + a[Mesh::ARRAY_INDEX] = Geometry2D::triangulate_polygon(polygon); + mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a, Array(), Dictionary(), Mesh::ARRAY_FLAG_USE_2D_VERTICES); + terrain_peering_bits_meshes[terrain_mode][bit] = mesh; + } + } + } + } + terrain_bits_meshes_dirty = false; + } + + int terrain_set = p_tile_data->get_terrain_set(); + if (terrain_set < 0) { + return; + } + TileSet::TerrainMode terrain_mode = get_terrain_set_mode(terrain_set); + + RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform); + int terrain_id = p_tile_data->get_terrain(); + if (terrain_id >= 0) { + Color color = get_terrain_color(terrain_set, terrain_id); + color.a = TERRAIN_ALPHA; + p_canvas_item->draw_mesh(terrain_meshes[terrain_mode], Ref(), Transform2D(), color); + } + + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + CellNeighbor bit = CellNeighbor(i); + if (is_valid_terrain_peering_bit(terrain_set, bit)) { + terrain_id = p_tile_data->get_terrain_peering_bit(bit); + if (terrain_id >= 0) { + Color color = get_terrain_color(terrain_set, terrain_id); + color.a = TERRAIN_ALPHA; + p_canvas_item->draw_mesh(terrain_peering_bits_meshes[terrain_mode][bit], Ref(), Transform2D(), color); + } + } + } + RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D()); +} + +Vector>> TileSet::generate_terrains_icons(Size2i p_size) { + // Counts the number of matching terrain tiles and find the best matching icon. + struct Count { + int count = 0; + float probability = 0.0; + Ref texture; + Rect2i region; + }; + Vector>> output; + LocalVector> counts; + output.resize(get_terrain_sets_count()); + counts.resize(get_terrain_sets_count()); + for (int terrain_set = 0; terrain_set < get_terrain_sets_count(); terrain_set++) { + output.write[terrain_set].resize(get_terrains_count(terrain_set)); + counts[terrain_set].resize(get_terrains_count(terrain_set)); + } + + for (int source_index = 0; source_index < get_source_count(); source_index++) { + int source_id = get_source_id(source_index); + Ref source = get_source(source_id); + + Ref atlas_source = source; + if (atlas_source.is_valid()) { + for (int tile_index = 0; tile_index < source->get_tiles_count(); tile_index++) { + Vector2i tile_id = source->get_tile_id(tile_index); + for (int alternative_index = 0; alternative_index < source->get_alternative_tiles_count(tile_id); alternative_index++) { + int alternative_id = source->get_alternative_tile_id(tile_id, alternative_index); + + TileData *tile_data = atlas_source->get_tile_data(tile_id, alternative_id); + int terrain_set = tile_data->get_terrain_set(); + if (terrain_set >= 0) { + ERR_FAIL_INDEX_V(terrain_set, get_terrain_sets_count(), Vector>>()); + + LocalVector bit_counts; + bit_counts.resize(get_terrains_count(terrain_set)); + for (int terrain = 0; terrain < get_terrains_count(terrain_set); terrain++) { + bit_counts[terrain] = 0; + } + if (tile_data->get_terrain() >= 0) { + bit_counts[tile_data->get_terrain()] += 10; + } + for (int terrain_bit = 0; terrain_bit < TileSet::CELL_NEIGHBOR_MAX; terrain_bit++) { + TileSet::CellNeighbor cell_neighbor = TileSet::CellNeighbor(terrain_bit); + if (is_valid_terrain_peering_bit(terrain_set, cell_neighbor)) { + int terrain = tile_data->get_terrain_peering_bit(cell_neighbor); + if (terrain >= 0) { + if (terrain >= (int)bit_counts.size()) { + WARN_PRINT(vformat("Invalid terrain peering bit: %d", terrain)); + } else { + bit_counts[terrain] += 1; + } + } + } + } + + for (int terrain = 0; terrain < get_terrains_count(terrain_set); terrain++) { + if ((bit_counts[terrain] > counts[terrain_set][terrain].count) || (bit_counts[terrain] == counts[terrain_set][terrain].count && tile_data->get_probability() > counts[terrain_set][terrain].probability)) { + counts[terrain_set][terrain].count = bit_counts[terrain]; + counts[terrain_set][terrain].probability = tile_data->get_probability(); + counts[terrain_set][terrain].texture = atlas_source->get_texture(); + counts[terrain_set][terrain].region = atlas_source->get_tile_texture_region(tile_id); + } + } + } + } + } + } + } + + // Generate the icons. + for (int terrain_set = 0; terrain_set < get_terrain_sets_count(); terrain_set++) { + for (int terrain = 0; terrain < get_terrains_count(terrain_set); terrain++) { + Ref dst_image; + dst_image.instantiate(); + if (counts[terrain_set][terrain].count > 0) { + // Get the best tile. + Ref src_texture = counts[terrain_set][terrain].texture; + ERR_FAIL_COND_V(src_texture.is_null(), output); + Ref src_image = src_texture->get_image(); + ERR_FAIL_COND_V(src_image.is_null(), output); + Rect2i region = counts[terrain_set][terrain].region; + + dst_image->initialize_data(region.size.x, region.size.y, false, src_image->get_format()); + dst_image->blit_rect(src_image, region, Point2i()); + dst_image->convert(Image::FORMAT_RGBA8); + dst_image->resize(p_size.x, p_size.y, Image::INTERPOLATE_NEAREST); + } else { + dst_image->initialize_data(1, 1, false, Image::FORMAT_RGBA8); + dst_image->set_pixel(0, 0, get_terrain_color(terrain_set, terrain)); + } + Ref icon = ImageTexture::create_from_image(dst_image); + icon->set_size_override(p_size); + output.write[terrain_set].write[terrain] = icon; + } + } + return output; +} + +void TileSet::_source_changed() { + terrains_cache_dirty = true; + emit_changed(); +} + +Vector TileSet::_get_square_terrain_polygon(Vector2i p_size) { + Rect2 rect(-Vector2(p_size) / 6.0, Vector2(p_size) / 3.0); + return { + rect.position, + Vector2(rect.get_end().x, rect.position.y), + rect.get_end(), + Vector2(rect.position.x, rect.get_end().y) + }; +} + +Vector TileSet::_get_square_corner_or_side_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { + Rect2 bit_rect; + bit_rect.size = Vector2(p_size) / 3; + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: + bit_rect.position = Vector2(1, -1); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + bit_rect.position = Vector2(1, 1); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: + bit_rect.position = Vector2(-1, 1); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + bit_rect.position = Vector2(-3, 1); + break; + case TileSet::CELL_NEIGHBOR_LEFT_SIDE: + bit_rect.position = Vector2(-3, -1); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + bit_rect.position = Vector2(-3, -3); + break; + case TileSet::CELL_NEIGHBOR_TOP_SIDE: + bit_rect.position = Vector2(-1, -3); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + bit_rect.position = Vector2(1, -3); + break; + default: + break; + } + bit_rect.position *= Vector2(p_size) / 6.0; + + Vector polygon = { + bit_rect.position, + Vector2(bit_rect.get_end().x, bit_rect.position.y), + bit_rect.get_end(), + Vector2(bit_rect.position.x, bit_rect.get_end().y) + }; + + return polygon; +} + +Vector TileSet::_get_square_corner_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { + Vector2 unit = Vector2(p_size) / 6.0; + Vector polygon; + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + polygon.push_back(Vector2(0, 3) * unit); + polygon.push_back(Vector2(3, 3) * unit); + polygon.push_back(Vector2(3, 0) * unit); + polygon.push_back(Vector2(1, 0) * unit); + polygon.push_back(Vector2(1, 1) * unit); + polygon.push_back(Vector2(0, 1) * unit); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + polygon.push_back(Vector2(0, 3) * unit); + polygon.push_back(Vector2(-3, 3) * unit); + polygon.push_back(Vector2(-3, 0) * unit); + polygon.push_back(Vector2(-1, 0) * unit); + polygon.push_back(Vector2(-1, 1) * unit); + polygon.push_back(Vector2(0, 1) * unit); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + polygon.push_back(Vector2(0, -3) * unit); + polygon.push_back(Vector2(-3, -3) * unit); + polygon.push_back(Vector2(-3, 0) * unit); + polygon.push_back(Vector2(-1, 0) * unit); + polygon.push_back(Vector2(-1, -1) * unit); + polygon.push_back(Vector2(0, -1) * unit); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + polygon.push_back(Vector2(0, -3) * unit); + polygon.push_back(Vector2(3, -3) * unit); + polygon.push_back(Vector2(3, 0) * unit); + polygon.push_back(Vector2(1, 0) * unit); + polygon.push_back(Vector2(1, -1) * unit); + polygon.push_back(Vector2(0, -1) * unit); + break; + default: + break; + } + return polygon; +} + +Vector TileSet::_get_square_side_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { + Vector2 unit = Vector2(p_size) / 6.0; + Vector polygon; + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: + polygon.push_back(Vector2(1, -1) * unit); + polygon.push_back(Vector2(3, -3) * unit); + polygon.push_back(Vector2(3, 3) * unit); + polygon.push_back(Vector2(1, 1) * unit); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: + polygon.push_back(Vector2(-1, 1) * unit); + polygon.push_back(Vector2(-3, 3) * unit); + polygon.push_back(Vector2(3, 3) * unit); + polygon.push_back(Vector2(1, 1) * unit); + break; + case TileSet::CELL_NEIGHBOR_LEFT_SIDE: + polygon.push_back(Vector2(-1, -1) * unit); + polygon.push_back(Vector2(-3, -3) * unit); + polygon.push_back(Vector2(-3, 3) * unit); + polygon.push_back(Vector2(-1, 1) * unit); + break; + case TileSet::CELL_NEIGHBOR_TOP_SIDE: + polygon.push_back(Vector2(-1, -1) * unit); + polygon.push_back(Vector2(-3, -3) * unit); + polygon.push_back(Vector2(3, -3) * unit); + polygon.push_back(Vector2(1, -1) * unit); + break; + default: + break; + } + return polygon; +} + +Vector TileSet::_get_isometric_terrain_polygon(Vector2i p_size) { + Vector2 unit = Vector2(p_size) / 6.0; + return { + Vector2(1, 0) * unit, + Vector2(0, 1) * unit, + Vector2(-1, 0) * unit, + Vector2(0, -1) * unit, + }; +} + +Vector TileSet::_get_isometric_corner_or_side_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { + Vector2 unit = Vector2(p_size) / 6.0; + Vector polygon; + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: + polygon.push_back(Vector2(1, 0) * unit); + polygon.push_back(Vector2(2, -1) * unit); + polygon.push_back(Vector2(3, 0) * unit); + polygon.push_back(Vector2(2, 1) * unit); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + polygon.push_back(Vector2(0, 1) * unit); + polygon.push_back(Vector2(1, 2) * unit); + polygon.push_back(Vector2(2, 1) * unit); + polygon.push_back(Vector2(1, 0) * unit); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: + polygon.push_back(Vector2(0, 1) * unit); + polygon.push_back(Vector2(-1, 2) * unit); + polygon.push_back(Vector2(0, 3) * unit); + polygon.push_back(Vector2(1, 2) * unit); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + polygon.push_back(Vector2(0, 1) * unit); + polygon.push_back(Vector2(-1, 2) * unit); + polygon.push_back(Vector2(-2, 1) * unit); + polygon.push_back(Vector2(-1, 0) * unit); + break; + case TileSet::CELL_NEIGHBOR_LEFT_CORNER: + polygon.push_back(Vector2(-1, 0) * unit); + polygon.push_back(Vector2(-2, -1) * unit); + polygon.push_back(Vector2(-3, 0) * unit); + polygon.push_back(Vector2(-2, 1) * unit); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: + polygon.push_back(Vector2(0, -1) * unit); + polygon.push_back(Vector2(-1, -2) * unit); + polygon.push_back(Vector2(-2, -1) * unit); + polygon.push_back(Vector2(-1, 0) * unit); + break; + case TileSet::CELL_NEIGHBOR_TOP_CORNER: + polygon.push_back(Vector2(0, -1) * unit); + polygon.push_back(Vector2(-1, -2) * unit); + polygon.push_back(Vector2(0, -3) * unit); + polygon.push_back(Vector2(1, -2) * unit); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: + polygon.push_back(Vector2(0, -1) * unit); + polygon.push_back(Vector2(1, -2) * unit); + polygon.push_back(Vector2(2, -1) * unit); + polygon.push_back(Vector2(1, 0) * unit); + break; + default: + break; + } + return polygon; +} + +Vector TileSet::_get_isometric_corner_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { + Vector2 unit = Vector2(p_size) / 6.0; + Vector polygon; + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: + polygon.push_back(Vector2(0.5, -0.5) * unit); + polygon.push_back(Vector2(1.5, -1.5) * unit); + polygon.push_back(Vector2(3, 0) * unit); + polygon.push_back(Vector2(1.5, 1.5) * unit); + polygon.push_back(Vector2(0.5, 0.5) * unit); + polygon.push_back(Vector2(1, 0) * unit); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: + polygon.push_back(Vector2(-0.5, 0.5) * unit); + polygon.push_back(Vector2(-1.5, 1.5) * unit); + polygon.push_back(Vector2(0, 3) * unit); + polygon.push_back(Vector2(1.5, 1.5) * unit); + polygon.push_back(Vector2(0.5, 0.5) * unit); + polygon.push_back(Vector2(0, 1) * unit); + break; + case TileSet::CELL_NEIGHBOR_LEFT_CORNER: + polygon.push_back(Vector2(-0.5, -0.5) * unit); + polygon.push_back(Vector2(-1.5, -1.5) * unit); + polygon.push_back(Vector2(-3, 0) * unit); + polygon.push_back(Vector2(-1.5, 1.5) * unit); + polygon.push_back(Vector2(-0.5, 0.5) * unit); + polygon.push_back(Vector2(-1, 0) * unit); + break; + case TileSet::CELL_NEIGHBOR_TOP_CORNER: + polygon.push_back(Vector2(-0.5, -0.5) * unit); + polygon.push_back(Vector2(-1.5, -1.5) * unit); + polygon.push_back(Vector2(0, -3) * unit); + polygon.push_back(Vector2(1.5, -1.5) * unit); + polygon.push_back(Vector2(0.5, -0.5) * unit); + polygon.push_back(Vector2(0, -1) * unit); + break; + default: + break; + } + return polygon; +} + +Vector TileSet::_get_isometric_side_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { + Vector2 unit = Vector2(p_size) / 6.0; + Vector polygon; + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + polygon.push_back(Vector2(1, 0) * unit); + polygon.push_back(Vector2(3, 0) * unit); + polygon.push_back(Vector2(0, 3) * unit); + polygon.push_back(Vector2(0, 1) * unit); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + polygon.push_back(Vector2(-1, 0) * unit); + polygon.push_back(Vector2(-3, 0) * unit); + polygon.push_back(Vector2(0, 3) * unit); + polygon.push_back(Vector2(0, 1) * unit); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: + polygon.push_back(Vector2(-1, 0) * unit); + polygon.push_back(Vector2(-3, 0) * unit); + polygon.push_back(Vector2(0, -3) * unit); + polygon.push_back(Vector2(0, -1) * unit); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: + polygon.push_back(Vector2(1, 0) * unit); + polygon.push_back(Vector2(3, 0) * unit); + polygon.push_back(Vector2(0, -3) * unit); + polygon.push_back(Vector2(0, -1) * unit); + break; + default: + break; + } + return polygon; +} + +Vector TileSet::_get_half_offset_terrain_polygon(Vector2i p_size, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) { + Vector2 unit = Vector2(p_size) / 6.0; + if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + return { + Vector2(1, 1.0 - p_overlap * 2.0) * unit, + Vector2(0, 1) * unit, + Vector2(-1, 1.0 - p_overlap * 2.0) * unit, + Vector2(-1, -1.0 + p_overlap * 2.0) * unit, + Vector2(0, -1) * unit, + Vector2(1, -1.0 + p_overlap * 2.0) * unit, + }; + } else { + return { + Vector2(1, 0) * unit, + Vector2(1.0 - p_overlap * 2.0, -1) * unit, + Vector2(-1.0 + p_overlap * 2.0, -1) * unit, + Vector2(-1, 0) * unit, + Vector2(-1.0 + p_overlap * 2.0, 1) * unit, + Vector2(1.0 - p_overlap * 2.0, 1) * unit, + }; + } +} + +Vector TileSet::_get_half_offset_corner_or_side_terrain_peering_bit_polygon(Vector2i p_size, float p_overlap, TileSet::TileOffsetAxis p_offset_axis, TileSet::CellNeighbor p_bit) { + Vector point_list = { + Vector2(3, (3.0 * (1.0 - p_overlap * 2.0)) / 2.0), + Vector2(3, 3.0 * (1.0 - p_overlap * 2.0)), + Vector2(2, 3.0 * (1.0 - (p_overlap * 2.0) * 2.0 / 3.0)), + Vector2(1, 3.0 - p_overlap * 2.0), + Vector2(0, 3), + Vector2(-1, 3.0 - p_overlap * 2.0), + Vector2(-2, 3.0 * (1.0 - (p_overlap * 2.0) * 2.0 / 3.0)), + Vector2(-3, 3.0 * (1.0 - p_overlap * 2.0)), + Vector2(-3, (3.0 * (1.0 - p_overlap * 2.0)) / 2.0), + Vector2(-3, -(3.0 * (1.0 - p_overlap * 2.0)) / 2.0), + Vector2(-3, -3.0 * (1.0 - p_overlap * 2.0)), + Vector2(-2, -3.0 * (1.0 - (p_overlap * 2.0) * 2.0 / 3.0)), + Vector2(-1, -(3.0 - p_overlap * 2.0)), + Vector2(0, -3), + Vector2(1, -(3.0 - p_overlap * 2.0)), + Vector2(2, -3.0 * (1.0 - (p_overlap * 2.0) * 2.0 / 3.0)), + Vector2(3, -3.0 * (1.0 - p_overlap * 2.0)), + Vector2(3, -(3.0 * (1.0 - p_overlap * 2.0)) / 2.0) + }; + + Vector2 unit = Vector2(p_size) / 6.0; + Vector polygon; + if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + for (int i = 0; i < point_list.size(); i++) { + point_list.write[i] = point_list[i] * unit; + } + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: + polygon.push_back(point_list[17]); + polygon.push_back(point_list[0]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + polygon.push_back(point_list[0]); + polygon.push_back(point_list[1]); + polygon.push_back(point_list[2]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + polygon.push_back(point_list[2]); + polygon.push_back(point_list[3]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: + polygon.push_back(point_list[3]); + polygon.push_back(point_list[4]); + polygon.push_back(point_list[5]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + polygon.push_back(point_list[5]); + polygon.push_back(point_list[6]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + polygon.push_back(point_list[6]); + polygon.push_back(point_list[7]); + polygon.push_back(point_list[8]); + break; + case TileSet::CELL_NEIGHBOR_LEFT_SIDE: + polygon.push_back(point_list[8]); + polygon.push_back(point_list[9]); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + polygon.push_back(point_list[9]); + polygon.push_back(point_list[10]); + polygon.push_back(point_list[11]); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: + polygon.push_back(point_list[11]); + polygon.push_back(point_list[12]); + break; + case TileSet::CELL_NEIGHBOR_TOP_CORNER: + polygon.push_back(point_list[12]); + polygon.push_back(point_list[13]); + polygon.push_back(point_list[14]); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: + polygon.push_back(point_list[14]); + polygon.push_back(point_list[15]); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + polygon.push_back(point_list[15]); + polygon.push_back(point_list[16]); + polygon.push_back(point_list[17]); + break; + default: + break; + } + } else { + for (int i = 0; i < point_list.size(); i++) { + point_list.write[i] = Vector2(point_list[i].y, point_list[i].x) * unit; + } + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: + polygon.push_back(point_list[3]); + polygon.push_back(point_list[4]); + polygon.push_back(point_list[5]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + polygon.push_back(point_list[2]); + polygon.push_back(point_list[3]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + polygon.push_back(point_list[0]); + polygon.push_back(point_list[1]); + polygon.push_back(point_list[2]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: + polygon.push_back(point_list[17]); + polygon.push_back(point_list[0]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + polygon.push_back(point_list[15]); + polygon.push_back(point_list[16]); + polygon.push_back(point_list[17]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + polygon.push_back(point_list[14]); + polygon.push_back(point_list[15]); + break; + case TileSet::CELL_NEIGHBOR_LEFT_CORNER: + polygon.push_back(point_list[12]); + polygon.push_back(point_list[13]); + polygon.push_back(point_list[14]); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: + polygon.push_back(point_list[11]); + polygon.push_back(point_list[12]); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + polygon.push_back(point_list[9]); + polygon.push_back(point_list[10]); + polygon.push_back(point_list[11]); + break; + case TileSet::CELL_NEIGHBOR_TOP_SIDE: + polygon.push_back(point_list[8]); + polygon.push_back(point_list[9]); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + polygon.push_back(point_list[6]); + polygon.push_back(point_list[7]); + polygon.push_back(point_list[8]); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: + polygon.push_back(point_list[5]); + polygon.push_back(point_list[6]); + break; + default: + break; + } + } + + int half_polygon_size = polygon.size(); + for (int i = 0; i < half_polygon_size; i++) { + polygon.push_back(polygon[half_polygon_size - 1 - i] / 3.0); + } + + return polygon; +} + +Vector TileSet::_get_half_offset_corner_terrain_peering_bit_polygon(Vector2i p_size, float p_overlap, TileSet::TileOffsetAxis p_offset_axis, TileSet::CellNeighbor p_bit) { + Vector point_list = { + Vector2(3, 0), + Vector2(3, 3.0 * (1.0 - p_overlap * 2.0)), + Vector2(1.5, (3.0 * (1.0 - p_overlap * 2.0) + 3.0) / 2.0), + Vector2(0, 3), + Vector2(-1.5, (3.0 * (1.0 - p_overlap * 2.0) + 3.0) / 2.0), + Vector2(-3, 3.0 * (1.0 - p_overlap * 2.0)), + Vector2(-3, 0), + Vector2(-3, -3.0 * (1.0 - p_overlap * 2.0)), + Vector2(-1.5, -(3.0 * (1.0 - p_overlap * 2.0) + 3.0) / 2.0), + Vector2(0, -3), + Vector2(1.5, -(3.0 * (1.0 - p_overlap * 2.0) + 3.0) / 2.0), + Vector2(3, -3.0 * (1.0 - p_overlap * 2.0)) + }; + + Vector2 unit = Vector2(p_size) / 6.0; + Vector polygon; + if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + for (int i = 0; i < point_list.size(); i++) { + point_list.write[i] = point_list[i] * unit; + } + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + polygon.push_back(point_list[0]); + polygon.push_back(point_list[1]); + polygon.push_back(point_list[2]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: + polygon.push_back(point_list[2]); + polygon.push_back(point_list[3]); + polygon.push_back(point_list[4]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + polygon.push_back(point_list[4]); + polygon.push_back(point_list[5]); + polygon.push_back(point_list[6]); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + polygon.push_back(point_list[6]); + polygon.push_back(point_list[7]); + polygon.push_back(point_list[8]); + break; + case TileSet::CELL_NEIGHBOR_TOP_CORNER: + polygon.push_back(point_list[8]); + polygon.push_back(point_list[9]); + polygon.push_back(point_list[10]); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + polygon.push_back(point_list[10]); + polygon.push_back(point_list[11]); + polygon.push_back(point_list[0]); + break; + default: + break; + } + } else { + for (int i = 0; i < point_list.size(); i++) { + point_list.write[i] = Vector2(point_list[i].y, point_list[i].x) * unit; + } + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: + polygon.push_back(point_list[2]); + polygon.push_back(point_list[3]); + polygon.push_back(point_list[4]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + polygon.push_back(point_list[0]); + polygon.push_back(point_list[1]); + polygon.push_back(point_list[2]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + polygon.push_back(point_list[10]); + polygon.push_back(point_list[11]); + polygon.push_back(point_list[0]); + break; + case TileSet::CELL_NEIGHBOR_LEFT_CORNER: + polygon.push_back(point_list[8]); + polygon.push_back(point_list[9]); + polygon.push_back(point_list[10]); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + polygon.push_back(point_list[6]); + polygon.push_back(point_list[7]); + polygon.push_back(point_list[8]); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + polygon.push_back(point_list[4]); + polygon.push_back(point_list[5]); + polygon.push_back(point_list[6]); + break; + default: + break; + } + } + + int half_polygon_size = polygon.size(); + for (int i = 0; i < half_polygon_size; i++) { + polygon.push_back(polygon[half_polygon_size - 1 - i] / 3.0); + } + + return polygon; +} + +Vector TileSet::_get_half_offset_side_terrain_peering_bit_polygon(Vector2i p_size, float p_overlap, TileSet::TileOffsetAxis p_offset_axis, TileSet::CellNeighbor p_bit) { + Vector point_list = { + Vector2(3, 3.0 * (1.0 - p_overlap * 2.0)), + Vector2(0, 3), + Vector2(-3, 3.0 * (1.0 - p_overlap * 2.0)), + Vector2(-3, -3.0 * (1.0 - p_overlap * 2.0)), + Vector2(0, -3), + Vector2(3, -3.0 * (1.0 - p_overlap * 2.0)) + }; + + Vector2 unit = Vector2(p_size) / 6.0; + Vector polygon; + if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + for (int i = 0; i < point_list.size(); i++) { + point_list.write[i] = point_list[i] * unit; + } + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: + polygon.push_back(point_list[5]); + polygon.push_back(point_list[0]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + polygon.push_back(point_list[0]); + polygon.push_back(point_list[1]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + polygon.push_back(point_list[1]); + polygon.push_back(point_list[2]); + break; + case TileSet::CELL_NEIGHBOR_LEFT_SIDE: + polygon.push_back(point_list[2]); + polygon.push_back(point_list[3]); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: + polygon.push_back(point_list[3]); + polygon.push_back(point_list[4]); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: + polygon.push_back(point_list[4]); + polygon.push_back(point_list[5]); + break; + default: + break; + } + } else { + for (int i = 0; i < point_list.size(); i++) { + point_list.write[i] = Vector2(point_list[i].y, point_list[i].x) * unit; + } + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + polygon.push_back(point_list[0]); + polygon.push_back(point_list[1]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: + polygon.push_back(point_list[5]); + polygon.push_back(point_list[0]); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + polygon.push_back(point_list[4]); + polygon.push_back(point_list[5]); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: + polygon.push_back(point_list[3]); + polygon.push_back(point_list[4]); + break; + case TileSet::CELL_NEIGHBOR_TOP_SIDE: + polygon.push_back(point_list[2]); + polygon.push_back(point_list[3]); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: + polygon.push_back(point_list[1]); + polygon.push_back(point_list[2]); + break; + default: + break; + } + } + + int half_polygon_size = polygon.size(); + for (int i = 0; i < half_polygon_size; i++) { + polygon.push_back(polygon[half_polygon_size - 1 - i] / 3.0); + } + + return polygon; +} + +void TileSet::reset_state() { + // Rendering + occlusion_layers.clear(); + tile_lines_mesh.instantiate(); + tile_filled_mesh.instantiate(); + tile_meshes_dirty = true; + + // Physics + physics_layers.clear(); + + // Terrains + terrain_sets.clear(); + terrain_meshes.clear(); + terrain_peering_bits_meshes.clear(); + per_terrain_pattern_tiles.clear(); + terrains_cache_dirty = true; + + // Navigation + navigation_layers.clear(); + + custom_data_layers.clear(); + custom_data_layers_by_name.clear(); + + // Proxies + source_level_proxies.clear(); + coords_level_proxies.clear(); + alternative_level_proxies.clear(); + +#ifndef DISABLE_DEPRECATED + for (const KeyValue &E : compatibility_data) { + memdelete(E.value); + } + compatibility_data.clear(); +#endif // DISABLE_DEPRECATED + while (!source_ids.is_empty()) { + remove_source(source_ids[0]); + } + + tile_shape = TILE_SHAPE_SQUARE; + tile_layout = TILE_LAYOUT_STACKED; + tile_offset_axis = TILE_OFFSET_AXIS_HORIZONTAL; + tile_size = Size2i(16, 16); +} + +Vector2i TileSet::transform_coords_layout(const Vector2i &p_coords, TileSet::TileOffsetAxis p_offset_axis, TileSet::TileLayout p_from_layout, TileSet::TileLayout p_to_layout) { + // Transform to stacked layout. + Vector2i output = p_coords; + if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) { + SWAP(output.x, output.y); + } + switch (p_from_layout) { + case TileSet::TILE_LAYOUT_STACKED: + break; + case TileSet::TILE_LAYOUT_STACKED_OFFSET: + if (output.y % 2) { + output.x -= 1; + } + break; + case TileSet::TILE_LAYOUT_STAIRS_RIGHT: + case TileSet::TILE_LAYOUT_STAIRS_DOWN: + if ((p_from_layout == TileSet::TILE_LAYOUT_STAIRS_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { + if (output.y < 0 && bool(output.y % 2)) { + output = Vector2i(output.x + output.y / 2 - 1, output.y); + } else { + output = Vector2i(output.x + output.y / 2, output.y); + } + } else { + if (output.x < 0 && bool(output.x % 2)) { + output = Vector2i(output.x / 2 - 1, output.x + output.y * 2); + } else { + output = Vector2i(output.x / 2, output.x + output.y * 2); + } + } + break; + case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: + case TileSet::TILE_LAYOUT_DIAMOND_DOWN: + if ((p_from_layout == TileSet::TILE_LAYOUT_DIAMOND_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { + if ((output.x + output.y) < 0 && (output.x - output.y) % 2) { + output = Vector2i((output.x + output.y) / 2 - 1, output.y - output.x); + } else { + output = Vector2i((output.x + output.y) / 2, -output.x + output.y); + } + } else { + if ((output.x - output.y) < 0 && (output.x + output.y) % 2) { + output = Vector2i((output.x - output.y) / 2 - 1, output.x + output.y); + } else { + output = Vector2i((output.x - output.y) / 2, output.x + output.y); + } + } + break; + } + + switch (p_to_layout) { + case TileSet::TILE_LAYOUT_STACKED: + break; + case TileSet::TILE_LAYOUT_STACKED_OFFSET: + if (output.y % 2) { + output.x += 1; + } + break; + case TileSet::TILE_LAYOUT_STAIRS_RIGHT: + case TileSet::TILE_LAYOUT_STAIRS_DOWN: + if ((p_to_layout == TileSet::TILE_LAYOUT_STAIRS_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { + if (output.y < 0 && (output.y % 2)) { + output = Vector2i(output.x - output.y / 2 + 1, output.y); + } else { + output = Vector2i(output.x - output.y / 2, output.y); + } + } else { + if (output.y % 2) { + if (output.y < 0) { + output = Vector2i(2 * output.x + 1, -output.x + output.y / 2 - 1); + } else { + output = Vector2i(2 * output.x + 1, -output.x + output.y / 2); + } + } else { + output = Vector2i(2 * output.x, -output.x + output.y / 2); + } + } + break; + case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: + case TileSet::TILE_LAYOUT_DIAMOND_DOWN: + if ((p_to_layout == TileSet::TILE_LAYOUT_DIAMOND_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { + if (output.y % 2) { + if (output.y > 0) { + output = Vector2i(output.x - output.y / 2, output.x + output.y / 2 + 1); + } else { + output = Vector2i(output.x - output.y / 2 + 1, output.x + output.y / 2); + } + } else { + output = Vector2i(output.x - output.y / 2, output.x + output.y / 2); + } + } else { + if (output.y % 2) { + if (output.y < 0) { + output = Vector2i(output.x + output.y / 2, -output.x + output.y / 2 - 1); + } else { + output = Vector2i(output.x + output.y / 2 + 1, -output.x + output.y / 2); + } + } else { + output = Vector2i(output.x + output.y / 2, -output.x + output.y / 2); + } + } + break; + } + + if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) { + SWAP(output.x, output.y); + } + + return output; +} + +const Vector2i TileSetSource::INVALID_ATLAS_COORDS = Vector2i(-1, -1); +const int TileSetSource::INVALID_TILE_ALTERNATIVE = -1; + +#ifndef DISABLE_DEPRECATED +void TileSet::_compatibility_conversion() { + for (KeyValue &E : compatibility_data) { + CompatibilityTileData *ctd = E.value; + + // Add the texture + TileSetAtlasSource *atlas_source = memnew(TileSetAtlasSource); + int source_id = add_source(Ref(atlas_source)); + + atlas_source->set_texture(ctd->texture); + + // Handle each tile as a new source. Not optimal but at least it should stay compatible. + switch (ctd->tile_mode) { + case COMPATIBILITY_TILE_MODE_SINGLE_TILE: { + atlas_source->set_margins(ctd->region.get_position()); + atlas_source->set_texture_region_size(ctd->region.get_size()); + + Vector2i coords; + for (int flags = 0; flags < 8; flags++) { + bool flip_h = flags & 1; + bool flip_v = flags & 2; + bool transpose = flags & 4; + + Transform2D xform; + xform = flip_h ? xform.scaled(Size2(-1, 1)) : xform; + xform = flip_v ? xform.scaled(Size2(1, -1)) : xform; + xform = transpose ? Transform2D(xform[1], xform[0], Vector2()) : xform; + + int alternative_tile = 0; + if (!atlas_source->has_tile(coords)) { + atlas_source->create_tile(coords); + } else { + alternative_tile = atlas_source->create_alternative_tile(coords); + } + + // Add to the mapping. + Array key_array; + key_array.push_back(flip_h); + key_array.push_back(flip_v); + key_array.push_back(transpose); + + Array value_array; + value_array.push_back(source_id); + value_array.push_back(coords); + value_array.push_back(alternative_tile); + + if (!compatibility_tilemap_mapping.has(E.key)) { + compatibility_tilemap_mapping[E.key] = RBMap(); + } + compatibility_tilemap_mapping[E.key][key_array] = value_array; + compatibility_tilemap_mapping_tile_modes[E.key] = COMPATIBILITY_TILE_MODE_SINGLE_TILE; + + TileData *tile_data = atlas_source->get_tile_data(coords, alternative_tile); + ERR_CONTINUE(!tile_data); + + tile_data->set_flip_h(flip_h); + tile_data->set_flip_v(flip_v); + tile_data->set_transpose(transpose); + tile_data->set_material(ctd->material); + tile_data->set_modulate(ctd->modulate); + tile_data->set_z_index(ctd->z_index); + + if (ctd->occluder.is_valid()) { + if (get_occlusion_layers_count() < 1) { + add_occlusion_layer(); + }; + Ref occluder = ctd->occluder->duplicate(); + Vector polygon = ctd->occluder->get_polygon(); + for (int index = 0; index < polygon.size(); index++) { + polygon.write[index] = xform.xform(polygon[index] - ctd->region.get_size() / 2.0); + } + occluder->set_polygon(polygon); + tile_data->set_occluder(0, occluder); + } + if (ctd->navigation.is_valid()) { + if (get_navigation_layers_count() < 1) { + add_navigation_layer(); + } + Ref navigation = ctd->navigation->duplicate(); + Vector vertices = navigation->get_vertices(); + for (int index = 0; index < vertices.size(); index++) { + vertices.write[index] = xform.xform(vertices[index] - ctd->region.get_size() / 2.0); + } + navigation->set_vertices(vertices); + tile_data->set_navigation_polygon(0, navigation); + } + + tile_data->set_z_index(ctd->z_index); + + // Add the shapes. + if (ctd->shapes.size() > 0) { + if (get_physics_layers_count() < 1) { + add_physics_layer(); + } + } + for (int k = 0; k < ctd->shapes.size(); k++) { + CompatibilityShapeData csd = ctd->shapes[k]; + if (csd.autotile_coords == coords) { + Ref convex_shape = csd.shape; // Only ConvexPolygonShape2D are supported, which is the default type used by the 3.x editor + if (convex_shape.is_valid()) { + Vector polygon = convex_shape->get_points(); + for (int point_index = 0; point_index < polygon.size(); point_index++) { + polygon.write[point_index] = xform.xform(csd.transform.xform(polygon[point_index]) - ctd->region.get_size() / 2.0); + } + tile_data->set_collision_polygons_count(0, tile_data->get_collision_polygons_count(0) + 1); + int index = tile_data->get_collision_polygons_count(0) - 1; + tile_data->set_collision_polygon_one_way(0, index, csd.one_way); + tile_data->set_collision_polygon_one_way_margin(0, index, csd.one_way_margin); + tile_data->set_collision_polygon_points(0, index, polygon); + } + } + } + } + // Update the size count. + if (!compatibility_size_count.has(ctd->region.get_size())) { + compatibility_size_count[ctd->region.get_size()] = 0; + } + compatibility_size_count[ctd->region.get_size()]++; + } break; + case COMPATIBILITY_TILE_MODE_AUTO_TILE: { + // Not supported. It would need manual conversion. + WARN_PRINT_ONCE("Could not convert 3.x autotiles to 4.x. This operation cannot be done automatically, autotiles must be re-created using the terrain system."); + } break; + case COMPATIBILITY_TILE_MODE_ATLAS_TILE: { + atlas_source->set_margins(ctd->region.get_position()); + atlas_source->set_separation(Vector2i(ctd->autotile_spacing, ctd->autotile_spacing)); + atlas_source->set_texture_region_size(ctd->autotile_tile_size); + + Size2i atlas_size = ctd->region.get_size() / (ctd->autotile_tile_size + atlas_source->get_separation()); + for (int i = 0; i < atlas_size.x; i++) { + for (int j = 0; j < atlas_size.y; j++) { + Vector2i coords = Vector2i(i, j); + + for (int flags = 0; flags < 8; flags++) { + bool flip_h = flags & 1; + bool flip_v = flags & 2; + bool transpose = flags & 4; + + Transform2D xform; + xform = flip_h ? xform.scaled(Size2(-1, 1)) : xform; + xform = flip_v ? xform.scaled(Size2(1, -1)) : xform; + xform = transpose ? Transform2D(xform[1], xform[0], Vector2()) : xform; + + int alternative_tile = 0; + if (!atlas_source->has_tile(coords)) { + atlas_source->create_tile(coords); + } else { + alternative_tile = atlas_source->create_alternative_tile(coords); + } + + // Add to the mapping. + Array key_array; + key_array.push_back(coords); + key_array.push_back(flip_h); + key_array.push_back(flip_v); + key_array.push_back(transpose); + + Array value_array; + value_array.push_back(source_id); + value_array.push_back(coords); + value_array.push_back(alternative_tile); + + if (!compatibility_tilemap_mapping.has(E.key)) { + compatibility_tilemap_mapping[E.key] = RBMap(); + } + compatibility_tilemap_mapping[E.key][key_array] = value_array; + compatibility_tilemap_mapping_tile_modes[E.key] = COMPATIBILITY_TILE_MODE_ATLAS_TILE; + + TileData *tile_data = atlas_source->get_tile_data(coords, alternative_tile); + + tile_data->set_flip_h(flip_h); + tile_data->set_flip_v(flip_v); + tile_data->set_transpose(transpose); + tile_data->set_material(ctd->material); + tile_data->set_modulate(ctd->modulate); + tile_data->set_z_index(ctd->z_index); + if (ctd->autotile_occluder_map.has(coords)) { + if (get_occlusion_layers_count() < 1) { + add_occlusion_layer(); + } + Ref occluder = ctd->autotile_occluder_map[coords]->duplicate(); + Vector polygon = ctd->occluder->get_polygon(); + for (int index = 0; index < polygon.size(); index++) { + polygon.write[index] = xform.xform(polygon[index] - ctd->region.get_size() / 2.0); + } + occluder->set_polygon(polygon); + tile_data->set_occluder(0, occluder); + } + if (ctd->autotile_navpoly_map.has(coords)) { + if (get_navigation_layers_count() < 1) { + add_navigation_layer(); + } + Ref navigation = ctd->autotile_navpoly_map[coords]->duplicate(); + Vector vertices = navigation->get_vertices(); + for (int index = 0; index < vertices.size(); index++) { + vertices.write[index] = xform.xform(vertices[index] - ctd->region.get_size() / 2.0); + } + navigation->set_vertices(vertices); + tile_data->set_navigation_polygon(0, navigation); + } + if (ctd->autotile_priority_map.has(coords)) { + tile_data->set_probability(ctd->autotile_priority_map[coords]); + } + if (ctd->autotile_z_index_map.has(coords)) { + tile_data->set_z_index(ctd->autotile_z_index_map[coords]); + } + + // Add the shapes. + if (ctd->shapes.size() > 0) { + if (get_physics_layers_count() < 1) { + add_physics_layer(); + } + } + for (int k = 0; k < ctd->shapes.size(); k++) { + CompatibilityShapeData csd = ctd->shapes[k]; + if (csd.autotile_coords == coords) { + Ref convex_shape = csd.shape; // Only ConvexPolygonShape2D are supported, which is the default type used by the 3.x editor + if (convex_shape.is_valid()) { + Vector polygon = convex_shape->get_points(); + for (int point_index = 0; point_index < polygon.size(); point_index++) { + polygon.write[point_index] = xform.xform(csd.transform.xform(polygon[point_index]) - ctd->autotile_tile_size / 2.0); + } + tile_data->set_collision_polygons_count(0, tile_data->get_collision_polygons_count(0) + 1); + int index = tile_data->get_collision_polygons_count(0) - 1; + tile_data->set_collision_polygon_one_way(0, index, csd.one_way); + tile_data->set_collision_polygon_one_way_margin(0, index, csd.one_way_margin); + tile_data->set_collision_polygon_points(0, index, polygon); + } + } + } + + // -- TODO: handle -- + // Those are offset for the whole atlas, they are likely useless for the atlases, but might make sense for single tiles. + // texture offset + // occluder_offset + // navigation_offset + + // For terrains, ignored for now? + // bitmask_mode + // bitmask_flags + } + } + } + + // Update the size count. + if (!compatibility_size_count.has(ctd->region.get_size())) { + compatibility_size_count[ctd->autotile_tile_size] = 0; + } + compatibility_size_count[ctd->autotile_tile_size] += atlas_size.x * atlas_size.y; + } break; + } + + // Offset all shapes + for (int k = 0; k < ctd->shapes.size(); k++) { + Ref convex = ctd->shapes[k].shape; + if (convex.is_valid()) { + Vector points = convex->get_points(); + for (int i_point = 0; i_point < points.size(); i_point++) { + points.write[i_point] = points[i_point] - get_tile_size() / 2; + } + convex->set_points(points); + } + } + } + + // Update the TileSet tile_size according to the most common size found. + Vector2i max_size = get_tile_size(); + int max_count = 0; + for (KeyValue kv : compatibility_size_count) { + if (kv.value > max_count) { + max_size = kv.key; + max_count = kv.value; + } + } + set_tile_size(max_size); + + // Reset compatibility data (besides the histogram counts) + for (const KeyValue &E : compatibility_data) { + memdelete(E.value); + } + compatibility_data = HashMap(); +} + +Array TileSet::compatibility_tilemap_map(int p_tile_id, Vector2i p_coords, bool p_flip_h, bool p_flip_v, bool p_transpose) { + Array cannot_convert_array; + cannot_convert_array.push_back(TileSet::INVALID_SOURCE); + cannot_convert_array.push_back(TileSetAtlasSource::INVALID_ATLAS_COORDS); + cannot_convert_array.push_back(TileSetAtlasSource::INVALID_TILE_ALTERNATIVE); + + if (!compatibility_tilemap_mapping.has(p_tile_id)) { + return cannot_convert_array; + } + + int tile_mode = compatibility_tilemap_mapping_tile_modes[p_tile_id]; + switch (tile_mode) { + case COMPATIBILITY_TILE_MODE_SINGLE_TILE: { + Array a; + a.push_back(p_flip_h); + a.push_back(p_flip_v); + a.push_back(p_transpose); + return compatibility_tilemap_mapping[p_tile_id][a]; + } + case COMPATIBILITY_TILE_MODE_AUTO_TILE: + return cannot_convert_array; + break; + case COMPATIBILITY_TILE_MODE_ATLAS_TILE: { + Array a; + a.push_back(p_coords); + a.push_back(p_flip_h); + a.push_back(p_flip_v); + a.push_back(p_transpose); + return compatibility_tilemap_mapping[p_tile_id][a]; + } + default: + return cannot_convert_array; + break; + } +}; + +#endif // DISABLE_DEPRECATED + +bool TileSet::_set(const StringName &p_name, const Variant &p_value) { + Vector components = String(p_name).split("/", true, 2); + +#ifndef DISABLE_DEPRECATED + // TODO: This should be moved to a dedicated conversion system (see #50691) + if (components.size() >= 1 && components[0].is_valid_int()) { + int id = components[0].to_int(); + + // Get or create the compatibility object + CompatibilityTileData *ctd; + HashMap::Iterator E = compatibility_data.find(id); + if (!E) { + ctd = memnew(CompatibilityTileData); + compatibility_data.insert(id, ctd); + } else { + ctd = E->value; + } + + if (components.size() < 2) { + return false; + } + + String what = components[1]; + + if (what == "name") { + ctd->name = p_value; + } else if (what == "texture") { + ctd->texture = p_value; + } else if (what == "tex_offset") { + ctd->tex_offset = p_value; + } else if (what == "material") { + ctd->material = p_value; + } else if (what == "modulate") { + ctd->modulate = p_value; + } else if (what == "region") { + ctd->region = p_value; + } else if (what == "tile_mode") { + ctd->tile_mode = p_value; + } else if (what.left(9) == "autotile") { + what = what.substr(9); + if (what == "bitmask_mode") { + ctd->autotile_bitmask_mode = p_value; + } else if (what == "icon_coordinate") { + ctd->autotile_icon_coordinate = p_value; + } else if (what == "tile_size") { + ctd->autotile_tile_size = p_value; + } else if (what == "spacing") { + ctd->autotile_spacing = p_value; + } else if (what == "bitmask_flags") { + if (p_value.is_array()) { + Array p = p_value; + Vector2i last_coord; + while (p.size() > 0) { + if (p[0].get_type() == Variant::VECTOR2) { + last_coord = p[0]; + } else if (p[0].get_type() == Variant::INT) { + ctd->autotile_bitmask_flags.insert(last_coord, p[0]); + } + p.pop_front(); + } + } + } else if (what == "occluder_map") { + Array p = p_value; + Vector2 last_coord; + while (p.size() > 0) { + if (p[0].get_type() == Variant::VECTOR2) { + last_coord = p[0]; + } else if (p[0].get_type() == Variant::OBJECT) { + ctd->autotile_occluder_map.insert(last_coord, p[0]); + } + p.pop_front(); + } + } else if (what == "navpoly_map") { + Array p = p_value; + Vector2 last_coord; + while (p.size() > 0) { + if (p[0].get_type() == Variant::VECTOR2) { + last_coord = p[0]; + } else if (p[0].get_type() == Variant::OBJECT) { + ctd->autotile_navpoly_map.insert(last_coord, p[0]); + } + p.pop_front(); + } + } else if (what == "priority_map") { + Array p = p_value; + Vector3 val; + Vector2 v; + int priority; + while (p.size() > 0) { + val = p[0]; + if (val.z > 1) { + v.x = val.x; + v.y = val.y; + priority = (int)val.z; + ctd->autotile_priority_map.insert(v, priority); + } + p.pop_front(); + } + } else if (what == "z_index_map") { + Array p = p_value; + Vector3 val; + Vector2 v; + int z_index; + while (p.size() > 0) { + val = p[0]; + if (val.z != 0) { + v.x = val.x; + v.y = val.y; + z_index = (int)val.z; + ctd->autotile_z_index_map.insert(v, z_index); + } + p.pop_front(); + } + } + + } else if (what == "shapes") { + Array p = p_value; + for (int i = 0; i < p.size(); i++) { + CompatibilityShapeData csd; + Dictionary d = p[i]; + for (int j = 0; j < d.size(); j++) { + String key = d.get_key_at_index(j); + if (key == "autotile_coord") { + csd.autotile_coords = d[key]; + } else if (key == "one_way") { + csd.one_way = d[key]; + } else if (key == "one_way_margin") { + csd.one_way_margin = d[key]; + } else if (key == "shape") { + csd.shape = d[key]; + } else if (key == "shape_transform") { + csd.transform = d[key]; + } + } + ctd->shapes.push_back(csd); + } + } else if (what == "occluder") { + ctd->occluder = p_value; + } else if (what == "navigation") { + ctd->navigation = p_value; + + /* + // IGNORED FOR NOW, they seem duplicated data compared to the shapes array + } else if (what == "shape") { + } else if (what == "shape_offset") { + } else if (what == "shape_transform") { + } else if (what == "shape_one_way") { + } else if (what == "shape_one_way_margin") { + } + // IGNORED FOR NOW, maybe useless ? + else if (what == "occluder_offset") { + // Not + } else if (what == "navigation_offset") { + } + */ + + } else if (what == "z_index") { + ctd->z_index = p_value; + + // TODO: remove the conversion from here, it's not where it should be done (see #50691) + _compatibility_conversion(); + } else { + return false; + } + } else { +#endif // DISABLE_DEPRECATED + + // This is now a new property. + if (components.size() == 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) { + // Occlusion layers. + int index = components[0].trim_prefix("occlusion_layer_").to_int(); + ERR_FAIL_COND_V(index < 0, false); + if (components[1] == "light_mask") { + ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); + while (index >= occlusion_layers.size()) { + add_occlusion_layer(); + } + set_occlusion_layer_light_mask(index, p_value); + return true; + } else if (components[1] == "sdf_collision") { + ERR_FAIL_COND_V(p_value.get_type() != Variant::BOOL, false); + while (index >= occlusion_layers.size()) { + add_occlusion_layer(); + } + set_occlusion_layer_sdf_collision(index, p_value); + return true; + } + } else if (components.size() == 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) { + // Physics layers. + int index = components[0].trim_prefix("physics_layer_").to_int(); + ERR_FAIL_COND_V(index < 0, false); + if (components[1] == "collision_layer") { + ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); + while (index >= physics_layers.size()) { + add_physics_layer(); + } + set_physics_layer_collision_layer(index, p_value); + return true; + } else if (components[1] == "collision_mask") { + ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); + while (index >= physics_layers.size()) { + add_physics_layer(); + } + set_physics_layer_collision_mask(index, p_value); + return true; + } else if (components[1] == "physics_material") { + Ref physics_material = p_value; + while (index >= physics_layers.size()) { + add_physics_layer(); + } + set_physics_layer_physics_material(index, physics_material); + return true; + } + } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int()) { + // Terrains. + int terrain_set_index = components[0].trim_prefix("terrain_set_").to_int(); + ERR_FAIL_COND_V(terrain_set_index < 0, false); + if (components[1] == "mode") { + ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); + while (terrain_set_index >= terrain_sets.size()) { + add_terrain_set(); + } + set_terrain_set_mode(terrain_set_index, TerrainMode(int(p_value))); + } else if (components.size() >= 3 && components[1].begins_with("terrain_") && components[1].trim_prefix("terrain_").is_valid_int()) { + int terrain_index = components[1].trim_prefix("terrain_").to_int(); + ERR_FAIL_COND_V(terrain_index < 0, false); + if (components[2] == "name") { + ERR_FAIL_COND_V(p_value.get_type() != Variant::STRING, false); + while (terrain_set_index >= terrain_sets.size()) { + add_terrain_set(); + } + while (terrain_index >= terrain_sets[terrain_set_index].terrains.size()) { + add_terrain(terrain_set_index); + } + set_terrain_name(terrain_set_index, terrain_index, p_value); + return true; + } else if (components[2] == "color") { + ERR_FAIL_COND_V(p_value.get_type() != Variant::COLOR, false); + while (terrain_set_index >= terrain_sets.size()) { + add_terrain_set(); + } + while (terrain_index >= terrain_sets[terrain_set_index].terrains.size()) { + add_terrain(terrain_set_index); + } + set_terrain_color(terrain_set_index, terrain_index, p_value); + return true; + } + } + } else if (components.size() == 2 && components[0].begins_with("navigation_layer_") && components[0].trim_prefix("navigation_layer_").is_valid_int()) { + // Navigation layers. + int index = components[0].trim_prefix("navigation_layer_").to_int(); + ERR_FAIL_COND_V(index < 0, false); + if (components[1] == "layers") { + ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); + while (index >= navigation_layers.size()) { + add_navigation_layer(); + } + set_navigation_layer_layers(index, p_value); + return true; + } + } else if (components.size() == 2 && components[0].begins_with("custom_data_layer_") && components[0].trim_prefix("custom_data_layer_").is_valid_int()) { + // Custom data layers. + int index = components[0].trim_prefix("custom_data_layer_").to_int(); + ERR_FAIL_COND_V(index < 0, false); + if (components[1] == "name") { + ERR_FAIL_COND_V(p_value.get_type() != Variant::STRING, false); + while (index >= custom_data_layers.size()) { + add_custom_data_layer(); + } + set_custom_data_layer_name(index, p_value); + return true; + } else if (components[1] == "type") { + ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); + while (index >= custom_data_layers.size()) { + add_custom_data_layer(); + } + set_custom_data_layer_type(index, Variant::Type(int(p_value))); + return true; + } + } else if (components.size() == 2 && components[0] == "sources" && components[1].is_valid_int()) { + // Create source only if it does not exists. + int source_id = components[1].to_int(); + + if (has_source(source_id)) { + remove_source(source_id); + } + add_source(p_value, source_id); + return true; + } else if (components.size() == 2 && components[0] == "tile_proxies") { + ERR_FAIL_COND_V(p_value.get_type() != Variant::ARRAY, false); + Array a = p_value; + ERR_FAIL_COND_V(a.size() % 2 != 0, false); + if (components[1] == "source_level") { + for (int i = 0; i < a.size(); i += 2) { + set_source_level_tile_proxy(a[i], a[i + 1]); + } + return true; + } else if (components[1] == "coords_level") { + for (int i = 0; i < a.size(); i += 2) { + Array key = a[i]; + Array value = a[i + 1]; + set_coords_level_tile_proxy(key[0], key[1], value[0], value[1]); + } + return true; + } else if (components[1] == "alternative_level") { + for (int i = 0; i < a.size(); i += 2) { + Array key = a[i]; + Array value = a[i + 1]; + set_alternative_level_tile_proxy(key[0], key[1], key[2], value[0], value[1], value[2]); + } + return true; + } + return false; + } else if (components.size() == 1 && components[0].begins_with("pattern_") && components[0].trim_prefix("pattern_").is_valid_int()) { + int pattern_index = components[0].trim_prefix("pattern_").to_int(); + for (int i = patterns.size(); i <= pattern_index; i++) { + add_pattern(p_value); + } + return true; + } + +#ifndef DISABLE_DEPRECATED + } +#endif // DISABLE_DEPRECATED + + return false; +} + +bool TileSet::_get(const StringName &p_name, Variant &r_ret) const { + Vector components = String(p_name).split("/", true, 2); + + if (components.size() == 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) { + // Occlusion layers. + int index = components[0].trim_prefix("occlusion_layer_").to_int(); + if (index < 0 || index >= occlusion_layers.size()) { + return false; + } + if (components[1] == "light_mask") { + r_ret = get_occlusion_layer_light_mask(index); + return true; + } else if (components[1] == "sdf_collision") { + r_ret = get_occlusion_layer_sdf_collision(index); + return true; + } + } else if (components.size() == 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) { + // Physics layers. + int index = components[0].trim_prefix("physics_layer_").to_int(); + if (index < 0 || index >= physics_layers.size()) { + return false; + } + if (components[1] == "collision_layer") { + r_ret = get_physics_layer_collision_layer(index); + return true; + } else if (components[1] == "collision_mask") { + r_ret = get_physics_layer_collision_mask(index); + return true; + } else if (components[1] == "physics_material") { + r_ret = get_physics_layer_physics_material(index); + return true; + } + } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int()) { + // Terrains. + int terrain_set_index = components[0].trim_prefix("terrain_set_").to_int(); + if (terrain_set_index < 0 || terrain_set_index >= terrain_sets.size()) { + return false; + } + if (components[1] == "mode") { + r_ret = get_terrain_set_mode(terrain_set_index); + return true; + } else if (components.size() >= 3 && components[1].begins_with("terrain_") && components[1].trim_prefix("terrain_").is_valid_int()) { + int terrain_index = components[1].trim_prefix("terrain_").to_int(); + if (terrain_index < 0 || terrain_index >= terrain_sets[terrain_set_index].terrains.size()) { + return false; + } + if (components[2] == "name") { + r_ret = get_terrain_name(terrain_set_index, terrain_index); + return true; + } else if (components[2] == "color") { + r_ret = get_terrain_color(terrain_set_index, terrain_index); + return true; + } + } + } else if (components.size() == 2 && components[0].begins_with("navigation_layer_") && components[0].trim_prefix("navigation_layer_").is_valid_int()) { + // navigation layers. + int index = components[0].trim_prefix("navigation_layer_").to_int(); + if (index < 0 || index >= navigation_layers.size()) { + return false; + } + if (components[1] == "layers") { + r_ret = get_navigation_layer_layers(index); + return true; + } + } else if (components.size() == 2 && components[0].begins_with("custom_data_layer_") && components[0].trim_prefix("custom_data_layer_").is_valid_int()) { + // Custom data layers. + int index = components[0].trim_prefix("custom_data_layer_").to_int(); + if (index < 0 || index >= custom_data_layers.size()) { + return false; + } + if (components[1] == "name") { + r_ret = get_custom_data_layer_name(index); + return true; + } else if (components[1] == "type") { + r_ret = get_custom_data_layer_type(index); + return true; + } + } else if (components.size() == 2 && components[0] == "sources" && components[1].is_valid_int()) { + // Atlases data. + int source_id = components[1].to_int(); + + if (has_source(source_id)) { + r_ret = get_source(source_id); + return true; + } else { + return false; + } + } else if (components.size() == 2 && components[0] == "tile_proxies") { + if (components[1] == "source_level") { + Array a; + for (const KeyValue &E : source_level_proxies) { + a.push_back(E.key); + a.push_back(E.value); + } + r_ret = a; + return true; + } else if (components[1] == "coords_level") { + Array a; + for (const KeyValue &E : coords_level_proxies) { + a.push_back(E.key); + a.push_back(E.value); + } + r_ret = a; + return true; + } else if (components[1] == "alternative_level") { + Array a; + for (const KeyValue &E : alternative_level_proxies) { + a.push_back(E.key); + a.push_back(E.value); + } + r_ret = a; + return true; + } + return false; + } else if (components.size() == 1 && components[0].begins_with("pattern_") && components[0].trim_prefix("pattern_").is_valid_int()) { + int pattern_index = components[0].trim_prefix("pattern_").to_int(); + if (pattern_index < 0 || pattern_index >= (int)patterns.size()) { + return false; + } + r_ret = patterns[pattern_index]; + return true; + } + + return false; +} + +void TileSet::_get_property_list(List *p_list) const { + PropertyInfo property_info; + // Rendering. + p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Rendering", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); + for (int i = 0; i < occlusion_layers.size(); i++) { + p_list->push_back(PropertyInfo(Variant::INT, vformat("occlusion_layer_%d/light_mask", i), PROPERTY_HINT_LAYERS_2D_RENDER)); + + // occlusion_layer_%d/sdf_collision + property_info = PropertyInfo(Variant::BOOL, vformat("occlusion_layer_%d/sdf_collision", i)); + if (occlusion_layers[i].sdf_collision == false) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + p_list->push_back(property_info); + } + + // Physics. + p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Physics", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); + for (int i = 0; i < physics_layers.size(); i++) { + p_list->push_back(PropertyInfo(Variant::INT, vformat("physics_layer_%d/collision_layer", i), PROPERTY_HINT_LAYERS_2D_PHYSICS)); + + // physics_layer_%d/collision_mask + property_info = PropertyInfo(Variant::INT, vformat("physics_layer_%d/collision_mask", i), PROPERTY_HINT_LAYERS_2D_PHYSICS); + if (physics_layers[i].collision_mask == 1) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + p_list->push_back(property_info); + + // physics_layer_%d/physics_material + property_info = PropertyInfo(Variant::OBJECT, vformat("physics_layer_%d/physics_material", i), PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"); + if (!physics_layers[i].physics_material.is_valid()) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + p_list->push_back(property_info); + } + + // Terrains. + p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Terrains", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); + for (int terrain_set_index = 0; terrain_set_index < terrain_sets.size(); terrain_set_index++) { + p_list->push_back(PropertyInfo(Variant::INT, vformat("terrain_set_%d/mode", terrain_set_index), PROPERTY_HINT_ENUM, "Match Corners and Sides,Match Corners,Match Sides")); + p_list->push_back(PropertyInfo(Variant::NIL, vformat("terrain_set_%d/terrains", terrain_set_index), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, vformat("terrain_set_%d/terrain_", terrain_set_index))); + for (int terrain_index = 0; terrain_index < terrain_sets[terrain_set_index].terrains.size(); terrain_index++) { + p_list->push_back(PropertyInfo(Variant::STRING, vformat("terrain_set_%d/terrain_%d/name", terrain_set_index, terrain_index))); + p_list->push_back(PropertyInfo(Variant::COLOR, vformat("terrain_set_%d/terrain_%d/color", terrain_set_index, terrain_index))); + } + } + + // Navigation. + p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Navigation", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); + for (int i = 0; i < navigation_layers.size(); i++) { + p_list->push_back(PropertyInfo(Variant::INT, vformat("navigation_layer_%d/layers", i), PROPERTY_HINT_LAYERS_2D_NAVIGATION)); + } + + // Custom data. + String argt = "Any"; + for (int i = 1; i < Variant::VARIANT_MAX; i++) { + argt += "," + Variant::get_type_name(Variant::Type(i)); + } + p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Custom Data", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); + for (int i = 0; i < custom_data_layers.size(); i++) { + p_list->push_back(PropertyInfo(Variant::STRING, vformat("custom_data_layer_%d/name", i))); + p_list->push_back(PropertyInfo(Variant::INT, vformat("custom_data_layer_%d/type", i), PROPERTY_HINT_ENUM, argt)); + } + + // Sources. + // Note: sources have to be listed in at the end as some TileData rely on the TileSet properties being initialized first. + for (const KeyValue> &E_source : sources) { + p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("sources/%d", E_source.key), PROPERTY_HINT_RESOURCE_TYPE, "TileSetAtlasSource", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_ALWAYS_DUPLICATE)); + } + + // Tile Proxies. + // Note: proxies need to be set after sources are set. + p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Tile Proxies", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); + p_list->push_back(PropertyInfo(Variant::ARRAY, PNAME("tile_proxies/source_level"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + p_list->push_back(PropertyInfo(Variant::ARRAY, PNAME("tile_proxies/coords_level"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + p_list->push_back(PropertyInfo(Variant::ARRAY, PNAME("tile_proxies/alternative_level"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + + // Patterns. + for (unsigned int pattern_index = 0; pattern_index < patterns.size(); pattern_index++) { + p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("pattern_%d", pattern_index), PROPERTY_HINT_RESOURCE_TYPE, "TileMapPattern", PROPERTY_USAGE_NO_EDITOR)); + } +} + +void TileSet::_validate_property(PropertyInfo &p_property) const { + if (p_property.name == "tile_layout" && tile_shape == TILE_SHAPE_SQUARE) { + p_property.usage ^= PROPERTY_USAGE_READ_ONLY; + } else if (p_property.name == "tile_offset_axis" && tile_shape == TILE_SHAPE_SQUARE) { + p_property.usage ^= PROPERTY_USAGE_READ_ONLY; + } +} + +void TileSet::_bind_methods() { + // Sources management. + ClassDB::bind_method(D_METHOD("get_next_source_id"), &TileSet::get_next_source_id); + ClassDB::bind_method(D_METHOD("add_source", "source", "atlas_source_id_override"), &TileSet::add_source, DEFVAL(TileSet::INVALID_SOURCE)); + ClassDB::bind_method(D_METHOD("remove_source", "source_id"), &TileSet::remove_source); + ClassDB::bind_method(D_METHOD("set_source_id", "source_id", "new_source_id"), &TileSet::set_source_id); + ClassDB::bind_method(D_METHOD("get_source_count"), &TileSet::get_source_count); + ClassDB::bind_method(D_METHOD("get_source_id", "index"), &TileSet::get_source_id); + ClassDB::bind_method(D_METHOD("has_source", "source_id"), &TileSet::has_source); + ClassDB::bind_method(D_METHOD("get_source", "source_id"), &TileSet::get_source); + + // Shape and layout. + ClassDB::bind_method(D_METHOD("set_tile_shape", "shape"), &TileSet::set_tile_shape); + ClassDB::bind_method(D_METHOD("get_tile_shape"), &TileSet::get_tile_shape); + ClassDB::bind_method(D_METHOD("set_tile_layout", "layout"), &TileSet::set_tile_layout); + ClassDB::bind_method(D_METHOD("get_tile_layout"), &TileSet::get_tile_layout); + ClassDB::bind_method(D_METHOD("set_tile_offset_axis", "alignment"), &TileSet::set_tile_offset_axis); + ClassDB::bind_method(D_METHOD("get_tile_offset_axis"), &TileSet::get_tile_offset_axis); + ClassDB::bind_method(D_METHOD("set_tile_size", "size"), &TileSet::set_tile_size); + ClassDB::bind_method(D_METHOD("get_tile_size"), &TileSet::get_tile_size); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "tile_shape", PROPERTY_HINT_ENUM, "Square,Isometric,Half-Offset Square,Hexagon"), "set_tile_shape", "get_tile_shape"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "tile_layout", PROPERTY_HINT_ENUM, "Stacked,Stacked Offset,Stairs Right,Stairs Down,Diamond Right,Diamond Down"), "set_tile_layout", "get_tile_layout"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "tile_offset_axis", PROPERTY_HINT_ENUM, "Horizontal Offset,Vertical Offset"), "set_tile_offset_axis", "get_tile_offset_axis"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "tile_size", PROPERTY_HINT_NONE, "suffix:px"), "set_tile_size", "get_tile_size"); + + // Rendering. + ClassDB::bind_method(D_METHOD("set_uv_clipping", "uv_clipping"), &TileSet::set_uv_clipping); + ClassDB::bind_method(D_METHOD("is_uv_clipping"), &TileSet::is_uv_clipping); + + ClassDB::bind_method(D_METHOD("get_occlusion_layers_count"), &TileSet::get_occlusion_layers_count); + ClassDB::bind_method(D_METHOD("add_occlusion_layer", "to_position"), &TileSet::add_occlusion_layer, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("move_occlusion_layer", "layer_index", "to_position"), &TileSet::move_occlusion_layer); + ClassDB::bind_method(D_METHOD("remove_occlusion_layer", "layer_index"), &TileSet::remove_occlusion_layer); + ClassDB::bind_method(D_METHOD("set_occlusion_layer_light_mask", "layer_index", "light_mask"), &TileSet::set_occlusion_layer_light_mask); + ClassDB::bind_method(D_METHOD("get_occlusion_layer_light_mask", "layer_index"), &TileSet::get_occlusion_layer_light_mask); + ClassDB::bind_method(D_METHOD("set_occlusion_layer_sdf_collision", "layer_index", "sdf_collision"), &TileSet::set_occlusion_layer_sdf_collision); + ClassDB::bind_method(D_METHOD("get_occlusion_layer_sdf_collision", "layer_index"), &TileSet::get_occlusion_layer_sdf_collision); + + // Physics + ClassDB::bind_method(D_METHOD("get_physics_layers_count"), &TileSet::get_physics_layers_count); + ClassDB::bind_method(D_METHOD("add_physics_layer", "to_position"), &TileSet::add_physics_layer, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("move_physics_layer", "layer_index", "to_position"), &TileSet::move_physics_layer); + ClassDB::bind_method(D_METHOD("remove_physics_layer", "layer_index"), &TileSet::remove_physics_layer); + ClassDB::bind_method(D_METHOD("set_physics_layer_collision_layer", "layer_index", "layer"), &TileSet::set_physics_layer_collision_layer); + ClassDB::bind_method(D_METHOD("get_physics_layer_collision_layer", "layer_index"), &TileSet::get_physics_layer_collision_layer); + ClassDB::bind_method(D_METHOD("set_physics_layer_collision_mask", "layer_index", "mask"), &TileSet::set_physics_layer_collision_mask); + ClassDB::bind_method(D_METHOD("get_physics_layer_collision_mask", "layer_index"), &TileSet::get_physics_layer_collision_mask); + ClassDB::bind_method(D_METHOD("set_physics_layer_physics_material", "layer_index", "physics_material"), &TileSet::set_physics_layer_physics_material); + ClassDB::bind_method(D_METHOD("get_physics_layer_physics_material", "layer_index"), &TileSet::get_physics_layer_physics_material); + + // Terrains + ClassDB::bind_method(D_METHOD("get_terrain_sets_count"), &TileSet::get_terrain_sets_count); + ClassDB::bind_method(D_METHOD("add_terrain_set", "to_position"), &TileSet::add_terrain_set, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("move_terrain_set", "terrain_set", "to_position"), &TileSet::move_terrain_set); + ClassDB::bind_method(D_METHOD("remove_terrain_set", "terrain_set"), &TileSet::remove_terrain_set); + ClassDB::bind_method(D_METHOD("set_terrain_set_mode", "terrain_set", "mode"), &TileSet::set_terrain_set_mode); + ClassDB::bind_method(D_METHOD("get_terrain_set_mode", "terrain_set"), &TileSet::get_terrain_set_mode); + + ClassDB::bind_method(D_METHOD("get_terrains_count", "terrain_set"), &TileSet::get_terrains_count); + ClassDB::bind_method(D_METHOD("add_terrain", "terrain_set", "to_position"), &TileSet::add_terrain, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("move_terrain", "terrain_set", "terrain_index", "to_position"), &TileSet::move_terrain); + ClassDB::bind_method(D_METHOD("remove_terrain", "terrain_set", "terrain_index"), &TileSet::remove_terrain); + ClassDB::bind_method(D_METHOD("set_terrain_name", "terrain_set", "terrain_index", "name"), &TileSet::set_terrain_name); + ClassDB::bind_method(D_METHOD("get_terrain_name", "terrain_set", "terrain_index"), &TileSet::get_terrain_name); + ClassDB::bind_method(D_METHOD("set_terrain_color", "terrain_set", "terrain_index", "color"), &TileSet::set_terrain_color); + ClassDB::bind_method(D_METHOD("get_terrain_color", "terrain_set", "terrain_index"), &TileSet::get_terrain_color); + + // Navigation + ClassDB::bind_method(D_METHOD("get_navigation_layers_count"), &TileSet::get_navigation_layers_count); + ClassDB::bind_method(D_METHOD("add_navigation_layer", "to_position"), &TileSet::add_navigation_layer, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("move_navigation_layer", "layer_index", "to_position"), &TileSet::move_navigation_layer); + ClassDB::bind_method(D_METHOD("remove_navigation_layer", "layer_index"), &TileSet::remove_navigation_layer); + ClassDB::bind_method(D_METHOD("set_navigation_layer_layers", "layer_index", "layers"), &TileSet::set_navigation_layer_layers); + ClassDB::bind_method(D_METHOD("get_navigation_layer_layers", "layer_index"), &TileSet::get_navigation_layer_layers); + ClassDB::bind_method(D_METHOD("set_navigation_layer_layer_value", "layer_index", "layer_number", "value"), &TileSet::set_navigation_layer_layer_value); + ClassDB::bind_method(D_METHOD("get_navigation_layer_layer_value", "layer_index", "layer_number"), &TileSet::get_navigation_layer_layer_value); + + // Custom data + ClassDB::bind_method(D_METHOD("get_custom_data_layers_count"), &TileSet::get_custom_data_layers_count); + ClassDB::bind_method(D_METHOD("add_custom_data_layer", "to_position"), &TileSet::add_custom_data_layer, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("move_custom_data_layer", "layer_index", "to_position"), &TileSet::move_custom_data_layer); + ClassDB::bind_method(D_METHOD("remove_custom_data_layer", "layer_index"), &TileSet::remove_custom_data_layer); + ClassDB::bind_method(D_METHOD("get_custom_data_layer_by_name", "layer_name"), &TileSet::get_custom_data_layer_by_name); + ClassDB::bind_method(D_METHOD("set_custom_data_layer_name", "layer_index", "layer_name"), &TileSet::set_custom_data_layer_name); + ClassDB::bind_method(D_METHOD("get_custom_data_layer_name", "layer_index"), &TileSet::get_custom_data_layer_name); + ClassDB::bind_method(D_METHOD("set_custom_data_layer_type", "layer_index", "layer_type"), &TileSet::set_custom_data_layer_type); + ClassDB::bind_method(D_METHOD("get_custom_data_layer_type", "layer_index"), &TileSet::get_custom_data_layer_type); + + // Tile proxies + ClassDB::bind_method(D_METHOD("set_source_level_tile_proxy", "source_from", "source_to"), &TileSet::set_source_level_tile_proxy); + ClassDB::bind_method(D_METHOD("get_source_level_tile_proxy", "source_from"), &TileSet::get_source_level_tile_proxy); + ClassDB::bind_method(D_METHOD("has_source_level_tile_proxy", "source_from"), &TileSet::has_source_level_tile_proxy); + ClassDB::bind_method(D_METHOD("remove_source_level_tile_proxy", "source_from"), &TileSet::remove_source_level_tile_proxy); + + ClassDB::bind_method(D_METHOD("set_coords_level_tile_proxy", "p_source_from", "coords_from", "source_to", "coords_to"), &TileSet::set_coords_level_tile_proxy); + ClassDB::bind_method(D_METHOD("get_coords_level_tile_proxy", "source_from", "coords_from"), &TileSet::get_coords_level_tile_proxy); + ClassDB::bind_method(D_METHOD("has_coords_level_tile_proxy", "source_from", "coords_from"), &TileSet::has_coords_level_tile_proxy); + ClassDB::bind_method(D_METHOD("remove_coords_level_tile_proxy", "source_from", "coords_from"), &TileSet::remove_coords_level_tile_proxy); + + ClassDB::bind_method(D_METHOD("set_alternative_level_tile_proxy", "source_from", "coords_from", "alternative_from", "source_to", "coords_to", "alternative_to"), &TileSet::set_alternative_level_tile_proxy); + ClassDB::bind_method(D_METHOD("get_alternative_level_tile_proxy", "source_from", "coords_from", "alternative_from"), &TileSet::get_alternative_level_tile_proxy); + ClassDB::bind_method(D_METHOD("has_alternative_level_tile_proxy", "source_from", "coords_from", "alternative_from"), &TileSet::has_alternative_level_tile_proxy); + ClassDB::bind_method(D_METHOD("remove_alternative_level_tile_proxy", "source_from", "coords_from", "alternative_from"), &TileSet::remove_alternative_level_tile_proxy); + + ClassDB::bind_method(D_METHOD("map_tile_proxy", "source_from", "coords_from", "alternative_from"), &TileSet::map_tile_proxy); + + ClassDB::bind_method(D_METHOD("cleanup_invalid_tile_proxies"), &TileSet::cleanup_invalid_tile_proxies); + ClassDB::bind_method(D_METHOD("clear_tile_proxies"), &TileSet::clear_tile_proxies); + + // Patterns + ClassDB::bind_method(D_METHOD("add_pattern", "pattern", "index"), &TileSet::add_pattern, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("get_pattern", "index"), &TileSet::get_pattern, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("remove_pattern", "index"), &TileSet::remove_pattern); + ClassDB::bind_method(D_METHOD("get_patterns_count"), &TileSet::get_patterns_count); + + ADD_GROUP("Rendering", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uv_clipping"), "set_uv_clipping", "is_uv_clipping"); + ADD_ARRAY("occlusion_layers", "occlusion_layer_"); + + ADD_GROUP("", ""); + ADD_ARRAY("physics_layers", "physics_layer_"); + ADD_ARRAY("terrain_sets", "terrain_set_"); + ADD_ARRAY("navigation_layers", "navigation_layer_"); + ADD_ARRAY("custom_data_layers", "custom_data_layer_"); + + // -- Enum binding -- + BIND_ENUM_CONSTANT(TILE_SHAPE_SQUARE); + BIND_ENUM_CONSTANT(TILE_SHAPE_ISOMETRIC); + BIND_ENUM_CONSTANT(TILE_SHAPE_HALF_OFFSET_SQUARE); + BIND_ENUM_CONSTANT(TILE_SHAPE_HEXAGON); + + BIND_ENUM_CONSTANT(TILE_LAYOUT_STACKED); + BIND_ENUM_CONSTANT(TILE_LAYOUT_STACKED_OFFSET); + BIND_ENUM_CONSTANT(TILE_LAYOUT_STAIRS_RIGHT); + BIND_ENUM_CONSTANT(TILE_LAYOUT_STAIRS_DOWN); + BIND_ENUM_CONSTANT(TILE_LAYOUT_DIAMOND_RIGHT); + BIND_ENUM_CONSTANT(TILE_LAYOUT_DIAMOND_DOWN); + + BIND_ENUM_CONSTANT(TILE_OFFSET_AXIS_HORIZONTAL); + BIND_ENUM_CONSTANT(TILE_OFFSET_AXIS_VERTICAL); + + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_RIGHT_SIDE); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_RIGHT_CORNER); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_BOTTOM_SIDE); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_BOTTOM_CORNER); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_BOTTOM_LEFT_SIDE); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_BOTTOM_LEFT_CORNER); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_LEFT_SIDE); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_LEFT_CORNER); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_TOP_LEFT_SIDE); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_TOP_LEFT_CORNER); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_TOP_SIDE); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_TOP_CORNER); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_TOP_RIGHT_SIDE); + BIND_ENUM_CONSTANT(CELL_NEIGHBOR_TOP_RIGHT_CORNER); + + BIND_ENUM_CONSTANT(TERRAIN_MODE_MATCH_CORNERS_AND_SIDES); + BIND_ENUM_CONSTANT(TERRAIN_MODE_MATCH_CORNERS); + BIND_ENUM_CONSTANT(TERRAIN_MODE_MATCH_SIDES); +} + +TileSet::TileSet() { + // Instantiate the tile meshes. + tile_lines_mesh.instantiate(); + tile_filled_mesh.instantiate(); +} + +TileSet::~TileSet() { +#ifndef DISABLE_DEPRECATED + for (const KeyValue &E : compatibility_data) { + memdelete(E.value); + } +#endif // DISABLE_DEPRECATED + while (!source_ids.is_empty()) { + remove_source(source_ids[0]); + } +} + +/////////////////////////////// TileSetSource ////////////////////////////////////// + +void TileSetSource::set_tile_set(const TileSet *p_tile_set) { + tile_set = p_tile_set; +} + +TileSet *TileSetSource::get_tile_set() const { + return (TileSet *)tile_set; +} + +void TileSetSource::reset_state() { + tile_set = nullptr; +}; + +void TileSetSource::_bind_methods() { + // Base tiles + ClassDB::bind_method(D_METHOD("get_tiles_count"), &TileSetSource::get_tiles_count); + ClassDB::bind_method(D_METHOD("get_tile_id", "index"), &TileSetSource::get_tile_id); + ClassDB::bind_method(D_METHOD("has_tile", "atlas_coords"), &TileSetSource::has_tile); + + // Alternative tiles + ClassDB::bind_method(D_METHOD("get_alternative_tiles_count", "atlas_coords"), &TileSetSource::get_alternative_tiles_count); + ClassDB::bind_method(D_METHOD("get_alternative_tile_id", "atlas_coords", "index"), &TileSetSource::get_alternative_tile_id); + ClassDB::bind_method(D_METHOD("has_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetSource::has_alternative_tile); +} + +/////////////////////////////// TileSetAtlasSource ////////////////////////////////////// + +void TileSetAtlasSource::set_tile_set(const TileSet *p_tile_set) { + tile_set = p_tile_set; + + // Set the TileSet on all TileData. + for (KeyValue &E_tile : tiles) { + for (KeyValue &E_alternative : E_tile.value.alternatives) { + E_alternative.value->set_tile_set(tile_set); + } + } +} + +const TileSet *TileSetAtlasSource::get_tile_set() const { + return tile_set; +} + +void TileSetAtlasSource::notify_tile_data_properties_should_change() { + // Set the TileSet on all TileData. + for (KeyValue &E_tile : tiles) { + for (KeyValue &E_alternative : E_tile.value.alternatives) { + E_alternative.value->notify_tile_data_properties_should_change(); + } + } +} + +void TileSetAtlasSource::add_occlusion_layer(int p_to_pos) { + for (KeyValue E_tile : tiles) { + for (KeyValue E_alternative : E_tile.value.alternatives) { + E_alternative.value->add_occlusion_layer(p_to_pos); + } + } +} + +void TileSetAtlasSource::move_occlusion_layer(int p_from_index, int p_to_pos) { + for (KeyValue E_tile : tiles) { + for (KeyValue E_alternative : E_tile.value.alternatives) { + E_alternative.value->move_occlusion_layer(p_from_index, p_to_pos); + } + } +} + +void TileSetAtlasSource::remove_occlusion_layer(int p_index) { + for (KeyValue E_tile : tiles) { + for (KeyValue E_alternative : E_tile.value.alternatives) { + E_alternative.value->remove_occlusion_layer(p_index); + } + } +} + +void TileSetAtlasSource::add_physics_layer(int p_to_pos) { + for (KeyValue E_tile : tiles) { + for (KeyValue E_alternative : E_tile.value.alternatives) { + E_alternative.value->add_physics_layer(p_to_pos); + } + } +} + +void TileSetAtlasSource::move_physics_layer(int p_from_index, int p_to_pos) { + for (KeyValue E_tile : tiles) { + for (KeyValue E_alternative : E_tile.value.alternatives) { + E_alternative.value->move_physics_layer(p_from_index, p_to_pos); + } + } +} + +void TileSetAtlasSource::remove_physics_layer(int p_index) { + for (KeyValue E_tile : tiles) { + for (KeyValue E_alternative : E_tile.value.alternatives) { + E_alternative.value->remove_physics_layer(p_index); + } + } +} + +void TileSetAtlasSource::add_terrain_set(int p_to_pos) { + for (KeyValue E_tile : tiles) { + for (KeyValue E_alternative : E_tile.value.alternatives) { + E_alternative.value->add_terrain_set(p_to_pos); + } + } +} + +void TileSetAtlasSource::move_terrain_set(int p_from_index, int p_to_pos) { + for (KeyValue E_tile : tiles) { + for (KeyValue E_alternative : E_tile.value.alternatives) { + E_alternative.value->move_terrain_set(p_from_index, p_to_pos); + } + } +} + +void TileSetAtlasSource::remove_terrain_set(int p_index) { + for (KeyValue E_tile : tiles) { + for (KeyValue E_alternative : E_tile.value.alternatives) { + E_alternative.value->remove_terrain_set(p_index); + } + } +} + +void TileSetAtlasSource::add_terrain(int p_terrain_set, int p_to_pos) { + for (KeyValue E_tile : tiles) { + for (KeyValue E_alternative : E_tile.value.alternatives) { + E_alternative.value->add_terrain(p_terrain_set, p_to_pos); + } + } +} + +void TileSetAtlasSource::move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) { + for (KeyValue E_tile : tiles) { + for (KeyValue E_alternative : E_tile.value.alternatives) { + E_alternative.value->move_terrain(p_terrain_set, p_from_index, p_to_pos); + } + } +} + +void TileSetAtlasSource::remove_terrain(int p_terrain_set, int p_index) { + for (KeyValue E_tile : tiles) { + for (KeyValue E_alternative : E_tile.value.alternatives) { + E_alternative.value->remove_terrain(p_terrain_set, p_index); + } + } +} + +void TileSetAtlasSource::add_navigation_layer(int p_to_pos) { + for (KeyValue E_tile : tiles) { + for (KeyValue E_alternative : E_tile.value.alternatives) { + E_alternative.value->add_navigation_layer(p_to_pos); + } + } +} + +void TileSetAtlasSource::move_navigation_layer(int p_from_index, int p_to_pos) { + for (KeyValue E_tile : tiles) { + for (KeyValue E_alternative : E_tile.value.alternatives) { + E_alternative.value->move_navigation_layer(p_from_index, p_to_pos); + } + } +} + +void TileSetAtlasSource::remove_navigation_layer(int p_index) { + for (KeyValue E_tile : tiles) { + for (KeyValue E_alternative : E_tile.value.alternatives) { + E_alternative.value->remove_navigation_layer(p_index); + } + } +} + +void TileSetAtlasSource::add_custom_data_layer(int p_to_pos) { + for (KeyValue E_tile : tiles) { + for (KeyValue E_alternative : E_tile.value.alternatives) { + E_alternative.value->add_custom_data_layer(p_to_pos); + } + } +} + +void TileSetAtlasSource::move_custom_data_layer(int p_from_index, int p_to_pos) { + for (KeyValue E_tile : tiles) { + for (KeyValue E_alternative : E_tile.value.alternatives) { + E_alternative.value->move_custom_data_layer(p_from_index, p_to_pos); + } + } +} + +void TileSetAtlasSource::remove_custom_data_layer(int p_index) { + for (KeyValue E_tile : tiles) { + for (KeyValue E_alternative : E_tile.value.alternatives) { + E_alternative.value->remove_custom_data_layer(p_index); + } + } +} + +void TileSetAtlasSource::reset_state() { + tile_set = nullptr; + + for (KeyValue &E_tile : tiles) { + for (const KeyValue &E_tile_data : E_tile.value.alternatives) { + memdelete(E_tile_data.value); + } + } + _coords_mapping_cache.clear(); + tiles.clear(); + tiles_ids.clear(); + _queue_update_padded_texture(); +} + +void TileSetAtlasSource::set_texture(Ref p_texture) { + if (texture.is_valid()) { + texture->disconnect_changed(callable_mp(this, &TileSetAtlasSource::_queue_update_padded_texture)); + } + + texture = p_texture; + + if (texture.is_valid()) { + texture->connect_changed(callable_mp(this, &TileSetAtlasSource::_queue_update_padded_texture)); + } + + _queue_update_padded_texture(); + emit_changed(); +} + +Ref TileSetAtlasSource::get_texture() const { + return texture; +} + +void TileSetAtlasSource::set_margins(Vector2i p_margins) { + if (p_margins.x < 0 || p_margins.y < 0) { + WARN_PRINT("Atlas source margins should be positive."); + margins = Vector2i(MAX(0, p_margins.x), MAX(0, p_margins.y)); + } else { + margins = p_margins; + } + + _queue_update_padded_texture(); + emit_changed(); +} + +Vector2i TileSetAtlasSource::get_margins() const { + return margins; +} + +void TileSetAtlasSource::set_separation(Vector2i p_separation) { + if (p_separation.x < 0 || p_separation.y < 0) { + WARN_PRINT("Atlas source separation should be positive."); + separation = Vector2i(MAX(0, p_separation.x), MAX(0, p_separation.y)); + } else { + separation = p_separation; + } + + _queue_update_padded_texture(); + emit_changed(); +} + +Vector2i TileSetAtlasSource::get_separation() const { + return separation; +} + +void TileSetAtlasSource::set_texture_region_size(Vector2i p_tile_size) { + if (p_tile_size.x <= 0 || p_tile_size.y <= 0) { + WARN_PRINT("Atlas source tile_size should be strictly positive."); + texture_region_size = Vector2i(MAX(1, p_tile_size.x), MAX(1, p_tile_size.y)); + } else { + texture_region_size = p_tile_size; + } + + _queue_update_padded_texture(); + emit_changed(); +} + +Vector2i TileSetAtlasSource::get_texture_region_size() const { + return texture_region_size; +} + +void TileSetAtlasSource::set_use_texture_padding(bool p_use_padding) { + if (use_texture_padding == p_use_padding) { + return; + } + use_texture_padding = p_use_padding; + _queue_update_padded_texture(); + emit_changed(); +} + +bool TileSetAtlasSource::get_use_texture_padding() const { + return use_texture_padding; +} + +Vector2i TileSetAtlasSource::get_atlas_grid_size() const { + Ref txt = get_texture(); + if (!txt.is_valid()) { + return Vector2i(); + } + + ERR_FAIL_COND_V(texture_region_size.x <= 0 || texture_region_size.y <= 0, Vector2i()); + + Size2i valid_area = txt->get_size() - margins; + + // Compute the number of valid tiles in the tiles atlas + Size2i grid_size; + if (valid_area.x >= texture_region_size.x && valid_area.y >= texture_region_size.y) { + valid_area -= texture_region_size; + grid_size = Size2i(1, 1) + valid_area / (texture_region_size + separation); + } + return grid_size; +} + +bool TileSetAtlasSource::_set(const StringName &p_name, const Variant &p_value) { + Vector components = String(p_name).split("/", true, 2); + + // Compute the vector2i if we have coordinates. + Vector coords_split = components[0].split(":"); + Vector2i coords = TileSetSource::INVALID_ATLAS_COORDS; + if (coords_split.size() == 2 && coords_split[0].is_valid_int() && coords_split[1].is_valid_int()) { + coords = Vector2i(coords_split[0].to_int(), coords_split[1].to_int()); + } + + // Properties. + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + // Create the tile if needed. + if (!has_tile(coords)) { + create_tile(coords); + } + if (components.size() >= 2) { + // Properties. + if (components[1] == "size_in_atlas") { + move_tile_in_atlas(coords, coords, p_value); + return true; + } else if (components[1] == "next_alternative_id") { + tiles[coords].next_alternative_id = p_value; + return true; + } else if (components[1] == "animation_columns") { + set_tile_animation_columns(coords, p_value); + return true; + } else if (components[1] == "animation_separation") { + set_tile_animation_separation(coords, p_value); + return true; + } else if (components[1] == "animation_speed") { + set_tile_animation_speed(coords, p_value); + return true; + } else if (components[1] == "animation_mode") { + set_tile_animation_mode(coords, VariantCaster::cast(p_value)); + return true; + } else if (components[1] == "animation_frames_count") { + set_tile_animation_frames_count(coords, p_value); + return true; + } else if (components.size() >= 3 && components[1].begins_with("animation_frame_") && components[1].trim_prefix("animation_frame_").is_valid_int()) { + int frame = components[1].trim_prefix("animation_frame_").to_int(); + if (components[2] == "duration") { + if (frame >= get_tile_animation_frames_count(coords)) { + set_tile_animation_frames_count(coords, frame + 1); + } + set_tile_animation_frame_duration(coords, frame, p_value); + return true; + } + return false; + } else if (components[1].is_valid_int()) { + int alternative_id = components[1].to_int(); + if (alternative_id != TileSetSource::INVALID_TILE_ALTERNATIVE) { + // Create the alternative if needed ? + if (!has_alternative_tile(coords, alternative_id)) { + create_alternative_tile(coords, alternative_id); + } + if (!tiles[coords].alternatives.has(alternative_id)) { + tiles[coords].alternatives[alternative_id] = memnew(TileData); + tiles[coords].alternatives[alternative_id]->set_tile_set(tile_set); + tiles[coords].alternatives[alternative_id]->set_allow_transform(alternative_id > 0); + tiles[coords].alternatives_ids.push_back(alternative_id); + } + if (components.size() >= 3) { + bool valid; + tiles[coords].alternatives[alternative_id]->set(components[2], p_value, &valid); + return valid; + } else { + // Only create the alternative if it did not exist yet. + return true; + } + } + } + } + } + + return false; +} + +bool TileSetAtlasSource::_get(const StringName &p_name, Variant &r_ret) const { + Vector components = String(p_name).split("/", true, 2); + + // Properties. + Vector coords_split = components[0].split(":"); + if (coords_split.size() == 2 && coords_split[0].is_valid_int() && coords_split[1].is_valid_int()) { + Vector2i coords = Vector2i(coords_split[0].to_int(), coords_split[1].to_int()); + if (tiles.has(coords)) { + if (components.size() >= 2) { + // Properties. + if (components[1] == "size_in_atlas") { + r_ret = tiles[coords].size_in_atlas; + return true; + } else if (components[1] == "next_alternative_id") { + r_ret = tiles[coords].next_alternative_id; + return true; + } else if (components[1] == "animation_columns") { + r_ret = get_tile_animation_columns(coords); + return true; + } else if (components[1] == "animation_separation") { + r_ret = get_tile_animation_separation(coords); + return true; + } else if (components[1] == "animation_speed") { + r_ret = get_tile_animation_speed(coords); + return true; + } else if (components[1] == "animation_mode") { + r_ret = get_tile_animation_mode(coords); + return true; + } else if (components[1] == "animation_frames_count") { + r_ret = get_tile_animation_frames_count(coords); + return true; + } else if (components.size() >= 3 && components[1].begins_with("animation_frame_") && components[1].trim_prefix("animation_frame_").is_valid_int()) { + int frame = components[1].trim_prefix("animation_frame_").to_int(); + if (frame < 0 || frame >= get_tile_animation_frames_count(coords)) { + return false; + } + if (components[2] == "duration") { + r_ret = get_tile_animation_frame_duration(coords, frame); + return true; + } + return false; + } else if (components[1].is_valid_int()) { + int alternative_id = components[1].to_int(); + if (alternative_id != TileSetSource::INVALID_TILE_ALTERNATIVE && tiles[coords].alternatives.has(alternative_id)) { + if (components.size() >= 3) { + bool valid; + r_ret = tiles[coords].alternatives[alternative_id]->get(components[2], &valid); + return valid; + } else { + // Only to notify the tile alternative exists. + r_ret = alternative_id; + return true; + } + } + } + } + } + } + + return false; +} + +void TileSetAtlasSource::_get_property_list(List *p_list) const { + // Atlases data. + PropertyInfo property_info; + for (const KeyValue &E_tile : tiles) { + List tile_property_list; + + // size_in_atlas + property_info = PropertyInfo(Variant::VECTOR2I, "size_in_atlas", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR); + if (E_tile.value.size_in_atlas == Vector2i(1, 1)) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + tile_property_list.push_back(property_info); + + // next_alternative_id + property_info = PropertyInfo(Variant::INT, "next_alternative_id", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR); + if (E_tile.value.next_alternative_id == 1) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + tile_property_list.push_back(property_info); + + // animation_columns. + property_info = PropertyInfo(Variant::INT, "animation_columns", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR); + if (E_tile.value.animation_columns == 0) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + tile_property_list.push_back(property_info); + + // animation_separation. + property_info = PropertyInfo(Variant::INT, "animation_separation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR); + if (E_tile.value.animation_separation == Vector2i()) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + tile_property_list.push_back(property_info); + + // animation_speed. + property_info = PropertyInfo(Variant::FLOAT, "animation_speed", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR); + if (E_tile.value.animation_speed == 1.0) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + tile_property_list.push_back(property_info); + + // animation_mode. + property_info = PropertyInfo(Variant::INT, "animation_mode", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR); + if (E_tile.value.animation_mode == TILE_ANIMATION_MODE_DEFAULT) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + tile_property_list.push_back(property_info); + + // animation_frames_count. + tile_property_list.push_back(PropertyInfo(Variant::INT, "animation_frames_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); + + // animation_frame_*. + bool store_durations = tiles[E_tile.key].animation_frames_durations.size() >= 2; + for (int i = 0; i < (int)tiles[E_tile.key].animation_frames_durations.size(); i++) { + property_info = PropertyInfo(Variant::FLOAT, vformat("animation_frame_%d/duration", i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR); + if (!store_durations) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + tile_property_list.push_back(property_info); + } + + for (const KeyValue &E_alternative : E_tile.value.alternatives) { + // Add a dummy property to show the alternative exists. + tile_property_list.push_back(PropertyInfo(Variant::INT, vformat("%d", E_alternative.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); + + // Get the alternative tile's properties and append them to the list of properties. + List alternative_property_list; + E_alternative.value->get_property_list(&alternative_property_list); + for (PropertyInfo &alternative_property_info : alternative_property_list) { + Variant default_value = ClassDB::class_get_default_property_value("TileData", alternative_property_info.name); + Variant value = E_alternative.value->get(alternative_property_info.name); + if (default_value.get_type() != Variant::NIL && bool(Variant::evaluate(Variant::OP_EQUAL, value, default_value))) { + alternative_property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + alternative_property_info.name = vformat("%s/%s", vformat("%d", E_alternative.key), alternative_property_info.name); + tile_property_list.push_back(alternative_property_info); + } + } + + // Add all alternative. + for (PropertyInfo &tile_property_info : tile_property_list) { + tile_property_info.name = vformat("%s/%s", vformat("%d:%d", E_tile.key.x, E_tile.key.y), tile_property_info.name); + p_list->push_back(tile_property_info); + } + } +} + +void TileSetAtlasSource::create_tile(const Vector2i p_atlas_coords, const Vector2i p_size) { + // Create a tile if it does not exists. + ERR_FAIL_COND(p_atlas_coords.x < 0 || p_atlas_coords.y < 0); + ERR_FAIL_COND(p_size.x <= 0 || p_size.y <= 0); + + bool room_for_tile = has_room_for_tile(p_atlas_coords, p_size, 1, Vector2i(), 1); + ERR_FAIL_COND_MSG(!room_for_tile, "Cannot create tile. The tile is outside the texture or tiles are already present in the space the tile would cover."); + + // Initialize the tile data. + TileAlternativesData tad; + tad.size_in_atlas = p_size; + tad.animation_frames_durations.push_back(1.0); + tad.alternatives[0] = memnew(TileData); + tad.alternatives[0]->set_tile_set(tile_set); + tad.alternatives[0]->set_allow_transform(false); + tad.alternatives[0]->connect("changed", callable_mp((Resource *)this, &TileSetAtlasSource::emit_changed)); + tad.alternatives[0]->notify_property_list_changed(); + tad.alternatives_ids.push_back(0); + + // Create and resize the tile. + tiles.insert(p_atlas_coords, tad); + tiles_ids.push_back(p_atlas_coords); + tiles_ids.sort(); + + _create_coords_mapping_cache(p_atlas_coords); + _queue_update_padded_texture(); + + emit_signal(SNAME("changed")); +} + +void TileSetAtlasSource::remove_tile(Vector2i p_atlas_coords) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + + // Remove all covered positions from the mapping cache + _clear_coords_mapping_cache(p_atlas_coords); + + // Free tile data. + for (const KeyValue &E_tile_data : tiles[p_atlas_coords].alternatives) { + memdelete(E_tile_data.value); + } + + // Delete the tile + tiles.erase(p_atlas_coords); + tiles_ids.erase(p_atlas_coords); + tiles_ids.sort(); + + _queue_update_padded_texture(); + + emit_signal(SNAME("changed")); +} + +bool TileSetAtlasSource::has_tile(Vector2i p_atlas_coords) const { + return tiles.has(p_atlas_coords); +} + +Vector2i TileSetAtlasSource::get_tile_at_coords(Vector2i p_atlas_coords) const { + if (!_coords_mapping_cache.has(p_atlas_coords)) { + return INVALID_ATLAS_COORDS; + } + + return _coords_mapping_cache[p_atlas_coords]; +} + +void TileSetAtlasSource::set_tile_animation_columns(const Vector2i p_atlas_coords, int p_frame_columns) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + ERR_FAIL_COND(p_frame_columns < 0); + + TileAlternativesData &tad = tiles[p_atlas_coords]; + bool room_for_tile = has_room_for_tile(p_atlas_coords, tad.size_in_atlas, p_frame_columns, tad.animation_separation, tad.animation_frames_durations.size(), p_atlas_coords); + ERR_FAIL_COND_MSG(!room_for_tile, "Cannot set animation columns count, tiles are already present in the space the tile would cover."); + + _clear_coords_mapping_cache(p_atlas_coords); + + tiles[p_atlas_coords].animation_columns = p_frame_columns; + + _create_coords_mapping_cache(p_atlas_coords); + _queue_update_padded_texture(); + + emit_signal(SNAME("changed")); +} + +int TileSetAtlasSource::get_tile_animation_columns(const Vector2i p_atlas_coords) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), 1, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + return tiles[p_atlas_coords].animation_columns; +} + +void TileSetAtlasSource::set_tile_animation_separation(const Vector2i p_atlas_coords, const Vector2i p_separation) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + ERR_FAIL_COND(p_separation.x < 0 || p_separation.y < 0); + + TileAlternativesData &tad = tiles[p_atlas_coords]; + bool room_for_tile = has_room_for_tile(p_atlas_coords, tad.size_in_atlas, tad.animation_columns, p_separation, tad.animation_frames_durations.size(), p_atlas_coords); + ERR_FAIL_COND_MSG(!room_for_tile, "Cannot set animation columns count, tiles are already present in the space the tile would cover."); + + _clear_coords_mapping_cache(p_atlas_coords); + + tiles[p_atlas_coords].animation_separation = p_separation; + + _create_coords_mapping_cache(p_atlas_coords); + _queue_update_padded_texture(); + + emit_signal(SNAME("changed")); +} + +Vector2i TileSetAtlasSource::get_tile_animation_separation(const Vector2i p_atlas_coords) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Vector2i(), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + return tiles[p_atlas_coords].animation_separation; +} + +void TileSetAtlasSource::set_tile_animation_speed(const Vector2i p_atlas_coords, real_t p_speed) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + ERR_FAIL_COND(p_speed <= 0); + + tiles[p_atlas_coords].animation_speed = p_speed; + + emit_signal(SNAME("changed")); +} + +real_t TileSetAtlasSource::get_tile_animation_speed(const Vector2i p_atlas_coords) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), 1.0, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + return tiles[p_atlas_coords].animation_speed; +} + +void TileSetAtlasSource::set_tile_animation_mode(const Vector2i p_atlas_coords, TileSetAtlasSource::TileAnimationMode p_mode) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + + tiles[p_atlas_coords].animation_mode = p_mode; + + emit_signal(SNAME("changed")); +} + +TileSetAtlasSource::TileAnimationMode TileSetAtlasSource::get_tile_animation_mode(const Vector2i p_atlas_coords) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), TILE_ANIMATION_MODE_DEFAULT, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + + return tiles[p_atlas_coords].animation_mode; +} + +void TileSetAtlasSource::set_tile_animation_frames_count(const Vector2i p_atlas_coords, int p_frames_count) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + ERR_FAIL_COND(p_frames_count < 1); + + int old_size = tiles[p_atlas_coords].animation_frames_durations.size(); + if (p_frames_count == old_size) { + return; + } + + TileAlternativesData &tad = tiles[p_atlas_coords]; + bool room_for_tile = has_room_for_tile(p_atlas_coords, tad.size_in_atlas, tad.animation_columns, tad.animation_separation, p_frames_count, p_atlas_coords); + ERR_FAIL_COND_MSG(!room_for_tile, "Cannot set animation columns count, tiles are already present in the space the tile would cover."); + + _clear_coords_mapping_cache(p_atlas_coords); + + tiles[p_atlas_coords].animation_frames_durations.resize(p_frames_count); + for (int i = old_size; i < p_frames_count; i++) { + tiles[p_atlas_coords].animation_frames_durations[i] = 1.0; + } + + _create_coords_mapping_cache(p_atlas_coords); + _queue_update_padded_texture(); + + notify_property_list_changed(); + + emit_signal(SNAME("changed")); +} + +int TileSetAtlasSource::get_tile_animation_frames_count(const Vector2i p_atlas_coords) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), 1, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + return tiles[p_atlas_coords].animation_frames_durations.size(); +} + +void TileSetAtlasSource::set_tile_animation_frame_duration(const Vector2i p_atlas_coords, int p_frame_index, real_t p_duration) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + ERR_FAIL_INDEX(p_frame_index, (int)tiles[p_atlas_coords].animation_frames_durations.size()); + ERR_FAIL_COND(p_duration <= 0.0); + + tiles[p_atlas_coords].animation_frames_durations[p_frame_index] = p_duration; + + emit_signal(SNAME("changed")); +} + +real_t TileSetAtlasSource::get_tile_animation_frame_duration(const Vector2i p_atlas_coords, int p_frame_index) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), 1, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + ERR_FAIL_INDEX_V(p_frame_index, (int)tiles[p_atlas_coords].animation_frames_durations.size(), 0.0); + return tiles[p_atlas_coords].animation_frames_durations[p_frame_index]; +} + +real_t TileSetAtlasSource::get_tile_animation_total_duration(const Vector2i p_atlas_coords) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), 1, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + + real_t sum = 0.0; + for (const real_t &duration : tiles[p_atlas_coords].animation_frames_durations) { + sum += duration; + } + return sum; +} + +Vector2i TileSetAtlasSource::get_tile_size_in_atlas(Vector2i p_atlas_coords) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Vector2i(-1, -1), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + + return tiles[p_atlas_coords].size_in_atlas; +} + +int TileSetAtlasSource::get_tiles_count() const { + return tiles_ids.size(); +} + +Vector2i TileSetAtlasSource::get_tile_id(int p_index) const { + ERR_FAIL_INDEX_V(p_index, tiles_ids.size(), TileSetSource::INVALID_ATLAS_COORDS); + return tiles_ids[p_index]; +} + +bool TileSetAtlasSource::has_room_for_tile(Vector2i p_atlas_coords, Vector2i p_size, int p_animation_columns, Vector2i p_animation_separation, int p_frames_count, Vector2i p_ignored_tile) const { + if (p_atlas_coords.x < 0 || p_atlas_coords.y < 0) { + return false; + } + if (p_size.x <= 0 || p_size.y <= 0) { + return false; + } + if (p_frames_count <= 0) { + return false; + } + Size2i atlas_grid_size = get_atlas_grid_size(); + for (int frame = 0; frame < p_frames_count; frame++) { + Vector2i frame_coords = p_atlas_coords + (p_size + p_animation_separation) * ((p_animation_columns > 0) ? Vector2i(frame % p_animation_columns, frame / p_animation_columns) : Vector2i(frame, 0)); + for (int x = 0; x < p_size.x; x++) { + for (int y = 0; y < p_size.y; y++) { + Vector2i coords = frame_coords + Vector2i(x, y); + if (_coords_mapping_cache.has(coords) && _coords_mapping_cache[coords] != p_ignored_tile) { + return false; + } + if (coords.x >= atlas_grid_size.x || coords.y >= atlas_grid_size.y) { + return false; + } + } + } + } + return true; +} + +bool TileSetAtlasSource::has_tiles_outside_texture() const { + for (const KeyValue &E : tiles) { + if (!has_room_for_tile(E.key, E.value.size_in_atlas, E.value.animation_columns, E.value.animation_separation, E.value.animation_frames_durations.size(), E.key)) { + return true; + } + } + return false; +} + +Vector TileSetAtlasSource::get_tiles_outside_texture() const { + Vector to_return; + + for (const KeyValue &E : tiles) { + if (!has_room_for_tile(E.key, E.value.size_in_atlas, E.value.animation_columns, E.value.animation_separation, E.value.animation_frames_durations.size(), E.key)) { + to_return.push_back(E.key); + } + } + return to_return; +} + +void TileSetAtlasSource::clear_tiles_outside_texture() { + LocalVector to_remove; + + for (const KeyValue &E : tiles) { + if (!has_room_for_tile(E.key, E.value.size_in_atlas, E.value.animation_columns, E.value.animation_separation, E.value.animation_frames_durations.size(), E.key)) { + to_remove.push_back(E.key); + } + } + + for (const Vector2i &v : to_remove) { + remove_tile(v); + } +} + +PackedVector2Array TileSetAtlasSource::get_tiles_to_be_removed_on_change(Ref p_texture, Vector2i p_margins, Vector2i p_separation, Vector2i p_texture_region_size) { + ERR_FAIL_COND_V(p_margins.x < 0 || p_margins.y < 0, PackedVector2Array()); + ERR_FAIL_COND_V(p_separation.x < 0 || p_separation.y < 0, PackedVector2Array()); + ERR_FAIL_COND_V(p_texture_region_size.x <= 0 || p_texture_region_size.y <= 0, PackedVector2Array()); + + // Compute the new atlas grid size. + Size2 new_grid_size; + if (p_texture.is_valid()) { + Size2i valid_area = p_texture->get_size() - p_margins; + + // Compute the number of valid tiles in the tiles atlas + if (valid_area.x >= p_texture_region_size.x && valid_area.y >= p_texture_region_size.y) { + valid_area -= p_texture_region_size; + new_grid_size = Size2i(1, 1) + valid_area / (p_texture_region_size + p_separation); + } + } + + Vector output; + for (KeyValue &E : tiles) { + for (unsigned int frame = 0; frame < E.value.animation_frames_durations.size(); frame++) { + Vector2i frame_coords = E.key + (E.value.size_in_atlas + E.value.animation_separation) * ((E.value.animation_columns > 0) ? Vector2i(frame % E.value.animation_columns, frame / E.value.animation_columns) : Vector2i(frame, 0)); + frame_coords += E.value.size_in_atlas; + if (frame_coords.x > new_grid_size.x || frame_coords.y > new_grid_size.y) { + output.push_back(E.key); + break; + } + } + } + return output; +} + +Rect2i TileSetAtlasSource::get_tile_texture_region(Vector2i p_atlas_coords, int p_frame) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Rect2i(), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + ERR_FAIL_INDEX_V(p_frame, (int)tiles[p_atlas_coords].animation_frames_durations.size(), Rect2i()); + + const TileAlternativesData &tad = tiles[p_atlas_coords]; + + Vector2i size_in_atlas = tad.size_in_atlas; + Vector2 region_size = texture_region_size * size_in_atlas + separation * (size_in_atlas - Vector2i(1, 1)); + + Vector2i frame_coords = p_atlas_coords + (size_in_atlas + tad.animation_separation) * ((tad.animation_columns > 0) ? Vector2i(p_frame % tad.animation_columns, p_frame / tad.animation_columns) : Vector2i(p_frame, 0)); + Vector2 origin = margins + (frame_coords * (texture_region_size + separation)); + + return Rect2(origin, region_size); +} + +bool TileSetAtlasSource::is_position_in_tile_texture_region(const Vector2i p_atlas_coords, int p_alternative_tile, Vector2 p_position) const { + Size2 size = get_tile_texture_region(p_atlas_coords).size; + Rect2 rect = Rect2(-size / 2 - get_tile_data(p_atlas_coords, p_alternative_tile)->get_texture_origin(), size); + + return rect.has_point(p_position); +} + +int TileSetAtlasSource::alternative_no_transform(int p_alternative_id) { + return p_alternative_id & ~(TRANSFORM_FLIP_H | TRANSFORM_FLIP_V | TRANSFORM_TRANSPOSE); +} + +// Getters for texture and tile region (padded or not) +Ref TileSetAtlasSource::get_runtime_texture() const { + if (use_texture_padding) { + return padded_texture; + } else { + return texture; + } +} + +Rect2i TileSetAtlasSource::get_runtime_tile_texture_region(Vector2i p_atlas_coords, int p_frame) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Rect2i(), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + ERR_FAIL_INDEX_V(p_frame, (int)tiles[p_atlas_coords].animation_frames_durations.size(), Rect2i()); + + Rect2i src_rect = get_tile_texture_region(p_atlas_coords, p_frame); + if (use_texture_padding) { + const TileAlternativesData &tad = tiles[p_atlas_coords]; + Vector2i frame_coords = p_atlas_coords + (tad.size_in_atlas + tad.animation_separation) * ((tad.animation_columns > 0) ? Vector2i(p_frame % tad.animation_columns, p_frame / tad.animation_columns) : Vector2i(p_frame, 0)); + Vector2i base_pos = frame_coords * (texture_region_size + Vector2i(2, 2)) + Vector2i(1, 1); + + return Rect2i(base_pos, src_rect.size); + } else { + return src_rect; + } +} + +void TileSetAtlasSource::move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords, Vector2i p_new_size) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + + TileAlternativesData &tad = tiles[p_atlas_coords]; + + // Compute the actual new rect from arguments. + Vector2i new_atlas_coords = (p_new_atlas_coords != INVALID_ATLAS_COORDS) ? p_new_atlas_coords : p_atlas_coords; + Vector2i new_size = (p_new_size != Vector2i(-1, -1)) ? p_new_size : tad.size_in_atlas; + + if (new_atlas_coords == p_atlas_coords && new_size == tad.size_in_atlas) { + return; + } + + bool room_for_tile = has_room_for_tile(new_atlas_coords, new_size, tad.animation_columns, tad.animation_separation, tad.animation_frames_durations.size(), p_atlas_coords); + ERR_FAIL_COND_MSG(!room_for_tile, vformat("Cannot move tile at position %s with size %s. Tile already present.", new_atlas_coords, new_size)); + + _clear_coords_mapping_cache(p_atlas_coords); + + // Move the tile and update its size. + if (new_atlas_coords != p_atlas_coords) { + tiles[new_atlas_coords] = tiles[p_atlas_coords]; + tiles.erase(p_atlas_coords); + + tiles_ids.erase(p_atlas_coords); + tiles_ids.push_back(new_atlas_coords); + tiles_ids.sort(); + } + tiles[new_atlas_coords].size_in_atlas = new_size; + + _create_coords_mapping_cache(new_atlas_coords); + _queue_update_padded_texture(); + + emit_signal(SNAME("changed")); +} + +int TileSetAtlasSource::create_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_id_override) { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), TileSetSource::INVALID_TILE_ALTERNATIVE, vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + ERR_FAIL_COND_V_MSG(p_alternative_id_override >= 0 && tiles[p_atlas_coords].alternatives.has(p_alternative_id_override), TileSetSource::INVALID_TILE_ALTERNATIVE, vformat("Cannot create alternative tile. Another alternative exists with id %d.", p_alternative_id_override)); + + int new_alternative_id = p_alternative_id_override >= 0 ? p_alternative_id_override : tiles[p_atlas_coords].next_alternative_id; + + tiles[p_atlas_coords].alternatives[new_alternative_id] = memnew(TileData); + tiles[p_atlas_coords].alternatives[new_alternative_id]->set_tile_set(tile_set); + tiles[p_atlas_coords].alternatives[new_alternative_id]->set_allow_transform(true); + tiles[p_atlas_coords].alternatives[new_alternative_id]->connect("changed", callable_mp((Resource *)this, &TileSetAtlasSource::emit_changed)); + tiles[p_atlas_coords].alternatives[new_alternative_id]->notify_property_list_changed(); + tiles[p_atlas_coords].alternatives_ids.push_back(new_alternative_id); + tiles[p_atlas_coords].alternatives_ids.sort(); + _compute_next_alternative_id(p_atlas_coords); + + emit_signal(SNAME("changed")); + + return new_alternative_id; +} + +void TileSetAtlasSource::remove_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + ERR_FAIL_COND_MSG(!tiles[p_atlas_coords].alternatives.has(p_alternative_tile), vformat("TileSetAtlasSource has no alternative with id %d for tile coords %s.", p_alternative_tile, String(p_atlas_coords))); + p_alternative_tile = alternative_no_transform(p_alternative_tile); + ERR_FAIL_COND_MSG(p_alternative_tile == 0, "Cannot remove the alternative with id 0, the base tile alternative cannot be removed."); + + memdelete(tiles[p_atlas_coords].alternatives[p_alternative_tile]); + tiles[p_atlas_coords].alternatives.erase(p_alternative_tile); + tiles[p_atlas_coords].alternatives_ids.erase(p_alternative_tile); + tiles[p_atlas_coords].alternatives_ids.sort(); + + emit_signal(SNAME("changed")); +} + +void TileSetAtlasSource::set_alternative_tile_id(const Vector2i p_atlas_coords, int p_alternative_tile, int p_new_id) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + ERR_FAIL_COND_MSG(!tiles[p_atlas_coords].alternatives.has(p_alternative_tile), vformat("TileSetAtlasSource has no alternative with id %d for tile coords %s.", p_alternative_tile, String(p_atlas_coords))); + p_alternative_tile = alternative_no_transform(p_alternative_tile); + ERR_FAIL_COND_MSG(p_alternative_tile == 0, "Cannot change the alternative with id 0, the base tile alternative cannot be modified."); + + ERR_FAIL_COND_MSG(tiles[p_atlas_coords].alternatives.has(p_new_id), vformat("TileSetAtlasSource has already an alternative with id %d at %s.", p_new_id, String(p_atlas_coords))); + + tiles[p_atlas_coords].alternatives[p_new_id] = tiles[p_atlas_coords].alternatives[p_alternative_tile]; + tiles[p_atlas_coords].alternatives_ids.push_back(p_new_id); + + tiles[p_atlas_coords].alternatives.erase(p_alternative_tile); + tiles[p_atlas_coords].alternatives_ids.erase(p_alternative_tile); + tiles[p_atlas_coords].alternatives_ids.sort(); + + emit_signal(SNAME("changed")); +} + +bool TileSetAtlasSource::has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), false, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords))); + return tiles[p_atlas_coords].alternatives.has(alternative_no_transform(p_alternative_tile)); +} + +int TileSetAtlasSource::get_next_alternative_tile_id(const Vector2i p_atlas_coords) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), TileSetSource::INVALID_TILE_ALTERNATIVE, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords))); + return tiles[p_atlas_coords].next_alternative_id; +} + +int TileSetAtlasSource::get_alternative_tiles_count(const Vector2i p_atlas_coords) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), -1, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords))); + return tiles[p_atlas_coords].alternatives_ids.size(); +} + +int TileSetAtlasSource::get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), TileSetSource::INVALID_TILE_ALTERNATIVE, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords))); + p_index = alternative_no_transform(p_index); + ERR_FAIL_INDEX_V(p_index, tiles[p_atlas_coords].alternatives_ids.size(), TileSetSource::INVALID_TILE_ALTERNATIVE); + + return tiles[p_atlas_coords].alternatives_ids[p_index]; +} + +TileData *TileSetAtlasSource::get_tile_data(const Vector2i p_atlas_coords, int p_alternative_tile) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), nullptr, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords))); + p_alternative_tile = alternative_no_transform(p_alternative_tile); + ERR_FAIL_COND_V_MSG(!tiles[p_atlas_coords].alternatives.has(p_alternative_tile), nullptr, vformat("TileSetAtlasSource has no alternative with id %d for tile coords %s.", p_alternative_tile, String(p_atlas_coords))); + + return tiles[p_atlas_coords].alternatives[p_alternative_tile]; +} + +void TileSetAtlasSource::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_texture", "texture"), &TileSetAtlasSource::set_texture); + ClassDB::bind_method(D_METHOD("get_texture"), &TileSetAtlasSource::get_texture); + ClassDB::bind_method(D_METHOD("set_margins", "margins"), &TileSetAtlasSource::set_margins); + ClassDB::bind_method(D_METHOD("get_margins"), &TileSetAtlasSource::get_margins); + ClassDB::bind_method(D_METHOD("set_separation", "separation"), &TileSetAtlasSource::set_separation); + ClassDB::bind_method(D_METHOD("get_separation"), &TileSetAtlasSource::get_separation); + ClassDB::bind_method(D_METHOD("set_texture_region_size", "texture_region_size"), &TileSetAtlasSource::set_texture_region_size); + ClassDB::bind_method(D_METHOD("get_texture_region_size"), &TileSetAtlasSource::get_texture_region_size); + ClassDB::bind_method(D_METHOD("set_use_texture_padding", "use_texture_padding"), &TileSetAtlasSource::set_use_texture_padding); + ClassDB::bind_method(D_METHOD("get_use_texture_padding"), &TileSetAtlasSource::get_use_texture_padding); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_NO_EDITOR), "set_texture", "get_texture"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "margins", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_NO_EDITOR), "set_margins", "get_margins"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "separation", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_NO_EDITOR), "set_separation", "get_separation"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "texture_region_size", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_NO_EDITOR), "set_texture_region_size", "get_texture_region_size"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_texture_padding", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_use_texture_padding", "get_use_texture_padding"); + + // Base tiles + ClassDB::bind_method(D_METHOD("create_tile", "atlas_coords", "size"), &TileSetAtlasSource::create_tile, DEFVAL(Vector2i(1, 1))); + ClassDB::bind_method(D_METHOD("remove_tile", "atlas_coords"), &TileSetAtlasSource::remove_tile); // Remove a tile. If p_tile_key.alternative_tile if different from 0, remove the alternative + ClassDB::bind_method(D_METHOD("move_tile_in_atlas", "atlas_coords", "new_atlas_coords", "new_size"), &TileSetAtlasSource::move_tile_in_atlas, DEFVAL(INVALID_ATLAS_COORDS), DEFVAL(Vector2i(-1, -1))); + ClassDB::bind_method(D_METHOD("get_tile_size_in_atlas", "atlas_coords"), &TileSetAtlasSource::get_tile_size_in_atlas); + + ClassDB::bind_method(D_METHOD("has_room_for_tile", "atlas_coords", "size", "animation_columns", "animation_separation", "frames_count", "ignored_tile"), &TileSetAtlasSource::has_room_for_tile, DEFVAL(INVALID_ATLAS_COORDS)); + ClassDB::bind_method(D_METHOD("get_tiles_to_be_removed_on_change", "texture", "margins", "separation", "texture_region_size"), &TileSetAtlasSource::get_tiles_to_be_removed_on_change); + ClassDB::bind_method(D_METHOD("get_tile_at_coords", "atlas_coords"), &TileSetAtlasSource::get_tile_at_coords); + + ClassDB::bind_method(D_METHOD("has_tiles_outside_texture"), &TileSetAtlasSource::has_tiles_outside_texture); + ClassDB::bind_method(D_METHOD("clear_tiles_outside_texture"), &TileSetAtlasSource::clear_tiles_outside_texture); + + ClassDB::bind_method(D_METHOD("set_tile_animation_columns", "atlas_coords", "frame_columns"), &TileSetAtlasSource::set_tile_animation_columns); + ClassDB::bind_method(D_METHOD("get_tile_animation_columns", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_columns); + ClassDB::bind_method(D_METHOD("set_tile_animation_separation", "atlas_coords", "separation"), &TileSetAtlasSource::set_tile_animation_separation); + ClassDB::bind_method(D_METHOD("get_tile_animation_separation", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_separation); + ClassDB::bind_method(D_METHOD("set_tile_animation_speed", "atlas_coords", "speed"), &TileSetAtlasSource::set_tile_animation_speed); + ClassDB::bind_method(D_METHOD("get_tile_animation_speed", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_speed); + ClassDB::bind_method(D_METHOD("set_tile_animation_mode", "atlas_coords", "mode"), &TileSetAtlasSource::set_tile_animation_mode); + ClassDB::bind_method(D_METHOD("get_tile_animation_mode", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_mode); + ClassDB::bind_method(D_METHOD("set_tile_animation_frames_count", "atlas_coords", "frames_count"), &TileSetAtlasSource::set_tile_animation_frames_count); + ClassDB::bind_method(D_METHOD("get_tile_animation_frames_count", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_frames_count); + ClassDB::bind_method(D_METHOD("set_tile_animation_frame_duration", "atlas_coords", "frame_index", "duration"), &TileSetAtlasSource::set_tile_animation_frame_duration); + ClassDB::bind_method(D_METHOD("get_tile_animation_frame_duration", "atlas_coords", "frame_index"), &TileSetAtlasSource::get_tile_animation_frame_duration); + ClassDB::bind_method(D_METHOD("get_tile_animation_total_duration", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_total_duration); + + // Alternative tiles + ClassDB::bind_method(D_METHOD("create_alternative_tile", "atlas_coords", "alternative_id_override"), &TileSetAtlasSource::create_alternative_tile, DEFVAL(INVALID_TILE_ALTERNATIVE)); + ClassDB::bind_method(D_METHOD("remove_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetAtlasSource::remove_alternative_tile); + ClassDB::bind_method(D_METHOD("set_alternative_tile_id", "atlas_coords", "alternative_tile", "new_id"), &TileSetAtlasSource::set_alternative_tile_id); + ClassDB::bind_method(D_METHOD("get_next_alternative_tile_id", "atlas_coords"), &TileSetAtlasSource::get_next_alternative_tile_id); + + ClassDB::bind_method(D_METHOD("get_tile_data", "atlas_coords", "alternative_tile"), &TileSetAtlasSource::get_tile_data); + + // Helpers. + ClassDB::bind_method(D_METHOD("get_atlas_grid_size"), &TileSetAtlasSource::get_atlas_grid_size); + ClassDB::bind_method(D_METHOD("get_tile_texture_region", "atlas_coords", "frame"), &TileSetAtlasSource::get_tile_texture_region, DEFVAL(0)); + + // Getters for texture and tile region (padded or not) + ClassDB::bind_method(D_METHOD("get_runtime_texture"), &TileSetAtlasSource::get_runtime_texture); + ClassDB::bind_method(D_METHOD("get_runtime_tile_texture_region", "atlas_coords", "frame"), &TileSetAtlasSource::get_runtime_tile_texture_region); + + BIND_ENUM_CONSTANT(TILE_ANIMATION_MODE_DEFAULT) + BIND_ENUM_CONSTANT(TILE_ANIMATION_MODE_RANDOM_START_TIMES) + BIND_ENUM_CONSTANT(TILE_ANIMATION_MODE_MAX) + + BIND_CONSTANT(TRANSFORM_FLIP_H) + BIND_CONSTANT(TRANSFORM_FLIP_V) + BIND_CONSTANT(TRANSFORM_TRANSPOSE) +} + +TileSetAtlasSource::~TileSetAtlasSource() { + // Free everything needed. + for (KeyValue &E_alternatives : tiles) { + for (KeyValue &E_tile_data : E_alternatives.value.alternatives) { + memdelete(E_tile_data.value); + } + } +} + +TileData *TileSetAtlasSource::_get_atlas_tile_data(Vector2i p_atlas_coords, int p_alternative_tile) { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), nullptr, vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + p_alternative_tile = alternative_no_transform(p_alternative_tile); + ERR_FAIL_COND_V_MSG(!tiles[p_atlas_coords].alternatives.has(p_alternative_tile), nullptr, vformat("TileSetAtlasSource has no alternative with id %d for tile coords %s.", p_alternative_tile, String(p_atlas_coords))); + + return tiles[p_atlas_coords].alternatives[p_alternative_tile]; +} + +const TileData *TileSetAtlasSource::_get_atlas_tile_data(Vector2i p_atlas_coords, int p_alternative_tile) const { + ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), nullptr, vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + ERR_FAIL_COND_V_MSG(!tiles[p_atlas_coords].alternatives.has(p_alternative_tile), nullptr, vformat("TileSetAtlasSource has no alternative with id %d for tile coords %s.", p_alternative_tile, String(p_atlas_coords))); + + return tiles[p_atlas_coords].alternatives[p_alternative_tile]; +} + +void TileSetAtlasSource::_compute_next_alternative_id(const Vector2i p_atlas_coords) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); + + while (tiles[p_atlas_coords].alternatives.has(tiles[p_atlas_coords].next_alternative_id)) { + tiles[p_atlas_coords].next_alternative_id = (tiles[p_atlas_coords].next_alternative_id % 1073741823) + 1; // 2 ** 30 + }; +} + +void TileSetAtlasSource::_clear_coords_mapping_cache(Vector2i p_atlas_coords) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + TileAlternativesData &tad = tiles[p_atlas_coords]; + for (int frame = 0; frame < (int)tad.animation_frames_durations.size(); frame++) { + Vector2i frame_coords = p_atlas_coords + (tad.size_in_atlas + tad.animation_separation) * ((tad.animation_columns > 0) ? Vector2i(frame % tad.animation_columns, frame / tad.animation_columns) : Vector2i(frame, 0)); + for (int x = 0; x < tad.size_in_atlas.x; x++) { + for (int y = 0; y < tad.size_in_atlas.y; y++) { + Vector2i coords = frame_coords + Vector2i(x, y); + if (!_coords_mapping_cache.has(coords)) { + WARN_PRINT(vformat("TileSetAtlasSource has no cached tile at position %s, the position cache might be corrupted.", coords)); + } else { + if (_coords_mapping_cache[coords] != p_atlas_coords) { + WARN_PRINT(vformat("The position cache at position %s is pointing to a wrong tile, the position cache might be corrupted.", coords)); + } + _coords_mapping_cache.erase(coords); + } + } + } + } +} + +void TileSetAtlasSource::_create_coords_mapping_cache(Vector2i p_atlas_coords) { + ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); + + TileAlternativesData &tad = tiles[p_atlas_coords]; + for (int frame = 0; frame < (int)tad.animation_frames_durations.size(); frame++) { + Vector2i frame_coords = p_atlas_coords + (tad.size_in_atlas + tad.animation_separation) * ((tad.animation_columns > 0) ? Vector2i(frame % tad.animation_columns, frame / tad.animation_columns) : Vector2i(frame, 0)); + for (int x = 0; x < tad.size_in_atlas.x; x++) { + for (int y = 0; y < tad.size_in_atlas.y; y++) { + Vector2i coords = frame_coords + Vector2i(x, y); + if (_coords_mapping_cache.has(coords)) { + WARN_PRINT(vformat("The cache already has a tile for position %s, the position cache might be corrupted.", coords)); + } + _coords_mapping_cache[coords] = p_atlas_coords; + } + } + } +} + +void TileSetAtlasSource::_queue_update_padded_texture() { + padded_texture_needs_update = true; + callable_mp(this, &TileSetAtlasSource::_update_padded_texture).call_deferred(); +} + +Ref TileSetAtlasSource::_create_padded_image_texture(const Ref &p_source) { + ERR_FAIL_COND_V(p_source.is_null(), Ref()); + + Ref src_image = p_source->get_image(); + if (src_image.is_null()) { + Ref ret; + ret.instantiate(); + return ret; + } + + Size2 size = get_atlas_grid_size() * (texture_region_size + Vector2i(2, 2)); + Ref image = Image::create_empty(size.x, size.y, false, src_image->get_format()); + + for (KeyValue kv : tiles) { + for (int frame = 0; frame < (int)kv.value.animation_frames_durations.size(); frame++) { + // Compute the source rects. + Rect2i src_rect = get_tile_texture_region(kv.key, frame); + + Rect2i top_src_rect = Rect2i(src_rect.position, Vector2i(src_rect.size.x, 1)); + Rect2i bottom_src_rect = Rect2i(src_rect.position + Vector2i(0, src_rect.size.y - 1), Vector2i(src_rect.size.x, 1)); + Rect2i left_src_rect = Rect2i(src_rect.position, Vector2i(1, src_rect.size.y)); + Rect2i right_src_rect = Rect2i(src_rect.position + Vector2i(src_rect.size.x - 1, 0), Vector2i(1, src_rect.size.y)); + + // Copy the tile and the paddings. + Vector2i frame_coords = kv.key + (kv.value.size_in_atlas + kv.value.animation_separation) * ((kv.value.animation_columns > 0) ? Vector2i(frame % kv.value.animation_columns, frame / kv.value.animation_columns) : Vector2i(frame, 0)); + Vector2i base_pos = frame_coords * (texture_region_size + Vector2i(2, 2)) + Vector2i(1, 1); + + image->blit_rect(*src_image, src_rect, base_pos); + + // Sides + image->blit_rect(*src_image, top_src_rect, base_pos + Vector2i(0, -1)); + image->blit_rect(*src_image, bottom_src_rect, base_pos + Vector2i(0, src_rect.size.y)); + image->blit_rect(*src_image, left_src_rect, base_pos + Vector2i(-1, 0)); + image->blit_rect(*src_image, right_src_rect, base_pos + Vector2i(src_rect.size.x, 0)); + + // Corners + image->blit_rect(*src_image, Rect2i(src_rect.position, Vector2i(1, 1)), base_pos + Vector2i(-1, -1)); + image->blit_rect(*src_image, Rect2i(src_rect.position + Vector2i(src_rect.size.x - 1, 0), Vector2i(1, 1)), base_pos + Vector2i(src_rect.size.x, -1)); + image->blit_rect(*src_image, Rect2i(src_rect.position + Vector2i(0, src_rect.size.y - 1), Vector2i(1, 1)), base_pos + Vector2i(-1, src_rect.size.y)); + image->blit_rect(*src_image, Rect2i(src_rect.position + Vector2i(src_rect.size.x - 1, src_rect.size.y - 1), Vector2i(1, 1)), base_pos + Vector2i(src_rect.size.x, src_rect.size.y)); + } + } + + return ImageTexture::create_from_image(image); +} + +void TileSetAtlasSource::_update_padded_texture() { + if (!padded_texture_needs_update) { + return; + } + padded_texture_needs_update = false; + + if (padded_texture.is_valid()) { + padded_texture->disconnect_changed(callable_mp(this, &TileSetAtlasSource::_queue_update_padded_texture)); + } + + padded_texture = Ref(); + + if (texture.is_null()) { + return; + } + + if (!use_texture_padding) { + return; + } + + padded_texture.instantiate(); + + Ref src_canvas_texture = texture; + if (src_canvas_texture.is_valid()) { + // Use all textures. + // Diffuse + Ref src = src_canvas_texture->get_diffuse_texture(); + Ref image_texture; + if (src.is_valid()) { + padded_texture->set_diffuse_texture(_create_padded_image_texture(src)); + } + + // Normal + src = src_canvas_texture->get_normal_texture(); + if (src.is_valid()) { + padded_texture->set_normal_texture(_create_padded_image_texture(src)); + } + + // Specular + src = src_canvas_texture->get_specular_texture(); + if (src.is_valid()) { + padded_texture->set_specular_texture(_create_padded_image_texture(src)); + } + + // Other properties. + padded_texture->set_specular_color(src_canvas_texture->get_specular_color()); + padded_texture->set_specular_shininess(src_canvas_texture->get_specular_shininess()); + padded_texture->set_texture_filter(src_canvas_texture->get_texture_filter()); + padded_texture->set_texture_repeat(src_canvas_texture->get_texture_repeat()); + } else { + // Use only diffuse. + Ref image_texture = _create_padded_image_texture(texture); + padded_texture->set_diffuse_texture(image_texture); + } + padded_texture->connect_changed(callable_mp(this, &TileSetAtlasSource::_queue_update_padded_texture)); + emit_changed(); +} + +/////////////////////////////// TileSetScenesCollectionSource ////////////////////////////////////// + +void TileSetScenesCollectionSource::_compute_next_alternative_id() { + while (scenes.has(next_scene_id)) { + next_scene_id = (next_scene_id % 1073741823) + 1; // 2 ** 30 + }; +} + +int TileSetScenesCollectionSource::get_tiles_count() const { + return 1; +} + +Vector2i TileSetScenesCollectionSource::get_tile_id(int p_tile_index) const { + ERR_FAIL_COND_V(p_tile_index != 0, TileSetSource::INVALID_ATLAS_COORDS); + return Vector2i(); +} + +bool TileSetScenesCollectionSource::has_tile(Vector2i p_atlas_coords) const { + return p_atlas_coords == Vector2i(); +} + +int TileSetScenesCollectionSource::get_alternative_tiles_count(const Vector2i p_atlas_coords) const { + return scenes_ids.size(); +} + +int TileSetScenesCollectionSource::get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const { + ERR_FAIL_COND_V(p_atlas_coords != Vector2i(), TileSetSource::INVALID_TILE_ALTERNATIVE); + ERR_FAIL_INDEX_V(p_index, scenes_ids.size(), TileSetSource::INVALID_TILE_ALTERNATIVE); + + return scenes_ids[p_index]; +} + +bool TileSetScenesCollectionSource::has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const { + ERR_FAIL_COND_V(p_atlas_coords != Vector2i(), false); + return scenes.has(p_alternative_tile); +} + +int TileSetScenesCollectionSource::create_scene_tile(Ref p_packed_scene, int p_id_override) { + ERR_FAIL_COND_V_MSG(p_id_override >= 0 && scenes.has(p_id_override), INVALID_TILE_ALTERNATIVE, vformat("Cannot create scene tile. Another scene tile exists with id %d.", p_id_override)); + + int new_scene_id = p_id_override >= 0 ? p_id_override : next_scene_id; + + scenes[new_scene_id] = SceneData(); + scenes_ids.push_back(new_scene_id); + scenes_ids.sort(); + set_scene_tile_scene(new_scene_id, p_packed_scene); + _compute_next_alternative_id(); + + emit_signal(SNAME("changed")); + + return new_scene_id; +} + +void TileSetScenesCollectionSource::set_scene_tile_id(int p_id, int p_new_id) { + ERR_FAIL_COND(p_new_id < 0); + ERR_FAIL_COND(!has_scene_tile_id(p_id)); + ERR_FAIL_COND(has_scene_tile_id(p_new_id)); + + scenes[p_new_id] = SceneData(); + scenes[p_new_id] = scenes[p_id]; + scenes_ids.push_back(p_new_id); + scenes_ids.sort(); + + _compute_next_alternative_id(); + + scenes.erase(p_id); + scenes_ids.erase(p_id); + + emit_signal(SNAME("changed")); +} + +void TileSetScenesCollectionSource::set_scene_tile_scene(int p_id, Ref p_packed_scene) { + ERR_FAIL_COND(!scenes.has(p_id)); + if (p_packed_scene.is_valid()) { + // Check if it extends CanvasItem. + Ref scene_state = p_packed_scene->get_state(); + String type; + while (scene_state.is_valid() && type.is_empty()) { + // Make sure we have a root node. Supposed to be at 0 index because find_node_by_path() does not seem to work. + ERR_FAIL_COND(scene_state->get_node_count() < 1); + + type = scene_state->get_node_type(0); + scene_state = scene_state->get_base_scene_state(); + } + ERR_FAIL_COND_EDMSG(type.is_empty(), vformat("Invalid PackedScene for TileSetScenesCollectionSource: %s. Could not get the type of the root node.", p_packed_scene->get_path())); + bool extends_correct_class = ClassDB::is_parent_class(type, "CanvasItem"); + ERR_FAIL_COND_EDMSG(!extends_correct_class, vformat("Invalid PackedScene for TileSetScenesCollectionSource: %s. Root node should extend CanvasItem. Found %s instead.", p_packed_scene->get_path(), type)); + + scenes[p_id].scene = p_packed_scene; + } else { + scenes[p_id].scene = Ref(); + } + emit_signal(SNAME("changed")); +} + +Ref TileSetScenesCollectionSource::get_scene_tile_scene(int p_id) const { + ERR_FAIL_COND_V(!scenes.has(p_id), Ref()); + return scenes[p_id].scene; +} + +void TileSetScenesCollectionSource::set_scene_tile_display_placeholder(int p_id, bool p_display_placeholder) { + ERR_FAIL_COND(!scenes.has(p_id)); + + scenes[p_id].display_placeholder = p_display_placeholder; + + emit_signal(SNAME("changed")); +} + +bool TileSetScenesCollectionSource::get_scene_tile_display_placeholder(int p_id) const { + ERR_FAIL_COND_V(!scenes.has(p_id), false); + return scenes[p_id].display_placeholder; +} + +void TileSetScenesCollectionSource::remove_scene_tile(int p_id) { + ERR_FAIL_COND(!scenes.has(p_id)); + + scenes.erase(p_id); + scenes_ids.erase(p_id); + emit_signal(SNAME("changed")); +} + +int TileSetScenesCollectionSource::get_next_scene_tile_id() const { + return next_scene_id; +} + +bool TileSetScenesCollectionSource::_set(const StringName &p_name, const Variant &p_value) { + Vector components = String(p_name).split("/", true, 2); + + if (components.size() >= 2 && components[0] == "scenes" && components[1].is_valid_int()) { + int scene_id = components[1].to_int(); + if (components.size() >= 3 && components[2] == "scene") { + if (has_scene_tile_id(scene_id)) { + set_scene_tile_scene(scene_id, p_value); + } else { + create_scene_tile(p_value, scene_id); + } + return true; + } else if (components.size() >= 3 && components[2] == "display_placeholder") { + if (!has_scene_tile_id(scene_id)) { + create_scene_tile(p_value, scene_id); + } + + return true; + } + } + + return false; +} + +bool TileSetScenesCollectionSource::_get(const StringName &p_name, Variant &r_ret) const { + Vector components = String(p_name).split("/", true, 2); + + if (components.size() >= 2 && components[0] == "scenes" && components[1].is_valid_int() && scenes.has(components[1].to_int())) { + if (components.size() >= 3 && components[2] == "scene") { + r_ret = scenes[components[1].to_int()].scene; + return true; + } else if (components.size() >= 3 && components[2] == "display_placeholder") { + r_ret = scenes[components[1].to_int()].scene; + return true; + } + } + + return false; +} + +void TileSetScenesCollectionSource::_get_property_list(List *p_list) const { + for (int i = 0; i < scenes_ids.size(); i++) { + p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("%s/%d/%s", PNAME("scenes"), scenes_ids[i], PNAME("scene")), PROPERTY_HINT_RESOURCE_TYPE, "TileSetScenesCollectionSource")); + + PropertyInfo property_info = PropertyInfo(Variant::BOOL, vformat("%s/%d/%s", PNAME("scenes"), scenes_ids[i], PNAME("display_placeholder"))); + if (scenes[scenes_ids[i]].display_placeholder == false) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + p_list->push_back(property_info); + } +} + +void TileSetScenesCollectionSource::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_scene_tiles_count"), &TileSetScenesCollectionSource::get_scene_tiles_count); + ClassDB::bind_method(D_METHOD("get_scene_tile_id", "index"), &TileSetScenesCollectionSource::get_scene_tile_id); + ClassDB::bind_method(D_METHOD("has_scene_tile_id", "id"), &TileSetScenesCollectionSource::has_scene_tile_id); + ClassDB::bind_method(D_METHOD("create_scene_tile", "packed_scene", "id_override"), &TileSetScenesCollectionSource::create_scene_tile, DEFVAL(INVALID_TILE_ALTERNATIVE)); + ClassDB::bind_method(D_METHOD("set_scene_tile_id", "id", "new_id"), &TileSetScenesCollectionSource::set_scene_tile_id); + ClassDB::bind_method(D_METHOD("set_scene_tile_scene", "id", "packed_scene"), &TileSetScenesCollectionSource::set_scene_tile_scene); + ClassDB::bind_method(D_METHOD("get_scene_tile_scene", "id"), &TileSetScenesCollectionSource::get_scene_tile_scene); + ClassDB::bind_method(D_METHOD("set_scene_tile_display_placeholder", "id", "display_placeholder"), &TileSetScenesCollectionSource::set_scene_tile_display_placeholder); + ClassDB::bind_method(D_METHOD("get_scene_tile_display_placeholder", "id"), &TileSetScenesCollectionSource::get_scene_tile_display_placeholder); + ClassDB::bind_method(D_METHOD("remove_scene_tile", "id"), &TileSetScenesCollectionSource::remove_scene_tile); + ClassDB::bind_method(D_METHOD("get_next_scene_tile_id"), &TileSetScenesCollectionSource::get_next_scene_tile_id); +} + +/////////////////////////////// TileData ////////////////////////////////////// + +void TileData::set_tile_set(const TileSet *p_tile_set) { + tile_set = p_tile_set; + notify_tile_data_properties_should_change(); +} + +void TileData::notify_tile_data_properties_should_change() { + if (!tile_set) { + return; + } + + occluders.resize(tile_set->get_occlusion_layers_count()); + physics.resize(tile_set->get_physics_layers_count()); + for (int bit_index = 0; bit_index < 16; bit_index++) { + if (terrain_set < 0 || terrain_peering_bits[bit_index] >= tile_set->get_terrains_count(terrain_set)) { + terrain_peering_bits[bit_index] = -1; + } + } + navigation.resize(tile_set->get_navigation_layers_count()); + + // Convert custom data to the new type. + custom_data.resize(tile_set->get_custom_data_layers_count()); + for (int i = 0; i < custom_data.size(); i++) { + if (custom_data[i].get_type() != tile_set->get_custom_data_layer_type(i)) { + Variant new_val; + Callable::CallError error; + if (Variant::can_convert(custom_data[i].get_type(), tile_set->get_custom_data_layer_type(i))) { + const Variant *args[] = { &custom_data[i] }; + Variant::construct(tile_set->get_custom_data_layer_type(i), new_val, args, 1, error); + } else { + Variant::construct(tile_set->get_custom_data_layer_type(i), new_val, nullptr, 0, error); + } + custom_data.write[i] = new_val; + } + } + + notify_property_list_changed(); + emit_signal(SNAME("changed")); +} + +void TileData::add_occlusion_layer(int p_to_pos) { + if (p_to_pos < 0) { + p_to_pos = occluders.size(); + } + ERR_FAIL_INDEX(p_to_pos, occluders.size() + 1); + occluders.insert(p_to_pos, OcclusionLayerTileData()); +} + +void TileData::move_occlusion_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, occluders.size()); + ERR_FAIL_INDEX(p_to_pos, occluders.size() + 1); + occluders.insert(p_to_pos, occluders[p_from_index]); + occluders.remove_at(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); +} + +void TileData::remove_occlusion_layer(int p_index) { + ERR_FAIL_INDEX(p_index, occluders.size()); + occluders.remove_at(p_index); +} + +void TileData::add_physics_layer(int p_to_pos) { + if (p_to_pos < 0) { + p_to_pos = physics.size(); + } + ERR_FAIL_INDEX(p_to_pos, physics.size() + 1); + physics.insert(p_to_pos, PhysicsLayerTileData()); +} + +void TileData::move_physics_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, physics.size()); + ERR_FAIL_INDEX(p_to_pos, physics.size() + 1); + physics.insert(p_to_pos, physics[p_from_index]); + physics.remove_at(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); +} + +void TileData::remove_physics_layer(int p_index) { + ERR_FAIL_INDEX(p_index, physics.size()); + physics.remove_at(p_index); +} + +void TileData::add_terrain_set(int p_to_pos) { + if (p_to_pos >= 0 && p_to_pos <= terrain_set) { + terrain_set += 1; + } +} + +void TileData::move_terrain_set(int p_from_index, int p_to_pos) { + if (p_from_index == terrain_set) { + terrain_set = (p_from_index < p_to_pos) ? p_to_pos - 1 : p_to_pos; + } else { + if (p_from_index < terrain_set) { + terrain_set -= 1; + } + if (p_to_pos <= terrain_set) { + terrain_set += 1; + } + } +} + +void TileData::remove_terrain_set(int p_index) { + if (p_index == terrain_set) { + terrain_set = -1; + for (int i = 0; i < 16; i++) { + terrain_peering_bits[i] = -1; + } + } else if (terrain_set > p_index) { + terrain_set -= 1; + } +} + +void TileData::add_terrain(int p_terrain_set, int p_to_pos) { + if (terrain_set == p_terrain_set) { + for (int i = 0; i < 16; i++) { + if (p_to_pos >= 0 && p_to_pos <= terrain_peering_bits[i]) { + terrain_peering_bits[i] += 1; + } + } + } +} + +void TileData::move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) { + if (terrain_set == p_terrain_set) { + for (int i = 0; i < 16; i++) { + if (p_from_index == terrain_peering_bits[i]) { + terrain_peering_bits[i] = (p_from_index < p_to_pos) ? p_to_pos - 1 : p_to_pos; + } else { + if (p_from_index < terrain_peering_bits[i]) { + terrain_peering_bits[i] -= 1; + } + if (p_to_pos <= terrain_peering_bits[i]) { + terrain_peering_bits[i] += 1; + } + } + } + } +} + +void TileData::remove_terrain(int p_terrain_set, int p_index) { + if (terrain_set == p_terrain_set) { + if (terrain == p_index) { + terrain = -1; + } else if (terrain > p_index) { + terrain -= 1; + } + + for (int i = 0; i < 16; i++) { + if (terrain_peering_bits[i] == p_index) { + terrain_peering_bits[i] = -1; + } else if (terrain_peering_bits[i] > p_index) { + terrain_peering_bits[i] -= 1; + } + } + } +} + +void TileData::add_navigation_layer(int p_to_pos) { + if (p_to_pos < 0) { + p_to_pos = navigation.size(); + } + ERR_FAIL_INDEX(p_to_pos, navigation.size() + 1); + navigation.insert(p_to_pos, NavigationLayerTileData()); +} + +void TileData::move_navigation_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, navigation.size()); + ERR_FAIL_INDEX(p_to_pos, navigation.size() + 1); + navigation.insert(p_to_pos, navigation[p_from_index]); + navigation.remove_at(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); +} + +void TileData::remove_navigation_layer(int p_index) { + ERR_FAIL_INDEX(p_index, navigation.size()); + navigation.remove_at(p_index); +} + +void TileData::add_custom_data_layer(int p_to_pos) { + if (p_to_pos < 0) { + p_to_pos = custom_data.size(); + } + ERR_FAIL_INDEX(p_to_pos, custom_data.size() + 1); + custom_data.insert(p_to_pos, Variant()); +} + +void TileData::move_custom_data_layer(int p_from_index, int p_to_pos) { + ERR_FAIL_INDEX(p_from_index, custom_data.size()); + ERR_FAIL_INDEX(p_to_pos, custom_data.size() + 1); + custom_data.insert(p_to_pos, custom_data[p_from_index]); + custom_data.remove_at(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); +} + +void TileData::remove_custom_data_layer(int p_index) { + ERR_FAIL_INDEX(p_index, custom_data.size()); + custom_data.remove_at(p_index); +} + +void TileData::set_allow_transform(bool p_allow_transform) { + allow_transform = p_allow_transform; +} + +bool TileData::is_allowing_transform() const { + return allow_transform; +} + +TileData *TileData::duplicate() { + TileData *output = memnew(TileData); + output->tile_set = tile_set; + + output->allow_transform = allow_transform; + + // Rendering + output->flip_h = flip_h; + output->flip_v = flip_v; + output->transpose = transpose; + output->texture_origin = texture_origin; + output->material = material; + output->modulate = modulate; + output->z_index = z_index; + output->y_sort_origin = y_sort_origin; + output->occluders = occluders; + // Physics + output->physics = physics; + // Terrain + output->terrain_set = -1; + memcpy(output->terrain_peering_bits, terrain_peering_bits, 16 * sizeof(int)); + // Navigation + output->navigation = navigation; + // Misc + output->probability = probability; + // Custom data + output->custom_data = custom_data; + + return output; +} + +// Rendering +void TileData::set_flip_h(bool p_flip_h) { + ERR_FAIL_COND_MSG(!allow_transform && p_flip_h, "Transform is only allowed for alternative tiles (with its alternative_id != 0)"); + flip_h = p_flip_h; + emit_signal(SNAME("changed")); +} +bool TileData::get_flip_h() const { + return flip_h; +} + +void TileData::set_flip_v(bool p_flip_v) { + ERR_FAIL_COND_MSG(!allow_transform && p_flip_v, "Transform is only allowed for alternative tiles (with its alternative_id != 0)"); + flip_v = p_flip_v; + emit_signal(SNAME("changed")); +} + +bool TileData::get_flip_v() const { + return flip_v; +} + +void TileData::set_transpose(bool p_transpose) { + ERR_FAIL_COND_MSG(!allow_transform && p_transpose, "Transform is only allowed for alternative tiles (with its alternative_id != 0)"); + transpose = p_transpose; + emit_signal(SNAME("changed")); +} +bool TileData::get_transpose() const { + return transpose; +} + +void TileData::set_texture_origin(Vector2i p_texture_origin) { + texture_origin = p_texture_origin; + emit_signal(SNAME("changed")); +} + +Vector2i TileData::get_texture_origin() const { + return texture_origin; +} + +void TileData::set_material(Ref p_material) { + material = p_material; + emit_signal(SNAME("changed")); +} +Ref TileData::get_material() const { + return material; +} + +void TileData::set_modulate(Color p_modulate) { + modulate = p_modulate; + emit_signal(SNAME("changed")); +} +Color TileData::get_modulate() const { + return modulate; +} + +void TileData::set_z_index(int p_z_index) { + z_index = p_z_index; + emit_signal(SNAME("changed")); +} +int TileData::get_z_index() const { + return z_index; +} + +void TileData::set_y_sort_origin(int p_y_sort_origin) { + y_sort_origin = p_y_sort_origin; + emit_signal(SNAME("changed")); +} +int TileData::get_y_sort_origin() const { + return y_sort_origin; +} + +void TileData::set_occluder(int p_layer_id, Ref p_occluder_polygon) { + ERR_FAIL_INDEX(p_layer_id, occluders.size()); + occluders.write[p_layer_id].occluder = p_occluder_polygon; + occluders.write[p_layer_id].transformed_occluders.clear(); + emit_signal(SNAME("changed")); +} + +Ref TileData::get_occluder(int p_layer_id, bool p_flip_h, bool p_flip_v, bool p_transpose) const { + ERR_FAIL_INDEX_V(p_layer_id, occluders.size(), Ref()); + + const OcclusionLayerTileData &layer_tile_data = occluders[p_layer_id]; + + int key = int(p_flip_h) | int(p_flip_v) << 1 | int(p_transpose) << 2; + if (key == 0) { + return layer_tile_data.occluder; + } + + if (layer_tile_data.occluder.is_null()) { + return Ref(); + } + + HashMap>::Iterator I = layer_tile_data.transformed_occluders.find(key); + if (!I) { + Ref transformed_polygon; + transformed_polygon.instantiate(); + transformed_polygon->set_polygon(get_transformed_vertices(layer_tile_data.occluder->get_polygon(), p_flip_h, p_flip_v, p_transpose)); + layer_tile_data.transformed_occluders[key] = transformed_polygon; + return transformed_polygon; + } else { + return I->value; + } +} + +// Physics +void TileData::set_constant_linear_velocity(int p_layer_id, const Vector2 &p_velocity) { + ERR_FAIL_INDEX(p_layer_id, physics.size()); + physics.write[p_layer_id].linear_velocity = p_velocity; + emit_signal(SNAME("changed")); +} + +Vector2 TileData::get_constant_linear_velocity(int p_layer_id) const { + ERR_FAIL_INDEX_V(p_layer_id, physics.size(), Vector2()); + return physics[p_layer_id].linear_velocity; +} + +void TileData::set_constant_angular_velocity(int p_layer_id, real_t p_velocity) { + ERR_FAIL_INDEX(p_layer_id, physics.size()); + physics.write[p_layer_id].angular_velocity = p_velocity; + emit_signal(SNAME("changed")); +} + +real_t TileData::get_constant_angular_velocity(int p_layer_id) const { + ERR_FAIL_INDEX_V(p_layer_id, physics.size(), 0.0); + return physics[p_layer_id].angular_velocity; +} + +void TileData::set_collision_polygons_count(int p_layer_id, int p_polygons_count) { + ERR_FAIL_INDEX(p_layer_id, physics.size()); + ERR_FAIL_COND(p_polygons_count < 0); + if (p_polygons_count == physics.write[p_layer_id].polygons.size()) { + return; + } + physics.write[p_layer_id].polygons.resize(p_polygons_count); + notify_property_list_changed(); + emit_signal(SNAME("changed")); +} + +int TileData::get_collision_polygons_count(int p_layer_id) const { + ERR_FAIL_INDEX_V(p_layer_id, physics.size(), 0); + return physics[p_layer_id].polygons.size(); +} + +void TileData::add_collision_polygon(int p_layer_id) { + ERR_FAIL_INDEX(p_layer_id, physics.size()); + physics.write[p_layer_id].polygons.push_back(PhysicsLayerTileData::PolygonShapeTileData()); + emit_signal(SNAME("changed")); +} + +void TileData::remove_collision_polygon(int p_layer_id, int p_polygon_index) { + ERR_FAIL_INDEX(p_layer_id, physics.size()); + ERR_FAIL_INDEX(p_polygon_index, physics[p_layer_id].polygons.size()); + physics.write[p_layer_id].polygons.remove_at(p_polygon_index); + emit_signal(SNAME("changed")); +} + +void TileData::set_collision_polygon_points(int p_layer_id, int p_polygon_index, Vector p_polygon) { + ERR_FAIL_INDEX(p_layer_id, physics.size()); + ERR_FAIL_INDEX(p_polygon_index, physics[p_layer_id].polygons.size()); + ERR_FAIL_COND_MSG(p_polygon.size() != 0 && p_polygon.size() < 3, "Invalid polygon. Needs either 0 or more than 3 points."); + + TileData::PhysicsLayerTileData::PolygonShapeTileData &polygon_shape_tile_data = physics.write[p_layer_id].polygons.write[p_polygon_index]; + + if (p_polygon.is_empty()) { + polygon_shape_tile_data.shapes.clear(); + } else { + // Decompose into convex shapes. + Vector> decomp = Geometry2D::decompose_polygon_in_convex(p_polygon); + ERR_FAIL_COND_MSG(decomp.is_empty(), "Could not decompose the polygon into convex shapes."); + + polygon_shape_tile_data.shapes.resize(decomp.size()); + for (int i = 0; i < decomp.size(); i++) { + Ref shape; + shape.instantiate(); + shape->set_points(decomp[i]); + polygon_shape_tile_data.shapes[i] = shape; + } + } + polygon_shape_tile_data.transformed_shapes.clear(); + polygon_shape_tile_data.polygon = p_polygon; + emit_signal(SNAME("changed")); +} + +Vector TileData::get_collision_polygon_points(int p_layer_id, int p_polygon_index) const { + ERR_FAIL_INDEX_V(p_layer_id, physics.size(), Vector()); + ERR_FAIL_INDEX_V(p_polygon_index, physics[p_layer_id].polygons.size(), Vector()); + return physics[p_layer_id].polygons[p_polygon_index].polygon; +} + +void TileData::set_collision_polygon_one_way(int p_layer_id, int p_polygon_index, bool p_one_way) { + ERR_FAIL_INDEX(p_layer_id, physics.size()); + ERR_FAIL_INDEX(p_polygon_index, physics[p_layer_id].polygons.size()); + physics.write[p_layer_id].polygons.write[p_polygon_index].one_way = p_one_way; + emit_signal(SNAME("changed")); +} + +bool TileData::is_collision_polygon_one_way(int p_layer_id, int p_polygon_index) const { + ERR_FAIL_INDEX_V(p_layer_id, physics.size(), false); + ERR_FAIL_INDEX_V(p_polygon_index, physics[p_layer_id].polygons.size(), false); + return physics[p_layer_id].polygons[p_polygon_index].one_way; +} + +void TileData::set_collision_polygon_one_way_margin(int p_layer_id, int p_polygon_index, float p_one_way_margin) { + ERR_FAIL_INDEX(p_layer_id, physics.size()); + ERR_FAIL_INDEX(p_polygon_index, physics[p_layer_id].polygons.size()); + physics.write[p_layer_id].polygons.write[p_polygon_index].one_way_margin = p_one_way_margin; + emit_signal(SNAME("changed")); +} + +float TileData::get_collision_polygon_one_way_margin(int p_layer_id, int p_polygon_index) const { + ERR_FAIL_INDEX_V(p_layer_id, physics.size(), 0.0); + ERR_FAIL_INDEX_V(p_polygon_index, physics[p_layer_id].polygons.size(), 0.0); + return physics[p_layer_id].polygons[p_polygon_index].one_way_margin; +} + +int TileData::get_collision_polygon_shapes_count(int p_layer_id, int p_polygon_index) const { + ERR_FAIL_INDEX_V(p_layer_id, physics.size(), 0); + ERR_FAIL_INDEX_V(p_polygon_index, physics[p_layer_id].polygons.size(), 0); + return physics[p_layer_id].polygons[p_polygon_index].shapes.size(); +} + +Ref TileData::get_collision_polygon_shape(int p_layer_id, int p_polygon_index, int shape_index, bool p_flip_h, bool p_flip_v, bool p_transpose) const { + ERR_FAIL_INDEX_V(p_layer_id, physics.size(), Ref()); + ERR_FAIL_INDEX_V(p_polygon_index, physics[p_layer_id].polygons.size(), Ref()); + ERR_FAIL_INDEX_V(shape_index, (int)physics[p_layer_id].polygons[p_polygon_index].shapes.size(), Ref()); + + const PhysicsLayerTileData &layer_tile_data = physics[p_layer_id]; + const PhysicsLayerTileData::PolygonShapeTileData &shapes_data = layer_tile_data.polygons[p_polygon_index]; + + int key = int(p_flip_h) | int(p_flip_v) << 1 | int(p_transpose) << 2; + if (key == 0) { + return shapes_data.shapes[shape_index]; + } + if (shapes_data.shapes[shape_index].is_null()) { + return Ref(); + } + + HashMap>>::Iterator I = shapes_data.transformed_shapes.find(key); + if (!I) { + int size = shapes_data.shapes.size(); + shapes_data.transformed_shapes[key].resize(size); + for (int i = 0; i < size; i++) { + Ref transformed_polygon; + transformed_polygon.instantiate(); + transformed_polygon->set_points(get_transformed_vertices(shapes_data.shapes[shape_index]->get_points(), p_flip_h, p_flip_v, p_transpose)); + shapes_data.transformed_shapes[key][i] = transformed_polygon; + } + return shapes_data.transformed_shapes[key][shape_index]; + } else { + return I->value[shape_index]; + } +} + +// Terrain +void TileData::set_terrain_set(int p_terrain_set) { + ERR_FAIL_COND(p_terrain_set < -1); + if (p_terrain_set == terrain_set) { + return; + } + if (tile_set) { + ERR_FAIL_COND(p_terrain_set >= tile_set->get_terrain_sets_count()); + terrain = -1; + for (int i = 0; i < 16; i++) { + terrain_peering_bits[i] = -1; + } + } + terrain_set = p_terrain_set; + notify_property_list_changed(); + emit_signal(SNAME("changed")); +} + +int TileData::get_terrain_set() const { + return terrain_set; +} + +void TileData::set_terrain(int p_terrain) { + ERR_FAIL_COND(terrain_set < 0); + ERR_FAIL_COND(p_terrain < -1); + if (tile_set) { + ERR_FAIL_COND(p_terrain >= tile_set->get_terrains_count(terrain_set)); + } + terrain = p_terrain; + emit_signal(SNAME("changed")); +} + +int TileData::get_terrain() const { + return terrain; +} + +void TileData::set_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit, int p_terrain_index) { + ERR_FAIL_INDEX(p_peering_bit, TileSet::CellNeighbor::CELL_NEIGHBOR_MAX); + ERR_FAIL_COND(terrain_set < 0); + ERR_FAIL_COND(p_terrain_index < -1); + if (tile_set) { + ERR_FAIL_COND(p_terrain_index >= tile_set->get_terrains_count(terrain_set)); + ERR_FAIL_COND(!is_valid_terrain_peering_bit(p_peering_bit)); + } + terrain_peering_bits[p_peering_bit] = p_terrain_index; + emit_signal(SNAME("changed")); +} + +int TileData::get_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit) const { + ERR_FAIL_COND_V(!is_valid_terrain_peering_bit(p_peering_bit), -1); + return terrain_peering_bits[p_peering_bit]; +} + +bool TileData::is_valid_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit) const { + ERR_FAIL_NULL_V(tile_set, false); + + return tile_set->is_valid_terrain_peering_bit(terrain_set, p_peering_bit); +} + +TileSet::TerrainsPattern TileData::get_terrains_pattern() const { + ERR_FAIL_NULL_V(tile_set, TileSet::TerrainsPattern()); + + TileSet::TerrainsPattern output(tile_set, terrain_set); + output.set_terrain(terrain); + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + if (tile_set->is_valid_terrain_peering_bit(terrain_set, TileSet::CellNeighbor(i))) { + output.set_terrain_peering_bit(TileSet::CellNeighbor(i), get_terrain_peering_bit(TileSet::CellNeighbor(i))); + } + } + return output; +} + +// Navigation +void TileData::set_navigation_polygon(int p_layer_id, Ref p_navigation_polygon) { + ERR_FAIL_INDEX(p_layer_id, navigation.size()); + navigation.write[p_layer_id].navigation_polygon = p_navigation_polygon; + navigation.write[p_layer_id].transformed_navigation_polygon.clear(); + emit_signal(SNAME("changed")); +} + +Ref TileData::get_navigation_polygon(int p_layer_id, bool p_flip_h, bool p_flip_v, bool p_transpose) const { + ERR_FAIL_INDEX_V(p_layer_id, navigation.size(), Ref()); + + const NavigationLayerTileData &layer_tile_data = navigation[p_layer_id]; + + int key = int(p_flip_h) | int(p_flip_v) << 1 | int(p_transpose) << 2; + if (key == 0) { + return layer_tile_data.navigation_polygon; + } + + if (layer_tile_data.navigation_polygon.is_null()) { + return Ref(); + } + + HashMap>::Iterator I = layer_tile_data.transformed_navigation_polygon.find(key); + if (!I) { + Ref transformed_polygon; + transformed_polygon.instantiate(); + + PackedVector2Array new_points = get_transformed_vertices(layer_tile_data.navigation_polygon->get_vertices(), p_flip_h, p_flip_v, p_transpose); + transformed_polygon->set_vertices(new_points); + + for (int i = 0; i < layer_tile_data.navigation_polygon->get_outline_count(); i++) { + PackedVector2Array new_outline = get_transformed_vertices(layer_tile_data.navigation_polygon->get_outline(i), p_flip_h, p_flip_v, p_transpose); + transformed_polygon->add_outline(new_outline); + } + + PackedInt32Array indices; + indices.resize(new_points.size()); + int *w = indices.ptrw(); + for (int i = 0; i < new_points.size(); i++) { + w[i] = i; + } + transformed_polygon->add_polygon(indices); + layer_tile_data.transformed_navigation_polygon[key] = transformed_polygon; + return transformed_polygon; + } else { + return I->value; + } +} + +// Misc +void TileData::set_probability(float p_probability) { + ERR_FAIL_COND(p_probability < 0.0); + probability = p_probability; + emit_signal(SNAME("changed")); +} +float TileData::get_probability() const { + return probability; +} + +// Custom data +void TileData::set_custom_data(String p_layer_name, Variant p_value) { + ERR_FAIL_NULL(tile_set); + int p_layer_id = tile_set->get_custom_data_layer_by_name(p_layer_name); + ERR_FAIL_COND_MSG(p_layer_id < 0, vformat("TileSet has no layer with name: %s", p_layer_name)); + set_custom_data_by_layer_id(p_layer_id, p_value); +} + +Variant TileData::get_custom_data(String p_layer_name) const { + ERR_FAIL_NULL_V(tile_set, Variant()); + int p_layer_id = tile_set->get_custom_data_layer_by_name(p_layer_name); + ERR_FAIL_COND_V_MSG(p_layer_id < 0, Variant(), vformat("TileSet has no layer with name: %s", p_layer_name)); + return get_custom_data_by_layer_id(p_layer_id); +} + +void TileData::set_custom_data_by_layer_id(int p_layer_id, Variant p_value) { + ERR_FAIL_INDEX(p_layer_id, custom_data.size()); + custom_data.write[p_layer_id] = p_value; + emit_signal(SNAME("changed")); +} + +Variant TileData::get_custom_data_by_layer_id(int p_layer_id) const { + ERR_FAIL_INDEX_V(p_layer_id, custom_data.size(), Variant()); + return custom_data[p_layer_id]; +} + +PackedVector2Array TileData::get_transformed_vertices(const PackedVector2Array &p_vertices, bool p_flip_h, bool p_flip_v, bool p_transpose) { + const Vector2 *r = p_vertices.ptr(); + int size = p_vertices.size(); + + PackedVector2Array new_points; + new_points.resize(size); + Vector2 *w = new_points.ptrw(); + + for (int i = 0; i < size; i++) { + Vector2 v; + if (p_transpose) { + v = Vector2(r[i].y, r[i].x); + } else { + v = r[i]; + } + + if (p_flip_h) { + v.x *= -1; + } + if (p_flip_v) { + v.y *= -1; + } + w[i] = v; + } + return new_points; +} + +bool TileData::_set(const StringName &p_name, const Variant &p_value) { +#ifndef DISABLE_DEPRECATED + if (p_name == "texture_offset") { + texture_origin = p_value; + return true; + } +#endif + + Vector components = String(p_name).split("/", true, 2); + + if (components.size() == 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) { + // Occlusion layers. + int layer_index = components[0].trim_prefix("occlusion_layer_").to_int(); + ERR_FAIL_COND_V(layer_index < 0, false); + if (components[1] == "polygon") { + Ref polygon = p_value; + + if (layer_index >= occluders.size()) { + if (tile_set) { + return false; + } else { + occluders.resize(layer_index + 1); + } + } + set_occluder(layer_index, polygon); + return true; + } + } else if (components.size() >= 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) { + // Physics layers. + int layer_index = components[0].trim_prefix("physics_layer_").to_int(); + ERR_FAIL_COND_V(layer_index < 0, false); + if (components.size() == 2) { + if (layer_index >= physics.size()) { + if (tile_set) { + return false; + } else { + physics.resize(layer_index + 1); + } + } + if (components[1] == "linear_velocity") { + set_constant_linear_velocity(layer_index, p_value); + return true; + } else if (components[1] == "angular_velocity") { + set_constant_angular_velocity(layer_index, p_value); + return true; + } else if (components[1] == "polygons_count") { + if (p_value.get_type() != Variant::INT) { + return false; + } + set_collision_polygons_count(layer_index, p_value); + return true; + } + } else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) { + int polygon_index = components[1].trim_prefix("polygon_").to_int(); + ERR_FAIL_COND_V(polygon_index < 0, false); + + if (components[2] == "points" || components[2] == "one_way" || components[2] == "one_way_margin") { + if (layer_index >= physics.size()) { + if (tile_set) { + return false; + } else { + physics.resize(layer_index + 1); + } + } + + if (polygon_index >= physics[layer_index].polygons.size()) { + physics.write[layer_index].polygons.resize(polygon_index + 1); + } + } + if (components[2] == "points") { + Vector polygon = p_value; + set_collision_polygon_points(layer_index, polygon_index, polygon); + return true; + } else if (components[2] == "one_way") { + set_collision_polygon_one_way(layer_index, polygon_index, p_value); + return true; + } else if (components[2] == "one_way_margin") { + set_collision_polygon_one_way_margin(layer_index, polygon_index, p_value); + return true; + } + } + } else if (components.size() == 2 && components[0].begins_with("navigation_layer_") && components[0].trim_prefix("navigation_layer_").is_valid_int()) { + // Navigation layers. + int layer_index = components[0].trim_prefix("navigation_layer_").to_int(); + ERR_FAIL_COND_V(layer_index < 0, false); + if (components[1] == "polygon") { + Ref polygon = p_value; + + if (layer_index >= navigation.size()) { + if (tile_set) { + return false; + } else { + navigation.resize(layer_index + 1); + } + } + set_navigation_polygon(layer_index, polygon); + return true; + } + } else if (components.size() == 2 && components[0] == "terrains_peering_bit") { + // Terrains. + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + if (components[1] == TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]) { + set_terrain_peering_bit(bit, p_value); + return true; + } + } + return false; + } else if (components.size() == 1 && components[0].begins_with("custom_data_") && components[0].trim_prefix("custom_data_").is_valid_int()) { + // Custom data layers. + int layer_index = components[0].trim_prefix("custom_data_").to_int(); + ERR_FAIL_COND_V(layer_index < 0, false); + + if (layer_index >= custom_data.size()) { + if (tile_set) { + return false; + } else { + custom_data.resize(layer_index + 1); + } + } + set_custom_data_by_layer_id(layer_index, p_value); + + return true; + } + + return false; +} + +bool TileData::_get(const StringName &p_name, Variant &r_ret) const { +#ifndef DISABLE_DEPRECATED + if (p_name == "texture_offset") { + r_ret = texture_origin; + return true; + } +#endif + + Vector components = String(p_name).split("/", true, 2); + + if (tile_set) { + if (components.size() == 2 && components[0].begins_with("occlusion_layer") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) { + // Occlusion layers. + int layer_index = components[0].trim_prefix("occlusion_layer_").to_int(); + ERR_FAIL_COND_V(layer_index < 0, false); + if (layer_index >= occluders.size()) { + return false; + } + if (components[1] == "polygon") { + r_ret = get_occluder(layer_index); + return true; + } + } else if (components.size() >= 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) { + // Physics layers. + int layer_index = components[0].trim_prefix("physics_layer_").to_int(); + ERR_FAIL_COND_V(layer_index < 0, false); + if (layer_index >= physics.size()) { + return false; + } + + if (components.size() == 2) { + if (components[1] == "linear_velocity") { + r_ret = get_constant_linear_velocity(layer_index); + return true; + } else if (components[1] == "angular_velocity") { + r_ret = get_constant_angular_velocity(layer_index); + return true; + } else if (components[1] == "polygons_count") { + r_ret = get_collision_polygons_count(layer_index); + return true; + } + } else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) { + int polygon_index = components[1].trim_prefix("polygon_").to_int(); + ERR_FAIL_COND_V(polygon_index < 0, false); + if (polygon_index >= physics[layer_index].polygons.size()) { + return false; + } + if (components[2] == "points") { + r_ret = get_collision_polygon_points(layer_index, polygon_index); + return true; + } else if (components[2] == "one_way") { + r_ret = is_collision_polygon_one_way(layer_index, polygon_index); + return true; + } else if (components[2] == "one_way_margin") { + r_ret = get_collision_polygon_one_way_margin(layer_index, polygon_index); + return true; + } + } + } else if (components.size() == 2 && components[0] == "terrains_peering_bit") { + // Terrains. + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + if (components[1] == TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]) { + r_ret = terrain_peering_bits[i]; + return true; + } + } + return false; + } else if (components.size() == 2 && components[0].begins_with("navigation_layer_") && components[0].trim_prefix("navigation_layer_").is_valid_int()) { + // Occlusion layers. + int layer_index = components[0].trim_prefix("navigation_layer_").to_int(); + ERR_FAIL_COND_V(layer_index < 0, false); + if (layer_index >= navigation.size()) { + return false; + } + if (components[1] == "polygon") { + r_ret = get_navigation_polygon(layer_index); + return true; + } + } else if (components.size() == 1 && components[0].begins_with("custom_data_") && components[0].trim_prefix("custom_data_").is_valid_int()) { + // Custom data layers. + int layer_index = components[0].trim_prefix("custom_data_").to_int(); + ERR_FAIL_COND_V(layer_index < 0, false); + if (layer_index >= custom_data.size()) { + return false; + } + r_ret = get_custom_data_by_layer_id(layer_index); + return true; + } + } + + return false; +} + +void TileData::_get_property_list(List *p_list) const { + PropertyInfo property_info; + // Add the groups manually. + if (tile_set) { + // Occlusion layers. + p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Rendering", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); + for (int i = 0; i < occluders.size(); i++) { + // occlusion_layer_%d/polygon + property_info = PropertyInfo(Variant::OBJECT, vformat("occlusion_layer_%d/%s", i, PNAME("polygon")), PROPERTY_HINT_RESOURCE_TYPE, "OccluderPolygon2D", PROPERTY_USAGE_DEFAULT); + if (occluders[i].occluder.is_null()) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + p_list->push_back(property_info); + } + + // Physics layers. + p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Physics", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); + for (int i = 0; i < physics.size(); i++) { + p_list->push_back(PropertyInfo(Variant::VECTOR2, vformat("physics_layer_%d/%s", i, PNAME("linear_velocity")), PROPERTY_HINT_NONE)); + p_list->push_back(PropertyInfo(Variant::FLOAT, vformat("physics_layer_%d/%s", i, PNAME("angular_velocity")), PROPERTY_HINT_NONE)); + p_list->push_back(PropertyInfo(Variant::INT, vformat("physics_layer_%d/%s", i, PNAME("polygons_count")), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); + + for (int j = 0; j < physics[i].polygons.size(); j++) { + // physics_layer_%d/points + property_info = PropertyInfo(Variant::ARRAY, vformat("physics_layer_%d/polygon_%d/%s", i, j, PNAME("points")), PROPERTY_HINT_ARRAY_TYPE, "Vector2", PROPERTY_USAGE_DEFAULT); + if (physics[i].polygons[j].polygon.is_empty()) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + p_list->push_back(property_info); + + // physics_layer_%d/polygon_%d/one_way + property_info = PropertyInfo(Variant::BOOL, vformat("physics_layer_%d/polygon_%d/%s", i, j, PNAME("one_way"))); + if (physics[i].polygons[j].one_way == false) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + p_list->push_back(property_info); + + // physics_layer_%d/polygon_%d/one_way_margin + property_info = PropertyInfo(Variant::FLOAT, vformat("physics_layer_%d/polygon_%d/%s", i, j, PNAME("one_way_margin"))); + if (physics[i].polygons[j].one_way_margin == 1.0) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + p_list->push_back(property_info); + } + } + + // Terrain data + if (terrain_set >= 0) { + p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Terrains", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + if (is_valid_terrain_peering_bit(bit)) { + property_info = PropertyInfo(Variant::INT, vformat("%s/%s", PNAME("terrains_peering_bit"), TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i])); + if (get_terrain_peering_bit(bit) == -1) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + p_list->push_back(property_info); + } + } + } + + // Navigation layers. + p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Navigation", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); + for (int i = 0; i < navigation.size(); i++) { + property_info = PropertyInfo(Variant::OBJECT, vformat("navigation_layer_%d/%s", i, PNAME("polygon")), PROPERTY_HINT_RESOURCE_TYPE, "NavigationPolygon", PROPERTY_USAGE_DEFAULT); + if (navigation[i].navigation_polygon.is_null()) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + p_list->push_back(property_info); + } + + // Custom data layers. + p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Custom Data", "custom_data_"), PROPERTY_HINT_NONE, "custom_data_", PROPERTY_USAGE_GROUP)); + for (int i = 0; i < custom_data.size(); i++) { + Variant default_val; + Callable::CallError error; + Variant::construct(custom_data[i].get_type(), default_val, nullptr, 0, error); + property_info = PropertyInfo(tile_set->get_custom_data_layer_type(i), vformat("custom_data_%d", i), PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT); + if (custom_data[i] == default_val) { + property_info.usage ^= PROPERTY_USAGE_STORAGE; + } + p_list->push_back(property_info); + } + } +} + +void TileData::_bind_methods() { + // Rendering. + ClassDB::bind_method(D_METHOD("set_flip_h", "flip_h"), &TileData::set_flip_h); + ClassDB::bind_method(D_METHOD("get_flip_h"), &TileData::get_flip_h); + ClassDB::bind_method(D_METHOD("set_flip_v", "flip_v"), &TileData::set_flip_v); + ClassDB::bind_method(D_METHOD("get_flip_v"), &TileData::get_flip_v); + ClassDB::bind_method(D_METHOD("set_transpose", "transpose"), &TileData::set_transpose); + ClassDB::bind_method(D_METHOD("get_transpose"), &TileData::get_transpose); + ClassDB::bind_method(D_METHOD("set_material", "material"), &TileData::set_material); + ClassDB::bind_method(D_METHOD("get_material"), &TileData::get_material); + ClassDB::bind_method(D_METHOD("set_texture_origin", "texture_origin"), &TileData::set_texture_origin); + ClassDB::bind_method(D_METHOD("get_texture_origin"), &TileData::get_texture_origin); + ClassDB::bind_method(D_METHOD("set_modulate", "modulate"), &TileData::set_modulate); + ClassDB::bind_method(D_METHOD("get_modulate"), &TileData::get_modulate); + ClassDB::bind_method(D_METHOD("set_z_index", "z_index"), &TileData::set_z_index); + ClassDB::bind_method(D_METHOD("get_z_index"), &TileData::get_z_index); + ClassDB::bind_method(D_METHOD("set_y_sort_origin", "y_sort_origin"), &TileData::set_y_sort_origin); + ClassDB::bind_method(D_METHOD("get_y_sort_origin"), &TileData::get_y_sort_origin); + + ClassDB::bind_method(D_METHOD("set_occluder", "layer_id", "occluder_polygon"), &TileData::set_occluder); + ClassDB::bind_method(D_METHOD("get_occluder", "layer_id", "flip_h", "flip_v", "transpose"), &TileData::get_occluder, DEFVAL(false), DEFVAL(false), DEFVAL(false)); + + // Physics. + ClassDB::bind_method(D_METHOD("set_constant_linear_velocity", "layer_id", "velocity"), &TileData::set_constant_linear_velocity); + ClassDB::bind_method(D_METHOD("get_constant_linear_velocity", "layer_id"), &TileData::get_constant_linear_velocity); + ClassDB::bind_method(D_METHOD("set_constant_angular_velocity", "layer_id", "velocity"), &TileData::set_constant_angular_velocity); + ClassDB::bind_method(D_METHOD("get_constant_angular_velocity", "layer_id"), &TileData::get_constant_angular_velocity); + ClassDB::bind_method(D_METHOD("set_collision_polygons_count", "layer_id", "polygons_count"), &TileData::set_collision_polygons_count); + ClassDB::bind_method(D_METHOD("get_collision_polygons_count", "layer_id"), &TileData::get_collision_polygons_count); + ClassDB::bind_method(D_METHOD("add_collision_polygon", "layer_id"), &TileData::add_collision_polygon); + ClassDB::bind_method(D_METHOD("remove_collision_polygon", "layer_id", "polygon_index"), &TileData::remove_collision_polygon); + ClassDB::bind_method(D_METHOD("set_collision_polygon_points", "layer_id", "polygon_index", "polygon"), &TileData::set_collision_polygon_points); + ClassDB::bind_method(D_METHOD("get_collision_polygon_points", "layer_id", "polygon_index"), &TileData::get_collision_polygon_points); + ClassDB::bind_method(D_METHOD("set_collision_polygon_one_way", "layer_id", "polygon_index", "one_way"), &TileData::set_collision_polygon_one_way); + ClassDB::bind_method(D_METHOD("is_collision_polygon_one_way", "layer_id", "polygon_index"), &TileData::is_collision_polygon_one_way); + ClassDB::bind_method(D_METHOD("set_collision_polygon_one_way_margin", "layer_id", "polygon_index", "one_way_margin"), &TileData::set_collision_polygon_one_way_margin); + ClassDB::bind_method(D_METHOD("get_collision_polygon_one_way_margin", "layer_id", "polygon_index"), &TileData::get_collision_polygon_one_way_margin); + + // Terrain + ClassDB::bind_method(D_METHOD("set_terrain_set", "terrain_set"), &TileData::set_terrain_set); + ClassDB::bind_method(D_METHOD("get_terrain_set"), &TileData::get_terrain_set); + ClassDB::bind_method(D_METHOD("set_terrain", "terrain"), &TileData::set_terrain); + ClassDB::bind_method(D_METHOD("get_terrain"), &TileData::get_terrain); + ClassDB::bind_method(D_METHOD("set_terrain_peering_bit", "peering_bit", "terrain"), &TileData::set_terrain_peering_bit); + ClassDB::bind_method(D_METHOD("get_terrain_peering_bit", "peering_bit"), &TileData::get_terrain_peering_bit); + + // Navigation + ClassDB::bind_method(D_METHOD("set_navigation_polygon", "layer_id", "navigation_polygon"), &TileData::set_navigation_polygon); + ClassDB::bind_method(D_METHOD("get_navigation_polygon", "layer_id", "flip_h", "flip_v", "transpose"), &TileData::get_navigation_polygon, DEFVAL(false), DEFVAL(false), DEFVAL(false)); + + // Misc. + ClassDB::bind_method(D_METHOD("set_probability", "probability"), &TileData::set_probability); + ClassDB::bind_method(D_METHOD("get_probability"), &TileData::get_probability); + + // Custom data. + ClassDB::bind_method(D_METHOD("set_custom_data", "layer_name", "value"), &TileData::set_custom_data); + ClassDB::bind_method(D_METHOD("get_custom_data", "layer_name"), &TileData::get_custom_data); + ClassDB::bind_method(D_METHOD("set_custom_data_by_layer_id", "layer_id", "value"), &TileData::set_custom_data_by_layer_id); + ClassDB::bind_method(D_METHOD("get_custom_data_by_layer_id", "layer_id"), &TileData::get_custom_data_by_layer_id); + + ADD_GROUP("Rendering", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_h"), "set_flip_h", "get_flip_h"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_v"), "set_flip_v", "get_flip_v"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "transpose"), "set_transpose", "get_transpose"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "texture_origin", PROPERTY_HINT_NONE, "suffix:px"), "set_texture_origin", "get_texture_origin"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "modulate"), "set_modulate", "get_modulate"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "CanvasItemMaterial,ShaderMaterial"), "set_material", "get_material"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "z_index"), "set_z_index", "get_z_index"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "y_sort_origin"), "set_y_sort_origin", "get_y_sort_origin"); + + ADD_GROUP("Terrains", ""); + ADD_PROPERTY(PropertyInfo(Variant::INT, "terrain_set"), "set_terrain_set", "get_terrain_set"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "terrain"), "set_terrain", "get_terrain"); + + ADD_GROUP("Miscellaneous", ""); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "probability"), "set_probability", "get_probability"); + + ADD_SIGNAL(MethodInfo("changed")); +} diff --git a/scene/resources/2d/tile_set.h b/scene/resources/2d/tile_set.h new file mode 100644 index 0000000000..b55afd2de5 --- /dev/null +++ b/scene/resources/2d/tile_set.h @@ -0,0 +1,987 @@ +/**************************************************************************/ +/* tile_set.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 TILE_SET_H +#define TILE_SET_H + +#include "core/io/resource.h" +#include "core/object/object.h" +#include "core/templates/local_vector.h" +#include "core/templates/rb_set.h" +#include "scene/2d/light_occluder_2d.h" +#include "scene/main/canvas_item.h" +#include "scene/resources/2d/convex_polygon_shape_2d.h" +#include "scene/resources/image_texture.h" +#include "scene/resources/navigation_polygon.h" +#include "scene/resources/packed_scene.h" +#include "scene/resources/physics_material.h" + +#ifndef DISABLE_DEPRECATED +#include "scene/resources/shader.h" +#endif + +class TileMap; +class TileSetSource; +class TileSetAtlasSource; +class TileData; + +// Forward-declare the plugins. +class TileSetPlugin; +class TileSetPluginAtlasRendering; +class TileSetPluginAtlasPhysics; +class TileSetPluginAtlasNavigation; + +union TileMapCell { + struct { + int16_t source_id; + int16_t coord_x; + int16_t coord_y; + int16_t alternative_tile; + }; + + uint64_t _u64t; + + static uint32_t hash(const TileMapCell &p_hash) { + return hash_one_uint64(p_hash._u64t); + } + + TileMapCell(int p_source_id = -1, Vector2i p_atlas_coords = Vector2i(-1, -1), int p_alternative_tile = -1) { // default are INVALID_SOURCE, INVALID_ATLAS_COORDS, INVALID_TILE_ALTERNATIVE + source_id = p_source_id; + set_atlas_coords(p_atlas_coords); + alternative_tile = p_alternative_tile; + } + + Vector2i get_atlas_coords() const { + return Vector2i(coord_x, coord_y); + } + + void set_atlas_coords(const Vector2i &r_coords) { + coord_x = r_coords.x; + coord_y = r_coords.y; + } + + bool operator<(const TileMapCell &p_other) const { + if (source_id == p_other.source_id) { + if (coord_x == p_other.coord_x) { + if (coord_y == p_other.coord_y) { + return alternative_tile < p_other.alternative_tile; + } else { + return coord_y < p_other.coord_y; + } + } else { + return coord_x < p_other.coord_x; + } + } else { + return source_id < p_other.source_id; + } + } + + bool operator!=(const TileMapCell &p_other) const { + return !(source_id == p_other.source_id && coord_x == p_other.coord_x && coord_y == p_other.coord_y && alternative_tile == p_other.alternative_tile); + } + bool operator==(const TileMapCell &p_other) const { + return source_id == p_other.source_id && coord_x == p_other.coord_x && coord_y == p_other.coord_y && alternative_tile == p_other.alternative_tile; + } +}; + +class TileMapPattern : public Resource { + GDCLASS(TileMapPattern, Resource); + + Size2i size; + HashMap pattern; + + void _set_tile_data(const Vector &p_data); + Vector _get_tile_data() const; + +protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List *p_list) const; + + static void _bind_methods(); + +public: + void set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile = 0); + bool has_cell(const Vector2i &p_coords) const; + void remove_cell(const Vector2i &p_coords, bool p_update_size = true); + int get_cell_source_id(const Vector2i &p_coords) const; + Vector2i get_cell_atlas_coords(const Vector2i &p_coords) const; + int get_cell_alternative_tile(const Vector2i &p_coords) const; + + const HashMap &get_pattern() const { return pattern; } + TypedArray get_used_cells() const; + + Size2i get_size() const; + void set_size(const Size2i &p_size); + bool is_empty() const; + + void clear(); +}; + +class TileSet : public Resource { + GDCLASS(TileSet, Resource); + +#ifndef DISABLE_DEPRECATED +private: + struct CompatibilityShapeData { + Vector2i autotile_coords; + bool one_way; + float one_way_margin; + Ref shape; + Transform2D transform; + }; + + struct CompatibilityTileData { + String name; + Ref texture; + Vector2 tex_offset; + Ref material; + Rect2 region; + int tile_mode = 0; + Color modulate = Color(1, 1, 1); + + // Atlas or autotiles data + int autotile_bitmask_mode = 0; + Vector2 autotile_icon_coordinate; + Size2i autotile_tile_size = Size2i(16, 16); + + int autotile_spacing = 0; + HashMap autotile_bitmask_flags; + HashMap> autotile_occluder_map; + HashMap> autotile_navpoly_map; + HashMap autotile_priority_map; + HashMap autotile_z_index_map; + + Vector shapes; + Ref occluder; + Vector2 occluder_offset; + Ref navigation; + Vector2 navigation_offset; + int z_index = 0; + }; + + enum CompatibilityTileMode { + COMPATIBILITY_TILE_MODE_SINGLE_TILE = 0, + COMPATIBILITY_TILE_MODE_AUTO_TILE, + COMPATIBILITY_TILE_MODE_ATLAS_TILE, + }; + + HashMap compatibility_data; + HashMap compatibility_tilemap_mapping_tile_modes; + HashMap> compatibility_tilemap_mapping; + HashMap compatibility_size_count; + + void _compatibility_conversion(); + +public: + // Format of output array [source_id, atlas_coords, alternative] + Array compatibility_tilemap_map(int p_tile_id, Vector2i p_coords, bool p_flip_h, bool p_flip_v, bool p_transpose); +#endif // DISABLE_DEPRECATED + +public: + static const int INVALID_SOURCE; // -1; + + enum CellNeighbor { + CELL_NEIGHBOR_RIGHT_SIDE = 0, + CELL_NEIGHBOR_RIGHT_CORNER, + CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, + CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER, + CELL_NEIGHBOR_BOTTOM_SIDE, + CELL_NEIGHBOR_BOTTOM_CORNER, + CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, + CELL_NEIGHBOR_BOTTOM_LEFT_CORNER, + CELL_NEIGHBOR_LEFT_SIDE, + CELL_NEIGHBOR_LEFT_CORNER, + CELL_NEIGHBOR_TOP_LEFT_SIDE, + CELL_NEIGHBOR_TOP_LEFT_CORNER, + CELL_NEIGHBOR_TOP_SIDE, + CELL_NEIGHBOR_TOP_CORNER, + CELL_NEIGHBOR_TOP_RIGHT_SIDE, + CELL_NEIGHBOR_TOP_RIGHT_CORNER, + CELL_NEIGHBOR_MAX, + }; + + static const char *CELL_NEIGHBOR_ENUM_TO_TEXT[]; + + enum TerrainMode { + TERRAIN_MODE_MATCH_CORNERS_AND_SIDES = 0, + TERRAIN_MODE_MATCH_CORNERS, + TERRAIN_MODE_MATCH_SIDES, + }; + + enum TileShape { + TILE_SHAPE_SQUARE, + TILE_SHAPE_ISOMETRIC, + TILE_SHAPE_HALF_OFFSET_SQUARE, + TILE_SHAPE_HEXAGON, + }; + + enum TileLayout { + TILE_LAYOUT_STACKED, + TILE_LAYOUT_STACKED_OFFSET, + TILE_LAYOUT_STAIRS_RIGHT, + TILE_LAYOUT_STAIRS_DOWN, + TILE_LAYOUT_DIAMOND_RIGHT, + TILE_LAYOUT_DIAMOND_DOWN, + }; + + enum TileOffsetAxis { + TILE_OFFSET_AXIS_HORIZONTAL, + TILE_OFFSET_AXIS_VERTICAL, + }; + + struct PackedSceneSource { + Ref scene; + Vector2 offset; + }; + + class TerrainsPattern { + bool valid = false; + int terrain = -1; + int bits[TileSet::CELL_NEIGHBOR_MAX]; + bool is_valid_bit[TileSet::CELL_NEIGHBOR_MAX]; + + int not_empty_terrains_count = 0; + + public: + bool is_valid() const; + bool is_erase_pattern() const; + + bool operator<(const TerrainsPattern &p_terrains_pattern) const; + bool operator==(const TerrainsPattern &p_terrains_pattern) const; + bool operator!=(const TerrainsPattern &p_terrains_pattern) const { + return !operator==(p_terrains_pattern); + }; + + void set_terrain(int p_terrain); + int get_terrain() const; + + void set_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit, int p_terrain); + int get_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit) const; + + void from_array(Array p_terrains); + Array as_array() const; + + TerrainsPattern(const TileSet *p_tile_set, int p_terrain_set); + TerrainsPattern() {} + }; + +protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List *p_list) const; + void _validate_property(PropertyInfo &p_property) const; + +private: + // --- TileSet data --- + // Basic shape and layout. + TileShape tile_shape = TILE_SHAPE_SQUARE; + TileLayout tile_layout = TILE_LAYOUT_STACKED; + TileOffsetAxis tile_offset_axis = TILE_OFFSET_AXIS_HORIZONTAL; + Size2i tile_size = Size2i(16, 16); //Size2(64, 64); + + // Rendering. + bool uv_clipping = false; + struct OcclusionLayer { + uint32_t light_mask = 1; + bool sdf_collision = false; + }; + Vector occlusion_layers; + + Ref tile_lines_mesh; + Ref tile_filled_mesh; + mutable bool tile_meshes_dirty = true; + + // Physics + struct PhysicsLayer { + uint32_t collision_layer = 1; + uint32_t collision_mask = 1; + Ref physics_material; + }; + Vector physics_layers; + + // Terrains + struct Terrain { + String name; + Color color; + }; + struct TerrainSet { + TerrainMode mode = TERRAIN_MODE_MATCH_CORNERS_AND_SIDES; + Vector terrains; + }; + Vector terrain_sets; + + HashMap> terrain_meshes; + HashMap>> terrain_peering_bits_meshes; + bool terrain_bits_meshes_dirty = true; + + LocalVector>> per_terrain_pattern_tiles; // Cached data. + bool terrains_cache_dirty = true; + void _update_terrains_cache(); + + // Navigation + struct NavigationLayer { + uint32_t layers = 1; + }; + Vector navigation_layers; + + // CustomData + struct CustomDataLayer { + String name; + Variant::Type type = Variant::NIL; + }; + Vector custom_data_layers; + HashMap custom_data_layers_by_name; + + // Per Atlas source data. + HashMap> sources; + Vector source_ids; + int next_source_id = 0; + // --------------------- + + LocalVector> patterns; + + void _compute_next_source_id(); + void _source_changed(); + + // Tile proxies + RBMap source_level_proxies; + RBMap coords_level_proxies; + RBMap alternative_level_proxies; + + // Helpers + Vector _get_square_terrain_polygon(Vector2i p_size); + Vector _get_square_corner_or_side_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); + Vector _get_square_corner_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); + Vector _get_square_side_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); + + Vector _get_isometric_terrain_polygon(Vector2i p_size); + Vector _get_isometric_corner_or_side_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); + Vector _get_isometric_corner_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); + Vector _get_isometric_side_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); + + Vector _get_half_offset_terrain_polygon(Vector2i p_size, float p_overlap, TileSet::TileOffsetAxis p_offset_axis); + Vector _get_half_offset_corner_or_side_terrain_peering_bit_polygon(Vector2i p_size, float p_overlap, TileSet::TileOffsetAxis p_offset_axis, TileSet::CellNeighbor p_bit); + Vector _get_half_offset_corner_terrain_peering_bit_polygon(Vector2i p_size, float p_overlap, TileSet::TileOffsetAxis p_offset_axis, TileSet::CellNeighbor p_bit); + Vector _get_half_offset_side_terrain_peering_bit_polygon(Vector2i p_size, float p_overlap, TileSet::TileOffsetAxis p_offset_axis, TileSet::CellNeighbor p_bit); + +protected: + static void _bind_methods(); + +public: + // --- Plugins --- + Vector get_tile_set_atlas_plugins() const; + + // --- Accessors for TileSet data --- + + // -- Shape and layout -- + void set_tile_shape(TileShape p_shape); + TileShape get_tile_shape() const; + void set_tile_layout(TileLayout p_layout); + TileLayout get_tile_layout() const; + void set_tile_offset_axis(TileOffsetAxis p_alignment); + TileOffsetAxis get_tile_offset_axis() const; + void set_tile_size(Size2i p_size); + Size2i get_tile_size() const; + + // -- Sources management -- + int get_next_source_id() const; + int get_source_count() const; + int get_source_id(int p_index) const; + int add_source(Ref p_tile_set_source, int p_source_id_override = -1); + void set_source_id(int p_source_id, int p_new_id); + void remove_source(int p_source_id); + void remove_source_ptr(TileSetSource *p_tile_set_source); // Not exposed + bool has_source(int p_source_id) const; + Ref get_source(int p_source_id) const; + + // Rendering + void set_uv_clipping(bool p_uv_clipping); + bool is_uv_clipping() const; + + int get_occlusion_layers_count() const; + void add_occlusion_layer(int p_index = -1); + void move_occlusion_layer(int p_from_index, int p_to_pos); + void remove_occlusion_layer(int p_index); + void set_occlusion_layer_light_mask(int p_layer_index, int p_light_mask); + int get_occlusion_layer_light_mask(int p_layer_index) const; + void set_occlusion_layer_sdf_collision(int p_layer_index, bool p_sdf_collision); + bool get_occlusion_layer_sdf_collision(int p_layer_index) const; + + // Physics + int get_physics_layers_count() const; + void add_physics_layer(int p_index = -1); + void move_physics_layer(int p_from_index, int p_to_pos); + void remove_physics_layer(int p_index); + void set_physics_layer_collision_layer(int p_layer_index, uint32_t p_layer); + uint32_t get_physics_layer_collision_layer(int p_layer_index) const; + void set_physics_layer_collision_mask(int p_layer_index, uint32_t p_mask); + uint32_t get_physics_layer_collision_mask(int p_layer_index) const; + void set_physics_layer_physics_material(int p_layer_index, Ref p_physics_material); + Ref get_physics_layer_physics_material(int p_layer_index) const; + + // Terrain sets + int get_terrain_sets_count() const; + void add_terrain_set(int p_index = -1); + void move_terrain_set(int p_from_index, int p_to_pos); + void remove_terrain_set(int p_index); + void set_terrain_set_mode(int p_terrain_set, TerrainMode p_terrain_mode); + TerrainMode get_terrain_set_mode(int p_terrain_set) const; + + // Terrains + int get_terrains_count(int p_terrain_set) const; + void add_terrain(int p_terrain_set, int p_index = -1); + void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos); + void remove_terrain(int p_terrain_set, int p_index); + void set_terrain_name(int p_terrain_set, int p_terrain_index, String p_name); + String get_terrain_name(int p_terrain_set, int p_terrain_index) const; + void set_terrain_color(int p_terrain_set, int p_terrain_index, Color p_color); + Color get_terrain_color(int p_terrain_set, int p_terrain_index) const; + bool is_valid_terrain_peering_bit_for_mode(TileSet::TerrainMode p_terrain_mode, TileSet::CellNeighbor p_peering_bit) const; + bool is_valid_terrain_peering_bit(int p_terrain_set, TileSet::CellNeighbor p_peering_bit) const; + + // Navigation + int get_navigation_layers_count() const; + void add_navigation_layer(int p_index = -1); + void move_navigation_layer(int p_from_index, int p_to_pos); + void remove_navigation_layer(int p_index); + void set_navigation_layer_layers(int p_layer_index, uint32_t p_layers); + uint32_t get_navigation_layer_layers(int p_layer_index) const; + void set_navigation_layer_layer_value(int p_layer_index, int p_layer_number, bool p_value); + bool get_navigation_layer_layer_value(int p_layer_index, int p_layer_number) const; + + // Custom data + int get_custom_data_layers_count() const; + void add_custom_data_layer(int p_index = -1); + void move_custom_data_layer(int p_from_index, int p_to_pos); + void remove_custom_data_layer(int p_index); + int get_custom_data_layer_by_name(String p_value) const; + void set_custom_data_layer_name(int p_layer_id, String p_value); + String get_custom_data_layer_name(int p_layer_id) const; + void set_custom_data_layer_type(int p_layer_id, Variant::Type p_value); + Variant::Type get_custom_data_layer_type(int p_layer_id) const; + + // Tiles proxies. + void set_source_level_tile_proxy(int p_source_from, int p_source_to); + int get_source_level_tile_proxy(int p_source_from); + bool has_source_level_tile_proxy(int p_source_from); + void remove_source_level_tile_proxy(int p_source_from); + + void set_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_source_to, Vector2i p_coords_to); + Array get_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from); + bool has_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from); + void remove_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from); + + void set_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from, int p_source_to, Vector2i p_coords_to, int p_alternative_to); + Array get_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from); + bool has_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from); + void remove_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from); + + Array get_source_level_tile_proxies() const; + Array get_coords_level_tile_proxies() const; + Array get_alternative_level_tile_proxies() const; + + Array map_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from) const; + + void cleanup_invalid_tile_proxies(); + void clear_tile_proxies(); + + // Patterns. + int add_pattern(Ref p_pattern, int p_index = -1); + Ref get_pattern(int p_index); + void remove_pattern(int p_index); + int get_patterns_count(); + + // Terrains. + RBSet get_terrains_pattern_set(int p_terrain_set); + RBSet get_tiles_for_terrains_pattern(int p_terrain_set, TerrainsPattern p_terrain_tile_pattern); + TileMapCell get_random_tile_from_terrains_pattern(int p_terrain_set, TerrainsPattern p_terrain_tile_pattern); + + // Helpers + Vector get_tile_shape_polygon() const; + void draw_tile_shape(CanvasItem *p_canvas_item, Transform2D p_transform, Color p_color, bool p_filled = false, Ref p_texture = Ref()) const; + + // Used by TileMap/TileMapLayer + Vector2 map_to_local(const Vector2i &p_pos) const; + Vector2i local_to_map(const Vector2 &p_pos) const; + bool is_existing_neighbor(TileSet::CellNeighbor p_cell_neighbor) const; + Vector2i get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeighbor p_cell_neighbor) const; + TypedArray get_surrounding_cells(const Vector2i &p_coords) const; + Vector2i map_pattern(const Vector2i &p_position_in_tilemap, const Vector2i &p_coords_in_pattern, Ref p_pattern) const; + void draw_cells_outline(CanvasItem *p_canvas_item, const RBSet &p_cells, Color p_color, Transform2D p_transform = Transform2D()) const; + + Vector get_terrain_polygon(int p_terrain_set); + Vector get_terrain_peering_bit_polygon(int p_terrain_set, TileSet::CellNeighbor p_bit); + void draw_terrains(CanvasItem *p_canvas_item, Transform2D p_transform, const TileData *p_tile_data); + Vector>> generate_terrains_icons(Size2i p_size); + + // Resource management + virtual void reset_state() override; + + // Helpers. + static Vector2i transform_coords_layout(const Vector2i &p_coords, TileSet::TileOffsetAxis p_offset_axis, TileSet::TileLayout p_from_layout, TileSet::TileLayout p_to_layout); + + TileSet(); + ~TileSet(); +}; + +class TileSetSource : public Resource { + GDCLASS(TileSetSource, Resource); + +protected: + const TileSet *tile_set = nullptr; + + static void _bind_methods(); + +public: + static const Vector2i INVALID_ATLAS_COORDS; // Vector2i(-1, -1); + static const int INVALID_TILE_ALTERNATIVE; // -1; + + // Not exposed. + virtual void set_tile_set(const TileSet *p_tile_set); + TileSet *get_tile_set() const; + virtual void notify_tile_data_properties_should_change(){}; + virtual void add_occlusion_layer(int p_index){}; + virtual void move_occlusion_layer(int p_from_index, int p_to_pos){}; + virtual void remove_occlusion_layer(int p_index){}; + virtual void add_physics_layer(int p_index){}; + virtual void move_physics_layer(int p_from_index, int p_to_pos){}; + virtual void remove_physics_layer(int p_index){}; + virtual void add_terrain_set(int p_index){}; + virtual void move_terrain_set(int p_from_index, int p_to_pos){}; + virtual void remove_terrain_set(int p_index){}; + virtual void add_terrain(int p_terrain_set, int p_index){}; + virtual void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos){}; + virtual void remove_terrain(int p_terrain_set, int p_index){}; + virtual void add_navigation_layer(int p_index){}; + virtual void move_navigation_layer(int p_from_index, int p_to_pos){}; + virtual void remove_navigation_layer(int p_index){}; + virtual void add_custom_data_layer(int p_index){}; + virtual void move_custom_data_layer(int p_from_index, int p_to_pos){}; + virtual void remove_custom_data_layer(int p_index){}; + virtual void reset_state() override; + + // Tiles. + virtual int get_tiles_count() const = 0; + virtual Vector2i get_tile_id(int tile_index) const = 0; + virtual bool has_tile(Vector2i p_atlas_coords) const = 0; + + // Alternative tiles. + virtual int get_alternative_tiles_count(const Vector2i p_atlas_coords) const = 0; + virtual int get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const = 0; + virtual bool has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const = 0; +}; + +class TileSetAtlasSource : public TileSetSource { + GDCLASS(TileSetAtlasSource, TileSetSource); + +public: + enum TileAnimationMode { + TILE_ANIMATION_MODE_DEFAULT, + TILE_ANIMATION_MODE_RANDOM_START_TIMES, + TILE_ANIMATION_MODE_MAX, + }; + + enum TransformBits { + TRANSFORM_FLIP_H = 1 << 12, + TRANSFORM_FLIP_V = 1 << 13, + TRANSFORM_TRANSPOSE = 1 << 14, + }; + +private: + struct TileAlternativesData { + Vector2i size_in_atlas = Vector2i(1, 1); + Vector2i texture_offset; + + // Animation + int animation_columns = 0; + Vector2i animation_separation; + real_t animation_speed = 1.0; + TileSetAtlasSource::TileAnimationMode animation_mode = TILE_ANIMATION_MODE_DEFAULT; + LocalVector animation_frames_durations; + + // Alternatives + HashMap alternatives; + Vector alternatives_ids; + int next_alternative_id = 1; + }; + + Ref texture; + Vector2i margins; + Vector2i separation; + Size2i texture_region_size = Size2i(16, 16); + + HashMap tiles; + Vector tiles_ids; + HashMap _coords_mapping_cache; // Maps any coordinate to the including tile + + TileData *_get_atlas_tile_data(Vector2i p_atlas_coords, int p_alternative_tile); + const TileData *_get_atlas_tile_data(Vector2i p_atlas_coords, int p_alternative_tile) const; + + void _compute_next_alternative_id(const Vector2i p_atlas_coords); + + void _clear_coords_mapping_cache(Vector2i p_atlas_coords); + void _create_coords_mapping_cache(Vector2i p_atlas_coords); + + bool use_texture_padding = true; + Ref padded_texture; + bool padded_texture_needs_update = false; + void _queue_update_padded_texture(); + Ref _create_padded_image_texture(const Ref &p_source); + void _update_padded_texture(); + +protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List *p_list) const; + + static void _bind_methods(); + +public: + // Not exposed. + virtual void set_tile_set(const TileSet *p_tile_set) override; + const TileSet *get_tile_set() const; + virtual void notify_tile_data_properties_should_change() override; + virtual void add_occlusion_layer(int p_index) override; + virtual void move_occlusion_layer(int p_from_index, int p_to_pos) override; + virtual void remove_occlusion_layer(int p_index) override; + virtual void add_physics_layer(int p_index) override; + virtual void move_physics_layer(int p_from_index, int p_to_pos) override; + virtual void remove_physics_layer(int p_index) override; + virtual void add_terrain_set(int p_index) override; + virtual void move_terrain_set(int p_from_index, int p_to_pos) override; + virtual void remove_terrain_set(int p_index) override; + virtual void add_terrain(int p_terrain_set, int p_index) override; + virtual void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) override; + virtual void remove_terrain(int p_terrain_set, int p_index) override; + virtual void add_navigation_layer(int p_index) override; + virtual void move_navigation_layer(int p_from_index, int p_to_pos) override; + virtual void remove_navigation_layer(int p_index) override; + virtual void add_custom_data_layer(int p_index) override; + virtual void move_custom_data_layer(int p_from_index, int p_to_pos) override; + virtual void remove_custom_data_layer(int p_index) override; + virtual void reset_state() override; + + // Base properties. + void set_texture(Ref p_texture); + Ref get_texture() const; + void set_margins(Vector2i p_margins); + Vector2i get_margins() const; + void set_separation(Vector2i p_separation); + Vector2i get_separation() const; + void set_texture_region_size(Vector2i p_tile_size); + Vector2i get_texture_region_size() const; + + // Padding. + void set_use_texture_padding(bool p_use_padding); + bool get_use_texture_padding() const; + + // Base tiles. + void create_tile(const Vector2i p_atlas_coords, const Vector2i p_size = Vector2i(1, 1)); + void remove_tile(Vector2i p_atlas_coords); + virtual bool has_tile(Vector2i p_atlas_coords) const override; + void move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords = INVALID_ATLAS_COORDS, Vector2i p_new_size = Vector2i(-1, -1)); + Vector2i get_tile_size_in_atlas(Vector2i p_atlas_coords) const; + + virtual int get_tiles_count() const override; + virtual Vector2i get_tile_id(int p_index) const override; + + bool has_room_for_tile(Vector2i p_atlas_coords, Vector2i p_size, int p_animation_columns, Vector2i p_animation_separation, int p_frames_count, Vector2i p_ignored_tile = INVALID_ATLAS_COORDS) const; + PackedVector2Array get_tiles_to_be_removed_on_change(Ref p_texture, Vector2i p_margins, Vector2i p_separation, Vector2i p_texture_region_size); + Vector2i get_tile_at_coords(Vector2i p_atlas_coords) const; + + bool has_tiles_outside_texture() const; + Vector get_tiles_outside_texture() const; + void clear_tiles_outside_texture(); + + // Animation. + void set_tile_animation_columns(const Vector2i p_atlas_coords, int p_frame_columns); + int get_tile_animation_columns(const Vector2i p_atlas_coords) const; + void set_tile_animation_separation(const Vector2i p_atlas_coords, const Vector2i p_separation); + Vector2i get_tile_animation_separation(const Vector2i p_atlas_coords) const; + void set_tile_animation_speed(const Vector2i p_atlas_coords, real_t p_speed); + real_t get_tile_animation_speed(const Vector2i p_atlas_coords) const; + void set_tile_animation_mode(const Vector2i p_atlas_coords, const TileSetAtlasSource::TileAnimationMode p_mode); + TileSetAtlasSource::TileAnimationMode get_tile_animation_mode(const Vector2i p_atlas_coords) const; + void set_tile_animation_frames_count(const Vector2i p_atlas_coords, int p_frames_count); + int get_tile_animation_frames_count(const Vector2i p_atlas_coords) const; + void set_tile_animation_frame_duration(const Vector2i p_atlas_coords, int p_frame_index, real_t p_duration); + real_t get_tile_animation_frame_duration(const Vector2i p_atlas_coords, int p_frame_index) const; + real_t get_tile_animation_total_duration(const Vector2i p_atlas_coords) const; + + // Alternative tiles. + int create_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_id_override = -1); + void remove_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile); + void set_alternative_tile_id(const Vector2i p_atlas_coords, int p_alternative_tile, int p_new_id); + virtual bool has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const override; + int get_next_alternative_tile_id(const Vector2i p_atlas_coords) const; + + virtual int get_alternative_tiles_count(const Vector2i p_atlas_coords) const override; + virtual int get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const override; + + // Get data associated to a tile. + TileData *get_tile_data(const Vector2i p_atlas_coords, int p_alternative_tile) const; + + // Helpers. + Vector2i get_atlas_grid_size() const; + Rect2i get_tile_texture_region(Vector2i p_atlas_coords, int p_frame = 0) const; + bool is_position_in_tile_texture_region(const Vector2i p_atlas_coords, int p_alternative_tile, Vector2 p_position) const; + + static int alternative_no_transform(int p_alternative_id); + + // Getters for texture and tile region (padded or not) + Ref get_runtime_texture() const; + Rect2i get_runtime_tile_texture_region(Vector2i p_atlas_coords, int p_frame = 0) const; + + ~TileSetAtlasSource(); +}; + +class TileSetScenesCollectionSource : public TileSetSource { + GDCLASS(TileSetScenesCollectionSource, TileSetSource); + +private: + struct SceneData { + Ref scene; + bool display_placeholder = false; + }; + Vector scenes_ids; + HashMap scenes; + int next_scene_id = 1; + + void _compute_next_alternative_id(); + +protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List *p_list) const; + + static void _bind_methods(); + +public: + // Tiles. + int get_tiles_count() const override; + Vector2i get_tile_id(int p_tile_index) const override; + bool has_tile(Vector2i p_atlas_coords) const override; + + // Alternative tiles. + int get_alternative_tiles_count(const Vector2i p_atlas_coords) const override; + int get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const override; + bool has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const override; + + // Scenes accessors. Lot are similar to "Alternative tiles". + int get_scene_tiles_count() { return get_alternative_tiles_count(Vector2i()); } + int get_scene_tile_id(int p_index) { return get_alternative_tile_id(Vector2i(), p_index); }; + bool has_scene_tile_id(int p_id) { return has_alternative_tile(Vector2i(), p_id); }; + int create_scene_tile(Ref p_packed_scene = Ref(), int p_id_override = -1); + void set_scene_tile_id(int p_id, int p_new_id); + void set_scene_tile_scene(int p_id, Ref p_packed_scene); + Ref get_scene_tile_scene(int p_id) const; + void set_scene_tile_display_placeholder(int p_id, bool p_packed_scene); + bool get_scene_tile_display_placeholder(int p_id) const; + void remove_scene_tile(int p_id); + int get_next_scene_tile_id() const; +}; + +class TileData : public Object { + GDCLASS(TileData, Object); + +private: + const TileSet *tile_set = nullptr; + bool allow_transform = true; + + // Rendering + bool flip_h = false; + bool flip_v = false; + bool transpose = false; + Vector2i texture_origin; + Ref material = Ref(); + Color modulate = Color(1.0, 1.0, 1.0, 1.0); + int z_index = 0; + int y_sort_origin = 0; + struct OcclusionLayerTileData { + Ref occluder; + mutable HashMap> transformed_occluders; + }; + Vector occluders; + + // Physics + struct PhysicsLayerTileData { + struct PolygonShapeTileData { + LocalVector polygon; + LocalVector> shapes; + mutable HashMap>> transformed_shapes; + bool one_way = false; + float one_way_margin = 1.0; + }; + + Vector2 linear_velocity; + double angular_velocity = 0.0; + Vector polygons; + }; + Vector physics; + // TODO add support for areas. + + // Terrain + int terrain_set = -1; + int terrain = -1; + int terrain_peering_bits[16] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; + + // Navigation + struct NavigationLayerTileData { + Ref navigation_polygon; + mutable HashMap> transformed_navigation_polygon; + }; + Vector navigation; + + // Misc + double probability = 1.0; + + // Custom data + Vector custom_data; + +protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List *p_list) const; + static void _bind_methods(); + +#ifndef DISABLE_DEPRECATED + Ref _get_navigation_polygon_bind_compat_84660(int p_layer_id) const; + Ref _get_occluder_bind_compat_84660(int p_layer_id) const; + + static void _bind_compatibility_methods(); +#endif + +public: + // Not exposed. + void set_tile_set(const TileSet *p_tile_set); + void notify_tile_data_properties_should_change(); + void add_occlusion_layer(int p_index); + void move_occlusion_layer(int p_from_index, int p_to_pos); + void remove_occlusion_layer(int p_index); + void add_physics_layer(int p_index); + void move_physics_layer(int p_from_index, int p_to_pos); + void remove_physics_layer(int p_index); + void add_terrain_set(int p_index); + void move_terrain_set(int p_from_index, int p_to_pos); + void remove_terrain_set(int p_index); + void add_terrain(int p_terrain_set, int p_index); + void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos); + void remove_terrain(int p_terrain_set, int p_index); + void add_navigation_layer(int p_index); + void move_navigation_layer(int p_from_index, int p_to_pos); + void remove_navigation_layer(int p_index); + void add_custom_data_layer(int p_index); + void move_custom_data_layer(int p_from_index, int p_to_pos); + void remove_custom_data_layer(int p_index); + void set_allow_transform(bool p_allow_transform); + bool is_allowing_transform() const; + + // To duplicate a TileData object, needed for runtiume update. + TileData *duplicate(); + + // Rendering + void set_flip_h(bool p_flip_h); + bool get_flip_h() const; + void set_flip_v(bool p_flip_v); + bool get_flip_v() const; + void set_transpose(bool p_transpose); + bool get_transpose() const; + + void set_texture_origin(Vector2i p_texture_origin); + Vector2i get_texture_origin() const; + void set_material(Ref p_material); + Ref get_material() const; + void set_modulate(Color p_modulate); + Color get_modulate() const; + void set_z_index(int p_z_index); + int get_z_index() const; + void set_y_sort_origin(int p_y_sort_origin); + int get_y_sort_origin() const; + + void set_occluder(int p_layer_id, Ref p_occluder_polygon); + Ref get_occluder(int p_layer_id, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false) const; + + // Physics + void set_constant_linear_velocity(int p_layer_id, const Vector2 &p_velocity); + Vector2 get_constant_linear_velocity(int p_layer_id) const; + void set_constant_angular_velocity(int p_layer_id, real_t p_velocity); + real_t get_constant_angular_velocity(int p_layer_id) const; + void set_collision_polygons_count(int p_layer_id, int p_shapes_count); + int get_collision_polygons_count(int p_layer_id) const; + void add_collision_polygon(int p_layer_id); + void remove_collision_polygon(int p_layer_id, int p_polygon_index); + void set_collision_polygon_points(int p_layer_id, int p_polygon_index, Vector p_polygon); + Vector get_collision_polygon_points(int p_layer_id, int p_polygon_index) const; + void set_collision_polygon_one_way(int p_layer_id, int p_polygon_index, bool p_one_way); + bool is_collision_polygon_one_way(int p_layer_id, int p_polygon_index) const; + void set_collision_polygon_one_way_margin(int p_layer_id, int p_polygon_index, float p_one_way_margin); + float get_collision_polygon_one_way_margin(int p_layer_id, int p_polygon_index) const; + int get_collision_polygon_shapes_count(int p_layer_id, int p_polygon_index) const; + Ref get_collision_polygon_shape(int p_layer_id, int p_polygon_index, int shape_index, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false) const; + + // Terrain + void set_terrain_set(int p_terrain_id); + int get_terrain_set() const; + void set_terrain(int p_terrain_id); + int get_terrain() const; + void set_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit, int p_terrain_id); + int get_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit) const; + bool is_valid_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit) const; + + TileSet::TerrainsPattern get_terrains_pattern() const; // Not exposed. + + // Navigation + void set_navigation_polygon(int p_layer_id, Ref p_navigation_polygon); + Ref get_navigation_polygon(int p_layer_id, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false) const; + + // Misc + void set_probability(float p_probability); + float get_probability() const; + + // Custom data. + void set_custom_data(String p_layer_name, Variant p_value); + Variant get_custom_data(String p_layer_name) const; + void set_custom_data_by_layer_id(int p_layer_id, Variant p_value); + Variant get_custom_data_by_layer_id(int p_layer_id) const; + + // Polygons. + static PackedVector2Array get_transformed_vertices(const PackedVector2Array &p_vertices, bool p_flip_h, bool p_flip_v, bool p_transpose); +}; + +VARIANT_ENUM_CAST(TileSet::CellNeighbor); +VARIANT_ENUM_CAST(TileSet::TerrainMode); +VARIANT_ENUM_CAST(TileSet::TileShape); +VARIANT_ENUM_CAST(TileSet::TileLayout); +VARIANT_ENUM_CAST(TileSet::TileOffsetAxis); + +VARIANT_ENUM_CAST(TileSetAtlasSource::TileAnimationMode); + +#endif // TILE_SET_H diff --git a/scene/resources/2d/world_boundary_shape_2d.cpp b/scene/resources/2d/world_boundary_shape_2d.cpp new file mode 100644 index 0000000000..294653659a --- /dev/null +++ b/scene/resources/2d/world_boundary_shape_2d.cpp @@ -0,0 +1,126 @@ +/**************************************************************************/ +/* world_boundary_shape_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 "world_boundary_shape_2d.h" + +#include "core/math/geometry_2d.h" +#include "servers/physics_server_2d.h" +#include "servers/rendering_server.h" + +bool WorldBoundaryShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { + Vector2 point = distance * normal; + Vector2 l[2][2] = { { point - normal.orthogonal() * 100, point + normal.orthogonal() * 100 }, { point, point + normal * 30 } }; + + for (int i = 0; i < 2; i++) { + Vector2 closest = Geometry2D::get_closest_point_to_segment(p_point, l[i]); + if (p_point.distance_to(closest) < p_tolerance) { + return true; + } + } + + return false; +} + +void WorldBoundaryShape2D::_update_shape() { + Array arr; + arr.push_back(normal); + arr.push_back(distance); + PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), arr); + emit_changed(); +} + +void WorldBoundaryShape2D::set_normal(const Vector2 &p_normal) { + // Can be non-unit but prevent zero. + ERR_FAIL_COND(p_normal.is_zero_approx()); + if (normal == p_normal) { + return; + } + normal = p_normal; + _update_shape(); +} + +void WorldBoundaryShape2D::set_distance(real_t p_distance) { + if (distance == p_distance) { + return; + } + distance = p_distance; + _update_shape(); +} + +Vector2 WorldBoundaryShape2D::get_normal() const { + return normal; +} + +real_t WorldBoundaryShape2D::get_distance() const { + return distance; +} + +void WorldBoundaryShape2D::draw(const RID &p_to_rid, const Color &p_color) { + Vector2 point = distance * normal; + real_t line_width = 3.0; + + Vector2 l1[2] = { point - normal.orthogonal() * 100, point + normal.orthogonal() * 100 }; + RS::get_singleton()->canvas_item_add_line(p_to_rid, l1[0], l1[1], p_color, line_width); + Vector2 l2[2] = { point + normal.normalized() * (0.5 * line_width), point + normal * 30 }; + RS::get_singleton()->canvas_item_add_line(p_to_rid, l2[0], l2[1], p_color, line_width); +} + +Rect2 WorldBoundaryShape2D::get_rect() const { + Vector2 point = distance * normal; + + Vector2 l1[2] = { point - normal.orthogonal() * 100, point + normal.orthogonal() * 100 }; + Vector2 l2[2] = { point, point + normal * 30 }; + Rect2 rect; + rect.position = l1[0]; + rect.expand_to(l1[1]); + rect.expand_to(l2[0]); + rect.expand_to(l2[1]); + return rect; +} + +real_t WorldBoundaryShape2D::get_enclosing_radius() const { + return distance; +} + +void WorldBoundaryShape2D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_normal", "normal"), &WorldBoundaryShape2D::set_normal); + ClassDB::bind_method(D_METHOD("get_normal"), &WorldBoundaryShape2D::get_normal); + + ClassDB::bind_method(D_METHOD("set_distance", "distance"), &WorldBoundaryShape2D::set_distance); + ClassDB::bind_method(D_METHOD("get_distance"), &WorldBoundaryShape2D::get_distance); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "normal"), "set_normal", "get_normal"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance", PROPERTY_HINT_RANGE, "-1024,1024,0.01,or_greater,or_less,suffix:px"), "set_distance", "get_distance"); +} + +WorldBoundaryShape2D::WorldBoundaryShape2D() : + Shape2D(PhysicsServer2D::get_singleton()->world_boundary_shape_create()) { + _update_shape(); +} diff --git a/scene/resources/2d/world_boundary_shape_2d.h b/scene/resources/2d/world_boundary_shape_2d.h new file mode 100644 index 0000000000..a7f11e39b7 --- /dev/null +++ b/scene/resources/2d/world_boundary_shape_2d.h @@ -0,0 +1,64 @@ +/**************************************************************************/ +/* world_boundary_shape_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 WORLD_BOUNDARY_SHAPE_2D_H +#define WORLD_BOUNDARY_SHAPE_2D_H + +#include "scene/resources/2d/shape_2d.h" + +class WorldBoundaryShape2D : public Shape2D { + GDCLASS(WorldBoundaryShape2D, Shape2D); + + // WorldBoundaryShape2D is often used for one-way platforms, where the normal pointing up makes sense. + Vector2 normal = Vector2(0, -1); + real_t distance = 0.0; + + void _update_shape(); + +protected: + static void _bind_methods(); + +public: + virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override; + + void set_normal(const Vector2 &p_normal); + void set_distance(real_t p_distance); + + Vector2 get_normal() const; + real_t get_distance() const; + + virtual void draw(const RID &p_to_rid, const Color &p_color) override; + virtual Rect2 get_rect() const override; + virtual real_t get_enclosing_radius() const override; + + WorldBoundaryShape2D(); +}; + +#endif // WORLD_BOUNDARY_SHAPE_2D_H diff --git a/scene/resources/SCsub b/scene/resources/SCsub index 9e21c627f9..060b47d687 100644 --- a/scene/resources/SCsub +++ b/scene/resources/SCsub @@ -22,3 +22,5 @@ env.scene_sources += scene_obj # Needed to force rebuilding the scene files when the thirdparty code is updated. env.Depends(scene_obj, thirdparty_obj) + +SConscript("2d/SCsub") diff --git a/scene/resources/capsule_shape_2d.cpp b/scene/resources/capsule_shape_2d.cpp deleted file mode 100644 index 8268040ed9..0000000000 --- a/scene/resources/capsule_shape_2d.cpp +++ /dev/null @@ -1,124 +0,0 @@ -/**************************************************************************/ -/* capsule_shape_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 "capsule_shape_2d.h" - -#include "core/math/geometry_2d.h" -#include "servers/physics_server_2d.h" -#include "servers/rendering_server.h" - -Vector CapsuleShape2D::_get_points() const { - Vector points; - const real_t turn_step = Math_TAU / 24.0; - for (int i = 0; i < 24; i++) { - Vector2 ofs = Vector2(0, (i > 6 && i <= 18) ? -height * 0.5 + radius : height * 0.5 - radius); - - points.push_back(Vector2(Math::sin(i * turn_step), Math::cos(i * turn_step)) * radius + ofs); - if (i == 6 || i == 18) { - points.push_back(Vector2(Math::sin(i * turn_step), Math::cos(i * turn_step)) * radius - ofs); - } - } - - return points; -} - -bool CapsuleShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { - return Geometry2D::is_point_in_polygon(p_point, _get_points()); -} - -void CapsuleShape2D::_update_shape() { - PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), Vector2(radius, height)); - emit_changed(); -} - -void CapsuleShape2D::set_radius(real_t p_radius) { - ERR_FAIL_COND_MSG(p_radius < 0, "CapsuleShape2D radius cannot be negative."); - radius = p_radius; - if (radius > height * 0.5) { - height = radius * 2.0; - } - _update_shape(); -} - -real_t CapsuleShape2D::get_radius() const { - return radius; -} - -void CapsuleShape2D::set_height(real_t p_height) { - ERR_FAIL_COND_MSG(p_height < 0, "CapsuleShape2D height cannot be negative."); - height = p_height; - if (radius > height * 0.5) { - radius = height * 0.5; - } - _update_shape(); -} - -real_t CapsuleShape2D::get_height() const { - return height; -} - -void CapsuleShape2D::draw(const RID &p_to_rid, const Color &p_color) { - Vector points = _get_points(); - Vector col = { p_color }; - RenderingServer::get_singleton()->canvas_item_add_polygon(p_to_rid, points, col); - - if (is_collision_outline_enabled()) { - points.push_back(points[0]); - col = { Color(p_color, 1.0) }; - RenderingServer::get_singleton()->canvas_item_add_polyline(p_to_rid, points, col); - } -} - -Rect2 CapsuleShape2D::get_rect() const { - const Vector2 half_size = Vector2(radius, height * 0.5); - return Rect2(-half_size, half_size * 2.0); -} - -real_t CapsuleShape2D::get_enclosing_radius() const { - return height * 0.5; -} - -void CapsuleShape2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CapsuleShape2D::set_radius); - ClassDB::bind_method(D_METHOD("get_radius"), &CapsuleShape2D::get_radius); - - ClassDB::bind_method(D_METHOD("set_height", "height"), &CapsuleShape2D::set_height); - ClassDB::bind_method(D_METHOD("get_height"), &CapsuleShape2D::get_height); - - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:px"), "set_radius", "get_radius"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:px"), "set_height", "get_height"); - ADD_LINKED_PROPERTY("radius", "height"); - ADD_LINKED_PROPERTY("height", "radius"); -} - -CapsuleShape2D::CapsuleShape2D() : - Shape2D(PhysicsServer2D::get_singleton()->capsule_shape_create()) { - _update_shape(); -} diff --git a/scene/resources/capsule_shape_2d.h b/scene/resources/capsule_shape_2d.h deleted file mode 100644 index 4912466f96..0000000000 --- a/scene/resources/capsule_shape_2d.h +++ /dev/null @@ -1,64 +0,0 @@ -/**************************************************************************/ -/* capsule_shape_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 CAPSULE_SHAPE_2D_H -#define CAPSULE_SHAPE_2D_H - -#include "scene/resources/shape_2d.h" - -class CapsuleShape2D : public Shape2D { - GDCLASS(CapsuleShape2D, Shape2D); - - real_t height = 30.0; - real_t radius = 10.0; - - void _update_shape(); - Vector _get_points() const; - -protected: - static void _bind_methods(); - -public: - virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override; - - void set_height(real_t p_height); - real_t get_height() const; - - void set_radius(real_t p_radius); - real_t get_radius() const; - - virtual void draw(const RID &p_to_rid, const Color &p_color) override; - virtual Rect2 get_rect() const override; - virtual real_t get_enclosing_radius() const override; - - CapsuleShape2D(); -}; - -#endif // CAPSULE_SHAPE_2D_H diff --git a/scene/resources/circle_shape_2d.cpp b/scene/resources/circle_shape_2d.cpp deleted file mode 100644 index 0b207c33ca..0000000000 --- a/scene/resources/circle_shape_2d.cpp +++ /dev/null @@ -1,95 +0,0 @@ -/**************************************************************************/ -/* circle_shape_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 "circle_shape_2d.h" - -#include "servers/physics_server_2d.h" -#include "servers/rendering_server.h" - -bool CircleShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { - return p_point.length() < get_radius() + p_tolerance; -} - -void CircleShape2D::_update_shape() { - PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), radius); - emit_changed(); -} - -void CircleShape2D::set_radius(real_t p_radius) { - ERR_FAIL_COND_MSG(p_radius < 0, "CircleShape2D radius cannot be negative."); - radius = p_radius; - _update_shape(); -} - -real_t CircleShape2D::get_radius() const { - return radius; -} - -void CircleShape2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CircleShape2D::set_radius); - ClassDB::bind_method(D_METHOD("get_radius"), &CircleShape2D::get_radius); - - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:px"), "set_radius", "get_radius"); -} - -Rect2 CircleShape2D::get_rect() const { - Rect2 rect; - rect.position = -Point2(get_radius(), get_radius()); - rect.size = Point2(get_radius(), get_radius()) * 2.0; - return rect; -} - -real_t CircleShape2D::get_enclosing_radius() const { - return radius; -} - -void CircleShape2D::draw(const RID &p_to_rid, const Color &p_color) { - Vector points; - points.resize(24); - - const real_t turn_step = Math_TAU / 24.0; - for (int i = 0; i < 24; i++) { - points.write[i] = Vector2(Math::cos(i * turn_step), Math::sin(i * turn_step)) * get_radius(); - } - - Vector col = { p_color }; - RenderingServer::get_singleton()->canvas_item_add_polygon(p_to_rid, points, col); - - if (is_collision_outline_enabled()) { - points.push_back(points[0]); - col = { Color(p_color, 1.0) }; - RenderingServer::get_singleton()->canvas_item_add_polyline(p_to_rid, points, col); - } -} - -CircleShape2D::CircleShape2D() : - Shape2D(PhysicsServer2D::get_singleton()->circle_shape_create()) { - _update_shape(); -} diff --git a/scene/resources/circle_shape_2d.h b/scene/resources/circle_shape_2d.h deleted file mode 100644 index ab0bff4ca8..0000000000 --- a/scene/resources/circle_shape_2d.h +++ /dev/null @@ -1,58 +0,0 @@ -/**************************************************************************/ -/* circle_shape_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 CIRCLE_SHAPE_2D_H -#define CIRCLE_SHAPE_2D_H - -#include "scene/resources/shape_2d.h" - -class CircleShape2D : public Shape2D { - GDCLASS(CircleShape2D, Shape2D); - - real_t radius = 10.0; - void _update_shape(); - -protected: - static void _bind_methods(); - -public: - virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override; - - void set_radius(real_t p_radius); - real_t get_radius() const; - - virtual void draw(const RID &p_to_rid, const Color &p_color) override; - virtual Rect2 get_rect() const override; - virtual real_t get_enclosing_radius() const override; - - CircleShape2D(); -}; - -#endif // CIRCLE_SHAPE_2D_H diff --git a/scene/resources/concave_polygon_shape_2d.cpp b/scene/resources/concave_polygon_shape_2d.cpp deleted file mode 100644 index 56734a8f0b..0000000000 --- a/scene/resources/concave_polygon_shape_2d.cpp +++ /dev/null @@ -1,119 +0,0 @@ -/**************************************************************************/ -/* concave_polygon_shape_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 "concave_polygon_shape_2d.h" - -#include "core/math/geometry_2d.h" -#include "servers/physics_server_2d.h" -#include "servers/rendering_server.h" - -bool ConcavePolygonShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { - Vector s = get_segments(); - int len = s.size(); - if (len == 0 || (len % 2) == 1) { - return false; - } - - const Vector2 *r = s.ptr(); - for (int i = 0; i < len; i += 2) { - Vector2 closest = Geometry2D::get_closest_point_to_segment(p_point, &r[i]); - if (p_point.distance_to(closest) < p_tolerance) { - return true; - } - } - - return false; -} - -void ConcavePolygonShape2D::set_segments(const Vector &p_segments) { - PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), p_segments); - emit_changed(); -} - -Vector ConcavePolygonShape2D::get_segments() const { - return PhysicsServer2D::get_singleton()->shape_get_data(get_rid()); -} - -void ConcavePolygonShape2D::draw(const RID &p_to_rid, const Color &p_color) { - Vector s = get_segments(); - int len = s.size(); - if (len == 0 || (len % 2) == 1) { - return; - } - - const Vector2 *r = s.ptr(); - for (int i = 0; i < len; i += 2) { - RenderingServer::get_singleton()->canvas_item_add_line(p_to_rid, r[i], r[i + 1], p_color, 2); - } -} - -Rect2 ConcavePolygonShape2D::get_rect() const { - Vector s = get_segments(); - int len = s.size(); - if (len == 0) { - return Rect2(); - } - - Rect2 rect; - - const Vector2 *r = s.ptr(); - for (int i = 0; i < len; i++) { - if (i == 0) { - rect.position = r[i]; - } else { - rect.expand_to(r[i]); - } - } - - return rect; -} - -real_t ConcavePolygonShape2D::get_enclosing_radius() const { - Vector data = get_segments(); - const Vector2 *read = data.ptr(); - real_t r = 0.0; - for (int i(0); i < data.size(); i++) { - r = MAX(read[i].length_squared(), r); - } - return Math::sqrt(r); -} - -void ConcavePolygonShape2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_segments", "segments"), &ConcavePolygonShape2D::set_segments); - ClassDB::bind_method(D_METHOD("get_segments"), &ConcavePolygonShape2D::get_segments); - - ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "segments"), "set_segments", "get_segments"); -} - -ConcavePolygonShape2D::ConcavePolygonShape2D() : - Shape2D(PhysicsServer2D::get_singleton()->concave_polygon_shape_create()) { - Vector empty; - set_segments(empty); -} diff --git a/scene/resources/concave_polygon_shape_2d.h b/scene/resources/concave_polygon_shape_2d.h deleted file mode 100644 index 7b3540a20d..0000000000 --- a/scene/resources/concave_polygon_shape_2d.h +++ /dev/null @@ -1,55 +0,0 @@ -/**************************************************************************/ -/* concave_polygon_shape_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 CONCAVE_POLYGON_SHAPE_2D_H -#define CONCAVE_POLYGON_SHAPE_2D_H - -#include "scene/resources/shape_2d.h" - -class ConcavePolygonShape2D : public Shape2D { - GDCLASS(ConcavePolygonShape2D, Shape2D); - -protected: - static void _bind_methods(); - -public: - virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override; - - void set_segments(const Vector &p_segments); - Vector get_segments() const; - - virtual void draw(const RID &p_to_rid, const Color &p_color) override; - virtual Rect2 get_rect() const override; - virtual real_t get_enclosing_radius() const override; - - ConcavePolygonShape2D(); -}; - -#endif // CONCAVE_POLYGON_SHAPE_2D_H diff --git a/scene/resources/convex_polygon_shape_2d.cpp b/scene/resources/convex_polygon_shape_2d.cpp deleted file mode 100644 index 0d9e570149..0000000000 --- a/scene/resources/convex_polygon_shape_2d.cpp +++ /dev/null @@ -1,143 +0,0 @@ -/**************************************************************************/ -/* convex_polygon_shape_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 "convex_polygon_shape_2d.h" - -#include "core/math/geometry_2d.h" -#include "servers/physics_server_2d.h" -#include "servers/rendering_server.h" - -bool ConvexPolygonShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { - return Geometry2D::is_point_in_polygon(p_point, points); -} - -#ifdef DEBUG_ENABLED -// Check if point p3 is to the left of [p1, p2] segment or on it. -bool left_test(const Vector2 &p1, const Vector2 &p2, const Vector2 &p3) { - Vector2 p12 = p2 - p1; - Vector2 p13 = p3 - p1; - // If the value of the cross product is positive or zero; p3 is to the left or on the segment, respectively. - return p12.cross(p13) >= 0; -} - -bool is_convex(const Vector &p_points) { - // Pre-condition: Polygon is in counter-clockwise order. - int polygon_size = p_points.size(); - for (int i = 0; i < polygon_size && polygon_size > 3; i++) { - int j = (i + 1) % polygon_size; - int k = (j + 1) % polygon_size; - // If any consecutive three points fail left-test, then there is a concavity. - if (!left_test(p_points[i], p_points[j], p_points[k])) { - return false; - } - } - - return true; -} -#endif - -void ConvexPolygonShape2D::_update_shape() { - Vector final_points = points; - if (Geometry2D::is_polygon_clockwise(final_points)) { //needs to be counter clockwise - final_points.reverse(); - } -#ifdef DEBUG_ENABLED - if (!is_convex(final_points)) { - WARN_PRINT("Concave polygon is assigned to ConvexPolygonShape2D."); - } -#endif - PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), final_points); - emit_changed(); -} - -void ConvexPolygonShape2D::set_point_cloud(const Vector &p_points) { - Vector hull = Geometry2D::convex_hull(p_points); - ERR_FAIL_COND(hull.size() < 3); - set_points(hull); -} - -void ConvexPolygonShape2D::set_points(const Vector &p_points) { - points = p_points; - - _update_shape(); -} - -Vector ConvexPolygonShape2D::get_points() const { - return points; -} - -void ConvexPolygonShape2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_point_cloud", "point_cloud"), &ConvexPolygonShape2D::set_point_cloud); - ClassDB::bind_method(D_METHOD("set_points", "points"), &ConvexPolygonShape2D::set_points); - ClassDB::bind_method(D_METHOD("get_points"), &ConvexPolygonShape2D::get_points); - - ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "points"), "set_points", "get_points"); -} - -void ConvexPolygonShape2D::draw(const RID &p_to_rid, const Color &p_color) { - if (points.size() < 3) { - return; - } - - Vector col = { p_color }; - RenderingServer::get_singleton()->canvas_item_add_polygon(p_to_rid, points, col); - - if (is_collision_outline_enabled()) { - col = { Color(p_color, 1.0) }; - RenderingServer::get_singleton()->canvas_item_add_polyline(p_to_rid, points, col); - // Draw the last segment. - RenderingServer::get_singleton()->canvas_item_add_line(p_to_rid, points[points.size() - 1], points[0], Color(p_color, 1.0)); - } -} - -Rect2 ConvexPolygonShape2D::get_rect() const { - Rect2 rect; - for (int i = 0; i < points.size(); i++) { - if (i == 0) { - rect.position = points[i]; - } else { - rect.expand_to(points[i]); - } - } - - return rect; -} - -real_t ConvexPolygonShape2D::get_enclosing_radius() const { - real_t r = 0.0; - for (int i(0); i < get_points().size(); i++) { - r = MAX(get_points()[i].length_squared(), r); - } - return Math::sqrt(r); -} - -ConvexPolygonShape2D::ConvexPolygonShape2D() : - Shape2D(PhysicsServer2D::get_singleton()->convex_polygon_shape_create()) { -} diff --git a/scene/resources/convex_polygon_shape_2d.h b/scene/resources/convex_polygon_shape_2d.h deleted file mode 100644 index 71b98a0d25..0000000000 --- a/scene/resources/convex_polygon_shape_2d.h +++ /dev/null @@ -1,59 +0,0 @@ -/**************************************************************************/ -/* convex_polygon_shape_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 CONVEX_POLYGON_SHAPE_2D_H -#define CONVEX_POLYGON_SHAPE_2D_H - -#include "scene/resources/shape_2d.h" - -class ConvexPolygonShape2D : public Shape2D { - GDCLASS(ConvexPolygonShape2D, Shape2D); - - Vector points; - void _update_shape(); - -protected: - static void _bind_methods(); - -public: - virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override; - - void set_point_cloud(const Vector &p_points); - void set_points(const Vector &p_points); - Vector get_points() const; - - virtual void draw(const RID &p_to_rid, const Color &p_color) override; - virtual Rect2 get_rect() const override; - virtual real_t get_enclosing_radius() const override; - - ConvexPolygonShape2D(); -}; - -#endif // CONVEX_POLYGON_SHAPE_2D_H diff --git a/scene/resources/polygon_path_finder.cpp b/scene/resources/polygon_path_finder.cpp deleted file mode 100644 index 617a53f0a3..0000000000 --- a/scene/resources/polygon_path_finder.cpp +++ /dev/null @@ -1,563 +0,0 @@ -/**************************************************************************/ -/* polygon_path_finder.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 "polygon_path_finder.h" -#include "core/math/geometry_2d.h" - -bool PolygonPathFinder::_is_point_inside(const Vector2 &p_point) const { - int crosses = 0; - - for (const Edge &E : edges) { - const Edge &e = E; - - Vector2 a = points[e.points[0]].pos; - Vector2 b = points[e.points[1]].pos; - - if (Geometry2D::segment_intersects_segment(a, b, p_point, outside_point, nullptr)) { - crosses++; - } - } - - return crosses & 1; -} - -void PolygonPathFinder::setup(const Vector &p_points, const Vector &p_connections) { - ERR_FAIL_COND(p_connections.size() & 1); - - points.clear(); - edges.clear(); - - //insert points - - int point_count = p_points.size(); - points.resize(point_count + 2); - bounds = Rect2(); - - for (int i = 0; i < p_points.size(); i++) { - points.write[i].pos = p_points[i]; - points.write[i].penalty = 0; - - outside_point.x = i == 0 ? p_points[0].x : (MAX(p_points[i].x, outside_point.x)); - outside_point.y = i == 0 ? p_points[0].y : (MAX(p_points[i].y, outside_point.y)); - - if (i == 0) { - bounds.position = points[i].pos; - } else { - bounds.expand_to(points[i].pos); - } - } - - outside_point.x += 20.451 + Math::randf() * 10.2039; - outside_point.y += 21.193 + Math::randf() * 12.5412; - - //insert edges (which are also connections) - - for (int i = 0; i < p_connections.size(); i += 2) { - Edge e(p_connections[i], p_connections[i + 1]); - ERR_FAIL_INDEX(e.points[0], point_count); - ERR_FAIL_INDEX(e.points[1], point_count); - points.write[p_connections[i]].connections.insert(p_connections[i + 1]); - points.write[p_connections[i + 1]].connections.insert(p_connections[i]); - edges.insert(e); - } - - //fill the remaining connections based on visibility - - for (int i = 0; i < point_count; i++) { - for (int j = i + 1; j < point_count; j++) { - if (edges.has(Edge(i, j))) { - continue; //if in edge ignore - } - - Vector2 from = points[i].pos; - Vector2 to = points[j].pos; - - if (!_is_point_inside(from * 0.5 + to * 0.5)) { //connection between points in inside space - continue; - } - - bool valid = true; - - for (const Edge &E : edges) { - const Edge &e = E; - if (e.points[0] == i || e.points[1] == i || e.points[0] == j || e.points[1] == j) { - continue; - } - - Vector2 a = points[e.points[0]].pos; - Vector2 b = points[e.points[1]].pos; - - if (Geometry2D::segment_intersects_segment(a, b, from, to, nullptr)) { - valid = false; - break; - } - } - - if (valid) { - points.write[i].connections.insert(j); - points.write[j].connections.insert(i); - } - } - } -} - -Vector PolygonPathFinder::find_path(const Vector2 &p_from, const Vector2 &p_to) { - Vector path; - - Vector2 from = p_from; - Vector2 to = p_to; - Edge ignore_from_edge(-1, -1); - Edge ignore_to_edge(-1, -1); - - if (!_is_point_inside(from)) { - float closest_dist = 1e20f; - Vector2 closest_point; - - for (const Edge &E : edges) { - const Edge &e = E; - Vector2 seg[2] = { - points[e.points[0]].pos, - points[e.points[1]].pos - }; - - Vector2 closest = Geometry2D::get_closest_point_to_segment(from, seg); - float d = from.distance_squared_to(closest); - - if (d < closest_dist) { - ignore_from_edge = E; - closest_dist = d; - closest_point = closest; - } - } - - from = closest_point; - }; - - if (!_is_point_inside(to)) { - float closest_dist = 1e20f; - Vector2 closest_point; - - for (const Edge &E : edges) { - const Edge &e = E; - Vector2 seg[2] = { - points[e.points[0]].pos, - points[e.points[1]].pos - }; - - Vector2 closest = Geometry2D::get_closest_point_to_segment(to, seg); - float d = to.distance_squared_to(closest); - - if (d < closest_dist) { - ignore_to_edge = E; - closest_dist = d; - closest_point = closest; - } - } - - to = closest_point; - }; - - //test direct connection - { - bool can_see_eachother = true; - - for (const Edge &E : edges) { - const Edge &e = E; - if (e.points[0] == ignore_from_edge.points[0] && e.points[1] == ignore_from_edge.points[1]) { - continue; - } - if (e.points[0] == ignore_to_edge.points[0] && e.points[1] == ignore_to_edge.points[1]) { - continue; - } - - Vector2 a = points[e.points[0]].pos; - Vector2 b = points[e.points[1]].pos; - - if (Geometry2D::segment_intersects_segment(a, b, from, to, nullptr)) { - can_see_eachother = false; - break; - } - } - - if (can_see_eachother) { - path.push_back(from); - path.push_back(to); - return path; - } - } - - //add to graph - - int aidx = points.size() - 2; - int bidx = points.size() - 1; - points.write[aidx].pos = from; - points.write[bidx].pos = to; - points.write[aidx].distance = 0; - points.write[bidx].distance = 0; - points.write[aidx].prev = -1; - points.write[bidx].prev = -1; - points.write[aidx].penalty = 0; - points.write[bidx].penalty = 0; - - for (int i = 0; i < points.size() - 2; i++) { - bool valid_a = true; - bool valid_b = true; - points.write[i].prev = -1; - points.write[i].distance = 0; - - if (!_is_point_inside(from * 0.5 + points[i].pos * 0.5)) { - valid_a = false; - } - - if (!_is_point_inside(to * 0.5 + points[i].pos * 0.5)) { - valid_b = false; - } - - for (const Edge &E : edges) { - const Edge &e = E; - - if (e.points[0] == i || e.points[1] == i) { - continue; - } - - Vector2 a = points[e.points[0]].pos; - Vector2 b = points[e.points[1]].pos; - - if (valid_a) { - if (e.points[0] != ignore_from_edge.points[1] && - e.points[1] != ignore_from_edge.points[1] && - e.points[0] != ignore_from_edge.points[0] && - e.points[1] != ignore_from_edge.points[0]) { - if (Geometry2D::segment_intersects_segment(a, b, from, points[i].pos, nullptr)) { - valid_a = false; - } - } - } - - if (valid_b) { - if (e.points[0] != ignore_to_edge.points[1] && - e.points[1] != ignore_to_edge.points[1] && - e.points[0] != ignore_to_edge.points[0] && - e.points[1] != ignore_to_edge.points[0]) { - if (Geometry2D::segment_intersects_segment(a, b, to, points[i].pos, nullptr)) { - valid_b = false; - } - } - } - - if (!valid_a && !valid_b) { - break; - } - } - - if (valid_a) { - points.write[i].connections.insert(aidx); - points.write[aidx].connections.insert(i); - } - - if (valid_b) { - points.write[i].connections.insert(bidx); - points.write[bidx].connections.insert(i); - } - } - //solve graph - - HashSet open_list; - - points.write[aidx].distance = 0; - points.write[aidx].prev = aidx; - for (const int &E : points[aidx].connections) { - open_list.insert(E); - points.write[E].distance = from.distance_to(points[E].pos); - points.write[E].prev = aidx; - } - - bool found_route = false; - - while (true) { - if (open_list.size() == 0) { - print_verbose("Open list empty."); - break; - } - //check open list - - int least_cost_point = -1; - float least_cost = 1e30; - - //this could be faster (cache previous results) - for (const int &E : open_list) { - const Point &p = points[E]; - float cost = p.distance; - cost += p.pos.distance_to(to); - cost += p.penalty; - - if (cost < least_cost) { - least_cost_point = E; - least_cost = cost; - } - } - - const Point &np = points[least_cost_point]; - //open the neighbors for search - - for (const int &E : np.connections) { - Point &p = points.write[E]; - float distance = np.pos.distance_to(p.pos) + np.distance; - - if (p.prev != -1) { - //oh this was visited already, can we win the cost? - - if (p.distance > distance) { - p.prev = least_cost_point; //reassign previous - p.distance = distance; - } - } else { - //add to open neighbors - - p.prev = least_cost_point; - p.distance = distance; - open_list.insert(E); - - if (E == bidx) { - //oh my reached end! stop algorithm - found_route = true; - break; - } - } - } - - if (found_route) { - break; - } - - open_list.erase(least_cost_point); - } - - if (found_route) { - int at = bidx; - path.push_back(points[at].pos); - do { - at = points[at].prev; - path.push_back(points[at].pos); - } while (at != aidx); - - path.reverse(); - } - - for (int i = 0; i < points.size() - 2; i++) { - points.write[i].connections.erase(aidx); - points.write[i].connections.erase(bidx); - points.write[i].prev = -1; - points.write[i].distance = 0; - } - - points.write[aidx].connections.clear(); - points.write[aidx].prev = -1; - points.write[aidx].distance = 0; - points.write[bidx].connections.clear(); - points.write[bidx].prev = -1; - points.write[bidx].distance = 0; - - return path; -} - -void PolygonPathFinder::_set_data(const Dictionary &p_data) { - ERR_FAIL_COND(!p_data.has("points")); - ERR_FAIL_COND(!p_data.has("connections")); - ERR_FAIL_COND(!p_data.has("segments")); - ERR_FAIL_COND(!p_data.has("bounds")); - - Vector p = p_data["points"]; - Array c = p_data["connections"]; - - ERR_FAIL_COND(c.size() != p.size()); - if (c.size()) { - return; - } - - int pc = p.size(); - points.resize(pc + 2); - - const Vector2 *pr = p.ptr(); - for (int i = 0; i < pc; i++) { - points.write[i].pos = pr[i]; - Vector con = c[i]; - const int *cr = con.ptr(); - int cc = con.size(); - for (int j = 0; j < cc; j++) { - points.write[i].connections.insert(cr[j]); - } - } - - if (p_data.has("penalties")) { - Vector penalties = p_data["penalties"]; - if (penalties.size() == pc) { - const real_t *pr2 = penalties.ptr(); - for (int i = 0; i < pc; i++) { - points.write[i].penalty = pr2[i]; - } - } - } - - Vector segs = p_data["segments"]; - int sc = segs.size(); - ERR_FAIL_COND(sc & 1); - const int *sr = segs.ptr(); - for (int i = 0; i < sc; i += 2) { - Edge e(sr[i], sr[i + 1]); - edges.insert(e); - } - bounds = p_data["bounds"]; -} - -Dictionary PolygonPathFinder::_get_data() const { - Dictionary d; - Vector p; - Vector ind; - Array path_connections; - p.resize(MAX(0, points.size() - 2)); - path_connections.resize(MAX(0, points.size() - 2)); - ind.resize(edges.size() * 2); - Vector penalties; - penalties.resize(MAX(0, points.size() - 2)); - { - Vector2 *wp = p.ptrw(); - real_t *pw = penalties.ptrw(); - - for (int i = 0; i < points.size() - 2; i++) { - wp[i] = points[i].pos; - pw[i] = points[i].penalty; - Vector c; - c.resize(points[i].connections.size()); - { - int *cw = c.ptrw(); - int idx = 0; - for (const int &E : points[i].connections) { - cw[idx++] = E; - } - } - path_connections[i] = c; - } - } - { - int *iw = ind.ptrw(); - int idx = 0; - for (const Edge &E : edges) { - iw[idx++] = E.points[0]; - iw[idx++] = E.points[1]; - } - } - - d["bounds"] = bounds; - d["points"] = p; - d["penalties"] = penalties; - d["connections"] = path_connections; - d["segments"] = ind; - - return d; -} - -bool PolygonPathFinder::is_point_inside(const Vector2 &p_point) const { - return _is_point_inside(p_point); -} - -Vector2 PolygonPathFinder::get_closest_point(const Vector2 &p_point) const { - float closest_dist = 1e20f; - Vector2 closest_point; - - for (const Edge &E : edges) { - const Edge &e = E; - Vector2 seg[2] = { - points[e.points[0]].pos, - points[e.points[1]].pos - }; - - Vector2 closest = Geometry2D::get_closest_point_to_segment(p_point, seg); - float d = p_point.distance_squared_to(closest); - - if (d < closest_dist) { - closest_dist = d; - closest_point = closest; - } - } - - ERR_FAIL_COND_V(Math::is_equal_approx(closest_dist, 1e20f), Vector2()); - - return closest_point; -} - -Vector PolygonPathFinder::get_intersections(const Vector2 &p_from, const Vector2 &p_to) const { - Vector inters; - - for (const Edge &E : edges) { - Vector2 a = points[E.points[0]].pos; - Vector2 b = points[E.points[1]].pos; - - Vector2 res; - if (Geometry2D::segment_intersects_segment(a, b, p_from, p_to, &res)) { - inters.push_back(res); - } - } - - return inters; -} - -Rect2 PolygonPathFinder::get_bounds() const { - return bounds; -} - -void PolygonPathFinder::set_point_penalty(int p_point, float p_penalty) { - ERR_FAIL_INDEX(p_point, points.size() - 2); - points.write[p_point].penalty = p_penalty; -} - -float PolygonPathFinder::get_point_penalty(int p_point) const { - ERR_FAIL_INDEX_V(p_point, points.size() - 2, 0); - return points[p_point].penalty; -} - -void PolygonPathFinder::_bind_methods() { - ClassDB::bind_method(D_METHOD("setup", "points", "connections"), &PolygonPathFinder::setup); - ClassDB::bind_method(D_METHOD("find_path", "from", "to"), &PolygonPathFinder::find_path); - ClassDB::bind_method(D_METHOD("get_intersections", "from", "to"), &PolygonPathFinder::get_intersections); - ClassDB::bind_method(D_METHOD("get_closest_point", "point"), &PolygonPathFinder::get_closest_point); - ClassDB::bind_method(D_METHOD("is_point_inside", "point"), &PolygonPathFinder::is_point_inside); - ClassDB::bind_method(D_METHOD("set_point_penalty", "idx", "penalty"), &PolygonPathFinder::set_point_penalty); - ClassDB::bind_method(D_METHOD("get_point_penalty", "idx"), &PolygonPathFinder::get_point_penalty); - - ClassDB::bind_method(D_METHOD("get_bounds"), &PolygonPathFinder::get_bounds); - ClassDB::bind_method(D_METHOD("_set_data", "data"), &PolygonPathFinder::_set_data); - ClassDB::bind_method(D_METHOD("_get_data"), &PolygonPathFinder::_get_data); - - ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_data", "_get_data"); -} - -PolygonPathFinder::PolygonPathFinder() { -} diff --git a/scene/resources/polygon_path_finder.h b/scene/resources/polygon_path_finder.h deleted file mode 100644 index a6cf6cd584..0000000000 --- a/scene/resources/polygon_path_finder.h +++ /dev/null @@ -1,98 +0,0 @@ -/**************************************************************************/ -/* polygon_path_finder.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 POLYGON_PATH_FINDER_H -#define POLYGON_PATH_FINDER_H - -#include "core/io/resource.h" - -class PolygonPathFinder : public Resource { - GDCLASS(PolygonPathFinder, Resource); - - struct Point { - Vector2 pos; - HashSet connections; - float distance = 0.0; - float penalty = 0.0; - int prev = 0; - }; - - union Edge { - struct { - int32_t points[2]; - }; - uint64_t key = 0; - - _FORCE_INLINE_ bool operator==(const Edge &p_edge) const { - return key == p_edge.key; - } - _FORCE_INLINE_ static uint32_t hash(const Edge &p_edge) { - return hash_one_uint64(p_edge.key); - } - - Edge(int a = 0, int b = 0) { - if (a > b) { - SWAP(a, b); - } - points[0] = a; - points[1] = b; - } - }; - - Vector2 outside_point; - Rect2 bounds; - - Vector points; - HashSet edges; - - bool _is_point_inside(const Vector2 &p_point) const; - - void _set_data(const Dictionary &p_data); - Dictionary _get_data() const; - -protected: - static void _bind_methods(); - -public: - void setup(const Vector &p_points, const Vector &p_connections); - Vector find_path(const Vector2 &p_from, const Vector2 &p_to); - - void set_point_penalty(int p_point, float p_penalty); - float get_point_penalty(int p_point) const; - - bool is_point_inside(const Vector2 &p_point) const; - Vector2 get_closest_point(const Vector2 &p_point) const; - Vector get_intersections(const Vector2 &p_from, const Vector2 &p_to) const; - Rect2 get_bounds() const; - - PolygonPathFinder(); -}; - -#endif // POLYGON_PATH_FINDER_H diff --git a/scene/resources/rectangle_shape_2d.cpp b/scene/resources/rectangle_shape_2d.cpp deleted file mode 100644 index 65b1653293..0000000000 --- a/scene/resources/rectangle_shape_2d.cpp +++ /dev/null @@ -1,107 +0,0 @@ -/**************************************************************************/ -/* rectangle_shape_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 "rectangle_shape_2d.h" - -#include "servers/physics_server_2d.h" -#include "servers/rendering_server.h" -void RectangleShape2D::_update_shape() { - PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), size * 0.5); - emit_changed(); -} - -#ifndef DISABLE_DEPRECATED -bool RectangleShape2D::_set(const StringName &p_name, const Variant &p_value) { - if (p_name == "extents") { // Compatibility with Godot 3.x. - // Convert to `size`, twice as big. - set_size((Size2)p_value * 2); - return true; - } - return false; -} - -bool RectangleShape2D::_get(const StringName &p_name, Variant &r_property) const { - if (p_name == "extents") { // Compatibility with Godot 3.x. - // Convert to `extents`, half as big. - r_property = size / 2; - return true; - } - return false; -} -#endif // DISABLE_DEPRECATED - -void RectangleShape2D::set_size(const Size2 &p_size) { - ERR_FAIL_COND_MSG(p_size.x < 0 || p_size.y < 0, "RectangleShape2D size cannot be negative."); - size = p_size; - _update_shape(); -} - -Size2 RectangleShape2D::get_size() const { - return size; -} - -void RectangleShape2D::draw(const RID &p_to_rid, const Color &p_color) { - RenderingServer::get_singleton()->canvas_item_add_rect(p_to_rid, Rect2(-size * 0.5, size), p_color); - if (is_collision_outline_enabled()) { - // Draw an outlined rectangle to make individual shapes easier to distinguish. - Vector stroke_points; - stroke_points.resize(5); - stroke_points.write[0] = -size * 0.5; - stroke_points.write[1] = Vector2(size.x, -size.y) * 0.5; - stroke_points.write[2] = size * 0.5; - stroke_points.write[3] = Vector2(-size.x, size.y) * 0.5; - stroke_points.write[4] = -size * 0.5; - - Vector stroke_colors = { Color(p_color, 1.0) }; - - RenderingServer::get_singleton()->canvas_item_add_polyline(p_to_rid, stroke_points, stroke_colors); - } -} - -Rect2 RectangleShape2D::get_rect() const { - return Rect2(-size * 0.5, size); -} - -real_t RectangleShape2D::get_enclosing_radius() const { - return size.length() / 2; -} - -void RectangleShape2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_size", "size"), &RectangleShape2D::set_size); - ClassDB::bind_method(D_METHOD("get_size"), &RectangleShape2D::get_size); - - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size", PROPERTY_HINT_NONE, "suffix:px"), "set_size", "get_size"); -} - -RectangleShape2D::RectangleShape2D() : - Shape2D(PhysicsServer2D::get_singleton()->rectangle_shape_create()) { - size = Size2(20, 20); - _update_shape(); -} diff --git a/scene/resources/rectangle_shape_2d.h b/scene/resources/rectangle_shape_2d.h deleted file mode 100644 index f990b5967e..0000000000 --- a/scene/resources/rectangle_shape_2d.h +++ /dev/null @@ -1,60 +0,0 @@ -/**************************************************************************/ -/* rectangle_shape_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 RECTANGLE_SHAPE_2D_H -#define RECTANGLE_SHAPE_2D_H - -#include "scene/resources/shape_2d.h" - -class RectangleShape2D : public Shape2D { - GDCLASS(RectangleShape2D, Shape2D); - - Size2 size; - void _update_shape(); - -protected: - static void _bind_methods(); -#ifndef DISABLE_DEPRECATED - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_property) const; -#endif // DISABLE_DEPRECATED - -public: - void set_size(const Size2 &p_size); - Size2 get_size() const; - - virtual void draw(const RID &p_to_rid, const Color &p_color) override; - virtual Rect2 get_rect() const override; - virtual real_t get_enclosing_radius() const override; - - RectangleShape2D(); -}; - -#endif // RECTANGLE_SHAPE_2D_H diff --git a/scene/resources/segment_shape_2d.cpp b/scene/resources/segment_shape_2d.cpp deleted file mode 100644 index 864b4c952b..0000000000 --- a/scene/resources/segment_shape_2d.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/**************************************************************************/ -/* segment_shape_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 "segment_shape_2d.h" - -#include "core/math/geometry_2d.h" -#include "servers/physics_server_2d.h" -#include "servers/rendering_server.h" - -bool SegmentShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { - Vector2 l[2] = { a, b }; - Vector2 closest = Geometry2D::get_closest_point_to_segment(p_point, l); - return p_point.distance_to(closest) < p_tolerance; -} - -void SegmentShape2D::_update_shape() { - Rect2 r; - r.position = a; - r.size = b; - PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), r); - emit_changed(); -} - -void SegmentShape2D::set_a(const Vector2 &p_a) { - a = p_a; - _update_shape(); -} - -Vector2 SegmentShape2D::get_a() const { - return a; -} - -void SegmentShape2D::set_b(const Vector2 &p_b) { - b = p_b; - _update_shape(); -} - -Vector2 SegmentShape2D::get_b() const { - return b; -} - -void SegmentShape2D::draw(const RID &p_to_rid, const Color &p_color) { - RenderingServer::get_singleton()->canvas_item_add_line(p_to_rid, a, b, p_color, 3); -} - -Rect2 SegmentShape2D::get_rect() const { - Rect2 rect; - rect.position = a; - rect.expand_to(b); - return rect; -} - -real_t SegmentShape2D::get_enclosing_radius() const { - return (a + b).length(); -} - -void SegmentShape2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_a", "a"), &SegmentShape2D::set_a); - ClassDB::bind_method(D_METHOD("get_a"), &SegmentShape2D::get_a); - - ClassDB::bind_method(D_METHOD("set_b", "b"), &SegmentShape2D::set_b); - ClassDB::bind_method(D_METHOD("get_b"), &SegmentShape2D::get_b); - - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "a", PROPERTY_HINT_NONE, "suffix:px"), "set_a", "get_a"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "b", PROPERTY_HINT_NONE, "suffix:px"), "set_b", "get_b"); -} - -SegmentShape2D::SegmentShape2D() : - Shape2D(PhysicsServer2D::get_singleton()->segment_shape_create()) { - a = Vector2(); - b = Vector2(0, 10); - _update_shape(); -} diff --git a/scene/resources/segment_shape_2d.h b/scene/resources/segment_shape_2d.h deleted file mode 100644 index 4bec192fd2..0000000000 --- a/scene/resources/segment_shape_2d.h +++ /dev/null @@ -1,63 +0,0 @@ -/**************************************************************************/ -/* segment_shape_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 SEGMENT_SHAPE_2D_H -#define SEGMENT_SHAPE_2D_H - -#include "scene/resources/shape_2d.h" - -class SegmentShape2D : public Shape2D { - GDCLASS(SegmentShape2D, Shape2D); - - Vector2 a; - Vector2 b; - - void _update_shape(); - -protected: - static void _bind_methods(); - -public: - virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override; - - void set_a(const Vector2 &p_a); - void set_b(const Vector2 &p_b); - - Vector2 get_a() const; - Vector2 get_b() const; - - virtual void draw(const RID &p_to_rid, const Color &p_color) override; - virtual Rect2 get_rect() const override; - virtual real_t get_enclosing_radius() const override; - - SegmentShape2D(); -}; - -#endif // SEGMENT_SHAPE_2D_H diff --git a/scene/resources/separation_ray_shape_2d.cpp b/scene/resources/separation_ray_shape_2d.cpp deleted file mode 100644 index 83d526626d..0000000000 --- a/scene/resources/separation_ray_shape_2d.cpp +++ /dev/null @@ -1,117 +0,0 @@ -/**************************************************************************/ -/* separation_ray_shape_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 "separation_ray_shape_2d.h" - -#include "servers/physics_server_2d.h" -#include "servers/rendering_server.h" - -void SeparationRayShape2D::_update_shape() { - Dictionary d; - d["length"] = length; - d["slide_on_slope"] = slide_on_slope; - PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), d); - emit_changed(); -} - -void SeparationRayShape2D::draw(const RID &p_to_rid, const Color &p_color) { - const Vector2 target_position = Vector2(0, get_length()); - - const float max_arrow_size = 6; - const float line_width = 1.4; - bool no_line = target_position.length() < line_width; - float arrow_size = CLAMP(target_position.length() * 2 / 3, line_width, max_arrow_size); - - if (no_line) { - arrow_size = target_position.length(); - } else { - RS::get_singleton()->canvas_item_add_line(p_to_rid, Vector2(), target_position - target_position.normalized() * arrow_size, p_color, line_width); - } - - Transform2D xf; - xf.rotate(target_position.angle()); - xf.translate_local(Vector2(no_line ? 0 : target_position.length() - arrow_size, 0)); - - Vector pts = { - xf.xform(Vector2(arrow_size, 0)), - xf.xform(Vector2(0, 0.5 * arrow_size)), - xf.xform(Vector2(0, -0.5 * arrow_size)) - }; - - Vector cols = { p_color, p_color, p_color }; - - RS::get_singleton()->canvas_item_add_primitive(p_to_rid, pts, cols, Vector(), RID()); -} - -Rect2 SeparationRayShape2D::get_rect() const { - Rect2 rect; - rect.position = Vector2(); - rect.expand_to(Vector2(0, length)); - rect = rect.grow(Math_SQRT12 * 4); - return rect; -} - -real_t SeparationRayShape2D::get_enclosing_radius() const { - return length; -} - -void SeparationRayShape2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_length", "length"), &SeparationRayShape2D::set_length); - ClassDB::bind_method(D_METHOD("get_length"), &SeparationRayShape2D::get_length); - - ClassDB::bind_method(D_METHOD("set_slide_on_slope", "active"), &SeparationRayShape2D::set_slide_on_slope); - ClassDB::bind_method(D_METHOD("get_slide_on_slope"), &SeparationRayShape2D::get_slide_on_slope); - - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "0.01,1024,0.01,or_greater,suffix:px"), "set_length", "get_length"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_slope"), "set_slide_on_slope", "get_slide_on_slope"); -} - -void SeparationRayShape2D::set_length(real_t p_length) { - length = p_length; - _update_shape(); -} - -real_t SeparationRayShape2D::get_length() const { - return length; -} - -void SeparationRayShape2D::set_slide_on_slope(bool p_active) { - slide_on_slope = p_active; - _update_shape(); -} - -bool SeparationRayShape2D::get_slide_on_slope() const { - return slide_on_slope; -} - -SeparationRayShape2D::SeparationRayShape2D() : - Shape2D(PhysicsServer2D::get_singleton()->separation_ray_shape_create()) { - _update_shape(); -} diff --git a/scene/resources/separation_ray_shape_2d.h b/scene/resources/separation_ray_shape_2d.h deleted file mode 100644 index 8eab73d9c5..0000000000 --- a/scene/resources/separation_ray_shape_2d.h +++ /dev/null @@ -1,61 +0,0 @@ -/**************************************************************************/ -/* separation_ray_shape_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 SEPARATION_RAY_SHAPE_2D_H -#define SEPARATION_RAY_SHAPE_2D_H - -#include "scene/resources/shape_2d.h" - -class SeparationRayShape2D : public Shape2D { - GDCLASS(SeparationRayShape2D, Shape2D); - - real_t length = 20.0; - bool slide_on_slope = false; - - void _update_shape(); - -protected: - static void _bind_methods(); - -public: - void set_length(real_t p_length); - real_t get_length() const; - - void set_slide_on_slope(bool p_active); - bool get_slide_on_slope() const; - - virtual void draw(const RID &p_to_rid, const Color &p_color) override; - virtual Rect2 get_rect() const override; - virtual real_t get_enclosing_radius() const override; - - SeparationRayShape2D(); -}; - -#endif // SEPARATION_RAY_SHAPE_2D_H diff --git a/scene/resources/shape_2d.cpp b/scene/resources/shape_2d.cpp deleted file mode 100644 index 2de9b70f53..0000000000 --- a/scene/resources/shape_2d.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/**************************************************************************/ -/* shape_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 "shape_2d.h" - -#include "core/config/engine.h" -#include "core/config/project_settings.h" -#include "servers/physics_server_2d.h" - -RID Shape2D::get_rid() const { - return shape; -} - -void Shape2D::set_custom_solver_bias(real_t p_bias) { - custom_bias = p_bias; - PhysicsServer2D::get_singleton()->shape_set_custom_solver_bias(shape, custom_bias); -} - -real_t Shape2D::get_custom_solver_bias() const { - return custom_bias; -} - -bool Shape2D::collide_with_motion(const Transform2D &p_local_xform, const Vector2 &p_local_motion, const Ref &p_shape, const Transform2D &p_shape_xform, const Vector2 &p_shape_motion) { - ERR_FAIL_COND_V(p_shape.is_null(), false); - int r; - return PhysicsServer2D::get_singleton()->shape_collide(get_rid(), p_local_xform, p_local_motion, p_shape->get_rid(), p_shape_xform, p_shape_motion, nullptr, 0, r); -} - -bool Shape2D::collide(const Transform2D &p_local_xform, const Ref &p_shape, const Transform2D &p_shape_xform) { - ERR_FAIL_COND_V(p_shape.is_null(), false); - int r; - return PhysicsServer2D::get_singleton()->shape_collide(get_rid(), p_local_xform, Vector2(), p_shape->get_rid(), p_shape_xform, Vector2(), nullptr, 0, r); -} - -PackedVector2Array Shape2D::collide_with_motion_and_get_contacts(const Transform2D &p_local_xform, const Vector2 &p_local_motion, const Ref &p_shape, const Transform2D &p_shape_xform, const Vector2 &p_shape_motion) { - ERR_FAIL_COND_V(p_shape.is_null(), PackedVector2Array()); - const int max_contacts = 16; - Vector2 result[max_contacts * 2]; - int contacts = 0; - - if (!PhysicsServer2D::get_singleton()->shape_collide(get_rid(), p_local_xform, p_local_motion, p_shape->get_rid(), p_shape_xform, p_shape_motion, result, max_contacts, contacts)) { - return PackedVector2Array(); - } - - PackedVector2Array results; - results.resize(contacts * 2); - for (int i = 0; i < contacts * 2; i++) { - results.write[i] = result[i]; - } - - return results; -} - -PackedVector2Array Shape2D::collide_and_get_contacts(const Transform2D &p_local_xform, const Ref &p_shape, const Transform2D &p_shape_xform) { - ERR_FAIL_COND_V(p_shape.is_null(), PackedVector2Array()); - const int max_contacts = 16; - Vector2 result[max_contacts * 2]; - int contacts = 0; - - if (!PhysicsServer2D::get_singleton()->shape_collide(get_rid(), p_local_xform, Vector2(), p_shape->get_rid(), p_shape_xform, Vector2(), result, max_contacts, contacts)) { - return PackedVector2Array(); - } - - PackedVector2Array results; - results.resize(contacts * 2); - for (int i = 0; i < contacts * 2; i++) { - results.write[i] = result[i]; - } - - return results; -} - -void Shape2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_custom_solver_bias", "bias"), &Shape2D::set_custom_solver_bias); - ClassDB::bind_method(D_METHOD("get_custom_solver_bias"), &Shape2D::get_custom_solver_bias); - ClassDB::bind_method(D_METHOD("collide", "local_xform", "with_shape", "shape_xform"), &Shape2D::collide); - ClassDB::bind_method(D_METHOD("collide_with_motion", "local_xform", "local_motion", "with_shape", "shape_xform", "shape_motion"), &Shape2D::collide_with_motion); - ClassDB::bind_method(D_METHOD("collide_and_get_contacts", "local_xform", "with_shape", "shape_xform"), &Shape2D::collide_and_get_contacts); - ClassDB::bind_method(D_METHOD("collide_with_motion_and_get_contacts", "local_xform", "local_motion", "with_shape", "shape_xform", "shape_motion"), &Shape2D::collide_with_motion_and_get_contacts); - ClassDB::bind_method(D_METHOD("draw", "canvas_item", "color"), &Shape2D::draw); - ClassDB::bind_method(D_METHOD("get_rect"), &Shape2D::get_rect); - - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "custom_solver_bias", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_custom_solver_bias", "get_custom_solver_bias"); -} - -bool Shape2D::is_collision_outline_enabled() { -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - return true; - } -#endif - return GLOBAL_GET("debug/shapes/collision/draw_2d_outlines"); -} - -Shape2D::Shape2D(const RID &p_rid) { - shape = p_rid; -} - -Shape2D::~Shape2D() { - ERR_FAIL_NULL(PhysicsServer2D::get_singleton()); - PhysicsServer2D::get_singleton()->free(shape); -} diff --git a/scene/resources/shape_2d.h b/scene/resources/shape_2d.h deleted file mode 100644 index b1a5b24d05..0000000000 --- a/scene/resources/shape_2d.h +++ /dev/null @@ -1,70 +0,0 @@ -/**************************************************************************/ -/* shape_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 SHAPE_2D_H -#define SHAPE_2D_H - -#include "core/io/resource.h" - -class Shape2D : public Resource { - GDCLASS(Shape2D, Resource); - OBJ_SAVE_TYPE(Shape2D); - - RID shape; - real_t custom_bias = 0.0; - -protected: - static void _bind_methods(); - Shape2D(const RID &p_rid); - -public: - virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { return get_rect().has_point(p_point); } - - void set_custom_solver_bias(real_t p_bias); - real_t get_custom_solver_bias() const; - - bool collide_with_motion(const Transform2D &p_local_xform, const Vector2 &p_local_motion, const Ref &p_shape, const Transform2D &p_shape_xform, const Vector2 &p_shape_motion); - bool collide(const Transform2D &p_local_xform, const Ref &p_shape, const Transform2D &p_shape_xform); - - PackedVector2Array collide_with_motion_and_get_contacts(const Transform2D &p_local_xform, const Vector2 &p_local_motion, const Ref &p_shape, const Transform2D &p_shape_xform, const Vector2 &p_shape_motion); - PackedVector2Array collide_and_get_contacts(const Transform2D &p_local_xform, const Ref &p_shape, const Transform2D &p_shape_xform); - - virtual void draw(const RID &p_to_rid, const Color &p_color) {} - virtual Rect2 get_rect() const { return Rect2(); } - /// Returns the radius of a circle that fully enclose this shape - virtual real_t get_enclosing_radius() const = 0; - virtual RID get_rid() const override; - - static bool is_collision_outline_enabled(); - - ~Shape2D(); -}; - -#endif // SHAPE_2D_H diff --git a/scene/resources/skeleton_modification_2d.cpp b/scene/resources/skeleton_modification_2d.cpp deleted file mode 100644 index 88a0f7b92d..0000000000 --- a/scene/resources/skeleton_modification_2d.cpp +++ /dev/null @@ -1,239 +0,0 @@ -/**************************************************************************/ -/* skeleton_modification_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 "skeleton_modification_2d.h" -#include "scene/2d/skeleton_2d.h" - -#include "scene/2d/collision_object_2d.h" -#include "scene/2d/collision_shape_2d.h" -#include "scene/2d/physical_bone_2d.h" - -#ifdef TOOLS_ENABLED -#include "editor/editor_settings.h" -#endif // TOOLS_ENABLED - -/////////////////////////////////////// -// Modification2D -/////////////////////////////////////// - -void SkeletonModification2D::_execute(float p_delta) { - GDVIRTUAL_CALL(_execute, p_delta); - - if (!enabled) { - return; - } -} - -void SkeletonModification2D::_setup_modification(SkeletonModificationStack2D *p_stack) { - stack = p_stack; - if (stack) { - is_setup = true; - } else { - WARN_PRINT("Could not setup modification with name " + get_name()); - } - - GDVIRTUAL_CALL(_setup_modification, Ref(p_stack)); -} - -void SkeletonModification2D::_draw_editor_gizmo() { - GDVIRTUAL_CALL(_draw_editor_gizmo); -} - -void SkeletonModification2D::set_enabled(bool p_enabled) { - enabled = p_enabled; - -#ifdef TOOLS_ENABLED - if (editor_draw_gizmo) { - if (stack) { - stack->set_editor_gizmos_dirty(true); - } - } -#endif // TOOLS_ENABLED -} - -bool SkeletonModification2D::get_enabled() { - return enabled; -} - -float SkeletonModification2D::clamp_angle(float p_angle, float p_min_bound, float p_max_bound, bool p_invert) { - // Map to the 0 to 360 range (in radians though) instead of the -180 to 180 range. - if (p_angle < 0) { - p_angle = Math_TAU + p_angle; - } - - // Make min and max in the range of 0 to 360 (in radians), and make sure they are in the right order - if (p_min_bound < 0) { - p_min_bound = Math_TAU + p_min_bound; - } - if (p_max_bound < 0) { - p_max_bound = Math_TAU + p_max_bound; - } - if (p_min_bound > p_max_bound) { - SWAP(p_min_bound, p_max_bound); - } - - bool is_beyond_bounds = (p_angle < p_min_bound || p_angle > p_max_bound); - bool is_within_bounds = (p_angle > p_min_bound && p_angle < p_max_bound); - - // Note: May not be the most optimal way to clamp, but it always constraints to the nearest angle. - if ((!p_invert && is_beyond_bounds) || (p_invert && is_within_bounds)) { - Vector2 min_bound_vec = Vector2(Math::cos(p_min_bound), Math::sin(p_min_bound)); - Vector2 max_bound_vec = Vector2(Math::cos(p_max_bound), Math::sin(p_max_bound)); - Vector2 angle_vec = Vector2(Math::cos(p_angle), Math::sin(p_angle)); - - if (angle_vec.distance_squared_to(min_bound_vec) <= angle_vec.distance_squared_to(max_bound_vec)) { - p_angle = p_min_bound; - } else { - p_angle = p_max_bound; - } - } - - return p_angle; -} - -void SkeletonModification2D::editor_draw_angle_constraints(Bone2D *p_operation_bone, float p_min_bound, float p_max_bound, - bool p_constraint_enabled, bool p_constraint_in_localspace, bool p_constraint_inverted) { - if (!p_operation_bone) { - return; - } - - Color bone_ik_color = Color(1.0, 0.65, 0.0, 0.4); -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - bone_ik_color = EDITOR_GET("editors/2d/bone_ik_color"); - } -#endif // TOOLS_ENABLED - - float arc_angle_min = p_min_bound; - float arc_angle_max = p_max_bound; - if (arc_angle_min < 0) { - arc_angle_min = (Math_PI * 2) + arc_angle_min; - } - if (arc_angle_max < 0) { - arc_angle_max = (Math_PI * 2) + arc_angle_max; - } - if (arc_angle_min > arc_angle_max) { - SWAP(arc_angle_min, arc_angle_max); - } - arc_angle_min += p_operation_bone->get_bone_angle(); - arc_angle_max += p_operation_bone->get_bone_angle(); - - if (p_constraint_enabled) { - if (p_constraint_in_localspace) { - Node *operation_bone_parent = p_operation_bone->get_parent(); - Bone2D *operation_bone_parent_bone = Object::cast_to(operation_bone_parent); - - if (operation_bone_parent_bone) { - stack->skeleton->draw_set_transform( - stack->skeleton->to_local(p_operation_bone->get_global_position()), - operation_bone_parent_bone->get_global_rotation() - stack->skeleton->get_global_rotation()); - } else { - stack->skeleton->draw_set_transform(stack->skeleton->to_local(p_operation_bone->get_global_position())); - } - } else { - stack->skeleton->draw_set_transform(stack->skeleton->to_local(p_operation_bone->get_global_position())); - } - - if (p_constraint_inverted) { - stack->skeleton->draw_arc(Vector2(0, 0), p_operation_bone->get_length(), - arc_angle_min + (Math_PI * 2), arc_angle_max, 32, bone_ik_color, 1.0); - } else { - stack->skeleton->draw_arc(Vector2(0, 0), p_operation_bone->get_length(), - arc_angle_min, arc_angle_max, 32, bone_ik_color, 1.0); - } - stack->skeleton->draw_line(Vector2(0, 0), Vector2(Math::cos(arc_angle_min), Math::sin(arc_angle_min)) * p_operation_bone->get_length(), bone_ik_color, 1.0); - stack->skeleton->draw_line(Vector2(0, 0), Vector2(Math::cos(arc_angle_max), Math::sin(arc_angle_max)) * p_operation_bone->get_length(), bone_ik_color, 1.0); - - } else { - stack->skeleton->draw_set_transform(stack->skeleton->to_local(p_operation_bone->get_global_position())); - stack->skeleton->draw_arc(Vector2(0, 0), p_operation_bone->get_length(), 0, Math_PI * 2, 32, bone_ik_color, 1.0); - stack->skeleton->draw_line(Vector2(0, 0), Vector2(1, 0) * p_operation_bone->get_length(), bone_ik_color, 1.0); - } -} - -Ref SkeletonModification2D::get_modification_stack() { - return stack; -} - -void SkeletonModification2D::set_is_setup(bool p_setup) { - is_setup = p_setup; -} - -bool SkeletonModification2D::get_is_setup() const { - return is_setup; -} - -void SkeletonModification2D::set_execution_mode(int p_mode) { - execution_mode = p_mode; -} - -int SkeletonModification2D::get_execution_mode() const { - return execution_mode; -} - -void SkeletonModification2D::set_editor_draw_gizmo(bool p_draw_gizmo) { - editor_draw_gizmo = p_draw_gizmo; -#ifdef TOOLS_ENABLED - if (is_setup) { - if (stack) { - stack->set_editor_gizmos_dirty(true); - } - } -#endif // TOOLS_ENABLED -} - -bool SkeletonModification2D::get_editor_draw_gizmo() const { - return editor_draw_gizmo; -} - -void SkeletonModification2D::_bind_methods() { - GDVIRTUAL_BIND(_execute, "delta"); - GDVIRTUAL_BIND(_setup_modification, "modification_stack") - GDVIRTUAL_BIND(_draw_editor_gizmo) - - ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &SkeletonModification2D::set_enabled); - ClassDB::bind_method(D_METHOD("get_enabled"), &SkeletonModification2D::get_enabled); - ClassDB::bind_method(D_METHOD("get_modification_stack"), &SkeletonModification2D::get_modification_stack); - ClassDB::bind_method(D_METHOD("set_is_setup", "is_setup"), &SkeletonModification2D::set_is_setup); - ClassDB::bind_method(D_METHOD("get_is_setup"), &SkeletonModification2D::get_is_setup); - ClassDB::bind_method(D_METHOD("set_execution_mode", "execution_mode"), &SkeletonModification2D::set_execution_mode); - ClassDB::bind_method(D_METHOD("get_execution_mode"), &SkeletonModification2D::get_execution_mode); - ClassDB::bind_method(D_METHOD("clamp_angle", "angle", "min", "max", "invert"), &SkeletonModification2D::clamp_angle); - ClassDB::bind_method(D_METHOD("set_editor_draw_gizmo", "draw_gizmo"), &SkeletonModification2D::set_editor_draw_gizmo); - ClassDB::bind_method(D_METHOD("get_editor_draw_gizmo"), &SkeletonModification2D::get_editor_draw_gizmo); - - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "get_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "execution_mode", PROPERTY_HINT_ENUM, "process,physics_process"), "set_execution_mode", "get_execution_mode"); -} - -SkeletonModification2D::SkeletonModification2D() { - stack = nullptr; - is_setup = false; -} diff --git a/scene/resources/skeleton_modification_2d.h b/scene/resources/skeleton_modification_2d.h deleted file mode 100644 index c00c3c8dc2..0000000000 --- a/scene/resources/skeleton_modification_2d.h +++ /dev/null @@ -1,89 +0,0 @@ -/**************************************************************************/ -/* skeleton_modification_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 SKELETON_MODIFICATION_2D_H -#define SKELETON_MODIFICATION_2D_H - -#include "scene/2d/skeleton_2d.h" -#include "scene/resources/skeleton_modification_stack_2d.h" - -/////////////////////////////////////// -// SkeletonModification2D -/////////////////////////////////////// - -class SkeletonModificationStack2D; -class Bone2D; - -class SkeletonModification2D : public Resource { - GDCLASS(SkeletonModification2D, Resource); - friend class Skeleton2D; - friend class Bone2D; - -protected: - static void _bind_methods(); - - SkeletonModificationStack2D *stack = nullptr; - int execution_mode = 0; // 0 = process - - bool enabled = true; - bool is_setup = false; - - bool _print_execution_error(bool p_condition, String p_message); - - GDVIRTUAL1(_execute, double) - GDVIRTUAL1(_setup_modification, Ref) - GDVIRTUAL0(_draw_editor_gizmo) - -public: - virtual void _execute(float _delta); - virtual void _setup_modification(SkeletonModificationStack2D *p_stack); - virtual void _draw_editor_gizmo(); - - bool editor_draw_gizmo = false; - void set_editor_draw_gizmo(bool p_draw_gizmo); - bool get_editor_draw_gizmo() const; - - void set_enabled(bool p_enabled); - bool get_enabled(); - - Ref get_modification_stack(); - void set_is_setup(bool p_setup); - bool get_is_setup() const; - - void set_execution_mode(int p_mode); - int get_execution_mode() const; - - float clamp_angle(float p_angle, float p_min_bound, float p_max_bound, bool p_invert_clamp = false); - void editor_draw_angle_constraints(Bone2D *p_operation_bone, float p_min_bound, float p_max_bound, bool p_constraint_enabled, bool p_constraint_in_localspace, bool p_constraint_inverted); - - SkeletonModification2D(); -}; - -#endif // SKELETON_MODIFICATION_2D_H diff --git a/scene/resources/skeleton_modification_2d_ccdik.cpp b/scene/resources/skeleton_modification_2d_ccdik.cpp deleted file mode 100644 index 1ad8d0eccc..0000000000 --- a/scene/resources/skeleton_modification_2d_ccdik.cpp +++ /dev/null @@ -1,549 +0,0 @@ -/**************************************************************************/ -/* skeleton_modification_2d_ccdik.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 "skeleton_modification_2d_ccdik.h" -#include "scene/2d/skeleton_2d.h" - -#ifdef TOOLS_ENABLED -#include "editor/editor_settings.h" -#endif // TOOLS_ENABLED - -bool SkeletonModification2DCCDIK::_set(const StringName &p_path, const Variant &p_value) { - String path = p_path; - - if (path.begins_with("joint_data/")) { - int which = path.get_slicec('/', 1).to_int(); - String what = path.get_slicec('/', 2); - ERR_FAIL_INDEX_V(which, ccdik_data_chain.size(), false); - - if (what == "bone2d_node") { - set_ccdik_joint_bone2d_node(which, p_value); - } else if (what == "bone_index") { - set_ccdik_joint_bone_index(which, p_value); - } else if (what == "rotate_from_joint") { - set_ccdik_joint_rotate_from_joint(which, p_value); - } else if (what == "enable_constraint") { - set_ccdik_joint_enable_constraint(which, p_value); - } else if (what == "constraint_angle_min") { - set_ccdik_joint_constraint_angle_min(which, Math::deg_to_rad(float(p_value))); - } else if (what == "constraint_angle_max") { - set_ccdik_joint_constraint_angle_max(which, Math::deg_to_rad(float(p_value))); - } else if (what == "constraint_angle_invert") { - set_ccdik_joint_constraint_angle_invert(which, p_value); - } else if (what == "constraint_in_localspace") { - set_ccdik_joint_constraint_in_localspace(which, p_value); - } -#ifdef TOOLS_ENABLED - else if (what.begins_with("editor_draw_gizmo")) { - set_ccdik_joint_editor_draw_gizmo(which, p_value); - } -#endif // TOOLS_ENABLED - else { - return false; - } - } -#ifdef TOOLS_ENABLED - else if (path.begins_with("editor/draw_gizmo")) { - set_editor_draw_gizmo(p_value); - } -#endif // TOOLS_ENABLED - else { - return false; - } - - return true; -} - -bool SkeletonModification2DCCDIK::_get(const StringName &p_path, Variant &r_ret) const { - String path = p_path; - - if (path.begins_with("joint_data/")) { - int which = path.get_slicec('/', 1).to_int(); - String what = path.get_slicec('/', 2); - ERR_FAIL_INDEX_V(which, ccdik_data_chain.size(), false); - - if (what == "bone2d_node") { - r_ret = get_ccdik_joint_bone2d_node(which); - } else if (what == "bone_index") { - r_ret = get_ccdik_joint_bone_index(which); - } else if (what == "rotate_from_joint") { - r_ret = get_ccdik_joint_rotate_from_joint(which); - } else if (what == "enable_constraint") { - r_ret = get_ccdik_joint_enable_constraint(which); - } else if (what == "constraint_angle_min") { - r_ret = Math::rad_to_deg(get_ccdik_joint_constraint_angle_min(which)); - } else if (what == "constraint_angle_max") { - r_ret = Math::rad_to_deg(get_ccdik_joint_constraint_angle_max(which)); - } else if (what == "constraint_angle_invert") { - r_ret = get_ccdik_joint_constraint_angle_invert(which); - } else if (what == "constraint_in_localspace") { - r_ret = get_ccdik_joint_constraint_in_localspace(which); - } -#ifdef TOOLS_ENABLED - else if (what.begins_with("editor_draw_gizmo")) { - r_ret = get_ccdik_joint_editor_draw_gizmo(which); - } -#endif // TOOLS_ENABLED - else { - return false; - } - } -#ifdef TOOLS_ENABLED - else if (path.begins_with("editor/draw_gizmo")) { - r_ret = get_editor_draw_gizmo(); - } -#endif // TOOLS_ENABLED - else { - return false; - } - - return true; -} - -void SkeletonModification2DCCDIK::_get_property_list(List *p_list) const { - for (int i = 0; i < ccdik_data_chain.size(); i++) { - String base_string = "joint_data/" + itos(i) + "/"; - - p_list->push_back(PropertyInfo(Variant::INT, base_string + "bone_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "rotate_from_joint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - - p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "enable_constraint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - if (ccdik_data_chain[i].enable_constraint) { - p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "constraint_angle_min", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "constraint_angle_max", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "constraint_angle_invert", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "constraint_in_localspace", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - } - -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "editor_draw_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - } -#endif // TOOLS_ENABLED - } - -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - } -#endif // TOOLS_ENABLED -} - -void SkeletonModification2DCCDIK::_execute(float p_delta) { - ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, - "Modification is not setup and therefore cannot execute!"); - if (!enabled) { - return; - } - - if (target_node_cache.is_null()) { - WARN_PRINT_ONCE("Target cache is out of date. Attempting to update..."); - update_target_cache(); - return; - } - if (tip_node_cache.is_null()) { - WARN_PRINT_ONCE("Tip cache is out of date. Attempting to update..."); - update_tip_cache(); - return; - } - - Node2D *target = Object::cast_to(ObjectDB::get_instance(target_node_cache)); - if (!target || !target->is_inside_tree()) { - ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!"); - return; - } - - Node2D *tip = Object::cast_to(ObjectDB::get_instance(tip_node_cache)); - if (!tip || !tip->is_inside_tree()) { - ERR_PRINT_ONCE("Tip node is not in the scene tree. Cannot execute modification!"); - return; - } - - for (int i = 0; i < ccdik_data_chain.size(); i++) { - _execute_ccdik_joint(i, target, tip); - } -} - -void SkeletonModification2DCCDIK::_execute_ccdik_joint(int p_joint_idx, Node2D *p_target, Node2D *p_tip) { - CCDIK_Joint_Data2D ccdik_data = ccdik_data_chain[p_joint_idx]; - if (ccdik_data.bone_idx < 0 || ccdik_data.bone_idx > stack->skeleton->get_bone_count()) { - ERR_PRINT_ONCE("2D CCDIK joint: bone index not found!"); - return; - } - - Bone2D *operation_bone = stack->skeleton->get_bone(ccdik_data.bone_idx); - Transform2D operation_transform = operation_bone->get_global_transform(); - - if (ccdik_data.rotate_from_joint) { - // To rotate from the joint, simply look at the target! - operation_transform.set_rotation( - operation_transform.looking_at(p_target->get_global_position()).get_rotation() - operation_bone->get_bone_angle()); - } else { - // How to rotate from the tip: get the difference of rotation needed from the tip to the target, from the perspective of the joint. - // Because we are only using the offset, we do not need to account for the bone angle of the Bone2D node. - float joint_to_tip = p_tip->get_global_position().angle_to_point(operation_transform.get_origin()); - float joint_to_target = p_target->get_global_position().angle_to_point(operation_transform.get_origin()); - operation_transform.set_rotation( - operation_transform.get_rotation() + (joint_to_target - joint_to_tip)); - } - - // Reset scale - operation_transform.set_scale(operation_bone->get_global_scale()); - - // Apply constraints in globalspace: - if (ccdik_data.enable_constraint && !ccdik_data.constraint_in_localspace) { - operation_transform.set_rotation(clamp_angle(operation_transform.get_rotation(), ccdik_data.constraint_angle_min, ccdik_data.constraint_angle_max, ccdik_data.constraint_angle_invert)); - } - - // Convert from a global transform to a delta and then apply the delta to the local transform. - operation_bone->set_global_transform(operation_transform); - operation_transform = operation_bone->get_transform(); - - // Apply constraints in localspace: - if (ccdik_data.enable_constraint && ccdik_data.constraint_in_localspace) { - operation_transform.set_rotation(clamp_angle(operation_transform.get_rotation(), ccdik_data.constraint_angle_min, ccdik_data.constraint_angle_max, ccdik_data.constraint_angle_invert)); - } - - // Set the local pose override, and to make sure child bones are also updated, set the transform of the bone. - stack->skeleton->set_bone_local_pose_override(ccdik_data.bone_idx, operation_transform, stack->strength, true); - operation_bone->set_transform(operation_transform); - operation_bone->notification(operation_bone->NOTIFICATION_TRANSFORM_CHANGED); -} - -void SkeletonModification2DCCDIK::_setup_modification(SkeletonModificationStack2D *p_stack) { - stack = p_stack; - - if (stack != nullptr) { - is_setup = true; - update_target_cache(); - update_tip_cache(); - } -} - -void SkeletonModification2DCCDIK::_draw_editor_gizmo() { - if (!enabled || !is_setup) { - return; - } - - for (int i = 0; i < ccdik_data_chain.size(); i++) { - if (!ccdik_data_chain[i].editor_draw_gizmo) { - continue; - } - - Bone2D *operation_bone = stack->skeleton->get_bone(ccdik_data_chain[i].bone_idx); - editor_draw_angle_constraints(operation_bone, ccdik_data_chain[i].constraint_angle_min, ccdik_data_chain[i].constraint_angle_max, - ccdik_data_chain[i].enable_constraint, ccdik_data_chain[i].constraint_in_localspace, ccdik_data_chain[i].constraint_angle_invert); - } -} - -void SkeletonModification2DCCDIK::update_target_cache() { - if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); - return; - } - - target_node_cache = ObjectID(); - if (stack->skeleton) { - if (stack->skeleton->is_inside_tree()) { - if (stack->skeleton->has_node(target_node)) { - Node *node = stack->skeleton->get_node(target_node); - ERR_FAIL_COND_MSG(!node || stack->skeleton == node, - "Cannot update target cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update target cache: node is not in the scene tree!"); - target_node_cache = node->get_instance_id(); - } - } - } -} - -void SkeletonModification2DCCDIK::update_tip_cache() { - if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update tip cache: modification is not properly setup!"); - return; - } - - tip_node_cache = ObjectID(); - if (stack->skeleton) { - if (stack->skeleton->is_inside_tree()) { - if (stack->skeleton->has_node(tip_node)) { - Node *node = stack->skeleton->get_node(tip_node); - ERR_FAIL_COND_MSG(!node || stack->skeleton == node, - "Cannot update tip cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update tip cache: node is not in the scene tree!"); - tip_node_cache = node->get_instance_id(); - } - } - } -} - -void SkeletonModification2DCCDIK::ccdik_joint_update_bone2d_cache(int p_joint_idx) { - ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "Cannot update bone2d cache: joint index out of range!"); - if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update CCDIK Bone2D cache: modification is not properly setup!"); - return; - } - - ccdik_data_chain.write[p_joint_idx].bone2d_node_cache = ObjectID(); - if (stack->skeleton) { - if (stack->skeleton->is_inside_tree()) { - if (stack->skeleton->has_node(ccdik_data_chain[p_joint_idx].bone2d_node)) { - Node *node = stack->skeleton->get_node(ccdik_data_chain[p_joint_idx].bone2d_node); - ERR_FAIL_COND_MSG(!node || stack->skeleton == node, - "Cannot update CCDIK joint " + itos(p_joint_idx) + " Bone2D cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update CCDIK joint " + itos(p_joint_idx) + " Bone2D cache: node is not in the scene tree!"); - ccdik_data_chain.write[p_joint_idx].bone2d_node_cache = node->get_instance_id(); - - Bone2D *bone = Object::cast_to(node); - if (bone) { - ccdik_data_chain.write[p_joint_idx].bone_idx = bone->get_index_in_skeleton(); - } else { - ERR_FAIL_MSG("CCDIK joint " + itos(p_joint_idx) + " Bone2D cache: Nodepath to Bone2D is not a Bone2D node!"); - } - } - } - } -} - -void SkeletonModification2DCCDIK::set_target_node(const NodePath &p_target_node) { - target_node = p_target_node; - update_target_cache(); -} - -NodePath SkeletonModification2DCCDIK::get_target_node() const { - return target_node; -} - -void SkeletonModification2DCCDIK::set_tip_node(const NodePath &p_tip_node) { - tip_node = p_tip_node; - update_tip_cache(); -} - -NodePath SkeletonModification2DCCDIK::get_tip_node() const { - return tip_node; -} - -void SkeletonModification2DCCDIK::set_ccdik_data_chain_length(int p_length) { - ccdik_data_chain.resize(p_length); - notify_property_list_changed(); -} - -int SkeletonModification2DCCDIK::get_ccdik_data_chain_length() { - return ccdik_data_chain.size(); -} - -void SkeletonModification2DCCDIK::set_ccdik_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node) { - ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); - ccdik_data_chain.write[p_joint_idx].bone2d_node = p_target_node; - ccdik_joint_update_bone2d_cache(p_joint_idx); - - notify_property_list_changed(); -} - -NodePath SkeletonModification2DCCDIK::get_ccdik_joint_bone2d_node(int p_joint_idx) const { - ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), NodePath(), "CCDIK joint out of range!"); - return ccdik_data_chain[p_joint_idx].bone2d_node; -} - -void SkeletonModification2DCCDIK::set_ccdik_joint_bone_index(int p_joint_idx, int p_bone_idx) { - ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCCDIK joint out of range!"); - ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); - - if (is_setup) { - if (stack->skeleton) { - ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); - ccdik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; - ccdik_data_chain.write[p_joint_idx].bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id(); - ccdik_data_chain.write[p_joint_idx].bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx)); - } else { - WARN_PRINT("Cannot verify the CCDIK joint " + itos(p_joint_idx) + " bone index for this modification..."); - ccdik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; - } - } else { - WARN_PRINT("Cannot verify the CCDIK joint " + itos(p_joint_idx) + " bone index for this modification..."); - ccdik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; - } - - notify_property_list_changed(); -} - -int SkeletonModification2DCCDIK::get_ccdik_joint_bone_index(int p_joint_idx) const { - ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), -1, "CCDIK joint out of range!"); - return ccdik_data_chain[p_joint_idx].bone_idx; -} - -void SkeletonModification2DCCDIK::set_ccdik_joint_rotate_from_joint(int p_joint_idx, bool p_rotate_from_joint) { - ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); - ccdik_data_chain.write[p_joint_idx].rotate_from_joint = p_rotate_from_joint; -} - -bool SkeletonModification2DCCDIK::get_ccdik_joint_rotate_from_joint(int p_joint_idx) const { - ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), false, "CCDIK joint out of range!"); - return ccdik_data_chain[p_joint_idx].rotate_from_joint; -} - -void SkeletonModification2DCCDIK::set_ccdik_joint_enable_constraint(int p_joint_idx, bool p_constraint) { - ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); - ccdik_data_chain.write[p_joint_idx].enable_constraint = p_constraint; - notify_property_list_changed(); - -#ifdef TOOLS_ENABLED - if (stack && is_setup) { - stack->set_editor_gizmos_dirty(true); - } -#endif // TOOLS_ENABLED -} - -bool SkeletonModification2DCCDIK::get_ccdik_joint_enable_constraint(int p_joint_idx) const { - ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), false, "CCDIK joint out of range!"); - return ccdik_data_chain[p_joint_idx].enable_constraint; -} - -void SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_min(int p_joint_idx, float p_angle_min) { - ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); - ccdik_data_chain.write[p_joint_idx].constraint_angle_min = p_angle_min; - -#ifdef TOOLS_ENABLED - if (stack && is_setup) { - stack->set_editor_gizmos_dirty(true); - } -#endif // TOOLS_ENABLED -} - -float SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_min(int p_joint_idx) const { - ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), 0.0, "CCDIK joint out of range!"); - return ccdik_data_chain[p_joint_idx].constraint_angle_min; -} - -void SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_max(int p_joint_idx, float p_angle_max) { - ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); - ccdik_data_chain.write[p_joint_idx].constraint_angle_max = p_angle_max; - -#ifdef TOOLS_ENABLED - if (stack && is_setup) { - stack->set_editor_gizmos_dirty(true); - } -#endif // TOOLS_ENABLED -} - -float SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_max(int p_joint_idx) const { - ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), 0.0, "CCDIK joint out of range!"); - return ccdik_data_chain[p_joint_idx].constraint_angle_max; -} - -void SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_invert(int p_joint_idx, bool p_invert) { - ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); - ccdik_data_chain.write[p_joint_idx].constraint_angle_invert = p_invert; - -#ifdef TOOLS_ENABLED - if (stack && is_setup) { - stack->set_editor_gizmos_dirty(true); - } -#endif // TOOLS_ENABLED -} - -bool SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_invert(int p_joint_idx) const { - ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), false, "CCDIK joint out of range!"); - return ccdik_data_chain[p_joint_idx].constraint_angle_invert; -} - -void SkeletonModification2DCCDIK::set_ccdik_joint_constraint_in_localspace(int p_joint_idx, bool p_constraint_in_localspace) { - ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); - ccdik_data_chain.write[p_joint_idx].constraint_in_localspace = p_constraint_in_localspace; - -#ifdef TOOLS_ENABLED - if (stack && is_setup) { - stack->set_editor_gizmos_dirty(true); - } -#endif // TOOLS_ENABLED -} - -bool SkeletonModification2DCCDIK::get_ccdik_joint_constraint_in_localspace(int p_joint_idx) const { - ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), false, "CCDIK joint out of range!"); - return ccdik_data_chain[p_joint_idx].constraint_in_localspace; -} - -void SkeletonModification2DCCDIK::set_ccdik_joint_editor_draw_gizmo(int p_joint_idx, bool p_draw_gizmo) { - ERR_FAIL_INDEX_MSG(p_joint_idx, ccdik_data_chain.size(), "CCDIK joint out of range!"); - ccdik_data_chain.write[p_joint_idx].editor_draw_gizmo = p_draw_gizmo; - -#ifdef TOOLS_ENABLED - if (stack && is_setup) { - stack->set_editor_gizmos_dirty(true); - } -#endif // TOOLS_ENABLED -} - -bool SkeletonModification2DCCDIK::get_ccdik_joint_editor_draw_gizmo(int p_joint_idx) const { - ERR_FAIL_INDEX_V_MSG(p_joint_idx, ccdik_data_chain.size(), false, "CCDIK joint out of range!"); - return ccdik_data_chain[p_joint_idx].editor_draw_gizmo; -} - -void SkeletonModification2DCCDIK::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DCCDIK::set_target_node); - ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DCCDIK::get_target_node); - ClassDB::bind_method(D_METHOD("set_tip_node", "tip_nodepath"), &SkeletonModification2DCCDIK::set_tip_node); - ClassDB::bind_method(D_METHOD("get_tip_node"), &SkeletonModification2DCCDIK::get_tip_node); - - ClassDB::bind_method(D_METHOD("set_ccdik_data_chain_length", "length"), &SkeletonModification2DCCDIK::set_ccdik_data_chain_length); - ClassDB::bind_method(D_METHOD("get_ccdik_data_chain_length"), &SkeletonModification2DCCDIK::get_ccdik_data_chain_length); - - ClassDB::bind_method(D_METHOD("set_ccdik_joint_bone2d_node", "joint_idx", "bone2d_nodepath"), &SkeletonModification2DCCDIK::set_ccdik_joint_bone2d_node); - ClassDB::bind_method(D_METHOD("get_ccdik_joint_bone2d_node", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_bone2d_node); - ClassDB::bind_method(D_METHOD("set_ccdik_joint_bone_index", "joint_idx", "bone_idx"), &SkeletonModification2DCCDIK::set_ccdik_joint_bone_index); - ClassDB::bind_method(D_METHOD("get_ccdik_joint_bone_index", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_bone_index); - ClassDB::bind_method(D_METHOD("set_ccdik_joint_rotate_from_joint", "joint_idx", "rotate_from_joint"), &SkeletonModification2DCCDIK::set_ccdik_joint_rotate_from_joint); - ClassDB::bind_method(D_METHOD("get_ccdik_joint_rotate_from_joint", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_rotate_from_joint); - ClassDB::bind_method(D_METHOD("set_ccdik_joint_enable_constraint", "joint_idx", "enable_constraint"), &SkeletonModification2DCCDIK::set_ccdik_joint_enable_constraint); - ClassDB::bind_method(D_METHOD("get_ccdik_joint_enable_constraint", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_enable_constraint); - ClassDB::bind_method(D_METHOD("set_ccdik_joint_constraint_angle_min", "joint_idx", "angle_min"), &SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_min); - ClassDB::bind_method(D_METHOD("get_ccdik_joint_constraint_angle_min", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_min); - ClassDB::bind_method(D_METHOD("set_ccdik_joint_constraint_angle_max", "joint_idx", "angle_max"), &SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_max); - ClassDB::bind_method(D_METHOD("get_ccdik_joint_constraint_angle_max", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_max); - ClassDB::bind_method(D_METHOD("set_ccdik_joint_constraint_angle_invert", "joint_idx", "invert"), &SkeletonModification2DCCDIK::set_ccdik_joint_constraint_angle_invert); - ClassDB::bind_method(D_METHOD("get_ccdik_joint_constraint_angle_invert", "joint_idx"), &SkeletonModification2DCCDIK::get_ccdik_joint_constraint_angle_invert); - - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "tip_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_tip_node", "get_tip_node"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "ccdik_data_chain_length", PROPERTY_HINT_RANGE, "0, 100, 1"), "set_ccdik_data_chain_length", "get_ccdik_data_chain_length"); -} - -SkeletonModification2DCCDIK::SkeletonModification2DCCDIK() { - stack = nullptr; - is_setup = false; - enabled = true; - editor_draw_gizmo = true; -} - -SkeletonModification2DCCDIK::~SkeletonModification2DCCDIK() { -} diff --git a/scene/resources/skeleton_modification_2d_ccdik.h b/scene/resources/skeleton_modification_2d_ccdik.h deleted file mode 100644 index 39ea02c1df..0000000000 --- a/scene/resources/skeleton_modification_2d_ccdik.h +++ /dev/null @@ -1,116 +0,0 @@ -/**************************************************************************/ -/* skeleton_modification_2d_ccdik.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 SKELETON_MODIFICATION_2D_CCDIK_H -#define SKELETON_MODIFICATION_2D_CCDIK_H - -#include "scene/2d/skeleton_2d.h" -#include "scene/resources/skeleton_modification_2d.h" - -/////////////////////////////////////// -// SkeletonModification2DCCDIK -/////////////////////////////////////// - -class SkeletonModification2DCCDIK : public SkeletonModification2D { - GDCLASS(SkeletonModification2DCCDIK, SkeletonModification2D); - -private: - struct CCDIK_Joint_Data2D { - int bone_idx = -1; - NodePath bone2d_node; - ObjectID bone2d_node_cache; - bool rotate_from_joint = false; - - bool enable_constraint = false; - float constraint_angle_min = 0; - float constraint_angle_max = (2.0 * Math_PI); - bool constraint_angle_invert = false; - bool constraint_in_localspace = true; - - bool editor_draw_gizmo = true; - }; - - Vector ccdik_data_chain; - - NodePath target_node; - ObjectID target_node_cache; - void update_target_cache(); - - NodePath tip_node; - ObjectID tip_node_cache; - void update_tip_cache(); - - void ccdik_joint_update_bone2d_cache(int p_joint_idx); - void _execute_ccdik_joint(int p_joint_idx, Node2D *p_target, Node2D *p_tip); - -protected: - static void _bind_methods(); - bool _set(const StringName &p_path, const Variant &p_value); - bool _get(const StringName &p_path, Variant &r_ret) const; - void _get_property_list(List *p_list) const; - -public: - void _execute(float p_delta) override; - void _setup_modification(SkeletonModificationStack2D *p_stack) override; - void _draw_editor_gizmo() override; - - void set_target_node(const NodePath &p_target_node); - NodePath get_target_node() const; - void set_tip_node(const NodePath &p_tip_node); - NodePath get_tip_node() const; - - int get_ccdik_data_chain_length(); - void set_ccdik_data_chain_length(int p_new_length); - - void set_ccdik_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node); - NodePath get_ccdik_joint_bone2d_node(int p_joint_idx) const; - void set_ccdik_joint_bone_index(int p_joint_idx, int p_bone_idx); - int get_ccdik_joint_bone_index(int p_joint_idx) const; - - void set_ccdik_joint_rotate_from_joint(int p_joint_idx, bool p_rotate_from_joint); - bool get_ccdik_joint_rotate_from_joint(int p_joint_idx) const; - void set_ccdik_joint_enable_constraint(int p_joint_idx, bool p_constraint); - bool get_ccdik_joint_enable_constraint(int p_joint_idx) const; - void set_ccdik_joint_constraint_angle_min(int p_joint_idx, float p_angle_min); - float get_ccdik_joint_constraint_angle_min(int p_joint_idx) const; - void set_ccdik_joint_constraint_angle_max(int p_joint_idx, float p_angle_max); - float get_ccdik_joint_constraint_angle_max(int p_joint_idx) const; - void set_ccdik_joint_constraint_angle_invert(int p_joint_idx, bool p_invert); - bool get_ccdik_joint_constraint_angle_invert(int p_joint_idx) const; - void set_ccdik_joint_constraint_in_localspace(int p_joint_idx, bool p_constraint_in_localspace); - bool get_ccdik_joint_constraint_in_localspace(int p_joint_idx) const; - void set_ccdik_joint_editor_draw_gizmo(int p_joint_idx, bool p_draw_gizmo); - bool get_ccdik_joint_editor_draw_gizmo(int p_joint_idx) const; - - SkeletonModification2DCCDIK(); - ~SkeletonModification2DCCDIK(); -}; - -#endif // SKELETON_MODIFICATION_2D_CCDIK_H diff --git a/scene/resources/skeleton_modification_2d_fabrik.cpp b/scene/resources/skeleton_modification_2d_fabrik.cpp deleted file mode 100644 index dd1c4a91d5..0000000000 --- a/scene/resources/skeleton_modification_2d_fabrik.cpp +++ /dev/null @@ -1,452 +0,0 @@ -/**************************************************************************/ -/* skeleton_modification_2d_fabrik.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 "skeleton_modification_2d_fabrik.h" -#include "scene/2d/skeleton_2d.h" - -#ifdef TOOLS_ENABLED -#include "editor/editor_settings.h" -#endif // TOOLS_ENABLED - -bool SkeletonModification2DFABRIK::_set(const StringName &p_path, const Variant &p_value) { - String path = p_path; - - if (path.begins_with("joint_data/")) { - int which = path.get_slicec('/', 1).to_int(); - String what = path.get_slicec('/', 2); - ERR_FAIL_INDEX_V(which, fabrik_data_chain.size(), false); - - if (what == "bone2d_node") { - set_fabrik_joint_bone2d_node(which, p_value); - } else if (what == "bone_index") { - set_fabrik_joint_bone_index(which, p_value); - } else if (what == "magnet_position") { - set_fabrik_joint_magnet_position(which, p_value); - } else if (what == "use_target_rotation") { - set_fabrik_joint_use_target_rotation(which, p_value); - } else { - return false; - } - } else { - return false; - } - - return true; -} - -bool SkeletonModification2DFABRIK::_get(const StringName &p_path, Variant &r_ret) const { - String path = p_path; - - if (path.begins_with("joint_data/")) { - int which = path.get_slicec('/', 1).to_int(); - String what = path.get_slicec('/', 2); - ERR_FAIL_INDEX_V(which, fabrik_data_chain.size(), false); - - if (what == "bone2d_node") { - r_ret = get_fabrik_joint_bone2d_node(which); - } else if (what == "bone_index") { - r_ret = get_fabrik_joint_bone_index(which); - } else if (what == "magnet_position") { - r_ret = get_fabrik_joint_magnet_position(which); - } else if (what == "use_target_rotation") { - r_ret = get_fabrik_joint_use_target_rotation(which); - } else { - return false; - } - } else { - return false; - } - return true; -} - -void SkeletonModification2DFABRIK::_get_property_list(List *p_list) const { - for (int i = 0; i < fabrik_data_chain.size(); i++) { - String base_string = "joint_data/" + itos(i) + "/"; - - p_list->push_back(PropertyInfo(Variant::INT, base_string + "bone_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT)); - - if (i > 0) { - p_list->push_back(PropertyInfo(Variant::VECTOR2, base_string + "magnet_position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - } - if (i == fabrik_data_chain.size() - 1) { - p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "use_target_rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - } - } -} - -void SkeletonModification2DFABRIK::_execute(float p_delta) { - ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, - "Modification is not setup and therefore cannot execute!"); - if (!enabled) { - return; - } - - if (target_node_cache.is_null()) { - WARN_PRINT_ONCE("Target cache is out of date. Attempting to update..."); - update_target_cache(); - return; - } - - if (fabrik_data_chain.size() <= 1) { - ERR_PRINT_ONCE("FABRIK requires at least two joints to operate! Cannot execute modification!"); - return; - } - - Node2D *target = Object::cast_to(ObjectDB::get_instance(target_node_cache)); - if (!target || !target->is_inside_tree()) { - ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!"); - return; - } - target_global_pose = target->get_global_transform(); - - if (fabrik_data_chain[0].bone2d_node_cache.is_null() && !fabrik_data_chain[0].bone2d_node.is_empty()) { - fabrik_joint_update_bone2d_cache(0); - WARN_PRINT("Bone2D cache for origin joint is out of date. Updating..."); - } - - Bone2D *origin_bone2d_node = Object::cast_to(ObjectDB::get_instance(fabrik_data_chain[0].bone2d_node_cache)); - if (!origin_bone2d_node || !origin_bone2d_node->is_inside_tree()) { - ERR_PRINT_ONCE("Origin joint's Bone2D node is not in the scene tree. Cannot execute modification!"); - return; - } - - origin_global_pose = origin_bone2d_node->get_global_transform(); - - if (fabrik_transform_chain.size() != fabrik_data_chain.size()) { - fabrik_transform_chain.resize(fabrik_data_chain.size()); - } - - for (int i = 0; i < fabrik_data_chain.size(); i++) { - // Update the transform chain - if (fabrik_data_chain[i].bone2d_node_cache.is_null() && !fabrik_data_chain[i].bone2d_node.is_empty()) { - WARN_PRINT_ONCE("Bone2D cache for joint " + itos(i) + " is out of date.. Attempting to update..."); - fabrik_joint_update_bone2d_cache(i); - } - Bone2D *joint_bone2d_node = Object::cast_to(ObjectDB::get_instance(fabrik_data_chain[i].bone2d_node_cache)); - if (!joint_bone2d_node) { - ERR_PRINT_ONCE("FABRIK Joint " + itos(i) + " does not have a Bone2D node set! Cannot execute modification!"); - return; - } - fabrik_transform_chain.write[i] = joint_bone2d_node->get_global_transform(); - } - - Bone2D *final_bone2d_node = Object::cast_to(ObjectDB::get_instance(fabrik_data_chain[fabrik_data_chain.size() - 1].bone2d_node_cache)); - float final_bone2d_angle = final_bone2d_node->get_global_rotation(); - if (fabrik_data_chain[fabrik_data_chain.size() - 1].use_target_rotation) { - final_bone2d_angle = target_global_pose.get_rotation(); - } - Vector2 final_bone2d_direction = Vector2(Math::cos(final_bone2d_angle), Math::sin(final_bone2d_angle)); - float final_bone2d_length = final_bone2d_node->get_length() * MIN(final_bone2d_node->get_global_scale().x, final_bone2d_node->get_global_scale().y); - float target_distance = (final_bone2d_node->get_global_position() + (final_bone2d_direction * final_bone2d_length)).distance_to(target->get_global_position()); - chain_iterations = 0; - - while (target_distance > chain_tolarance) { - chain_backwards(); - chain_forwards(); - - final_bone2d_angle = final_bone2d_node->get_global_rotation(); - if (fabrik_data_chain[fabrik_data_chain.size() - 1].use_target_rotation) { - final_bone2d_angle = target_global_pose.get_rotation(); - } - final_bone2d_direction = Vector2(Math::cos(final_bone2d_angle), Math::sin(final_bone2d_angle)); - target_distance = (final_bone2d_node->get_global_position() + (final_bone2d_direction * final_bone2d_length)).distance_to(target->get_global_position()); - - chain_iterations += 1; - if (chain_iterations >= chain_max_iterations) { - break; - } - } - - // Apply all of the saved transforms to the Bone2D nodes - for (int i = 0; i < fabrik_data_chain.size(); i++) { - Bone2D *joint_bone2d_node = Object::cast_to(ObjectDB::get_instance(fabrik_data_chain[i].bone2d_node_cache)); - if (!joint_bone2d_node) { - ERR_PRINT_ONCE("FABRIK Joint " + itos(i) + " does not have a Bone2D node set!"); - continue; - } - Transform2D chain_trans = fabrik_transform_chain[i]; - - // Apply rotation - if (i + 1 < fabrik_data_chain.size()) { - chain_trans = chain_trans.looking_at(fabrik_transform_chain[i + 1].get_origin()); - } else { - if (fabrik_data_chain[i].use_target_rotation) { - chain_trans.set_rotation(target_global_pose.get_rotation()); - } else { - chain_trans = chain_trans.looking_at(target_global_pose.get_origin()); - } - } - // Adjust for the bone angle - chain_trans.set_rotation(chain_trans.get_rotation() - joint_bone2d_node->get_bone_angle()); - - // Reset scale - chain_trans.set_scale(joint_bone2d_node->get_global_scale()); - - // Apply to the bone, and to the override - joint_bone2d_node->set_global_transform(chain_trans); - stack->skeleton->set_bone_local_pose_override(fabrik_data_chain[i].bone_idx, joint_bone2d_node->get_transform(), stack->strength, true); - } -} - -void SkeletonModification2DFABRIK::chain_backwards() { - int final_joint_index = fabrik_data_chain.size() - 1; - Bone2D *final_bone2d_node = Object::cast_to(ObjectDB::get_instance(fabrik_data_chain[final_joint_index].bone2d_node_cache)); - Transform2D final_bone2d_trans = fabrik_transform_chain[final_joint_index]; - - // Apply magnet position - if (final_joint_index != 0) { - final_bone2d_trans.set_origin(final_bone2d_trans.get_origin() + fabrik_data_chain[final_joint_index].magnet_position); - } - - // Set the rotation of the tip bone - final_bone2d_trans = final_bone2d_trans.looking_at(target_global_pose.get_origin()); - - // Set the position of the tip bone - float final_bone2d_angle = final_bone2d_trans.get_rotation(); - if (fabrik_data_chain[final_joint_index].use_target_rotation) { - final_bone2d_angle = target_global_pose.get_rotation(); - } - Vector2 final_bone2d_direction = Vector2(Math::cos(final_bone2d_angle), Math::sin(final_bone2d_angle)); - float final_bone2d_length = final_bone2d_node->get_length() * MIN(final_bone2d_node->get_global_scale().x, final_bone2d_node->get_global_scale().y); - final_bone2d_trans.set_origin(target_global_pose.get_origin() - (final_bone2d_direction * final_bone2d_length)); - - // Save the transform - fabrik_transform_chain.write[final_joint_index] = final_bone2d_trans; - - int i = final_joint_index; - while (i >= 1) { - Transform2D previous_pose = fabrik_transform_chain[i]; - i -= 1; - Bone2D *current_bone2d_node = Object::cast_to(ObjectDB::get_instance(fabrik_data_chain[i].bone2d_node_cache)); - Transform2D current_pose = fabrik_transform_chain[i]; - - // Apply magnet position - if (i != 0) { - current_pose.set_origin(current_pose.get_origin() + fabrik_data_chain[i].magnet_position); - } - - float current_bone2d_node_length = current_bone2d_node->get_length() * MIN(current_bone2d_node->get_global_scale().x, current_bone2d_node->get_global_scale().y); - float length = current_bone2d_node_length / (current_pose.get_origin().distance_to(previous_pose.get_origin())); - Vector2 finish_position = previous_pose.get_origin().lerp(current_pose.get_origin(), length); - current_pose.set_origin(finish_position); - - // Save the transform - fabrik_transform_chain.write[i] = current_pose; - } -} - -void SkeletonModification2DFABRIK::chain_forwards() { - Transform2D origin_bone2d_trans = fabrik_transform_chain[0]; - origin_bone2d_trans.set_origin(origin_global_pose.get_origin()); - // Save the position - fabrik_transform_chain.write[0] = origin_bone2d_trans; - - for (int i = 0; i < fabrik_data_chain.size() - 1; i++) { - Bone2D *current_bone2d_node = Object::cast_to(ObjectDB::get_instance(fabrik_data_chain[i].bone2d_node_cache)); - Transform2D current_pose = fabrik_transform_chain[i]; - Transform2D next_pose = fabrik_transform_chain[i + 1]; - - float current_bone2d_node_length = current_bone2d_node->get_length() * MIN(current_bone2d_node->get_global_scale().x, current_bone2d_node->get_global_scale().y); - float length = current_bone2d_node_length / (next_pose.get_origin().distance_to(current_pose.get_origin())); - Vector2 finish_position = current_pose.get_origin().lerp(next_pose.get_origin(), length); - current_pose.set_origin(finish_position); - - // Apply to the bone - fabrik_transform_chain.write[i + 1] = current_pose; - } -} - -void SkeletonModification2DFABRIK::_setup_modification(SkeletonModificationStack2D *p_stack) { - stack = p_stack; - - if (stack != nullptr) { - is_setup = true; - update_target_cache(); - } -} - -void SkeletonModification2DFABRIK::update_target_cache() { - if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); - return; - } - - target_node_cache = ObjectID(); - if (stack->skeleton) { - if (stack->skeleton->is_inside_tree()) { - if (stack->skeleton->has_node(target_node)) { - Node *node = stack->skeleton->get_node(target_node); - ERR_FAIL_COND_MSG(!node || stack->skeleton == node, - "Cannot update target cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update target cache: node is not in scene tree!"); - target_node_cache = node->get_instance_id(); - } - } - } -} - -void SkeletonModification2DFABRIK::fabrik_joint_update_bone2d_cache(int p_joint_idx) { - ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "Cannot update bone2d cache: joint index out of range!"); - if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update FABRIK Bone2D cache: modification is not properly setup!"); - return; - } - - fabrik_data_chain.write[p_joint_idx].bone2d_node_cache = ObjectID(); - if (stack->skeleton) { - if (stack->skeleton->is_inside_tree()) { - if (stack->skeleton->has_node(fabrik_data_chain[p_joint_idx].bone2d_node)) { - Node *node = stack->skeleton->get_node(fabrik_data_chain[p_joint_idx].bone2d_node); - ERR_FAIL_COND_MSG(!node || stack->skeleton == node, - "Cannot update FABRIK joint " + itos(p_joint_idx) + " Bone2D cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update FABRIK joint " + itos(p_joint_idx) + " Bone2D cache: node is not in scene tree!"); - fabrik_data_chain.write[p_joint_idx].bone2d_node_cache = node->get_instance_id(); - - Bone2D *bone = Object::cast_to(node); - if (bone) { - fabrik_data_chain.write[p_joint_idx].bone_idx = bone->get_index_in_skeleton(); - } else { - ERR_FAIL_MSG("FABRIK joint " + itos(p_joint_idx) + " Bone2D cache: Nodepath to Bone2D is not a Bone2D node!"); - } - } - } - } -} - -void SkeletonModification2DFABRIK::set_target_node(const NodePath &p_target_node) { - target_node = p_target_node; - update_target_cache(); -} - -NodePath SkeletonModification2DFABRIK::get_target_node() const { - return target_node; -} - -void SkeletonModification2DFABRIK::set_fabrik_data_chain_length(int p_length) { - fabrik_data_chain.resize(p_length); - notify_property_list_changed(); -} - -int SkeletonModification2DFABRIK::get_fabrik_data_chain_length() { - return fabrik_data_chain.size(); -} - -void SkeletonModification2DFABRIK::set_fabrik_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node) { - ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "FABRIK joint out of range!"); - fabrik_data_chain.write[p_joint_idx].bone2d_node = p_target_node; - fabrik_joint_update_bone2d_cache(p_joint_idx); - - notify_property_list_changed(); -} - -NodePath SkeletonModification2DFABRIK::get_fabrik_joint_bone2d_node(int p_joint_idx) const { - ERR_FAIL_INDEX_V_MSG(p_joint_idx, fabrik_data_chain.size(), NodePath(), "FABRIK joint out of range!"); - return fabrik_data_chain[p_joint_idx].bone2d_node; -} - -void SkeletonModification2DFABRIK::set_fabrik_joint_bone_index(int p_joint_idx, int p_bone_idx) { - ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "FABRIK joint out of range!"); - ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); - - if (is_setup) { - if (stack->skeleton) { - ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); - fabrik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; - fabrik_data_chain.write[p_joint_idx].bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id(); - fabrik_data_chain.write[p_joint_idx].bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx)); - } else { - WARN_PRINT("Cannot verify the FABRIK joint " + itos(p_joint_idx) + " bone index for this modification..."); - fabrik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; - } - } else { - WARN_PRINT("Cannot verify the FABRIK joint " + itos(p_joint_idx) + " bone index for this modification..."); - fabrik_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; - } - - notify_property_list_changed(); -} - -int SkeletonModification2DFABRIK::get_fabrik_joint_bone_index(int p_joint_idx) const { - ERR_FAIL_INDEX_V_MSG(p_joint_idx, fabrik_data_chain.size(), -1, "FABRIK joint out of range!"); - return fabrik_data_chain[p_joint_idx].bone_idx; -} - -void SkeletonModification2DFABRIK::set_fabrik_joint_magnet_position(int p_joint_idx, Vector2 p_magnet_position) { - ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "FABRIK joint out of range!"); - fabrik_data_chain.write[p_joint_idx].magnet_position = p_magnet_position; -} - -Vector2 SkeletonModification2DFABRIK::get_fabrik_joint_magnet_position(int p_joint_idx) const { - ERR_FAIL_INDEX_V_MSG(p_joint_idx, fabrik_data_chain.size(), Vector2(), "FABRIK joint out of range!"); - return fabrik_data_chain[p_joint_idx].magnet_position; -} - -void SkeletonModification2DFABRIK::set_fabrik_joint_use_target_rotation(int p_joint_idx, bool p_use_target_rotation) { - ERR_FAIL_INDEX_MSG(p_joint_idx, fabrik_data_chain.size(), "FABRIK joint out of range!"); - fabrik_data_chain.write[p_joint_idx].use_target_rotation = p_use_target_rotation; -} - -bool SkeletonModification2DFABRIK::get_fabrik_joint_use_target_rotation(int p_joint_idx) const { - ERR_FAIL_INDEX_V_MSG(p_joint_idx, fabrik_data_chain.size(), false, "FABRIK joint out of range!"); - return fabrik_data_chain[p_joint_idx].use_target_rotation; -} - -void SkeletonModification2DFABRIK::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DFABRIK::set_target_node); - ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DFABRIK::get_target_node); - - ClassDB::bind_method(D_METHOD("set_fabrik_data_chain_length", "length"), &SkeletonModification2DFABRIK::set_fabrik_data_chain_length); - ClassDB::bind_method(D_METHOD("get_fabrik_data_chain_length"), &SkeletonModification2DFABRIK::get_fabrik_data_chain_length); - - ClassDB::bind_method(D_METHOD("set_fabrik_joint_bone2d_node", "joint_idx", "bone2d_nodepath"), &SkeletonModification2DFABRIK::set_fabrik_joint_bone2d_node); - ClassDB::bind_method(D_METHOD("get_fabrik_joint_bone2d_node", "joint_idx"), &SkeletonModification2DFABRIK::get_fabrik_joint_bone2d_node); - ClassDB::bind_method(D_METHOD("set_fabrik_joint_bone_index", "joint_idx", "bone_idx"), &SkeletonModification2DFABRIK::set_fabrik_joint_bone_index); - ClassDB::bind_method(D_METHOD("get_fabrik_joint_bone_index", "joint_idx"), &SkeletonModification2DFABRIK::get_fabrik_joint_bone_index); - ClassDB::bind_method(D_METHOD("set_fabrik_joint_magnet_position", "joint_idx", "magnet_position"), &SkeletonModification2DFABRIK::set_fabrik_joint_magnet_position); - ClassDB::bind_method(D_METHOD("get_fabrik_joint_magnet_position", "joint_idx"), &SkeletonModification2DFABRIK::get_fabrik_joint_magnet_position); - ClassDB::bind_method(D_METHOD("set_fabrik_joint_use_target_rotation", "joint_idx", "use_target_rotation"), &SkeletonModification2DFABRIK::set_fabrik_joint_use_target_rotation); - ClassDB::bind_method(D_METHOD("get_fabrik_joint_use_target_rotation", "joint_idx"), &SkeletonModification2DFABRIK::get_fabrik_joint_use_target_rotation); - - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "fabrik_data_chain_length", PROPERTY_HINT_RANGE, "0, 100, 1"), "set_fabrik_data_chain_length", "get_fabrik_data_chain_length"); -} - -SkeletonModification2DFABRIK::SkeletonModification2DFABRIK() { - stack = nullptr; - is_setup = false; - enabled = true; - editor_draw_gizmo = false; -} - -SkeletonModification2DFABRIK::~SkeletonModification2DFABRIK() { -} diff --git a/scene/resources/skeleton_modification_2d_fabrik.h b/scene/resources/skeleton_modification_2d_fabrik.h deleted file mode 100644 index 3c8f1f779c..0000000000 --- a/scene/resources/skeleton_modification_2d_fabrik.h +++ /dev/null @@ -1,108 +0,0 @@ -/**************************************************************************/ -/* skeleton_modification_2d_fabrik.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 SKELETON_MODIFICATION_2D_FABRIK_H -#define SKELETON_MODIFICATION_2D_FABRIK_H - -#include "scene/2d/skeleton_2d.h" -#include "scene/resources/skeleton_modification_2d.h" - -/////////////////////////////////////// -// SkeletonModification2DFABRIK -/////////////////////////////////////// - -class SkeletonModification2DFABRIK : public SkeletonModification2D { - GDCLASS(SkeletonModification2DFABRIK, SkeletonModification2D); - -private: - struct FABRIK_Joint_Data2D { - int bone_idx = -1; - NodePath bone2d_node; - ObjectID bone2d_node_cache; - - Vector2 magnet_position = Vector2(0, 0); - bool use_target_rotation = false; - - bool editor_draw_gizmo = true; - }; - - Vector fabrik_data_chain; - - // Unlike in 3D, we need a vector of Transform2D objects to perform FABRIK. - // This is because FABRIK (unlike CCDIK) needs to operate on transforms that are NOT - // affected by each other, making the transforms stored in Bone2D unusable, as well as those in Skeleton2D. - // For this reason, this modification stores a vector of Transform2Ds used for the calculations, which are then applied at the end. - Vector fabrik_transform_chain; - - NodePath target_node; - ObjectID target_node_cache; - void update_target_cache(); - - float chain_tolarance = 0.01; - int chain_max_iterations = 10; - int chain_iterations = 0; - Transform2D target_global_pose; - Transform2D origin_global_pose; - - void fabrik_joint_update_bone2d_cache(int p_joint_idx); - void chain_backwards(); - void chain_forwards(); - -protected: - static void _bind_methods(); - bool _set(const StringName &p_path, const Variant &p_value); - bool _get(const StringName &p_path, Variant &r_ret) const; - void _get_property_list(List *p_list) const; - -public: - void _execute(float p_delta) override; - void _setup_modification(SkeletonModificationStack2D *p_stack) override; - - void set_target_node(const NodePath &p_target_node); - NodePath get_target_node() const; - - int get_fabrik_data_chain_length(); - void set_fabrik_data_chain_length(int p_new_length); - - void set_fabrik_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node); - NodePath get_fabrik_joint_bone2d_node(int p_joint_idx) const; - void set_fabrik_joint_bone_index(int p_joint_idx, int p_bone_idx); - int get_fabrik_joint_bone_index(int p_joint_idx) const; - - void set_fabrik_joint_magnet_position(int p_joint_idx, Vector2 p_magnet_position); - Vector2 get_fabrik_joint_magnet_position(int p_joint_idx) const; - void set_fabrik_joint_use_target_rotation(int p_joint_idx, bool p_use_target_rotation); - bool get_fabrik_joint_use_target_rotation(int p_joint_idx) const; - - SkeletonModification2DFABRIK(); - ~SkeletonModification2DFABRIK(); -}; - -#endif // SKELETON_MODIFICATION_2D_FABRIK_H diff --git a/scene/resources/skeleton_modification_2d_jiggle.cpp b/scene/resources/skeleton_modification_2d_jiggle.cpp deleted file mode 100644 index 2ace9577e4..0000000000 --- a/scene/resources/skeleton_modification_2d_jiggle.cpp +++ /dev/null @@ -1,572 +0,0 @@ -/**************************************************************************/ -/* skeleton_modification_2d_jiggle.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 "skeleton_modification_2d_jiggle.h" - -#include "scene/2d/skeleton_2d.h" -#include "scene/resources/world_2d.h" - -bool SkeletonModification2DJiggle::_set(const StringName &p_path, const Variant &p_value) { - String path = p_path; - - if (path.begins_with("joint_data/")) { - int which = path.get_slicec('/', 1).to_int(); - String what = path.get_slicec('/', 2); - ERR_FAIL_INDEX_V(which, jiggle_data_chain.size(), false); - - if (what == "bone2d_node") { - set_jiggle_joint_bone2d_node(which, p_value); - } else if (what == "bone_index") { - set_jiggle_joint_bone_index(which, p_value); - } else if (what == "override_defaults") { - set_jiggle_joint_override(which, p_value); - } else if (what == "stiffness") { - set_jiggle_joint_stiffness(which, p_value); - } else if (what == "mass") { - set_jiggle_joint_mass(which, p_value); - } else if (what == "damping") { - set_jiggle_joint_damping(which, p_value); - } else if (what == "use_gravity") { - set_jiggle_joint_use_gravity(which, p_value); - } else if (what == "gravity") { - set_jiggle_joint_gravity(which, p_value); - } else { - return false; - } - } else if (path == "use_colliders") { - set_use_colliders(p_value); - } else if (path == "collision_mask") { - set_collision_mask(p_value); - } else { - return false; - } - return true; -} - -bool SkeletonModification2DJiggle::_get(const StringName &p_path, Variant &r_ret) const { - String path = p_path; - - if (path.begins_with("joint_data/")) { - int which = path.get_slicec('/', 1).to_int(); - String what = path.get_slicec('/', 2); - ERR_FAIL_INDEX_V(which, jiggle_data_chain.size(), false); - - if (what == "bone2d_node") { - r_ret = get_jiggle_joint_bone2d_node(which); - } else if (what == "bone_index") { - r_ret = get_jiggle_joint_bone_index(which); - } else if (what == "override_defaults") { - r_ret = get_jiggle_joint_override(which); - } else if (what == "stiffness") { - r_ret = get_jiggle_joint_stiffness(which); - } else if (what == "mass") { - r_ret = get_jiggle_joint_mass(which); - } else if (what == "damping") { - r_ret = get_jiggle_joint_damping(which); - } else if (what == "use_gravity") { - r_ret = get_jiggle_joint_use_gravity(which); - } else if (what == "gravity") { - r_ret = get_jiggle_joint_gravity(which); - } else { - return false; - } - } else if (path == "use_colliders") { - r_ret = get_use_colliders(); - } else if (path == "collision_mask") { - r_ret = get_collision_mask(); - } else { - return false; - } - return true; -} - -void SkeletonModification2DJiggle::_get_property_list(List *p_list) const { - p_list->push_back(PropertyInfo(Variant::BOOL, "use_colliders", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - if (use_colliders) { - p_list->push_back(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS, "", PROPERTY_USAGE_DEFAULT)); - } - - for (int i = 0; i < jiggle_data_chain.size(); i++) { - String base_string = "joint_data/" + itos(i) + "/"; - - p_list->push_back(PropertyInfo(Variant::INT, base_string + "bone_index", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "override_defaults", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - - if (jiggle_data_chain[i].override_defaults) { - p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "stiffness", PROPERTY_HINT_RANGE, "0, 1000, 0.01", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "mass", PROPERTY_HINT_RANGE, "0, 1000, 0.01", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::FLOAT, base_string + "damping", PROPERTY_HINT_RANGE, "0, 1, 0.01", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::BOOL, base_string + "use_gravity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - if (jiggle_data_chain[i].use_gravity) { - p_list->push_back(PropertyInfo(Variant::VECTOR2, base_string + "gravity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - } - } - } -} - -void SkeletonModification2DJiggle::_execute(float p_delta) { - ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, - "Modification is not setup and therefore cannot execute!"); - if (!enabled) { - return; - } - if (target_node_cache.is_null()) { - WARN_PRINT_ONCE("Target cache is out of date. Attempting to update..."); - update_target_cache(); - return; - } - Node2D *target = Object::cast_to(ObjectDB::get_instance(target_node_cache)); - if (!target || !target->is_inside_tree()) { - ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!"); - return; - } - - for (int i = 0; i < jiggle_data_chain.size(); i++) { - _execute_jiggle_joint(i, target, p_delta); - } -} - -void SkeletonModification2DJiggle::_execute_jiggle_joint(int p_joint_idx, Node2D *p_target, float p_delta) { - // Adopted from: https://wiki.unity3d.com/index.php/JiggleBone - // With modifications by TwistedTwigleg. - - if (jiggle_data_chain[p_joint_idx].bone_idx <= -1 || jiggle_data_chain[p_joint_idx].bone_idx > stack->skeleton->get_bone_count()) { - ERR_PRINT_ONCE("Jiggle joint " + itos(p_joint_idx) + " bone index is invalid. Cannot execute modification on joint..."); - return; - } - - if (jiggle_data_chain[p_joint_idx].bone2d_node_cache.is_null() && !jiggle_data_chain[p_joint_idx].bone2d_node.is_empty()) { - WARN_PRINT_ONCE("Bone2D cache for joint " + itos(p_joint_idx) + " is out of date. Updating..."); - jiggle_joint_update_bone2d_cache(p_joint_idx); - } - - Bone2D *operation_bone = stack->skeleton->get_bone(jiggle_data_chain[p_joint_idx].bone_idx); - if (!operation_bone) { - ERR_PRINT_ONCE("Jiggle joint " + itos(p_joint_idx) + " does not have a Bone2D node or it cannot be found!"); - return; - } - - Transform2D operation_bone_trans = operation_bone->get_global_transform(); - Vector2 target_position = p_target->get_global_position(); - - jiggle_data_chain.write[p_joint_idx].force = (target_position - jiggle_data_chain[p_joint_idx].dynamic_position) * jiggle_data_chain[p_joint_idx].stiffness * p_delta; - - if (jiggle_data_chain[p_joint_idx].use_gravity) { - jiggle_data_chain.write[p_joint_idx].force += jiggle_data_chain[p_joint_idx].gravity * p_delta; - } - - jiggle_data_chain.write[p_joint_idx].acceleration = jiggle_data_chain[p_joint_idx].force / jiggle_data_chain[p_joint_idx].mass; - jiggle_data_chain.write[p_joint_idx].velocity += jiggle_data_chain[p_joint_idx].acceleration * (1 - jiggle_data_chain[p_joint_idx].damping); - - jiggle_data_chain.write[p_joint_idx].dynamic_position += jiggle_data_chain[p_joint_idx].velocity + jiggle_data_chain[p_joint_idx].force; - jiggle_data_chain.write[p_joint_idx].dynamic_position += operation_bone_trans.get_origin() - jiggle_data_chain[p_joint_idx].last_position; - jiggle_data_chain.write[p_joint_idx].last_position = operation_bone_trans.get_origin(); - - // Collision detection/response - if (use_colliders) { - if (execution_mode == SkeletonModificationStack2D::EXECUTION_MODE::execution_mode_physics_process) { - Ref world_2d = stack->skeleton->get_world_2d(); - ERR_FAIL_COND(world_2d.is_null()); - PhysicsDirectSpaceState2D *space_state = PhysicsServer2D::get_singleton()->space_get_direct_state(world_2d->get_space()); - PhysicsDirectSpaceState2D::RayResult ray_result; - - PhysicsDirectSpaceState2D::RayParameters ray_params; - ray_params.from = operation_bone_trans.get_origin(); - ray_params.to = jiggle_data_chain[p_joint_idx].dynamic_position; - ray_params.collision_mask = collision_mask; - - // Add exception support? - bool ray_hit = space_state->intersect_ray(ray_params, ray_result); - - if (ray_hit) { - jiggle_data_chain.write[p_joint_idx].dynamic_position = jiggle_data_chain[p_joint_idx].last_noncollision_position; - jiggle_data_chain.write[p_joint_idx].acceleration = Vector2(0, 0); - jiggle_data_chain.write[p_joint_idx].velocity = Vector2(0, 0); - } else { - jiggle_data_chain.write[p_joint_idx].last_noncollision_position = jiggle_data_chain[p_joint_idx].dynamic_position; - } - } else { - WARN_PRINT_ONCE("Jiggle 2D modifier: You cannot detect colliders without the stack mode being set to _physics_process!"); - } - } - - // Rotate the bone using the dynamic position! - operation_bone_trans = operation_bone_trans.looking_at(jiggle_data_chain[p_joint_idx].dynamic_position); - operation_bone_trans.set_rotation(operation_bone_trans.get_rotation() - operation_bone->get_bone_angle()); - - // Reset scale - operation_bone_trans.set_scale(operation_bone->get_global_scale()); - - operation_bone->set_global_transform(operation_bone_trans); - stack->skeleton->set_bone_local_pose_override(jiggle_data_chain[p_joint_idx].bone_idx, operation_bone->get_transform(), stack->strength, true); -} - -void SkeletonModification2DJiggle::_update_jiggle_joint_data() { - for (int i = 0; i < jiggle_data_chain.size(); i++) { - if (!jiggle_data_chain[i].override_defaults) { - set_jiggle_joint_stiffness(i, stiffness); - set_jiggle_joint_mass(i, mass); - set_jiggle_joint_damping(i, damping); - set_jiggle_joint_use_gravity(i, use_gravity); - set_jiggle_joint_gravity(i, gravity); - } - } -} - -void SkeletonModification2DJiggle::_setup_modification(SkeletonModificationStack2D *p_stack) { - stack = p_stack; - - if (stack) { - is_setup = true; - - if (stack->skeleton) { - for (int i = 0; i < jiggle_data_chain.size(); i++) { - int bone_idx = jiggle_data_chain[i].bone_idx; - if (bone_idx > 0 && bone_idx < stack->skeleton->get_bone_count()) { - Bone2D *bone2d_node = stack->skeleton->get_bone(bone_idx); - jiggle_data_chain.write[i].dynamic_position = bone2d_node->get_global_position(); - } - } - } - - update_target_cache(); - } -} - -void SkeletonModification2DJiggle::update_target_cache() { - if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); - return; - } - - target_node_cache = ObjectID(); - if (stack->skeleton) { - if (stack->skeleton->is_inside_tree()) { - if (stack->skeleton->has_node(target_node)) { - Node *node = stack->skeleton->get_node(target_node); - ERR_FAIL_COND_MSG(!node || stack->skeleton == node, - "Cannot update target cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update target cache: node is not in scene tree!"); - target_node_cache = node->get_instance_id(); - } - } - } -} - -void SkeletonModification2DJiggle::jiggle_joint_update_bone2d_cache(int p_joint_idx) { - ERR_FAIL_INDEX_MSG(p_joint_idx, jiggle_data_chain.size(), "Cannot update bone2d cache: joint index out of range!"); - if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update Jiggle " + itos(p_joint_idx) + " Bone2D cache: modification is not properly setup!"); - return; - } - - jiggle_data_chain.write[p_joint_idx].bone2d_node_cache = ObjectID(); - if (stack->skeleton) { - if (stack->skeleton->is_inside_tree()) { - if (stack->skeleton->has_node(jiggle_data_chain[p_joint_idx].bone2d_node)) { - Node *node = stack->skeleton->get_node(jiggle_data_chain[p_joint_idx].bone2d_node); - ERR_FAIL_COND_MSG(!node || stack->skeleton == node, - "Cannot update Jiggle joint " + itos(p_joint_idx) + " Bone2D cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update Jiggle joint " + itos(p_joint_idx) + " Bone2D cache: node is not in scene tree!"); - jiggle_data_chain.write[p_joint_idx].bone2d_node_cache = node->get_instance_id(); - - Bone2D *bone = Object::cast_to(node); - if (bone) { - jiggle_data_chain.write[p_joint_idx].bone_idx = bone->get_index_in_skeleton(); - } else { - ERR_FAIL_MSG("Jiggle joint " + itos(p_joint_idx) + " Bone2D cache: Nodepath to Bone2D is not a Bone2D node!"); - } - } - } - } -} - -void SkeletonModification2DJiggle::set_target_node(const NodePath &p_target_node) { - target_node = p_target_node; - update_target_cache(); -} - -NodePath SkeletonModification2DJiggle::get_target_node() const { - return target_node; -} - -void SkeletonModification2DJiggle::set_stiffness(float p_stiffness) { - ERR_FAIL_COND_MSG(p_stiffness < 0, "Stiffness cannot be set to a negative value!"); - stiffness = p_stiffness; - _update_jiggle_joint_data(); -} - -float SkeletonModification2DJiggle::get_stiffness() const { - return stiffness; -} - -void SkeletonModification2DJiggle::set_mass(float p_mass) { - ERR_FAIL_COND_MSG(p_mass < 0, "Mass cannot be set to a negative value!"); - mass = p_mass; - _update_jiggle_joint_data(); -} - -float SkeletonModification2DJiggle::get_mass() const { - return mass; -} - -void SkeletonModification2DJiggle::set_damping(float p_damping) { - ERR_FAIL_COND_MSG(p_damping < 0, "Damping cannot be set to a negative value!"); - ERR_FAIL_COND_MSG(p_damping > 1, "Damping cannot be more than one!"); - damping = p_damping; - _update_jiggle_joint_data(); -} - -float SkeletonModification2DJiggle::get_damping() const { - return damping; -} - -void SkeletonModification2DJiggle::set_use_gravity(bool p_use_gravity) { - use_gravity = p_use_gravity; - _update_jiggle_joint_data(); -} - -bool SkeletonModification2DJiggle::get_use_gravity() const { - return use_gravity; -} - -void SkeletonModification2DJiggle::set_gravity(Vector2 p_gravity) { - gravity = p_gravity; - _update_jiggle_joint_data(); -} - -Vector2 SkeletonModification2DJiggle::get_gravity() const { - return gravity; -} - -void SkeletonModification2DJiggle::set_use_colliders(bool p_use_colliders) { - use_colliders = p_use_colliders; - notify_property_list_changed(); -} - -bool SkeletonModification2DJiggle::get_use_colliders() const { - return use_colliders; -} - -void SkeletonModification2DJiggle::set_collision_mask(int p_mask) { - collision_mask = p_mask; -} - -int SkeletonModification2DJiggle::get_collision_mask() const { - return collision_mask; -} - -// Jiggle joint data functions -int SkeletonModification2DJiggle::get_jiggle_data_chain_length() { - return jiggle_data_chain.size(); -} - -void SkeletonModification2DJiggle::set_jiggle_data_chain_length(int p_length) { - ERR_FAIL_COND(p_length < 0); - jiggle_data_chain.resize(p_length); - notify_property_list_changed(); -} - -void SkeletonModification2DJiggle::set_jiggle_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node) { - ERR_FAIL_INDEX_MSG(p_joint_idx, jiggle_data_chain.size(), "Jiggle joint out of range!"); - jiggle_data_chain.write[p_joint_idx].bone2d_node = p_target_node; - jiggle_joint_update_bone2d_cache(p_joint_idx); - - notify_property_list_changed(); -} - -NodePath SkeletonModification2DJiggle::get_jiggle_joint_bone2d_node(int p_joint_idx) const { - ERR_FAIL_INDEX_V_MSG(p_joint_idx, jiggle_data_chain.size(), NodePath(), "Jiggle joint out of range!"); - return jiggle_data_chain[p_joint_idx].bone2d_node; -} - -void SkeletonModification2DJiggle::set_jiggle_joint_bone_index(int p_joint_idx, int p_bone_idx) { - ERR_FAIL_INDEX_MSG(p_joint_idx, jiggle_data_chain.size(), "Jiggle joint out of range!"); - ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); - - if (is_setup) { - if (stack->skeleton) { - ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); - jiggle_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; - jiggle_data_chain.write[p_joint_idx].bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id(); - jiggle_data_chain.write[p_joint_idx].bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx)); - } else { - WARN_PRINT("Cannot verify the Jiggle joint " + itos(p_joint_idx) + " bone index for this modification..."); - jiggle_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; - } - } else { - WARN_PRINT("Cannot verify the Jiggle joint " + itos(p_joint_idx) + " bone index for this modification..."); - jiggle_data_chain.write[p_joint_idx].bone_idx = p_bone_idx; - } - - notify_property_list_changed(); -} - -int SkeletonModification2DJiggle::get_jiggle_joint_bone_index(int p_joint_idx) const { - ERR_FAIL_INDEX_V_MSG(p_joint_idx, jiggle_data_chain.size(), -1, "Jiggle joint out of range!"); - return jiggle_data_chain[p_joint_idx].bone_idx; -} - -void SkeletonModification2DJiggle::set_jiggle_joint_override(int p_joint_idx, bool p_override) { - ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size()); - jiggle_data_chain.write[p_joint_idx].override_defaults = p_override; - _update_jiggle_joint_data(); - notify_property_list_changed(); -} - -bool SkeletonModification2DJiggle::get_jiggle_joint_override(int p_joint_idx) const { - ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), false); - return jiggle_data_chain[p_joint_idx].override_defaults; -} - -void SkeletonModification2DJiggle::set_jiggle_joint_stiffness(int p_joint_idx, float p_stiffness) { - ERR_FAIL_COND_MSG(p_stiffness < 0, "Stiffness cannot be set to a negative value!"); - ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size()); - jiggle_data_chain.write[p_joint_idx].stiffness = p_stiffness; -} - -float SkeletonModification2DJiggle::get_jiggle_joint_stiffness(int p_joint_idx) const { - ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), -1); - return jiggle_data_chain[p_joint_idx].stiffness; -} - -void SkeletonModification2DJiggle::set_jiggle_joint_mass(int p_joint_idx, float p_mass) { - ERR_FAIL_COND_MSG(p_mass < 0, "Mass cannot be set to a negative value!"); - ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size()); - jiggle_data_chain.write[p_joint_idx].mass = p_mass; -} - -float SkeletonModification2DJiggle::get_jiggle_joint_mass(int p_joint_idx) const { - ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), -1); - return jiggle_data_chain[p_joint_idx].mass; -} - -void SkeletonModification2DJiggle::set_jiggle_joint_damping(int p_joint_idx, float p_damping) { - ERR_FAIL_COND_MSG(p_damping < 0, "Damping cannot be set to a negative value!"); - ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size()); - jiggle_data_chain.write[p_joint_idx].damping = p_damping; -} - -float SkeletonModification2DJiggle::get_jiggle_joint_damping(int p_joint_idx) const { - ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), -1); - return jiggle_data_chain[p_joint_idx].damping; -} - -void SkeletonModification2DJiggle::set_jiggle_joint_use_gravity(int p_joint_idx, bool p_use_gravity) { - ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size()); - jiggle_data_chain.write[p_joint_idx].use_gravity = p_use_gravity; - notify_property_list_changed(); -} - -bool SkeletonModification2DJiggle::get_jiggle_joint_use_gravity(int p_joint_idx) const { - ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), false); - return jiggle_data_chain[p_joint_idx].use_gravity; -} - -void SkeletonModification2DJiggle::set_jiggle_joint_gravity(int p_joint_idx, Vector2 p_gravity) { - ERR_FAIL_INDEX(p_joint_idx, jiggle_data_chain.size()); - jiggle_data_chain.write[p_joint_idx].gravity = p_gravity; -} - -Vector2 SkeletonModification2DJiggle::get_jiggle_joint_gravity(int p_joint_idx) const { - ERR_FAIL_INDEX_V(p_joint_idx, jiggle_data_chain.size(), Vector2(0, 0)); - return jiggle_data_chain[p_joint_idx].gravity; -} - -void SkeletonModification2DJiggle::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DJiggle::set_target_node); - ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DJiggle::get_target_node); - - ClassDB::bind_method(D_METHOD("set_jiggle_data_chain_length", "length"), &SkeletonModification2DJiggle::set_jiggle_data_chain_length); - ClassDB::bind_method(D_METHOD("get_jiggle_data_chain_length"), &SkeletonModification2DJiggle::get_jiggle_data_chain_length); - - ClassDB::bind_method(D_METHOD("set_stiffness", "stiffness"), &SkeletonModification2DJiggle::set_stiffness); - ClassDB::bind_method(D_METHOD("get_stiffness"), &SkeletonModification2DJiggle::get_stiffness); - ClassDB::bind_method(D_METHOD("set_mass", "mass"), &SkeletonModification2DJiggle::set_mass); - ClassDB::bind_method(D_METHOD("get_mass"), &SkeletonModification2DJiggle::get_mass); - ClassDB::bind_method(D_METHOD("set_damping", "damping"), &SkeletonModification2DJiggle::set_damping); - ClassDB::bind_method(D_METHOD("get_damping"), &SkeletonModification2DJiggle::get_damping); - ClassDB::bind_method(D_METHOD("set_use_gravity", "use_gravity"), &SkeletonModification2DJiggle::set_use_gravity); - ClassDB::bind_method(D_METHOD("get_use_gravity"), &SkeletonModification2DJiggle::get_use_gravity); - ClassDB::bind_method(D_METHOD("set_gravity", "gravity"), &SkeletonModification2DJiggle::set_gravity); - ClassDB::bind_method(D_METHOD("get_gravity"), &SkeletonModification2DJiggle::get_gravity); - - ClassDB::bind_method(D_METHOD("set_use_colliders", "use_colliders"), &SkeletonModification2DJiggle::set_use_colliders); - ClassDB::bind_method(D_METHOD("get_use_colliders"), &SkeletonModification2DJiggle::get_use_colliders); - ClassDB::bind_method(D_METHOD("set_collision_mask", "collision_mask"), &SkeletonModification2DJiggle::set_collision_mask); - ClassDB::bind_method(D_METHOD("get_collision_mask"), &SkeletonModification2DJiggle::get_collision_mask); - - // Jiggle joint data functions - ClassDB::bind_method(D_METHOD("set_jiggle_joint_bone2d_node", "joint_idx", "bone2d_node"), &SkeletonModification2DJiggle::set_jiggle_joint_bone2d_node); - ClassDB::bind_method(D_METHOD("get_jiggle_joint_bone2d_node", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_bone2d_node); - ClassDB::bind_method(D_METHOD("set_jiggle_joint_bone_index", "joint_idx", "bone_idx"), &SkeletonModification2DJiggle::set_jiggle_joint_bone_index); - ClassDB::bind_method(D_METHOD("get_jiggle_joint_bone_index", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_bone_index); - ClassDB::bind_method(D_METHOD("set_jiggle_joint_override", "joint_idx", "override"), &SkeletonModification2DJiggle::set_jiggle_joint_override); - ClassDB::bind_method(D_METHOD("get_jiggle_joint_override", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_override); - ClassDB::bind_method(D_METHOD("set_jiggle_joint_stiffness", "joint_idx", "stiffness"), &SkeletonModification2DJiggle::set_jiggle_joint_stiffness); - ClassDB::bind_method(D_METHOD("get_jiggle_joint_stiffness", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_stiffness); - ClassDB::bind_method(D_METHOD("set_jiggle_joint_mass", "joint_idx", "mass"), &SkeletonModification2DJiggle::set_jiggle_joint_mass); - ClassDB::bind_method(D_METHOD("get_jiggle_joint_mass", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_mass); - ClassDB::bind_method(D_METHOD("set_jiggle_joint_damping", "joint_idx", "damping"), &SkeletonModification2DJiggle::set_jiggle_joint_damping); - ClassDB::bind_method(D_METHOD("get_jiggle_joint_damping", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_damping); - ClassDB::bind_method(D_METHOD("set_jiggle_joint_use_gravity", "joint_idx", "use_gravity"), &SkeletonModification2DJiggle::set_jiggle_joint_use_gravity); - ClassDB::bind_method(D_METHOD("get_jiggle_joint_use_gravity", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_use_gravity); - ClassDB::bind_method(D_METHOD("set_jiggle_joint_gravity", "joint_idx", "gravity"), &SkeletonModification2DJiggle::set_jiggle_joint_gravity); - ClassDB::bind_method(D_METHOD("get_jiggle_joint_gravity", "joint_idx"), &SkeletonModification2DJiggle::get_jiggle_joint_gravity); - - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "jiggle_data_chain_length", PROPERTY_HINT_RANGE, "0,100,1"), "set_jiggle_data_chain_length", "get_jiggle_data_chain_length"); - ADD_GROUP("Default Joint Settings", ""); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "stiffness"), "set_stiffness", "get_stiffness"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mass"), "set_mass", "get_mass"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "damping", PROPERTY_HINT_RANGE, "0, 1, 0.01"), "set_damping", "get_damping"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_gravity"), "set_use_gravity", "get_use_gravity"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "gravity"), "set_gravity", "get_gravity"); - ADD_GROUP("", ""); -} - -SkeletonModification2DJiggle::SkeletonModification2DJiggle() { - stack = nullptr; - is_setup = false; - jiggle_data_chain = Vector(); - stiffness = 3; - mass = 0.75; - damping = 0.75; - use_gravity = false; - gravity = Vector2(0, 6.0); - enabled = true; - editor_draw_gizmo = false; // Nothing to really show in a gizmo right now. -} - -SkeletonModification2DJiggle::~SkeletonModification2DJiggle() { -} diff --git a/scene/resources/skeleton_modification_2d_jiggle.h b/scene/resources/skeleton_modification_2d_jiggle.h deleted file mode 100644 index 9e0d8d1bd4..0000000000 --- a/scene/resources/skeleton_modification_2d_jiggle.h +++ /dev/null @@ -1,139 +0,0 @@ -/**************************************************************************/ -/* skeleton_modification_2d_jiggle.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 SKELETON_MODIFICATION_2D_JIGGLE_H -#define SKELETON_MODIFICATION_2D_JIGGLE_H - -#include "scene/2d/skeleton_2d.h" -#include "scene/resources/skeleton_modification_2d.h" - -/////////////////////////////////////// -// SkeletonModification2DJIGGLE -/////////////////////////////////////// - -class SkeletonModification2DJiggle : public SkeletonModification2D { - GDCLASS(SkeletonModification2DJiggle, SkeletonModification2D); - -private: - struct Jiggle_Joint_Data2D { - int bone_idx = -1; - NodePath bone2d_node; - ObjectID bone2d_node_cache; - - bool override_defaults = false; - float stiffness = 3; - float mass = 0.75; - float damping = 0.75; - bool use_gravity = false; - Vector2 gravity = Vector2(0, 6.0); - - Vector2 force = Vector2(0, 0); - Vector2 acceleration = Vector2(0, 0); - Vector2 velocity = Vector2(0, 0); - Vector2 last_position = Vector2(0, 0); - Vector2 dynamic_position = Vector2(0, 0); - - Vector2 last_noncollision_position = Vector2(0, 0); - }; - - Vector jiggle_data_chain; - - NodePath target_node; - ObjectID target_node_cache; - void update_target_cache(); - - float stiffness = 3; - float mass = 0.75; - float damping = 0.75; - bool use_gravity = false; - Vector2 gravity = Vector2(0, 6); - - bool use_colliders = false; - uint32_t collision_mask = 1; - - void jiggle_joint_update_bone2d_cache(int p_joint_idx); - void _execute_jiggle_joint(int p_joint_idx, Node2D *p_target, float p_delta); - void _update_jiggle_joint_data(); - -protected: - static void _bind_methods(); - bool _set(const StringName &p_path, const Variant &p_value); - bool _get(const StringName &p_path, Variant &r_ret) const; - void _get_property_list(List *p_list) const; - -public: - void _execute(float p_delta) override; - void _setup_modification(SkeletonModificationStack2D *p_stack) override; - - void set_target_node(const NodePath &p_target_node); - NodePath get_target_node() const; - - void set_stiffness(float p_stiffness); - float get_stiffness() const; - void set_mass(float p_mass); - float get_mass() const; - void set_damping(float p_damping); - float get_damping() const; - void set_use_gravity(bool p_use_gravity); - bool get_use_gravity() const; - void set_gravity(Vector2 p_gravity); - Vector2 get_gravity() const; - - void set_use_colliders(bool p_use_colliders); - bool get_use_colliders() const; - void set_collision_mask(int p_mask); - int get_collision_mask() const; - - int get_jiggle_data_chain_length(); - void set_jiggle_data_chain_length(int p_new_length); - - void set_jiggle_joint_bone2d_node(int p_joint_idx, const NodePath &p_target_node); - NodePath get_jiggle_joint_bone2d_node(int p_joint_idx) const; - void set_jiggle_joint_bone_index(int p_joint_idx, int p_bone_idx); - int get_jiggle_joint_bone_index(int p_joint_idx) const; - - void set_jiggle_joint_override(int p_joint_idx, bool p_override); - bool get_jiggle_joint_override(int p_joint_idx) const; - void set_jiggle_joint_stiffness(int p_joint_idx, float p_stiffness); - float get_jiggle_joint_stiffness(int p_joint_idx) const; - void set_jiggle_joint_mass(int p_joint_idx, float p_mass); - float get_jiggle_joint_mass(int p_joint_idx) const; - void set_jiggle_joint_damping(int p_joint_idx, float p_damping); - float get_jiggle_joint_damping(int p_joint_idx) const; - void set_jiggle_joint_use_gravity(int p_joint_idx, bool p_use_gravity); - bool get_jiggle_joint_use_gravity(int p_joint_idx) const; - void set_jiggle_joint_gravity(int p_joint_idx, Vector2 p_gravity); - Vector2 get_jiggle_joint_gravity(int p_joint_idx) const; - - SkeletonModification2DJiggle(); - ~SkeletonModification2DJiggle(); -}; - -#endif // SKELETON_MODIFICATION_2D_JIGGLE_H diff --git a/scene/resources/skeleton_modification_2d_lookat.cpp b/scene/resources/skeleton_modification_2d_lookat.cpp deleted file mode 100644 index 8f6f6bc4ae..0000000000 --- a/scene/resources/skeleton_modification_2d_lookat.cpp +++ /dev/null @@ -1,411 +0,0 @@ -/**************************************************************************/ -/* skeleton_modification_2d_lookat.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 "skeleton_modification_2d_lookat.h" -#include "scene/2d/skeleton_2d.h" - -#ifdef TOOLS_ENABLED -#include "editor/editor_settings.h" -#endif // TOOLS_ENABLED - -bool SkeletonModification2DLookAt::_set(const StringName &p_path, const Variant &p_value) { - String path = p_path; - - if (path.begins_with("enable_constraint")) { - set_enable_constraint(p_value); - } else if (path.begins_with("constraint_angle_min")) { - set_constraint_angle_min(Math::deg_to_rad(float(p_value))); - } else if (path.begins_with("constraint_angle_max")) { - set_constraint_angle_max(Math::deg_to_rad(float(p_value))); - } else if (path.begins_with("constraint_angle_invert")) { - set_constraint_angle_invert(p_value); - } else if (path.begins_with("constraint_in_localspace")) { - set_constraint_in_localspace(p_value); - } else if (path.begins_with("additional_rotation")) { - set_additional_rotation(Math::deg_to_rad(float(p_value))); - } -#ifdef TOOLS_ENABLED - else if (path.begins_with("editor/draw_gizmo")) { - set_editor_draw_gizmo(p_value); - } -#endif // TOOLS_ENABLED - else { - return false; - } - - return true; -} - -bool SkeletonModification2DLookAt::_get(const StringName &p_path, Variant &r_ret) const { - String path = p_path; - - if (path.begins_with("enable_constraint")) { - r_ret = get_enable_constraint(); - } else if (path.begins_with("constraint_angle_min")) { - r_ret = Math::rad_to_deg(get_constraint_angle_min()); - } else if (path.begins_with("constraint_angle_max")) { - r_ret = Math::rad_to_deg(get_constraint_angle_max()); - } else if (path.begins_with("constraint_angle_invert")) { - r_ret = get_constraint_angle_invert(); - } else if (path.begins_with("constraint_in_localspace")) { - r_ret = get_constraint_in_localspace(); - } else if (path.begins_with("additional_rotation")) { - r_ret = Math::rad_to_deg(get_additional_rotation()); - } -#ifdef TOOLS_ENABLED - else if (path.begins_with("editor/draw_gizmo")) { - r_ret = get_editor_draw_gizmo(); - } -#endif // TOOLS_ENABLED - else { - return false; - } - - return true; -} - -void SkeletonModification2DLookAt::_get_property_list(List *p_list) const { - p_list->push_back(PropertyInfo(Variant::BOOL, "enable_constraint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - if (enable_constraint) { - p_list->push_back(PropertyInfo(Variant::FLOAT, "constraint_angle_min", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::FLOAT, "constraint_angle_max", PROPERTY_HINT_RANGE, "-360, 360, 0.01", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::BOOL, "constraint_angle_invert", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::BOOL, "constraint_in_localspace", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - } - p_list->push_back(PropertyInfo(Variant::FLOAT, "additional_rotation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - } -#endif // TOOLS_ENABLED -} - -void SkeletonModification2DLookAt::_execute(float p_delta) { - ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, - "Modification is not setup and therefore cannot execute!"); - if (!enabled) { - return; - } - - if (target_node_cache.is_null()) { - WARN_PRINT_ONCE("Target cache is out of date. Attempting to update..."); - update_target_cache(); - return; - } - - if (bone2d_node_cache.is_null() && !bone2d_node.is_empty()) { - update_bone2d_cache(); - WARN_PRINT_ONCE("Bone2D node cache is out of date. Attempting to update..."); - return; - } - - if (target_node_reference == nullptr) { - target_node_reference = Object::cast_to(ObjectDB::get_instance(target_node_cache)); - } - if (!target_node_reference || !target_node_reference->is_inside_tree()) { - ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!"); - return; - } - if (bone_idx <= -1) { - ERR_PRINT_ONCE("Bone index is invalid. Cannot execute modification!"); - return; - } - - Bone2D *operation_bone = stack->skeleton->get_bone(bone_idx); - if (operation_bone == nullptr) { - ERR_PRINT_ONCE("bone_idx for modification does not point to a valid bone! Cannot execute modification"); - return; - } - - Transform2D operation_transform = operation_bone->get_global_transform(); - Transform2D target_trans = target_node_reference->get_global_transform(); - - // Look at the target! - operation_transform = operation_transform.looking_at(target_trans.get_origin()); - // Apply whatever scale it had prior to looking_at - operation_transform.set_scale(operation_bone->get_global_scale()); - - // Account for the direction the bone faces in: - operation_transform.set_rotation(operation_transform.get_rotation() - operation_bone->get_bone_angle()); - - // Apply additional rotation - operation_transform.set_rotation(operation_transform.get_rotation() + additional_rotation); - - // Apply constraints in globalspace: - if (enable_constraint && !constraint_in_localspace) { - operation_transform.set_rotation(clamp_angle(operation_transform.get_rotation(), constraint_angle_min, constraint_angle_max, constraint_angle_invert)); - } - - // Convert from a global transform to a local transform via the Bone2D node - operation_bone->set_global_transform(operation_transform); - operation_transform = operation_bone->get_transform(); - - // Apply constraints in localspace: - if (enable_constraint && constraint_in_localspace) { - operation_transform.set_rotation(clamp_angle(operation_transform.get_rotation(), constraint_angle_min, constraint_angle_max, constraint_angle_invert)); - } - - // Set the local pose override, and to make sure child bones are also updated, set the transform of the bone. - stack->skeleton->set_bone_local_pose_override(bone_idx, operation_transform, stack->strength, true); - operation_bone->set_transform(operation_transform); -} - -void SkeletonModification2DLookAt::_setup_modification(SkeletonModificationStack2D *p_stack) { - stack = p_stack; - - if (stack != nullptr) { - is_setup = true; - update_target_cache(); - update_bone2d_cache(); - } -} - -void SkeletonModification2DLookAt::_draw_editor_gizmo() { - if (!enabled || !is_setup) { - return; - } - - Bone2D *operation_bone = stack->skeleton->get_bone(bone_idx); - editor_draw_angle_constraints(operation_bone, constraint_angle_min, constraint_angle_max, - enable_constraint, constraint_in_localspace, constraint_angle_invert); -} - -void SkeletonModification2DLookAt::update_bone2d_cache() { - if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update Bone2D cache: modification is not properly setup!"); - return; - } - - bone2d_node_cache = ObjectID(); - if (stack->skeleton) { - if (stack->skeleton->is_inside_tree()) { - if (stack->skeleton->has_node(bone2d_node)) { - Node *node = stack->skeleton->get_node(bone2d_node); - ERR_FAIL_COND_MSG(!node || stack->skeleton == node, - "Cannot update Bone2D cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update Bone2D cache: node is not in the scene tree!"); - bone2d_node_cache = node->get_instance_id(); - - Bone2D *bone = Object::cast_to(node); - if (bone) { - bone_idx = bone->get_index_in_skeleton(); - } else { - ERR_FAIL_MSG("Error Bone2D cache: Nodepath to Bone2D is not a Bone2D node!"); - } - - // Set this to null so we update it - target_node_reference = nullptr; - } - } - } -} - -void SkeletonModification2DLookAt::set_bone2d_node(const NodePath &p_target_node) { - bone2d_node = p_target_node; - update_bone2d_cache(); -} - -NodePath SkeletonModification2DLookAt::get_bone2d_node() const { - return bone2d_node; -} - -int SkeletonModification2DLookAt::get_bone_index() const { - return bone_idx; -} - -void SkeletonModification2DLookAt::set_bone_index(int p_bone_idx) { - ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); - - if (is_setup && stack) { - if (stack->skeleton) { - ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); - bone_idx = p_bone_idx; - bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id(); - bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx)); - } else { - WARN_PRINT("Cannot verify the bone index for this modification..."); - bone_idx = p_bone_idx; - } - } else { - WARN_PRINT("Cannot verify the bone index for this modification..."); - bone_idx = p_bone_idx; - } - - notify_property_list_changed(); -} - -void SkeletonModification2DLookAt::update_target_cache() { - if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); - return; - } - - target_node_cache = ObjectID(); - if (stack->skeleton) { - if (stack->skeleton->is_inside_tree()) { - if (stack->skeleton->has_node(target_node)) { - Node *node = stack->skeleton->get_node(target_node); - ERR_FAIL_COND_MSG(!node || stack->skeleton == node, - "Cannot update target cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update target cache: node is not in the scene tree!"); - target_node_cache = node->get_instance_id(); - } - } - } -} - -void SkeletonModification2DLookAt::set_target_node(const NodePath &p_target_node) { - target_node = p_target_node; - update_target_cache(); -} - -NodePath SkeletonModification2DLookAt::get_target_node() const { - return target_node; -} - -float SkeletonModification2DLookAt::get_additional_rotation() const { - return additional_rotation; -} - -void SkeletonModification2DLookAt::set_additional_rotation(float p_rotation) { - additional_rotation = p_rotation; -} - -void SkeletonModification2DLookAt::set_enable_constraint(bool p_constraint) { - enable_constraint = p_constraint; - notify_property_list_changed(); -#ifdef TOOLS_ENABLED - if (stack && is_setup) { - stack->set_editor_gizmos_dirty(true); - } -#endif // TOOLS_ENABLED -} - -bool SkeletonModification2DLookAt::get_enable_constraint() const { - return enable_constraint; -} - -void SkeletonModification2DLookAt::set_constraint_angle_min(float p_angle_min) { - constraint_angle_min = p_angle_min; -#ifdef TOOLS_ENABLED - if (stack && is_setup) { - stack->set_editor_gizmos_dirty(true); - } -#endif // TOOLS_ENABLED -} - -float SkeletonModification2DLookAt::get_constraint_angle_min() const { - return constraint_angle_min; -} - -void SkeletonModification2DLookAt::set_constraint_angle_max(float p_angle_max) { - constraint_angle_max = p_angle_max; -#ifdef TOOLS_ENABLED - if (stack && is_setup) { - stack->set_editor_gizmos_dirty(true); - } -#endif // TOOLS_ENABLED -} - -float SkeletonModification2DLookAt::get_constraint_angle_max() const { - return constraint_angle_max; -} - -void SkeletonModification2DLookAt::set_constraint_angle_invert(bool p_invert) { - constraint_angle_invert = p_invert; -#ifdef TOOLS_ENABLED - if (stack && is_setup) { - stack->set_editor_gizmos_dirty(true); - } -#endif // TOOLS_ENABLED -} - -bool SkeletonModification2DLookAt::get_constraint_angle_invert() const { - return constraint_angle_invert; -} - -void SkeletonModification2DLookAt::set_constraint_in_localspace(bool p_constraint_in_localspace) { - constraint_in_localspace = p_constraint_in_localspace; -#ifdef TOOLS_ENABLED - if (stack && is_setup) { - stack->set_editor_gizmos_dirty(true); - } -#endif // TOOLS_ENABLED -} - -bool SkeletonModification2DLookAt::get_constraint_in_localspace() const { - return constraint_in_localspace; -} - -void SkeletonModification2DLookAt::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_bone2d_node", "bone2d_nodepath"), &SkeletonModification2DLookAt::set_bone2d_node); - ClassDB::bind_method(D_METHOD("get_bone2d_node"), &SkeletonModification2DLookAt::get_bone2d_node); - ClassDB::bind_method(D_METHOD("set_bone_index", "bone_idx"), &SkeletonModification2DLookAt::set_bone_index); - ClassDB::bind_method(D_METHOD("get_bone_index"), &SkeletonModification2DLookAt::get_bone_index); - - ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DLookAt::set_target_node); - ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DLookAt::get_target_node); - - ClassDB::bind_method(D_METHOD("set_additional_rotation", "rotation"), &SkeletonModification2DLookAt::set_additional_rotation); - ClassDB::bind_method(D_METHOD("get_additional_rotation"), &SkeletonModification2DLookAt::get_additional_rotation); - - ClassDB::bind_method(D_METHOD("set_enable_constraint", "enable_constraint"), &SkeletonModification2DLookAt::set_enable_constraint); - ClassDB::bind_method(D_METHOD("get_enable_constraint"), &SkeletonModification2DLookAt::get_enable_constraint); - ClassDB::bind_method(D_METHOD("set_constraint_angle_min", "angle_min"), &SkeletonModification2DLookAt::set_constraint_angle_min); - ClassDB::bind_method(D_METHOD("get_constraint_angle_min"), &SkeletonModification2DLookAt::get_constraint_angle_min); - ClassDB::bind_method(D_METHOD("set_constraint_angle_max", "angle_max"), &SkeletonModification2DLookAt::set_constraint_angle_max); - ClassDB::bind_method(D_METHOD("get_constraint_angle_max"), &SkeletonModification2DLookAt::get_constraint_angle_max); - ClassDB::bind_method(D_METHOD("set_constraint_angle_invert", "invert"), &SkeletonModification2DLookAt::set_constraint_angle_invert); - ClassDB::bind_method(D_METHOD("get_constraint_angle_invert"), &SkeletonModification2DLookAt::get_constraint_angle_invert); - - ADD_PROPERTY(PropertyInfo(Variant::INT, "bone_index"), "set_bone_index", "get_bone_index"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D"), "set_bone2d_node", "get_bone2d_node"); - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node"); -} - -SkeletonModification2DLookAt::SkeletonModification2DLookAt() { - stack = nullptr; - is_setup = false; - bone_idx = -1; - additional_rotation = 0; - enable_constraint = false; - constraint_angle_min = 0; - constraint_angle_max = Math_PI * 2; - constraint_angle_invert = false; - enabled = true; - - editor_draw_gizmo = true; -} - -SkeletonModification2DLookAt::~SkeletonModification2DLookAt() { -} diff --git a/scene/resources/skeleton_modification_2d_lookat.h b/scene/resources/skeleton_modification_2d_lookat.h deleted file mode 100644 index d952d249f9..0000000000 --- a/scene/resources/skeleton_modification_2d_lookat.h +++ /dev/null @@ -1,100 +0,0 @@ -/**************************************************************************/ -/* skeleton_modification_2d_lookat.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 SKELETON_MODIFICATION_2D_LOOKAT_H -#define SKELETON_MODIFICATION_2D_LOOKAT_H - -#include "scene/2d/skeleton_2d.h" -#include "scene/resources/skeleton_modification_2d.h" - -/////////////////////////////////////// -// SkeletonModification2DLookAt -/////////////////////////////////////// - -class SkeletonModification2DLookAt : public SkeletonModification2D { - GDCLASS(SkeletonModification2DLookAt, SkeletonModification2D); - -private: - int bone_idx = -1; - NodePath bone2d_node; - ObjectID bone2d_node_cache; - - NodePath target_node; - ObjectID target_node_cache; - Node2D *target_node_reference = nullptr; - - float additional_rotation = 0; - bool enable_constraint = false; - float constraint_angle_min = 0; - float constraint_angle_max = (2.0 * Math_PI); - bool constraint_angle_invert = false; - bool constraint_in_localspace = true; - - void update_bone2d_cache(); - void update_target_cache(); - -protected: - static void _bind_methods(); - bool _set(const StringName &p_path, const Variant &p_value); - bool _get(const StringName &p_path, Variant &r_ret) const; - void _get_property_list(List *p_list) const; - -public: - void _execute(float p_delta) override; - void _setup_modification(SkeletonModificationStack2D *p_stack) override; - void _draw_editor_gizmo() override; - - void set_bone2d_node(const NodePath &p_target_node); - NodePath get_bone2d_node() const; - void set_bone_index(int p_idx); - int get_bone_index() const; - - void set_target_node(const NodePath &p_target_node); - NodePath get_target_node() const; - - void set_additional_rotation(float p_rotation); - float get_additional_rotation() const; - - void set_enable_constraint(bool p_constraint); - bool get_enable_constraint() const; - void set_constraint_angle_min(float p_angle_min); - float get_constraint_angle_min() const; - void set_constraint_angle_max(float p_angle_max); - float get_constraint_angle_max() const; - void set_constraint_angle_invert(bool p_invert); - bool get_constraint_angle_invert() const; - void set_constraint_in_localspace(bool p_constraint_in_localspace); - bool get_constraint_in_localspace() const; - - SkeletonModification2DLookAt(); - ~SkeletonModification2DLookAt(); -}; - -#endif // SKELETON_MODIFICATION_2D_LOOKAT_H diff --git a/scene/resources/skeleton_modification_2d_physicalbones.cpp b/scene/resources/skeleton_modification_2d_physicalbones.cpp deleted file mode 100644 index e000e947cd..0000000000 --- a/scene/resources/skeleton_modification_2d_physicalbones.cpp +++ /dev/null @@ -1,297 +0,0 @@ -/**************************************************************************/ -/* skeleton_modification_2d_physicalbones.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 "skeleton_modification_2d_physicalbones.h" -#include "scene/2d/physical_bone_2d.h" -#include "scene/2d/skeleton_2d.h" - -bool SkeletonModification2DPhysicalBones::_set(const StringName &p_path, const Variant &p_value) { - String path = p_path; - -#ifdef TOOLS_ENABLED - // Exposes a way to fetch the PhysicalBone2D nodes from the Godot editor. - if (is_setup) { - if (Engine::get_singleton()->is_editor_hint()) { - if (path.begins_with("fetch_bones")) { - fetch_physical_bones(); - notify_property_list_changed(); - return true; - } - } - } -#endif //TOOLS_ENABLED - - if (path.begins_with("joint_")) { - int which = path.get_slicec('_', 1).to_int(); - String what = path.get_slicec('_', 2); - ERR_FAIL_INDEX_V(which, physical_bone_chain.size(), false); - - if (what == "nodepath") { - set_physical_bone_node(which, p_value); - return true; - } - } - return false; -} - -bool SkeletonModification2DPhysicalBones::_get(const StringName &p_path, Variant &r_ret) const { - String path = p_path; - -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - if (path.begins_with("fetch_bones")) { - return true; // Do nothing! - } - } -#endif //TOOLS_ENABLED - - if (path.begins_with("joint_")) { - int which = path.get_slicec('_', 1).to_int(); - String what = path.get_slicec('_', 2); - ERR_FAIL_INDEX_V(which, physical_bone_chain.size(), false); - - if (what == "nodepath") { - r_ret = get_physical_bone_node(which); - return true; - } - } - return false; -} - -void SkeletonModification2DPhysicalBones::_get_property_list(List *p_list) const { -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - p_list->push_back(PropertyInfo(Variant::BOOL, "fetch_bones", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - } -#endif //TOOLS_ENABLED - - for (int i = 0; i < physical_bone_chain.size(); i++) { - String base_string = "joint_" + itos(i) + "_"; - - p_list->push_back(PropertyInfo(Variant::NODE_PATH, base_string + "nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "PhysicalBone2D", PROPERTY_USAGE_DEFAULT)); - } -} - -void SkeletonModification2DPhysicalBones::_execute(float p_delta) { - ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, - "Modification is not setup and therefore cannot execute!"); - if (!enabled) { - return; - } - - if (_simulation_state_dirty) { - _update_simulation_state(); - } - - for (int i = 0; i < physical_bone_chain.size(); i++) { - PhysicalBone_Data2D bone_data = physical_bone_chain[i]; - if (bone_data.physical_bone_node_cache.is_null()) { - WARN_PRINT_ONCE("PhysicalBone2D cache " + itos(i) + " is out of date. Attempting to update..."); - _physical_bone_update_cache(i); - continue; - } - - PhysicalBone2D *physical_bone = Object::cast_to(ObjectDB::get_instance(bone_data.physical_bone_node_cache)); - if (!physical_bone) { - ERR_PRINT_ONCE("PhysicalBone2D not found at index " + itos(i) + "!"); - return; - } - if (physical_bone->get_bone2d_index() < 0 || physical_bone->get_bone2d_index() > stack->skeleton->get_bone_count()) { - ERR_PRINT_ONCE("PhysicalBone2D at index " + itos(i) + " has invalid Bone2D!"); - return; - } - Bone2D *bone_2d = stack->skeleton->get_bone(physical_bone->get_bone2d_index()); - - if (physical_bone->get_simulate_physics() && !physical_bone->get_follow_bone_when_simulating()) { - bone_2d->set_global_transform(physical_bone->get_global_transform()); - stack->skeleton->set_bone_local_pose_override(physical_bone->get_bone2d_index(), bone_2d->get_transform(), stack->strength, true); - } - } -} - -void SkeletonModification2DPhysicalBones::_setup_modification(SkeletonModificationStack2D *p_stack) { - stack = p_stack; - - if (stack) { - is_setup = true; - - if (stack->skeleton) { - for (int i = 0; i < physical_bone_chain.size(); i++) { - _physical_bone_update_cache(i); - } - } - } -} - -void SkeletonModification2DPhysicalBones::_physical_bone_update_cache(int p_joint_idx) { - ERR_FAIL_INDEX_MSG(p_joint_idx, physical_bone_chain.size(), "Cannot update PhysicalBone2D cache: joint index out of range!"); - if (!is_setup || !stack) { - if (!stack) { - ERR_PRINT_ONCE("Cannot update PhysicalBone2D cache: modification is not properly setup!"); - } - return; - } - - physical_bone_chain.write[p_joint_idx].physical_bone_node_cache = ObjectID(); - if (stack->skeleton) { - if (stack->skeleton->is_inside_tree()) { - if (stack->skeleton->has_node(physical_bone_chain[p_joint_idx].physical_bone_node)) { - Node *node = stack->skeleton->get_node(physical_bone_chain[p_joint_idx].physical_bone_node); - ERR_FAIL_COND_MSG(!node || stack->skeleton == node, - "Cannot update Physical Bone2D " + itos(p_joint_idx) + " cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update Physical Bone2D " + itos(p_joint_idx) + " cache: node is not in scene tree!"); - physical_bone_chain.write[p_joint_idx].physical_bone_node_cache = node->get_instance_id(); - } - } - } -} - -int SkeletonModification2DPhysicalBones::get_physical_bone_chain_length() { - return physical_bone_chain.size(); -} - -void SkeletonModification2DPhysicalBones::set_physical_bone_chain_length(int p_length) { - ERR_FAIL_COND(p_length < 0); - physical_bone_chain.resize(p_length); - notify_property_list_changed(); -} - -void SkeletonModification2DPhysicalBones::fetch_physical_bones() { - ERR_FAIL_NULL_MSG(stack, "No modification stack found! Cannot fetch physical bones!"); - ERR_FAIL_NULL_MSG(stack->skeleton, "No skeleton found! Cannot fetch physical bones!"); - - physical_bone_chain.clear(); - - List node_queue = List(); - node_queue.push_back(stack->skeleton); - - while (node_queue.size() > 0) { - Node *node_to_process = node_queue[0]; - node_queue.pop_front(); - - if (node_to_process != nullptr) { - PhysicalBone2D *potential_bone = Object::cast_to(node_to_process); - if (potential_bone) { - PhysicalBone_Data2D new_data = PhysicalBone_Data2D(); - new_data.physical_bone_node = stack->skeleton->get_path_to(potential_bone); - new_data.physical_bone_node_cache = potential_bone->get_instance_id(); - physical_bone_chain.push_back(new_data); - } - for (int i = 0; i < node_to_process->get_child_count(); i++) { - node_queue.push_back(node_to_process->get_child(i)); - } - } - } -} - -void SkeletonModification2DPhysicalBones::start_simulation(const TypedArray &p_bones) { - _simulation_state_dirty = true; - _simulation_state_dirty_names = p_bones; - _simulation_state_dirty_process = true; - - if (is_setup) { - _update_simulation_state(); - } -} - -void SkeletonModification2DPhysicalBones::stop_simulation(const TypedArray &p_bones) { - _simulation_state_dirty = true; - _simulation_state_dirty_names = p_bones; - _simulation_state_dirty_process = false; - - if (is_setup) { - _update_simulation_state(); - } -} - -void SkeletonModification2DPhysicalBones::_update_simulation_state() { - if (!_simulation_state_dirty) { - return; - } - _simulation_state_dirty = false; - - if (_simulation_state_dirty_names.size() <= 0) { - for (int i = 0; i < physical_bone_chain.size(); i++) { - PhysicalBone2D *physical_bone = Object::cast_to(stack->skeleton->get_node(physical_bone_chain[i].physical_bone_node)); - if (!physical_bone) { - continue; - } - - physical_bone->set_simulate_physics(_simulation_state_dirty_process); - } - } else { - for (int i = 0; i < physical_bone_chain.size(); i++) { - PhysicalBone2D *physical_bone = Object::cast_to(ObjectDB::get_instance(physical_bone_chain[i].physical_bone_node_cache)); - if (!physical_bone) { - continue; - } - if (_simulation_state_dirty_names.has(physical_bone->get_name())) { - physical_bone->set_simulate_physics(_simulation_state_dirty_process); - } - } - } -} - -void SkeletonModification2DPhysicalBones::set_physical_bone_node(int p_joint_idx, const NodePath &p_nodepath) { - ERR_FAIL_INDEX_MSG(p_joint_idx, physical_bone_chain.size(), "Joint index out of range!"); - physical_bone_chain.write[p_joint_idx].physical_bone_node = p_nodepath; - _physical_bone_update_cache(p_joint_idx); -} - -NodePath SkeletonModification2DPhysicalBones::get_physical_bone_node(int p_joint_idx) const { - ERR_FAIL_INDEX_V_MSG(p_joint_idx, physical_bone_chain.size(), NodePath(), "Joint index out of range!"); - return physical_bone_chain[p_joint_idx].physical_bone_node; -} - -void SkeletonModification2DPhysicalBones::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_physical_bone_chain_length", "length"), &SkeletonModification2DPhysicalBones::set_physical_bone_chain_length); - ClassDB::bind_method(D_METHOD("get_physical_bone_chain_length"), &SkeletonModification2DPhysicalBones::get_physical_bone_chain_length); - - ClassDB::bind_method(D_METHOD("set_physical_bone_node", "joint_idx", "physicalbone2d_node"), &SkeletonModification2DPhysicalBones::set_physical_bone_node); - ClassDB::bind_method(D_METHOD("get_physical_bone_node", "joint_idx"), &SkeletonModification2DPhysicalBones::get_physical_bone_node); - - ClassDB::bind_method(D_METHOD("fetch_physical_bones"), &SkeletonModification2DPhysicalBones::fetch_physical_bones); - ClassDB::bind_method(D_METHOD("start_simulation", "bones"), &SkeletonModification2DPhysicalBones::start_simulation, DEFVAL(Array())); - ClassDB::bind_method(D_METHOD("stop_simulation", "bones"), &SkeletonModification2DPhysicalBones::stop_simulation, DEFVAL(Array())); - - ADD_PROPERTY(PropertyInfo(Variant::INT, "physical_bone_chain_length", PROPERTY_HINT_RANGE, "0,100,1"), "set_physical_bone_chain_length", "get_physical_bone_chain_length"); -} - -SkeletonModification2DPhysicalBones::SkeletonModification2DPhysicalBones() { - stack = nullptr; - is_setup = false; - physical_bone_chain = Vector(); - enabled = true; - editor_draw_gizmo = false; // Nothing to really show in a gizmo right now. -} - -SkeletonModification2DPhysicalBones::~SkeletonModification2DPhysicalBones() { -} diff --git a/scene/resources/skeleton_modification_2d_physicalbones.h b/scene/resources/skeleton_modification_2d_physicalbones.h deleted file mode 100644 index ec1b63bf73..0000000000 --- a/scene/resources/skeleton_modification_2d_physicalbones.h +++ /dev/null @@ -1,82 +0,0 @@ -/**************************************************************************/ -/* skeleton_modification_2d_physicalbones.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 SKELETON_MODIFICATION_2D_PHYSICALBONES_H -#define SKELETON_MODIFICATION_2D_PHYSICALBONES_H - -#include "scene/2d/skeleton_2d.h" -#include "scene/resources/skeleton_modification_2d.h" - -/////////////////////////////////////// -// SkeletonModification2DJIGGLE -/////////////////////////////////////// - -class SkeletonModification2DPhysicalBones : public SkeletonModification2D { - GDCLASS(SkeletonModification2DPhysicalBones, SkeletonModification2D); - -private: - struct PhysicalBone_Data2D { - NodePath physical_bone_node; - ObjectID physical_bone_node_cache; - }; - Vector physical_bone_chain; - - void _physical_bone_update_cache(int p_joint_idx); - - bool _simulation_state_dirty = false; - TypedArray _simulation_state_dirty_names; - bool _simulation_state_dirty_process = false; - void _update_simulation_state(); - -protected: - static void _bind_methods(); - bool _get(const StringName &p_path, Variant &r_ret) const; - bool _set(const StringName &p_path, const Variant &p_value); - void _get_property_list(List *p_list) const; - -public: - void _execute(float p_delta) override; - void _setup_modification(SkeletonModificationStack2D *p_stack) override; - - int get_physical_bone_chain_length(); - void set_physical_bone_chain_length(int p_new_length); - - void set_physical_bone_node(int p_joint_idx, const NodePath &p_path); - NodePath get_physical_bone_node(int p_joint_idx) const; - - void fetch_physical_bones(); - void start_simulation(const TypedArray &p_bones); - void stop_simulation(const TypedArray &p_bones); - - SkeletonModification2DPhysicalBones(); - ~SkeletonModification2DPhysicalBones(); -}; - -#endif // SKELETON_MODIFICATION_2D_PHYSICALBONES_H diff --git a/scene/resources/skeleton_modification_2d_stackholder.cpp b/scene/resources/skeleton_modification_2d_stackholder.cpp deleted file mode 100644 index 6d4cd290f1..0000000000 --- a/scene/resources/skeleton_modification_2d_stackholder.cpp +++ /dev/null @@ -1,135 +0,0 @@ -/**************************************************************************/ -/* skeleton_modification_2d_stackholder.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 "skeleton_modification_2d_stackholder.h" -#include "scene/2d/skeleton_2d.h" - -bool SkeletonModification2DStackHolder::_set(const StringName &p_path, const Variant &p_value) { - String path = p_path; - - if (path == "held_modification_stack") { - set_held_modification_stack(p_value); - } -#ifdef TOOLS_ENABLED - else if (path == "editor/draw_gizmo") { - set_editor_draw_gizmo(p_value); - } -#endif // TOOLS_ENABLED - else { - return false; - } - - return true; -} - -bool SkeletonModification2DStackHolder::_get(const StringName &p_path, Variant &r_ret) const { - String path = p_path; - - if (path == "held_modification_stack") { - r_ret = get_held_modification_stack(); - } -#ifdef TOOLS_ENABLED - else if (path == "editor/draw_gizmo") { - r_ret = get_editor_draw_gizmo(); - } -#endif // TOOLS_ENABLED - else { - return false; - } - - return true; -} - -void SkeletonModification2DStackHolder::_get_property_list(List *p_list) const { - p_list->push_back(PropertyInfo(Variant::OBJECT, "held_modification_stack", PROPERTY_HINT_RESOURCE_TYPE, "SkeletonModificationStack2D", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ALWAYS_DUPLICATE)); - -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - } -#endif // TOOLS_ENABLED -} - -void SkeletonModification2DStackHolder::_execute(float p_delta) { - ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, - "Modification is not setup and therefore cannot execute!"); - - if (held_modification_stack.is_valid()) { - held_modification_stack->execute(p_delta, execution_mode); - } -} - -void SkeletonModification2DStackHolder::_setup_modification(SkeletonModificationStack2D *p_stack) { - stack = p_stack; - - if (stack != nullptr) { - is_setup = true; - - if (held_modification_stack.is_valid()) { - held_modification_stack->set_skeleton(stack->get_skeleton()); - held_modification_stack->setup(); - } - } -} - -void SkeletonModification2DStackHolder::_draw_editor_gizmo() { - if (stack) { - if (held_modification_stack.is_valid()) { - held_modification_stack->draw_editor_gizmos(); - } - } -} - -void SkeletonModification2DStackHolder::set_held_modification_stack(Ref p_held_stack) { - held_modification_stack = p_held_stack; - - if (is_setup && held_modification_stack.is_valid()) { - held_modification_stack->set_skeleton(stack->get_skeleton()); - held_modification_stack->setup(); - } -} - -Ref SkeletonModification2DStackHolder::get_held_modification_stack() const { - return held_modification_stack; -} - -void SkeletonModification2DStackHolder::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_held_modification_stack", "held_modification_stack"), &SkeletonModification2DStackHolder::set_held_modification_stack); - ClassDB::bind_method(D_METHOD("get_held_modification_stack"), &SkeletonModification2DStackHolder::get_held_modification_stack); -} - -SkeletonModification2DStackHolder::SkeletonModification2DStackHolder() { - stack = nullptr; - is_setup = false; - enabled = true; -} - -SkeletonModification2DStackHolder::~SkeletonModification2DStackHolder() { -} diff --git a/scene/resources/skeleton_modification_2d_stackholder.h b/scene/resources/skeleton_modification_2d_stackholder.h deleted file mode 100644 index 17fa5f5cfe..0000000000 --- a/scene/resources/skeleton_modification_2d_stackholder.h +++ /dev/null @@ -1,64 +0,0 @@ -/**************************************************************************/ -/* skeleton_modification_2d_stackholder.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 SKELETON_MODIFICATION_2D_STACKHOLDER_H -#define SKELETON_MODIFICATION_2D_STACKHOLDER_H - -#include "scene/2d/skeleton_2d.h" -#include "scene/resources/skeleton_modification_2d.h" - -/////////////////////////////////////// -// SkeletonModification2DJIGGLE -/////////////////////////////////////// - -class SkeletonModification2DStackHolder : public SkeletonModification2D { - GDCLASS(SkeletonModification2DStackHolder, SkeletonModification2D); - -protected: - static void _bind_methods(); - bool _get(const StringName &p_path, Variant &r_ret) const; - bool _set(const StringName &p_path, const Variant &p_value); - void _get_property_list(List *p_list) const; - -public: - Ref held_modification_stack; - - void _execute(float p_delta) override; - void _setup_modification(SkeletonModificationStack2D *p_stack) override; - void _draw_editor_gizmo() override; - - void set_held_modification_stack(Ref p_held_stack); - Ref get_held_modification_stack() const; - - SkeletonModification2DStackHolder(); - ~SkeletonModification2DStackHolder(); -}; - -#endif // SKELETON_MODIFICATION_2D_STACKHOLDER_H diff --git a/scene/resources/skeleton_modification_2d_twoboneik.cpp b/scene/resources/skeleton_modification_2d_twoboneik.cpp deleted file mode 100644 index c3366d5c36..0000000000 --- a/scene/resources/skeleton_modification_2d_twoboneik.cpp +++ /dev/null @@ -1,485 +0,0 @@ -/**************************************************************************/ -/* skeleton_modification_2d_twoboneik.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 "skeleton_modification_2d_twoboneik.h" -#include "scene/2d/skeleton_2d.h" - -#ifdef TOOLS_ENABLED -#include "editor/editor_settings.h" -#endif // TOOLS_ENABLED - -bool SkeletonModification2DTwoBoneIK::_set(const StringName &p_path, const Variant &p_value) { - String path = p_path; - - if (path == "joint_one_bone_idx") { - set_joint_one_bone_idx(p_value); - } else if (path == "joint_one_bone2d_node") { - set_joint_one_bone2d_node(p_value); - } else if (path == "joint_two_bone_idx") { - set_joint_two_bone_idx(p_value); - } else if (path == "joint_two_bone2d_node") { - set_joint_two_bone2d_node(p_value); - } -#ifdef TOOLS_ENABLED - else if (path.begins_with("editor/draw_gizmo")) { - set_editor_draw_gizmo(p_value); - } else if (path.begins_with("editor/draw_min_max")) { - set_editor_draw_min_max(p_value); - } -#endif // TOOLS_ENABLED - else { - return false; - } - - return true; -} - -bool SkeletonModification2DTwoBoneIK::_get(const StringName &p_path, Variant &r_ret) const { - String path = p_path; - - if (path == "joint_one_bone_idx") { - r_ret = get_joint_one_bone_idx(); - } else if (path == "joint_one_bone2d_node") { - r_ret = get_joint_one_bone2d_node(); - } else if (path == "joint_two_bone_idx") { - r_ret = get_joint_two_bone_idx(); - } else if (path == "joint_two_bone2d_node") { - r_ret = get_joint_two_bone2d_node(); - } -#ifdef TOOLS_ENABLED - else if (path.begins_with("editor/draw_gizmo")) { - r_ret = get_editor_draw_gizmo(); - } else if (path.begins_with("editor/draw_min_max")) { - r_ret = get_editor_draw_min_max(); - } -#endif // TOOLS_ENABLED - else { - return false; - } - - return true; -} - -void SkeletonModification2DTwoBoneIK::_get_property_list(List *p_list) const { - p_list->push_back(PropertyInfo(Variant::INT, "joint_one_bone_idx", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::NODE_PATH, "joint_one_bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT)); - - p_list->push_back(PropertyInfo(Variant::INT, "joint_two_bone_idx", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::NODE_PATH, "joint_two_bone2d_node", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Bone2D", PROPERTY_USAGE_DEFAULT)); - -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_gizmo", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::BOOL, "editor/draw_min_max", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)); - } -#endif // TOOLS_ENABLED -} - -void SkeletonModification2DTwoBoneIK::_execute(float p_delta) { - ERR_FAIL_COND_MSG(!stack || !is_setup || stack->skeleton == nullptr, - "Modification is not setup and therefore cannot execute!"); - if (!enabled) { - return; - } - - if (target_node_cache.is_null()) { - WARN_PRINT_ONCE("Target cache is out of date. Attempting to update..."); - update_target_cache(); - return; - } - - if (joint_one_bone2d_node_cache.is_null() && !joint_one_bone2d_node.is_empty()) { - WARN_PRINT_ONCE("Joint one Bone2D node cache is out of date. Attempting to update..."); - update_joint_one_bone2d_cache(); - } - if (joint_two_bone2d_node_cache.is_null() && !joint_two_bone2d_node.is_empty()) { - WARN_PRINT_ONCE("Joint two Bone2D node cache is out of date. Attempting to update..."); - update_joint_two_bone2d_cache(); - } - - Node2D *target = Object::cast_to(ObjectDB::get_instance(target_node_cache)); - if (!target || !target->is_inside_tree()) { - ERR_PRINT_ONCE("Target node is not in the scene tree. Cannot execute modification!"); - return; - } - - Bone2D *joint_one_bone = stack->skeleton->get_bone(joint_one_bone_idx); - if (joint_one_bone == nullptr) { - ERR_PRINT_ONCE("Joint one bone_idx does not point to a valid bone! Cannot execute modification!"); - return; - } - - Bone2D *joint_two_bone = stack->skeleton->get_bone(joint_two_bone_idx); - if (joint_two_bone == nullptr) { - ERR_PRINT_ONCE("Joint two bone_idx does not point to a valid bone! Cannot execute modification!"); - return; - } - - // Adopted from the links below: - // http://theorangeduck.com/page/simple-two-joint - // https://www.alanzucconi.com/2018/05/02/ik-2d-2/ - // With modifications by TwistedTwigleg - Vector2 target_difference = target->get_global_position() - joint_one_bone->get_global_position(); - float joint_one_to_target = target_difference.length(); - float angle_atan = target_difference.angle(); - - float bone_one_length = joint_one_bone->get_length() * MIN(joint_one_bone->get_global_scale().x, joint_one_bone->get_global_scale().y); - float bone_two_length = joint_two_bone->get_length() * MIN(joint_two_bone->get_global_scale().x, joint_two_bone->get_global_scale().y); - bool override_angles_due_to_out_of_range = false; - - if (joint_one_to_target < target_minimum_distance) { - joint_one_to_target = target_minimum_distance; - } - if (joint_one_to_target > target_maximum_distance && target_maximum_distance > 0.0) { - joint_one_to_target = target_maximum_distance; - } - - if (bone_one_length + bone_two_length < joint_one_to_target) { - override_angles_due_to_out_of_range = true; - } - - if (!override_angles_due_to_out_of_range) { - float angle_0 = Math::acos(((joint_one_to_target * joint_one_to_target) + (bone_one_length * bone_one_length) - (bone_two_length * bone_two_length)) / (2.0 * joint_one_to_target * bone_one_length)); - float angle_1 = Math::acos(((bone_two_length * bone_two_length) + (bone_one_length * bone_one_length) - (joint_one_to_target * joint_one_to_target)) / (2.0 * bone_two_length * bone_one_length)); - - if (flip_bend_direction) { - angle_0 = -angle_0; - angle_1 = -angle_1; - } - - if (isnan(angle_0) || isnan(angle_1)) { - // We cannot solve for this angle! Do nothing to avoid setting the rotation (and scale) to NaN. - } else { - joint_one_bone->set_global_rotation(angle_atan - angle_0 - joint_one_bone->get_bone_angle()); - joint_two_bone->set_rotation(-Math_PI - angle_1 - joint_two_bone->get_bone_angle() + joint_one_bone->get_bone_angle()); - } - } else { - joint_one_bone->set_global_rotation(angle_atan - joint_one_bone->get_bone_angle()); - joint_two_bone->set_global_rotation(angle_atan - joint_two_bone->get_bone_angle()); - } - - stack->skeleton->set_bone_local_pose_override(joint_one_bone_idx, joint_one_bone->get_transform(), stack->strength, true); - stack->skeleton->set_bone_local_pose_override(joint_two_bone_idx, joint_two_bone->get_transform(), stack->strength, true); -} - -void SkeletonModification2DTwoBoneIK::_setup_modification(SkeletonModificationStack2D *p_stack) { - stack = p_stack; - - if (stack) { - is_setup = true; - update_target_cache(); - update_joint_one_bone2d_cache(); - update_joint_two_bone2d_cache(); - } -} - -void SkeletonModification2DTwoBoneIK::_draw_editor_gizmo() { - if (!enabled || !is_setup) { - return; - } - - Bone2D *operation_bone_one = stack->skeleton->get_bone(joint_one_bone_idx); - if (!operation_bone_one) { - return; - } - stack->skeleton->draw_set_transform( - stack->skeleton->to_local(operation_bone_one->get_global_position()), - operation_bone_one->get_global_rotation() - stack->skeleton->get_global_rotation()); - - Color bone_ik_color = Color(1.0, 0.65, 0.0, 0.4); -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - bone_ik_color = EDITOR_GET("editors/2d/bone_ik_color"); - } -#endif // TOOLS_ENABLED - - if (flip_bend_direction) { - float angle = -(Math_PI * 0.5) + operation_bone_one->get_bone_angle(); - stack->skeleton->draw_line(Vector2(0, 0), Vector2(Math::cos(angle), sin(angle)) * (operation_bone_one->get_length() * 0.5), bone_ik_color, 2.0); - } else { - float angle = (Math_PI * 0.5) + operation_bone_one->get_bone_angle(); - stack->skeleton->draw_line(Vector2(0, 0), Vector2(Math::cos(angle), sin(angle)) * (operation_bone_one->get_length() * 0.5), bone_ik_color, 2.0); - } - -#ifdef TOOLS_ENABLED - if (Engine::get_singleton()->is_editor_hint()) { - if (editor_draw_min_max) { - if (target_maximum_distance != 0.0 || target_minimum_distance != 0.0) { - Vector2 target_direction = Vector2(0, 1); - if (target_node_cache.is_valid()) { - stack->skeleton->draw_set_transform(Vector2(0, 0), 0.0); - Node2D *target = Object::cast_to(ObjectDB::get_instance(target_node_cache)); - target_direction = operation_bone_one->get_global_position().direction_to(target->get_global_position()); - } - - stack->skeleton->draw_circle(target_direction * target_minimum_distance, 8, bone_ik_color); - stack->skeleton->draw_circle(target_direction * target_maximum_distance, 8, bone_ik_color); - stack->skeleton->draw_line(target_direction * target_minimum_distance, target_direction * target_maximum_distance, bone_ik_color, 2.0); - } - } - } -#endif // TOOLS_ENABLED -} - -void SkeletonModification2DTwoBoneIK::update_target_cache() { - if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update target cache: modification is not properly setup!"); - return; - } - - target_node_cache = ObjectID(); - if (stack->skeleton) { - if (stack->skeleton->is_inside_tree()) { - if (stack->skeleton->has_node(target_node)) { - Node *node = stack->skeleton->get_node(target_node); - ERR_FAIL_COND_MSG(!node || stack->skeleton == node, - "Cannot update target cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update target cache: node is not in the scene tree!"); - target_node_cache = node->get_instance_id(); - } - } - } -} - -void SkeletonModification2DTwoBoneIK::update_joint_one_bone2d_cache() { - if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update joint one Bone2D cache: modification is not properly setup!"); - return; - } - - joint_one_bone2d_node_cache = ObjectID(); - if (stack->skeleton) { - if (stack->skeleton->is_inside_tree()) { - if (stack->skeleton->has_node(joint_one_bone2d_node)) { - Node *node = stack->skeleton->get_node(joint_one_bone2d_node); - ERR_FAIL_COND_MSG(!node || stack->skeleton == node, - "Cannot update joint one Bone2D cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update joint one Bone2D cache: node is not in the scene tree!"); - joint_one_bone2d_node_cache = node->get_instance_id(); - - Bone2D *bone = Object::cast_to(node); - if (bone) { - joint_one_bone_idx = bone->get_index_in_skeleton(); - } else { - ERR_FAIL_MSG("Update joint one Bone2D cache: Nodepath to Bone2D is not a Bone2D node!"); - } - } - } - } -} - -void SkeletonModification2DTwoBoneIK::update_joint_two_bone2d_cache() { - if (!is_setup || !stack) { - ERR_PRINT_ONCE("Cannot update joint two Bone2D cache: modification is not properly setup!"); - return; - } - - joint_two_bone2d_node_cache = ObjectID(); - if (stack->skeleton) { - if (stack->skeleton->is_inside_tree()) { - if (stack->skeleton->has_node(joint_two_bone2d_node)) { - Node *node = stack->skeleton->get_node(joint_two_bone2d_node); - ERR_FAIL_COND_MSG(!node || stack->skeleton == node, - "Cannot update joint two Bone2D cache: node is this modification's skeleton or cannot be found!"); - ERR_FAIL_COND_MSG(!node->is_inside_tree(), - "Cannot update joint two Bone2D cache: node is not in scene tree!"); - joint_two_bone2d_node_cache = node->get_instance_id(); - - Bone2D *bone = Object::cast_to(node); - if (bone) { - joint_two_bone_idx = bone->get_index_in_skeleton(); - } else { - ERR_FAIL_MSG("Update joint two Bone2D cache: Nodepath to Bone2D is not a Bone2D node!"); - } - } - } - } -} - -void SkeletonModification2DTwoBoneIK::set_target_node(const NodePath &p_target_node) { - target_node = p_target_node; - update_target_cache(); -} - -NodePath SkeletonModification2DTwoBoneIK::get_target_node() const { - return target_node; -} - -void SkeletonModification2DTwoBoneIK::set_joint_one_bone2d_node(const NodePath &p_target_node) { - joint_one_bone2d_node = p_target_node; - update_joint_one_bone2d_cache(); - notify_property_list_changed(); -} - -void SkeletonModification2DTwoBoneIK::set_target_minimum_distance(float p_distance) { - ERR_FAIL_COND_MSG(p_distance < 0, "Target minimum distance cannot be less than zero!"); - target_minimum_distance = p_distance; -} - -float SkeletonModification2DTwoBoneIK::get_target_minimum_distance() const { - return target_minimum_distance; -} - -void SkeletonModification2DTwoBoneIK::set_target_maximum_distance(float p_distance) { - ERR_FAIL_COND_MSG(p_distance < 0, "Target maximum distance cannot be less than zero!"); - target_maximum_distance = p_distance; -} - -float SkeletonModification2DTwoBoneIK::get_target_maximum_distance() const { - return target_maximum_distance; -} - -void SkeletonModification2DTwoBoneIK::set_flip_bend_direction(bool p_flip_direction) { - flip_bend_direction = p_flip_direction; - -#ifdef TOOLS_ENABLED - if (stack && is_setup) { - stack->set_editor_gizmos_dirty(true); - } -#endif // TOOLS_ENABLED -} - -bool SkeletonModification2DTwoBoneIK::get_flip_bend_direction() const { - return flip_bend_direction; -} - -NodePath SkeletonModification2DTwoBoneIK::get_joint_one_bone2d_node() const { - return joint_one_bone2d_node; -} - -void SkeletonModification2DTwoBoneIK::set_joint_two_bone2d_node(const NodePath &p_target_node) { - joint_two_bone2d_node = p_target_node; - update_joint_two_bone2d_cache(); - notify_property_list_changed(); -} - -NodePath SkeletonModification2DTwoBoneIK::get_joint_two_bone2d_node() const { - return joint_two_bone2d_node; -} - -void SkeletonModification2DTwoBoneIK::set_joint_one_bone_idx(int p_bone_idx) { - ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); - - if (is_setup) { - if (stack->skeleton) { - ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); - joint_one_bone_idx = p_bone_idx; - joint_one_bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id(); - joint_one_bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx)); - } else { - WARN_PRINT("TwoBoneIK: Cannot verify the joint bone index for joint one..."); - joint_one_bone_idx = p_bone_idx; - } - } else { - WARN_PRINT("TwoBoneIK: Cannot verify the joint bone index for joint one..."); - joint_one_bone_idx = p_bone_idx; - } - - notify_property_list_changed(); -} - -int SkeletonModification2DTwoBoneIK::get_joint_one_bone_idx() const { - return joint_one_bone_idx; -} - -void SkeletonModification2DTwoBoneIK::set_joint_two_bone_idx(int p_bone_idx) { - ERR_FAIL_COND_MSG(p_bone_idx < 0, "Bone index is out of range: The index is too low!"); - - if (is_setup) { - if (stack->skeleton) { - ERR_FAIL_INDEX_MSG(p_bone_idx, stack->skeleton->get_bone_count(), "Passed-in Bone index is out of range!"); - joint_two_bone_idx = p_bone_idx; - joint_two_bone2d_node_cache = stack->skeleton->get_bone(p_bone_idx)->get_instance_id(); - joint_two_bone2d_node = stack->skeleton->get_path_to(stack->skeleton->get_bone(p_bone_idx)); - } else { - WARN_PRINT("TwoBoneIK: Cannot verify the joint bone index for joint two..."); - joint_two_bone_idx = p_bone_idx; - } - } else { - WARN_PRINT("TwoBoneIK: Cannot verify the joint bone index for joint two..."); - joint_two_bone_idx = p_bone_idx; - } - - notify_property_list_changed(); -} - -int SkeletonModification2DTwoBoneIK::get_joint_two_bone_idx() const { - return joint_two_bone_idx; -} - -#ifdef TOOLS_ENABLED -void SkeletonModification2DTwoBoneIK::set_editor_draw_min_max(bool p_draw) { - editor_draw_min_max = p_draw; -} - -bool SkeletonModification2DTwoBoneIK::get_editor_draw_min_max() const { - return editor_draw_min_max; -} -#endif // TOOLS_ENABLED - -void SkeletonModification2DTwoBoneIK::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_target_node", "target_nodepath"), &SkeletonModification2DTwoBoneIK::set_target_node); - ClassDB::bind_method(D_METHOD("get_target_node"), &SkeletonModification2DTwoBoneIK::get_target_node); - - ClassDB::bind_method(D_METHOD("set_target_minimum_distance", "minimum_distance"), &SkeletonModification2DTwoBoneIK::set_target_minimum_distance); - ClassDB::bind_method(D_METHOD("get_target_minimum_distance"), &SkeletonModification2DTwoBoneIK::get_target_minimum_distance); - ClassDB::bind_method(D_METHOD("set_target_maximum_distance", "maximum_distance"), &SkeletonModification2DTwoBoneIK::set_target_maximum_distance); - ClassDB::bind_method(D_METHOD("get_target_maximum_distance"), &SkeletonModification2DTwoBoneIK::get_target_maximum_distance); - ClassDB::bind_method(D_METHOD("set_flip_bend_direction", "flip_direction"), &SkeletonModification2DTwoBoneIK::set_flip_bend_direction); - ClassDB::bind_method(D_METHOD("get_flip_bend_direction"), &SkeletonModification2DTwoBoneIK::get_flip_bend_direction); - - ClassDB::bind_method(D_METHOD("set_joint_one_bone2d_node", "bone2d_node"), &SkeletonModification2DTwoBoneIK::set_joint_one_bone2d_node); - ClassDB::bind_method(D_METHOD("get_joint_one_bone2d_node"), &SkeletonModification2DTwoBoneIK::get_joint_one_bone2d_node); - ClassDB::bind_method(D_METHOD("set_joint_one_bone_idx", "bone_idx"), &SkeletonModification2DTwoBoneIK::set_joint_one_bone_idx); - ClassDB::bind_method(D_METHOD("get_joint_one_bone_idx"), &SkeletonModification2DTwoBoneIK::get_joint_one_bone_idx); - - ClassDB::bind_method(D_METHOD("set_joint_two_bone2d_node", "bone2d_node"), &SkeletonModification2DTwoBoneIK::set_joint_two_bone2d_node); - ClassDB::bind_method(D_METHOD("get_joint_two_bone2d_node"), &SkeletonModification2DTwoBoneIK::get_joint_two_bone2d_node); - ClassDB::bind_method(D_METHOD("set_joint_two_bone_idx", "bone_idx"), &SkeletonModification2DTwoBoneIK::set_joint_two_bone_idx); - ClassDB::bind_method(D_METHOD("get_joint_two_bone_idx"), &SkeletonModification2DTwoBoneIK::get_joint_two_bone_idx); - - ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "target_nodepath", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Node2D"), "set_target_node", "get_target_node"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_minimum_distance", PROPERTY_HINT_RANGE, "0,100000000,0.01,suffix:px"), "set_target_minimum_distance", "get_target_minimum_distance"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "target_maximum_distance", PROPERTY_HINT_NONE, "0,100000000,0.01,suffix:px"), "set_target_maximum_distance", "get_target_maximum_distance"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_bend_direction", PROPERTY_HINT_NONE, ""), "set_flip_bend_direction", "get_flip_bend_direction"); - ADD_GROUP("", ""); -} - -SkeletonModification2DTwoBoneIK::SkeletonModification2DTwoBoneIK() { - stack = nullptr; - is_setup = false; - enabled = true; - editor_draw_gizmo = true; -} - -SkeletonModification2DTwoBoneIK::~SkeletonModification2DTwoBoneIK() { -} diff --git a/scene/resources/skeleton_modification_2d_twoboneik.h b/scene/resources/skeleton_modification_2d_twoboneik.h deleted file mode 100644 index 7a5cd91c86..0000000000 --- a/scene/resources/skeleton_modification_2d_twoboneik.h +++ /dev/null @@ -1,107 +0,0 @@ -/**************************************************************************/ -/* skeleton_modification_2d_twoboneik.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 SKELETON_MODIFICATION_2D_TWOBONEIK_H -#define SKELETON_MODIFICATION_2D_TWOBONEIK_H - -#include "scene/2d/skeleton_2d.h" -#include "scene/resources/skeleton_modification_2d.h" - -/////////////////////////////////////// -// SkeletonModification2DJIGGLE -/////////////////////////////////////// - -class SkeletonModification2DTwoBoneIK : public SkeletonModification2D { - GDCLASS(SkeletonModification2DTwoBoneIK, SkeletonModification2D); - -private: - NodePath target_node; - ObjectID target_node_cache; - float target_minimum_distance = 0; - float target_maximum_distance = 0; - bool flip_bend_direction = false; - - NodePath joint_one_bone2d_node; - ObjectID joint_one_bone2d_node_cache; - int joint_one_bone_idx = -1; - - NodePath joint_two_bone2d_node; - ObjectID joint_two_bone2d_node_cache; - int joint_two_bone_idx = -1; - -#ifdef TOOLS_ENABLED - bool editor_draw_min_max = false; -#endif // TOOLS_ENABLED - - void update_target_cache(); - void update_joint_one_bone2d_cache(); - void update_joint_two_bone2d_cache(); - -protected: - static void _bind_methods(); - bool _get(const StringName &p_path, Variant &r_ret) const; - bool _set(const StringName &p_path, const Variant &p_value); - void _get_property_list(List *p_list) const; - -public: - void _execute(float p_delta) override; - void _setup_modification(SkeletonModificationStack2D *p_stack) override; - void _draw_editor_gizmo() override; - - void set_target_node(const NodePath &p_target_node); - NodePath get_target_node() const; - - void set_target_minimum_distance(float p_minimum_distance); - float get_target_minimum_distance() const; - void set_target_maximum_distance(float p_maximum_distance); - float get_target_maximum_distance() const; - void set_flip_bend_direction(bool p_flip_direction); - bool get_flip_bend_direction() const; - - void set_joint_one_bone2d_node(const NodePath &p_node); - NodePath get_joint_one_bone2d_node() const; - void set_joint_one_bone_idx(int p_bone_idx); - int get_joint_one_bone_idx() const; - - void set_joint_two_bone2d_node(const NodePath &p_node); - NodePath get_joint_two_bone2d_node() const; - void set_joint_two_bone_idx(int p_bone_idx); - int get_joint_two_bone_idx() const; - -#ifdef TOOLS_ENABLED - void set_editor_draw_min_max(bool p_draw); - bool get_editor_draw_min_max() const; -#endif // TOOLS_ENABLED - - SkeletonModification2DTwoBoneIK(); - ~SkeletonModification2DTwoBoneIK(); -}; - -#endif // SKELETON_MODIFICATION_2D_TWOBONEIK_H diff --git a/scene/resources/skeleton_modification_stack_2d.cpp b/scene/resources/skeleton_modification_stack_2d.cpp deleted file mode 100644 index dcc69d4831..0000000000 --- a/scene/resources/skeleton_modification_stack_2d.cpp +++ /dev/null @@ -1,270 +0,0 @@ -/**************************************************************************/ -/* skeleton_modification_stack_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 "skeleton_modification_stack_2d.h" -#include "scene/2d/skeleton_2d.h" - -void SkeletonModificationStack2D::_get_property_list(List *p_list) const { - for (int i = 0; i < modifications.size(); i++) { - p_list->push_back( - PropertyInfo(Variant::OBJECT, "modifications/" + itos(i), - PROPERTY_HINT_RESOURCE_TYPE, - "SkeletonModification2D", - PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ALWAYS_DUPLICATE)); - } -} - -bool SkeletonModificationStack2D::_set(const StringName &p_path, const Variant &p_value) { - String path = p_path; - - if (path.begins_with("modifications/")) { - int mod_idx = path.get_slicec('/', 1).to_int(); - set_modification(mod_idx, p_value); - return true; - } - return false; -} - -bool SkeletonModificationStack2D::_get(const StringName &p_path, Variant &r_ret) const { - String path = p_path; - - if (path.begins_with("modifications/")) { - int mod_idx = path.get_slicec('/', 1).to_int(); - r_ret = get_modification(mod_idx); - return true; - } - return false; -} - -void SkeletonModificationStack2D::setup() { - if (is_setup) { - return; - } - - if (skeleton != nullptr) { - is_setup = true; - for (int i = 0; i < modifications.size(); i++) { - if (!modifications[i].is_valid()) { - continue; - } - modifications.get(i)->_setup_modification(this); - } - -#ifdef TOOLS_ENABLED - set_editor_gizmos_dirty(true); -#endif // TOOLS_ENABLED - - } else { - WARN_PRINT("Cannot setup SkeletonModificationStack2D: no Skeleton2D set!"); - } -} - -void SkeletonModificationStack2D::execute(float p_delta, int p_execution_mode) { - ERR_FAIL_COND_MSG(!is_setup || skeleton == nullptr || is_queued_for_deletion(), - "Modification stack is not properly setup and therefore cannot execute!"); - - if (!skeleton->is_inside_tree()) { - ERR_PRINT_ONCE("Skeleton is not inside SceneTree! Cannot execute modification!"); - return; - } - - if (!enabled) { - return; - } - - for (int i = 0; i < modifications.size(); i++) { - if (!modifications[i].is_valid()) { - continue; - } - - if (modifications[i]->get_execution_mode() == p_execution_mode) { - modifications.get(i)->_execute(p_delta); - } - } -} - -void SkeletonModificationStack2D::draw_editor_gizmos() { - if (!is_setup) { - return; - } - - if (editor_gizmo_dirty) { - for (int i = 0; i < modifications.size(); i++) { - if (!modifications[i].is_valid()) { - continue; - } - - if (modifications[i]->editor_draw_gizmo) { - modifications.get(i)->_draw_editor_gizmo(); - } - } - skeleton->draw_set_transform(Vector2(0, 0)); - editor_gizmo_dirty = false; - } -} - -void SkeletonModificationStack2D::set_editor_gizmos_dirty(bool p_dirty) { - if (!is_setup) { - return; - } - - if (!editor_gizmo_dirty && p_dirty) { - editor_gizmo_dirty = p_dirty; - if (skeleton) { - skeleton->queue_redraw(); - } - } else { - editor_gizmo_dirty = p_dirty; - } -} - -void SkeletonModificationStack2D::enable_all_modifications(bool p_enabled) { - for (int i = 0; i < modifications.size(); i++) { - if (!modifications[i].is_valid()) { - continue; - } - modifications.get(i)->set_enabled(p_enabled); - } -} - -Ref SkeletonModificationStack2D::get_modification(int p_mod_idx) const { - ERR_FAIL_INDEX_V(p_mod_idx, modifications.size(), nullptr); - return modifications[p_mod_idx]; -} - -void SkeletonModificationStack2D::add_modification(Ref p_mod) { - ERR_FAIL_COND(!p_mod.is_valid()); - - p_mod->_setup_modification(this); - modifications.push_back(p_mod); - -#ifdef TOOLS_ENABLED - set_editor_gizmos_dirty(true); -#endif // TOOLS_ENABLED -} - -void SkeletonModificationStack2D::delete_modification(int p_mod_idx) { - ERR_FAIL_INDEX(p_mod_idx, modifications.size()); - modifications.remove_at(p_mod_idx); - -#ifdef TOOLS_ENABLED - set_editor_gizmos_dirty(true); -#endif // TOOLS_ENABLED -} - -void SkeletonModificationStack2D::set_modification(int p_mod_idx, Ref p_mod) { - ERR_FAIL_INDEX(p_mod_idx, modifications.size()); - - if (p_mod.is_null()) { - modifications.write[p_mod_idx] = Ref(); - } else { - modifications.write[p_mod_idx] = p_mod; - p_mod->_setup_modification(this); - } - -#ifdef TOOLS_ENABLED - set_editor_gizmos_dirty(true); -#endif // TOOLS_ENABLED -} - -void SkeletonModificationStack2D::set_modification_count(int p_count) { - ERR_FAIL_COND_MSG(p_count < 0, "Modification count cannot be less than zero."); - modifications.resize(p_count); - notify_property_list_changed(); - -#ifdef TOOLS_ENABLED - set_editor_gizmos_dirty(true); -#endif // TOOLS_ENABLED -} - -int SkeletonModificationStack2D::get_modification_count() const { - return modifications.size(); -} - -void SkeletonModificationStack2D::set_skeleton(Skeleton2D *p_skeleton) { - skeleton = p_skeleton; -} - -Skeleton2D *SkeletonModificationStack2D::get_skeleton() const { - return skeleton; -} - -bool SkeletonModificationStack2D::get_is_setup() const { - return is_setup; -} - -void SkeletonModificationStack2D::set_enabled(bool p_enabled) { - enabled = p_enabled; -} - -bool SkeletonModificationStack2D::get_enabled() const { - return enabled; -} - -void SkeletonModificationStack2D::set_strength(float p_strength) { - ERR_FAIL_COND_MSG(p_strength < 0, "Strength cannot be less than zero!"); - ERR_FAIL_COND_MSG(p_strength > 1, "Strength cannot be more than one!"); - strength = p_strength; -} - -float SkeletonModificationStack2D::get_strength() const { - return strength; -} - -void SkeletonModificationStack2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("setup"), &SkeletonModificationStack2D::setup); - ClassDB::bind_method(D_METHOD("execute", "delta", "execution_mode"), &SkeletonModificationStack2D::execute); - - ClassDB::bind_method(D_METHOD("enable_all_modifications", "enabled"), &SkeletonModificationStack2D::enable_all_modifications); - ClassDB::bind_method(D_METHOD("get_modification", "mod_idx"), &SkeletonModificationStack2D::get_modification); - ClassDB::bind_method(D_METHOD("add_modification", "modification"), &SkeletonModificationStack2D::add_modification); - ClassDB::bind_method(D_METHOD("delete_modification", "mod_idx"), &SkeletonModificationStack2D::delete_modification); - ClassDB::bind_method(D_METHOD("set_modification", "mod_idx", "modification"), &SkeletonModificationStack2D::set_modification); - - ClassDB::bind_method(D_METHOD("set_modification_count", "count"), &SkeletonModificationStack2D::set_modification_count); - ClassDB::bind_method(D_METHOD("get_modification_count"), &SkeletonModificationStack2D::get_modification_count); - - ClassDB::bind_method(D_METHOD("get_is_setup"), &SkeletonModificationStack2D::get_is_setup); - - ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &SkeletonModificationStack2D::set_enabled); - ClassDB::bind_method(D_METHOD("get_enabled"), &SkeletonModificationStack2D::get_enabled); - - ClassDB::bind_method(D_METHOD("set_strength", "strength"), &SkeletonModificationStack2D::set_strength); - ClassDB::bind_method(D_METHOD("get_strength"), &SkeletonModificationStack2D::get_strength); - - ClassDB::bind_method(D_METHOD("get_skeleton"), &SkeletonModificationStack2D::get_skeleton); - - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "get_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "strength", PROPERTY_HINT_RANGE, "0, 1, 0.001"), "set_strength", "get_strength"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "modification_count", PROPERTY_HINT_RANGE, "0, 100, 1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ARRAY, "Modifications,modifications/"), "set_modification_count", "get_modification_count"); -} - -SkeletonModificationStack2D::SkeletonModificationStack2D() { -} diff --git a/scene/resources/skeleton_modification_stack_2d.h b/scene/resources/skeleton_modification_stack_2d.h deleted file mode 100644 index 6e501436fd..0000000000 --- a/scene/resources/skeleton_modification_stack_2d.h +++ /dev/null @@ -1,99 +0,0 @@ -/**************************************************************************/ -/* skeleton_modification_stack_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 SKELETON_MODIFICATION_STACK_2D_H -#define SKELETON_MODIFICATION_STACK_2D_H - -#include "scene/2d/skeleton_2d.h" -#include "scene/resources/skeleton_modification_2d.h" - -/////////////////////////////////////// -// SkeletonModificationStack2D -/////////////////////////////////////// - -class Skeleton2D; -class SkeletonModification2D; -class Bone2D; - -class SkeletonModificationStack2D : public Resource { - GDCLASS(SkeletonModificationStack2D, Resource); - friend class Skeleton2D; - friend class SkeletonModification2D; - -protected: - static void _bind_methods(); - void _get_property_list(List *p_list) const; - bool _set(const StringName &p_path, const Variant &p_value); - bool _get(const StringName &p_path, Variant &r_ret) const; - -public: - Skeleton2D *skeleton = nullptr; - bool is_setup = false; - bool enabled = false; - float strength = 1.0; - - enum EXECUTION_MODE { - execution_mode_process, - execution_mode_physics_process - }; - - Vector> modifications = Vector>(); - - void setup(); - void execute(float p_delta, int p_execution_mode); - - bool editor_gizmo_dirty = false; - void draw_editor_gizmos(); - void set_editor_gizmos_dirty(bool p_dirty); - - void enable_all_modifications(bool p_enable); - Ref get_modification(int p_mod_idx) const; - void add_modification(Ref p_mod); - void delete_modification(int p_mod_idx); - void set_modification(int p_mod_idx, Ref p_mod); - - void set_modification_count(int p_count); - int get_modification_count() const; - - void set_skeleton(Skeleton2D *p_skeleton); - Skeleton2D *get_skeleton() const; - - bool get_is_setup() const; - - void set_enabled(bool p_enabled); - bool get_enabled() const; - - void set_strength(float p_strength); - float get_strength() const; - - SkeletonModificationStack2D(); -}; - -#endif // SKELETON_MODIFICATION_STACK_2D_H diff --git a/scene/resources/tile_set.compat.inc b/scene/resources/tile_set.compat.inc deleted file mode 100644 index 873ae3aa93..0000000000 --- a/scene/resources/tile_set.compat.inc +++ /dev/null @@ -1,48 +0,0 @@ -/**************************************************************************/ -/* tile_set.compat.inc */ -/**************************************************************************/ -/* 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 DISABLE_DEPRECATED - -#include "tile_set.h" - -Ref TileData::_get_navigation_polygon_bind_compat_84660(int p_layer_id) const { - return get_navigation_polygon(p_layer_id, false, false, false); -} - -Ref TileData::_get_occluder_bind_compat_84660(int p_layer_id) const { - return get_occluder(p_layer_id, false, false, false); -} - -void TileData::_bind_compatibility_methods() { - ClassDB::bind_compatibility_method(D_METHOD("get_navigation_polygon"), &TileData::_get_navigation_polygon_bind_compat_84660); - ClassDB::bind_compatibility_method(D_METHOD("get_occluder"), &TileData::_get_occluder_bind_compat_84660); -} - -#endif diff --git a/scene/resources/tile_set.cpp b/scene/resources/tile_set.cpp deleted file mode 100644 index 7e08d727ac..0000000000 --- a/scene/resources/tile_set.cpp +++ /dev/null @@ -1,6928 +0,0 @@ -/**************************************************************************/ -/* tile_set.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 "tile_set.h" -#include "tile_set.compat.inc" - -#include "core/io/marshalls.h" -#include "core/math/geometry_2d.h" -#include "core/templates/local_vector.h" -#include "core/templates/rb_set.h" -#include "scene/gui/control.h" -#include "scene/resources/convex_polygon_shape_2d.h" -#include "scene/resources/image_texture.h" -#include "servers/navigation_server_2d.h" - -/////////////////////////////// TileMapPattern ////////////////////////////////////// - -void TileMapPattern::_set_tile_data(const Vector &p_data) { - int c = p_data.size(); - const int *r = p_data.ptr(); - - int offset = 3; - ERR_FAIL_COND_MSG(c % offset != 0, "Corrupted tile data."); - - clear(); - - for (int i = 0; i < c; i += offset) { - const uint8_t *ptr = (const uint8_t *)&r[i]; - uint8_t local[12]; - for (int j = 0; j < 12; j++) { - local[j] = ptr[j]; - } - -#ifdef BIG_ENDIAN_ENABLED - SWAP(local[0], local[3]); - SWAP(local[1], local[2]); - SWAP(local[4], local[7]); - SWAP(local[5], local[6]); - SWAP(local[8], local[11]); - SWAP(local[9], local[10]); -#endif - - int16_t x = decode_uint16(&local[0]); - int16_t y = decode_uint16(&local[2]); - uint16_t source_id = decode_uint16(&local[4]); - uint16_t atlas_coords_x = decode_uint16(&local[6]); - uint16_t atlas_coords_y = decode_uint16(&local[8]); - uint16_t alternative_tile = decode_uint16(&local[10]); - set_cell(Vector2i(x, y), source_id, Vector2i(atlas_coords_x, atlas_coords_y), alternative_tile); - } - emit_signal(SNAME("changed")); -} - -Vector TileMapPattern::_get_tile_data() const { - // Export tile data to raw format - Vector data; - data.resize(pattern.size() * 3); - int *w = data.ptrw(); - - // Save in highest format - - int idx = 0; - for (const KeyValue &E : pattern) { - uint8_t *ptr = (uint8_t *)&w[idx]; - encode_uint16((int16_t)(E.key.x), &ptr[0]); - encode_uint16((int16_t)(E.key.y), &ptr[2]); - encode_uint16(E.value.source_id, &ptr[4]); - encode_uint16(E.value.coord_x, &ptr[6]); - encode_uint16(E.value.coord_y, &ptr[8]); - encode_uint16(E.value.alternative_tile, &ptr[10]); - idx += 3; - } - - return data; -} - -void TileMapPattern::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) { - ERR_FAIL_COND_MSG(p_coords.x < 0 || p_coords.y < 0, vformat("Cannot set cell with negative coords in a TileMapPattern. Wrong coords: %s", p_coords)); - - size = size.max(p_coords + Vector2i(1, 1)); - pattern[p_coords] = TileMapCell(p_source_id, p_atlas_coords, p_alternative_tile); - emit_changed(); -} - -bool TileMapPattern::has_cell(const Vector2i &p_coords) const { - return pattern.has(p_coords); -} - -void TileMapPattern::remove_cell(const Vector2i &p_coords, bool p_update_size) { - ERR_FAIL_COND(!pattern.has(p_coords)); - - pattern.erase(p_coords); - if (p_update_size) { - size = Size2i(); - for (const KeyValue &E : pattern) { - size = size.max(E.key + Vector2i(1, 1)); - } - } - emit_changed(); -} - -int TileMapPattern::get_cell_source_id(const Vector2i &p_coords) const { - ERR_FAIL_COND_V(!pattern.has(p_coords), TileSet::INVALID_SOURCE); - - return pattern[p_coords].source_id; -} - -Vector2i TileMapPattern::get_cell_atlas_coords(const Vector2i &p_coords) const { - ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetSource::INVALID_ATLAS_COORDS); - - return pattern[p_coords].get_atlas_coords(); -} - -int TileMapPattern::get_cell_alternative_tile(const Vector2i &p_coords) const { - ERR_FAIL_COND_V(!pattern.has(p_coords), TileSetSource::INVALID_TILE_ALTERNATIVE); - - return pattern[p_coords].alternative_tile; -} - -TypedArray TileMapPattern::get_used_cells() const { - // Returns the cells used in the tilemap. - TypedArray a; - a.resize(pattern.size()); - int i = 0; - for (const KeyValue &E : pattern) { - Vector2i p(E.key.x, E.key.y); - a[i++] = p; - } - - return a; -} - -Size2i TileMapPattern::get_size() const { - return size; -} - -void TileMapPattern::set_size(const Size2i &p_size) { - for (const KeyValue &E : pattern) { - Vector2i coords = E.key; - if (p_size.x <= coords.x || p_size.y <= coords.y) { - ERR_FAIL_MSG(vformat("Cannot set pattern size to %s, it contains a tile at %s. Size can only be increased.", p_size, coords)); - }; - } - - size = p_size; - emit_changed(); -} - -bool TileMapPattern::is_empty() const { - return pattern.is_empty(); -}; - -void TileMapPattern::clear() { - size = Size2i(); - pattern.clear(); - emit_changed(); -}; - -bool TileMapPattern::_set(const StringName &p_name, const Variant &p_value) { - if (p_name == "tile_data") { - if (p_value.is_array()) { - _set_tile_data(p_value); - return true; - } - return false; - } - return false; -} - -bool TileMapPattern::_get(const StringName &p_name, Variant &r_ret) const { - if (p_name == "tile_data") { - r_ret = _get_tile_data(); - return true; - } - return false; -} - -void TileMapPattern::_get_property_list(List *p_list) const { - p_list->push_back(PropertyInfo(Variant::OBJECT, "tile_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL)); -} - -void TileMapPattern::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_cell", "coords", "source_id", "atlas_coords", "alternative_tile"), &TileMapPattern::set_cell, DEFVAL(TileSet::INVALID_SOURCE), DEFVAL(TileSetSource::INVALID_ATLAS_COORDS), DEFVAL(TileSetSource::INVALID_TILE_ALTERNATIVE)); - ClassDB::bind_method(D_METHOD("has_cell", "coords"), &TileMapPattern::has_cell); - ClassDB::bind_method(D_METHOD("remove_cell", "coords", "update_size"), &TileMapPattern::remove_cell); - ClassDB::bind_method(D_METHOD("get_cell_source_id", "coords"), &TileMapPattern::get_cell_source_id); - ClassDB::bind_method(D_METHOD("get_cell_atlas_coords", "coords"), &TileMapPattern::get_cell_atlas_coords); - ClassDB::bind_method(D_METHOD("get_cell_alternative_tile", "coords"), &TileMapPattern::get_cell_alternative_tile); - - ClassDB::bind_method(D_METHOD("get_used_cells"), &TileMapPattern::get_used_cells); - ClassDB::bind_method(D_METHOD("get_size"), &TileMapPattern::get_size); - ClassDB::bind_method(D_METHOD("set_size", "size"), &TileMapPattern::set_size); - ClassDB::bind_method(D_METHOD("is_empty"), &TileMapPattern::is_empty); -} - -/////////////////////////////// TileSet ////////////////////////////////////// - -bool TileSet::TerrainsPattern::is_valid() const { - return valid; -} - -bool TileSet::TerrainsPattern::is_erase_pattern() const { - return not_empty_terrains_count == 0; -} - -bool TileSet::TerrainsPattern::operator<(const TerrainsPattern &p_terrains_pattern) const { - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - if (is_valid_bit[i] != p_terrains_pattern.is_valid_bit[i]) { - return is_valid_bit[i] < p_terrains_pattern.is_valid_bit[i]; - } - } - if (terrain != p_terrains_pattern.terrain) { - return terrain < p_terrains_pattern.terrain; - } - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - if (is_valid_bit[i] && bits[i] != p_terrains_pattern.bits[i]) { - return bits[i] < p_terrains_pattern.bits[i]; - } - } - return false; -} - -bool TileSet::TerrainsPattern::operator==(const TerrainsPattern &p_terrains_pattern) const { - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - if (is_valid_bit[i] != p_terrains_pattern.is_valid_bit[i]) { - return false; - } - if (is_valid_bit[i] && bits[i] != p_terrains_pattern.bits[i]) { - return false; - } - } - if (terrain != p_terrains_pattern.terrain) { - return false; - } - return true; -} - -void TileSet::TerrainsPattern::set_terrain(int p_terrain) { - ERR_FAIL_COND(p_terrain < -1); - - terrain = p_terrain; -} - -int TileSet::TerrainsPattern::get_terrain() const { - return terrain; -} - -void TileSet::TerrainsPattern::set_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit, int p_terrain) { - ERR_FAIL_COND(p_peering_bit == TileSet::CELL_NEIGHBOR_MAX); - ERR_FAIL_COND(!is_valid_bit[p_peering_bit]); - ERR_FAIL_COND(p_terrain < -1); - - // Update the "is_erase_pattern" status. - if (p_terrain >= 0 && bits[p_peering_bit] < 0) { - not_empty_terrains_count++; - } else if (p_terrain < 0 && bits[p_peering_bit] >= 0) { - not_empty_terrains_count--; - } - - bits[p_peering_bit] = p_terrain; -} - -int TileSet::TerrainsPattern::get_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit) const { - ERR_FAIL_COND_V(p_peering_bit == TileSet::CELL_NEIGHBOR_MAX, -1); - ERR_FAIL_COND_V(!is_valid_bit[p_peering_bit], -1); - return bits[p_peering_bit]; -} - -void TileSet::TerrainsPattern::from_array(Array p_terrains) { - set_terrain(p_terrains[0]); - int in_array_index = 1; - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - if (is_valid_bit[i]) { - ERR_FAIL_INDEX(in_array_index, p_terrains.size()); - set_terrain_peering_bit(TileSet::CellNeighbor(i), p_terrains[in_array_index]); - in_array_index++; - } - } -} - -Array TileSet::TerrainsPattern::as_array() const { - Array output; - output.push_back(get_terrain()); - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - if (is_valid_bit[i]) { - output.push_back(bits[i]); - } - } - return output; -} - -TileSet::TerrainsPattern::TerrainsPattern(const TileSet *p_tile_set, int p_terrain_set) { - ERR_FAIL_COND(p_terrain_set < 0); - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - is_valid_bit[i] = (p_tile_set->is_valid_terrain_peering_bit(p_terrain_set, TileSet::CellNeighbor(i))); - bits[i] = -1; - } - valid = true; -} - -const int TileSet::INVALID_SOURCE = -1; - -const char *TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[] = { - PNAME("right_side"), - PNAME("right_corner"), - PNAME("bottom_right_side"), - PNAME("bottom_right_corner"), - PNAME("bottom_side"), - PNAME("bottom_corner"), - PNAME("bottom_left_side"), - PNAME("bottom_left_corner"), - PNAME("left_side"), - PNAME("left_corner"), - PNAME("top_left_side"), - PNAME("top_left_corner"), - PNAME("top_side"), - PNAME("top_corner"), - PNAME("top_right_side"), - PNAME("top_right_corner"), -}; - -// -- Shape and layout -- -void TileSet::set_tile_shape(TileSet::TileShape p_shape) { - tile_shape = p_shape; - - for (KeyValue> &E_source : sources) { - E_source.value->notify_tile_data_properties_should_change(); - } - - terrain_bits_meshes_dirty = true; - tile_meshes_dirty = true; - notify_property_list_changed(); - emit_changed(); -} -TileSet::TileShape TileSet::get_tile_shape() const { - return tile_shape; -} - -void TileSet::set_tile_layout(TileSet::TileLayout p_layout) { - tile_layout = p_layout; - emit_changed(); -} -TileSet::TileLayout TileSet::get_tile_layout() const { - return tile_layout; -} - -void TileSet::set_tile_offset_axis(TileSet::TileOffsetAxis p_alignment) { - tile_offset_axis = p_alignment; - - for (KeyValue> &E_source : sources) { - E_source.value->notify_tile_data_properties_should_change(); - } - - terrain_bits_meshes_dirty = true; - tile_meshes_dirty = true; - emit_changed(); -} -TileSet::TileOffsetAxis TileSet::get_tile_offset_axis() const { - return tile_offset_axis; -} - -void TileSet::set_tile_size(Size2i p_size) { - ERR_FAIL_COND(p_size.x < 1 || p_size.y < 1); - tile_size = p_size; - terrain_bits_meshes_dirty = true; - tile_meshes_dirty = true; - emit_changed(); -} -Size2i TileSet::get_tile_size() const { - return tile_size; -} - -int TileSet::get_next_source_id() const { - return next_source_id; -} - -void TileSet::_update_terrains_cache() { - if (terrains_cache_dirty) { - // Organizes tiles into structures. - per_terrain_pattern_tiles.resize(terrain_sets.size()); - for (RBMap> &tiles : per_terrain_pattern_tiles) { - tiles.clear(); - } - - for (const KeyValue> &kv : sources) { - Ref source = kv.value; - Ref atlas_source = source; - if (atlas_source.is_valid()) { - for (int tile_index = 0; tile_index < source->get_tiles_count(); tile_index++) { - Vector2i tile_id = source->get_tile_id(tile_index); - for (int alternative_index = 0; alternative_index < source->get_alternative_tiles_count(tile_id); alternative_index++) { - int alternative_id = source->get_alternative_tile_id(tile_id, alternative_index); - - // Executed for each tile_data. - TileData *tile_data = atlas_source->get_tile_data(tile_id, alternative_id); - int terrain_set = tile_data->get_terrain_set(); - if (terrain_set >= 0) { - TileMapCell cell; - cell.source_id = kv.key; - cell.set_atlas_coords(tile_id); - cell.alternative_tile = alternative_id; - - TileSet::TerrainsPattern terrains_pattern = tile_data->get_terrains_pattern(); - - // Main terrain. - if (terrains_pattern.get_terrain() >= 0) { - per_terrain_pattern_tiles[terrain_set][terrains_pattern].insert(cell); - } - - // Terrain bits. - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - CellNeighbor bit = CellNeighbor(i); - if (is_valid_terrain_peering_bit(terrain_set, bit)) { - int terrain = terrains_pattern.get_terrain_peering_bit(bit); - if (terrain >= 0) { - per_terrain_pattern_tiles[terrain_set][terrains_pattern].insert(cell); - } - } - } - } - } - } - } - } - - // Add the empty cell in the possible patterns and cells. - for (int i = 0; i < terrain_sets.size(); i++) { - TileSet::TerrainsPattern empty_pattern(this, i); - - TileMapCell empty_cell; - empty_cell.source_id = TileSet::INVALID_SOURCE; - empty_cell.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); - empty_cell.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; - per_terrain_pattern_tiles[i][empty_pattern].insert(empty_cell); - } - terrains_cache_dirty = false; - } -} - -void TileSet::_compute_next_source_id() { - while (sources.has(next_source_id)) { - next_source_id = (next_source_id + 1) % 1073741824; // 2 ** 30 - }; -} - -// Sources management -int TileSet::add_source(Ref p_tile_set_source, int p_atlas_source_id_override) { - ERR_FAIL_COND_V(!p_tile_set_source.is_valid(), TileSet::INVALID_SOURCE); - ERR_FAIL_COND_V_MSG(p_atlas_source_id_override >= 0 && (sources.has(p_atlas_source_id_override)), TileSet::INVALID_SOURCE, vformat("Cannot create TileSet atlas source. Another atlas source exists with id %d.", p_atlas_source_id_override)); - ERR_FAIL_COND_V_MSG(p_atlas_source_id_override < 0 && p_atlas_source_id_override != TileSet::INVALID_SOURCE, TileSet::INVALID_SOURCE, vformat("Provided source ID %d is not valid. Negative source IDs are not allowed.", p_atlas_source_id_override)); - - int new_source_id = p_atlas_source_id_override >= 0 ? p_atlas_source_id_override : next_source_id; - sources[new_source_id] = p_tile_set_source; - source_ids.push_back(new_source_id); - source_ids.sort(); - TileSet *old_tileset = p_tile_set_source->get_tile_set(); - if (old_tileset != this && old_tileset != nullptr) { - // If the source is already in a TileSet (might happen when duplicating), remove it from the other TileSet. - old_tileset->remove_source_ptr(p_tile_set_source.ptr()); - } - p_tile_set_source->set_tile_set(this); - _compute_next_source_id(); - - sources[new_source_id]->connect_changed(callable_mp(this, &TileSet::_source_changed)); - - terrains_cache_dirty = true; - emit_changed(); - - return new_source_id; -} - -void TileSet::remove_source(int p_source_id) { - ERR_FAIL_COND_MSG(!sources.has(p_source_id), vformat("Cannot remove TileSet atlas source. No tileset atlas source with id %d.", p_source_id)); - - sources[p_source_id]->disconnect_changed(callable_mp(this, &TileSet::_source_changed)); - - sources[p_source_id]->set_tile_set(nullptr); - sources.erase(p_source_id); - source_ids.erase(p_source_id); - source_ids.sort(); - - terrains_cache_dirty = true; - emit_changed(); -} - -void TileSet::remove_source_ptr(TileSetSource *p_tile_set_source) { - for (const KeyValue> &kv : sources) { - if (kv.value.ptr() == p_tile_set_source) { - remove_source(kv.key); - return; - } - } - ERR_FAIL_MSG(vformat("Attempting to remove source from a tileset, but the tileset doesn't have it: %s", p_tile_set_source)); -} - -void TileSet::set_source_id(int p_source_id, int p_new_source_id) { - ERR_FAIL_COND(p_new_source_id < 0); - ERR_FAIL_COND_MSG(!sources.has(p_source_id), vformat("Cannot change TileSet atlas source ID. No tileset atlas source with id %d.", p_source_id)); - if (p_source_id == p_new_source_id) { - return; - } - - ERR_FAIL_COND_MSG(sources.has(p_new_source_id), vformat("Cannot change TileSet atlas source ID. Another atlas source exists with id %d.", p_new_source_id)); - - sources[p_new_source_id] = sources[p_source_id]; - sources.erase(p_source_id); - - source_ids.erase(p_source_id); - source_ids.push_back(p_new_source_id); - source_ids.sort(); - - _compute_next_source_id(); - - terrains_cache_dirty = true; - emit_changed(); -} - -bool TileSet::has_source(int p_source_id) const { - return sources.has(p_source_id); -} - -Ref TileSet::get_source(int p_source_id) const { - ERR_FAIL_COND_V_MSG(!sources.has(p_source_id), nullptr, vformat("No TileSet atlas source with id %d.", p_source_id)); - - return sources[p_source_id]; -} - -int TileSet::get_source_count() const { - return source_ids.size(); -} - -int TileSet::get_source_id(int p_index) const { - ERR_FAIL_INDEX_V(p_index, source_ids.size(), TileSet::INVALID_SOURCE); - return source_ids[p_index]; -} - -// Rendering -void TileSet::set_uv_clipping(bool p_uv_clipping) { - if (uv_clipping == p_uv_clipping) { - return; - } - uv_clipping = p_uv_clipping; - emit_changed(); -} - -bool TileSet::is_uv_clipping() const { - return uv_clipping; -}; - -int TileSet::get_occlusion_layers_count() const { - return occlusion_layers.size(); -}; - -void TileSet::add_occlusion_layer(int p_index) { - if (p_index < 0) { - p_index = occlusion_layers.size(); - } - ERR_FAIL_INDEX(p_index, occlusion_layers.size() + 1); - occlusion_layers.insert(p_index, OcclusionLayer()); - - for (KeyValue> source : sources) { - source.value->add_occlusion_layer(p_index); - } - - notify_property_list_changed(); - emit_changed(); -} - -void TileSet::move_occlusion_layer(int p_from_index, int p_to_pos) { - ERR_FAIL_INDEX(p_from_index, occlusion_layers.size()); - ERR_FAIL_INDEX(p_to_pos, occlusion_layers.size() + 1); - occlusion_layers.insert(p_to_pos, occlusion_layers[p_from_index]); - occlusion_layers.remove_at(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); - for (KeyValue> source : sources) { - source.value->move_occlusion_layer(p_from_index, p_to_pos); - } - notify_property_list_changed(); - emit_changed(); -} - -void TileSet::remove_occlusion_layer(int p_index) { - ERR_FAIL_INDEX(p_index, occlusion_layers.size()); - occlusion_layers.remove_at(p_index); - for (KeyValue> source : sources) { - source.value->remove_occlusion_layer(p_index); - } - notify_property_list_changed(); - emit_changed(); -} - -void TileSet::set_occlusion_layer_light_mask(int p_layer_index, int p_light_mask) { - ERR_FAIL_INDEX(p_layer_index, occlusion_layers.size()); - occlusion_layers.write[p_layer_index].light_mask = p_light_mask; - emit_changed(); -} - -int TileSet::get_occlusion_layer_light_mask(int p_layer_index) const { - ERR_FAIL_INDEX_V(p_layer_index, occlusion_layers.size(), 0); - return occlusion_layers[p_layer_index].light_mask; -} - -void TileSet::set_occlusion_layer_sdf_collision(int p_layer_index, bool p_sdf_collision) { - ERR_FAIL_INDEX(p_layer_index, occlusion_layers.size()); - occlusion_layers.write[p_layer_index].sdf_collision = p_sdf_collision; - emit_changed(); -} - -bool TileSet::get_occlusion_layer_sdf_collision(int p_layer_index) const { - ERR_FAIL_INDEX_V(p_layer_index, occlusion_layers.size(), false); - return occlusion_layers[p_layer_index].sdf_collision; -} - -int TileSet::get_physics_layers_count() const { - return physics_layers.size(); -} - -void TileSet::add_physics_layer(int p_index) { - if (p_index < 0) { - p_index = physics_layers.size(); - } - ERR_FAIL_INDEX(p_index, physics_layers.size() + 1); - physics_layers.insert(p_index, PhysicsLayer()); - - for (KeyValue> source : sources) { - source.value->add_physics_layer(p_index); - } - - notify_property_list_changed(); - emit_changed(); -} - -void TileSet::move_physics_layer(int p_from_index, int p_to_pos) { - ERR_FAIL_INDEX(p_from_index, physics_layers.size()); - ERR_FAIL_INDEX(p_to_pos, physics_layers.size() + 1); - physics_layers.insert(p_to_pos, physics_layers[p_from_index]); - physics_layers.remove_at(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); - for (KeyValue> source : sources) { - source.value->move_physics_layer(p_from_index, p_to_pos); - } - notify_property_list_changed(); - emit_changed(); -} - -void TileSet::remove_physics_layer(int p_index) { - ERR_FAIL_INDEX(p_index, physics_layers.size()); - physics_layers.remove_at(p_index); - for (KeyValue> source : sources) { - source.value->remove_physics_layer(p_index); - } - notify_property_list_changed(); - emit_changed(); -} - -void TileSet::set_physics_layer_collision_layer(int p_layer_index, uint32_t p_layer) { - ERR_FAIL_INDEX(p_layer_index, physics_layers.size()); - physics_layers.write[p_layer_index].collision_layer = p_layer; - emit_changed(); -} - -uint32_t TileSet::get_physics_layer_collision_layer(int p_layer_index) const { - ERR_FAIL_INDEX_V(p_layer_index, physics_layers.size(), 0); - return physics_layers[p_layer_index].collision_layer; -} - -void TileSet::set_physics_layer_collision_mask(int p_layer_index, uint32_t p_mask) { - ERR_FAIL_INDEX(p_layer_index, physics_layers.size()); - physics_layers.write[p_layer_index].collision_mask = p_mask; - emit_changed(); -} - -uint32_t TileSet::get_physics_layer_collision_mask(int p_layer_index) const { - ERR_FAIL_INDEX_V(p_layer_index, physics_layers.size(), 0); - return physics_layers[p_layer_index].collision_mask; -} - -void TileSet::set_physics_layer_physics_material(int p_layer_index, Ref p_physics_material) { - ERR_FAIL_INDEX(p_layer_index, physics_layers.size()); - physics_layers.write[p_layer_index].physics_material = p_physics_material; -} - -Ref TileSet::get_physics_layer_physics_material(int p_layer_index) const { - ERR_FAIL_INDEX_V(p_layer_index, physics_layers.size(), Ref()); - return physics_layers[p_layer_index].physics_material; -} - -// Terrains -int TileSet::get_terrain_sets_count() const { - return terrain_sets.size(); -} - -void TileSet::add_terrain_set(int p_index) { - if (p_index < 0) { - p_index = terrain_sets.size(); - } - ERR_FAIL_INDEX(p_index, terrain_sets.size() + 1); - terrain_sets.insert(p_index, TerrainSet()); - - for (KeyValue> source : sources) { - source.value->add_terrain_set(p_index); - } - - notify_property_list_changed(); - terrains_cache_dirty = true; - emit_changed(); -} - -void TileSet::move_terrain_set(int p_from_index, int p_to_pos) { - ERR_FAIL_INDEX(p_from_index, terrain_sets.size()); - ERR_FAIL_INDEX(p_to_pos, terrain_sets.size() + 1); - terrain_sets.insert(p_to_pos, terrain_sets[p_from_index]); - terrain_sets.remove_at(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); - for (KeyValue> source : sources) { - source.value->move_terrain_set(p_from_index, p_to_pos); - } - notify_property_list_changed(); - terrains_cache_dirty = true; - emit_changed(); -} - -void TileSet::remove_terrain_set(int p_index) { - ERR_FAIL_INDEX(p_index, terrain_sets.size()); - terrain_sets.remove_at(p_index); - for (KeyValue> source : sources) { - source.value->remove_terrain_set(p_index); - } - notify_property_list_changed(); - terrains_cache_dirty = true; - emit_changed(); -} - -void TileSet::set_terrain_set_mode(int p_terrain_set, TerrainMode p_terrain_mode) { - ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size()); - terrain_sets.write[p_terrain_set].mode = p_terrain_mode; - for (KeyValue> &E_source : sources) { - E_source.value->notify_tile_data_properties_should_change(); - } - - notify_property_list_changed(); - terrains_cache_dirty = true; - emit_changed(); -} - -TileSet::TerrainMode TileSet::get_terrain_set_mode(int p_terrain_set) const { - ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES); - return terrain_sets[p_terrain_set].mode; -} - -int TileSet::get_terrains_count(int p_terrain_set) const { - ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), -1); - return terrain_sets[p_terrain_set].terrains.size(); -} - -void TileSet::add_terrain(int p_terrain_set, int p_index) { - ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size()); - Vector &terrains = terrain_sets.write[p_terrain_set].terrains; - if (p_index < 0) { - p_index = terrains.size(); - } - ERR_FAIL_INDEX(p_index, terrains.size() + 1); - terrains.insert(p_index, Terrain()); - - // Default name and color - float hue_rotate = (terrains.size() % 16) / 16.0; - Color c; - c.set_hsv(Math::fmod(float(hue_rotate), float(1.0)), 0.5, 0.5); - terrains.write[p_index].color = c; - terrains.write[p_index].name = String(vformat("Terrain %d", p_index)); - - for (KeyValue> source : sources) { - source.value->add_terrain(p_terrain_set, p_index); - } - - notify_property_list_changed(); - terrains_cache_dirty = true; - emit_changed(); -} - -void TileSet::move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) { - ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size()); - Vector &terrains = terrain_sets.write[p_terrain_set].terrains; - - ERR_FAIL_INDEX(p_from_index, terrains.size()); - ERR_FAIL_INDEX(p_to_pos, terrains.size() + 1); - terrains.insert(p_to_pos, terrains[p_from_index]); - terrains.remove_at(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); - for (KeyValue> source : sources) { - source.value->move_terrain(p_terrain_set, p_from_index, p_to_pos); - } - notify_property_list_changed(); - terrains_cache_dirty = true; - emit_changed(); -} - -void TileSet::remove_terrain(int p_terrain_set, int p_index) { - ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size()); - Vector &terrains = terrain_sets.write[p_terrain_set].terrains; - - ERR_FAIL_INDEX(p_index, terrains.size()); - terrains.remove_at(p_index); - for (KeyValue> source : sources) { - source.value->remove_terrain(p_terrain_set, p_index); - } - notify_property_list_changed(); - terrains_cache_dirty = true; - emit_changed(); -} - -void TileSet::set_terrain_name(int p_terrain_set, int p_terrain_index, String p_name) { - ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size()); - ERR_FAIL_INDEX(p_terrain_index, terrain_sets[p_terrain_set].terrains.size()); - terrain_sets.write[p_terrain_set].terrains.write[p_terrain_index].name = p_name; - emit_changed(); -} - -String TileSet::get_terrain_name(int p_terrain_set, int p_terrain_index) const { - ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), String()); - ERR_FAIL_INDEX_V(p_terrain_index, terrain_sets[p_terrain_set].terrains.size(), String()); - return terrain_sets[p_terrain_set].terrains[p_terrain_index].name; -} - -void TileSet::set_terrain_color(int p_terrain_set, int p_terrain_index, Color p_color) { - ERR_FAIL_INDEX(p_terrain_set, terrain_sets.size()); - ERR_FAIL_INDEX(p_terrain_index, terrain_sets[p_terrain_set].terrains.size()); - if (p_color.a != 1.0) { - WARN_PRINT("Terrain color should have alpha == 1.0"); - p_color.a = 1.0; - } - terrain_sets.write[p_terrain_set].terrains.write[p_terrain_index].color = p_color; - emit_changed(); -} - -Color TileSet::get_terrain_color(int p_terrain_set, int p_terrain_index) const { - ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), Color()); - ERR_FAIL_INDEX_V(p_terrain_index, terrain_sets[p_terrain_set].terrains.size(), Color()); - return terrain_sets[p_terrain_set].terrains[p_terrain_index].color; -} - -bool TileSet::is_valid_terrain_peering_bit_for_mode(TileSet::TerrainMode p_terrain_mode, TileSet::CellNeighbor p_peering_bit) const { - if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { - if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_SIDES) { - if (p_peering_bit == TileSet::CELL_NEIGHBOR_RIGHT_SIDE || - p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE || - p_peering_bit == TileSet::CELL_NEIGHBOR_LEFT_SIDE || - p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_SIDE) { - return true; - } - } - if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { - if (p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER || - p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER || - p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER || - p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER) { - return true; - } - } - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { - if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_SIDES) { - if (p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE || - p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE || - p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE || - p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return true; - } - } - if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { - if (p_peering_bit == TileSet::CELL_NEIGHBOR_RIGHT_CORNER || - p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER || - p_peering_bit == TileSet::CELL_NEIGHBOR_LEFT_CORNER || - p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_CORNER) { - return true; - } - } - } else { - if (get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_SIDES) { - if (p_peering_bit == TileSet::CELL_NEIGHBOR_RIGHT_SIDE || - p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE || - p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE || - p_peering_bit == TileSet::CELL_NEIGHBOR_LEFT_SIDE || - p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE || - p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return true; - } - } - if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { - if (p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER || - p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER || - p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER || - p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER || - p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_CORNER || - p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER) { - return true; - } - } - } else { - if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_SIDES) { - if (p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE || - p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE || - p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE || - p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE || - p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_SIDE || - p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return true; - } - } - if (p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES || p_terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { - if (p_peering_bit == TileSet::CELL_NEIGHBOR_RIGHT_CORNER || - p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER || - p_peering_bit == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER || - p_peering_bit == TileSet::CELL_NEIGHBOR_LEFT_CORNER || - p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER || - p_peering_bit == TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER) { - return true; - } - } - } - } - return false; -} - -bool TileSet::is_valid_terrain_peering_bit(int p_terrain_set, TileSet::CellNeighbor p_peering_bit) const { - if (p_terrain_set < 0 || p_terrain_set >= get_terrain_sets_count()) { - return false; - } - - TileSet::TerrainMode terrain_mode = get_terrain_set_mode(p_terrain_set); - return is_valid_terrain_peering_bit_for_mode(terrain_mode, p_peering_bit); -} - -// Navigation -int TileSet::get_navigation_layers_count() const { - return navigation_layers.size(); -} - -void TileSet::add_navigation_layer(int p_index) { - if (p_index < 0) { - p_index = navigation_layers.size(); - } - ERR_FAIL_INDEX(p_index, navigation_layers.size() + 1); - navigation_layers.insert(p_index, NavigationLayer()); - - for (KeyValue> source : sources) { - source.value->add_navigation_layer(p_index); - } - - notify_property_list_changed(); - emit_changed(); -} - -void TileSet::move_navigation_layer(int p_from_index, int p_to_pos) { - ERR_FAIL_INDEX(p_from_index, navigation_layers.size()); - ERR_FAIL_INDEX(p_to_pos, navigation_layers.size() + 1); - navigation_layers.insert(p_to_pos, navigation_layers[p_from_index]); - navigation_layers.remove_at(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); - for (KeyValue> source : sources) { - source.value->move_navigation_layer(p_from_index, p_to_pos); - } - notify_property_list_changed(); - emit_changed(); -} - -void TileSet::remove_navigation_layer(int p_index) { - ERR_FAIL_INDEX(p_index, navigation_layers.size()); - navigation_layers.remove_at(p_index); - for (KeyValue> source : sources) { - source.value->remove_navigation_layer(p_index); - } - notify_property_list_changed(); - emit_changed(); -} - -void TileSet::set_navigation_layer_layers(int p_layer_index, uint32_t p_layers) { - ERR_FAIL_INDEX(p_layer_index, navigation_layers.size()); - navigation_layers.write[p_layer_index].layers = p_layers; - emit_changed(); -} - -uint32_t TileSet::get_navigation_layer_layers(int p_layer_index) const { - ERR_FAIL_INDEX_V(p_layer_index, navigation_layers.size(), 0); - return navigation_layers[p_layer_index].layers; -} - -void TileSet::set_navigation_layer_layer_value(int p_layer_index, int p_layer_number, bool p_value) { - ERR_FAIL_COND_MSG(p_layer_number < 1, "Navigation layer number must be between 1 and 32 inclusive."); - ERR_FAIL_COND_MSG(p_layer_number > 32, "Navigation layer number must be between 1 and 32 inclusive."); - - uint32_t _navigation_layers = get_navigation_layer_layers(p_layer_index); - - if (p_value) { - _navigation_layers |= 1 << (p_layer_number - 1); - } else { - _navigation_layers &= ~(1 << (p_layer_number - 1)); - } - - set_navigation_layer_layers(p_layer_index, _navigation_layers); -} - -bool TileSet::get_navigation_layer_layer_value(int p_layer_index, int p_layer_number) const { - ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Navigation layer number must be between 1 and 32 inclusive."); - ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Navigation layer number must be between 1 and 32 inclusive."); - - return get_navigation_layer_layers(p_layer_index) & (1 << (p_layer_number - 1)); -} - -// Custom data. -int TileSet::get_custom_data_layers_count() const { - return custom_data_layers.size(); -} - -void TileSet::add_custom_data_layer(int p_index) { - if (p_index < 0) { - p_index = custom_data_layers.size(); - } - ERR_FAIL_INDEX(p_index, custom_data_layers.size() + 1); - custom_data_layers.insert(p_index, CustomDataLayer()); - - for (KeyValue> source : sources) { - source.value->add_custom_data_layer(p_index); - } - - notify_property_list_changed(); - emit_changed(); -} - -void TileSet::move_custom_data_layer(int p_from_index, int p_to_pos) { - ERR_FAIL_INDEX(p_from_index, custom_data_layers.size()); - ERR_FAIL_INDEX(p_to_pos, custom_data_layers.size() + 1); - custom_data_layers.insert(p_to_pos, custom_data_layers[p_from_index]); - custom_data_layers.remove_at(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); - for (KeyValue> source : sources) { - source.value->move_custom_data_layer(p_from_index, p_to_pos); - } - notify_property_list_changed(); - emit_changed(); -} - -void TileSet::remove_custom_data_layer(int p_index) { - ERR_FAIL_INDEX(p_index, custom_data_layers.size()); - custom_data_layers.remove_at(p_index); - - String to_erase; - for (KeyValue &E : custom_data_layers_by_name) { - if (E.value == p_index) { - to_erase = E.key; - } else if (E.value > p_index) { - E.value--; - } - } - custom_data_layers_by_name.erase(to_erase); - - for (KeyValue> source : sources) { - source.value->remove_custom_data_layer(p_index); - } - notify_property_list_changed(); - emit_changed(); -} - -int TileSet::get_custom_data_layer_by_name(String p_value) const { - if (custom_data_layers_by_name.has(p_value)) { - return custom_data_layers_by_name[p_value]; - } else { - return -1; - } -} - -void TileSet::set_custom_data_layer_name(int p_layer_id, String p_value) { - ERR_FAIL_INDEX(p_layer_id, custom_data_layers.size()); - - // Exit if another property has the same name. - if (!p_value.is_empty()) { - for (int other_layer_id = 0; other_layer_id < get_custom_data_layers_count(); other_layer_id++) { - if (other_layer_id != p_layer_id && get_custom_data_layer_name(other_layer_id) == p_value) { - ERR_FAIL_MSG(vformat("There is already a custom property named %s", p_value)); - } - } - } - - if (p_value.is_empty() && custom_data_layers_by_name.has(p_value)) { - custom_data_layers_by_name.erase(p_value); - } else { - custom_data_layers_by_name[p_value] = p_layer_id; - } - - custom_data_layers.write[p_layer_id].name = p_value; - emit_changed(); -} - -String TileSet::get_custom_data_layer_name(int p_layer_id) const { - ERR_FAIL_INDEX_V(p_layer_id, custom_data_layers.size(), ""); - return custom_data_layers[p_layer_id].name; -} - -void TileSet::set_custom_data_layer_type(int p_layer_id, Variant::Type p_value) { - ERR_FAIL_INDEX(p_layer_id, custom_data_layers.size()); - custom_data_layers.write[p_layer_id].type = p_value; - - for (KeyValue> &E_source : sources) { - E_source.value->notify_tile_data_properties_should_change(); - } - - emit_changed(); -} - -Variant::Type TileSet::get_custom_data_layer_type(int p_layer_id) const { - ERR_FAIL_INDEX_V(p_layer_id, custom_data_layers.size(), Variant::NIL); - return custom_data_layers[p_layer_id].type; -} - -void TileSet::set_source_level_tile_proxy(int p_source_from, int p_source_to) { - ERR_FAIL_COND(p_source_from == TileSet::INVALID_SOURCE || p_source_to == TileSet::INVALID_SOURCE); - - source_level_proxies[p_source_from] = p_source_to; - - emit_changed(); -} - -int TileSet::get_source_level_tile_proxy(int p_source_from) { - ERR_FAIL_COND_V(!source_level_proxies.has(p_source_from), TileSet::INVALID_SOURCE); - - return source_level_proxies[p_source_from]; -} - -bool TileSet::has_source_level_tile_proxy(int p_source_from) { - return source_level_proxies.has(p_source_from); -} - -void TileSet::remove_source_level_tile_proxy(int p_source_from) { - ERR_FAIL_COND(!source_level_proxies.has(p_source_from)); - - source_level_proxies.erase(p_source_from); - - emit_changed(); -} - -void TileSet::set_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_source_to, Vector2i p_coords_to) { - ERR_FAIL_COND(p_source_from == TileSet::INVALID_SOURCE || p_source_to == TileSet::INVALID_SOURCE); - ERR_FAIL_COND(p_coords_from == TileSetSource::INVALID_ATLAS_COORDS || p_coords_to == TileSetSource::INVALID_ATLAS_COORDS); - - Array from; - from.push_back(p_source_from); - from.push_back(p_coords_from); - - Array to; - to.push_back(p_source_to); - to.push_back(p_coords_to); - - coords_level_proxies[from] = to; - - emit_changed(); -} - -Array TileSet::get_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from) { - Array from; - from.push_back(p_source_from); - from.push_back(p_coords_from); - - ERR_FAIL_COND_V(!coords_level_proxies.has(from), Array()); - - return coords_level_proxies[from]; -} - -bool TileSet::has_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from) { - Array from; - from.push_back(p_source_from); - from.push_back(p_coords_from); - - return coords_level_proxies.has(from); -} - -void TileSet::remove_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from) { - Array from; - from.push_back(p_source_from); - from.push_back(p_coords_from); - - ERR_FAIL_COND(!coords_level_proxies.has(from)); - - coords_level_proxies.erase(from); - - emit_changed(); -} - -void TileSet::set_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from, int p_source_to, Vector2i p_coords_to, int p_alternative_to) { - ERR_FAIL_COND(p_source_from == TileSet::INVALID_SOURCE || p_source_to == TileSet::INVALID_SOURCE); - ERR_FAIL_COND(p_coords_from == TileSetSource::INVALID_ATLAS_COORDS || p_coords_to == TileSetSource::INVALID_ATLAS_COORDS); - - Array from; - from.push_back(p_source_from); - from.push_back(p_coords_from); - from.push_back(p_alternative_from); - - Array to; - to.push_back(p_source_to); - to.push_back(p_coords_to); - to.push_back(p_alternative_to); - - alternative_level_proxies[from] = to; - - emit_changed(); -} - -Array TileSet::get_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from) { - Array from; - from.push_back(p_source_from); - from.push_back(p_coords_from); - from.push_back(p_alternative_from); - - ERR_FAIL_COND_V(!alternative_level_proxies.has(from), Array()); - - return alternative_level_proxies[from]; -} - -bool TileSet::has_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from) { - Array from; - from.push_back(p_source_from); - from.push_back(p_coords_from); - from.push_back(p_alternative_from); - - return alternative_level_proxies.has(from); -} - -void TileSet::remove_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from) { - Array from; - from.push_back(p_source_from); - from.push_back(p_coords_from); - from.push_back(p_alternative_from); - - ERR_FAIL_COND(!alternative_level_proxies.has(from)); - - alternative_level_proxies.erase(from); - - emit_changed(); -} - -Array TileSet::get_source_level_tile_proxies() const { - Array output; - for (const KeyValue &E : source_level_proxies) { - Array proxy; - proxy.push_back(E.key); - proxy.push_back(E.value); - output.push_back(proxy); - } - return output; -} - -Array TileSet::get_coords_level_tile_proxies() const { - Array output; - for (const KeyValue &E : coords_level_proxies) { - Array proxy; - proxy.append_array(E.key); - proxy.append_array(E.value); - output.push_back(proxy); - } - return output; -} - -Array TileSet::get_alternative_level_tile_proxies() const { - Array output; - for (const KeyValue &E : alternative_level_proxies) { - Array proxy; - proxy.append_array(E.key); - proxy.append_array(E.value); - output.push_back(proxy); - } - return output; -} - -Array TileSet::map_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from) const { - Array from; - from.push_back(p_source_from); - from.push_back(p_coords_from); - from.push_back(p_alternative_from); - - // Check if the tile is valid, and if so, don't map the tile and return the input. - if (has_source(p_source_from)) { - Ref source = get_source(p_source_from); - if (source->has_tile(p_coords_from) && source->has_alternative_tile(p_coords_from, p_alternative_from)) { - return from; - } - } - - // Source, coords and alternative match. - if (alternative_level_proxies.has(from)) { - return alternative_level_proxies[from].duplicate(); - } - - // Source and coords match. - from.pop_back(); - if (coords_level_proxies.has(from)) { - Array output = coords_level_proxies[from].duplicate(); - output.push_back(p_alternative_from); - return output; - } - - // Source matches. - if (source_level_proxies.has(p_source_from)) { - Array output; - output.push_back(source_level_proxies[p_source_from]); - output.push_back(p_coords_from); - output.push_back(p_alternative_from); - return output; - } - - Array output; - output.push_back(p_source_from); - output.push_back(p_coords_from); - output.push_back(p_alternative_from); - return output; -} - -void TileSet::cleanup_invalid_tile_proxies() { - // Source level. - Vector source_to_remove; - for (const KeyValue &E : source_level_proxies) { - if (has_source(E.key)) { - source_to_remove.push_back(E.key); - } - } - for (int i = 0; i < source_to_remove.size(); i++) { - remove_source_level_tile_proxy(source_to_remove[i]); - } - - // Coords level. - Vector coords_to_remove; - for (const KeyValue &E : coords_level_proxies) { - Array a = E.key; - if (has_source(a[0]) && get_source(a[0])->has_tile(a[1])) { - coords_to_remove.push_back(a); - } - } - for (int i = 0; i < coords_to_remove.size(); i++) { - Array a = coords_to_remove[i]; - remove_coords_level_tile_proxy(a[0], a[1]); - } - - // Alternative level. - Vector alternative_to_remove; - for (const KeyValue &E : alternative_level_proxies) { - Array a = E.key; - if (has_source(a[0]) && get_source(a[0])->has_tile(a[1]) && get_source(a[0])->has_alternative_tile(a[1], a[2])) { - alternative_to_remove.push_back(a); - } - } - for (int i = 0; i < alternative_to_remove.size(); i++) { - Array a = alternative_to_remove[i]; - remove_alternative_level_tile_proxy(a[0], a[1], a[2]); - } -} - -void TileSet::clear_tile_proxies() { - source_level_proxies.clear(); - coords_level_proxies.clear(); - alternative_level_proxies.clear(); - - emit_changed(); -} - -int TileSet::add_pattern(Ref p_pattern, int p_index) { - ERR_FAIL_COND_V(!p_pattern.is_valid(), -1); - ERR_FAIL_COND_V_MSG(p_pattern->is_empty(), -1, "Cannot add an empty pattern to the TileSet."); - for (const Ref &pattern : patterns) { - ERR_FAIL_COND_V_MSG(pattern == p_pattern, -1, "TileSet has already this pattern."); - } - ERR_FAIL_COND_V(p_index > (int)patterns.size(), -1); - if (p_index < 0) { - p_index = patterns.size(); - } - patterns.insert(p_index, p_pattern); - emit_changed(); - return p_index; -} - -Ref TileSet::get_pattern(int p_index) { - ERR_FAIL_INDEX_V(p_index, (int)patterns.size(), Ref()); - return patterns[p_index]; -} - -void TileSet::remove_pattern(int p_index) { - ERR_FAIL_INDEX(p_index, (int)patterns.size()); - patterns.remove_at(p_index); - emit_changed(); -} - -int TileSet::get_patterns_count() { - return patterns.size(); -} - -RBSet TileSet::get_terrains_pattern_set(int p_terrain_set) { - ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), RBSet()); - _update_terrains_cache(); - - RBSet output; - for (KeyValue> kv : per_terrain_pattern_tiles[p_terrain_set]) { - output.insert(kv.key); - } - return output; -} - -RBSet TileSet::get_tiles_for_terrains_pattern(int p_terrain_set, TerrainsPattern p_terrain_tile_pattern) { - ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), RBSet()); - _update_terrains_cache(); - return per_terrain_pattern_tiles[p_terrain_set][p_terrain_tile_pattern]; -} - -TileMapCell TileSet::get_random_tile_from_terrains_pattern(int p_terrain_set, TileSet::TerrainsPattern p_terrain_tile_pattern) { - ERR_FAIL_INDEX_V(p_terrain_set, terrain_sets.size(), TileMapCell()); - _update_terrains_cache(); - - // Count the sum of probabilities. - double sum = 0.0; - RBSet set = per_terrain_pattern_tiles[p_terrain_set][p_terrain_tile_pattern]; - for (const TileMapCell &E : set) { - if (E.source_id >= 0) { - Ref source = sources[E.source_id]; - Ref atlas_source = source; - if (atlas_source.is_valid()) { - TileData *tile_data = atlas_source->get_tile_data(E.get_atlas_coords(), E.alternative_tile); - sum += tile_data->get_probability(); - } else { - sum += 1.0; - } - } else { - sum += 1.0; - } - } - - // Generate a random number. - double count = 0.0; - double picked = Math::random(0.0, sum); - - // Pick the tile. - for (const TileMapCell &E : set) { - if (E.source_id >= 0) { - Ref source = sources[E.source_id]; - - Ref atlas_source = source; - if (atlas_source.is_valid()) { - TileData *tile_data = atlas_source->get_tile_data(E.get_atlas_coords(), E.alternative_tile); - count += tile_data->get_probability(); - } else { - count += 1.0; - } - } else { - count += 1.0; - } - - if (count >= picked) { - return E; - } - } - - ERR_FAIL_V(TileMapCell()); -} - -Vector TileSet::get_tile_shape_polygon() const { - Vector points; - if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { - points.push_back(Vector2(-0.5, -0.5)); - points.push_back(Vector2(0.5, -0.5)); - points.push_back(Vector2(0.5, 0.5)); - points.push_back(Vector2(-0.5, 0.5)); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { - points.push_back(Vector2(0.0, -0.5)); - points.push_back(Vector2(-0.5, 0.0)); - points.push_back(Vector2(0.0, 0.5)); - points.push_back(Vector2(0.5, 0.0)); - } else { - float overlap = 0.0; - switch (tile_shape) { - case TileSet::TILE_SHAPE_HEXAGON: - overlap = 0.25; - break; - case TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE: - overlap = 0.0; - break; - default: - break; - } - - points.push_back(Vector2(0.0, -0.5)); - points.push_back(Vector2(-0.5, overlap - 0.5)); - points.push_back(Vector2(-0.5, 0.5 - overlap)); - points.push_back(Vector2(0.0, 0.5)); - points.push_back(Vector2(0.5, 0.5 - overlap)); - points.push_back(Vector2(0.5, overlap - 0.5)); - - if (get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL) { - for (int i = 0; i < points.size(); i++) { - points.write[i] = Vector2(points[i].y, points[i].x); - } - } - } - return points; -} - -void TileSet::draw_tile_shape(CanvasItem *p_canvas_item, Transform2D p_transform, Color p_color, bool p_filled, Ref p_texture) const { - if (tile_meshes_dirty) { - Vector shape = get_tile_shape_polygon(); - Vector uvs; - uvs.resize(shape.size()); - for (int i = 0; i < shape.size(); i++) { - uvs.write[i] = shape[i] + Vector2(0.5, 0.5); - } - - Vector colors; - colors.resize(shape.size()); - colors.fill(Color(1.0, 1.0, 1.0, 1.0)); - - // Filled mesh. - tile_filled_mesh->clear_surfaces(); - Array a; - a.resize(Mesh::ARRAY_MAX); - a[Mesh::ARRAY_VERTEX] = shape; - a[Mesh::ARRAY_TEX_UV] = uvs; - a[Mesh::ARRAY_COLOR] = colors; - a[Mesh::ARRAY_INDEX] = Geometry2D::triangulate_polygon(shape); - tile_filled_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a, Array(), Dictionary(), Mesh::ARRAY_FLAG_USE_2D_VERTICES); - - // Lines mesh. - tile_lines_mesh->clear_surfaces(); - a.clear(); - a.resize(Mesh::ARRAY_MAX); - // Add the first point again when drawing lines. - shape.push_back(shape[0]); - colors.push_back(colors[0]); - a[Mesh::ARRAY_VERTEX] = shape; - a[Mesh::ARRAY_COLOR] = colors; - tile_lines_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_LINE_STRIP, a, Array(), Dictionary(), Mesh::ARRAY_FLAG_USE_2D_VERTICES); - - tile_meshes_dirty = false; - } - - if (p_filled) { - p_canvas_item->draw_mesh(tile_filled_mesh, p_texture, p_transform, p_color); - } else { - p_canvas_item->draw_mesh(tile_lines_mesh, Ref(), p_transform, p_color); - } -} - -Vector2 TileSet::map_to_local(const Vector2i &p_pos) const { - // SHOULD RETURN THE CENTER OF THE CELL. - Vector2 ret = p_pos; - - if (tile_shape == TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE || tile_shape == TileSet::TILE_SHAPE_HEXAGON || tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { - // Technically, those 3 shapes are equivalent, as they are basically half-offset, but with different levels or overlap. - // square = no overlap, hexagon = 0.25 overlap, isometric = 0.5 overlap. - if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - switch (tile_layout) { - case TileSet::TILE_LAYOUT_STACKED: - ret = Vector2(ret.x + (Math::posmod(ret.y, 2) == 0 ? 0.0 : 0.5), ret.y); - break; - case TileSet::TILE_LAYOUT_STACKED_OFFSET: - ret = Vector2(ret.x + (Math::posmod(ret.y, 2) == 1 ? 0.0 : 0.5), ret.y); - break; - case TileSet::TILE_LAYOUT_STAIRS_RIGHT: - ret = Vector2(ret.x + ret.y / 2, ret.y); - break; - case TileSet::TILE_LAYOUT_STAIRS_DOWN: - ret = Vector2(ret.x / 2, ret.y * 2 + ret.x); - break; - case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: - ret = Vector2((ret.x + ret.y) / 2, ret.y - ret.x); - break; - case TileSet::TILE_LAYOUT_DIAMOND_DOWN: - ret = Vector2((ret.x - ret.y) / 2, ret.y + ret.x); - break; - } - } else { // TILE_OFFSET_AXIS_VERTICAL. - switch (tile_layout) { - case TileSet::TILE_LAYOUT_STACKED: - ret = Vector2(ret.x, ret.y + (Math::posmod(ret.x, 2) == 0 ? 0.0 : 0.5)); - break; - case TileSet::TILE_LAYOUT_STACKED_OFFSET: - ret = Vector2(ret.x, ret.y + (Math::posmod(ret.x, 2) == 1 ? 0.0 : 0.5)); - break; - case TileSet::TILE_LAYOUT_STAIRS_RIGHT: - ret = Vector2(ret.x * 2 + ret.y, ret.y / 2); - break; - case TileSet::TILE_LAYOUT_STAIRS_DOWN: - ret = Vector2(ret.x, ret.y + ret.x / 2); - break; - case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: - ret = Vector2(ret.x + ret.y, (ret.y - ret.x) / 2); - break; - case TileSet::TILE_LAYOUT_DIAMOND_DOWN: - ret = Vector2(ret.x - ret.y, (ret.y + ret.x) / 2); - break; - } - } - } - - // Multiply by the overlapping ratio. - double overlapping_ratio = 1.0; - if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { - overlapping_ratio = 0.5; - } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) { - overlapping_ratio = 0.75; - } - ret.y *= overlapping_ratio; - } else { // TILE_OFFSET_AXIS_VERTICAL. - if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { - overlapping_ratio = 0.5; - } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) { - overlapping_ratio = 0.75; - } - ret.x *= overlapping_ratio; - } - - return (ret + Vector2(0.5, 0.5)) * tile_size; -} - -Vector2i TileSet::local_to_map(const Vector2 &p_local_position) const { - Vector2 ret = p_local_position; - ret /= tile_size; - - // Divide by the overlapping ratio. - double overlapping_ratio = 1.0; - if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { - overlapping_ratio = 0.5; - } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) { - overlapping_ratio = 0.75; - } - ret.y /= overlapping_ratio; - } else { // TILE_OFFSET_AXIS_VERTICAL. - if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { - overlapping_ratio = 0.5; - } else if (tile_shape == TileSet::TILE_SHAPE_HEXAGON) { - overlapping_ratio = 0.75; - } - ret.x /= overlapping_ratio; - } - - // For each half-offset shape, we check if we are in the corner of the tile, and thus should correct the local position accordingly. - if (tile_shape == TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE || tile_shape == TileSet::TILE_SHAPE_HEXAGON || tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { - // Technically, those 3 shapes are equivalent, as they are basically half-offset, but with different levels or overlap. - // square = no overlap, hexagon = 0.25 overlap, isometric = 0.5 overlap. - if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - // Smart floor of the position - Vector2 raw_pos = ret; - if (Math::posmod(Math::floor(ret.y), 2) ^ (tile_layout == TileSet::TILE_LAYOUT_STACKED_OFFSET)) { - ret = Vector2(Math::floor(ret.x + 0.5) - 0.5, Math::floor(ret.y)); - } else { - ret = ret.floor(); - } - - // Compute the tile offset, and if we might the output for a neighbor top tile. - Vector2 in_tile_pos = raw_pos - ret; - bool in_top_left_triangle = (in_tile_pos - Vector2(0.5, 0.0)).cross(Vector2(-0.5, 1.0 / overlapping_ratio - 1)) <= 0; - bool in_top_right_triangle = (in_tile_pos - Vector2(0.5, 0.0)).cross(Vector2(0.5, 1.0 / overlapping_ratio - 1)) > 0; - - switch (tile_layout) { - case TileSet::TILE_LAYOUT_STACKED: - ret = ret.floor(); - if (in_top_left_triangle) { - ret += Vector2i(Math::posmod(Math::floor(ret.y), 2) ? 0 : -1, -1); - } else if (in_top_right_triangle) { - ret += Vector2i(Math::posmod(Math::floor(ret.y), 2) ? 1 : 0, -1); - } - break; - case TileSet::TILE_LAYOUT_STACKED_OFFSET: - ret = ret.floor(); - if (in_top_left_triangle) { - ret += Vector2i(Math::posmod(Math::floor(ret.y), 2) ? -1 : 0, -1); - } else if (in_top_right_triangle) { - ret += Vector2i(Math::posmod(Math::floor(ret.y), 2) ? 0 : 1, -1); - } - break; - case TileSet::TILE_LAYOUT_STAIRS_RIGHT: - ret = Vector2(ret.x - ret.y / 2, ret.y).floor(); - if (in_top_left_triangle) { - ret += Vector2i(0, -1); - } else if (in_top_right_triangle) { - ret += Vector2i(1, -1); - } - break; - case TileSet::TILE_LAYOUT_STAIRS_DOWN: - ret = Vector2(ret.x * 2, ret.y / 2 - ret.x).floor(); - if (in_top_left_triangle) { - ret += Vector2i(-1, 0); - } else if (in_top_right_triangle) { - ret += Vector2i(1, -1); - } - break; - case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: - ret = Vector2(ret.x - ret.y / 2, ret.y / 2 + ret.x).floor(); - if (in_top_left_triangle) { - ret += Vector2i(0, -1); - } else if (in_top_right_triangle) { - ret += Vector2i(1, 0); - } - break; - case TileSet::TILE_LAYOUT_DIAMOND_DOWN: - ret = Vector2(ret.x + ret.y / 2, ret.y / 2 - ret.x).floor(); - if (in_top_left_triangle) { - ret += Vector2i(-1, 0); - } else if (in_top_right_triangle) { - ret += Vector2i(0, -1); - } - break; - } - } else { // TILE_OFFSET_AXIS_VERTICAL. - // Smart floor of the position. - Vector2 raw_pos = ret; - if (Math::posmod(Math::floor(ret.x), 2) ^ (tile_layout == TileSet::TILE_LAYOUT_STACKED_OFFSET)) { - ret = Vector2(Math::floor(ret.x), Math::floor(ret.y + 0.5) - 0.5); - } else { - ret = ret.floor(); - } - - // Compute the tile offset, and if we might the output for a neighbor top tile. - Vector2 in_tile_pos = raw_pos - ret; - bool in_top_left_triangle = (in_tile_pos - Vector2(0.0, 0.5)).cross(Vector2(1.0 / overlapping_ratio - 1, -0.5)) > 0; - bool in_bottom_left_triangle = (in_tile_pos - Vector2(0.0, 0.5)).cross(Vector2(1.0 / overlapping_ratio - 1, 0.5)) <= 0; - - switch (tile_layout) { - case TileSet::TILE_LAYOUT_STACKED: - ret = ret.floor(); - if (in_top_left_triangle) { - ret += Vector2i(-1, Math::posmod(Math::floor(ret.x), 2) ? 0 : -1); - } else if (in_bottom_left_triangle) { - ret += Vector2i(-1, Math::posmod(Math::floor(ret.x), 2) ? 1 : 0); - } - break; - case TileSet::TILE_LAYOUT_STACKED_OFFSET: - ret = ret.floor(); - if (in_top_left_triangle) { - ret += Vector2i(-1, Math::posmod(Math::floor(ret.x), 2) ? -1 : 0); - } else if (in_bottom_left_triangle) { - ret += Vector2i(-1, Math::posmod(Math::floor(ret.x), 2) ? 0 : 1); - } - break; - case TileSet::TILE_LAYOUT_STAIRS_RIGHT: - ret = Vector2(ret.x / 2 - ret.y, ret.y * 2).floor(); - if (in_top_left_triangle) { - ret += Vector2i(0, -1); - } else if (in_bottom_left_triangle) { - ret += Vector2i(-1, 1); - } - break; - case TileSet::TILE_LAYOUT_STAIRS_DOWN: - ret = Vector2(ret.x, ret.y - ret.x / 2).floor(); - if (in_top_left_triangle) { - ret += Vector2i(-1, 0); - } else if (in_bottom_left_triangle) { - ret += Vector2i(-1, 1); - } - break; - case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: - ret = Vector2(ret.x / 2 - ret.y, ret.y + ret.x / 2).floor(); - if (in_top_left_triangle) { - ret += Vector2i(0, -1); - } else if (in_bottom_left_triangle) { - ret += Vector2i(-1, 0); - } - break; - case TileSet::TILE_LAYOUT_DIAMOND_DOWN: - ret = Vector2(ret.x / 2 + ret.y, ret.y - ret.x / 2).floor(); - if (in_top_left_triangle) { - ret += Vector2i(-1, 0); - } else if (in_bottom_left_triangle) { - ret += Vector2i(0, 1); - } - break; - } - } - } else { - ret = (ret + Vector2(0.00005, 0.00005)).floor(); - } - return Vector2i(ret); -} - -bool TileSet::is_existing_neighbor(TileSet::CellNeighbor p_cell_neighbor) const { - if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { - return p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER; - - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { - return p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; - } else { - if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - return p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; - } else { - return p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE || - p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; - } - } -} - -Vector2i TileSet::get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeighbor p_cell_neighbor) const { - if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { - switch (p_cell_neighbor) { - case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: - return p_coords + Vector2i(1, 0); - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: - return p_coords + Vector2i(1, 1); - case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: - return p_coords + Vector2i(0, 1); - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: - return p_coords + Vector2i(-1, 1); - case TileSet::CELL_NEIGHBOR_LEFT_SIDE: - return p_coords + Vector2i(-1, 0); - case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: - return p_coords + Vector2i(-1, -1); - case TileSet::CELL_NEIGHBOR_TOP_SIDE: - return p_coords + Vector2i(0, -1); - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: - return p_coords + Vector2i(1, -1); - default: - ERR_FAIL_V(p_coords); - } - } else { // Half-offset shapes (square and hexagon). - switch (tile_layout) { - case TileSet::TILE_LAYOUT_STACKED: { - if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - bool is_offset = p_coords.y % 2; - if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || - (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { - return p_coords + Vector2i(1, 0); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { - return p_coords + Vector2i(is_offset ? 1 : 0, 1); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { - return p_coords + Vector2i(0, 2); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { - return p_coords + Vector2i(is_offset ? 0 : -1, 1); - } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || - (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { - return p_coords + Vector2i(-1, 0); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { - return p_coords + Vector2i(is_offset ? 0 : -1, -1); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { - return p_coords + Vector2i(0, -2); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return p_coords + Vector2i(is_offset ? 1 : 0, -1); - } else { - ERR_FAIL_V(p_coords); - } - } else { - bool is_offset = p_coords.x % 2; - - if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || - (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { - return p_coords + Vector2i(0, 1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { - return p_coords + Vector2i(1, is_offset ? 1 : 0); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { - return p_coords + Vector2i(2, 0); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return p_coords + Vector2i(1, is_offset ? 0 : -1); - } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || - (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { - return p_coords + Vector2i(0, -1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { - return p_coords + Vector2i(-1, is_offset ? 0 : -1); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { - return p_coords + Vector2i(-2, 0); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { - return p_coords + Vector2i(-1, is_offset ? 1 : 0); - } else { - ERR_FAIL_V(p_coords); - } - } - } break; - case TileSet::TILE_LAYOUT_STACKED_OFFSET: { - if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - bool is_offset = p_coords.y % 2; - - if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || - (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { - return p_coords + Vector2i(1, 0); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { - return p_coords + Vector2i(is_offset ? 0 : 1, 1); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { - return p_coords + Vector2i(0, 2); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { - return p_coords + Vector2i(is_offset ? -1 : 0, 1); - } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || - (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { - return p_coords + Vector2i(-1, 0); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { - return p_coords + Vector2i(is_offset ? -1 : 0, -1); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { - return p_coords + Vector2i(0, -2); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return p_coords + Vector2i(is_offset ? 0 : 1, -1); - } else { - ERR_FAIL_V(p_coords); - } - } else { - bool is_offset = p_coords.x % 2; - - if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || - (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { - return p_coords + Vector2i(0, 1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { - return p_coords + Vector2i(1, is_offset ? 0 : 1); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { - return p_coords + Vector2i(2, 0); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return p_coords + Vector2i(1, is_offset ? -1 : 0); - } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || - (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { - return p_coords + Vector2i(0, -1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { - return p_coords + Vector2i(-1, is_offset ? -1 : 0); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { - return p_coords + Vector2i(-2, 0); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { - return p_coords + Vector2i(-1, is_offset ? 0 : 1); - } else { - ERR_FAIL_V(p_coords); - } - } - } break; - case TileSet::TILE_LAYOUT_STAIRS_RIGHT: - case TileSet::TILE_LAYOUT_STAIRS_DOWN: { - if ((tile_layout == TileSet::TILE_LAYOUT_STAIRS_RIGHT) ^ (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { - if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || - (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { - return p_coords + Vector2i(1, 0); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { - return p_coords + Vector2i(0, 1); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { - return p_coords + Vector2i(-1, 2); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { - return p_coords + Vector2i(-1, 1); - } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || - (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { - return p_coords + Vector2i(-1, 0); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { - return p_coords + Vector2i(0, -1); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { - return p_coords + Vector2i(1, -2); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return p_coords + Vector2i(1, -1); - } else { - ERR_FAIL_V(p_coords); - } - - } else { - if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || - (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { - return p_coords + Vector2i(0, 1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { - return p_coords + Vector2i(1, 0); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { - return p_coords + Vector2i(2, -1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return p_coords + Vector2i(1, -1); - } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || - (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { - return p_coords + Vector2i(0, -1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { - return p_coords + Vector2i(-1, 0); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { - return p_coords + Vector2i(-2, 1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { - return p_coords + Vector2i(-1, 1); - } else { - ERR_FAIL_V(p_coords); - } - } - } else { - if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || - (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { - return p_coords + Vector2i(2, -1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { - return p_coords + Vector2i(1, 0); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { - return p_coords + Vector2i(0, 1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { - return p_coords + Vector2i(-1, 1); - } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || - (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { - return p_coords + Vector2i(-2, 1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { - return p_coords + Vector2i(-1, 0); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { - return p_coords + Vector2i(0, -1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return p_coords + Vector2i(1, -1); - } else { - ERR_FAIL_V(p_coords); - } - - } else { - if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || - (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { - return p_coords + Vector2i(-1, 2); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { - return p_coords + Vector2i(0, 1); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { - return p_coords + Vector2i(1, 0); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return p_coords + Vector2i(1, -1); - } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || - (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { - return p_coords + Vector2i(1, -2); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { - return p_coords + Vector2i(0, -1); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { - return p_coords + Vector2i(-1, 0); - - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { - return p_coords + Vector2i(-1, 1); - } else { - ERR_FAIL_V(p_coords); - } - } - } - } break; - case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: - case TileSet::TILE_LAYOUT_DIAMOND_DOWN: { - if ((tile_layout == TileSet::TILE_LAYOUT_DIAMOND_RIGHT) ^ (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { - if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || - (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { - return p_coords + Vector2i(1, 1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { - return p_coords + Vector2i(0, 1); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { - return p_coords + Vector2i(-1, 1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { - return p_coords + Vector2i(-1, 0); - } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || - (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { - return p_coords + Vector2i(-1, -1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { - return p_coords + Vector2i(0, -1); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { - return p_coords + Vector2i(1, -1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return p_coords + Vector2i(1, 0); - } else { - ERR_FAIL_V(p_coords); - } - - } else { - if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || - (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { - return p_coords + Vector2i(1, 1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { - return p_coords + Vector2i(1, 0); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { - return p_coords + Vector2i(1, -1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return p_coords + Vector2i(0, -1); - } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || - (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { - return p_coords + Vector2i(-1, -1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { - return p_coords + Vector2i(-1, 0); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { - return p_coords + Vector2i(-1, 1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { - return p_coords + Vector2i(0, 1); - } else { - ERR_FAIL_V(p_coords); - } - } - } else { - if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) || - (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { - return p_coords + Vector2i(1, -1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { - return p_coords + Vector2i(1, 0); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) { - return p_coords + Vector2i(1, 1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { - return p_coords + Vector2i(0, 1); - } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) || - (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { - return p_coords + Vector2i(-1, 1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { - return p_coords + Vector2i(-1, 0); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) { - return p_coords + Vector2i(-1, -1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return p_coords + Vector2i(0, -1); - } else { - ERR_FAIL_V(p_coords); - } - - } else { - if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_CORNER) || - (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { - return p_coords + Vector2i(-1, 1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE) { - return p_coords + Vector2i(0, 1); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_RIGHT_CORNER) { - return p_coords + Vector2i(1, 1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE) { - return p_coords + Vector2i(1, 0); - } else if ((tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_CORNER) || - (tile_shape != TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_SIDE)) { - return p_coords + Vector2i(1, -1); - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE) { - return p_coords + Vector2i(0, -1); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC && p_cell_neighbor == TileSet::CELL_NEIGHBOR_LEFT_CORNER) { - return p_coords + Vector2i(-1, -1); - - } else if (p_cell_neighbor == TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE) { - return p_coords + Vector2i(-1, 0); - } else { - ERR_FAIL_V(p_coords); - } - } - } - } break; - default: - ERR_FAIL_V(p_coords); - } - } -} - -TypedArray TileSet::get_surrounding_cells(const Vector2i &p_coords) const { - TypedArray around; - if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { - around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)); - around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)); - around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_LEFT_SIDE)); - around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_TOP_SIDE)); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { - around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)); - around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)); - around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE)); - around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)); - } else { - if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)); - around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)); - around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)); - around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_LEFT_SIDE)); - around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE)); - around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)); - } else { - around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)); - around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)); - around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)); - around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE)); - around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_TOP_SIDE)); - around.push_back(get_neighbor_cell(p_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)); - } - } - - return around; -} - -Vector2i TileSet::map_pattern(const Vector2i &p_position_in_tilemap, const Vector2i &p_coords_in_pattern, Ref p_pattern) const { - ERR_FAIL_COND_V(p_pattern.is_null(), Vector2i()); - ERR_FAIL_COND_V(!p_pattern->has_cell(p_coords_in_pattern), Vector2i()); - - Vector2i output = p_position_in_tilemap + p_coords_in_pattern; - if (tile_shape != TileSet::TILE_SHAPE_SQUARE) { - if (tile_layout == TileSet::TILE_LAYOUT_STACKED) { - if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL && bool(p_position_in_tilemap.y % 2) && bool(p_coords_in_pattern.y % 2)) { - output.x += 1; - } else if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(p_position_in_tilemap.x % 2) && bool(p_coords_in_pattern.x % 2)) { - output.y += 1; - } - } else if (tile_layout == TileSet::TILE_LAYOUT_STACKED_OFFSET) { - if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL && bool(p_position_in_tilemap.y % 2) && bool(p_coords_in_pattern.y % 2)) { - output.x -= 1; - } else if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(p_position_in_tilemap.x % 2) && bool(p_coords_in_pattern.x % 2)) { - output.y -= 1; - } - } - } - - return output; -} - -void TileSet::draw_cells_outline(CanvasItem *p_canvas_item, const RBSet &p_cells, Color p_color, Transform2D p_transform) const { - Vector polygon = get_tile_shape_polygon(); - for (const Vector2i &E : p_cells) { - Vector2 center = map_to_local(E); - -#define DRAW_SIDE_IF_NEEDED(side, polygon_index_from, polygon_index_to) \ - if (!p_cells.has(get_neighbor_cell(E, side))) { \ - Vector2 from = p_transform.xform(center + polygon[polygon_index_from] * tile_size); \ - Vector2 to = p_transform.xform(center + polygon[polygon_index_to] * tile_size); \ - p_canvas_item->draw_line(from, to, p_color); \ - } - - if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { - DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_RIGHT_SIDE, 1, 2); - DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE, 2, 3); - DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_LEFT_SIDE, 3, 0); - DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_SIDE, 0, 1); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { - DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, 2, 3); - DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, 1, 2); - DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE, 0, 1); - DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE, 3, 0); - } else { - if (tile_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, 3, 4); - DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, 2, 3); - DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_LEFT_SIDE, 1, 2); - DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE, 0, 1); - DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE, 5, 0); - DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_RIGHT_SIDE, 4, 5); - } else { - DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, 3, 4); - DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE, 4, 5); - DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, 5, 0); - DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE, 0, 1); - DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_SIDE, 1, 2); - DRAW_SIDE_IF_NEEDED(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE, 2, 3); - } - } - } -#undef DRAW_SIDE_IF_NEEDED -} - -Vector TileSet::get_terrain_polygon(int p_terrain_set) { - if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { - return _get_square_terrain_polygon(tile_size); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { - return _get_isometric_terrain_polygon(tile_size); - } else { - float overlap = 0.0; - switch (tile_shape) { - case TileSet::TILE_SHAPE_HEXAGON: - overlap = 0.25; - break; - case TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE: - overlap = 0.0; - break; - default: - break; - } - return _get_half_offset_terrain_polygon(tile_size, overlap, tile_offset_axis); - } -} - -Vector TileSet::get_terrain_peering_bit_polygon(int p_terrain_set, TileSet::CellNeighbor p_bit) { - ERR_FAIL_COND_V(p_terrain_set < 0 || p_terrain_set >= get_terrain_sets_count(), Vector()); - - TileSet::TerrainMode terrain_mode = get_terrain_set_mode(p_terrain_set); - - if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { - if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { - return _get_square_corner_or_side_terrain_peering_bit_polygon(tile_size, p_bit); - } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { - return _get_square_corner_terrain_peering_bit_polygon(tile_size, p_bit); - } else { // TileData::TERRAIN_MODE_MATCH_SIDES - return _get_square_side_terrain_peering_bit_polygon(tile_size, p_bit); - } - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { - if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { - return _get_isometric_corner_or_side_terrain_peering_bit_polygon(tile_size, p_bit); - } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { - return _get_isometric_corner_terrain_peering_bit_polygon(tile_size, p_bit); - } else { // TileData::TERRAIN_MODE_MATCH_SIDES - return _get_isometric_side_terrain_peering_bit_polygon(tile_size, p_bit); - } - } else { - float overlap = 0.0; - switch (tile_shape) { - case TileSet::TILE_SHAPE_HEXAGON: - overlap = 0.25; - break; - case TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE: - overlap = 0.0; - break; - default: - break; - } - if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { - return _get_half_offset_corner_or_side_terrain_peering_bit_polygon(tile_size, overlap, tile_offset_axis, p_bit); - } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { - return _get_half_offset_corner_terrain_peering_bit_polygon(tile_size, overlap, tile_offset_axis, p_bit); - } else { // TileData::TERRAIN_MODE_MATCH_SIDES - return _get_half_offset_side_terrain_peering_bit_polygon(tile_size, overlap, tile_offset_axis, p_bit); - } - } -} - -#define TERRAIN_ALPHA 0.6 - -void TileSet::draw_terrains(CanvasItem *p_canvas_item, Transform2D p_transform, const TileData *p_tile_data) { - ERR_FAIL_NULL(p_tile_data); - - if (terrain_bits_meshes_dirty) { - // Recompute the meshes. - terrain_peering_bits_meshes.clear(); - - for (int terrain_mode_index = 0; terrain_mode_index < 3; terrain_mode_index++) { - TerrainMode terrain_mode = TerrainMode(terrain_mode_index); - - // Center terrain - Vector polygon; - if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { - polygon = _get_square_terrain_polygon(tile_size); - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { - polygon = _get_isometric_terrain_polygon(tile_size); - } else { - float overlap = 0.0; - switch (tile_shape) { - case TileSet::TILE_SHAPE_HEXAGON: - overlap = 0.25; - break; - case TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE: - overlap = 0.0; - break; - default: - break; - } - polygon = _get_half_offset_terrain_polygon(tile_size, overlap, tile_offset_axis); - } - { - Ref mesh; - mesh.instantiate(); - Vector uvs; - uvs.resize(polygon.size()); - Vector colors; - colors.resize(polygon.size()); - colors.fill(Color(1.0, 1.0, 1.0, 1.0)); - Array a; - a.resize(Mesh::ARRAY_MAX); - a[Mesh::ARRAY_VERTEX] = polygon; - a[Mesh::ARRAY_TEX_UV] = uvs; - a[Mesh::ARRAY_COLOR] = colors; - a[Mesh::ARRAY_INDEX] = Geometry2D::triangulate_polygon(polygon); - mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a, Array(), Dictionary(), Mesh::ARRAY_FLAG_USE_2D_VERTICES); - terrain_meshes[terrain_mode] = mesh; - } - // Peering bits - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - CellNeighbor bit = CellNeighbor(i); - - if (is_valid_terrain_peering_bit_for_mode(terrain_mode, bit)) { - if (tile_shape == TileSet::TILE_SHAPE_SQUARE) { - if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { - polygon = _get_square_corner_or_side_terrain_peering_bit_polygon(tile_size, bit); - } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { - polygon = _get_square_corner_terrain_peering_bit_polygon(tile_size, bit); - } else { // TileData::TERRAIN_MODE_MATCH_SIDES - polygon = _get_square_side_terrain_peering_bit_polygon(tile_size, bit); - } - } else if (tile_shape == TileSet::TILE_SHAPE_ISOMETRIC) { - if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { - polygon = _get_isometric_corner_or_side_terrain_peering_bit_polygon(tile_size, bit); - } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { - polygon = _get_isometric_corner_terrain_peering_bit_polygon(tile_size, bit); - } else { // TileData::TERRAIN_MODE_MATCH_SIDES - polygon = _get_isometric_side_terrain_peering_bit_polygon(tile_size, bit); - } - } else { - float overlap = 0.0; - switch (tile_shape) { - case TileSet::TILE_SHAPE_HEXAGON: - overlap = 0.25; - break; - case TileSet::TILE_SHAPE_HALF_OFFSET_SQUARE: - overlap = 0.0; - break; - default: - break; - } - if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { - polygon = _get_half_offset_corner_or_side_terrain_peering_bit_polygon(tile_size, overlap, tile_offset_axis, bit); - } else if (terrain_mode == TileSet::TERRAIN_MODE_MATCH_CORNERS) { - polygon = _get_half_offset_corner_terrain_peering_bit_polygon(tile_size, overlap, tile_offset_axis, bit); - } else { // TileData::TERRAIN_MODE_MATCH_SIDES - polygon = _get_half_offset_side_terrain_peering_bit_polygon(tile_size, overlap, tile_offset_axis, bit); - } - } - { - Ref mesh; - mesh.instantiate(); - Vector uvs; - uvs.resize(polygon.size()); - Vector colors; - colors.resize(polygon.size()); - colors.fill(Color(1.0, 1.0, 1.0, 1.0)); - Array a; - a.resize(Mesh::ARRAY_MAX); - a[Mesh::ARRAY_VERTEX] = polygon; - a[Mesh::ARRAY_TEX_UV] = uvs; - a[Mesh::ARRAY_COLOR] = colors; - a[Mesh::ARRAY_INDEX] = Geometry2D::triangulate_polygon(polygon); - mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a, Array(), Dictionary(), Mesh::ARRAY_FLAG_USE_2D_VERTICES); - terrain_peering_bits_meshes[terrain_mode][bit] = mesh; - } - } - } - } - terrain_bits_meshes_dirty = false; - } - - int terrain_set = p_tile_data->get_terrain_set(); - if (terrain_set < 0) { - return; - } - TileSet::TerrainMode terrain_mode = get_terrain_set_mode(terrain_set); - - RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform); - int terrain_id = p_tile_data->get_terrain(); - if (terrain_id >= 0) { - Color color = get_terrain_color(terrain_set, terrain_id); - color.a = TERRAIN_ALPHA; - p_canvas_item->draw_mesh(terrain_meshes[terrain_mode], Ref(), Transform2D(), color); - } - - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - CellNeighbor bit = CellNeighbor(i); - if (is_valid_terrain_peering_bit(terrain_set, bit)) { - terrain_id = p_tile_data->get_terrain_peering_bit(bit); - if (terrain_id >= 0) { - Color color = get_terrain_color(terrain_set, terrain_id); - color.a = TERRAIN_ALPHA; - p_canvas_item->draw_mesh(terrain_peering_bits_meshes[terrain_mode][bit], Ref(), Transform2D(), color); - } - } - } - RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D()); -} - -Vector>> TileSet::generate_terrains_icons(Size2i p_size) { - // Counts the number of matching terrain tiles and find the best matching icon. - struct Count { - int count = 0; - float probability = 0.0; - Ref texture; - Rect2i region; - }; - Vector>> output; - LocalVector> counts; - output.resize(get_terrain_sets_count()); - counts.resize(get_terrain_sets_count()); - for (int terrain_set = 0; terrain_set < get_terrain_sets_count(); terrain_set++) { - output.write[terrain_set].resize(get_terrains_count(terrain_set)); - counts[terrain_set].resize(get_terrains_count(terrain_set)); - } - - for (int source_index = 0; source_index < get_source_count(); source_index++) { - int source_id = get_source_id(source_index); - Ref source = get_source(source_id); - - Ref atlas_source = source; - if (atlas_source.is_valid()) { - for (int tile_index = 0; tile_index < source->get_tiles_count(); tile_index++) { - Vector2i tile_id = source->get_tile_id(tile_index); - for (int alternative_index = 0; alternative_index < source->get_alternative_tiles_count(tile_id); alternative_index++) { - int alternative_id = source->get_alternative_tile_id(tile_id, alternative_index); - - TileData *tile_data = atlas_source->get_tile_data(tile_id, alternative_id); - int terrain_set = tile_data->get_terrain_set(); - if (terrain_set >= 0) { - ERR_FAIL_INDEX_V(terrain_set, get_terrain_sets_count(), Vector>>()); - - LocalVector bit_counts; - bit_counts.resize(get_terrains_count(terrain_set)); - for (int terrain = 0; terrain < get_terrains_count(terrain_set); terrain++) { - bit_counts[terrain] = 0; - } - if (tile_data->get_terrain() >= 0) { - bit_counts[tile_data->get_terrain()] += 10; - } - for (int terrain_bit = 0; terrain_bit < TileSet::CELL_NEIGHBOR_MAX; terrain_bit++) { - TileSet::CellNeighbor cell_neighbor = TileSet::CellNeighbor(terrain_bit); - if (is_valid_terrain_peering_bit(terrain_set, cell_neighbor)) { - int terrain = tile_data->get_terrain_peering_bit(cell_neighbor); - if (terrain >= 0) { - if (terrain >= (int)bit_counts.size()) { - WARN_PRINT(vformat("Invalid terrain peering bit: %d", terrain)); - } else { - bit_counts[terrain] += 1; - } - } - } - } - - for (int terrain = 0; terrain < get_terrains_count(terrain_set); terrain++) { - if ((bit_counts[terrain] > counts[terrain_set][terrain].count) || (bit_counts[terrain] == counts[terrain_set][terrain].count && tile_data->get_probability() > counts[terrain_set][terrain].probability)) { - counts[terrain_set][terrain].count = bit_counts[terrain]; - counts[terrain_set][terrain].probability = tile_data->get_probability(); - counts[terrain_set][terrain].texture = atlas_source->get_texture(); - counts[terrain_set][terrain].region = atlas_source->get_tile_texture_region(tile_id); - } - } - } - } - } - } - } - - // Generate the icons. - for (int terrain_set = 0; terrain_set < get_terrain_sets_count(); terrain_set++) { - for (int terrain = 0; terrain < get_terrains_count(terrain_set); terrain++) { - Ref dst_image; - dst_image.instantiate(); - if (counts[terrain_set][terrain].count > 0) { - // Get the best tile. - Ref src_texture = counts[terrain_set][terrain].texture; - ERR_FAIL_COND_V(src_texture.is_null(), output); - Ref src_image = src_texture->get_image(); - ERR_FAIL_COND_V(src_image.is_null(), output); - Rect2i region = counts[terrain_set][terrain].region; - - dst_image->initialize_data(region.size.x, region.size.y, false, src_image->get_format()); - dst_image->blit_rect(src_image, region, Point2i()); - dst_image->convert(Image::FORMAT_RGBA8); - dst_image->resize(p_size.x, p_size.y, Image::INTERPOLATE_NEAREST); - } else { - dst_image->initialize_data(1, 1, false, Image::FORMAT_RGBA8); - dst_image->set_pixel(0, 0, get_terrain_color(terrain_set, terrain)); - } - Ref icon = ImageTexture::create_from_image(dst_image); - icon->set_size_override(p_size); - output.write[terrain_set].write[terrain] = icon; - } - } - return output; -} - -void TileSet::_source_changed() { - terrains_cache_dirty = true; - emit_changed(); -} - -Vector TileSet::_get_square_terrain_polygon(Vector2i p_size) { - Rect2 rect(-Vector2(p_size) / 6.0, Vector2(p_size) / 3.0); - return { - rect.position, - Vector2(rect.get_end().x, rect.position.y), - rect.get_end(), - Vector2(rect.position.x, rect.get_end().y) - }; -} - -Vector TileSet::_get_square_corner_or_side_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { - Rect2 bit_rect; - bit_rect.size = Vector2(p_size) / 3; - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: - bit_rect.position = Vector2(1, -1); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: - bit_rect.position = Vector2(1, 1); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: - bit_rect.position = Vector2(-1, 1); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: - bit_rect.position = Vector2(-3, 1); - break; - case TileSet::CELL_NEIGHBOR_LEFT_SIDE: - bit_rect.position = Vector2(-3, -1); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: - bit_rect.position = Vector2(-3, -3); - break; - case TileSet::CELL_NEIGHBOR_TOP_SIDE: - bit_rect.position = Vector2(-1, -3); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: - bit_rect.position = Vector2(1, -3); - break; - default: - break; - } - bit_rect.position *= Vector2(p_size) / 6.0; - - Vector polygon = { - bit_rect.position, - Vector2(bit_rect.get_end().x, bit_rect.position.y), - bit_rect.get_end(), - Vector2(bit_rect.position.x, bit_rect.get_end().y) - }; - - return polygon; -} - -Vector TileSet::_get_square_corner_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { - Vector2 unit = Vector2(p_size) / 6.0; - Vector polygon; - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: - polygon.push_back(Vector2(0, 3) * unit); - polygon.push_back(Vector2(3, 3) * unit); - polygon.push_back(Vector2(3, 0) * unit); - polygon.push_back(Vector2(1, 0) * unit); - polygon.push_back(Vector2(1, 1) * unit); - polygon.push_back(Vector2(0, 1) * unit); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: - polygon.push_back(Vector2(0, 3) * unit); - polygon.push_back(Vector2(-3, 3) * unit); - polygon.push_back(Vector2(-3, 0) * unit); - polygon.push_back(Vector2(-1, 0) * unit); - polygon.push_back(Vector2(-1, 1) * unit); - polygon.push_back(Vector2(0, 1) * unit); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: - polygon.push_back(Vector2(0, -3) * unit); - polygon.push_back(Vector2(-3, -3) * unit); - polygon.push_back(Vector2(-3, 0) * unit); - polygon.push_back(Vector2(-1, 0) * unit); - polygon.push_back(Vector2(-1, -1) * unit); - polygon.push_back(Vector2(0, -1) * unit); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: - polygon.push_back(Vector2(0, -3) * unit); - polygon.push_back(Vector2(3, -3) * unit); - polygon.push_back(Vector2(3, 0) * unit); - polygon.push_back(Vector2(1, 0) * unit); - polygon.push_back(Vector2(1, -1) * unit); - polygon.push_back(Vector2(0, -1) * unit); - break; - default: - break; - } - return polygon; -} - -Vector TileSet::_get_square_side_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { - Vector2 unit = Vector2(p_size) / 6.0; - Vector polygon; - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: - polygon.push_back(Vector2(1, -1) * unit); - polygon.push_back(Vector2(3, -3) * unit); - polygon.push_back(Vector2(3, 3) * unit); - polygon.push_back(Vector2(1, 1) * unit); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: - polygon.push_back(Vector2(-1, 1) * unit); - polygon.push_back(Vector2(-3, 3) * unit); - polygon.push_back(Vector2(3, 3) * unit); - polygon.push_back(Vector2(1, 1) * unit); - break; - case TileSet::CELL_NEIGHBOR_LEFT_SIDE: - polygon.push_back(Vector2(-1, -1) * unit); - polygon.push_back(Vector2(-3, -3) * unit); - polygon.push_back(Vector2(-3, 3) * unit); - polygon.push_back(Vector2(-1, 1) * unit); - break; - case TileSet::CELL_NEIGHBOR_TOP_SIDE: - polygon.push_back(Vector2(-1, -1) * unit); - polygon.push_back(Vector2(-3, -3) * unit); - polygon.push_back(Vector2(3, -3) * unit); - polygon.push_back(Vector2(1, -1) * unit); - break; - default: - break; - } - return polygon; -} - -Vector TileSet::_get_isometric_terrain_polygon(Vector2i p_size) { - Vector2 unit = Vector2(p_size) / 6.0; - return { - Vector2(1, 0) * unit, - Vector2(0, 1) * unit, - Vector2(-1, 0) * unit, - Vector2(0, -1) * unit, - }; -} - -Vector TileSet::_get_isometric_corner_or_side_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { - Vector2 unit = Vector2(p_size) / 6.0; - Vector polygon; - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: - polygon.push_back(Vector2(1, 0) * unit); - polygon.push_back(Vector2(2, -1) * unit); - polygon.push_back(Vector2(3, 0) * unit); - polygon.push_back(Vector2(2, 1) * unit); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: - polygon.push_back(Vector2(0, 1) * unit); - polygon.push_back(Vector2(1, 2) * unit); - polygon.push_back(Vector2(2, 1) * unit); - polygon.push_back(Vector2(1, 0) * unit); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: - polygon.push_back(Vector2(0, 1) * unit); - polygon.push_back(Vector2(-1, 2) * unit); - polygon.push_back(Vector2(0, 3) * unit); - polygon.push_back(Vector2(1, 2) * unit); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: - polygon.push_back(Vector2(0, 1) * unit); - polygon.push_back(Vector2(-1, 2) * unit); - polygon.push_back(Vector2(-2, 1) * unit); - polygon.push_back(Vector2(-1, 0) * unit); - break; - case TileSet::CELL_NEIGHBOR_LEFT_CORNER: - polygon.push_back(Vector2(-1, 0) * unit); - polygon.push_back(Vector2(-2, -1) * unit); - polygon.push_back(Vector2(-3, 0) * unit); - polygon.push_back(Vector2(-2, 1) * unit); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: - polygon.push_back(Vector2(0, -1) * unit); - polygon.push_back(Vector2(-1, -2) * unit); - polygon.push_back(Vector2(-2, -1) * unit); - polygon.push_back(Vector2(-1, 0) * unit); - break; - case TileSet::CELL_NEIGHBOR_TOP_CORNER: - polygon.push_back(Vector2(0, -1) * unit); - polygon.push_back(Vector2(-1, -2) * unit); - polygon.push_back(Vector2(0, -3) * unit); - polygon.push_back(Vector2(1, -2) * unit); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: - polygon.push_back(Vector2(0, -1) * unit); - polygon.push_back(Vector2(1, -2) * unit); - polygon.push_back(Vector2(2, -1) * unit); - polygon.push_back(Vector2(1, 0) * unit); - break; - default: - break; - } - return polygon; -} - -Vector TileSet::_get_isometric_corner_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { - Vector2 unit = Vector2(p_size) / 6.0; - Vector polygon; - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: - polygon.push_back(Vector2(0.5, -0.5) * unit); - polygon.push_back(Vector2(1.5, -1.5) * unit); - polygon.push_back(Vector2(3, 0) * unit); - polygon.push_back(Vector2(1.5, 1.5) * unit); - polygon.push_back(Vector2(0.5, 0.5) * unit); - polygon.push_back(Vector2(1, 0) * unit); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: - polygon.push_back(Vector2(-0.5, 0.5) * unit); - polygon.push_back(Vector2(-1.5, 1.5) * unit); - polygon.push_back(Vector2(0, 3) * unit); - polygon.push_back(Vector2(1.5, 1.5) * unit); - polygon.push_back(Vector2(0.5, 0.5) * unit); - polygon.push_back(Vector2(0, 1) * unit); - break; - case TileSet::CELL_NEIGHBOR_LEFT_CORNER: - polygon.push_back(Vector2(-0.5, -0.5) * unit); - polygon.push_back(Vector2(-1.5, -1.5) * unit); - polygon.push_back(Vector2(-3, 0) * unit); - polygon.push_back(Vector2(-1.5, 1.5) * unit); - polygon.push_back(Vector2(-0.5, 0.5) * unit); - polygon.push_back(Vector2(-1, 0) * unit); - break; - case TileSet::CELL_NEIGHBOR_TOP_CORNER: - polygon.push_back(Vector2(-0.5, -0.5) * unit); - polygon.push_back(Vector2(-1.5, -1.5) * unit); - polygon.push_back(Vector2(0, -3) * unit); - polygon.push_back(Vector2(1.5, -1.5) * unit); - polygon.push_back(Vector2(0.5, -0.5) * unit); - polygon.push_back(Vector2(0, -1) * unit); - break; - default: - break; - } - return polygon; -} - -Vector TileSet::_get_isometric_side_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit) { - Vector2 unit = Vector2(p_size) / 6.0; - Vector polygon; - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: - polygon.push_back(Vector2(1, 0) * unit); - polygon.push_back(Vector2(3, 0) * unit); - polygon.push_back(Vector2(0, 3) * unit); - polygon.push_back(Vector2(0, 1) * unit); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: - polygon.push_back(Vector2(-1, 0) * unit); - polygon.push_back(Vector2(-3, 0) * unit); - polygon.push_back(Vector2(0, 3) * unit); - polygon.push_back(Vector2(0, 1) * unit); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: - polygon.push_back(Vector2(-1, 0) * unit); - polygon.push_back(Vector2(-3, 0) * unit); - polygon.push_back(Vector2(0, -3) * unit); - polygon.push_back(Vector2(0, -1) * unit); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: - polygon.push_back(Vector2(1, 0) * unit); - polygon.push_back(Vector2(3, 0) * unit); - polygon.push_back(Vector2(0, -3) * unit); - polygon.push_back(Vector2(0, -1) * unit); - break; - default: - break; - } - return polygon; -} - -Vector TileSet::_get_half_offset_terrain_polygon(Vector2i p_size, float p_overlap, TileSet::TileOffsetAxis p_offset_axis) { - Vector2 unit = Vector2(p_size) / 6.0; - if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - return { - Vector2(1, 1.0 - p_overlap * 2.0) * unit, - Vector2(0, 1) * unit, - Vector2(-1, 1.0 - p_overlap * 2.0) * unit, - Vector2(-1, -1.0 + p_overlap * 2.0) * unit, - Vector2(0, -1) * unit, - Vector2(1, -1.0 + p_overlap * 2.0) * unit, - }; - } else { - return { - Vector2(1, 0) * unit, - Vector2(1.0 - p_overlap * 2.0, -1) * unit, - Vector2(-1.0 + p_overlap * 2.0, -1) * unit, - Vector2(-1, 0) * unit, - Vector2(-1.0 + p_overlap * 2.0, 1) * unit, - Vector2(1.0 - p_overlap * 2.0, 1) * unit, - }; - } -} - -Vector TileSet::_get_half_offset_corner_or_side_terrain_peering_bit_polygon(Vector2i p_size, float p_overlap, TileSet::TileOffsetAxis p_offset_axis, TileSet::CellNeighbor p_bit) { - Vector point_list = { - Vector2(3, (3.0 * (1.0 - p_overlap * 2.0)) / 2.0), - Vector2(3, 3.0 * (1.0 - p_overlap * 2.0)), - Vector2(2, 3.0 * (1.0 - (p_overlap * 2.0) * 2.0 / 3.0)), - Vector2(1, 3.0 - p_overlap * 2.0), - Vector2(0, 3), - Vector2(-1, 3.0 - p_overlap * 2.0), - Vector2(-2, 3.0 * (1.0 - (p_overlap * 2.0) * 2.0 / 3.0)), - Vector2(-3, 3.0 * (1.0 - p_overlap * 2.0)), - Vector2(-3, (3.0 * (1.0 - p_overlap * 2.0)) / 2.0), - Vector2(-3, -(3.0 * (1.0 - p_overlap * 2.0)) / 2.0), - Vector2(-3, -3.0 * (1.0 - p_overlap * 2.0)), - Vector2(-2, -3.0 * (1.0 - (p_overlap * 2.0) * 2.0 / 3.0)), - Vector2(-1, -(3.0 - p_overlap * 2.0)), - Vector2(0, -3), - Vector2(1, -(3.0 - p_overlap * 2.0)), - Vector2(2, -3.0 * (1.0 - (p_overlap * 2.0) * 2.0 / 3.0)), - Vector2(3, -3.0 * (1.0 - p_overlap * 2.0)), - Vector2(3, -(3.0 * (1.0 - p_overlap * 2.0)) / 2.0) - }; - - Vector2 unit = Vector2(p_size) / 6.0; - Vector polygon; - if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - for (int i = 0; i < point_list.size(); i++) { - point_list.write[i] = point_list[i] * unit; - } - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: - polygon.push_back(point_list[17]); - polygon.push_back(point_list[0]); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: - polygon.push_back(point_list[0]); - polygon.push_back(point_list[1]); - polygon.push_back(point_list[2]); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: - polygon.push_back(point_list[2]); - polygon.push_back(point_list[3]); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: - polygon.push_back(point_list[3]); - polygon.push_back(point_list[4]); - polygon.push_back(point_list[5]); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: - polygon.push_back(point_list[5]); - polygon.push_back(point_list[6]); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: - polygon.push_back(point_list[6]); - polygon.push_back(point_list[7]); - polygon.push_back(point_list[8]); - break; - case TileSet::CELL_NEIGHBOR_LEFT_SIDE: - polygon.push_back(point_list[8]); - polygon.push_back(point_list[9]); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: - polygon.push_back(point_list[9]); - polygon.push_back(point_list[10]); - polygon.push_back(point_list[11]); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: - polygon.push_back(point_list[11]); - polygon.push_back(point_list[12]); - break; - case TileSet::CELL_NEIGHBOR_TOP_CORNER: - polygon.push_back(point_list[12]); - polygon.push_back(point_list[13]); - polygon.push_back(point_list[14]); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: - polygon.push_back(point_list[14]); - polygon.push_back(point_list[15]); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: - polygon.push_back(point_list[15]); - polygon.push_back(point_list[16]); - polygon.push_back(point_list[17]); - break; - default: - break; - } - } else { - for (int i = 0; i < point_list.size(); i++) { - point_list.write[i] = Vector2(point_list[i].y, point_list[i].x) * unit; - } - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: - polygon.push_back(point_list[3]); - polygon.push_back(point_list[4]); - polygon.push_back(point_list[5]); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: - polygon.push_back(point_list[2]); - polygon.push_back(point_list[3]); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: - polygon.push_back(point_list[0]); - polygon.push_back(point_list[1]); - polygon.push_back(point_list[2]); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: - polygon.push_back(point_list[17]); - polygon.push_back(point_list[0]); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: - polygon.push_back(point_list[15]); - polygon.push_back(point_list[16]); - polygon.push_back(point_list[17]); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: - polygon.push_back(point_list[14]); - polygon.push_back(point_list[15]); - break; - case TileSet::CELL_NEIGHBOR_LEFT_CORNER: - polygon.push_back(point_list[12]); - polygon.push_back(point_list[13]); - polygon.push_back(point_list[14]); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: - polygon.push_back(point_list[11]); - polygon.push_back(point_list[12]); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: - polygon.push_back(point_list[9]); - polygon.push_back(point_list[10]); - polygon.push_back(point_list[11]); - break; - case TileSet::CELL_NEIGHBOR_TOP_SIDE: - polygon.push_back(point_list[8]); - polygon.push_back(point_list[9]); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: - polygon.push_back(point_list[6]); - polygon.push_back(point_list[7]); - polygon.push_back(point_list[8]); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: - polygon.push_back(point_list[5]); - polygon.push_back(point_list[6]); - break; - default: - break; - } - } - - int half_polygon_size = polygon.size(); - for (int i = 0; i < half_polygon_size; i++) { - polygon.push_back(polygon[half_polygon_size - 1 - i] / 3.0); - } - - return polygon; -} - -Vector TileSet::_get_half_offset_corner_terrain_peering_bit_polygon(Vector2i p_size, float p_overlap, TileSet::TileOffsetAxis p_offset_axis, TileSet::CellNeighbor p_bit) { - Vector point_list = { - Vector2(3, 0), - Vector2(3, 3.0 * (1.0 - p_overlap * 2.0)), - Vector2(1.5, (3.0 * (1.0 - p_overlap * 2.0) + 3.0) / 2.0), - Vector2(0, 3), - Vector2(-1.5, (3.0 * (1.0 - p_overlap * 2.0) + 3.0) / 2.0), - Vector2(-3, 3.0 * (1.0 - p_overlap * 2.0)), - Vector2(-3, 0), - Vector2(-3, -3.0 * (1.0 - p_overlap * 2.0)), - Vector2(-1.5, -(3.0 * (1.0 - p_overlap * 2.0) + 3.0) / 2.0), - Vector2(0, -3), - Vector2(1.5, -(3.0 * (1.0 - p_overlap * 2.0) + 3.0) / 2.0), - Vector2(3, -3.0 * (1.0 - p_overlap * 2.0)) - }; - - Vector2 unit = Vector2(p_size) / 6.0; - Vector polygon; - if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - for (int i = 0; i < point_list.size(); i++) { - point_list.write[i] = point_list[i] * unit; - } - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: - polygon.push_back(point_list[0]); - polygon.push_back(point_list[1]); - polygon.push_back(point_list[2]); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: - polygon.push_back(point_list[2]); - polygon.push_back(point_list[3]); - polygon.push_back(point_list[4]); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: - polygon.push_back(point_list[4]); - polygon.push_back(point_list[5]); - polygon.push_back(point_list[6]); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: - polygon.push_back(point_list[6]); - polygon.push_back(point_list[7]); - polygon.push_back(point_list[8]); - break; - case TileSet::CELL_NEIGHBOR_TOP_CORNER: - polygon.push_back(point_list[8]); - polygon.push_back(point_list[9]); - polygon.push_back(point_list[10]); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: - polygon.push_back(point_list[10]); - polygon.push_back(point_list[11]); - polygon.push_back(point_list[0]); - break; - default: - break; - } - } else { - for (int i = 0; i < point_list.size(); i++) { - point_list.write[i] = Vector2(point_list[i].y, point_list[i].x) * unit; - } - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: - polygon.push_back(point_list[2]); - polygon.push_back(point_list[3]); - polygon.push_back(point_list[4]); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: - polygon.push_back(point_list[0]); - polygon.push_back(point_list[1]); - polygon.push_back(point_list[2]); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: - polygon.push_back(point_list[10]); - polygon.push_back(point_list[11]); - polygon.push_back(point_list[0]); - break; - case TileSet::CELL_NEIGHBOR_LEFT_CORNER: - polygon.push_back(point_list[8]); - polygon.push_back(point_list[9]); - polygon.push_back(point_list[10]); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: - polygon.push_back(point_list[6]); - polygon.push_back(point_list[7]); - polygon.push_back(point_list[8]); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: - polygon.push_back(point_list[4]); - polygon.push_back(point_list[5]); - polygon.push_back(point_list[6]); - break; - default: - break; - } - } - - int half_polygon_size = polygon.size(); - for (int i = 0; i < half_polygon_size; i++) { - polygon.push_back(polygon[half_polygon_size - 1 - i] / 3.0); - } - - return polygon; -} - -Vector TileSet::_get_half_offset_side_terrain_peering_bit_polygon(Vector2i p_size, float p_overlap, TileSet::TileOffsetAxis p_offset_axis, TileSet::CellNeighbor p_bit) { - Vector point_list = { - Vector2(3, 3.0 * (1.0 - p_overlap * 2.0)), - Vector2(0, 3), - Vector2(-3, 3.0 * (1.0 - p_overlap * 2.0)), - Vector2(-3, -3.0 * (1.0 - p_overlap * 2.0)), - Vector2(0, -3), - Vector2(3, -3.0 * (1.0 - p_overlap * 2.0)) - }; - - Vector2 unit = Vector2(p_size) / 6.0; - Vector polygon; - if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - for (int i = 0; i < point_list.size(); i++) { - point_list.write[i] = point_list[i] * unit; - } - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: - polygon.push_back(point_list[5]); - polygon.push_back(point_list[0]); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: - polygon.push_back(point_list[0]); - polygon.push_back(point_list[1]); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: - polygon.push_back(point_list[1]); - polygon.push_back(point_list[2]); - break; - case TileSet::CELL_NEIGHBOR_LEFT_SIDE: - polygon.push_back(point_list[2]); - polygon.push_back(point_list[3]); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: - polygon.push_back(point_list[3]); - polygon.push_back(point_list[4]); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: - polygon.push_back(point_list[4]); - polygon.push_back(point_list[5]); - break; - default: - break; - } - } else { - for (int i = 0; i < point_list.size(); i++) { - point_list.write[i] = Vector2(point_list[i].y, point_list[i].x) * unit; - } - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: - polygon.push_back(point_list[0]); - polygon.push_back(point_list[1]); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: - polygon.push_back(point_list[5]); - polygon.push_back(point_list[0]); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: - polygon.push_back(point_list[4]); - polygon.push_back(point_list[5]); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: - polygon.push_back(point_list[3]); - polygon.push_back(point_list[4]); - break; - case TileSet::CELL_NEIGHBOR_TOP_SIDE: - polygon.push_back(point_list[2]); - polygon.push_back(point_list[3]); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: - polygon.push_back(point_list[1]); - polygon.push_back(point_list[2]); - break; - default: - break; - } - } - - int half_polygon_size = polygon.size(); - for (int i = 0; i < half_polygon_size; i++) { - polygon.push_back(polygon[half_polygon_size - 1 - i] / 3.0); - } - - return polygon; -} - -void TileSet::reset_state() { - // Rendering - occlusion_layers.clear(); - tile_lines_mesh.instantiate(); - tile_filled_mesh.instantiate(); - tile_meshes_dirty = true; - - // Physics - physics_layers.clear(); - - // Terrains - terrain_sets.clear(); - terrain_meshes.clear(); - terrain_peering_bits_meshes.clear(); - per_terrain_pattern_tiles.clear(); - terrains_cache_dirty = true; - - // Navigation - navigation_layers.clear(); - - custom_data_layers.clear(); - custom_data_layers_by_name.clear(); - - // Proxies - source_level_proxies.clear(); - coords_level_proxies.clear(); - alternative_level_proxies.clear(); - -#ifndef DISABLE_DEPRECATED - for (const KeyValue &E : compatibility_data) { - memdelete(E.value); - } - compatibility_data.clear(); -#endif // DISABLE_DEPRECATED - while (!source_ids.is_empty()) { - remove_source(source_ids[0]); - } - - tile_shape = TILE_SHAPE_SQUARE; - tile_layout = TILE_LAYOUT_STACKED; - tile_offset_axis = TILE_OFFSET_AXIS_HORIZONTAL; - tile_size = Size2i(16, 16); -} - -Vector2i TileSet::transform_coords_layout(const Vector2i &p_coords, TileSet::TileOffsetAxis p_offset_axis, TileSet::TileLayout p_from_layout, TileSet::TileLayout p_to_layout) { - // Transform to stacked layout. - Vector2i output = p_coords; - if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) { - SWAP(output.x, output.y); - } - switch (p_from_layout) { - case TileSet::TILE_LAYOUT_STACKED: - break; - case TileSet::TILE_LAYOUT_STACKED_OFFSET: - if (output.y % 2) { - output.x -= 1; - } - break; - case TileSet::TILE_LAYOUT_STAIRS_RIGHT: - case TileSet::TILE_LAYOUT_STAIRS_DOWN: - if ((p_from_layout == TileSet::TILE_LAYOUT_STAIRS_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { - if (output.y < 0 && bool(output.y % 2)) { - output = Vector2i(output.x + output.y / 2 - 1, output.y); - } else { - output = Vector2i(output.x + output.y / 2, output.y); - } - } else { - if (output.x < 0 && bool(output.x % 2)) { - output = Vector2i(output.x / 2 - 1, output.x + output.y * 2); - } else { - output = Vector2i(output.x / 2, output.x + output.y * 2); - } - } - break; - case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: - case TileSet::TILE_LAYOUT_DIAMOND_DOWN: - if ((p_from_layout == TileSet::TILE_LAYOUT_DIAMOND_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { - if ((output.x + output.y) < 0 && (output.x - output.y) % 2) { - output = Vector2i((output.x + output.y) / 2 - 1, output.y - output.x); - } else { - output = Vector2i((output.x + output.y) / 2, -output.x + output.y); - } - } else { - if ((output.x - output.y) < 0 && (output.x + output.y) % 2) { - output = Vector2i((output.x - output.y) / 2 - 1, output.x + output.y); - } else { - output = Vector2i((output.x - output.y) / 2, output.x + output.y); - } - } - break; - } - - switch (p_to_layout) { - case TileSet::TILE_LAYOUT_STACKED: - break; - case TileSet::TILE_LAYOUT_STACKED_OFFSET: - if (output.y % 2) { - output.x += 1; - } - break; - case TileSet::TILE_LAYOUT_STAIRS_RIGHT: - case TileSet::TILE_LAYOUT_STAIRS_DOWN: - if ((p_to_layout == TileSet::TILE_LAYOUT_STAIRS_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { - if (output.y < 0 && (output.y % 2)) { - output = Vector2i(output.x - output.y / 2 + 1, output.y); - } else { - output = Vector2i(output.x - output.y / 2, output.y); - } - } else { - if (output.y % 2) { - if (output.y < 0) { - output = Vector2i(2 * output.x + 1, -output.x + output.y / 2 - 1); - } else { - output = Vector2i(2 * output.x + 1, -output.x + output.y / 2); - } - } else { - output = Vector2i(2 * output.x, -output.x + output.y / 2); - } - } - break; - case TileSet::TILE_LAYOUT_DIAMOND_RIGHT: - case TileSet::TILE_LAYOUT_DIAMOND_DOWN: - if ((p_to_layout == TileSet::TILE_LAYOUT_DIAMOND_RIGHT) ^ (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL)) { - if (output.y % 2) { - if (output.y > 0) { - output = Vector2i(output.x - output.y / 2, output.x + output.y / 2 + 1); - } else { - output = Vector2i(output.x - output.y / 2 + 1, output.x + output.y / 2); - } - } else { - output = Vector2i(output.x - output.y / 2, output.x + output.y / 2); - } - } else { - if (output.y % 2) { - if (output.y < 0) { - output = Vector2i(output.x + output.y / 2, -output.x + output.y / 2 - 1); - } else { - output = Vector2i(output.x + output.y / 2 + 1, -output.x + output.y / 2); - } - } else { - output = Vector2i(output.x + output.y / 2, -output.x + output.y / 2); - } - } - break; - } - - if (p_offset_axis == TileSet::TILE_OFFSET_AXIS_VERTICAL) { - SWAP(output.x, output.y); - } - - return output; -} - -const Vector2i TileSetSource::INVALID_ATLAS_COORDS = Vector2i(-1, -1); -const int TileSetSource::INVALID_TILE_ALTERNATIVE = -1; - -#ifndef DISABLE_DEPRECATED -void TileSet::_compatibility_conversion() { - for (KeyValue &E : compatibility_data) { - CompatibilityTileData *ctd = E.value; - - // Add the texture - TileSetAtlasSource *atlas_source = memnew(TileSetAtlasSource); - int source_id = add_source(Ref(atlas_source)); - - atlas_source->set_texture(ctd->texture); - - // Handle each tile as a new source. Not optimal but at least it should stay compatible. - switch (ctd->tile_mode) { - case COMPATIBILITY_TILE_MODE_SINGLE_TILE: { - atlas_source->set_margins(ctd->region.get_position()); - atlas_source->set_texture_region_size(ctd->region.get_size()); - - Vector2i coords; - for (int flags = 0; flags < 8; flags++) { - bool flip_h = flags & 1; - bool flip_v = flags & 2; - bool transpose = flags & 4; - - Transform2D xform; - xform = flip_h ? xform.scaled(Size2(-1, 1)) : xform; - xform = flip_v ? xform.scaled(Size2(1, -1)) : xform; - xform = transpose ? Transform2D(xform[1], xform[0], Vector2()) : xform; - - int alternative_tile = 0; - if (!atlas_source->has_tile(coords)) { - atlas_source->create_tile(coords); - } else { - alternative_tile = atlas_source->create_alternative_tile(coords); - } - - // Add to the mapping. - Array key_array; - key_array.push_back(flip_h); - key_array.push_back(flip_v); - key_array.push_back(transpose); - - Array value_array; - value_array.push_back(source_id); - value_array.push_back(coords); - value_array.push_back(alternative_tile); - - if (!compatibility_tilemap_mapping.has(E.key)) { - compatibility_tilemap_mapping[E.key] = RBMap(); - } - compatibility_tilemap_mapping[E.key][key_array] = value_array; - compatibility_tilemap_mapping_tile_modes[E.key] = COMPATIBILITY_TILE_MODE_SINGLE_TILE; - - TileData *tile_data = atlas_source->get_tile_data(coords, alternative_tile); - ERR_CONTINUE(!tile_data); - - tile_data->set_flip_h(flip_h); - tile_data->set_flip_v(flip_v); - tile_data->set_transpose(transpose); - tile_data->set_material(ctd->material); - tile_data->set_modulate(ctd->modulate); - tile_data->set_z_index(ctd->z_index); - - if (ctd->occluder.is_valid()) { - if (get_occlusion_layers_count() < 1) { - add_occlusion_layer(); - }; - Ref occluder = ctd->occluder->duplicate(); - Vector polygon = ctd->occluder->get_polygon(); - for (int index = 0; index < polygon.size(); index++) { - polygon.write[index] = xform.xform(polygon[index] - ctd->region.get_size() / 2.0); - } - occluder->set_polygon(polygon); - tile_data->set_occluder(0, occluder); - } - if (ctd->navigation.is_valid()) { - if (get_navigation_layers_count() < 1) { - add_navigation_layer(); - } - Ref navigation = ctd->navigation->duplicate(); - Vector vertices = navigation->get_vertices(); - for (int index = 0; index < vertices.size(); index++) { - vertices.write[index] = xform.xform(vertices[index] - ctd->region.get_size() / 2.0); - } - navigation->set_vertices(vertices); - tile_data->set_navigation_polygon(0, navigation); - } - - tile_data->set_z_index(ctd->z_index); - - // Add the shapes. - if (ctd->shapes.size() > 0) { - if (get_physics_layers_count() < 1) { - add_physics_layer(); - } - } - for (int k = 0; k < ctd->shapes.size(); k++) { - CompatibilityShapeData csd = ctd->shapes[k]; - if (csd.autotile_coords == coords) { - Ref convex_shape = csd.shape; // Only ConvexPolygonShape2D are supported, which is the default type used by the 3.x editor - if (convex_shape.is_valid()) { - Vector polygon = convex_shape->get_points(); - for (int point_index = 0; point_index < polygon.size(); point_index++) { - polygon.write[point_index] = xform.xform(csd.transform.xform(polygon[point_index]) - ctd->region.get_size() / 2.0); - } - tile_data->set_collision_polygons_count(0, tile_data->get_collision_polygons_count(0) + 1); - int index = tile_data->get_collision_polygons_count(0) - 1; - tile_data->set_collision_polygon_one_way(0, index, csd.one_way); - tile_data->set_collision_polygon_one_way_margin(0, index, csd.one_way_margin); - tile_data->set_collision_polygon_points(0, index, polygon); - } - } - } - } - // Update the size count. - if (!compatibility_size_count.has(ctd->region.get_size())) { - compatibility_size_count[ctd->region.get_size()] = 0; - } - compatibility_size_count[ctd->region.get_size()]++; - } break; - case COMPATIBILITY_TILE_MODE_AUTO_TILE: { - // Not supported. It would need manual conversion. - WARN_PRINT_ONCE("Could not convert 3.x autotiles to 4.x. This operation cannot be done automatically, autotiles must be re-created using the terrain system."); - } break; - case COMPATIBILITY_TILE_MODE_ATLAS_TILE: { - atlas_source->set_margins(ctd->region.get_position()); - atlas_source->set_separation(Vector2i(ctd->autotile_spacing, ctd->autotile_spacing)); - atlas_source->set_texture_region_size(ctd->autotile_tile_size); - - Size2i atlas_size = ctd->region.get_size() / (ctd->autotile_tile_size + atlas_source->get_separation()); - for (int i = 0; i < atlas_size.x; i++) { - for (int j = 0; j < atlas_size.y; j++) { - Vector2i coords = Vector2i(i, j); - - for (int flags = 0; flags < 8; flags++) { - bool flip_h = flags & 1; - bool flip_v = flags & 2; - bool transpose = flags & 4; - - Transform2D xform; - xform = flip_h ? xform.scaled(Size2(-1, 1)) : xform; - xform = flip_v ? xform.scaled(Size2(1, -1)) : xform; - xform = transpose ? Transform2D(xform[1], xform[0], Vector2()) : xform; - - int alternative_tile = 0; - if (!atlas_source->has_tile(coords)) { - atlas_source->create_tile(coords); - } else { - alternative_tile = atlas_source->create_alternative_tile(coords); - } - - // Add to the mapping. - Array key_array; - key_array.push_back(coords); - key_array.push_back(flip_h); - key_array.push_back(flip_v); - key_array.push_back(transpose); - - Array value_array; - value_array.push_back(source_id); - value_array.push_back(coords); - value_array.push_back(alternative_tile); - - if (!compatibility_tilemap_mapping.has(E.key)) { - compatibility_tilemap_mapping[E.key] = RBMap(); - } - compatibility_tilemap_mapping[E.key][key_array] = value_array; - compatibility_tilemap_mapping_tile_modes[E.key] = COMPATIBILITY_TILE_MODE_ATLAS_TILE; - - TileData *tile_data = atlas_source->get_tile_data(coords, alternative_tile); - - tile_data->set_flip_h(flip_h); - tile_data->set_flip_v(flip_v); - tile_data->set_transpose(transpose); - tile_data->set_material(ctd->material); - tile_data->set_modulate(ctd->modulate); - tile_data->set_z_index(ctd->z_index); - if (ctd->autotile_occluder_map.has(coords)) { - if (get_occlusion_layers_count() < 1) { - add_occlusion_layer(); - } - Ref occluder = ctd->autotile_occluder_map[coords]->duplicate(); - Vector polygon = ctd->occluder->get_polygon(); - for (int index = 0; index < polygon.size(); index++) { - polygon.write[index] = xform.xform(polygon[index] - ctd->region.get_size() / 2.0); - } - occluder->set_polygon(polygon); - tile_data->set_occluder(0, occluder); - } - if (ctd->autotile_navpoly_map.has(coords)) { - if (get_navigation_layers_count() < 1) { - add_navigation_layer(); - } - Ref navigation = ctd->autotile_navpoly_map[coords]->duplicate(); - Vector vertices = navigation->get_vertices(); - for (int index = 0; index < vertices.size(); index++) { - vertices.write[index] = xform.xform(vertices[index] - ctd->region.get_size() / 2.0); - } - navigation->set_vertices(vertices); - tile_data->set_navigation_polygon(0, navigation); - } - if (ctd->autotile_priority_map.has(coords)) { - tile_data->set_probability(ctd->autotile_priority_map[coords]); - } - if (ctd->autotile_z_index_map.has(coords)) { - tile_data->set_z_index(ctd->autotile_z_index_map[coords]); - } - - // Add the shapes. - if (ctd->shapes.size() > 0) { - if (get_physics_layers_count() < 1) { - add_physics_layer(); - } - } - for (int k = 0; k < ctd->shapes.size(); k++) { - CompatibilityShapeData csd = ctd->shapes[k]; - if (csd.autotile_coords == coords) { - Ref convex_shape = csd.shape; // Only ConvexPolygonShape2D are supported, which is the default type used by the 3.x editor - if (convex_shape.is_valid()) { - Vector polygon = convex_shape->get_points(); - for (int point_index = 0; point_index < polygon.size(); point_index++) { - polygon.write[point_index] = xform.xform(csd.transform.xform(polygon[point_index]) - ctd->autotile_tile_size / 2.0); - } - tile_data->set_collision_polygons_count(0, tile_data->get_collision_polygons_count(0) + 1); - int index = tile_data->get_collision_polygons_count(0) - 1; - tile_data->set_collision_polygon_one_way(0, index, csd.one_way); - tile_data->set_collision_polygon_one_way_margin(0, index, csd.one_way_margin); - tile_data->set_collision_polygon_points(0, index, polygon); - } - } - } - - // -- TODO: handle -- - // Those are offset for the whole atlas, they are likely useless for the atlases, but might make sense for single tiles. - // texture offset - // occluder_offset - // navigation_offset - - // For terrains, ignored for now? - // bitmask_mode - // bitmask_flags - } - } - } - - // Update the size count. - if (!compatibility_size_count.has(ctd->region.get_size())) { - compatibility_size_count[ctd->autotile_tile_size] = 0; - } - compatibility_size_count[ctd->autotile_tile_size] += atlas_size.x * atlas_size.y; - } break; - } - - // Offset all shapes - for (int k = 0; k < ctd->shapes.size(); k++) { - Ref convex = ctd->shapes[k].shape; - if (convex.is_valid()) { - Vector points = convex->get_points(); - for (int i_point = 0; i_point < points.size(); i_point++) { - points.write[i_point] = points[i_point] - get_tile_size() / 2; - } - convex->set_points(points); - } - } - } - - // Update the TileSet tile_size according to the most common size found. - Vector2i max_size = get_tile_size(); - int max_count = 0; - for (KeyValue kv : compatibility_size_count) { - if (kv.value > max_count) { - max_size = kv.key; - max_count = kv.value; - } - } - set_tile_size(max_size); - - // Reset compatibility data (besides the histogram counts) - for (const KeyValue &E : compatibility_data) { - memdelete(E.value); - } - compatibility_data = HashMap(); -} - -Array TileSet::compatibility_tilemap_map(int p_tile_id, Vector2i p_coords, bool p_flip_h, bool p_flip_v, bool p_transpose) { - Array cannot_convert_array; - cannot_convert_array.push_back(TileSet::INVALID_SOURCE); - cannot_convert_array.push_back(TileSetAtlasSource::INVALID_ATLAS_COORDS); - cannot_convert_array.push_back(TileSetAtlasSource::INVALID_TILE_ALTERNATIVE); - - if (!compatibility_tilemap_mapping.has(p_tile_id)) { - return cannot_convert_array; - } - - int tile_mode = compatibility_tilemap_mapping_tile_modes[p_tile_id]; - switch (tile_mode) { - case COMPATIBILITY_TILE_MODE_SINGLE_TILE: { - Array a; - a.push_back(p_flip_h); - a.push_back(p_flip_v); - a.push_back(p_transpose); - return compatibility_tilemap_mapping[p_tile_id][a]; - } - case COMPATIBILITY_TILE_MODE_AUTO_TILE: - return cannot_convert_array; - break; - case COMPATIBILITY_TILE_MODE_ATLAS_TILE: { - Array a; - a.push_back(p_coords); - a.push_back(p_flip_h); - a.push_back(p_flip_v); - a.push_back(p_transpose); - return compatibility_tilemap_mapping[p_tile_id][a]; - } - default: - return cannot_convert_array; - break; - } -}; - -#endif // DISABLE_DEPRECATED - -bool TileSet::_set(const StringName &p_name, const Variant &p_value) { - Vector components = String(p_name).split("/", true, 2); - -#ifndef DISABLE_DEPRECATED - // TODO: This should be moved to a dedicated conversion system (see #50691) - if (components.size() >= 1 && components[0].is_valid_int()) { - int id = components[0].to_int(); - - // Get or create the compatibility object - CompatibilityTileData *ctd; - HashMap::Iterator E = compatibility_data.find(id); - if (!E) { - ctd = memnew(CompatibilityTileData); - compatibility_data.insert(id, ctd); - } else { - ctd = E->value; - } - - if (components.size() < 2) { - return false; - } - - String what = components[1]; - - if (what == "name") { - ctd->name = p_value; - } else if (what == "texture") { - ctd->texture = p_value; - } else if (what == "tex_offset") { - ctd->tex_offset = p_value; - } else if (what == "material") { - ctd->material = p_value; - } else if (what == "modulate") { - ctd->modulate = p_value; - } else if (what == "region") { - ctd->region = p_value; - } else if (what == "tile_mode") { - ctd->tile_mode = p_value; - } else if (what.left(9) == "autotile") { - what = what.substr(9); - if (what == "bitmask_mode") { - ctd->autotile_bitmask_mode = p_value; - } else if (what == "icon_coordinate") { - ctd->autotile_icon_coordinate = p_value; - } else if (what == "tile_size") { - ctd->autotile_tile_size = p_value; - } else if (what == "spacing") { - ctd->autotile_spacing = p_value; - } else if (what == "bitmask_flags") { - if (p_value.is_array()) { - Array p = p_value; - Vector2i last_coord; - while (p.size() > 0) { - if (p[0].get_type() == Variant::VECTOR2) { - last_coord = p[0]; - } else if (p[0].get_type() == Variant::INT) { - ctd->autotile_bitmask_flags.insert(last_coord, p[0]); - } - p.pop_front(); - } - } - } else if (what == "occluder_map") { - Array p = p_value; - Vector2 last_coord; - while (p.size() > 0) { - if (p[0].get_type() == Variant::VECTOR2) { - last_coord = p[0]; - } else if (p[0].get_type() == Variant::OBJECT) { - ctd->autotile_occluder_map.insert(last_coord, p[0]); - } - p.pop_front(); - } - } else if (what == "navpoly_map") { - Array p = p_value; - Vector2 last_coord; - while (p.size() > 0) { - if (p[0].get_type() == Variant::VECTOR2) { - last_coord = p[0]; - } else if (p[0].get_type() == Variant::OBJECT) { - ctd->autotile_navpoly_map.insert(last_coord, p[0]); - } - p.pop_front(); - } - } else if (what == "priority_map") { - Array p = p_value; - Vector3 val; - Vector2 v; - int priority; - while (p.size() > 0) { - val = p[0]; - if (val.z > 1) { - v.x = val.x; - v.y = val.y; - priority = (int)val.z; - ctd->autotile_priority_map.insert(v, priority); - } - p.pop_front(); - } - } else if (what == "z_index_map") { - Array p = p_value; - Vector3 val; - Vector2 v; - int z_index; - while (p.size() > 0) { - val = p[0]; - if (val.z != 0) { - v.x = val.x; - v.y = val.y; - z_index = (int)val.z; - ctd->autotile_z_index_map.insert(v, z_index); - } - p.pop_front(); - } - } - - } else if (what == "shapes") { - Array p = p_value; - for (int i = 0; i < p.size(); i++) { - CompatibilityShapeData csd; - Dictionary d = p[i]; - for (int j = 0; j < d.size(); j++) { - String key = d.get_key_at_index(j); - if (key == "autotile_coord") { - csd.autotile_coords = d[key]; - } else if (key == "one_way") { - csd.one_way = d[key]; - } else if (key == "one_way_margin") { - csd.one_way_margin = d[key]; - } else if (key == "shape") { - csd.shape = d[key]; - } else if (key == "shape_transform") { - csd.transform = d[key]; - } - } - ctd->shapes.push_back(csd); - } - } else if (what == "occluder") { - ctd->occluder = p_value; - } else if (what == "navigation") { - ctd->navigation = p_value; - - /* - // IGNORED FOR NOW, they seem duplicated data compared to the shapes array - } else if (what == "shape") { - } else if (what == "shape_offset") { - } else if (what == "shape_transform") { - } else if (what == "shape_one_way") { - } else if (what == "shape_one_way_margin") { - } - // IGNORED FOR NOW, maybe useless ? - else if (what == "occluder_offset") { - // Not - } else if (what == "navigation_offset") { - } - */ - - } else if (what == "z_index") { - ctd->z_index = p_value; - - // TODO: remove the conversion from here, it's not where it should be done (see #50691) - _compatibility_conversion(); - } else { - return false; - } - } else { -#endif // DISABLE_DEPRECATED - - // This is now a new property. - if (components.size() == 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) { - // Occlusion layers. - int index = components[0].trim_prefix("occlusion_layer_").to_int(); - ERR_FAIL_COND_V(index < 0, false); - if (components[1] == "light_mask") { - ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - while (index >= occlusion_layers.size()) { - add_occlusion_layer(); - } - set_occlusion_layer_light_mask(index, p_value); - return true; - } else if (components[1] == "sdf_collision") { - ERR_FAIL_COND_V(p_value.get_type() != Variant::BOOL, false); - while (index >= occlusion_layers.size()) { - add_occlusion_layer(); - } - set_occlusion_layer_sdf_collision(index, p_value); - return true; - } - } else if (components.size() == 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) { - // Physics layers. - int index = components[0].trim_prefix("physics_layer_").to_int(); - ERR_FAIL_COND_V(index < 0, false); - if (components[1] == "collision_layer") { - ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - while (index >= physics_layers.size()) { - add_physics_layer(); - } - set_physics_layer_collision_layer(index, p_value); - return true; - } else if (components[1] == "collision_mask") { - ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - while (index >= physics_layers.size()) { - add_physics_layer(); - } - set_physics_layer_collision_mask(index, p_value); - return true; - } else if (components[1] == "physics_material") { - Ref physics_material = p_value; - while (index >= physics_layers.size()) { - add_physics_layer(); - } - set_physics_layer_physics_material(index, physics_material); - return true; - } - } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int()) { - // Terrains. - int terrain_set_index = components[0].trim_prefix("terrain_set_").to_int(); - ERR_FAIL_COND_V(terrain_set_index < 0, false); - if (components[1] == "mode") { - ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - while (terrain_set_index >= terrain_sets.size()) { - add_terrain_set(); - } - set_terrain_set_mode(terrain_set_index, TerrainMode(int(p_value))); - } else if (components.size() >= 3 && components[1].begins_with("terrain_") && components[1].trim_prefix("terrain_").is_valid_int()) { - int terrain_index = components[1].trim_prefix("terrain_").to_int(); - ERR_FAIL_COND_V(terrain_index < 0, false); - if (components[2] == "name") { - ERR_FAIL_COND_V(p_value.get_type() != Variant::STRING, false); - while (terrain_set_index >= terrain_sets.size()) { - add_terrain_set(); - } - while (terrain_index >= terrain_sets[terrain_set_index].terrains.size()) { - add_terrain(terrain_set_index); - } - set_terrain_name(terrain_set_index, terrain_index, p_value); - return true; - } else if (components[2] == "color") { - ERR_FAIL_COND_V(p_value.get_type() != Variant::COLOR, false); - while (terrain_set_index >= terrain_sets.size()) { - add_terrain_set(); - } - while (terrain_index >= terrain_sets[terrain_set_index].terrains.size()) { - add_terrain(terrain_set_index); - } - set_terrain_color(terrain_set_index, terrain_index, p_value); - return true; - } - } - } else if (components.size() == 2 && components[0].begins_with("navigation_layer_") && components[0].trim_prefix("navigation_layer_").is_valid_int()) { - // Navigation layers. - int index = components[0].trim_prefix("navigation_layer_").to_int(); - ERR_FAIL_COND_V(index < 0, false); - if (components[1] == "layers") { - ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - while (index >= navigation_layers.size()) { - add_navigation_layer(); - } - set_navigation_layer_layers(index, p_value); - return true; - } - } else if (components.size() == 2 && components[0].begins_with("custom_data_layer_") && components[0].trim_prefix("custom_data_layer_").is_valid_int()) { - // Custom data layers. - int index = components[0].trim_prefix("custom_data_layer_").to_int(); - ERR_FAIL_COND_V(index < 0, false); - if (components[1] == "name") { - ERR_FAIL_COND_V(p_value.get_type() != Variant::STRING, false); - while (index >= custom_data_layers.size()) { - add_custom_data_layer(); - } - set_custom_data_layer_name(index, p_value); - return true; - } else if (components[1] == "type") { - ERR_FAIL_COND_V(p_value.get_type() != Variant::INT, false); - while (index >= custom_data_layers.size()) { - add_custom_data_layer(); - } - set_custom_data_layer_type(index, Variant::Type(int(p_value))); - return true; - } - } else if (components.size() == 2 && components[0] == "sources" && components[1].is_valid_int()) { - // Create source only if it does not exists. - int source_id = components[1].to_int(); - - if (has_source(source_id)) { - remove_source(source_id); - } - add_source(p_value, source_id); - return true; - } else if (components.size() == 2 && components[0] == "tile_proxies") { - ERR_FAIL_COND_V(p_value.get_type() != Variant::ARRAY, false); - Array a = p_value; - ERR_FAIL_COND_V(a.size() % 2 != 0, false); - if (components[1] == "source_level") { - for (int i = 0; i < a.size(); i += 2) { - set_source_level_tile_proxy(a[i], a[i + 1]); - } - return true; - } else if (components[1] == "coords_level") { - for (int i = 0; i < a.size(); i += 2) { - Array key = a[i]; - Array value = a[i + 1]; - set_coords_level_tile_proxy(key[0], key[1], value[0], value[1]); - } - return true; - } else if (components[1] == "alternative_level") { - for (int i = 0; i < a.size(); i += 2) { - Array key = a[i]; - Array value = a[i + 1]; - set_alternative_level_tile_proxy(key[0], key[1], key[2], value[0], value[1], value[2]); - } - return true; - } - return false; - } else if (components.size() == 1 && components[0].begins_with("pattern_") && components[0].trim_prefix("pattern_").is_valid_int()) { - int pattern_index = components[0].trim_prefix("pattern_").to_int(); - for (int i = patterns.size(); i <= pattern_index; i++) { - add_pattern(p_value); - } - return true; - } - -#ifndef DISABLE_DEPRECATED - } -#endif // DISABLE_DEPRECATED - - return false; -} - -bool TileSet::_get(const StringName &p_name, Variant &r_ret) const { - Vector components = String(p_name).split("/", true, 2); - - if (components.size() == 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) { - // Occlusion layers. - int index = components[0].trim_prefix("occlusion_layer_").to_int(); - if (index < 0 || index >= occlusion_layers.size()) { - return false; - } - if (components[1] == "light_mask") { - r_ret = get_occlusion_layer_light_mask(index); - return true; - } else if (components[1] == "sdf_collision") { - r_ret = get_occlusion_layer_sdf_collision(index); - return true; - } - } else if (components.size() == 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) { - // Physics layers. - int index = components[0].trim_prefix("physics_layer_").to_int(); - if (index < 0 || index >= physics_layers.size()) { - return false; - } - if (components[1] == "collision_layer") { - r_ret = get_physics_layer_collision_layer(index); - return true; - } else if (components[1] == "collision_mask") { - r_ret = get_physics_layer_collision_mask(index); - return true; - } else if (components[1] == "physics_material") { - r_ret = get_physics_layer_physics_material(index); - return true; - } - } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int()) { - // Terrains. - int terrain_set_index = components[0].trim_prefix("terrain_set_").to_int(); - if (terrain_set_index < 0 || terrain_set_index >= terrain_sets.size()) { - return false; - } - if (components[1] == "mode") { - r_ret = get_terrain_set_mode(terrain_set_index); - return true; - } else if (components.size() >= 3 && components[1].begins_with("terrain_") && components[1].trim_prefix("terrain_").is_valid_int()) { - int terrain_index = components[1].trim_prefix("terrain_").to_int(); - if (terrain_index < 0 || terrain_index >= terrain_sets[terrain_set_index].terrains.size()) { - return false; - } - if (components[2] == "name") { - r_ret = get_terrain_name(terrain_set_index, terrain_index); - return true; - } else if (components[2] == "color") { - r_ret = get_terrain_color(terrain_set_index, terrain_index); - return true; - } - } - } else if (components.size() == 2 && components[0].begins_with("navigation_layer_") && components[0].trim_prefix("navigation_layer_").is_valid_int()) { - // navigation layers. - int index = components[0].trim_prefix("navigation_layer_").to_int(); - if (index < 0 || index >= navigation_layers.size()) { - return false; - } - if (components[1] == "layers") { - r_ret = get_navigation_layer_layers(index); - return true; - } - } else if (components.size() == 2 && components[0].begins_with("custom_data_layer_") && components[0].trim_prefix("custom_data_layer_").is_valid_int()) { - // Custom data layers. - int index = components[0].trim_prefix("custom_data_layer_").to_int(); - if (index < 0 || index >= custom_data_layers.size()) { - return false; - } - if (components[1] == "name") { - r_ret = get_custom_data_layer_name(index); - return true; - } else if (components[1] == "type") { - r_ret = get_custom_data_layer_type(index); - return true; - } - } else if (components.size() == 2 && components[0] == "sources" && components[1].is_valid_int()) { - // Atlases data. - int source_id = components[1].to_int(); - - if (has_source(source_id)) { - r_ret = get_source(source_id); - return true; - } else { - return false; - } - } else if (components.size() == 2 && components[0] == "tile_proxies") { - if (components[1] == "source_level") { - Array a; - for (const KeyValue &E : source_level_proxies) { - a.push_back(E.key); - a.push_back(E.value); - } - r_ret = a; - return true; - } else if (components[1] == "coords_level") { - Array a; - for (const KeyValue &E : coords_level_proxies) { - a.push_back(E.key); - a.push_back(E.value); - } - r_ret = a; - return true; - } else if (components[1] == "alternative_level") { - Array a; - for (const KeyValue &E : alternative_level_proxies) { - a.push_back(E.key); - a.push_back(E.value); - } - r_ret = a; - return true; - } - return false; - } else if (components.size() == 1 && components[0].begins_with("pattern_") && components[0].trim_prefix("pattern_").is_valid_int()) { - int pattern_index = components[0].trim_prefix("pattern_").to_int(); - if (pattern_index < 0 || pattern_index >= (int)patterns.size()) { - return false; - } - r_ret = patterns[pattern_index]; - return true; - } - - return false; -} - -void TileSet::_get_property_list(List *p_list) const { - PropertyInfo property_info; - // Rendering. - p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Rendering", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); - for (int i = 0; i < occlusion_layers.size(); i++) { - p_list->push_back(PropertyInfo(Variant::INT, vformat("occlusion_layer_%d/light_mask", i), PROPERTY_HINT_LAYERS_2D_RENDER)); - - // occlusion_layer_%d/sdf_collision - property_info = PropertyInfo(Variant::BOOL, vformat("occlusion_layer_%d/sdf_collision", i)); - if (occlusion_layers[i].sdf_collision == false) { - property_info.usage ^= PROPERTY_USAGE_STORAGE; - } - p_list->push_back(property_info); - } - - // Physics. - p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Physics", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); - for (int i = 0; i < physics_layers.size(); i++) { - p_list->push_back(PropertyInfo(Variant::INT, vformat("physics_layer_%d/collision_layer", i), PROPERTY_HINT_LAYERS_2D_PHYSICS)); - - // physics_layer_%d/collision_mask - property_info = PropertyInfo(Variant::INT, vformat("physics_layer_%d/collision_mask", i), PROPERTY_HINT_LAYERS_2D_PHYSICS); - if (physics_layers[i].collision_mask == 1) { - property_info.usage ^= PROPERTY_USAGE_STORAGE; - } - p_list->push_back(property_info); - - // physics_layer_%d/physics_material - property_info = PropertyInfo(Variant::OBJECT, vformat("physics_layer_%d/physics_material", i), PROPERTY_HINT_RESOURCE_TYPE, "PhysicsMaterial"); - if (!physics_layers[i].physics_material.is_valid()) { - property_info.usage ^= PROPERTY_USAGE_STORAGE; - } - p_list->push_back(property_info); - } - - // Terrains. - p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Terrains", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); - for (int terrain_set_index = 0; terrain_set_index < terrain_sets.size(); terrain_set_index++) { - p_list->push_back(PropertyInfo(Variant::INT, vformat("terrain_set_%d/mode", terrain_set_index), PROPERTY_HINT_ENUM, "Match Corners and Sides,Match Corners,Match Sides")); - p_list->push_back(PropertyInfo(Variant::NIL, vformat("terrain_set_%d/terrains", terrain_set_index), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, vformat("terrain_set_%d/terrain_", terrain_set_index))); - for (int terrain_index = 0; terrain_index < terrain_sets[terrain_set_index].terrains.size(); terrain_index++) { - p_list->push_back(PropertyInfo(Variant::STRING, vformat("terrain_set_%d/terrain_%d/name", terrain_set_index, terrain_index))); - p_list->push_back(PropertyInfo(Variant::COLOR, vformat("terrain_set_%d/terrain_%d/color", terrain_set_index, terrain_index))); - } - } - - // Navigation. - p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Navigation", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); - for (int i = 0; i < navigation_layers.size(); i++) { - p_list->push_back(PropertyInfo(Variant::INT, vformat("navigation_layer_%d/layers", i), PROPERTY_HINT_LAYERS_2D_NAVIGATION)); - } - - // Custom data. - String argt = "Any"; - for (int i = 1; i < Variant::VARIANT_MAX; i++) { - argt += "," + Variant::get_type_name(Variant::Type(i)); - } - p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Custom Data", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); - for (int i = 0; i < custom_data_layers.size(); i++) { - p_list->push_back(PropertyInfo(Variant::STRING, vformat("custom_data_layer_%d/name", i))); - p_list->push_back(PropertyInfo(Variant::INT, vformat("custom_data_layer_%d/type", i), PROPERTY_HINT_ENUM, argt)); - } - - // Sources. - // Note: sources have to be listed in at the end as some TileData rely on the TileSet properties being initialized first. - for (const KeyValue> &E_source : sources) { - p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("sources/%d", E_source.key), PROPERTY_HINT_RESOURCE_TYPE, "TileSetAtlasSource", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_ALWAYS_DUPLICATE)); - } - - // Tile Proxies. - // Note: proxies need to be set after sources are set. - p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Tile Proxies", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); - p_list->push_back(PropertyInfo(Variant::ARRAY, PNAME("tile_proxies/source_level"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); - p_list->push_back(PropertyInfo(Variant::ARRAY, PNAME("tile_proxies/coords_level"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); - p_list->push_back(PropertyInfo(Variant::ARRAY, PNAME("tile_proxies/alternative_level"), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); - - // Patterns. - for (unsigned int pattern_index = 0; pattern_index < patterns.size(); pattern_index++) { - p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("pattern_%d", pattern_index), PROPERTY_HINT_RESOURCE_TYPE, "TileMapPattern", PROPERTY_USAGE_NO_EDITOR)); - } -} - -void TileSet::_validate_property(PropertyInfo &p_property) const { - if (p_property.name == "tile_layout" && tile_shape == TILE_SHAPE_SQUARE) { - p_property.usage ^= PROPERTY_USAGE_READ_ONLY; - } else if (p_property.name == "tile_offset_axis" && tile_shape == TILE_SHAPE_SQUARE) { - p_property.usage ^= PROPERTY_USAGE_READ_ONLY; - } -} - -void TileSet::_bind_methods() { - // Sources management. - ClassDB::bind_method(D_METHOD("get_next_source_id"), &TileSet::get_next_source_id); - ClassDB::bind_method(D_METHOD("add_source", "source", "atlas_source_id_override"), &TileSet::add_source, DEFVAL(TileSet::INVALID_SOURCE)); - ClassDB::bind_method(D_METHOD("remove_source", "source_id"), &TileSet::remove_source); - ClassDB::bind_method(D_METHOD("set_source_id", "source_id", "new_source_id"), &TileSet::set_source_id); - ClassDB::bind_method(D_METHOD("get_source_count"), &TileSet::get_source_count); - ClassDB::bind_method(D_METHOD("get_source_id", "index"), &TileSet::get_source_id); - ClassDB::bind_method(D_METHOD("has_source", "source_id"), &TileSet::has_source); - ClassDB::bind_method(D_METHOD("get_source", "source_id"), &TileSet::get_source); - - // Shape and layout. - ClassDB::bind_method(D_METHOD("set_tile_shape", "shape"), &TileSet::set_tile_shape); - ClassDB::bind_method(D_METHOD("get_tile_shape"), &TileSet::get_tile_shape); - ClassDB::bind_method(D_METHOD("set_tile_layout", "layout"), &TileSet::set_tile_layout); - ClassDB::bind_method(D_METHOD("get_tile_layout"), &TileSet::get_tile_layout); - ClassDB::bind_method(D_METHOD("set_tile_offset_axis", "alignment"), &TileSet::set_tile_offset_axis); - ClassDB::bind_method(D_METHOD("get_tile_offset_axis"), &TileSet::get_tile_offset_axis); - ClassDB::bind_method(D_METHOD("set_tile_size", "size"), &TileSet::set_tile_size); - ClassDB::bind_method(D_METHOD("get_tile_size"), &TileSet::get_tile_size); - - ADD_PROPERTY(PropertyInfo(Variant::INT, "tile_shape", PROPERTY_HINT_ENUM, "Square,Isometric,Half-Offset Square,Hexagon"), "set_tile_shape", "get_tile_shape"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "tile_layout", PROPERTY_HINT_ENUM, "Stacked,Stacked Offset,Stairs Right,Stairs Down,Diamond Right,Diamond Down"), "set_tile_layout", "get_tile_layout"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "tile_offset_axis", PROPERTY_HINT_ENUM, "Horizontal Offset,Vertical Offset"), "set_tile_offset_axis", "get_tile_offset_axis"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "tile_size", PROPERTY_HINT_NONE, "suffix:px"), "set_tile_size", "get_tile_size"); - - // Rendering. - ClassDB::bind_method(D_METHOD("set_uv_clipping", "uv_clipping"), &TileSet::set_uv_clipping); - ClassDB::bind_method(D_METHOD("is_uv_clipping"), &TileSet::is_uv_clipping); - - ClassDB::bind_method(D_METHOD("get_occlusion_layers_count"), &TileSet::get_occlusion_layers_count); - ClassDB::bind_method(D_METHOD("add_occlusion_layer", "to_position"), &TileSet::add_occlusion_layer, DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("move_occlusion_layer", "layer_index", "to_position"), &TileSet::move_occlusion_layer); - ClassDB::bind_method(D_METHOD("remove_occlusion_layer", "layer_index"), &TileSet::remove_occlusion_layer); - ClassDB::bind_method(D_METHOD("set_occlusion_layer_light_mask", "layer_index", "light_mask"), &TileSet::set_occlusion_layer_light_mask); - ClassDB::bind_method(D_METHOD("get_occlusion_layer_light_mask", "layer_index"), &TileSet::get_occlusion_layer_light_mask); - ClassDB::bind_method(D_METHOD("set_occlusion_layer_sdf_collision", "layer_index", "sdf_collision"), &TileSet::set_occlusion_layer_sdf_collision); - ClassDB::bind_method(D_METHOD("get_occlusion_layer_sdf_collision", "layer_index"), &TileSet::get_occlusion_layer_sdf_collision); - - // Physics - ClassDB::bind_method(D_METHOD("get_physics_layers_count"), &TileSet::get_physics_layers_count); - ClassDB::bind_method(D_METHOD("add_physics_layer", "to_position"), &TileSet::add_physics_layer, DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("move_physics_layer", "layer_index", "to_position"), &TileSet::move_physics_layer); - ClassDB::bind_method(D_METHOD("remove_physics_layer", "layer_index"), &TileSet::remove_physics_layer); - ClassDB::bind_method(D_METHOD("set_physics_layer_collision_layer", "layer_index", "layer"), &TileSet::set_physics_layer_collision_layer); - ClassDB::bind_method(D_METHOD("get_physics_layer_collision_layer", "layer_index"), &TileSet::get_physics_layer_collision_layer); - ClassDB::bind_method(D_METHOD("set_physics_layer_collision_mask", "layer_index", "mask"), &TileSet::set_physics_layer_collision_mask); - ClassDB::bind_method(D_METHOD("get_physics_layer_collision_mask", "layer_index"), &TileSet::get_physics_layer_collision_mask); - ClassDB::bind_method(D_METHOD("set_physics_layer_physics_material", "layer_index", "physics_material"), &TileSet::set_physics_layer_physics_material); - ClassDB::bind_method(D_METHOD("get_physics_layer_physics_material", "layer_index"), &TileSet::get_physics_layer_physics_material); - - // Terrains - ClassDB::bind_method(D_METHOD("get_terrain_sets_count"), &TileSet::get_terrain_sets_count); - ClassDB::bind_method(D_METHOD("add_terrain_set", "to_position"), &TileSet::add_terrain_set, DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("move_terrain_set", "terrain_set", "to_position"), &TileSet::move_terrain_set); - ClassDB::bind_method(D_METHOD("remove_terrain_set", "terrain_set"), &TileSet::remove_terrain_set); - ClassDB::bind_method(D_METHOD("set_terrain_set_mode", "terrain_set", "mode"), &TileSet::set_terrain_set_mode); - ClassDB::bind_method(D_METHOD("get_terrain_set_mode", "terrain_set"), &TileSet::get_terrain_set_mode); - - ClassDB::bind_method(D_METHOD("get_terrains_count", "terrain_set"), &TileSet::get_terrains_count); - ClassDB::bind_method(D_METHOD("add_terrain", "terrain_set", "to_position"), &TileSet::add_terrain, DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("move_terrain", "terrain_set", "terrain_index", "to_position"), &TileSet::move_terrain); - ClassDB::bind_method(D_METHOD("remove_terrain", "terrain_set", "terrain_index"), &TileSet::remove_terrain); - ClassDB::bind_method(D_METHOD("set_terrain_name", "terrain_set", "terrain_index", "name"), &TileSet::set_terrain_name); - ClassDB::bind_method(D_METHOD("get_terrain_name", "terrain_set", "terrain_index"), &TileSet::get_terrain_name); - ClassDB::bind_method(D_METHOD("set_terrain_color", "terrain_set", "terrain_index", "color"), &TileSet::set_terrain_color); - ClassDB::bind_method(D_METHOD("get_terrain_color", "terrain_set", "terrain_index"), &TileSet::get_terrain_color); - - // Navigation - ClassDB::bind_method(D_METHOD("get_navigation_layers_count"), &TileSet::get_navigation_layers_count); - ClassDB::bind_method(D_METHOD("add_navigation_layer", "to_position"), &TileSet::add_navigation_layer, DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("move_navigation_layer", "layer_index", "to_position"), &TileSet::move_navigation_layer); - ClassDB::bind_method(D_METHOD("remove_navigation_layer", "layer_index"), &TileSet::remove_navigation_layer); - ClassDB::bind_method(D_METHOD("set_navigation_layer_layers", "layer_index", "layers"), &TileSet::set_navigation_layer_layers); - ClassDB::bind_method(D_METHOD("get_navigation_layer_layers", "layer_index"), &TileSet::get_navigation_layer_layers); - ClassDB::bind_method(D_METHOD("set_navigation_layer_layer_value", "layer_index", "layer_number", "value"), &TileSet::set_navigation_layer_layer_value); - ClassDB::bind_method(D_METHOD("get_navigation_layer_layer_value", "layer_index", "layer_number"), &TileSet::get_navigation_layer_layer_value); - - // Custom data - ClassDB::bind_method(D_METHOD("get_custom_data_layers_count"), &TileSet::get_custom_data_layers_count); - ClassDB::bind_method(D_METHOD("add_custom_data_layer", "to_position"), &TileSet::add_custom_data_layer, DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("move_custom_data_layer", "layer_index", "to_position"), &TileSet::move_custom_data_layer); - ClassDB::bind_method(D_METHOD("remove_custom_data_layer", "layer_index"), &TileSet::remove_custom_data_layer); - ClassDB::bind_method(D_METHOD("get_custom_data_layer_by_name", "layer_name"), &TileSet::get_custom_data_layer_by_name); - ClassDB::bind_method(D_METHOD("set_custom_data_layer_name", "layer_index", "layer_name"), &TileSet::set_custom_data_layer_name); - ClassDB::bind_method(D_METHOD("get_custom_data_layer_name", "layer_index"), &TileSet::get_custom_data_layer_name); - ClassDB::bind_method(D_METHOD("set_custom_data_layer_type", "layer_index", "layer_type"), &TileSet::set_custom_data_layer_type); - ClassDB::bind_method(D_METHOD("get_custom_data_layer_type", "layer_index"), &TileSet::get_custom_data_layer_type); - - // Tile proxies - ClassDB::bind_method(D_METHOD("set_source_level_tile_proxy", "source_from", "source_to"), &TileSet::set_source_level_tile_proxy); - ClassDB::bind_method(D_METHOD("get_source_level_tile_proxy", "source_from"), &TileSet::get_source_level_tile_proxy); - ClassDB::bind_method(D_METHOD("has_source_level_tile_proxy", "source_from"), &TileSet::has_source_level_tile_proxy); - ClassDB::bind_method(D_METHOD("remove_source_level_tile_proxy", "source_from"), &TileSet::remove_source_level_tile_proxy); - - ClassDB::bind_method(D_METHOD("set_coords_level_tile_proxy", "p_source_from", "coords_from", "source_to", "coords_to"), &TileSet::set_coords_level_tile_proxy); - ClassDB::bind_method(D_METHOD("get_coords_level_tile_proxy", "source_from", "coords_from"), &TileSet::get_coords_level_tile_proxy); - ClassDB::bind_method(D_METHOD("has_coords_level_tile_proxy", "source_from", "coords_from"), &TileSet::has_coords_level_tile_proxy); - ClassDB::bind_method(D_METHOD("remove_coords_level_tile_proxy", "source_from", "coords_from"), &TileSet::remove_coords_level_tile_proxy); - - ClassDB::bind_method(D_METHOD("set_alternative_level_tile_proxy", "source_from", "coords_from", "alternative_from", "source_to", "coords_to", "alternative_to"), &TileSet::set_alternative_level_tile_proxy); - ClassDB::bind_method(D_METHOD("get_alternative_level_tile_proxy", "source_from", "coords_from", "alternative_from"), &TileSet::get_alternative_level_tile_proxy); - ClassDB::bind_method(D_METHOD("has_alternative_level_tile_proxy", "source_from", "coords_from", "alternative_from"), &TileSet::has_alternative_level_tile_proxy); - ClassDB::bind_method(D_METHOD("remove_alternative_level_tile_proxy", "source_from", "coords_from", "alternative_from"), &TileSet::remove_alternative_level_tile_proxy); - - ClassDB::bind_method(D_METHOD("map_tile_proxy", "source_from", "coords_from", "alternative_from"), &TileSet::map_tile_proxy); - - ClassDB::bind_method(D_METHOD("cleanup_invalid_tile_proxies"), &TileSet::cleanup_invalid_tile_proxies); - ClassDB::bind_method(D_METHOD("clear_tile_proxies"), &TileSet::clear_tile_proxies); - - // Patterns - ClassDB::bind_method(D_METHOD("add_pattern", "pattern", "index"), &TileSet::add_pattern, DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("get_pattern", "index"), &TileSet::get_pattern, DEFVAL(-1)); - ClassDB::bind_method(D_METHOD("remove_pattern", "index"), &TileSet::remove_pattern); - ClassDB::bind_method(D_METHOD("get_patterns_count"), &TileSet::get_patterns_count); - - ADD_GROUP("Rendering", ""); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uv_clipping"), "set_uv_clipping", "is_uv_clipping"); - ADD_ARRAY("occlusion_layers", "occlusion_layer_"); - - ADD_GROUP("", ""); - ADD_ARRAY("physics_layers", "physics_layer_"); - ADD_ARRAY("terrain_sets", "terrain_set_"); - ADD_ARRAY("navigation_layers", "navigation_layer_"); - ADD_ARRAY("custom_data_layers", "custom_data_layer_"); - - // -- Enum binding -- - BIND_ENUM_CONSTANT(TILE_SHAPE_SQUARE); - BIND_ENUM_CONSTANT(TILE_SHAPE_ISOMETRIC); - BIND_ENUM_CONSTANT(TILE_SHAPE_HALF_OFFSET_SQUARE); - BIND_ENUM_CONSTANT(TILE_SHAPE_HEXAGON); - - BIND_ENUM_CONSTANT(TILE_LAYOUT_STACKED); - BIND_ENUM_CONSTANT(TILE_LAYOUT_STACKED_OFFSET); - BIND_ENUM_CONSTANT(TILE_LAYOUT_STAIRS_RIGHT); - BIND_ENUM_CONSTANT(TILE_LAYOUT_STAIRS_DOWN); - BIND_ENUM_CONSTANT(TILE_LAYOUT_DIAMOND_RIGHT); - BIND_ENUM_CONSTANT(TILE_LAYOUT_DIAMOND_DOWN); - - BIND_ENUM_CONSTANT(TILE_OFFSET_AXIS_HORIZONTAL); - BIND_ENUM_CONSTANT(TILE_OFFSET_AXIS_VERTICAL); - - BIND_ENUM_CONSTANT(CELL_NEIGHBOR_RIGHT_SIDE); - BIND_ENUM_CONSTANT(CELL_NEIGHBOR_RIGHT_CORNER); - BIND_ENUM_CONSTANT(CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE); - BIND_ENUM_CONSTANT(CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER); - BIND_ENUM_CONSTANT(CELL_NEIGHBOR_BOTTOM_SIDE); - BIND_ENUM_CONSTANT(CELL_NEIGHBOR_BOTTOM_CORNER); - BIND_ENUM_CONSTANT(CELL_NEIGHBOR_BOTTOM_LEFT_SIDE); - BIND_ENUM_CONSTANT(CELL_NEIGHBOR_BOTTOM_LEFT_CORNER); - BIND_ENUM_CONSTANT(CELL_NEIGHBOR_LEFT_SIDE); - BIND_ENUM_CONSTANT(CELL_NEIGHBOR_LEFT_CORNER); - BIND_ENUM_CONSTANT(CELL_NEIGHBOR_TOP_LEFT_SIDE); - BIND_ENUM_CONSTANT(CELL_NEIGHBOR_TOP_LEFT_CORNER); - BIND_ENUM_CONSTANT(CELL_NEIGHBOR_TOP_SIDE); - BIND_ENUM_CONSTANT(CELL_NEIGHBOR_TOP_CORNER); - BIND_ENUM_CONSTANT(CELL_NEIGHBOR_TOP_RIGHT_SIDE); - BIND_ENUM_CONSTANT(CELL_NEIGHBOR_TOP_RIGHT_CORNER); - - BIND_ENUM_CONSTANT(TERRAIN_MODE_MATCH_CORNERS_AND_SIDES); - BIND_ENUM_CONSTANT(TERRAIN_MODE_MATCH_CORNERS); - BIND_ENUM_CONSTANT(TERRAIN_MODE_MATCH_SIDES); -} - -TileSet::TileSet() { - // Instantiate the tile meshes. - tile_lines_mesh.instantiate(); - tile_filled_mesh.instantiate(); -} - -TileSet::~TileSet() { -#ifndef DISABLE_DEPRECATED - for (const KeyValue &E : compatibility_data) { - memdelete(E.value); - } -#endif // DISABLE_DEPRECATED - while (!source_ids.is_empty()) { - remove_source(source_ids[0]); - } -} - -/////////////////////////////// TileSetSource ////////////////////////////////////// - -void TileSetSource::set_tile_set(const TileSet *p_tile_set) { - tile_set = p_tile_set; -} - -TileSet *TileSetSource::get_tile_set() const { - return (TileSet *)tile_set; -} - -void TileSetSource::reset_state() { - tile_set = nullptr; -}; - -void TileSetSource::_bind_methods() { - // Base tiles - ClassDB::bind_method(D_METHOD("get_tiles_count"), &TileSetSource::get_tiles_count); - ClassDB::bind_method(D_METHOD("get_tile_id", "index"), &TileSetSource::get_tile_id); - ClassDB::bind_method(D_METHOD("has_tile", "atlas_coords"), &TileSetSource::has_tile); - - // Alternative tiles - ClassDB::bind_method(D_METHOD("get_alternative_tiles_count", "atlas_coords"), &TileSetSource::get_alternative_tiles_count); - ClassDB::bind_method(D_METHOD("get_alternative_tile_id", "atlas_coords", "index"), &TileSetSource::get_alternative_tile_id); - ClassDB::bind_method(D_METHOD("has_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetSource::has_alternative_tile); -} - -/////////////////////////////// TileSetAtlasSource ////////////////////////////////////// - -void TileSetAtlasSource::set_tile_set(const TileSet *p_tile_set) { - tile_set = p_tile_set; - - // Set the TileSet on all TileData. - for (KeyValue &E_tile : tiles) { - for (KeyValue &E_alternative : E_tile.value.alternatives) { - E_alternative.value->set_tile_set(tile_set); - } - } -} - -const TileSet *TileSetAtlasSource::get_tile_set() const { - return tile_set; -} - -void TileSetAtlasSource::notify_tile_data_properties_should_change() { - // Set the TileSet on all TileData. - for (KeyValue &E_tile : tiles) { - for (KeyValue &E_alternative : E_tile.value.alternatives) { - E_alternative.value->notify_tile_data_properties_should_change(); - } - } -} - -void TileSetAtlasSource::add_occlusion_layer(int p_to_pos) { - for (KeyValue E_tile : tiles) { - for (KeyValue E_alternative : E_tile.value.alternatives) { - E_alternative.value->add_occlusion_layer(p_to_pos); - } - } -} - -void TileSetAtlasSource::move_occlusion_layer(int p_from_index, int p_to_pos) { - for (KeyValue E_tile : tiles) { - for (KeyValue E_alternative : E_tile.value.alternatives) { - E_alternative.value->move_occlusion_layer(p_from_index, p_to_pos); - } - } -} - -void TileSetAtlasSource::remove_occlusion_layer(int p_index) { - for (KeyValue E_tile : tiles) { - for (KeyValue E_alternative : E_tile.value.alternatives) { - E_alternative.value->remove_occlusion_layer(p_index); - } - } -} - -void TileSetAtlasSource::add_physics_layer(int p_to_pos) { - for (KeyValue E_tile : tiles) { - for (KeyValue E_alternative : E_tile.value.alternatives) { - E_alternative.value->add_physics_layer(p_to_pos); - } - } -} - -void TileSetAtlasSource::move_physics_layer(int p_from_index, int p_to_pos) { - for (KeyValue E_tile : tiles) { - for (KeyValue E_alternative : E_tile.value.alternatives) { - E_alternative.value->move_physics_layer(p_from_index, p_to_pos); - } - } -} - -void TileSetAtlasSource::remove_physics_layer(int p_index) { - for (KeyValue E_tile : tiles) { - for (KeyValue E_alternative : E_tile.value.alternatives) { - E_alternative.value->remove_physics_layer(p_index); - } - } -} - -void TileSetAtlasSource::add_terrain_set(int p_to_pos) { - for (KeyValue E_tile : tiles) { - for (KeyValue E_alternative : E_tile.value.alternatives) { - E_alternative.value->add_terrain_set(p_to_pos); - } - } -} - -void TileSetAtlasSource::move_terrain_set(int p_from_index, int p_to_pos) { - for (KeyValue E_tile : tiles) { - for (KeyValue E_alternative : E_tile.value.alternatives) { - E_alternative.value->move_terrain_set(p_from_index, p_to_pos); - } - } -} - -void TileSetAtlasSource::remove_terrain_set(int p_index) { - for (KeyValue E_tile : tiles) { - for (KeyValue E_alternative : E_tile.value.alternatives) { - E_alternative.value->remove_terrain_set(p_index); - } - } -} - -void TileSetAtlasSource::add_terrain(int p_terrain_set, int p_to_pos) { - for (KeyValue E_tile : tiles) { - for (KeyValue E_alternative : E_tile.value.alternatives) { - E_alternative.value->add_terrain(p_terrain_set, p_to_pos); - } - } -} - -void TileSetAtlasSource::move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) { - for (KeyValue E_tile : tiles) { - for (KeyValue E_alternative : E_tile.value.alternatives) { - E_alternative.value->move_terrain(p_terrain_set, p_from_index, p_to_pos); - } - } -} - -void TileSetAtlasSource::remove_terrain(int p_terrain_set, int p_index) { - for (KeyValue E_tile : tiles) { - for (KeyValue E_alternative : E_tile.value.alternatives) { - E_alternative.value->remove_terrain(p_terrain_set, p_index); - } - } -} - -void TileSetAtlasSource::add_navigation_layer(int p_to_pos) { - for (KeyValue E_tile : tiles) { - for (KeyValue E_alternative : E_tile.value.alternatives) { - E_alternative.value->add_navigation_layer(p_to_pos); - } - } -} - -void TileSetAtlasSource::move_navigation_layer(int p_from_index, int p_to_pos) { - for (KeyValue E_tile : tiles) { - for (KeyValue E_alternative : E_tile.value.alternatives) { - E_alternative.value->move_navigation_layer(p_from_index, p_to_pos); - } - } -} - -void TileSetAtlasSource::remove_navigation_layer(int p_index) { - for (KeyValue E_tile : tiles) { - for (KeyValue E_alternative : E_tile.value.alternatives) { - E_alternative.value->remove_navigation_layer(p_index); - } - } -} - -void TileSetAtlasSource::add_custom_data_layer(int p_to_pos) { - for (KeyValue E_tile : tiles) { - for (KeyValue E_alternative : E_tile.value.alternatives) { - E_alternative.value->add_custom_data_layer(p_to_pos); - } - } -} - -void TileSetAtlasSource::move_custom_data_layer(int p_from_index, int p_to_pos) { - for (KeyValue E_tile : tiles) { - for (KeyValue E_alternative : E_tile.value.alternatives) { - E_alternative.value->move_custom_data_layer(p_from_index, p_to_pos); - } - } -} - -void TileSetAtlasSource::remove_custom_data_layer(int p_index) { - for (KeyValue E_tile : tiles) { - for (KeyValue E_alternative : E_tile.value.alternatives) { - E_alternative.value->remove_custom_data_layer(p_index); - } - } -} - -void TileSetAtlasSource::reset_state() { - tile_set = nullptr; - - for (KeyValue &E_tile : tiles) { - for (const KeyValue &E_tile_data : E_tile.value.alternatives) { - memdelete(E_tile_data.value); - } - } - _coords_mapping_cache.clear(); - tiles.clear(); - tiles_ids.clear(); - _queue_update_padded_texture(); -} - -void TileSetAtlasSource::set_texture(Ref p_texture) { - if (texture.is_valid()) { - texture->disconnect_changed(callable_mp(this, &TileSetAtlasSource::_queue_update_padded_texture)); - } - - texture = p_texture; - - if (texture.is_valid()) { - texture->connect_changed(callable_mp(this, &TileSetAtlasSource::_queue_update_padded_texture)); - } - - _queue_update_padded_texture(); - emit_changed(); -} - -Ref TileSetAtlasSource::get_texture() const { - return texture; -} - -void TileSetAtlasSource::set_margins(Vector2i p_margins) { - if (p_margins.x < 0 || p_margins.y < 0) { - WARN_PRINT("Atlas source margins should be positive."); - margins = Vector2i(MAX(0, p_margins.x), MAX(0, p_margins.y)); - } else { - margins = p_margins; - } - - _queue_update_padded_texture(); - emit_changed(); -} - -Vector2i TileSetAtlasSource::get_margins() const { - return margins; -} - -void TileSetAtlasSource::set_separation(Vector2i p_separation) { - if (p_separation.x < 0 || p_separation.y < 0) { - WARN_PRINT("Atlas source separation should be positive."); - separation = Vector2i(MAX(0, p_separation.x), MAX(0, p_separation.y)); - } else { - separation = p_separation; - } - - _queue_update_padded_texture(); - emit_changed(); -} - -Vector2i TileSetAtlasSource::get_separation() const { - return separation; -} - -void TileSetAtlasSource::set_texture_region_size(Vector2i p_tile_size) { - if (p_tile_size.x <= 0 || p_tile_size.y <= 0) { - WARN_PRINT("Atlas source tile_size should be strictly positive."); - texture_region_size = Vector2i(MAX(1, p_tile_size.x), MAX(1, p_tile_size.y)); - } else { - texture_region_size = p_tile_size; - } - - _queue_update_padded_texture(); - emit_changed(); -} - -Vector2i TileSetAtlasSource::get_texture_region_size() const { - return texture_region_size; -} - -void TileSetAtlasSource::set_use_texture_padding(bool p_use_padding) { - if (use_texture_padding == p_use_padding) { - return; - } - use_texture_padding = p_use_padding; - _queue_update_padded_texture(); - emit_changed(); -} - -bool TileSetAtlasSource::get_use_texture_padding() const { - return use_texture_padding; -} - -Vector2i TileSetAtlasSource::get_atlas_grid_size() const { - Ref txt = get_texture(); - if (!txt.is_valid()) { - return Vector2i(); - } - - ERR_FAIL_COND_V(texture_region_size.x <= 0 || texture_region_size.y <= 0, Vector2i()); - - Size2i valid_area = txt->get_size() - margins; - - // Compute the number of valid tiles in the tiles atlas - Size2i grid_size; - if (valid_area.x >= texture_region_size.x && valid_area.y >= texture_region_size.y) { - valid_area -= texture_region_size; - grid_size = Size2i(1, 1) + valid_area / (texture_region_size + separation); - } - return grid_size; -} - -bool TileSetAtlasSource::_set(const StringName &p_name, const Variant &p_value) { - Vector components = String(p_name).split("/", true, 2); - - // Compute the vector2i if we have coordinates. - Vector coords_split = components[0].split(":"); - Vector2i coords = TileSetSource::INVALID_ATLAS_COORDS; - if (coords_split.size() == 2 && coords_split[0].is_valid_int() && coords_split[1].is_valid_int()) { - coords = Vector2i(coords_split[0].to_int(), coords_split[1].to_int()); - } - - // Properties. - if (coords != TileSetSource::INVALID_ATLAS_COORDS) { - // Create the tile if needed. - if (!has_tile(coords)) { - create_tile(coords); - } - if (components.size() >= 2) { - // Properties. - if (components[1] == "size_in_atlas") { - move_tile_in_atlas(coords, coords, p_value); - return true; - } else if (components[1] == "next_alternative_id") { - tiles[coords].next_alternative_id = p_value; - return true; - } else if (components[1] == "animation_columns") { - set_tile_animation_columns(coords, p_value); - return true; - } else if (components[1] == "animation_separation") { - set_tile_animation_separation(coords, p_value); - return true; - } else if (components[1] == "animation_speed") { - set_tile_animation_speed(coords, p_value); - return true; - } else if (components[1] == "animation_mode") { - set_tile_animation_mode(coords, VariantCaster::cast(p_value)); - return true; - } else if (components[1] == "animation_frames_count") { - set_tile_animation_frames_count(coords, p_value); - return true; - } else if (components.size() >= 3 && components[1].begins_with("animation_frame_") && components[1].trim_prefix("animation_frame_").is_valid_int()) { - int frame = components[1].trim_prefix("animation_frame_").to_int(); - if (components[2] == "duration") { - if (frame >= get_tile_animation_frames_count(coords)) { - set_tile_animation_frames_count(coords, frame + 1); - } - set_tile_animation_frame_duration(coords, frame, p_value); - return true; - } - return false; - } else if (components[1].is_valid_int()) { - int alternative_id = components[1].to_int(); - if (alternative_id != TileSetSource::INVALID_TILE_ALTERNATIVE) { - // Create the alternative if needed ? - if (!has_alternative_tile(coords, alternative_id)) { - create_alternative_tile(coords, alternative_id); - } - if (!tiles[coords].alternatives.has(alternative_id)) { - tiles[coords].alternatives[alternative_id] = memnew(TileData); - tiles[coords].alternatives[alternative_id]->set_tile_set(tile_set); - tiles[coords].alternatives[alternative_id]->set_allow_transform(alternative_id > 0); - tiles[coords].alternatives_ids.push_back(alternative_id); - } - if (components.size() >= 3) { - bool valid; - tiles[coords].alternatives[alternative_id]->set(components[2], p_value, &valid); - return valid; - } else { - // Only create the alternative if it did not exist yet. - return true; - } - } - } - } - } - - return false; -} - -bool TileSetAtlasSource::_get(const StringName &p_name, Variant &r_ret) const { - Vector components = String(p_name).split("/", true, 2); - - // Properties. - Vector coords_split = components[0].split(":"); - if (coords_split.size() == 2 && coords_split[0].is_valid_int() && coords_split[1].is_valid_int()) { - Vector2i coords = Vector2i(coords_split[0].to_int(), coords_split[1].to_int()); - if (tiles.has(coords)) { - if (components.size() >= 2) { - // Properties. - if (components[1] == "size_in_atlas") { - r_ret = tiles[coords].size_in_atlas; - return true; - } else if (components[1] == "next_alternative_id") { - r_ret = tiles[coords].next_alternative_id; - return true; - } else if (components[1] == "animation_columns") { - r_ret = get_tile_animation_columns(coords); - return true; - } else if (components[1] == "animation_separation") { - r_ret = get_tile_animation_separation(coords); - return true; - } else if (components[1] == "animation_speed") { - r_ret = get_tile_animation_speed(coords); - return true; - } else if (components[1] == "animation_mode") { - r_ret = get_tile_animation_mode(coords); - return true; - } else if (components[1] == "animation_frames_count") { - r_ret = get_tile_animation_frames_count(coords); - return true; - } else if (components.size() >= 3 && components[1].begins_with("animation_frame_") && components[1].trim_prefix("animation_frame_").is_valid_int()) { - int frame = components[1].trim_prefix("animation_frame_").to_int(); - if (frame < 0 || frame >= get_tile_animation_frames_count(coords)) { - return false; - } - if (components[2] == "duration") { - r_ret = get_tile_animation_frame_duration(coords, frame); - return true; - } - return false; - } else if (components[1].is_valid_int()) { - int alternative_id = components[1].to_int(); - if (alternative_id != TileSetSource::INVALID_TILE_ALTERNATIVE && tiles[coords].alternatives.has(alternative_id)) { - if (components.size() >= 3) { - bool valid; - r_ret = tiles[coords].alternatives[alternative_id]->get(components[2], &valid); - return valid; - } else { - // Only to notify the tile alternative exists. - r_ret = alternative_id; - return true; - } - } - } - } - } - } - - return false; -} - -void TileSetAtlasSource::_get_property_list(List *p_list) const { - // Atlases data. - PropertyInfo property_info; - for (const KeyValue &E_tile : tiles) { - List tile_property_list; - - // size_in_atlas - property_info = PropertyInfo(Variant::VECTOR2I, "size_in_atlas", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR); - if (E_tile.value.size_in_atlas == Vector2i(1, 1)) { - property_info.usage ^= PROPERTY_USAGE_STORAGE; - } - tile_property_list.push_back(property_info); - - // next_alternative_id - property_info = PropertyInfo(Variant::INT, "next_alternative_id", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR); - if (E_tile.value.next_alternative_id == 1) { - property_info.usage ^= PROPERTY_USAGE_STORAGE; - } - tile_property_list.push_back(property_info); - - // animation_columns. - property_info = PropertyInfo(Variant::INT, "animation_columns", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR); - if (E_tile.value.animation_columns == 0) { - property_info.usage ^= PROPERTY_USAGE_STORAGE; - } - tile_property_list.push_back(property_info); - - // animation_separation. - property_info = PropertyInfo(Variant::INT, "animation_separation", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR); - if (E_tile.value.animation_separation == Vector2i()) { - property_info.usage ^= PROPERTY_USAGE_STORAGE; - } - tile_property_list.push_back(property_info); - - // animation_speed. - property_info = PropertyInfo(Variant::FLOAT, "animation_speed", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR); - if (E_tile.value.animation_speed == 1.0) { - property_info.usage ^= PROPERTY_USAGE_STORAGE; - } - tile_property_list.push_back(property_info); - - // animation_mode. - property_info = PropertyInfo(Variant::INT, "animation_mode", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR); - if (E_tile.value.animation_mode == TILE_ANIMATION_MODE_DEFAULT) { - property_info.usage ^= PROPERTY_USAGE_STORAGE; - } - tile_property_list.push_back(property_info); - - // animation_frames_count. - tile_property_list.push_back(PropertyInfo(Variant::INT, "animation_frames_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE)); - - // animation_frame_*. - bool store_durations = tiles[E_tile.key].animation_frames_durations.size() >= 2; - for (int i = 0; i < (int)tiles[E_tile.key].animation_frames_durations.size(); i++) { - property_info = PropertyInfo(Variant::FLOAT, vformat("animation_frame_%d/duration", i), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR); - if (!store_durations) { - property_info.usage ^= PROPERTY_USAGE_STORAGE; - } - tile_property_list.push_back(property_info); - } - - for (const KeyValue &E_alternative : E_tile.value.alternatives) { - // Add a dummy property to show the alternative exists. - tile_property_list.push_back(PropertyInfo(Variant::INT, vformat("%d", E_alternative.key), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR)); - - // Get the alternative tile's properties and append them to the list of properties. - List alternative_property_list; - E_alternative.value->get_property_list(&alternative_property_list); - for (PropertyInfo &alternative_property_info : alternative_property_list) { - Variant default_value = ClassDB::class_get_default_property_value("TileData", alternative_property_info.name); - Variant value = E_alternative.value->get(alternative_property_info.name); - if (default_value.get_type() != Variant::NIL && bool(Variant::evaluate(Variant::OP_EQUAL, value, default_value))) { - alternative_property_info.usage ^= PROPERTY_USAGE_STORAGE; - } - alternative_property_info.name = vformat("%s/%s", vformat("%d", E_alternative.key), alternative_property_info.name); - tile_property_list.push_back(alternative_property_info); - } - } - - // Add all alternative. - for (PropertyInfo &tile_property_info : tile_property_list) { - tile_property_info.name = vformat("%s/%s", vformat("%d:%d", E_tile.key.x, E_tile.key.y), tile_property_info.name); - p_list->push_back(tile_property_info); - } - } -} - -void TileSetAtlasSource::create_tile(const Vector2i p_atlas_coords, const Vector2i p_size) { - // Create a tile if it does not exists. - ERR_FAIL_COND(p_atlas_coords.x < 0 || p_atlas_coords.y < 0); - ERR_FAIL_COND(p_size.x <= 0 || p_size.y <= 0); - - bool room_for_tile = has_room_for_tile(p_atlas_coords, p_size, 1, Vector2i(), 1); - ERR_FAIL_COND_MSG(!room_for_tile, "Cannot create tile. The tile is outside the texture or tiles are already present in the space the tile would cover."); - - // Initialize the tile data. - TileAlternativesData tad; - tad.size_in_atlas = p_size; - tad.animation_frames_durations.push_back(1.0); - tad.alternatives[0] = memnew(TileData); - tad.alternatives[0]->set_tile_set(tile_set); - tad.alternatives[0]->set_allow_transform(false); - tad.alternatives[0]->connect("changed", callable_mp((Resource *)this, &TileSetAtlasSource::emit_changed)); - tad.alternatives[0]->notify_property_list_changed(); - tad.alternatives_ids.push_back(0); - - // Create and resize the tile. - tiles.insert(p_atlas_coords, tad); - tiles_ids.push_back(p_atlas_coords); - tiles_ids.sort(); - - _create_coords_mapping_cache(p_atlas_coords); - _queue_update_padded_texture(); - - emit_signal(SNAME("changed")); -} - -void TileSetAtlasSource::remove_tile(Vector2i p_atlas_coords) { - ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); - - // Remove all covered positions from the mapping cache - _clear_coords_mapping_cache(p_atlas_coords); - - // Free tile data. - for (const KeyValue &E_tile_data : tiles[p_atlas_coords].alternatives) { - memdelete(E_tile_data.value); - } - - // Delete the tile - tiles.erase(p_atlas_coords); - tiles_ids.erase(p_atlas_coords); - tiles_ids.sort(); - - _queue_update_padded_texture(); - - emit_signal(SNAME("changed")); -} - -bool TileSetAtlasSource::has_tile(Vector2i p_atlas_coords) const { - return tiles.has(p_atlas_coords); -} - -Vector2i TileSetAtlasSource::get_tile_at_coords(Vector2i p_atlas_coords) const { - if (!_coords_mapping_cache.has(p_atlas_coords)) { - return INVALID_ATLAS_COORDS; - } - - return _coords_mapping_cache[p_atlas_coords]; -} - -void TileSetAtlasSource::set_tile_animation_columns(const Vector2i p_atlas_coords, int p_frame_columns) { - ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); - ERR_FAIL_COND(p_frame_columns < 0); - - TileAlternativesData &tad = tiles[p_atlas_coords]; - bool room_for_tile = has_room_for_tile(p_atlas_coords, tad.size_in_atlas, p_frame_columns, tad.animation_separation, tad.animation_frames_durations.size(), p_atlas_coords); - ERR_FAIL_COND_MSG(!room_for_tile, "Cannot set animation columns count, tiles are already present in the space the tile would cover."); - - _clear_coords_mapping_cache(p_atlas_coords); - - tiles[p_atlas_coords].animation_columns = p_frame_columns; - - _create_coords_mapping_cache(p_atlas_coords); - _queue_update_padded_texture(); - - emit_signal(SNAME("changed")); -} - -int TileSetAtlasSource::get_tile_animation_columns(const Vector2i p_atlas_coords) const { - ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), 1, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); - return tiles[p_atlas_coords].animation_columns; -} - -void TileSetAtlasSource::set_tile_animation_separation(const Vector2i p_atlas_coords, const Vector2i p_separation) { - ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); - ERR_FAIL_COND(p_separation.x < 0 || p_separation.y < 0); - - TileAlternativesData &tad = tiles[p_atlas_coords]; - bool room_for_tile = has_room_for_tile(p_atlas_coords, tad.size_in_atlas, tad.animation_columns, p_separation, tad.animation_frames_durations.size(), p_atlas_coords); - ERR_FAIL_COND_MSG(!room_for_tile, "Cannot set animation columns count, tiles are already present in the space the tile would cover."); - - _clear_coords_mapping_cache(p_atlas_coords); - - tiles[p_atlas_coords].animation_separation = p_separation; - - _create_coords_mapping_cache(p_atlas_coords); - _queue_update_padded_texture(); - - emit_signal(SNAME("changed")); -} - -Vector2i TileSetAtlasSource::get_tile_animation_separation(const Vector2i p_atlas_coords) const { - ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Vector2i(), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); - return tiles[p_atlas_coords].animation_separation; -} - -void TileSetAtlasSource::set_tile_animation_speed(const Vector2i p_atlas_coords, real_t p_speed) { - ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); - ERR_FAIL_COND(p_speed <= 0); - - tiles[p_atlas_coords].animation_speed = p_speed; - - emit_signal(SNAME("changed")); -} - -real_t TileSetAtlasSource::get_tile_animation_speed(const Vector2i p_atlas_coords) const { - ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), 1.0, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); - return tiles[p_atlas_coords].animation_speed; -} - -void TileSetAtlasSource::set_tile_animation_mode(const Vector2i p_atlas_coords, TileSetAtlasSource::TileAnimationMode p_mode) { - ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); - - tiles[p_atlas_coords].animation_mode = p_mode; - - emit_signal(SNAME("changed")); -} - -TileSetAtlasSource::TileAnimationMode TileSetAtlasSource::get_tile_animation_mode(const Vector2i p_atlas_coords) const { - ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), TILE_ANIMATION_MODE_DEFAULT, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); - - return tiles[p_atlas_coords].animation_mode; -} - -void TileSetAtlasSource::set_tile_animation_frames_count(const Vector2i p_atlas_coords, int p_frames_count) { - ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); - ERR_FAIL_COND(p_frames_count < 1); - - int old_size = tiles[p_atlas_coords].animation_frames_durations.size(); - if (p_frames_count == old_size) { - return; - } - - TileAlternativesData &tad = tiles[p_atlas_coords]; - bool room_for_tile = has_room_for_tile(p_atlas_coords, tad.size_in_atlas, tad.animation_columns, tad.animation_separation, p_frames_count, p_atlas_coords); - ERR_FAIL_COND_MSG(!room_for_tile, "Cannot set animation columns count, tiles are already present in the space the tile would cover."); - - _clear_coords_mapping_cache(p_atlas_coords); - - tiles[p_atlas_coords].animation_frames_durations.resize(p_frames_count); - for (int i = old_size; i < p_frames_count; i++) { - tiles[p_atlas_coords].animation_frames_durations[i] = 1.0; - } - - _create_coords_mapping_cache(p_atlas_coords); - _queue_update_padded_texture(); - - notify_property_list_changed(); - - emit_signal(SNAME("changed")); -} - -int TileSetAtlasSource::get_tile_animation_frames_count(const Vector2i p_atlas_coords) const { - ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), 1, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); - return tiles[p_atlas_coords].animation_frames_durations.size(); -} - -void TileSetAtlasSource::set_tile_animation_frame_duration(const Vector2i p_atlas_coords, int p_frame_index, real_t p_duration) { - ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); - ERR_FAIL_INDEX(p_frame_index, (int)tiles[p_atlas_coords].animation_frames_durations.size()); - ERR_FAIL_COND(p_duration <= 0.0); - - tiles[p_atlas_coords].animation_frames_durations[p_frame_index] = p_duration; - - emit_signal(SNAME("changed")); -} - -real_t TileSetAtlasSource::get_tile_animation_frame_duration(const Vector2i p_atlas_coords, int p_frame_index) const { - ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), 1, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); - ERR_FAIL_INDEX_V(p_frame_index, (int)tiles[p_atlas_coords].animation_frames_durations.size(), 0.0); - return tiles[p_atlas_coords].animation_frames_durations[p_frame_index]; -} - -real_t TileSetAtlasSource::get_tile_animation_total_duration(const Vector2i p_atlas_coords) const { - ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), 1, vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); - - real_t sum = 0.0; - for (const real_t &duration : tiles[p_atlas_coords].animation_frames_durations) { - sum += duration; - } - return sum; -} - -Vector2i TileSetAtlasSource::get_tile_size_in_atlas(Vector2i p_atlas_coords) const { - ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Vector2i(-1, -1), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); - - return tiles[p_atlas_coords].size_in_atlas; -} - -int TileSetAtlasSource::get_tiles_count() const { - return tiles_ids.size(); -} - -Vector2i TileSetAtlasSource::get_tile_id(int p_index) const { - ERR_FAIL_INDEX_V(p_index, tiles_ids.size(), TileSetSource::INVALID_ATLAS_COORDS); - return tiles_ids[p_index]; -} - -bool TileSetAtlasSource::has_room_for_tile(Vector2i p_atlas_coords, Vector2i p_size, int p_animation_columns, Vector2i p_animation_separation, int p_frames_count, Vector2i p_ignored_tile) const { - if (p_atlas_coords.x < 0 || p_atlas_coords.y < 0) { - return false; - } - if (p_size.x <= 0 || p_size.y <= 0) { - return false; - } - if (p_frames_count <= 0) { - return false; - } - Size2i atlas_grid_size = get_atlas_grid_size(); - for (int frame = 0; frame < p_frames_count; frame++) { - Vector2i frame_coords = p_atlas_coords + (p_size + p_animation_separation) * ((p_animation_columns > 0) ? Vector2i(frame % p_animation_columns, frame / p_animation_columns) : Vector2i(frame, 0)); - for (int x = 0; x < p_size.x; x++) { - for (int y = 0; y < p_size.y; y++) { - Vector2i coords = frame_coords + Vector2i(x, y); - if (_coords_mapping_cache.has(coords) && _coords_mapping_cache[coords] != p_ignored_tile) { - return false; - } - if (coords.x >= atlas_grid_size.x || coords.y >= atlas_grid_size.y) { - return false; - } - } - } - } - return true; -} - -bool TileSetAtlasSource::has_tiles_outside_texture() const { - for (const KeyValue &E : tiles) { - if (!has_room_for_tile(E.key, E.value.size_in_atlas, E.value.animation_columns, E.value.animation_separation, E.value.animation_frames_durations.size(), E.key)) { - return true; - } - } - return false; -} - -Vector TileSetAtlasSource::get_tiles_outside_texture() const { - Vector to_return; - - for (const KeyValue &E : tiles) { - if (!has_room_for_tile(E.key, E.value.size_in_atlas, E.value.animation_columns, E.value.animation_separation, E.value.animation_frames_durations.size(), E.key)) { - to_return.push_back(E.key); - } - } - return to_return; -} - -void TileSetAtlasSource::clear_tiles_outside_texture() { - LocalVector to_remove; - - for (const KeyValue &E : tiles) { - if (!has_room_for_tile(E.key, E.value.size_in_atlas, E.value.animation_columns, E.value.animation_separation, E.value.animation_frames_durations.size(), E.key)) { - to_remove.push_back(E.key); - } - } - - for (const Vector2i &v : to_remove) { - remove_tile(v); - } -} - -PackedVector2Array TileSetAtlasSource::get_tiles_to_be_removed_on_change(Ref p_texture, Vector2i p_margins, Vector2i p_separation, Vector2i p_texture_region_size) { - ERR_FAIL_COND_V(p_margins.x < 0 || p_margins.y < 0, PackedVector2Array()); - ERR_FAIL_COND_V(p_separation.x < 0 || p_separation.y < 0, PackedVector2Array()); - ERR_FAIL_COND_V(p_texture_region_size.x <= 0 || p_texture_region_size.y <= 0, PackedVector2Array()); - - // Compute the new atlas grid size. - Size2 new_grid_size; - if (p_texture.is_valid()) { - Size2i valid_area = p_texture->get_size() - p_margins; - - // Compute the number of valid tiles in the tiles atlas - if (valid_area.x >= p_texture_region_size.x && valid_area.y >= p_texture_region_size.y) { - valid_area -= p_texture_region_size; - new_grid_size = Size2i(1, 1) + valid_area / (p_texture_region_size + p_separation); - } - } - - Vector output; - for (KeyValue &E : tiles) { - for (unsigned int frame = 0; frame < E.value.animation_frames_durations.size(); frame++) { - Vector2i frame_coords = E.key + (E.value.size_in_atlas + E.value.animation_separation) * ((E.value.animation_columns > 0) ? Vector2i(frame % E.value.animation_columns, frame / E.value.animation_columns) : Vector2i(frame, 0)); - frame_coords += E.value.size_in_atlas; - if (frame_coords.x > new_grid_size.x || frame_coords.y > new_grid_size.y) { - output.push_back(E.key); - break; - } - } - } - return output; -} - -Rect2i TileSetAtlasSource::get_tile_texture_region(Vector2i p_atlas_coords, int p_frame) const { - ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Rect2i(), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); - ERR_FAIL_INDEX_V(p_frame, (int)tiles[p_atlas_coords].animation_frames_durations.size(), Rect2i()); - - const TileAlternativesData &tad = tiles[p_atlas_coords]; - - Vector2i size_in_atlas = tad.size_in_atlas; - Vector2 region_size = texture_region_size * size_in_atlas + separation * (size_in_atlas - Vector2i(1, 1)); - - Vector2i frame_coords = p_atlas_coords + (size_in_atlas + tad.animation_separation) * ((tad.animation_columns > 0) ? Vector2i(p_frame % tad.animation_columns, p_frame / tad.animation_columns) : Vector2i(p_frame, 0)); - Vector2 origin = margins + (frame_coords * (texture_region_size + separation)); - - return Rect2(origin, region_size); -} - -bool TileSetAtlasSource::is_position_in_tile_texture_region(const Vector2i p_atlas_coords, int p_alternative_tile, Vector2 p_position) const { - Size2 size = get_tile_texture_region(p_atlas_coords).size; - Rect2 rect = Rect2(-size / 2 - get_tile_data(p_atlas_coords, p_alternative_tile)->get_texture_origin(), size); - - return rect.has_point(p_position); -} - -int TileSetAtlasSource::alternative_no_transform(int p_alternative_id) { - return p_alternative_id & ~(TRANSFORM_FLIP_H | TRANSFORM_FLIP_V | TRANSFORM_TRANSPOSE); -} - -// Getters for texture and tile region (padded or not) -Ref TileSetAtlasSource::get_runtime_texture() const { - if (use_texture_padding) { - return padded_texture; - } else { - return texture; - } -} - -Rect2i TileSetAtlasSource::get_runtime_tile_texture_region(Vector2i p_atlas_coords, int p_frame) const { - ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), Rect2i(), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); - ERR_FAIL_INDEX_V(p_frame, (int)tiles[p_atlas_coords].animation_frames_durations.size(), Rect2i()); - - Rect2i src_rect = get_tile_texture_region(p_atlas_coords, p_frame); - if (use_texture_padding) { - const TileAlternativesData &tad = tiles[p_atlas_coords]; - Vector2i frame_coords = p_atlas_coords + (tad.size_in_atlas + tad.animation_separation) * ((tad.animation_columns > 0) ? Vector2i(p_frame % tad.animation_columns, p_frame / tad.animation_columns) : Vector2i(p_frame, 0)); - Vector2i base_pos = frame_coords * (texture_region_size + Vector2i(2, 2)) + Vector2i(1, 1); - - return Rect2i(base_pos, src_rect.size); - } else { - return src_rect; - } -} - -void TileSetAtlasSource::move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords, Vector2i p_new_size) { - ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); - - TileAlternativesData &tad = tiles[p_atlas_coords]; - - // Compute the actual new rect from arguments. - Vector2i new_atlas_coords = (p_new_atlas_coords != INVALID_ATLAS_COORDS) ? p_new_atlas_coords : p_atlas_coords; - Vector2i new_size = (p_new_size != Vector2i(-1, -1)) ? p_new_size : tad.size_in_atlas; - - if (new_atlas_coords == p_atlas_coords && new_size == tad.size_in_atlas) { - return; - } - - bool room_for_tile = has_room_for_tile(new_atlas_coords, new_size, tad.animation_columns, tad.animation_separation, tad.animation_frames_durations.size(), p_atlas_coords); - ERR_FAIL_COND_MSG(!room_for_tile, vformat("Cannot move tile at position %s with size %s. Tile already present.", new_atlas_coords, new_size)); - - _clear_coords_mapping_cache(p_atlas_coords); - - // Move the tile and update its size. - if (new_atlas_coords != p_atlas_coords) { - tiles[new_atlas_coords] = tiles[p_atlas_coords]; - tiles.erase(p_atlas_coords); - - tiles_ids.erase(p_atlas_coords); - tiles_ids.push_back(new_atlas_coords); - tiles_ids.sort(); - } - tiles[new_atlas_coords].size_in_atlas = new_size; - - _create_coords_mapping_cache(new_atlas_coords); - _queue_update_padded_texture(); - - emit_signal(SNAME("changed")); -} - -int TileSetAtlasSource::create_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_id_override) { - ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), TileSetSource::INVALID_TILE_ALTERNATIVE, vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); - ERR_FAIL_COND_V_MSG(p_alternative_id_override >= 0 && tiles[p_atlas_coords].alternatives.has(p_alternative_id_override), TileSetSource::INVALID_TILE_ALTERNATIVE, vformat("Cannot create alternative tile. Another alternative exists with id %d.", p_alternative_id_override)); - - int new_alternative_id = p_alternative_id_override >= 0 ? p_alternative_id_override : tiles[p_atlas_coords].next_alternative_id; - - tiles[p_atlas_coords].alternatives[new_alternative_id] = memnew(TileData); - tiles[p_atlas_coords].alternatives[new_alternative_id]->set_tile_set(tile_set); - tiles[p_atlas_coords].alternatives[new_alternative_id]->set_allow_transform(true); - tiles[p_atlas_coords].alternatives[new_alternative_id]->connect("changed", callable_mp((Resource *)this, &TileSetAtlasSource::emit_changed)); - tiles[p_atlas_coords].alternatives[new_alternative_id]->notify_property_list_changed(); - tiles[p_atlas_coords].alternatives_ids.push_back(new_alternative_id); - tiles[p_atlas_coords].alternatives_ids.sort(); - _compute_next_alternative_id(p_atlas_coords); - - emit_signal(SNAME("changed")); - - return new_alternative_id; -} - -void TileSetAtlasSource::remove_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) { - ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); - ERR_FAIL_COND_MSG(!tiles[p_atlas_coords].alternatives.has(p_alternative_tile), vformat("TileSetAtlasSource has no alternative with id %d for tile coords %s.", p_alternative_tile, String(p_atlas_coords))); - p_alternative_tile = alternative_no_transform(p_alternative_tile); - ERR_FAIL_COND_MSG(p_alternative_tile == 0, "Cannot remove the alternative with id 0, the base tile alternative cannot be removed."); - - memdelete(tiles[p_atlas_coords].alternatives[p_alternative_tile]); - tiles[p_atlas_coords].alternatives.erase(p_alternative_tile); - tiles[p_atlas_coords].alternatives_ids.erase(p_alternative_tile); - tiles[p_atlas_coords].alternatives_ids.sort(); - - emit_signal(SNAME("changed")); -} - -void TileSetAtlasSource::set_alternative_tile_id(const Vector2i p_atlas_coords, int p_alternative_tile, int p_new_id) { - ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); - ERR_FAIL_COND_MSG(!tiles[p_atlas_coords].alternatives.has(p_alternative_tile), vformat("TileSetAtlasSource has no alternative with id %d for tile coords %s.", p_alternative_tile, String(p_atlas_coords))); - p_alternative_tile = alternative_no_transform(p_alternative_tile); - ERR_FAIL_COND_MSG(p_alternative_tile == 0, "Cannot change the alternative with id 0, the base tile alternative cannot be modified."); - - ERR_FAIL_COND_MSG(tiles[p_atlas_coords].alternatives.has(p_new_id), vformat("TileSetAtlasSource has already an alternative with id %d at %s.", p_new_id, String(p_atlas_coords))); - - tiles[p_atlas_coords].alternatives[p_new_id] = tiles[p_atlas_coords].alternatives[p_alternative_tile]; - tiles[p_atlas_coords].alternatives_ids.push_back(p_new_id); - - tiles[p_atlas_coords].alternatives.erase(p_alternative_tile); - tiles[p_atlas_coords].alternatives_ids.erase(p_alternative_tile); - tiles[p_atlas_coords].alternatives_ids.sort(); - - emit_signal(SNAME("changed")); -} - -bool TileSetAtlasSource::has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const { - ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), false, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords))); - return tiles[p_atlas_coords].alternatives.has(alternative_no_transform(p_alternative_tile)); -} - -int TileSetAtlasSource::get_next_alternative_tile_id(const Vector2i p_atlas_coords) const { - ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), TileSetSource::INVALID_TILE_ALTERNATIVE, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords))); - return tiles[p_atlas_coords].next_alternative_id; -} - -int TileSetAtlasSource::get_alternative_tiles_count(const Vector2i p_atlas_coords) const { - ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), -1, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords))); - return tiles[p_atlas_coords].alternatives_ids.size(); -} - -int TileSetAtlasSource::get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const { - ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), TileSetSource::INVALID_TILE_ALTERNATIVE, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords))); - p_index = alternative_no_transform(p_index); - ERR_FAIL_INDEX_V(p_index, tiles[p_atlas_coords].alternatives_ids.size(), TileSetSource::INVALID_TILE_ALTERNATIVE); - - return tiles[p_atlas_coords].alternatives_ids[p_index]; -} - -TileData *TileSetAtlasSource::get_tile_data(const Vector2i p_atlas_coords, int p_alternative_tile) const { - ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), nullptr, vformat("The TileSetAtlasSource atlas has no tile at %s.", String(p_atlas_coords))); - p_alternative_tile = alternative_no_transform(p_alternative_tile); - ERR_FAIL_COND_V_MSG(!tiles[p_atlas_coords].alternatives.has(p_alternative_tile), nullptr, vformat("TileSetAtlasSource has no alternative with id %d for tile coords %s.", p_alternative_tile, String(p_atlas_coords))); - - return tiles[p_atlas_coords].alternatives[p_alternative_tile]; -} - -void TileSetAtlasSource::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_texture", "texture"), &TileSetAtlasSource::set_texture); - ClassDB::bind_method(D_METHOD("get_texture"), &TileSetAtlasSource::get_texture); - ClassDB::bind_method(D_METHOD("set_margins", "margins"), &TileSetAtlasSource::set_margins); - ClassDB::bind_method(D_METHOD("get_margins"), &TileSetAtlasSource::get_margins); - ClassDB::bind_method(D_METHOD("set_separation", "separation"), &TileSetAtlasSource::set_separation); - ClassDB::bind_method(D_METHOD("get_separation"), &TileSetAtlasSource::get_separation); - ClassDB::bind_method(D_METHOD("set_texture_region_size", "texture_region_size"), &TileSetAtlasSource::set_texture_region_size); - ClassDB::bind_method(D_METHOD("get_texture_region_size"), &TileSetAtlasSource::get_texture_region_size); - ClassDB::bind_method(D_METHOD("set_use_texture_padding", "use_texture_padding"), &TileSetAtlasSource::set_use_texture_padding); - ClassDB::bind_method(D_METHOD("get_use_texture_padding"), &TileSetAtlasSource::get_use_texture_padding); - - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_NO_EDITOR), "set_texture", "get_texture"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "margins", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_NO_EDITOR), "set_margins", "get_margins"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "separation", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_NO_EDITOR), "set_separation", "get_separation"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "texture_region_size", PROPERTY_HINT_NONE, "suffix:px", PROPERTY_USAGE_NO_EDITOR), "set_texture_region_size", "get_texture_region_size"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_texture_padding", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_use_texture_padding", "get_use_texture_padding"); - - // Base tiles - ClassDB::bind_method(D_METHOD("create_tile", "atlas_coords", "size"), &TileSetAtlasSource::create_tile, DEFVAL(Vector2i(1, 1))); - ClassDB::bind_method(D_METHOD("remove_tile", "atlas_coords"), &TileSetAtlasSource::remove_tile); // Remove a tile. If p_tile_key.alternative_tile if different from 0, remove the alternative - ClassDB::bind_method(D_METHOD("move_tile_in_atlas", "atlas_coords", "new_atlas_coords", "new_size"), &TileSetAtlasSource::move_tile_in_atlas, DEFVAL(INVALID_ATLAS_COORDS), DEFVAL(Vector2i(-1, -1))); - ClassDB::bind_method(D_METHOD("get_tile_size_in_atlas", "atlas_coords"), &TileSetAtlasSource::get_tile_size_in_atlas); - - ClassDB::bind_method(D_METHOD("has_room_for_tile", "atlas_coords", "size", "animation_columns", "animation_separation", "frames_count", "ignored_tile"), &TileSetAtlasSource::has_room_for_tile, DEFVAL(INVALID_ATLAS_COORDS)); - ClassDB::bind_method(D_METHOD("get_tiles_to_be_removed_on_change", "texture", "margins", "separation", "texture_region_size"), &TileSetAtlasSource::get_tiles_to_be_removed_on_change); - ClassDB::bind_method(D_METHOD("get_tile_at_coords", "atlas_coords"), &TileSetAtlasSource::get_tile_at_coords); - - ClassDB::bind_method(D_METHOD("has_tiles_outside_texture"), &TileSetAtlasSource::has_tiles_outside_texture); - ClassDB::bind_method(D_METHOD("clear_tiles_outside_texture"), &TileSetAtlasSource::clear_tiles_outside_texture); - - ClassDB::bind_method(D_METHOD("set_tile_animation_columns", "atlas_coords", "frame_columns"), &TileSetAtlasSource::set_tile_animation_columns); - ClassDB::bind_method(D_METHOD("get_tile_animation_columns", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_columns); - ClassDB::bind_method(D_METHOD("set_tile_animation_separation", "atlas_coords", "separation"), &TileSetAtlasSource::set_tile_animation_separation); - ClassDB::bind_method(D_METHOD("get_tile_animation_separation", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_separation); - ClassDB::bind_method(D_METHOD("set_tile_animation_speed", "atlas_coords", "speed"), &TileSetAtlasSource::set_tile_animation_speed); - ClassDB::bind_method(D_METHOD("get_tile_animation_speed", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_speed); - ClassDB::bind_method(D_METHOD("set_tile_animation_mode", "atlas_coords", "mode"), &TileSetAtlasSource::set_tile_animation_mode); - ClassDB::bind_method(D_METHOD("get_tile_animation_mode", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_mode); - ClassDB::bind_method(D_METHOD("set_tile_animation_frames_count", "atlas_coords", "frames_count"), &TileSetAtlasSource::set_tile_animation_frames_count); - ClassDB::bind_method(D_METHOD("get_tile_animation_frames_count", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_frames_count); - ClassDB::bind_method(D_METHOD("set_tile_animation_frame_duration", "atlas_coords", "frame_index", "duration"), &TileSetAtlasSource::set_tile_animation_frame_duration); - ClassDB::bind_method(D_METHOD("get_tile_animation_frame_duration", "atlas_coords", "frame_index"), &TileSetAtlasSource::get_tile_animation_frame_duration); - ClassDB::bind_method(D_METHOD("get_tile_animation_total_duration", "atlas_coords"), &TileSetAtlasSource::get_tile_animation_total_duration); - - // Alternative tiles - ClassDB::bind_method(D_METHOD("create_alternative_tile", "atlas_coords", "alternative_id_override"), &TileSetAtlasSource::create_alternative_tile, DEFVAL(INVALID_TILE_ALTERNATIVE)); - ClassDB::bind_method(D_METHOD("remove_alternative_tile", "atlas_coords", "alternative_tile"), &TileSetAtlasSource::remove_alternative_tile); - ClassDB::bind_method(D_METHOD("set_alternative_tile_id", "atlas_coords", "alternative_tile", "new_id"), &TileSetAtlasSource::set_alternative_tile_id); - ClassDB::bind_method(D_METHOD("get_next_alternative_tile_id", "atlas_coords"), &TileSetAtlasSource::get_next_alternative_tile_id); - - ClassDB::bind_method(D_METHOD("get_tile_data", "atlas_coords", "alternative_tile"), &TileSetAtlasSource::get_tile_data); - - // Helpers. - ClassDB::bind_method(D_METHOD("get_atlas_grid_size"), &TileSetAtlasSource::get_atlas_grid_size); - ClassDB::bind_method(D_METHOD("get_tile_texture_region", "atlas_coords", "frame"), &TileSetAtlasSource::get_tile_texture_region, DEFVAL(0)); - - // Getters for texture and tile region (padded or not) - ClassDB::bind_method(D_METHOD("get_runtime_texture"), &TileSetAtlasSource::get_runtime_texture); - ClassDB::bind_method(D_METHOD("get_runtime_tile_texture_region", "atlas_coords", "frame"), &TileSetAtlasSource::get_runtime_tile_texture_region); - - BIND_ENUM_CONSTANT(TILE_ANIMATION_MODE_DEFAULT) - BIND_ENUM_CONSTANT(TILE_ANIMATION_MODE_RANDOM_START_TIMES) - BIND_ENUM_CONSTANT(TILE_ANIMATION_MODE_MAX) - - BIND_CONSTANT(TRANSFORM_FLIP_H) - BIND_CONSTANT(TRANSFORM_FLIP_V) - BIND_CONSTANT(TRANSFORM_TRANSPOSE) -} - -TileSetAtlasSource::~TileSetAtlasSource() { - // Free everything needed. - for (KeyValue &E_alternatives : tiles) { - for (KeyValue &E_tile_data : E_alternatives.value.alternatives) { - memdelete(E_tile_data.value); - } - } -} - -TileData *TileSetAtlasSource::_get_atlas_tile_data(Vector2i p_atlas_coords, int p_alternative_tile) { - ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), nullptr, vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); - p_alternative_tile = alternative_no_transform(p_alternative_tile); - ERR_FAIL_COND_V_MSG(!tiles[p_atlas_coords].alternatives.has(p_alternative_tile), nullptr, vformat("TileSetAtlasSource has no alternative with id %d for tile coords %s.", p_alternative_tile, String(p_atlas_coords))); - - return tiles[p_atlas_coords].alternatives[p_alternative_tile]; -} - -const TileData *TileSetAtlasSource::_get_atlas_tile_data(Vector2i p_atlas_coords, int p_alternative_tile) const { - ERR_FAIL_COND_V_MSG(!tiles.has(p_atlas_coords), nullptr, vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); - ERR_FAIL_COND_V_MSG(!tiles[p_atlas_coords].alternatives.has(p_alternative_tile), nullptr, vformat("TileSetAtlasSource has no alternative with id %d for tile coords %s.", p_alternative_tile, String(p_atlas_coords))); - - return tiles[p_atlas_coords].alternatives[p_alternative_tile]; -} - -void TileSetAtlasSource::_compute_next_alternative_id(const Vector2i p_atlas_coords) { - ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", String(p_atlas_coords))); - - while (tiles[p_atlas_coords].alternatives.has(tiles[p_atlas_coords].next_alternative_id)) { - tiles[p_atlas_coords].next_alternative_id = (tiles[p_atlas_coords].next_alternative_id % 1073741823) + 1; // 2 ** 30 - }; -} - -void TileSetAtlasSource::_clear_coords_mapping_cache(Vector2i p_atlas_coords) { - ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); - TileAlternativesData &tad = tiles[p_atlas_coords]; - for (int frame = 0; frame < (int)tad.animation_frames_durations.size(); frame++) { - Vector2i frame_coords = p_atlas_coords + (tad.size_in_atlas + tad.animation_separation) * ((tad.animation_columns > 0) ? Vector2i(frame % tad.animation_columns, frame / tad.animation_columns) : Vector2i(frame, 0)); - for (int x = 0; x < tad.size_in_atlas.x; x++) { - for (int y = 0; y < tad.size_in_atlas.y; y++) { - Vector2i coords = frame_coords + Vector2i(x, y); - if (!_coords_mapping_cache.has(coords)) { - WARN_PRINT(vformat("TileSetAtlasSource has no cached tile at position %s, the position cache might be corrupted.", coords)); - } else { - if (_coords_mapping_cache[coords] != p_atlas_coords) { - WARN_PRINT(vformat("The position cache at position %s is pointing to a wrong tile, the position cache might be corrupted.", coords)); - } - _coords_mapping_cache.erase(coords); - } - } - } - } -} - -void TileSetAtlasSource::_create_coords_mapping_cache(Vector2i p_atlas_coords) { - ERR_FAIL_COND_MSG(!tiles.has(p_atlas_coords), vformat("TileSetAtlasSource has no tile at %s.", Vector2i(p_atlas_coords))); - - TileAlternativesData &tad = tiles[p_atlas_coords]; - for (int frame = 0; frame < (int)tad.animation_frames_durations.size(); frame++) { - Vector2i frame_coords = p_atlas_coords + (tad.size_in_atlas + tad.animation_separation) * ((tad.animation_columns > 0) ? Vector2i(frame % tad.animation_columns, frame / tad.animation_columns) : Vector2i(frame, 0)); - for (int x = 0; x < tad.size_in_atlas.x; x++) { - for (int y = 0; y < tad.size_in_atlas.y; y++) { - Vector2i coords = frame_coords + Vector2i(x, y); - if (_coords_mapping_cache.has(coords)) { - WARN_PRINT(vformat("The cache already has a tile for position %s, the position cache might be corrupted.", coords)); - } - _coords_mapping_cache[coords] = p_atlas_coords; - } - } - } -} - -void TileSetAtlasSource::_queue_update_padded_texture() { - padded_texture_needs_update = true; - callable_mp(this, &TileSetAtlasSource::_update_padded_texture).call_deferred(); -} - -Ref TileSetAtlasSource::_create_padded_image_texture(const Ref &p_source) { - ERR_FAIL_COND_V(p_source.is_null(), Ref()); - - Ref src_image = p_source->get_image(); - if (src_image.is_null()) { - Ref ret; - ret.instantiate(); - return ret; - } - - Size2 size = get_atlas_grid_size() * (texture_region_size + Vector2i(2, 2)); - Ref image = Image::create_empty(size.x, size.y, false, src_image->get_format()); - - for (KeyValue kv : tiles) { - for (int frame = 0; frame < (int)kv.value.animation_frames_durations.size(); frame++) { - // Compute the source rects. - Rect2i src_rect = get_tile_texture_region(kv.key, frame); - - Rect2i top_src_rect = Rect2i(src_rect.position, Vector2i(src_rect.size.x, 1)); - Rect2i bottom_src_rect = Rect2i(src_rect.position + Vector2i(0, src_rect.size.y - 1), Vector2i(src_rect.size.x, 1)); - Rect2i left_src_rect = Rect2i(src_rect.position, Vector2i(1, src_rect.size.y)); - Rect2i right_src_rect = Rect2i(src_rect.position + Vector2i(src_rect.size.x - 1, 0), Vector2i(1, src_rect.size.y)); - - // Copy the tile and the paddings. - Vector2i frame_coords = kv.key + (kv.value.size_in_atlas + kv.value.animation_separation) * ((kv.value.animation_columns > 0) ? Vector2i(frame % kv.value.animation_columns, frame / kv.value.animation_columns) : Vector2i(frame, 0)); - Vector2i base_pos = frame_coords * (texture_region_size + Vector2i(2, 2)) + Vector2i(1, 1); - - image->blit_rect(*src_image, src_rect, base_pos); - - // Sides - image->blit_rect(*src_image, top_src_rect, base_pos + Vector2i(0, -1)); - image->blit_rect(*src_image, bottom_src_rect, base_pos + Vector2i(0, src_rect.size.y)); - image->blit_rect(*src_image, left_src_rect, base_pos + Vector2i(-1, 0)); - image->blit_rect(*src_image, right_src_rect, base_pos + Vector2i(src_rect.size.x, 0)); - - // Corners - image->blit_rect(*src_image, Rect2i(src_rect.position, Vector2i(1, 1)), base_pos + Vector2i(-1, -1)); - image->blit_rect(*src_image, Rect2i(src_rect.position + Vector2i(src_rect.size.x - 1, 0), Vector2i(1, 1)), base_pos + Vector2i(src_rect.size.x, -1)); - image->blit_rect(*src_image, Rect2i(src_rect.position + Vector2i(0, src_rect.size.y - 1), Vector2i(1, 1)), base_pos + Vector2i(-1, src_rect.size.y)); - image->blit_rect(*src_image, Rect2i(src_rect.position + Vector2i(src_rect.size.x - 1, src_rect.size.y - 1), Vector2i(1, 1)), base_pos + Vector2i(src_rect.size.x, src_rect.size.y)); - } - } - - return ImageTexture::create_from_image(image); -} - -void TileSetAtlasSource::_update_padded_texture() { - if (!padded_texture_needs_update) { - return; - } - padded_texture_needs_update = false; - - if (padded_texture.is_valid()) { - padded_texture->disconnect_changed(callable_mp(this, &TileSetAtlasSource::_queue_update_padded_texture)); - } - - padded_texture = Ref(); - - if (texture.is_null()) { - return; - } - - if (!use_texture_padding) { - return; - } - - padded_texture.instantiate(); - - Ref src_canvas_texture = texture; - if (src_canvas_texture.is_valid()) { - // Use all textures. - // Diffuse - Ref src = src_canvas_texture->get_diffuse_texture(); - Ref image_texture; - if (src.is_valid()) { - padded_texture->set_diffuse_texture(_create_padded_image_texture(src)); - } - - // Normal - src = src_canvas_texture->get_normal_texture(); - if (src.is_valid()) { - padded_texture->set_normal_texture(_create_padded_image_texture(src)); - } - - // Specular - src = src_canvas_texture->get_specular_texture(); - if (src.is_valid()) { - padded_texture->set_specular_texture(_create_padded_image_texture(src)); - } - - // Other properties. - padded_texture->set_specular_color(src_canvas_texture->get_specular_color()); - padded_texture->set_specular_shininess(src_canvas_texture->get_specular_shininess()); - padded_texture->set_texture_filter(src_canvas_texture->get_texture_filter()); - padded_texture->set_texture_repeat(src_canvas_texture->get_texture_repeat()); - } else { - // Use only diffuse. - Ref image_texture = _create_padded_image_texture(texture); - padded_texture->set_diffuse_texture(image_texture); - } - padded_texture->connect_changed(callable_mp(this, &TileSetAtlasSource::_queue_update_padded_texture)); - emit_changed(); -} - -/////////////////////////////// TileSetScenesCollectionSource ////////////////////////////////////// - -void TileSetScenesCollectionSource::_compute_next_alternative_id() { - while (scenes.has(next_scene_id)) { - next_scene_id = (next_scene_id % 1073741823) + 1; // 2 ** 30 - }; -} - -int TileSetScenesCollectionSource::get_tiles_count() const { - return 1; -} - -Vector2i TileSetScenesCollectionSource::get_tile_id(int p_tile_index) const { - ERR_FAIL_COND_V(p_tile_index != 0, TileSetSource::INVALID_ATLAS_COORDS); - return Vector2i(); -} - -bool TileSetScenesCollectionSource::has_tile(Vector2i p_atlas_coords) const { - return p_atlas_coords == Vector2i(); -} - -int TileSetScenesCollectionSource::get_alternative_tiles_count(const Vector2i p_atlas_coords) const { - return scenes_ids.size(); -} - -int TileSetScenesCollectionSource::get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const { - ERR_FAIL_COND_V(p_atlas_coords != Vector2i(), TileSetSource::INVALID_TILE_ALTERNATIVE); - ERR_FAIL_INDEX_V(p_index, scenes_ids.size(), TileSetSource::INVALID_TILE_ALTERNATIVE); - - return scenes_ids[p_index]; -} - -bool TileSetScenesCollectionSource::has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const { - ERR_FAIL_COND_V(p_atlas_coords != Vector2i(), false); - return scenes.has(p_alternative_tile); -} - -int TileSetScenesCollectionSource::create_scene_tile(Ref p_packed_scene, int p_id_override) { - ERR_FAIL_COND_V_MSG(p_id_override >= 0 && scenes.has(p_id_override), INVALID_TILE_ALTERNATIVE, vformat("Cannot create scene tile. Another scene tile exists with id %d.", p_id_override)); - - int new_scene_id = p_id_override >= 0 ? p_id_override : next_scene_id; - - scenes[new_scene_id] = SceneData(); - scenes_ids.push_back(new_scene_id); - scenes_ids.sort(); - set_scene_tile_scene(new_scene_id, p_packed_scene); - _compute_next_alternative_id(); - - emit_signal(SNAME("changed")); - - return new_scene_id; -} - -void TileSetScenesCollectionSource::set_scene_tile_id(int p_id, int p_new_id) { - ERR_FAIL_COND(p_new_id < 0); - ERR_FAIL_COND(!has_scene_tile_id(p_id)); - ERR_FAIL_COND(has_scene_tile_id(p_new_id)); - - scenes[p_new_id] = SceneData(); - scenes[p_new_id] = scenes[p_id]; - scenes_ids.push_back(p_new_id); - scenes_ids.sort(); - - _compute_next_alternative_id(); - - scenes.erase(p_id); - scenes_ids.erase(p_id); - - emit_signal(SNAME("changed")); -} - -void TileSetScenesCollectionSource::set_scene_tile_scene(int p_id, Ref p_packed_scene) { - ERR_FAIL_COND(!scenes.has(p_id)); - if (p_packed_scene.is_valid()) { - // Check if it extends CanvasItem. - Ref scene_state = p_packed_scene->get_state(); - String type; - while (scene_state.is_valid() && type.is_empty()) { - // Make sure we have a root node. Supposed to be at 0 index because find_node_by_path() does not seem to work. - ERR_FAIL_COND(scene_state->get_node_count() < 1); - - type = scene_state->get_node_type(0); - scene_state = scene_state->get_base_scene_state(); - } - ERR_FAIL_COND_EDMSG(type.is_empty(), vformat("Invalid PackedScene for TileSetScenesCollectionSource: %s. Could not get the type of the root node.", p_packed_scene->get_path())); - bool extends_correct_class = ClassDB::is_parent_class(type, "CanvasItem"); - ERR_FAIL_COND_EDMSG(!extends_correct_class, vformat("Invalid PackedScene for TileSetScenesCollectionSource: %s. Root node should extend CanvasItem. Found %s instead.", p_packed_scene->get_path(), type)); - - scenes[p_id].scene = p_packed_scene; - } else { - scenes[p_id].scene = Ref(); - } - emit_signal(SNAME("changed")); -} - -Ref TileSetScenesCollectionSource::get_scene_tile_scene(int p_id) const { - ERR_FAIL_COND_V(!scenes.has(p_id), Ref()); - return scenes[p_id].scene; -} - -void TileSetScenesCollectionSource::set_scene_tile_display_placeholder(int p_id, bool p_display_placeholder) { - ERR_FAIL_COND(!scenes.has(p_id)); - - scenes[p_id].display_placeholder = p_display_placeholder; - - emit_signal(SNAME("changed")); -} - -bool TileSetScenesCollectionSource::get_scene_tile_display_placeholder(int p_id) const { - ERR_FAIL_COND_V(!scenes.has(p_id), false); - return scenes[p_id].display_placeholder; -} - -void TileSetScenesCollectionSource::remove_scene_tile(int p_id) { - ERR_FAIL_COND(!scenes.has(p_id)); - - scenes.erase(p_id); - scenes_ids.erase(p_id); - emit_signal(SNAME("changed")); -} - -int TileSetScenesCollectionSource::get_next_scene_tile_id() const { - return next_scene_id; -} - -bool TileSetScenesCollectionSource::_set(const StringName &p_name, const Variant &p_value) { - Vector components = String(p_name).split("/", true, 2); - - if (components.size() >= 2 && components[0] == "scenes" && components[1].is_valid_int()) { - int scene_id = components[1].to_int(); - if (components.size() >= 3 && components[2] == "scene") { - if (has_scene_tile_id(scene_id)) { - set_scene_tile_scene(scene_id, p_value); - } else { - create_scene_tile(p_value, scene_id); - } - return true; - } else if (components.size() >= 3 && components[2] == "display_placeholder") { - if (!has_scene_tile_id(scene_id)) { - create_scene_tile(p_value, scene_id); - } - - return true; - } - } - - return false; -} - -bool TileSetScenesCollectionSource::_get(const StringName &p_name, Variant &r_ret) const { - Vector components = String(p_name).split("/", true, 2); - - if (components.size() >= 2 && components[0] == "scenes" && components[1].is_valid_int() && scenes.has(components[1].to_int())) { - if (components.size() >= 3 && components[2] == "scene") { - r_ret = scenes[components[1].to_int()].scene; - return true; - } else if (components.size() >= 3 && components[2] == "display_placeholder") { - r_ret = scenes[components[1].to_int()].scene; - return true; - } - } - - return false; -} - -void TileSetScenesCollectionSource::_get_property_list(List *p_list) const { - for (int i = 0; i < scenes_ids.size(); i++) { - p_list->push_back(PropertyInfo(Variant::OBJECT, vformat("%s/%d/%s", PNAME("scenes"), scenes_ids[i], PNAME("scene")), PROPERTY_HINT_RESOURCE_TYPE, "TileSetScenesCollectionSource")); - - PropertyInfo property_info = PropertyInfo(Variant::BOOL, vformat("%s/%d/%s", PNAME("scenes"), scenes_ids[i], PNAME("display_placeholder"))); - if (scenes[scenes_ids[i]].display_placeholder == false) { - property_info.usage ^= PROPERTY_USAGE_STORAGE; - } - p_list->push_back(property_info); - } -} - -void TileSetScenesCollectionSource::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_scene_tiles_count"), &TileSetScenesCollectionSource::get_scene_tiles_count); - ClassDB::bind_method(D_METHOD("get_scene_tile_id", "index"), &TileSetScenesCollectionSource::get_scene_tile_id); - ClassDB::bind_method(D_METHOD("has_scene_tile_id", "id"), &TileSetScenesCollectionSource::has_scene_tile_id); - ClassDB::bind_method(D_METHOD("create_scene_tile", "packed_scene", "id_override"), &TileSetScenesCollectionSource::create_scene_tile, DEFVAL(INVALID_TILE_ALTERNATIVE)); - ClassDB::bind_method(D_METHOD("set_scene_tile_id", "id", "new_id"), &TileSetScenesCollectionSource::set_scene_tile_id); - ClassDB::bind_method(D_METHOD("set_scene_tile_scene", "id", "packed_scene"), &TileSetScenesCollectionSource::set_scene_tile_scene); - ClassDB::bind_method(D_METHOD("get_scene_tile_scene", "id"), &TileSetScenesCollectionSource::get_scene_tile_scene); - ClassDB::bind_method(D_METHOD("set_scene_tile_display_placeholder", "id", "display_placeholder"), &TileSetScenesCollectionSource::set_scene_tile_display_placeholder); - ClassDB::bind_method(D_METHOD("get_scene_tile_display_placeholder", "id"), &TileSetScenesCollectionSource::get_scene_tile_display_placeholder); - ClassDB::bind_method(D_METHOD("remove_scene_tile", "id"), &TileSetScenesCollectionSource::remove_scene_tile); - ClassDB::bind_method(D_METHOD("get_next_scene_tile_id"), &TileSetScenesCollectionSource::get_next_scene_tile_id); -} - -/////////////////////////////// TileData ////////////////////////////////////// - -void TileData::set_tile_set(const TileSet *p_tile_set) { - tile_set = p_tile_set; - notify_tile_data_properties_should_change(); -} - -void TileData::notify_tile_data_properties_should_change() { - if (!tile_set) { - return; - } - - occluders.resize(tile_set->get_occlusion_layers_count()); - physics.resize(tile_set->get_physics_layers_count()); - for (int bit_index = 0; bit_index < 16; bit_index++) { - if (terrain_set < 0 || terrain_peering_bits[bit_index] >= tile_set->get_terrains_count(terrain_set)) { - terrain_peering_bits[bit_index] = -1; - } - } - navigation.resize(tile_set->get_navigation_layers_count()); - - // Convert custom data to the new type. - custom_data.resize(tile_set->get_custom_data_layers_count()); - for (int i = 0; i < custom_data.size(); i++) { - if (custom_data[i].get_type() != tile_set->get_custom_data_layer_type(i)) { - Variant new_val; - Callable::CallError error; - if (Variant::can_convert(custom_data[i].get_type(), tile_set->get_custom_data_layer_type(i))) { - const Variant *args[] = { &custom_data[i] }; - Variant::construct(tile_set->get_custom_data_layer_type(i), new_val, args, 1, error); - } else { - Variant::construct(tile_set->get_custom_data_layer_type(i), new_val, nullptr, 0, error); - } - custom_data.write[i] = new_val; - } - } - - notify_property_list_changed(); - emit_signal(SNAME("changed")); -} - -void TileData::add_occlusion_layer(int p_to_pos) { - if (p_to_pos < 0) { - p_to_pos = occluders.size(); - } - ERR_FAIL_INDEX(p_to_pos, occluders.size() + 1); - occluders.insert(p_to_pos, OcclusionLayerTileData()); -} - -void TileData::move_occlusion_layer(int p_from_index, int p_to_pos) { - ERR_FAIL_INDEX(p_from_index, occluders.size()); - ERR_FAIL_INDEX(p_to_pos, occluders.size() + 1); - occluders.insert(p_to_pos, occluders[p_from_index]); - occluders.remove_at(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); -} - -void TileData::remove_occlusion_layer(int p_index) { - ERR_FAIL_INDEX(p_index, occluders.size()); - occluders.remove_at(p_index); -} - -void TileData::add_physics_layer(int p_to_pos) { - if (p_to_pos < 0) { - p_to_pos = physics.size(); - } - ERR_FAIL_INDEX(p_to_pos, physics.size() + 1); - physics.insert(p_to_pos, PhysicsLayerTileData()); -} - -void TileData::move_physics_layer(int p_from_index, int p_to_pos) { - ERR_FAIL_INDEX(p_from_index, physics.size()); - ERR_FAIL_INDEX(p_to_pos, physics.size() + 1); - physics.insert(p_to_pos, physics[p_from_index]); - physics.remove_at(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); -} - -void TileData::remove_physics_layer(int p_index) { - ERR_FAIL_INDEX(p_index, physics.size()); - physics.remove_at(p_index); -} - -void TileData::add_terrain_set(int p_to_pos) { - if (p_to_pos >= 0 && p_to_pos <= terrain_set) { - terrain_set += 1; - } -} - -void TileData::move_terrain_set(int p_from_index, int p_to_pos) { - if (p_from_index == terrain_set) { - terrain_set = (p_from_index < p_to_pos) ? p_to_pos - 1 : p_to_pos; - } else { - if (p_from_index < terrain_set) { - terrain_set -= 1; - } - if (p_to_pos <= terrain_set) { - terrain_set += 1; - } - } -} - -void TileData::remove_terrain_set(int p_index) { - if (p_index == terrain_set) { - terrain_set = -1; - for (int i = 0; i < 16; i++) { - terrain_peering_bits[i] = -1; - } - } else if (terrain_set > p_index) { - terrain_set -= 1; - } -} - -void TileData::add_terrain(int p_terrain_set, int p_to_pos) { - if (terrain_set == p_terrain_set) { - for (int i = 0; i < 16; i++) { - if (p_to_pos >= 0 && p_to_pos <= terrain_peering_bits[i]) { - terrain_peering_bits[i] += 1; - } - } - } -} - -void TileData::move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) { - if (terrain_set == p_terrain_set) { - for (int i = 0; i < 16; i++) { - if (p_from_index == terrain_peering_bits[i]) { - terrain_peering_bits[i] = (p_from_index < p_to_pos) ? p_to_pos - 1 : p_to_pos; - } else { - if (p_from_index < terrain_peering_bits[i]) { - terrain_peering_bits[i] -= 1; - } - if (p_to_pos <= terrain_peering_bits[i]) { - terrain_peering_bits[i] += 1; - } - } - } - } -} - -void TileData::remove_terrain(int p_terrain_set, int p_index) { - if (terrain_set == p_terrain_set) { - if (terrain == p_index) { - terrain = -1; - } else if (terrain > p_index) { - terrain -= 1; - } - - for (int i = 0; i < 16; i++) { - if (terrain_peering_bits[i] == p_index) { - terrain_peering_bits[i] = -1; - } else if (terrain_peering_bits[i] > p_index) { - terrain_peering_bits[i] -= 1; - } - } - } -} - -void TileData::add_navigation_layer(int p_to_pos) { - if (p_to_pos < 0) { - p_to_pos = navigation.size(); - } - ERR_FAIL_INDEX(p_to_pos, navigation.size() + 1); - navigation.insert(p_to_pos, NavigationLayerTileData()); -} - -void TileData::move_navigation_layer(int p_from_index, int p_to_pos) { - ERR_FAIL_INDEX(p_from_index, navigation.size()); - ERR_FAIL_INDEX(p_to_pos, navigation.size() + 1); - navigation.insert(p_to_pos, navigation[p_from_index]); - navigation.remove_at(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); -} - -void TileData::remove_navigation_layer(int p_index) { - ERR_FAIL_INDEX(p_index, navigation.size()); - navigation.remove_at(p_index); -} - -void TileData::add_custom_data_layer(int p_to_pos) { - if (p_to_pos < 0) { - p_to_pos = custom_data.size(); - } - ERR_FAIL_INDEX(p_to_pos, custom_data.size() + 1); - custom_data.insert(p_to_pos, Variant()); -} - -void TileData::move_custom_data_layer(int p_from_index, int p_to_pos) { - ERR_FAIL_INDEX(p_from_index, custom_data.size()); - ERR_FAIL_INDEX(p_to_pos, custom_data.size() + 1); - custom_data.insert(p_to_pos, custom_data[p_from_index]); - custom_data.remove_at(p_to_pos < p_from_index ? p_from_index + 1 : p_from_index); -} - -void TileData::remove_custom_data_layer(int p_index) { - ERR_FAIL_INDEX(p_index, custom_data.size()); - custom_data.remove_at(p_index); -} - -void TileData::set_allow_transform(bool p_allow_transform) { - allow_transform = p_allow_transform; -} - -bool TileData::is_allowing_transform() const { - return allow_transform; -} - -TileData *TileData::duplicate() { - TileData *output = memnew(TileData); - output->tile_set = tile_set; - - output->allow_transform = allow_transform; - - // Rendering - output->flip_h = flip_h; - output->flip_v = flip_v; - output->transpose = transpose; - output->texture_origin = texture_origin; - output->material = material; - output->modulate = modulate; - output->z_index = z_index; - output->y_sort_origin = y_sort_origin; - output->occluders = occluders; - // Physics - output->physics = physics; - // Terrain - output->terrain_set = -1; - memcpy(output->terrain_peering_bits, terrain_peering_bits, 16 * sizeof(int)); - // Navigation - output->navigation = navigation; - // Misc - output->probability = probability; - // Custom data - output->custom_data = custom_data; - - return output; -} - -// Rendering -void TileData::set_flip_h(bool p_flip_h) { - ERR_FAIL_COND_MSG(!allow_transform && p_flip_h, "Transform is only allowed for alternative tiles (with its alternative_id != 0)"); - flip_h = p_flip_h; - emit_signal(SNAME("changed")); -} -bool TileData::get_flip_h() const { - return flip_h; -} - -void TileData::set_flip_v(bool p_flip_v) { - ERR_FAIL_COND_MSG(!allow_transform && p_flip_v, "Transform is only allowed for alternative tiles (with its alternative_id != 0)"); - flip_v = p_flip_v; - emit_signal(SNAME("changed")); -} - -bool TileData::get_flip_v() const { - return flip_v; -} - -void TileData::set_transpose(bool p_transpose) { - ERR_FAIL_COND_MSG(!allow_transform && p_transpose, "Transform is only allowed for alternative tiles (with its alternative_id != 0)"); - transpose = p_transpose; - emit_signal(SNAME("changed")); -} -bool TileData::get_transpose() const { - return transpose; -} - -void TileData::set_texture_origin(Vector2i p_texture_origin) { - texture_origin = p_texture_origin; - emit_signal(SNAME("changed")); -} - -Vector2i TileData::get_texture_origin() const { - return texture_origin; -} - -void TileData::set_material(Ref p_material) { - material = p_material; - emit_signal(SNAME("changed")); -} -Ref TileData::get_material() const { - return material; -} - -void TileData::set_modulate(Color p_modulate) { - modulate = p_modulate; - emit_signal(SNAME("changed")); -} -Color TileData::get_modulate() const { - return modulate; -} - -void TileData::set_z_index(int p_z_index) { - z_index = p_z_index; - emit_signal(SNAME("changed")); -} -int TileData::get_z_index() const { - return z_index; -} - -void TileData::set_y_sort_origin(int p_y_sort_origin) { - y_sort_origin = p_y_sort_origin; - emit_signal(SNAME("changed")); -} -int TileData::get_y_sort_origin() const { - return y_sort_origin; -} - -void TileData::set_occluder(int p_layer_id, Ref p_occluder_polygon) { - ERR_FAIL_INDEX(p_layer_id, occluders.size()); - occluders.write[p_layer_id].occluder = p_occluder_polygon; - occluders.write[p_layer_id].transformed_occluders.clear(); - emit_signal(SNAME("changed")); -} - -Ref TileData::get_occluder(int p_layer_id, bool p_flip_h, bool p_flip_v, bool p_transpose) const { - ERR_FAIL_INDEX_V(p_layer_id, occluders.size(), Ref()); - - const OcclusionLayerTileData &layer_tile_data = occluders[p_layer_id]; - - int key = int(p_flip_h) | int(p_flip_v) << 1 | int(p_transpose) << 2; - if (key == 0) { - return layer_tile_data.occluder; - } - - if (layer_tile_data.occluder.is_null()) { - return Ref(); - } - - HashMap>::Iterator I = layer_tile_data.transformed_occluders.find(key); - if (!I) { - Ref transformed_polygon; - transformed_polygon.instantiate(); - transformed_polygon->set_polygon(get_transformed_vertices(layer_tile_data.occluder->get_polygon(), p_flip_h, p_flip_v, p_transpose)); - layer_tile_data.transformed_occluders[key] = transformed_polygon; - return transformed_polygon; - } else { - return I->value; - } -} - -// Physics -void TileData::set_constant_linear_velocity(int p_layer_id, const Vector2 &p_velocity) { - ERR_FAIL_INDEX(p_layer_id, physics.size()); - physics.write[p_layer_id].linear_velocity = p_velocity; - emit_signal(SNAME("changed")); -} - -Vector2 TileData::get_constant_linear_velocity(int p_layer_id) const { - ERR_FAIL_INDEX_V(p_layer_id, physics.size(), Vector2()); - return physics[p_layer_id].linear_velocity; -} - -void TileData::set_constant_angular_velocity(int p_layer_id, real_t p_velocity) { - ERR_FAIL_INDEX(p_layer_id, physics.size()); - physics.write[p_layer_id].angular_velocity = p_velocity; - emit_signal(SNAME("changed")); -} - -real_t TileData::get_constant_angular_velocity(int p_layer_id) const { - ERR_FAIL_INDEX_V(p_layer_id, physics.size(), 0.0); - return physics[p_layer_id].angular_velocity; -} - -void TileData::set_collision_polygons_count(int p_layer_id, int p_polygons_count) { - ERR_FAIL_INDEX(p_layer_id, physics.size()); - ERR_FAIL_COND(p_polygons_count < 0); - if (p_polygons_count == physics.write[p_layer_id].polygons.size()) { - return; - } - physics.write[p_layer_id].polygons.resize(p_polygons_count); - notify_property_list_changed(); - emit_signal(SNAME("changed")); -} - -int TileData::get_collision_polygons_count(int p_layer_id) const { - ERR_FAIL_INDEX_V(p_layer_id, physics.size(), 0); - return physics[p_layer_id].polygons.size(); -} - -void TileData::add_collision_polygon(int p_layer_id) { - ERR_FAIL_INDEX(p_layer_id, physics.size()); - physics.write[p_layer_id].polygons.push_back(PhysicsLayerTileData::PolygonShapeTileData()); - emit_signal(SNAME("changed")); -} - -void TileData::remove_collision_polygon(int p_layer_id, int p_polygon_index) { - ERR_FAIL_INDEX(p_layer_id, physics.size()); - ERR_FAIL_INDEX(p_polygon_index, physics[p_layer_id].polygons.size()); - physics.write[p_layer_id].polygons.remove_at(p_polygon_index); - emit_signal(SNAME("changed")); -} - -void TileData::set_collision_polygon_points(int p_layer_id, int p_polygon_index, Vector p_polygon) { - ERR_FAIL_INDEX(p_layer_id, physics.size()); - ERR_FAIL_INDEX(p_polygon_index, physics[p_layer_id].polygons.size()); - ERR_FAIL_COND_MSG(p_polygon.size() != 0 && p_polygon.size() < 3, "Invalid polygon. Needs either 0 or more than 3 points."); - - TileData::PhysicsLayerTileData::PolygonShapeTileData &polygon_shape_tile_data = physics.write[p_layer_id].polygons.write[p_polygon_index]; - - if (p_polygon.is_empty()) { - polygon_shape_tile_data.shapes.clear(); - } else { - // Decompose into convex shapes. - Vector> decomp = Geometry2D::decompose_polygon_in_convex(p_polygon); - ERR_FAIL_COND_MSG(decomp.is_empty(), "Could not decompose the polygon into convex shapes."); - - polygon_shape_tile_data.shapes.resize(decomp.size()); - for (int i = 0; i < decomp.size(); i++) { - Ref shape; - shape.instantiate(); - shape->set_points(decomp[i]); - polygon_shape_tile_data.shapes[i] = shape; - } - } - polygon_shape_tile_data.transformed_shapes.clear(); - polygon_shape_tile_data.polygon = p_polygon; - emit_signal(SNAME("changed")); -} - -Vector TileData::get_collision_polygon_points(int p_layer_id, int p_polygon_index) const { - ERR_FAIL_INDEX_V(p_layer_id, physics.size(), Vector()); - ERR_FAIL_INDEX_V(p_polygon_index, physics[p_layer_id].polygons.size(), Vector()); - return physics[p_layer_id].polygons[p_polygon_index].polygon; -} - -void TileData::set_collision_polygon_one_way(int p_layer_id, int p_polygon_index, bool p_one_way) { - ERR_FAIL_INDEX(p_layer_id, physics.size()); - ERR_FAIL_INDEX(p_polygon_index, physics[p_layer_id].polygons.size()); - physics.write[p_layer_id].polygons.write[p_polygon_index].one_way = p_one_way; - emit_signal(SNAME("changed")); -} - -bool TileData::is_collision_polygon_one_way(int p_layer_id, int p_polygon_index) const { - ERR_FAIL_INDEX_V(p_layer_id, physics.size(), false); - ERR_FAIL_INDEX_V(p_polygon_index, physics[p_layer_id].polygons.size(), false); - return physics[p_layer_id].polygons[p_polygon_index].one_way; -} - -void TileData::set_collision_polygon_one_way_margin(int p_layer_id, int p_polygon_index, float p_one_way_margin) { - ERR_FAIL_INDEX(p_layer_id, physics.size()); - ERR_FAIL_INDEX(p_polygon_index, physics[p_layer_id].polygons.size()); - physics.write[p_layer_id].polygons.write[p_polygon_index].one_way_margin = p_one_way_margin; - emit_signal(SNAME("changed")); -} - -float TileData::get_collision_polygon_one_way_margin(int p_layer_id, int p_polygon_index) const { - ERR_FAIL_INDEX_V(p_layer_id, physics.size(), 0.0); - ERR_FAIL_INDEX_V(p_polygon_index, physics[p_layer_id].polygons.size(), 0.0); - return physics[p_layer_id].polygons[p_polygon_index].one_way_margin; -} - -int TileData::get_collision_polygon_shapes_count(int p_layer_id, int p_polygon_index) const { - ERR_FAIL_INDEX_V(p_layer_id, physics.size(), 0); - ERR_FAIL_INDEX_V(p_polygon_index, physics[p_layer_id].polygons.size(), 0); - return physics[p_layer_id].polygons[p_polygon_index].shapes.size(); -} - -Ref TileData::get_collision_polygon_shape(int p_layer_id, int p_polygon_index, int shape_index, bool p_flip_h, bool p_flip_v, bool p_transpose) const { - ERR_FAIL_INDEX_V(p_layer_id, physics.size(), Ref()); - ERR_FAIL_INDEX_V(p_polygon_index, physics[p_layer_id].polygons.size(), Ref()); - ERR_FAIL_INDEX_V(shape_index, (int)physics[p_layer_id].polygons[p_polygon_index].shapes.size(), Ref()); - - const PhysicsLayerTileData &layer_tile_data = physics[p_layer_id]; - const PhysicsLayerTileData::PolygonShapeTileData &shapes_data = layer_tile_data.polygons[p_polygon_index]; - - int key = int(p_flip_h) | int(p_flip_v) << 1 | int(p_transpose) << 2; - if (key == 0) { - return shapes_data.shapes[shape_index]; - } - if (shapes_data.shapes[shape_index].is_null()) { - return Ref(); - } - - HashMap>>::Iterator I = shapes_data.transformed_shapes.find(key); - if (!I) { - int size = shapes_data.shapes.size(); - shapes_data.transformed_shapes[key].resize(size); - for (int i = 0; i < size; i++) { - Ref transformed_polygon; - transformed_polygon.instantiate(); - transformed_polygon->set_points(get_transformed_vertices(shapes_data.shapes[shape_index]->get_points(), p_flip_h, p_flip_v, p_transpose)); - shapes_data.transformed_shapes[key][i] = transformed_polygon; - } - return shapes_data.transformed_shapes[key][shape_index]; - } else { - return I->value[shape_index]; - } -} - -// Terrain -void TileData::set_terrain_set(int p_terrain_set) { - ERR_FAIL_COND(p_terrain_set < -1); - if (p_terrain_set == terrain_set) { - return; - } - if (tile_set) { - ERR_FAIL_COND(p_terrain_set >= tile_set->get_terrain_sets_count()); - terrain = -1; - for (int i = 0; i < 16; i++) { - terrain_peering_bits[i] = -1; - } - } - terrain_set = p_terrain_set; - notify_property_list_changed(); - emit_signal(SNAME("changed")); -} - -int TileData::get_terrain_set() const { - return terrain_set; -} - -void TileData::set_terrain(int p_terrain) { - ERR_FAIL_COND(terrain_set < 0); - ERR_FAIL_COND(p_terrain < -1); - if (tile_set) { - ERR_FAIL_COND(p_terrain >= tile_set->get_terrains_count(terrain_set)); - } - terrain = p_terrain; - emit_signal(SNAME("changed")); -} - -int TileData::get_terrain() const { - return terrain; -} - -void TileData::set_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit, int p_terrain_index) { - ERR_FAIL_INDEX(p_peering_bit, TileSet::CellNeighbor::CELL_NEIGHBOR_MAX); - ERR_FAIL_COND(terrain_set < 0); - ERR_FAIL_COND(p_terrain_index < -1); - if (tile_set) { - ERR_FAIL_COND(p_terrain_index >= tile_set->get_terrains_count(terrain_set)); - ERR_FAIL_COND(!is_valid_terrain_peering_bit(p_peering_bit)); - } - terrain_peering_bits[p_peering_bit] = p_terrain_index; - emit_signal(SNAME("changed")); -} - -int TileData::get_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit) const { - ERR_FAIL_COND_V(!is_valid_terrain_peering_bit(p_peering_bit), -1); - return terrain_peering_bits[p_peering_bit]; -} - -bool TileData::is_valid_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit) const { - ERR_FAIL_NULL_V(tile_set, false); - - return tile_set->is_valid_terrain_peering_bit(terrain_set, p_peering_bit); -} - -TileSet::TerrainsPattern TileData::get_terrains_pattern() const { - ERR_FAIL_NULL_V(tile_set, TileSet::TerrainsPattern()); - - TileSet::TerrainsPattern output(tile_set, terrain_set); - output.set_terrain(terrain); - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - if (tile_set->is_valid_terrain_peering_bit(terrain_set, TileSet::CellNeighbor(i))) { - output.set_terrain_peering_bit(TileSet::CellNeighbor(i), get_terrain_peering_bit(TileSet::CellNeighbor(i))); - } - } - return output; -} - -// Navigation -void TileData::set_navigation_polygon(int p_layer_id, Ref p_navigation_polygon) { - ERR_FAIL_INDEX(p_layer_id, navigation.size()); - navigation.write[p_layer_id].navigation_polygon = p_navigation_polygon; - navigation.write[p_layer_id].transformed_navigation_polygon.clear(); - emit_signal(SNAME("changed")); -} - -Ref TileData::get_navigation_polygon(int p_layer_id, bool p_flip_h, bool p_flip_v, bool p_transpose) const { - ERR_FAIL_INDEX_V(p_layer_id, navigation.size(), Ref()); - - const NavigationLayerTileData &layer_tile_data = navigation[p_layer_id]; - - int key = int(p_flip_h) | int(p_flip_v) << 1 | int(p_transpose) << 2; - if (key == 0) { - return layer_tile_data.navigation_polygon; - } - - if (layer_tile_data.navigation_polygon.is_null()) { - return Ref(); - } - - HashMap>::Iterator I = layer_tile_data.transformed_navigation_polygon.find(key); - if (!I) { - Ref transformed_polygon; - transformed_polygon.instantiate(); - - PackedVector2Array new_points = get_transformed_vertices(layer_tile_data.navigation_polygon->get_vertices(), p_flip_h, p_flip_v, p_transpose); - transformed_polygon->set_vertices(new_points); - - for (int i = 0; i < layer_tile_data.navigation_polygon->get_outline_count(); i++) { - PackedVector2Array new_outline = get_transformed_vertices(layer_tile_data.navigation_polygon->get_outline(i), p_flip_h, p_flip_v, p_transpose); - transformed_polygon->add_outline(new_outline); - } - - PackedInt32Array indices; - indices.resize(new_points.size()); - int *w = indices.ptrw(); - for (int i = 0; i < new_points.size(); i++) { - w[i] = i; - } - transformed_polygon->add_polygon(indices); - layer_tile_data.transformed_navigation_polygon[key] = transformed_polygon; - return transformed_polygon; - } else { - return I->value; - } -} - -// Misc -void TileData::set_probability(float p_probability) { - ERR_FAIL_COND(p_probability < 0.0); - probability = p_probability; - emit_signal(SNAME("changed")); -} -float TileData::get_probability() const { - return probability; -} - -// Custom data -void TileData::set_custom_data(String p_layer_name, Variant p_value) { - ERR_FAIL_NULL(tile_set); - int p_layer_id = tile_set->get_custom_data_layer_by_name(p_layer_name); - ERR_FAIL_COND_MSG(p_layer_id < 0, vformat("TileSet has no layer with name: %s", p_layer_name)); - set_custom_data_by_layer_id(p_layer_id, p_value); -} - -Variant TileData::get_custom_data(String p_layer_name) const { - ERR_FAIL_NULL_V(tile_set, Variant()); - int p_layer_id = tile_set->get_custom_data_layer_by_name(p_layer_name); - ERR_FAIL_COND_V_MSG(p_layer_id < 0, Variant(), vformat("TileSet has no layer with name: %s", p_layer_name)); - return get_custom_data_by_layer_id(p_layer_id); -} - -void TileData::set_custom_data_by_layer_id(int p_layer_id, Variant p_value) { - ERR_FAIL_INDEX(p_layer_id, custom_data.size()); - custom_data.write[p_layer_id] = p_value; - emit_signal(SNAME("changed")); -} - -Variant TileData::get_custom_data_by_layer_id(int p_layer_id) const { - ERR_FAIL_INDEX_V(p_layer_id, custom_data.size(), Variant()); - return custom_data[p_layer_id]; -} - -PackedVector2Array TileData::get_transformed_vertices(const PackedVector2Array &p_vertices, bool p_flip_h, bool p_flip_v, bool p_transpose) { - const Vector2 *r = p_vertices.ptr(); - int size = p_vertices.size(); - - PackedVector2Array new_points; - new_points.resize(size); - Vector2 *w = new_points.ptrw(); - - for (int i = 0; i < size; i++) { - Vector2 v; - if (p_transpose) { - v = Vector2(r[i].y, r[i].x); - } else { - v = r[i]; - } - - if (p_flip_h) { - v.x *= -1; - } - if (p_flip_v) { - v.y *= -1; - } - w[i] = v; - } - return new_points; -} - -bool TileData::_set(const StringName &p_name, const Variant &p_value) { -#ifndef DISABLE_DEPRECATED - if (p_name == "texture_offset") { - texture_origin = p_value; - return true; - } -#endif - - Vector components = String(p_name).split("/", true, 2); - - if (components.size() == 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) { - // Occlusion layers. - int layer_index = components[0].trim_prefix("occlusion_layer_").to_int(); - ERR_FAIL_COND_V(layer_index < 0, false); - if (components[1] == "polygon") { - Ref polygon = p_value; - - if (layer_index >= occluders.size()) { - if (tile_set) { - return false; - } else { - occluders.resize(layer_index + 1); - } - } - set_occluder(layer_index, polygon); - return true; - } - } else if (components.size() >= 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) { - // Physics layers. - int layer_index = components[0].trim_prefix("physics_layer_").to_int(); - ERR_FAIL_COND_V(layer_index < 0, false); - if (components.size() == 2) { - if (layer_index >= physics.size()) { - if (tile_set) { - return false; - } else { - physics.resize(layer_index + 1); - } - } - if (components[1] == "linear_velocity") { - set_constant_linear_velocity(layer_index, p_value); - return true; - } else if (components[1] == "angular_velocity") { - set_constant_angular_velocity(layer_index, p_value); - return true; - } else if (components[1] == "polygons_count") { - if (p_value.get_type() != Variant::INT) { - return false; - } - set_collision_polygons_count(layer_index, p_value); - return true; - } - } else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) { - int polygon_index = components[1].trim_prefix("polygon_").to_int(); - ERR_FAIL_COND_V(polygon_index < 0, false); - - if (components[2] == "points" || components[2] == "one_way" || components[2] == "one_way_margin") { - if (layer_index >= physics.size()) { - if (tile_set) { - return false; - } else { - physics.resize(layer_index + 1); - } - } - - if (polygon_index >= physics[layer_index].polygons.size()) { - physics.write[layer_index].polygons.resize(polygon_index + 1); - } - } - if (components[2] == "points") { - Vector polygon = p_value; - set_collision_polygon_points(layer_index, polygon_index, polygon); - return true; - } else if (components[2] == "one_way") { - set_collision_polygon_one_way(layer_index, polygon_index, p_value); - return true; - } else if (components[2] == "one_way_margin") { - set_collision_polygon_one_way_margin(layer_index, polygon_index, p_value); - return true; - } - } - } else if (components.size() == 2 && components[0].begins_with("navigation_layer_") && components[0].trim_prefix("navigation_layer_").is_valid_int()) { - // Navigation layers. - int layer_index = components[0].trim_prefix("navigation_layer_").to_int(); - ERR_FAIL_COND_V(layer_index < 0, false); - if (components[1] == "polygon") { - Ref polygon = p_value; - - if (layer_index >= navigation.size()) { - if (tile_set) { - return false; - } else { - navigation.resize(layer_index + 1); - } - } - set_navigation_polygon(layer_index, polygon); - return true; - } - } else if (components.size() == 2 && components[0] == "terrains_peering_bit") { - // Terrains. - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - if (components[1] == TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]) { - set_terrain_peering_bit(bit, p_value); - return true; - } - } - return false; - } else if (components.size() == 1 && components[0].begins_with("custom_data_") && components[0].trim_prefix("custom_data_").is_valid_int()) { - // Custom data layers. - int layer_index = components[0].trim_prefix("custom_data_").to_int(); - ERR_FAIL_COND_V(layer_index < 0, false); - - if (layer_index >= custom_data.size()) { - if (tile_set) { - return false; - } else { - custom_data.resize(layer_index + 1); - } - } - set_custom_data_by_layer_id(layer_index, p_value); - - return true; - } - - return false; -} - -bool TileData::_get(const StringName &p_name, Variant &r_ret) const { -#ifndef DISABLE_DEPRECATED - if (p_name == "texture_offset") { - r_ret = texture_origin; - return true; - } -#endif - - Vector components = String(p_name).split("/", true, 2); - - if (tile_set) { - if (components.size() == 2 && components[0].begins_with("occlusion_layer") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) { - // Occlusion layers. - int layer_index = components[0].trim_prefix("occlusion_layer_").to_int(); - ERR_FAIL_COND_V(layer_index < 0, false); - if (layer_index >= occluders.size()) { - return false; - } - if (components[1] == "polygon") { - r_ret = get_occluder(layer_index); - return true; - } - } else if (components.size() >= 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) { - // Physics layers. - int layer_index = components[0].trim_prefix("physics_layer_").to_int(); - ERR_FAIL_COND_V(layer_index < 0, false); - if (layer_index >= physics.size()) { - return false; - } - - if (components.size() == 2) { - if (components[1] == "linear_velocity") { - r_ret = get_constant_linear_velocity(layer_index); - return true; - } else if (components[1] == "angular_velocity") { - r_ret = get_constant_angular_velocity(layer_index); - return true; - } else if (components[1] == "polygons_count") { - r_ret = get_collision_polygons_count(layer_index); - return true; - } - } else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) { - int polygon_index = components[1].trim_prefix("polygon_").to_int(); - ERR_FAIL_COND_V(polygon_index < 0, false); - if (polygon_index >= physics[layer_index].polygons.size()) { - return false; - } - if (components[2] == "points") { - r_ret = get_collision_polygon_points(layer_index, polygon_index); - return true; - } else if (components[2] == "one_way") { - r_ret = is_collision_polygon_one_way(layer_index, polygon_index); - return true; - } else if (components[2] == "one_way_margin") { - r_ret = get_collision_polygon_one_way_margin(layer_index, polygon_index); - return true; - } - } - } else if (components.size() == 2 && components[0] == "terrains_peering_bit") { - // Terrains. - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - if (components[1] == TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]) { - r_ret = terrain_peering_bits[i]; - return true; - } - } - return false; - } else if (components.size() == 2 && components[0].begins_with("navigation_layer_") && components[0].trim_prefix("navigation_layer_").is_valid_int()) { - // Occlusion layers. - int layer_index = components[0].trim_prefix("navigation_layer_").to_int(); - ERR_FAIL_COND_V(layer_index < 0, false); - if (layer_index >= navigation.size()) { - return false; - } - if (components[1] == "polygon") { - r_ret = get_navigation_polygon(layer_index); - return true; - } - } else if (components.size() == 1 && components[0].begins_with("custom_data_") && components[0].trim_prefix("custom_data_").is_valid_int()) { - // Custom data layers. - int layer_index = components[0].trim_prefix("custom_data_").to_int(); - ERR_FAIL_COND_V(layer_index < 0, false); - if (layer_index >= custom_data.size()) { - return false; - } - r_ret = get_custom_data_by_layer_id(layer_index); - return true; - } - } - - return false; -} - -void TileData::_get_property_list(List *p_list) const { - PropertyInfo property_info; - // Add the groups manually. - if (tile_set) { - // Occlusion layers. - p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Rendering", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); - for (int i = 0; i < occluders.size(); i++) { - // occlusion_layer_%d/polygon - property_info = PropertyInfo(Variant::OBJECT, vformat("occlusion_layer_%d/%s", i, PNAME("polygon")), PROPERTY_HINT_RESOURCE_TYPE, "OccluderPolygon2D", PROPERTY_USAGE_DEFAULT); - if (occluders[i].occluder.is_null()) { - property_info.usage ^= PROPERTY_USAGE_STORAGE; - } - p_list->push_back(property_info); - } - - // Physics layers. - p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Physics", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); - for (int i = 0; i < physics.size(); i++) { - p_list->push_back(PropertyInfo(Variant::VECTOR2, vformat("physics_layer_%d/%s", i, PNAME("linear_velocity")), PROPERTY_HINT_NONE)); - p_list->push_back(PropertyInfo(Variant::FLOAT, vformat("physics_layer_%d/%s", i, PNAME("angular_velocity")), PROPERTY_HINT_NONE)); - p_list->push_back(PropertyInfo(Variant::INT, vformat("physics_layer_%d/%s", i, PNAME("polygons_count")), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); - - for (int j = 0; j < physics[i].polygons.size(); j++) { - // physics_layer_%d/points - property_info = PropertyInfo(Variant::ARRAY, vformat("physics_layer_%d/polygon_%d/%s", i, j, PNAME("points")), PROPERTY_HINT_ARRAY_TYPE, "Vector2", PROPERTY_USAGE_DEFAULT); - if (physics[i].polygons[j].polygon.is_empty()) { - property_info.usage ^= PROPERTY_USAGE_STORAGE; - } - p_list->push_back(property_info); - - // physics_layer_%d/polygon_%d/one_way - property_info = PropertyInfo(Variant::BOOL, vformat("physics_layer_%d/polygon_%d/%s", i, j, PNAME("one_way"))); - if (physics[i].polygons[j].one_way == false) { - property_info.usage ^= PROPERTY_USAGE_STORAGE; - } - p_list->push_back(property_info); - - // physics_layer_%d/polygon_%d/one_way_margin - property_info = PropertyInfo(Variant::FLOAT, vformat("physics_layer_%d/polygon_%d/%s", i, j, PNAME("one_way_margin"))); - if (physics[i].polygons[j].one_way_margin == 1.0) { - property_info.usage ^= PROPERTY_USAGE_STORAGE; - } - p_list->push_back(property_info); - } - } - - // Terrain data - if (terrain_set >= 0) { - p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Terrains", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - if (is_valid_terrain_peering_bit(bit)) { - property_info = PropertyInfo(Variant::INT, vformat("%s/%s", PNAME("terrains_peering_bit"), TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i])); - if (get_terrain_peering_bit(bit) == -1) { - property_info.usage ^= PROPERTY_USAGE_STORAGE; - } - p_list->push_back(property_info); - } - } - } - - // Navigation layers. - p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Navigation", ""), PROPERTY_HINT_NONE, "", PROPERTY_USAGE_GROUP)); - for (int i = 0; i < navigation.size(); i++) { - property_info = PropertyInfo(Variant::OBJECT, vformat("navigation_layer_%d/%s", i, PNAME("polygon")), PROPERTY_HINT_RESOURCE_TYPE, "NavigationPolygon", PROPERTY_USAGE_DEFAULT); - if (navigation[i].navigation_polygon.is_null()) { - property_info.usage ^= PROPERTY_USAGE_STORAGE; - } - p_list->push_back(property_info); - } - - // Custom data layers. - p_list->push_back(PropertyInfo(Variant::NIL, GNAME("Custom Data", "custom_data_"), PROPERTY_HINT_NONE, "custom_data_", PROPERTY_USAGE_GROUP)); - for (int i = 0; i < custom_data.size(); i++) { - Variant default_val; - Callable::CallError error; - Variant::construct(custom_data[i].get_type(), default_val, nullptr, 0, error); - property_info = PropertyInfo(tile_set->get_custom_data_layer_type(i), vformat("custom_data_%d", i), PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NIL_IS_VARIANT); - if (custom_data[i] == default_val) { - property_info.usage ^= PROPERTY_USAGE_STORAGE; - } - p_list->push_back(property_info); - } - } -} - -void TileData::_bind_methods() { - // Rendering. - ClassDB::bind_method(D_METHOD("set_flip_h", "flip_h"), &TileData::set_flip_h); - ClassDB::bind_method(D_METHOD("get_flip_h"), &TileData::get_flip_h); - ClassDB::bind_method(D_METHOD("set_flip_v", "flip_v"), &TileData::set_flip_v); - ClassDB::bind_method(D_METHOD("get_flip_v"), &TileData::get_flip_v); - ClassDB::bind_method(D_METHOD("set_transpose", "transpose"), &TileData::set_transpose); - ClassDB::bind_method(D_METHOD("get_transpose"), &TileData::get_transpose); - ClassDB::bind_method(D_METHOD("set_material", "material"), &TileData::set_material); - ClassDB::bind_method(D_METHOD("get_material"), &TileData::get_material); - ClassDB::bind_method(D_METHOD("set_texture_origin", "texture_origin"), &TileData::set_texture_origin); - ClassDB::bind_method(D_METHOD("get_texture_origin"), &TileData::get_texture_origin); - ClassDB::bind_method(D_METHOD("set_modulate", "modulate"), &TileData::set_modulate); - ClassDB::bind_method(D_METHOD("get_modulate"), &TileData::get_modulate); - ClassDB::bind_method(D_METHOD("set_z_index", "z_index"), &TileData::set_z_index); - ClassDB::bind_method(D_METHOD("get_z_index"), &TileData::get_z_index); - ClassDB::bind_method(D_METHOD("set_y_sort_origin", "y_sort_origin"), &TileData::set_y_sort_origin); - ClassDB::bind_method(D_METHOD("get_y_sort_origin"), &TileData::get_y_sort_origin); - - ClassDB::bind_method(D_METHOD("set_occluder", "layer_id", "occluder_polygon"), &TileData::set_occluder); - ClassDB::bind_method(D_METHOD("get_occluder", "layer_id", "flip_h", "flip_v", "transpose"), &TileData::get_occluder, DEFVAL(false), DEFVAL(false), DEFVAL(false)); - - // Physics. - ClassDB::bind_method(D_METHOD("set_constant_linear_velocity", "layer_id", "velocity"), &TileData::set_constant_linear_velocity); - ClassDB::bind_method(D_METHOD("get_constant_linear_velocity", "layer_id"), &TileData::get_constant_linear_velocity); - ClassDB::bind_method(D_METHOD("set_constant_angular_velocity", "layer_id", "velocity"), &TileData::set_constant_angular_velocity); - ClassDB::bind_method(D_METHOD("get_constant_angular_velocity", "layer_id"), &TileData::get_constant_angular_velocity); - ClassDB::bind_method(D_METHOD("set_collision_polygons_count", "layer_id", "polygons_count"), &TileData::set_collision_polygons_count); - ClassDB::bind_method(D_METHOD("get_collision_polygons_count", "layer_id"), &TileData::get_collision_polygons_count); - ClassDB::bind_method(D_METHOD("add_collision_polygon", "layer_id"), &TileData::add_collision_polygon); - ClassDB::bind_method(D_METHOD("remove_collision_polygon", "layer_id", "polygon_index"), &TileData::remove_collision_polygon); - ClassDB::bind_method(D_METHOD("set_collision_polygon_points", "layer_id", "polygon_index", "polygon"), &TileData::set_collision_polygon_points); - ClassDB::bind_method(D_METHOD("get_collision_polygon_points", "layer_id", "polygon_index"), &TileData::get_collision_polygon_points); - ClassDB::bind_method(D_METHOD("set_collision_polygon_one_way", "layer_id", "polygon_index", "one_way"), &TileData::set_collision_polygon_one_way); - ClassDB::bind_method(D_METHOD("is_collision_polygon_one_way", "layer_id", "polygon_index"), &TileData::is_collision_polygon_one_way); - ClassDB::bind_method(D_METHOD("set_collision_polygon_one_way_margin", "layer_id", "polygon_index", "one_way_margin"), &TileData::set_collision_polygon_one_way_margin); - ClassDB::bind_method(D_METHOD("get_collision_polygon_one_way_margin", "layer_id", "polygon_index"), &TileData::get_collision_polygon_one_way_margin); - - // Terrain - ClassDB::bind_method(D_METHOD("set_terrain_set", "terrain_set"), &TileData::set_terrain_set); - ClassDB::bind_method(D_METHOD("get_terrain_set"), &TileData::get_terrain_set); - ClassDB::bind_method(D_METHOD("set_terrain", "terrain"), &TileData::set_terrain); - ClassDB::bind_method(D_METHOD("get_terrain"), &TileData::get_terrain); - ClassDB::bind_method(D_METHOD("set_terrain_peering_bit", "peering_bit", "terrain"), &TileData::set_terrain_peering_bit); - ClassDB::bind_method(D_METHOD("get_terrain_peering_bit", "peering_bit"), &TileData::get_terrain_peering_bit); - - // Navigation - ClassDB::bind_method(D_METHOD("set_navigation_polygon", "layer_id", "navigation_polygon"), &TileData::set_navigation_polygon); - ClassDB::bind_method(D_METHOD("get_navigation_polygon", "layer_id", "flip_h", "flip_v", "transpose"), &TileData::get_navigation_polygon, DEFVAL(false), DEFVAL(false), DEFVAL(false)); - - // Misc. - ClassDB::bind_method(D_METHOD("set_probability", "probability"), &TileData::set_probability); - ClassDB::bind_method(D_METHOD("get_probability"), &TileData::get_probability); - - // Custom data. - ClassDB::bind_method(D_METHOD("set_custom_data", "layer_name", "value"), &TileData::set_custom_data); - ClassDB::bind_method(D_METHOD("get_custom_data", "layer_name"), &TileData::get_custom_data); - ClassDB::bind_method(D_METHOD("set_custom_data_by_layer_id", "layer_id", "value"), &TileData::set_custom_data_by_layer_id); - ClassDB::bind_method(D_METHOD("get_custom_data_by_layer_id", "layer_id"), &TileData::get_custom_data_by_layer_id); - - ADD_GROUP("Rendering", ""); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_h"), "set_flip_h", "get_flip_h"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_v"), "set_flip_v", "get_flip_v"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "transpose"), "set_transpose", "get_transpose"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "texture_origin", PROPERTY_HINT_NONE, "suffix:px"), "set_texture_origin", "get_texture_origin"); - ADD_PROPERTY(PropertyInfo(Variant::COLOR, "modulate"), "set_modulate", "get_modulate"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "CanvasItemMaterial,ShaderMaterial"), "set_material", "get_material"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "z_index"), "set_z_index", "get_z_index"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "y_sort_origin"), "set_y_sort_origin", "get_y_sort_origin"); - - ADD_GROUP("Terrains", ""); - ADD_PROPERTY(PropertyInfo(Variant::INT, "terrain_set"), "set_terrain_set", "get_terrain_set"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "terrain"), "set_terrain", "get_terrain"); - - ADD_GROUP("Miscellaneous", ""); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "probability"), "set_probability", "get_probability"); - - ADD_SIGNAL(MethodInfo("changed")); -} diff --git a/scene/resources/tile_set.h b/scene/resources/tile_set.h deleted file mode 100644 index f26ed461c3..0000000000 --- a/scene/resources/tile_set.h +++ /dev/null @@ -1,989 +0,0 @@ -/**************************************************************************/ -/* tile_set.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 TILE_SET_H -#define TILE_SET_H - -#include "core/io/resource.h" -#include "core/object/object.h" -#include "core/templates/local_vector.h" -#include "core/templates/rb_set.h" -#include "scene/2d/light_occluder_2d.h" -#include "scene/main/canvas_item.h" -#include "scene/resources/concave_polygon_shape_2d.h" -#include "scene/resources/convex_polygon_shape_2d.h" -#include "scene/resources/image_texture.h" -#include "scene/resources/navigation_polygon.h" -#include "scene/resources/packed_scene.h" -#include "scene/resources/physics_material.h" -#include "scene/resources/shape_2d.h" - -#ifndef DISABLE_DEPRECATED -#include "scene/resources/shader.h" -#endif - -class TileMap; -class TileSetSource; -class TileSetAtlasSource; -class TileData; - -// Forward-declare the plugins. -class TileSetPlugin; -class TileSetPluginAtlasRendering; -class TileSetPluginAtlasPhysics; -class TileSetPluginAtlasNavigation; - -union TileMapCell { - struct { - int16_t source_id; - int16_t coord_x; - int16_t coord_y; - int16_t alternative_tile; - }; - - uint64_t _u64t; - - static uint32_t hash(const TileMapCell &p_hash) { - return hash_one_uint64(p_hash._u64t); - } - - TileMapCell(int p_source_id = -1, Vector2i p_atlas_coords = Vector2i(-1, -1), int p_alternative_tile = -1) { // default are INVALID_SOURCE, INVALID_ATLAS_COORDS, INVALID_TILE_ALTERNATIVE - source_id = p_source_id; - set_atlas_coords(p_atlas_coords); - alternative_tile = p_alternative_tile; - } - - Vector2i get_atlas_coords() const { - return Vector2i(coord_x, coord_y); - } - - void set_atlas_coords(const Vector2i &r_coords) { - coord_x = r_coords.x; - coord_y = r_coords.y; - } - - bool operator<(const TileMapCell &p_other) const { - if (source_id == p_other.source_id) { - if (coord_x == p_other.coord_x) { - if (coord_y == p_other.coord_y) { - return alternative_tile < p_other.alternative_tile; - } else { - return coord_y < p_other.coord_y; - } - } else { - return coord_x < p_other.coord_x; - } - } else { - return source_id < p_other.source_id; - } - } - - bool operator!=(const TileMapCell &p_other) const { - return !(source_id == p_other.source_id && coord_x == p_other.coord_x && coord_y == p_other.coord_y && alternative_tile == p_other.alternative_tile); - } - bool operator==(const TileMapCell &p_other) const { - return source_id == p_other.source_id && coord_x == p_other.coord_x && coord_y == p_other.coord_y && alternative_tile == p_other.alternative_tile; - } -}; - -class TileMapPattern : public Resource { - GDCLASS(TileMapPattern, Resource); - - Size2i size; - HashMap pattern; - - void _set_tile_data(const Vector &p_data); - Vector _get_tile_data() const; - -protected: - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List *p_list) const; - - static void _bind_methods(); - -public: - void set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile = 0); - bool has_cell(const Vector2i &p_coords) const; - void remove_cell(const Vector2i &p_coords, bool p_update_size = true); - int get_cell_source_id(const Vector2i &p_coords) const; - Vector2i get_cell_atlas_coords(const Vector2i &p_coords) const; - int get_cell_alternative_tile(const Vector2i &p_coords) const; - - const HashMap &get_pattern() const { return pattern; } - TypedArray get_used_cells() const; - - Size2i get_size() const; - void set_size(const Size2i &p_size); - bool is_empty() const; - - void clear(); -}; - -class TileSet : public Resource { - GDCLASS(TileSet, Resource); - -#ifndef DISABLE_DEPRECATED -private: - struct CompatibilityShapeData { - Vector2i autotile_coords; - bool one_way; - float one_way_margin; - Ref shape; - Transform2D transform; - }; - - struct CompatibilityTileData { - String name; - Ref texture; - Vector2 tex_offset; - Ref material; - Rect2 region; - int tile_mode = 0; - Color modulate = Color(1, 1, 1); - - // Atlas or autotiles data - int autotile_bitmask_mode = 0; - Vector2 autotile_icon_coordinate; - Size2i autotile_tile_size = Size2i(16, 16); - - int autotile_spacing = 0; - HashMap autotile_bitmask_flags; - HashMap> autotile_occluder_map; - HashMap> autotile_navpoly_map; - HashMap autotile_priority_map; - HashMap autotile_z_index_map; - - Vector shapes; - Ref occluder; - Vector2 occluder_offset; - Ref navigation; - Vector2 navigation_offset; - int z_index = 0; - }; - - enum CompatibilityTileMode { - COMPATIBILITY_TILE_MODE_SINGLE_TILE = 0, - COMPATIBILITY_TILE_MODE_AUTO_TILE, - COMPATIBILITY_TILE_MODE_ATLAS_TILE, - }; - - HashMap compatibility_data; - HashMap compatibility_tilemap_mapping_tile_modes; - HashMap> compatibility_tilemap_mapping; - HashMap compatibility_size_count; - - void _compatibility_conversion(); - -public: - // Format of output array [source_id, atlas_coords, alternative] - Array compatibility_tilemap_map(int p_tile_id, Vector2i p_coords, bool p_flip_h, bool p_flip_v, bool p_transpose); -#endif // DISABLE_DEPRECATED - -public: - static const int INVALID_SOURCE; // -1; - - enum CellNeighbor { - CELL_NEIGHBOR_RIGHT_SIDE = 0, - CELL_NEIGHBOR_RIGHT_CORNER, - CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE, - CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER, - CELL_NEIGHBOR_BOTTOM_SIDE, - CELL_NEIGHBOR_BOTTOM_CORNER, - CELL_NEIGHBOR_BOTTOM_LEFT_SIDE, - CELL_NEIGHBOR_BOTTOM_LEFT_CORNER, - CELL_NEIGHBOR_LEFT_SIDE, - CELL_NEIGHBOR_LEFT_CORNER, - CELL_NEIGHBOR_TOP_LEFT_SIDE, - CELL_NEIGHBOR_TOP_LEFT_CORNER, - CELL_NEIGHBOR_TOP_SIDE, - CELL_NEIGHBOR_TOP_CORNER, - CELL_NEIGHBOR_TOP_RIGHT_SIDE, - CELL_NEIGHBOR_TOP_RIGHT_CORNER, - CELL_NEIGHBOR_MAX, - }; - - static const char *CELL_NEIGHBOR_ENUM_TO_TEXT[]; - - enum TerrainMode { - TERRAIN_MODE_MATCH_CORNERS_AND_SIDES = 0, - TERRAIN_MODE_MATCH_CORNERS, - TERRAIN_MODE_MATCH_SIDES, - }; - - enum TileShape { - TILE_SHAPE_SQUARE, - TILE_SHAPE_ISOMETRIC, - TILE_SHAPE_HALF_OFFSET_SQUARE, - TILE_SHAPE_HEXAGON, - }; - - enum TileLayout { - TILE_LAYOUT_STACKED, - TILE_LAYOUT_STACKED_OFFSET, - TILE_LAYOUT_STAIRS_RIGHT, - TILE_LAYOUT_STAIRS_DOWN, - TILE_LAYOUT_DIAMOND_RIGHT, - TILE_LAYOUT_DIAMOND_DOWN, - }; - - enum TileOffsetAxis { - TILE_OFFSET_AXIS_HORIZONTAL, - TILE_OFFSET_AXIS_VERTICAL, - }; - - struct PackedSceneSource { - Ref scene; - Vector2 offset; - }; - - class TerrainsPattern { - bool valid = false; - int terrain = -1; - int bits[TileSet::CELL_NEIGHBOR_MAX]; - bool is_valid_bit[TileSet::CELL_NEIGHBOR_MAX]; - - int not_empty_terrains_count = 0; - - public: - bool is_valid() const; - bool is_erase_pattern() const; - - bool operator<(const TerrainsPattern &p_terrains_pattern) const; - bool operator==(const TerrainsPattern &p_terrains_pattern) const; - bool operator!=(const TerrainsPattern &p_terrains_pattern) const { - return !operator==(p_terrains_pattern); - }; - - void set_terrain(int p_terrain); - int get_terrain() const; - - void set_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit, int p_terrain); - int get_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit) const; - - void from_array(Array p_terrains); - Array as_array() const; - - TerrainsPattern(const TileSet *p_tile_set, int p_terrain_set); - TerrainsPattern() {} - }; - -protected: - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List *p_list) const; - void _validate_property(PropertyInfo &p_property) const; - -private: - // --- TileSet data --- - // Basic shape and layout. - TileShape tile_shape = TILE_SHAPE_SQUARE; - TileLayout tile_layout = TILE_LAYOUT_STACKED; - TileOffsetAxis tile_offset_axis = TILE_OFFSET_AXIS_HORIZONTAL; - Size2i tile_size = Size2i(16, 16); //Size2(64, 64); - - // Rendering. - bool uv_clipping = false; - struct OcclusionLayer { - uint32_t light_mask = 1; - bool sdf_collision = false; - }; - Vector occlusion_layers; - - Ref tile_lines_mesh; - Ref tile_filled_mesh; - mutable bool tile_meshes_dirty = true; - - // Physics - struct PhysicsLayer { - uint32_t collision_layer = 1; - uint32_t collision_mask = 1; - Ref physics_material; - }; - Vector physics_layers; - - // Terrains - struct Terrain { - String name; - Color color; - }; - struct TerrainSet { - TerrainMode mode = TERRAIN_MODE_MATCH_CORNERS_AND_SIDES; - Vector terrains; - }; - Vector terrain_sets; - - HashMap> terrain_meshes; - HashMap>> terrain_peering_bits_meshes; - bool terrain_bits_meshes_dirty = true; - - LocalVector>> per_terrain_pattern_tiles; // Cached data. - bool terrains_cache_dirty = true; - void _update_terrains_cache(); - - // Navigation - struct NavigationLayer { - uint32_t layers = 1; - }; - Vector navigation_layers; - - // CustomData - struct CustomDataLayer { - String name; - Variant::Type type = Variant::NIL; - }; - Vector custom_data_layers; - HashMap custom_data_layers_by_name; - - // Per Atlas source data. - HashMap> sources; - Vector source_ids; - int next_source_id = 0; - // --------------------- - - LocalVector> patterns; - - void _compute_next_source_id(); - void _source_changed(); - - // Tile proxies - RBMap source_level_proxies; - RBMap coords_level_proxies; - RBMap alternative_level_proxies; - - // Helpers - Vector _get_square_terrain_polygon(Vector2i p_size); - Vector _get_square_corner_or_side_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); - Vector _get_square_corner_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); - Vector _get_square_side_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); - - Vector _get_isometric_terrain_polygon(Vector2i p_size); - Vector _get_isometric_corner_or_side_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); - Vector _get_isometric_corner_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); - Vector _get_isometric_side_terrain_peering_bit_polygon(Vector2i p_size, TileSet::CellNeighbor p_bit); - - Vector _get_half_offset_terrain_polygon(Vector2i p_size, float p_overlap, TileSet::TileOffsetAxis p_offset_axis); - Vector _get_half_offset_corner_or_side_terrain_peering_bit_polygon(Vector2i p_size, float p_overlap, TileSet::TileOffsetAxis p_offset_axis, TileSet::CellNeighbor p_bit); - Vector _get_half_offset_corner_terrain_peering_bit_polygon(Vector2i p_size, float p_overlap, TileSet::TileOffsetAxis p_offset_axis, TileSet::CellNeighbor p_bit); - Vector _get_half_offset_side_terrain_peering_bit_polygon(Vector2i p_size, float p_overlap, TileSet::TileOffsetAxis p_offset_axis, TileSet::CellNeighbor p_bit); - -protected: - static void _bind_methods(); - -public: - // --- Plugins --- - Vector get_tile_set_atlas_plugins() const; - - // --- Accessors for TileSet data --- - - // -- Shape and layout -- - void set_tile_shape(TileShape p_shape); - TileShape get_tile_shape() const; - void set_tile_layout(TileLayout p_layout); - TileLayout get_tile_layout() const; - void set_tile_offset_axis(TileOffsetAxis p_alignment); - TileOffsetAxis get_tile_offset_axis() const; - void set_tile_size(Size2i p_size); - Size2i get_tile_size() const; - - // -- Sources management -- - int get_next_source_id() const; - int get_source_count() const; - int get_source_id(int p_index) const; - int add_source(Ref p_tile_set_source, int p_source_id_override = -1); - void set_source_id(int p_source_id, int p_new_id); - void remove_source(int p_source_id); - void remove_source_ptr(TileSetSource *p_tile_set_source); // Not exposed - bool has_source(int p_source_id) const; - Ref get_source(int p_source_id) const; - - // Rendering - void set_uv_clipping(bool p_uv_clipping); - bool is_uv_clipping() const; - - int get_occlusion_layers_count() const; - void add_occlusion_layer(int p_index = -1); - void move_occlusion_layer(int p_from_index, int p_to_pos); - void remove_occlusion_layer(int p_index); - void set_occlusion_layer_light_mask(int p_layer_index, int p_light_mask); - int get_occlusion_layer_light_mask(int p_layer_index) const; - void set_occlusion_layer_sdf_collision(int p_layer_index, bool p_sdf_collision); - bool get_occlusion_layer_sdf_collision(int p_layer_index) const; - - // Physics - int get_physics_layers_count() const; - void add_physics_layer(int p_index = -1); - void move_physics_layer(int p_from_index, int p_to_pos); - void remove_physics_layer(int p_index); - void set_physics_layer_collision_layer(int p_layer_index, uint32_t p_layer); - uint32_t get_physics_layer_collision_layer(int p_layer_index) const; - void set_physics_layer_collision_mask(int p_layer_index, uint32_t p_mask); - uint32_t get_physics_layer_collision_mask(int p_layer_index) const; - void set_physics_layer_physics_material(int p_layer_index, Ref p_physics_material); - Ref get_physics_layer_physics_material(int p_layer_index) const; - - // Terrain sets - int get_terrain_sets_count() const; - void add_terrain_set(int p_index = -1); - void move_terrain_set(int p_from_index, int p_to_pos); - void remove_terrain_set(int p_index); - void set_terrain_set_mode(int p_terrain_set, TerrainMode p_terrain_mode); - TerrainMode get_terrain_set_mode(int p_terrain_set) const; - - // Terrains - int get_terrains_count(int p_terrain_set) const; - void add_terrain(int p_terrain_set, int p_index = -1); - void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos); - void remove_terrain(int p_terrain_set, int p_index); - void set_terrain_name(int p_terrain_set, int p_terrain_index, String p_name); - String get_terrain_name(int p_terrain_set, int p_terrain_index) const; - void set_terrain_color(int p_terrain_set, int p_terrain_index, Color p_color); - Color get_terrain_color(int p_terrain_set, int p_terrain_index) const; - bool is_valid_terrain_peering_bit_for_mode(TileSet::TerrainMode p_terrain_mode, TileSet::CellNeighbor p_peering_bit) const; - bool is_valid_terrain_peering_bit(int p_terrain_set, TileSet::CellNeighbor p_peering_bit) const; - - // Navigation - int get_navigation_layers_count() const; - void add_navigation_layer(int p_index = -1); - void move_navigation_layer(int p_from_index, int p_to_pos); - void remove_navigation_layer(int p_index); - void set_navigation_layer_layers(int p_layer_index, uint32_t p_layers); - uint32_t get_navigation_layer_layers(int p_layer_index) const; - void set_navigation_layer_layer_value(int p_layer_index, int p_layer_number, bool p_value); - bool get_navigation_layer_layer_value(int p_layer_index, int p_layer_number) const; - - // Custom data - int get_custom_data_layers_count() const; - void add_custom_data_layer(int p_index = -1); - void move_custom_data_layer(int p_from_index, int p_to_pos); - void remove_custom_data_layer(int p_index); - int get_custom_data_layer_by_name(String p_value) const; - void set_custom_data_layer_name(int p_layer_id, String p_value); - String get_custom_data_layer_name(int p_layer_id) const; - void set_custom_data_layer_type(int p_layer_id, Variant::Type p_value); - Variant::Type get_custom_data_layer_type(int p_layer_id) const; - - // Tiles proxies. - void set_source_level_tile_proxy(int p_source_from, int p_source_to); - int get_source_level_tile_proxy(int p_source_from); - bool has_source_level_tile_proxy(int p_source_from); - void remove_source_level_tile_proxy(int p_source_from); - - void set_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_source_to, Vector2i p_coords_to); - Array get_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from); - bool has_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from); - void remove_coords_level_tile_proxy(int p_source_from, Vector2i p_coords_from); - - void set_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from, int p_source_to, Vector2i p_coords_to, int p_alternative_to); - Array get_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from); - bool has_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from); - void remove_alternative_level_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from); - - Array get_source_level_tile_proxies() const; - Array get_coords_level_tile_proxies() const; - Array get_alternative_level_tile_proxies() const; - - Array map_tile_proxy(int p_source_from, Vector2i p_coords_from, int p_alternative_from) const; - - void cleanup_invalid_tile_proxies(); - void clear_tile_proxies(); - - // Patterns. - int add_pattern(Ref p_pattern, int p_index = -1); - Ref get_pattern(int p_index); - void remove_pattern(int p_index); - int get_patterns_count(); - - // Terrains. - RBSet get_terrains_pattern_set(int p_terrain_set); - RBSet get_tiles_for_terrains_pattern(int p_terrain_set, TerrainsPattern p_terrain_tile_pattern); - TileMapCell get_random_tile_from_terrains_pattern(int p_terrain_set, TerrainsPattern p_terrain_tile_pattern); - - // Helpers - Vector get_tile_shape_polygon() const; - void draw_tile_shape(CanvasItem *p_canvas_item, Transform2D p_transform, Color p_color, bool p_filled = false, Ref p_texture = Ref()) const; - - // Used by TileMap/TileMapLayer - Vector2 map_to_local(const Vector2i &p_pos) const; - Vector2i local_to_map(const Vector2 &p_pos) const; - bool is_existing_neighbor(TileSet::CellNeighbor p_cell_neighbor) const; - Vector2i get_neighbor_cell(const Vector2i &p_coords, TileSet::CellNeighbor p_cell_neighbor) const; - TypedArray get_surrounding_cells(const Vector2i &p_coords) const; - Vector2i map_pattern(const Vector2i &p_position_in_tilemap, const Vector2i &p_coords_in_pattern, Ref p_pattern) const; - void draw_cells_outline(CanvasItem *p_canvas_item, const RBSet &p_cells, Color p_color, Transform2D p_transform = Transform2D()) const; - - Vector get_terrain_polygon(int p_terrain_set); - Vector get_terrain_peering_bit_polygon(int p_terrain_set, TileSet::CellNeighbor p_bit); - void draw_terrains(CanvasItem *p_canvas_item, Transform2D p_transform, const TileData *p_tile_data); - Vector>> generate_terrains_icons(Size2i p_size); - - // Resource management - virtual void reset_state() override; - - // Helpers. - static Vector2i transform_coords_layout(const Vector2i &p_coords, TileSet::TileOffsetAxis p_offset_axis, TileSet::TileLayout p_from_layout, TileSet::TileLayout p_to_layout); - - TileSet(); - ~TileSet(); -}; - -class TileSetSource : public Resource { - GDCLASS(TileSetSource, Resource); - -protected: - const TileSet *tile_set = nullptr; - - static void _bind_methods(); - -public: - static const Vector2i INVALID_ATLAS_COORDS; // Vector2i(-1, -1); - static const int INVALID_TILE_ALTERNATIVE; // -1; - - // Not exposed. - virtual void set_tile_set(const TileSet *p_tile_set); - TileSet *get_tile_set() const; - virtual void notify_tile_data_properties_should_change(){}; - virtual void add_occlusion_layer(int p_index){}; - virtual void move_occlusion_layer(int p_from_index, int p_to_pos){}; - virtual void remove_occlusion_layer(int p_index){}; - virtual void add_physics_layer(int p_index){}; - virtual void move_physics_layer(int p_from_index, int p_to_pos){}; - virtual void remove_physics_layer(int p_index){}; - virtual void add_terrain_set(int p_index){}; - virtual void move_terrain_set(int p_from_index, int p_to_pos){}; - virtual void remove_terrain_set(int p_index){}; - virtual void add_terrain(int p_terrain_set, int p_index){}; - virtual void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos){}; - virtual void remove_terrain(int p_terrain_set, int p_index){}; - virtual void add_navigation_layer(int p_index){}; - virtual void move_navigation_layer(int p_from_index, int p_to_pos){}; - virtual void remove_navigation_layer(int p_index){}; - virtual void add_custom_data_layer(int p_index){}; - virtual void move_custom_data_layer(int p_from_index, int p_to_pos){}; - virtual void remove_custom_data_layer(int p_index){}; - virtual void reset_state() override; - - // Tiles. - virtual int get_tiles_count() const = 0; - virtual Vector2i get_tile_id(int tile_index) const = 0; - virtual bool has_tile(Vector2i p_atlas_coords) const = 0; - - // Alternative tiles. - virtual int get_alternative_tiles_count(const Vector2i p_atlas_coords) const = 0; - virtual int get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const = 0; - virtual bool has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const = 0; -}; - -class TileSetAtlasSource : public TileSetSource { - GDCLASS(TileSetAtlasSource, TileSetSource); - -public: - enum TileAnimationMode { - TILE_ANIMATION_MODE_DEFAULT, - TILE_ANIMATION_MODE_RANDOM_START_TIMES, - TILE_ANIMATION_MODE_MAX, - }; - - enum TransformBits { - TRANSFORM_FLIP_H = 1 << 12, - TRANSFORM_FLIP_V = 1 << 13, - TRANSFORM_TRANSPOSE = 1 << 14, - }; - -private: - struct TileAlternativesData { - Vector2i size_in_atlas = Vector2i(1, 1); - Vector2i texture_offset; - - // Animation - int animation_columns = 0; - Vector2i animation_separation; - real_t animation_speed = 1.0; - TileSetAtlasSource::TileAnimationMode animation_mode = TILE_ANIMATION_MODE_DEFAULT; - LocalVector animation_frames_durations; - - // Alternatives - HashMap alternatives; - Vector alternatives_ids; - int next_alternative_id = 1; - }; - - Ref texture; - Vector2i margins; - Vector2i separation; - Size2i texture_region_size = Size2i(16, 16); - - HashMap tiles; - Vector tiles_ids; - HashMap _coords_mapping_cache; // Maps any coordinate to the including tile - - TileData *_get_atlas_tile_data(Vector2i p_atlas_coords, int p_alternative_tile); - const TileData *_get_atlas_tile_data(Vector2i p_atlas_coords, int p_alternative_tile) const; - - void _compute_next_alternative_id(const Vector2i p_atlas_coords); - - void _clear_coords_mapping_cache(Vector2i p_atlas_coords); - void _create_coords_mapping_cache(Vector2i p_atlas_coords); - - bool use_texture_padding = true; - Ref padded_texture; - bool padded_texture_needs_update = false; - void _queue_update_padded_texture(); - Ref _create_padded_image_texture(const Ref &p_source); - void _update_padded_texture(); - -protected: - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List *p_list) const; - - static void _bind_methods(); - -public: - // Not exposed. - virtual void set_tile_set(const TileSet *p_tile_set) override; - const TileSet *get_tile_set() const; - virtual void notify_tile_data_properties_should_change() override; - virtual void add_occlusion_layer(int p_index) override; - virtual void move_occlusion_layer(int p_from_index, int p_to_pos) override; - virtual void remove_occlusion_layer(int p_index) override; - virtual void add_physics_layer(int p_index) override; - virtual void move_physics_layer(int p_from_index, int p_to_pos) override; - virtual void remove_physics_layer(int p_index) override; - virtual void add_terrain_set(int p_index) override; - virtual void move_terrain_set(int p_from_index, int p_to_pos) override; - virtual void remove_terrain_set(int p_index) override; - virtual void add_terrain(int p_terrain_set, int p_index) override; - virtual void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos) override; - virtual void remove_terrain(int p_terrain_set, int p_index) override; - virtual void add_navigation_layer(int p_index) override; - virtual void move_navigation_layer(int p_from_index, int p_to_pos) override; - virtual void remove_navigation_layer(int p_index) override; - virtual void add_custom_data_layer(int p_index) override; - virtual void move_custom_data_layer(int p_from_index, int p_to_pos) override; - virtual void remove_custom_data_layer(int p_index) override; - virtual void reset_state() override; - - // Base properties. - void set_texture(Ref p_texture); - Ref get_texture() const; - void set_margins(Vector2i p_margins); - Vector2i get_margins() const; - void set_separation(Vector2i p_separation); - Vector2i get_separation() const; - void set_texture_region_size(Vector2i p_tile_size); - Vector2i get_texture_region_size() const; - - // Padding. - void set_use_texture_padding(bool p_use_padding); - bool get_use_texture_padding() const; - - // Base tiles. - void create_tile(const Vector2i p_atlas_coords, const Vector2i p_size = Vector2i(1, 1)); - void remove_tile(Vector2i p_atlas_coords); - virtual bool has_tile(Vector2i p_atlas_coords) const override; - void move_tile_in_atlas(Vector2i p_atlas_coords, Vector2i p_new_atlas_coords = INVALID_ATLAS_COORDS, Vector2i p_new_size = Vector2i(-1, -1)); - Vector2i get_tile_size_in_atlas(Vector2i p_atlas_coords) const; - - virtual int get_tiles_count() const override; - virtual Vector2i get_tile_id(int p_index) const override; - - bool has_room_for_tile(Vector2i p_atlas_coords, Vector2i p_size, int p_animation_columns, Vector2i p_animation_separation, int p_frames_count, Vector2i p_ignored_tile = INVALID_ATLAS_COORDS) const; - PackedVector2Array get_tiles_to_be_removed_on_change(Ref p_texture, Vector2i p_margins, Vector2i p_separation, Vector2i p_texture_region_size); - Vector2i get_tile_at_coords(Vector2i p_atlas_coords) const; - - bool has_tiles_outside_texture() const; - Vector get_tiles_outside_texture() const; - void clear_tiles_outside_texture(); - - // Animation. - void set_tile_animation_columns(const Vector2i p_atlas_coords, int p_frame_columns); - int get_tile_animation_columns(const Vector2i p_atlas_coords) const; - void set_tile_animation_separation(const Vector2i p_atlas_coords, const Vector2i p_separation); - Vector2i get_tile_animation_separation(const Vector2i p_atlas_coords) const; - void set_tile_animation_speed(const Vector2i p_atlas_coords, real_t p_speed); - real_t get_tile_animation_speed(const Vector2i p_atlas_coords) const; - void set_tile_animation_mode(const Vector2i p_atlas_coords, const TileSetAtlasSource::TileAnimationMode p_mode); - TileSetAtlasSource::TileAnimationMode get_tile_animation_mode(const Vector2i p_atlas_coords) const; - void set_tile_animation_frames_count(const Vector2i p_atlas_coords, int p_frames_count); - int get_tile_animation_frames_count(const Vector2i p_atlas_coords) const; - void set_tile_animation_frame_duration(const Vector2i p_atlas_coords, int p_frame_index, real_t p_duration); - real_t get_tile_animation_frame_duration(const Vector2i p_atlas_coords, int p_frame_index) const; - real_t get_tile_animation_total_duration(const Vector2i p_atlas_coords) const; - - // Alternative tiles. - int create_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_id_override = -1); - void remove_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile); - void set_alternative_tile_id(const Vector2i p_atlas_coords, int p_alternative_tile, int p_new_id); - virtual bool has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const override; - int get_next_alternative_tile_id(const Vector2i p_atlas_coords) const; - - virtual int get_alternative_tiles_count(const Vector2i p_atlas_coords) const override; - virtual int get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const override; - - // Get data associated to a tile. - TileData *get_tile_data(const Vector2i p_atlas_coords, int p_alternative_tile) const; - - // Helpers. - Vector2i get_atlas_grid_size() const; - Rect2i get_tile_texture_region(Vector2i p_atlas_coords, int p_frame = 0) const; - bool is_position_in_tile_texture_region(const Vector2i p_atlas_coords, int p_alternative_tile, Vector2 p_position) const; - - static int alternative_no_transform(int p_alternative_id); - - // Getters for texture and tile region (padded or not) - Ref get_runtime_texture() const; - Rect2i get_runtime_tile_texture_region(Vector2i p_atlas_coords, int p_frame = 0) const; - - ~TileSetAtlasSource(); -}; - -class TileSetScenesCollectionSource : public TileSetSource { - GDCLASS(TileSetScenesCollectionSource, TileSetSource); - -private: - struct SceneData { - Ref scene; - bool display_placeholder = false; - }; - Vector scenes_ids; - HashMap scenes; - int next_scene_id = 1; - - void _compute_next_alternative_id(); - -protected: - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List *p_list) const; - - static void _bind_methods(); - -public: - // Tiles. - int get_tiles_count() const override; - Vector2i get_tile_id(int p_tile_index) const override; - bool has_tile(Vector2i p_atlas_coords) const override; - - // Alternative tiles. - int get_alternative_tiles_count(const Vector2i p_atlas_coords) const override; - int get_alternative_tile_id(const Vector2i p_atlas_coords, int p_index) const override; - bool has_alternative_tile(const Vector2i p_atlas_coords, int p_alternative_tile) const override; - - // Scenes accessors. Lot are similar to "Alternative tiles". - int get_scene_tiles_count() { return get_alternative_tiles_count(Vector2i()); } - int get_scene_tile_id(int p_index) { return get_alternative_tile_id(Vector2i(), p_index); }; - bool has_scene_tile_id(int p_id) { return has_alternative_tile(Vector2i(), p_id); }; - int create_scene_tile(Ref p_packed_scene = Ref(), int p_id_override = -1); - void set_scene_tile_id(int p_id, int p_new_id); - void set_scene_tile_scene(int p_id, Ref p_packed_scene); - Ref get_scene_tile_scene(int p_id) const; - void set_scene_tile_display_placeholder(int p_id, bool p_packed_scene); - bool get_scene_tile_display_placeholder(int p_id) const; - void remove_scene_tile(int p_id); - int get_next_scene_tile_id() const; -}; - -class TileData : public Object { - GDCLASS(TileData, Object); - -private: - const TileSet *tile_set = nullptr; - bool allow_transform = true; - - // Rendering - bool flip_h = false; - bool flip_v = false; - bool transpose = false; - Vector2i texture_origin; - Ref material = Ref(); - Color modulate = Color(1.0, 1.0, 1.0, 1.0); - int z_index = 0; - int y_sort_origin = 0; - struct OcclusionLayerTileData { - Ref occluder; - mutable HashMap> transformed_occluders; - }; - Vector occluders; - - // Physics - struct PhysicsLayerTileData { - struct PolygonShapeTileData { - LocalVector polygon; - LocalVector> shapes; - mutable HashMap>> transformed_shapes; - bool one_way = false; - float one_way_margin = 1.0; - }; - - Vector2 linear_velocity; - double angular_velocity = 0.0; - Vector polygons; - }; - Vector physics; - // TODO add support for areas. - - // Terrain - int terrain_set = -1; - int terrain = -1; - int terrain_peering_bits[16] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; - - // Navigation - struct NavigationLayerTileData { - Ref navigation_polygon; - mutable HashMap> transformed_navigation_polygon; - }; - Vector navigation; - - // Misc - double probability = 1.0; - - // Custom data - Vector custom_data; - -protected: - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List *p_list) const; - static void _bind_methods(); - -#ifndef DISABLE_DEPRECATED - Ref _get_navigation_polygon_bind_compat_84660(int p_layer_id) const; - Ref _get_occluder_bind_compat_84660(int p_layer_id) const; - - static void _bind_compatibility_methods(); -#endif - -public: - // Not exposed. - void set_tile_set(const TileSet *p_tile_set); - void notify_tile_data_properties_should_change(); - void add_occlusion_layer(int p_index); - void move_occlusion_layer(int p_from_index, int p_to_pos); - void remove_occlusion_layer(int p_index); - void add_physics_layer(int p_index); - void move_physics_layer(int p_from_index, int p_to_pos); - void remove_physics_layer(int p_index); - void add_terrain_set(int p_index); - void move_terrain_set(int p_from_index, int p_to_pos); - void remove_terrain_set(int p_index); - void add_terrain(int p_terrain_set, int p_index); - void move_terrain(int p_terrain_set, int p_from_index, int p_to_pos); - void remove_terrain(int p_terrain_set, int p_index); - void add_navigation_layer(int p_index); - void move_navigation_layer(int p_from_index, int p_to_pos); - void remove_navigation_layer(int p_index); - void add_custom_data_layer(int p_index); - void move_custom_data_layer(int p_from_index, int p_to_pos); - void remove_custom_data_layer(int p_index); - void set_allow_transform(bool p_allow_transform); - bool is_allowing_transform() const; - - // To duplicate a TileData object, needed for runtiume update. - TileData *duplicate(); - - // Rendering - void set_flip_h(bool p_flip_h); - bool get_flip_h() const; - void set_flip_v(bool p_flip_v); - bool get_flip_v() const; - void set_transpose(bool p_transpose); - bool get_transpose() const; - - void set_texture_origin(Vector2i p_texture_origin); - Vector2i get_texture_origin() const; - void set_material(Ref p_material); - Ref get_material() const; - void set_modulate(Color p_modulate); - Color get_modulate() const; - void set_z_index(int p_z_index); - int get_z_index() const; - void set_y_sort_origin(int p_y_sort_origin); - int get_y_sort_origin() const; - - void set_occluder(int p_layer_id, Ref p_occluder_polygon); - Ref get_occluder(int p_layer_id, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false) const; - - // Physics - void set_constant_linear_velocity(int p_layer_id, const Vector2 &p_velocity); - Vector2 get_constant_linear_velocity(int p_layer_id) const; - void set_constant_angular_velocity(int p_layer_id, real_t p_velocity); - real_t get_constant_angular_velocity(int p_layer_id) const; - void set_collision_polygons_count(int p_layer_id, int p_shapes_count); - int get_collision_polygons_count(int p_layer_id) const; - void add_collision_polygon(int p_layer_id); - void remove_collision_polygon(int p_layer_id, int p_polygon_index); - void set_collision_polygon_points(int p_layer_id, int p_polygon_index, Vector p_polygon); - Vector get_collision_polygon_points(int p_layer_id, int p_polygon_index) const; - void set_collision_polygon_one_way(int p_layer_id, int p_polygon_index, bool p_one_way); - bool is_collision_polygon_one_way(int p_layer_id, int p_polygon_index) const; - void set_collision_polygon_one_way_margin(int p_layer_id, int p_polygon_index, float p_one_way_margin); - float get_collision_polygon_one_way_margin(int p_layer_id, int p_polygon_index) const; - int get_collision_polygon_shapes_count(int p_layer_id, int p_polygon_index) const; - Ref get_collision_polygon_shape(int p_layer_id, int p_polygon_index, int shape_index, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false) const; - - // Terrain - void set_terrain_set(int p_terrain_id); - int get_terrain_set() const; - void set_terrain(int p_terrain_id); - int get_terrain() const; - void set_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit, int p_terrain_id); - int get_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit) const; - bool is_valid_terrain_peering_bit(TileSet::CellNeighbor p_peering_bit) const; - - TileSet::TerrainsPattern get_terrains_pattern() const; // Not exposed. - - // Navigation - void set_navigation_polygon(int p_layer_id, Ref p_navigation_polygon); - Ref get_navigation_polygon(int p_layer_id, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false) const; - - // Misc - void set_probability(float p_probability); - float get_probability() const; - - // Custom data. - void set_custom_data(String p_layer_name, Variant p_value); - Variant get_custom_data(String p_layer_name) const; - void set_custom_data_by_layer_id(int p_layer_id, Variant p_value); - Variant get_custom_data_by_layer_id(int p_layer_id) const; - - // Polygons. - static PackedVector2Array get_transformed_vertices(const PackedVector2Array &p_vertices, bool p_flip_h, bool p_flip_v, bool p_transpose); -}; - -VARIANT_ENUM_CAST(TileSet::CellNeighbor); -VARIANT_ENUM_CAST(TileSet::TerrainMode); -VARIANT_ENUM_CAST(TileSet::TileShape); -VARIANT_ENUM_CAST(TileSet::TileLayout); -VARIANT_ENUM_CAST(TileSet::TileOffsetAxis); - -VARIANT_ENUM_CAST(TileSetAtlasSource::TileAnimationMode); - -#endif // TILE_SET_H diff --git a/scene/resources/world_boundary_shape_2d.cpp b/scene/resources/world_boundary_shape_2d.cpp deleted file mode 100644 index 294653659a..0000000000 --- a/scene/resources/world_boundary_shape_2d.cpp +++ /dev/null @@ -1,126 +0,0 @@ -/**************************************************************************/ -/* world_boundary_shape_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 "world_boundary_shape_2d.h" - -#include "core/math/geometry_2d.h" -#include "servers/physics_server_2d.h" -#include "servers/rendering_server.h" - -bool WorldBoundaryShape2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const { - Vector2 point = distance * normal; - Vector2 l[2][2] = { { point - normal.orthogonal() * 100, point + normal.orthogonal() * 100 }, { point, point + normal * 30 } }; - - for (int i = 0; i < 2; i++) { - Vector2 closest = Geometry2D::get_closest_point_to_segment(p_point, l[i]); - if (p_point.distance_to(closest) < p_tolerance) { - return true; - } - } - - return false; -} - -void WorldBoundaryShape2D::_update_shape() { - Array arr; - arr.push_back(normal); - arr.push_back(distance); - PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), arr); - emit_changed(); -} - -void WorldBoundaryShape2D::set_normal(const Vector2 &p_normal) { - // Can be non-unit but prevent zero. - ERR_FAIL_COND(p_normal.is_zero_approx()); - if (normal == p_normal) { - return; - } - normal = p_normal; - _update_shape(); -} - -void WorldBoundaryShape2D::set_distance(real_t p_distance) { - if (distance == p_distance) { - return; - } - distance = p_distance; - _update_shape(); -} - -Vector2 WorldBoundaryShape2D::get_normal() const { - return normal; -} - -real_t WorldBoundaryShape2D::get_distance() const { - return distance; -} - -void WorldBoundaryShape2D::draw(const RID &p_to_rid, const Color &p_color) { - Vector2 point = distance * normal; - real_t line_width = 3.0; - - Vector2 l1[2] = { point - normal.orthogonal() * 100, point + normal.orthogonal() * 100 }; - RS::get_singleton()->canvas_item_add_line(p_to_rid, l1[0], l1[1], p_color, line_width); - Vector2 l2[2] = { point + normal.normalized() * (0.5 * line_width), point + normal * 30 }; - RS::get_singleton()->canvas_item_add_line(p_to_rid, l2[0], l2[1], p_color, line_width); -} - -Rect2 WorldBoundaryShape2D::get_rect() const { - Vector2 point = distance * normal; - - Vector2 l1[2] = { point - normal.orthogonal() * 100, point + normal.orthogonal() * 100 }; - Vector2 l2[2] = { point, point + normal * 30 }; - Rect2 rect; - rect.position = l1[0]; - rect.expand_to(l1[1]); - rect.expand_to(l2[0]); - rect.expand_to(l2[1]); - return rect; -} - -real_t WorldBoundaryShape2D::get_enclosing_radius() const { - return distance; -} - -void WorldBoundaryShape2D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_normal", "normal"), &WorldBoundaryShape2D::set_normal); - ClassDB::bind_method(D_METHOD("get_normal"), &WorldBoundaryShape2D::get_normal); - - ClassDB::bind_method(D_METHOD("set_distance", "distance"), &WorldBoundaryShape2D::set_distance); - ClassDB::bind_method(D_METHOD("get_distance"), &WorldBoundaryShape2D::get_distance); - - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "normal"), "set_normal", "get_normal"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "distance", PROPERTY_HINT_RANGE, "-1024,1024,0.01,or_greater,or_less,suffix:px"), "set_distance", "get_distance"); -} - -WorldBoundaryShape2D::WorldBoundaryShape2D() : - Shape2D(PhysicsServer2D::get_singleton()->world_boundary_shape_create()) { - _update_shape(); -} diff --git a/scene/resources/world_boundary_shape_2d.h b/scene/resources/world_boundary_shape_2d.h deleted file mode 100644 index a79b8e4e1f..0000000000 --- a/scene/resources/world_boundary_shape_2d.h +++ /dev/null @@ -1,64 +0,0 @@ -/**************************************************************************/ -/* world_boundary_shape_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 WORLD_BOUNDARY_SHAPE_2D_H -#define WORLD_BOUNDARY_SHAPE_2D_H - -#include "scene/resources/shape_2d.h" - -class WorldBoundaryShape2D : public Shape2D { - GDCLASS(WorldBoundaryShape2D, Shape2D); - - // WorldBoundaryShape2D is often used for one-way platforms, where the normal pointing up makes sense. - Vector2 normal = Vector2(0, -1); - real_t distance = 0.0; - - void _update_shape(); - -protected: - static void _bind_methods(); - -public: - virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override; - - void set_normal(const Vector2 &p_normal); - void set_distance(real_t p_distance); - - Vector2 get_normal() const; - real_t get_distance() const; - - virtual void draw(const RID &p_to_rid, const Color &p_color) override; - virtual Rect2 get_rect() const override; - virtual real_t get_enclosing_radius() const override; - - WorldBoundaryShape2D(); -}; - -#endif // WORLD_BOUNDARY_SHAPE_2D_H diff --git a/scu_builders.py b/scu_builders.py index 56effb393c..740b4ca571 100644 --- a/scu_builders.py +++ b/scu_builders.py @@ -315,6 +315,7 @@ def generate_scu_files(max_includes_per_scu): process_folder(["scene/gui"]) process_folder(["scene/main"]) process_folder(["scene/resources"]) + process_folder(["scene/resources/2d"]) process_folder(["servers"]) process_folder(["servers/rendering"]) diff --git a/tests/scene/test_viewport.h b/tests/scene/test_viewport.h index 1afae66ee0..e15b01ae9f 100644 --- a/tests/scene/test_viewport.h +++ b/tests/scene/test_viewport.h @@ -37,7 +37,7 @@ #include "scene/gui/subviewport_container.h" #include "scene/main/canvas_layer.h" #include "scene/main/window.h" -#include "scene/resources/rectangle_shape_2d.h" +#include "scene/resources/2d/rectangle_shape_2d.h" #include "tests/test_macros.h" -- cgit v1.2.3 From c399424db9ee39e94cd21569ee87567de10586e2 Mon Sep 17 00:00:00 2001 From: Aaron Franke Date: Fri, 13 Aug 2021 11:42:45 -0500 Subject: Move 3D-only resources to their own folder --- editor/import/3d/editor_import_collada.cpp | 2 +- editor/import/3d/resource_importer_obj.cpp | 2 +- editor/import/3d/resource_importer_scene.cpp | 10 +- editor/import/3d/resource_importer_scene.h | 12 +- editor/import/3d/scene_import_settings.cpp | 2 +- editor/import/3d/scene_import_settings.h | 2 +- .../gizmos/collision_shape_3d_gizmo_plugin.cpp | 18 +- .../plugins/gizmos/spring_arm_3d_gizmo_plugin.cpp | 2 +- editor/plugins/material_editor_plugin.cpp | 4 +- editor/plugins/material_editor_plugin.h | 2 +- editor/plugins/mesh_instance_3d_editor_plugin.cpp | 6 +- editor/plugins/mesh_library_editor_plugin.h | 2 +- editor/plugins/node_3d_editor_gizmos.cpp | 2 +- editor/plugins/node_3d_editor_plugin.cpp | 2 +- editor/plugins/skeleton_3d_editor_plugin.cpp | 3 +- modules/csg/csg_shape.h | 2 +- ...tf_document_extension_convert_importer_mesh.cpp | 2 +- .../gltf/extensions/physics/gltf_physics_shape.cpp | 14 +- modules/gltf/gltf_document.cpp | 2 +- modules/gltf/structures/gltf_mesh.cpp | 2 +- modules/gltf/structures/gltf_mesh.h | 2 +- modules/gltf/structures/gltf_skin.cpp | 2 +- modules/gltf/structures/gltf_skin.h | 2 +- modules/gridmap/grid_map.cpp | 4 +- modules/gridmap/grid_map.h | 2 +- modules/navigation/3d/nav_mesh_generator_3d.cpp | 20 +- scene/3d/collision_object_3d.cpp | 2 +- scene/3d/collision_polygon_3d.cpp | 2 +- scene/3d/collision_polygon_3d.h | 2 +- scene/3d/collision_shape_3d.cpp | 6 +- scene/3d/collision_shape_3d.h | 2 +- scene/3d/gpu_particles_3d.h | 2 +- scene/3d/importer_mesh_instance_3d.cpp | 2 +- scene/3d/importer_mesh_instance_3d.h | 2 +- scene/3d/mesh_instance_3d.cpp | 4 +- scene/3d/node_3d.h | 2 +- scene/3d/occluder_instance_3d.cpp | 2 +- scene/3d/shape_cast_3d.cpp | 2 +- scene/3d/shape_cast_3d.h | 2 +- scene/3d/skeleton_3d.h | 2 +- scene/3d/spring_arm_3d.cpp | 2 +- scene/main/scene_tree.cpp | 2 +- scene/register_scene_types.cpp | 30 +- scene/resources/3d/SCsub | 6 + scene/resources/3d/box_shape_3d.cpp | 100 + scene/resources/3d/box_shape_3d.h | 59 + scene/resources/3d/capsule_shape_3d.cpp | 125 + scene/resources/3d/capsule_shape_3d.h | 58 + scene/resources/3d/concave_polygon_shape_3d.cpp | 118 + scene/resources/3d/concave_polygon_shape_3d.h | 80 + scene/resources/3d/convex_polygon_shape_3d.cpp | 90 + scene/resources/3d/convex_polygon_shape_3d.h | 55 + scene/resources/3d/cylinder_shape_3d.cpp | 110 + scene/resources/3d/cylinder_shape_3d.h | 57 + scene/resources/3d/fog_material.cpp | 182 + scene/resources/3d/fog_material.h | 87 + scene/resources/3d/height_map_shape_3d.cpp | 215 ++ scene/resources/3d/height_map_shape_3d.h | 66 + scene/resources/3d/importer_mesh.cpp | 1378 ++++++++ scene/resources/3d/importer_mesh.h | 135 + scene/resources/3d/mesh_library.cpp | 356 ++ scene/resources/3d/mesh_library.h | 106 + scene/resources/3d/primitive_meshes.cpp | 3655 ++++++++++++++++++++ scene/resources/3d/primitive_meshes.h | 689 ++++ scene/resources/3d/separation_ray_shape_3d.cpp | 92 + scene/resources/3d/separation_ray_shape_3d.h | 58 + scene/resources/3d/shape_3d.cpp | 133 + scene/resources/3d/shape_3d.h | 76 + scene/resources/3d/skin.cpp | 162 + scene/resources/3d/skin.h | 89 + scene/resources/3d/sky_material.cpp | 806 +++++ scene/resources/3d/sky_material.h | 235 ++ scene/resources/3d/sphere_shape_3d.cpp | 87 + scene/resources/3d/sphere_shape_3d.h | 55 + scene/resources/3d/world_3d.cpp | 188 + scene/resources/3d/world_3d.h | 93 + scene/resources/3d/world_boundary_shape_3d.cpp | 89 + scene/resources/3d/world_boundary_shape_3d.h | 57 + scene/resources/SCsub | 1 + scene/resources/box_shape_3d.cpp | 100 - scene/resources/box_shape_3d.h | 59 - scene/resources/capsule_shape_3d.cpp | 125 - scene/resources/capsule_shape_3d.h | 58 - scene/resources/concave_polygon_shape_3d.cpp | 118 - scene/resources/concave_polygon_shape_3d.h | 80 - scene/resources/convex_polygon_shape_3d.cpp | 90 - scene/resources/convex_polygon_shape_3d.h | 55 - scene/resources/cylinder_shape_3d.cpp | 110 - scene/resources/cylinder_shape_3d.h | 57 - scene/resources/fog_material.cpp | 182 - scene/resources/fog_material.h | 87 - scene/resources/height_map_shape_3d.cpp | 215 -- scene/resources/height_map_shape_3d.h | 66 - scene/resources/importer_mesh.cpp | 1378 -------- scene/resources/importer_mesh.h | 135 - scene/resources/mesh.cpp | 4 +- scene/resources/mesh.h | 1 + scene/resources/mesh_library.cpp | 356 -- scene/resources/mesh_library.h | 106 - scene/resources/primitive_meshes.cpp | 3655 -------------------- scene/resources/primitive_meshes.h | 689 ---- scene/resources/separation_ray_shape_3d.cpp | 92 - scene/resources/separation_ray_shape_3d.h | 58 - scene/resources/shape_3d.cpp | 133 - scene/resources/shape_3d.h | 76 - scene/resources/skin.cpp | 162 - scene/resources/skin.h | 89 - scene/resources/sky_material.cpp | 806 ----- scene/resources/sky_material.h | 235 -- scene/resources/sphere_shape_3d.cpp | 87 - scene/resources/sphere_shape_3d.h | 55 - scene/resources/world_3d.cpp | 188 - scene/resources/world_3d.h | 93 - scene/resources/world_boundary_shape_3d.cpp | 89 - scene/resources/world_boundary_shape_3d.h | 57 - scu_builders.py | 1 + tests/scene/test_arraymesh.h | 2 +- tests/scene/test_navigation_region_3d.h | 2 +- tests/scene/test_primitives.h | 2 +- tests/servers/test_navigation_server_3d.h | 2 +- 120 files changed, 10052 insertions(+), 10044 deletions(-) create mode 100644 scene/resources/3d/SCsub create mode 100644 scene/resources/3d/box_shape_3d.cpp create mode 100644 scene/resources/3d/box_shape_3d.h create mode 100644 scene/resources/3d/capsule_shape_3d.cpp create mode 100644 scene/resources/3d/capsule_shape_3d.h create mode 100644 scene/resources/3d/concave_polygon_shape_3d.cpp create mode 100644 scene/resources/3d/concave_polygon_shape_3d.h create mode 100644 scene/resources/3d/convex_polygon_shape_3d.cpp create mode 100644 scene/resources/3d/convex_polygon_shape_3d.h create mode 100644 scene/resources/3d/cylinder_shape_3d.cpp create mode 100644 scene/resources/3d/cylinder_shape_3d.h create mode 100644 scene/resources/3d/fog_material.cpp create mode 100644 scene/resources/3d/fog_material.h create mode 100644 scene/resources/3d/height_map_shape_3d.cpp create mode 100644 scene/resources/3d/height_map_shape_3d.h create mode 100644 scene/resources/3d/importer_mesh.cpp create mode 100644 scene/resources/3d/importer_mesh.h create mode 100644 scene/resources/3d/mesh_library.cpp create mode 100644 scene/resources/3d/mesh_library.h create mode 100644 scene/resources/3d/primitive_meshes.cpp create mode 100644 scene/resources/3d/primitive_meshes.h create mode 100644 scene/resources/3d/separation_ray_shape_3d.cpp create mode 100644 scene/resources/3d/separation_ray_shape_3d.h create mode 100644 scene/resources/3d/shape_3d.cpp create mode 100644 scene/resources/3d/shape_3d.h create mode 100644 scene/resources/3d/skin.cpp create mode 100644 scene/resources/3d/skin.h create mode 100644 scene/resources/3d/sky_material.cpp create mode 100644 scene/resources/3d/sky_material.h create mode 100644 scene/resources/3d/sphere_shape_3d.cpp create mode 100644 scene/resources/3d/sphere_shape_3d.h create mode 100644 scene/resources/3d/world_3d.cpp create mode 100644 scene/resources/3d/world_3d.h create mode 100644 scene/resources/3d/world_boundary_shape_3d.cpp create mode 100644 scene/resources/3d/world_boundary_shape_3d.h delete mode 100644 scene/resources/box_shape_3d.cpp delete mode 100644 scene/resources/box_shape_3d.h delete mode 100644 scene/resources/capsule_shape_3d.cpp delete mode 100644 scene/resources/capsule_shape_3d.h delete mode 100644 scene/resources/concave_polygon_shape_3d.cpp delete mode 100644 scene/resources/concave_polygon_shape_3d.h delete mode 100644 scene/resources/convex_polygon_shape_3d.cpp delete mode 100644 scene/resources/convex_polygon_shape_3d.h delete mode 100644 scene/resources/cylinder_shape_3d.cpp delete mode 100644 scene/resources/cylinder_shape_3d.h delete mode 100644 scene/resources/fog_material.cpp delete mode 100644 scene/resources/fog_material.h delete mode 100644 scene/resources/height_map_shape_3d.cpp delete mode 100644 scene/resources/height_map_shape_3d.h delete mode 100644 scene/resources/importer_mesh.cpp delete mode 100644 scene/resources/importer_mesh.h delete mode 100644 scene/resources/mesh_library.cpp delete mode 100644 scene/resources/mesh_library.h delete mode 100644 scene/resources/primitive_meshes.cpp delete mode 100644 scene/resources/primitive_meshes.h delete mode 100644 scene/resources/separation_ray_shape_3d.cpp delete mode 100644 scene/resources/separation_ray_shape_3d.h delete mode 100644 scene/resources/shape_3d.cpp delete mode 100644 scene/resources/shape_3d.h delete mode 100644 scene/resources/skin.cpp delete mode 100644 scene/resources/skin.h delete mode 100644 scene/resources/sky_material.cpp delete mode 100644 scene/resources/sky_material.h delete mode 100644 scene/resources/sphere_shape_3d.cpp delete mode 100644 scene/resources/sphere_shape_3d.h delete mode 100644 scene/resources/world_3d.cpp delete mode 100644 scene/resources/world_3d.h delete mode 100644 scene/resources/world_boundary_shape_3d.cpp delete mode 100644 scene/resources/world_boundary_shape_3d.h diff --git a/editor/import/3d/editor_import_collada.cpp b/editor/import/3d/editor_import_collada.cpp index 49fec679bf..b7dabf027b 100644 --- a/editor/import/3d/editor_import_collada.cpp +++ b/editor/import/3d/editor_import_collada.cpp @@ -41,8 +41,8 @@ #include "scene/3d/path_3d.h" #include "scene/3d/skeleton_3d.h" #include "scene/animation/animation_player.h" +#include "scene/resources/3d/importer_mesh.h" #include "scene/resources/animation.h" -#include "scene/resources/importer_mesh.h" #include "scene/resources/packed_scene.h" #include "scene/resources/surface_tool.h" diff --git a/editor/import/3d/resource_importer_obj.cpp b/editor/import/3d/resource_importer_obj.cpp index 4b340ea997..52d1b45ac2 100644 --- a/editor/import/3d/resource_importer_obj.cpp +++ b/editor/import/3d/resource_importer_obj.cpp @@ -35,7 +35,7 @@ #include "scene/3d/importer_mesh_instance_3d.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/node_3d.h" -#include "scene/resources/importer_mesh.h" +#include "scene/resources/3d/importer_mesh.h" #include "scene/resources/mesh.h" #include "scene/resources/surface_tool.h" diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp index 7a51394bbc..3310d6f298 100644 --- a/editor/import/3d/resource_importer_scene.cpp +++ b/editor/import/3d/resource_importer_scene.cpp @@ -46,15 +46,15 @@ #include "scene/3d/physics_body_3d.h" #include "scene/3d/vehicle_body_3d.h" #include "scene/animation/animation_player.h" +#include "scene/resources/3d/box_shape_3d.h" +#include "scene/resources/3d/importer_mesh.h" +#include "scene/resources/3d/separation_ray_shape_3d.h" +#include "scene/resources/3d/sphere_shape_3d.h" +#include "scene/resources/3d/world_boundary_shape_3d.h" #include "scene/resources/animation.h" -#include "scene/resources/box_shape_3d.h" -#include "scene/resources/importer_mesh.h" #include "scene/resources/packed_scene.h" #include "scene/resources/resource_format_text.h" -#include "scene/resources/separation_ray_shape_3d.h" -#include "scene/resources/sphere_shape_3d.h" #include "scene/resources/surface_tool.h" -#include "scene/resources/world_boundary_shape_3d.h" uint32_t EditorSceneFormatImporter::get_import_flags() const { uint32_t ret; diff --git a/editor/import/3d/resource_importer_scene.h b/editor/import/3d/resource_importer_scene.h index f82a5aa9e9..dbf7794b61 100644 --- a/editor/import/3d/resource_importer_scene.h +++ b/editor/import/3d/resource_importer_scene.h @@ -35,14 +35,14 @@ #include "core/io/resource_importer.h" #include "core/variant/dictionary.h" #include "scene/3d/importer_mesh_instance_3d.h" +#include "scene/resources/3d/box_shape_3d.h" +#include "scene/resources/3d/capsule_shape_3d.h" +#include "scene/resources/3d/cylinder_shape_3d.h" +#include "scene/resources/3d/importer_mesh.h" +#include "scene/resources/3d/skin.h" +#include "scene/resources/3d/sphere_shape_3d.h" #include "scene/resources/animation.h" -#include "scene/resources/box_shape_3d.h" -#include "scene/resources/capsule_shape_3d.h" -#include "scene/resources/cylinder_shape_3d.h" -#include "scene/resources/importer_mesh.h" #include "scene/resources/mesh.h" -#include "scene/resources/shape_3d.h" -#include "scene/resources/sphere_shape_3d.h" class Material; class AnimationPlayer; diff --git a/editor/import/3d/scene_import_settings.cpp b/editor/import/3d/scene_import_settings.cpp index 53d7e63dbb..aeff01fc85 100644 --- a/editor/import/3d/scene_import_settings.cpp +++ b/editor/import/3d/scene_import_settings.cpp @@ -40,7 +40,7 @@ #include "editor/themes/editor_scale.h" #include "scene/3d/importer_mesh_instance_3d.h" #include "scene/animation/animation_player.h" -#include "scene/resources/importer_mesh.h" +#include "scene/resources/3d/importer_mesh.h" #include "scene/resources/surface_tool.h" class SceneImportSettingsData : public Object { diff --git a/editor/import/3d/scene_import_settings.h b/editor/import/3d/scene_import_settings.h index c6c416daba..05682551b7 100644 --- a/editor/import/3d/scene_import_settings.h +++ b/editor/import/3d/scene_import_settings.h @@ -46,7 +46,7 @@ #include "scene/gui/subviewport_container.h" #include "scene/gui/tab_container.h" #include "scene/gui/tree.h" -#include "scene/resources/primitive_meshes.h" +#include "scene/resources/3d/primitive_meshes.h" class EditorFileDialog; class EditorInspector; diff --git a/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp index 2695253b78..3cf9164460 100644 --- a/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp +++ b/editor/plugins/gizmos/collision_shape_3d_gizmo_plugin.cpp @@ -37,15 +37,15 @@ #include "editor/plugins/gizmos/gizmo_3d_helper.h" #include "editor/plugins/node_3d_editor_plugin.h" #include "scene/3d/collision_shape_3d.h" -#include "scene/resources/box_shape_3d.h" -#include "scene/resources/capsule_shape_3d.h" -#include "scene/resources/concave_polygon_shape_3d.h" -#include "scene/resources/convex_polygon_shape_3d.h" -#include "scene/resources/cylinder_shape_3d.h" -#include "scene/resources/height_map_shape_3d.h" -#include "scene/resources/separation_ray_shape_3d.h" -#include "scene/resources/sphere_shape_3d.h" -#include "scene/resources/world_boundary_shape_3d.h" +#include "scene/resources/3d/box_shape_3d.h" +#include "scene/resources/3d/capsule_shape_3d.h" +#include "scene/resources/3d/concave_polygon_shape_3d.h" +#include "scene/resources/3d/convex_polygon_shape_3d.h" +#include "scene/resources/3d/cylinder_shape_3d.h" +#include "scene/resources/3d/height_map_shape_3d.h" +#include "scene/resources/3d/separation_ray_shape_3d.h" +#include "scene/resources/3d/sphere_shape_3d.h" +#include "scene/resources/3d/world_boundary_shape_3d.h" CollisionShape3DGizmoPlugin::CollisionShape3DGizmoPlugin() { helper.instantiate(); diff --git a/editor/plugins/gizmos/spring_arm_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/spring_arm_3d_gizmo_plugin.cpp index cf022fc30f..69acac5a89 100644 --- a/editor/plugins/gizmos/spring_arm_3d_gizmo_plugin.cpp +++ b/editor/plugins/gizmos/spring_arm_3d_gizmo_plugin.cpp @@ -33,7 +33,7 @@ #include "editor/editor_settings.h" #include "editor/plugins/node_3d_editor_plugin.h" #include "scene/3d/spring_arm_3d.h" -#include "scene/resources/shape_3d.h" +#include "scene/resources/3d/shape_3d.h" void SpringArm3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { SpringArm3D *spring_arm = Object::cast_to(p_gizmo->get_node_3d()); diff --git a/editor/plugins/material_editor_plugin.cpp b/editor/plugins/material_editor_plugin.cpp index b7c4479505..ba64c85b37 100644 --- a/editor/plugins/material_editor_plugin.cpp +++ b/editor/plugins/material_editor_plugin.cpp @@ -43,9 +43,9 @@ #include "scene/gui/color_rect.h" #include "scene/gui/subviewport_container.h" #include "scene/main/viewport.h" -#include "scene/resources/fog_material.h" +#include "scene/resources/3d/fog_material.h" +#include "scene/resources/3d/sky_material.h" #include "scene/resources/particle_process_material.h" -#include "scene/resources/sky_material.h" void MaterialEditor::gui_input(const Ref &p_event) { ERR_FAIL_COND(p_event.is_null()); diff --git a/editor/plugins/material_editor_plugin.h b/editor/plugins/material_editor_plugin.h index 5530592a81..5d79d94aaf 100644 --- a/editor/plugins/material_editor_plugin.h +++ b/editor/plugins/material_editor_plugin.h @@ -34,8 +34,8 @@ #include "editor/editor_inspector.h" #include "editor/editor_plugin.h" #include "editor/plugins/editor_resource_conversion_plugin.h" +#include "scene/resources/3d/primitive_meshes.h" #include "scene/resources/material.h" -#include "scene/resources/primitive_meshes.h" class Camera3D; class ColorRect; diff --git a/editor/plugins/mesh_instance_3d_editor_plugin.cpp b/editor/plugins/mesh_instance_3d_editor_plugin.cpp index 9669f992a8..061b839ee3 100644 --- a/editor/plugins/mesh_instance_3d_editor_plugin.cpp +++ b/editor/plugins/mesh_instance_3d_editor_plugin.cpp @@ -42,9 +42,9 @@ #include "scene/gui/dialogs.h" #include "scene/gui/menu_button.h" #include "scene/gui/spin_box.h" -#include "scene/resources/concave_polygon_shape_3d.h" -#include "scene/resources/convex_polygon_shape_3d.h" -#include "scene/resources/primitive_meshes.h" +#include "scene/resources/3d/concave_polygon_shape_3d.h" +#include "scene/resources/3d/convex_polygon_shape_3d.h" +#include "scene/resources/3d/primitive_meshes.h" #include "scene/scene_string_names.h" void MeshInstance3DEditor::_node_removed(Node *p_node) { diff --git a/editor/plugins/mesh_library_editor_plugin.h b/editor/plugins/mesh_library_editor_plugin.h index 7bdc70a00b..3895d10c37 100644 --- a/editor/plugins/mesh_library_editor_plugin.h +++ b/editor/plugins/mesh_library_editor_plugin.h @@ -32,7 +32,7 @@ #define MESH_LIBRARY_EDITOR_PLUGIN_H #include "editor/editor_plugin.h" -#include "scene/resources/mesh_library.h" +#include "scene/resources/3d/mesh_library.h" class EditorFileDialog; class ConfirmationDialog; diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index 77ed48a662..085e8cddbe 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -36,7 +36,7 @@ #include "editor/editor_settings.h" #include "editor/editor_string_names.h" #include "editor/plugins/node_3d_editor_plugin.h" -#include "scene/resources/primitive_meshes.h" +#include "scene/resources/3d/primitive_meshes.h" #define HANDLE_HALF_SIZE 9.5 diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 64590653a4..4e462cfe34 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -90,8 +90,8 @@ #include "scene/gui/flow_container.h" #include "scene/gui/split_container.h" #include "scene/gui/subviewport_container.h" +#include "scene/resources/3d/sky_material.h" #include "scene/resources/packed_scene.h" -#include "scene/resources/sky_material.h" #include "scene/resources/surface_tool.h" constexpr real_t DISTANCE_DEFAULT = 4; diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index 53fdde9e14..b6a6c8968e 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -46,9 +46,8 @@ #include "scene/3d/physics_body_3d.h" #include "scene/gui/separator.h" #include "scene/gui/texture_rect.h" -#include "scene/resources/capsule_shape_3d.h" +#include "scene/resources/3d/capsule_shape_3d.h" #include "scene/resources/skeleton_profile.h" -#include "scene/resources/sphere_shape_3d.h" #include "scene/resources/surface_tool.h" #include "scene/scene_string_names.h" diff --git a/modules/csg/csg_shape.h b/modules/csg/csg_shape.h index 6ac71b6946..bb7c8be431 100644 --- a/modules/csg/csg_shape.h +++ b/modules/csg/csg_shape.h @@ -35,7 +35,7 @@ #include "scene/3d/path_3d.h" #include "scene/3d/visual_instance_3d.h" -#include "scene/resources/concave_polygon_shape_3d.h" +#include "scene/resources/3d/concave_polygon_shape_3d.h" #include "thirdparty/misc/mikktspace.h" diff --git a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp index 2af716b867..1e64a6daa4 100644 --- a/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp +++ b/modules/gltf/extensions/gltf_document_extension_convert_importer_mesh.cpp @@ -32,7 +32,7 @@ #include "scene/3d/importer_mesh_instance_3d.h" #include "scene/3d/mesh_instance_3d.h" -#include "scene/resources/importer_mesh.h" +#include "scene/resources/3d/importer_mesh.h" void GLTFDocumentExtensionConvertImporterMesh::_bind_methods() { } diff --git a/modules/gltf/extensions/physics/gltf_physics_shape.cpp b/modules/gltf/extensions/physics/gltf_physics_shape.cpp index af4ac10313..467499a03f 100644 --- a/modules/gltf/extensions/physics/gltf_physics_shape.cpp +++ b/modules/gltf/extensions/physics/gltf_physics_shape.cpp @@ -34,13 +34,13 @@ #include "core/math/convex_hull.h" #include "scene/3d/area_3d.h" -#include "scene/resources/box_shape_3d.h" -#include "scene/resources/capsule_shape_3d.h" -#include "scene/resources/concave_polygon_shape_3d.h" -#include "scene/resources/convex_polygon_shape_3d.h" -#include "scene/resources/cylinder_shape_3d.h" -#include "scene/resources/importer_mesh.h" -#include "scene/resources/sphere_shape_3d.h" +#include "scene/resources/3d/box_shape_3d.h" +#include "scene/resources/3d/capsule_shape_3d.h" +#include "scene/resources/3d/concave_polygon_shape_3d.h" +#include "scene/resources/3d/convex_polygon_shape_3d.h" +#include "scene/resources/3d/cylinder_shape_3d.h" +#include "scene/resources/3d/importer_mesh.h" +#include "scene/resources/3d/sphere_shape_3d.h" void GLTFPhysicsShape::_bind_methods() { ClassDB::bind_static_method("GLTFPhysicsShape", D_METHOD("from_node", "shape_node"), &GLTFPhysicsShape::from_node); diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 7949d64920..740c9a2aa7 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -50,9 +50,9 @@ #include "scene/3d/light_3d.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/multimesh_instance_3d.h" +#include "scene/resources/3d/skin.h" #include "scene/resources/image_texture.h" #include "scene/resources/portable_compressed_texture.h" -#include "scene/resources/skin.h" #include "scene/resources/surface_tool.h" #ifdef TOOLS_ENABLED diff --git a/modules/gltf/structures/gltf_mesh.cpp b/modules/gltf/structures/gltf_mesh.cpp index d04d3fcd7f..9566cc2379 100644 --- a/modules/gltf/structures/gltf_mesh.cpp +++ b/modules/gltf/structures/gltf_mesh.cpp @@ -30,7 +30,7 @@ #include "gltf_mesh.h" -#include "scene/resources/importer_mesh.h" +#include "scene/resources/3d/importer_mesh.h" void GLTFMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("get_original_name"), &GLTFMesh::get_original_name); diff --git a/modules/gltf/structures/gltf_mesh.h b/modules/gltf/structures/gltf_mesh.h index c33e39df84..6983efeb2a 100644 --- a/modules/gltf/structures/gltf_mesh.h +++ b/modules/gltf/structures/gltf_mesh.h @@ -33,7 +33,7 @@ #include "../gltf_defines.h" -#include "scene/resources/importer_mesh.h" +#include "scene/resources/3d/importer_mesh.h" class GLTFMesh : public Resource { GDCLASS(GLTFMesh, Resource); diff --git a/modules/gltf/structures/gltf_skin.cpp b/modules/gltf/structures/gltf_skin.cpp index 8097c7b3d0..18aa90a628 100644 --- a/modules/gltf/structures/gltf_skin.cpp +++ b/modules/gltf/structures/gltf_skin.cpp @@ -33,7 +33,7 @@ #include "../gltf_template_convert.h" #include "core/variant/typed_array.h" -#include "scene/resources/skin.h" +#include "scene/resources/3d/skin.h" void GLTFSkin::_bind_methods() { ClassDB::bind_method(D_METHOD("get_skin_root"), &GLTFSkin::get_skin_root); diff --git a/modules/gltf/structures/gltf_skin.h b/modules/gltf/structures/gltf_skin.h index ce863da45d..4649a918e3 100644 --- a/modules/gltf/structures/gltf_skin.h +++ b/modules/gltf/structures/gltf_skin.h @@ -34,7 +34,7 @@ #include "../gltf_defines.h" #include "core/io/resource.h" -#include "scene/resources/skin.h" +#include "scene/resources/3d/skin.h" template class TypedArray; diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index e3475b3959..fb449a67f8 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -33,9 +33,9 @@ #include "core/core_string_names.h" #include "core/io/marshalls.h" #include "scene/3d/light_3d.h" -#include "scene/resources/mesh_library.h" +#include "scene/resources/3d/mesh_library.h" +#include "scene/resources/3d/primitive_meshes.h" #include "scene/resources/physics_material.h" -#include "scene/resources/primitive_meshes.h" #include "scene/resources/surface_tool.h" #include "scene/scene_string_names.h" #include "servers/navigation_server_3d.h" diff --git a/modules/gridmap/grid_map.h b/modules/gridmap/grid_map.h index 348ac5194c..7398a540de 100644 --- a/modules/gridmap/grid_map.h +++ b/modules/gridmap/grid_map.h @@ -32,7 +32,7 @@ #define GRID_MAP_H #include "scene/3d/node_3d.h" -#include "scene/resources/mesh_library.h" +#include "scene/resources/3d/mesh_library.h" #include "scene/resources/multimesh.h" //heh heh, godotsphir!! this shares no code and the design is completely different with previous projects i've done.. diff --git a/modules/navigation/3d/nav_mesh_generator_3d.cpp b/modules/navigation/3d/nav_mesh_generator_3d.cpp index 95854f29e7..4cd4f60edc 100644 --- a/modules/navigation/3d/nav_mesh_generator_3d.cpp +++ b/modules/navigation/3d/nav_mesh_generator_3d.cpp @@ -38,18 +38,18 @@ #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/multimesh_instance_3d.h" #include "scene/3d/physics_body_3d.h" -#include "scene/resources/box_shape_3d.h" -#include "scene/resources/capsule_shape_3d.h" -#include "scene/resources/concave_polygon_shape_3d.h" -#include "scene/resources/convex_polygon_shape_3d.h" -#include "scene/resources/cylinder_shape_3d.h" -#include "scene/resources/height_map_shape_3d.h" +#include "scene/resources/3d/box_shape_3d.h" +#include "scene/resources/3d/capsule_shape_3d.h" +#include "scene/resources/3d/concave_polygon_shape_3d.h" +#include "scene/resources/3d/convex_polygon_shape_3d.h" +#include "scene/resources/3d/cylinder_shape_3d.h" +#include "scene/resources/3d/height_map_shape_3d.h" +#include "scene/resources/3d/primitive_meshes.h" +#include "scene/resources/3d/shape_3d.h" +#include "scene/resources/3d/sphere_shape_3d.h" +#include "scene/resources/3d/world_boundary_shape_3d.h" #include "scene/resources/navigation_mesh.h" #include "scene/resources/navigation_mesh_source_geometry_data_3d.h" -#include "scene/resources/primitive_meshes.h" -#include "scene/resources/shape_3d.h" -#include "scene/resources/sphere_shape_3d.h" -#include "scene/resources/world_boundary_shape_3d.h" #include "modules/modules_enabled.gen.h" // For csg, gridmap. diff --git a/scene/3d/collision_object_3d.cpp b/scene/3d/collision_object_3d.cpp index 0cfe0f8cb7..bbd2ef2fb8 100644 --- a/scene/3d/collision_object_3d.cpp +++ b/scene/3d/collision_object_3d.cpp @@ -30,7 +30,7 @@ #include "collision_object_3d.h" -#include "scene/resources/shape_3d.h" +#include "scene/resources/3d/shape_3d.h" #include "scene/scene_string_names.h" void CollisionObject3D::_notification(int p_what) { diff --git a/scene/3d/collision_polygon_3d.cpp b/scene/3d/collision_polygon_3d.cpp index 9c1a7181aa..abde05778d 100644 --- a/scene/3d/collision_polygon_3d.cpp +++ b/scene/3d/collision_polygon_3d.cpp @@ -32,7 +32,7 @@ #include "collision_object_3d.h" #include "core/math/geometry_2d.h" -#include "scene/resources/convex_polygon_shape_3d.h" +#include "scene/resources/3d/convex_polygon_shape_3d.h" void CollisionPolygon3D::_build_polygon() { if (!collision_object) { diff --git a/scene/3d/collision_polygon_3d.h b/scene/3d/collision_polygon_3d.h index 61290a7947..fe4cf40ee3 100644 --- a/scene/3d/collision_polygon_3d.h +++ b/scene/3d/collision_polygon_3d.h @@ -32,7 +32,7 @@ #define COLLISION_POLYGON_3D_H #include "scene/3d/node_3d.h" -#include "scene/resources/shape_3d.h" +#include "scene/resources/3d/shape_3d.h" class CollisionObject3D; class CollisionPolygon3D : public Node3D { diff --git a/scene/3d/collision_shape_3d.cpp b/scene/3d/collision_shape_3d.cpp index 61941a0e53..08fc62ae4f 100644 --- a/scene/3d/collision_shape_3d.cpp +++ b/scene/3d/collision_shape_3d.cpp @@ -32,9 +32,9 @@ #include "mesh_instance_3d.h" #include "physics_body_3d.h" -#include "scene/resources/concave_polygon_shape_3d.h" -#include "scene/resources/convex_polygon_shape_3d.h" -#include "scene/resources/world_boundary_shape_3d.h" +#include "scene/resources/3d/concave_polygon_shape_3d.h" +#include "scene/resources/3d/convex_polygon_shape_3d.h" +#include "scene/resources/3d/world_boundary_shape_3d.h" #include "vehicle_body_3d.h" void CollisionShape3D::make_convex_from_siblings() { diff --git a/scene/3d/collision_shape_3d.h b/scene/3d/collision_shape_3d.h index bc0e70f8ac..15f6ef73cb 100644 --- a/scene/3d/collision_shape_3d.h +++ b/scene/3d/collision_shape_3d.h @@ -32,7 +32,7 @@ #define COLLISION_SHAPE_3D_H #include "scene/3d/node_3d.h" -#include "scene/resources/shape_3d.h" +#include "scene/resources/3d/shape_3d.h" class CollisionObject3D; class CollisionShape3D : public Node3D { diff --git a/scene/3d/gpu_particles_3d.h b/scene/3d/gpu_particles_3d.h index 0c9f2c1378..b5aee6fe34 100644 --- a/scene/3d/gpu_particles_3d.h +++ b/scene/3d/gpu_particles_3d.h @@ -32,7 +32,7 @@ #define GPU_PARTICLES_3D_H #include "scene/3d/visual_instance_3d.h" -#include "scene/resources/skin.h" +#include "scene/resources/3d/skin.h" class GPUParticles3D : public GeometryInstance3D { private: diff --git a/scene/3d/importer_mesh_instance_3d.cpp b/scene/3d/importer_mesh_instance_3d.cpp index 2216b3818d..ad44c9e56a 100644 --- a/scene/3d/importer_mesh_instance_3d.cpp +++ b/scene/3d/importer_mesh_instance_3d.cpp @@ -30,7 +30,7 @@ #include "importer_mesh_instance_3d.h" -#include "scene/resources/importer_mesh.h" +#include "scene/resources/3d/importer_mesh.h" void ImporterMeshInstance3D::set_mesh(const Ref &p_mesh) { mesh = p_mesh; diff --git a/scene/3d/importer_mesh_instance_3d.h b/scene/3d/importer_mesh_instance_3d.h index d48721383f..fbd95d4b68 100644 --- a/scene/3d/importer_mesh_instance_3d.h +++ b/scene/3d/importer_mesh_instance_3d.h @@ -33,8 +33,8 @@ #include "scene/3d/node_3d.h" #include "scene/3d/visual_instance_3d.h" +#include "scene/resources/3d/skin.h" #include "scene/resources/immediate_mesh.h" -#include "scene/resources/skin.h" class ImporterMesh; diff --git a/scene/3d/mesh_instance_3d.cpp b/scene/3d/mesh_instance_3d.cpp index 35baf81e89..eeb41573f7 100644 --- a/scene/3d/mesh_instance_3d.cpp +++ b/scene/3d/mesh_instance_3d.cpp @@ -32,8 +32,8 @@ #include "collision_shape_3d.h" #include "physics_body_3d.h" -#include "scene/resources/concave_polygon_shape_3d.h" -#include "scene/resources/convex_polygon_shape_3d.h" +#include "scene/resources/3d/concave_polygon_shape_3d.h" +#include "scene/resources/3d/convex_polygon_shape_3d.h" bool MeshInstance3D::_set(const StringName &p_name, const Variant &p_value) { //this is not _too_ bad performance wise, really. it only arrives here if the property was not set anywhere else. diff --git a/scene/3d/node_3d.h b/scene/3d/node_3d.h index 20030210c6..7f8c3e6e68 100644 --- a/scene/3d/node_3d.h +++ b/scene/3d/node_3d.h @@ -32,7 +32,7 @@ #define NODE_3D_H #include "scene/main/node.h" -#include "scene/resources/world_3d.h" +#include "scene/resources/3d/world_3d.h" class Node3DGizmo : public RefCounted { GDCLASS(Node3DGizmo, RefCounted); diff --git a/scene/3d/occluder_instance_3d.cpp b/scene/3d/occluder_instance_3d.cpp index a76488e479..8d995a8785 100644 --- a/scene/3d/occluder_instance_3d.cpp +++ b/scene/3d/occluder_instance_3d.cpp @@ -36,7 +36,7 @@ #include "core/math/triangulate.h" #include "scene/3d/importer_mesh_instance_3d.h" #include "scene/3d/mesh_instance_3d.h" -#include "scene/resources/importer_mesh.h" +#include "scene/resources/3d/importer_mesh.h" #include "scene/resources/surface_tool.h" #ifdef TOOLS_ENABLED diff --git a/scene/3d/shape_cast_3d.cpp b/scene/3d/shape_cast_3d.cpp index 4d9eeada06..2da5f52928 100644 --- a/scene/3d/shape_cast_3d.cpp +++ b/scene/3d/shape_cast_3d.cpp @@ -32,7 +32,7 @@ #include "scene/3d/collision_object_3d.h" #include "scene/3d/mesh_instance_3d.h" -#include "scene/resources/concave_polygon_shape_3d.h" +#include "scene/resources/3d/concave_polygon_shape_3d.h" void ShapeCast3D::_notification(int p_what) { switch (p_what) { diff --git a/scene/3d/shape_cast_3d.h b/scene/3d/shape_cast_3d.h index 043e35090f..9052db9046 100644 --- a/scene/3d/shape_cast_3d.h +++ b/scene/3d/shape_cast_3d.h @@ -32,7 +32,7 @@ #define SHAPE_CAST_3D_H #include "scene/3d/node_3d.h" -#include "scene/resources/shape_3d.h" +#include "scene/resources/3d/shape_3d.h" class CollisionObject3D; diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h index 458bbfb979..979e2e52b7 100644 --- a/scene/3d/skeleton_3d.h +++ b/scene/3d/skeleton_3d.h @@ -32,7 +32,7 @@ #define SKELETON_3D_H #include "scene/3d/node_3d.h" -#include "scene/resources/skin.h" +#include "scene/resources/3d/skin.h" typedef int BoneId; diff --git a/scene/3d/spring_arm_3d.cpp b/scene/3d/spring_arm_3d.cpp index 9151fa9593..8d2f10310c 100644 --- a/scene/3d/spring_arm_3d.cpp +++ b/scene/3d/spring_arm_3d.cpp @@ -31,7 +31,7 @@ #include "spring_arm_3d.h" #include "scene/3d/camera_3d.h" -#include "scene/resources/shape_3d.h" +#include "scene/resources/3d/shape_3d.h" void SpringArm3D::_notification(int p_what) { switch (p_what) { diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index a29311af43..8d0bad6942 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -55,12 +55,12 @@ #include "scene/resources/mesh.h" #include "scene/resources/packed_scene.h" #include "scene/resources/world_2d.h" -#include "scene/resources/world_3d.h" #include "scene/scene_string_names.h" #include "servers/display_server.h" #include "servers/navigation_server_3d.h" #include "servers/physics_server_2d.h" #ifndef _3D_DISABLED +#include "scene/resources/3d/world_3d.h" #include "servers/physics_server_3d.h" #endif // _3D_DISABLED #include "window.h" diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 77a331f52c..b156cd0422 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -194,7 +194,6 @@ #include "scene/resources/shader_include.h" #include "scene/resources/skeleton_profile.h" #include "scene/resources/sky.h" -#include "scene/resources/sky_material.h" #include "scene/resources/style_box.h" #include "scene/resources/style_box_flat.h" #include "scene/resources/style_box_line.h" @@ -265,20 +264,21 @@ #include "scene/3d/xr_hand_modifier_3d.h" #include "scene/3d/xr_nodes.h" #include "scene/animation/root_motion_view.h" -#include "scene/resources/box_shape_3d.h" -#include "scene/resources/capsule_shape_3d.h" -#include "scene/resources/concave_polygon_shape_3d.h" -#include "scene/resources/convex_polygon_shape_3d.h" -#include "scene/resources/cylinder_shape_3d.h" -#include "scene/resources/fog_material.h" -#include "scene/resources/height_map_shape_3d.h" -#include "scene/resources/importer_mesh.h" -#include "scene/resources/mesh_library.h" -#include "scene/resources/primitive_meshes.h" -#include "scene/resources/separation_ray_shape_3d.h" -#include "scene/resources/sphere_shape_3d.h" -#include "scene/resources/world_3d.h" -#include "scene/resources/world_boundary_shape_3d.h" +#include "scene/resources/3d/box_shape_3d.h" +#include "scene/resources/3d/capsule_shape_3d.h" +#include "scene/resources/3d/concave_polygon_shape_3d.h" +#include "scene/resources/3d/convex_polygon_shape_3d.h" +#include "scene/resources/3d/cylinder_shape_3d.h" +#include "scene/resources/3d/fog_material.h" +#include "scene/resources/3d/height_map_shape_3d.h" +#include "scene/resources/3d/importer_mesh.h" +#include "scene/resources/3d/mesh_library.h" +#include "scene/resources/3d/primitive_meshes.h" +#include "scene/resources/3d/separation_ray_shape_3d.h" +#include "scene/resources/3d/sky_material.h" +#include "scene/resources/3d/sphere_shape_3d.h" +#include "scene/resources/3d/world_3d.h" +#include "scene/resources/3d/world_boundary_shape_3d.h" #endif // _3D_DISABLED static Ref resource_saver_text; diff --git a/scene/resources/3d/SCsub b/scene/resources/3d/SCsub new file mode 100644 index 0000000000..fdf20e0bde --- /dev/null +++ b/scene/resources/3d/SCsub @@ -0,0 +1,6 @@ +#!/usr/bin/env python + +Import("env") + +env.add_source_files(env.scene_sources, "*.cpp") +env.add_source_files(env.scene_sources, "skeleton/*.cpp") diff --git a/scene/resources/3d/box_shape_3d.cpp b/scene/resources/3d/box_shape_3d.cpp new file mode 100644 index 0000000000..313aeb1bca --- /dev/null +++ b/scene/resources/3d/box_shape_3d.cpp @@ -0,0 +1,100 @@ +/**************************************************************************/ +/* box_shape_3d.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 "box_shape_3d.h" +#include "servers/physics_server_3d.h" + +Vector BoxShape3D::get_debug_mesh_lines() const { + Vector lines; + AABB aabb; + aabb.position = -size / 2; + aabb.size = size; + + for (int i = 0; i < 12; i++) { + Vector3 a, b; + aabb.get_edge(i, a, b); + lines.push_back(a); + lines.push_back(b); + } + + return lines; +} + +real_t BoxShape3D::get_enclosing_radius() const { + return size.length() / 2; +} + +void BoxShape3D::_update_shape() { + PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), size / 2); + Shape3D::_update_shape(); +} + +#ifndef DISABLE_DEPRECATED +bool BoxShape3D::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "extents") { // Compatibility with Godot 3.x. + // Convert to `size`, twice as big. + set_size((Vector3)p_value * 2); + return true; + } + return false; +} + +bool BoxShape3D::_get(const StringName &p_name, Variant &r_property) const { + if (p_name == "extents") { // Compatibility with Godot 3.x. + // Convert to `extents`, half as big. + r_property = size / 2; + return true; + } + return false; +} +#endif // DISABLE_DEPRECATED + +void BoxShape3D::set_size(const Vector3 &p_size) { + ERR_FAIL_COND_MSG(p_size.x < 0 || p_size.y < 0 || p_size.z < 0, "BoxShape3D size cannot be negative."); + size = p_size; + _update_shape(); + emit_changed(); +} + +Vector3 BoxShape3D::get_size() const { + return size; +} + +void BoxShape3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_size", "size"), &BoxShape3D::set_size); + ClassDB::bind_method(D_METHOD("get_size"), &BoxShape3D::get_size); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size", PROPERTY_HINT_NONE, "suffix:m"), "set_size", "get_size"); +} + +BoxShape3D::BoxShape3D() : + Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_BOX)) { + set_size(Vector3(1, 1, 1)); +} diff --git a/scene/resources/3d/box_shape_3d.h b/scene/resources/3d/box_shape_3d.h new file mode 100644 index 0000000000..45c1cde5d7 --- /dev/null +++ b/scene/resources/3d/box_shape_3d.h @@ -0,0 +1,59 @@ +/**************************************************************************/ +/* box_shape_3d.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 BOX_SHAPE_3D_H +#define BOX_SHAPE_3D_H + +#include "scene/resources/3d/shape_3d.h" + +class BoxShape3D : public Shape3D { + GDCLASS(BoxShape3D, Shape3D); + Vector3 size; + +protected: + static void _bind_methods(); +#ifndef DISABLE_DEPRECATED + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_property) const; +#endif // DISABLE_DEPRECATED + + virtual void _update_shape() override; + +public: + void set_size(const Vector3 &p_size); + Vector3 get_size() const; + + virtual Vector get_debug_mesh_lines() const override; + virtual real_t get_enclosing_radius() const override; + + BoxShape3D(); +}; + +#endif // BOX_SHAPE_3D_H diff --git a/scene/resources/3d/capsule_shape_3d.cpp b/scene/resources/3d/capsule_shape_3d.cpp new file mode 100644 index 0000000000..9e16801060 --- /dev/null +++ b/scene/resources/3d/capsule_shape_3d.cpp @@ -0,0 +1,125 @@ +/**************************************************************************/ +/* capsule_shape_3d.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 "capsule_shape_3d.h" + +#include "servers/physics_server_3d.h" + +Vector CapsuleShape3D::get_debug_mesh_lines() const { + float c_radius = get_radius(); + float c_height = get_height(); + + Vector points; + + Vector3 d(0, c_height * 0.5 - c_radius, 0); + for (int i = 0; i < 360; i++) { + float ra = Math::deg_to_rad((float)i); + float rb = Math::deg_to_rad((float)i + 1); + Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * c_radius; + Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * c_radius; + + points.push_back(Vector3(a.x, 0, a.y) + d); + points.push_back(Vector3(b.x, 0, b.y) + d); + + points.push_back(Vector3(a.x, 0, a.y) - d); + points.push_back(Vector3(b.x, 0, b.y) - d); + + if (i % 90 == 0) { + points.push_back(Vector3(a.x, 0, a.y) + d); + points.push_back(Vector3(a.x, 0, a.y) - d); + } + + Vector3 dud = i < 180 ? d : -d; + + points.push_back(Vector3(0, a.x, a.y) + dud); + points.push_back(Vector3(0, b.x, b.y) + dud); + points.push_back(Vector3(a.y, a.x, 0) + dud); + points.push_back(Vector3(b.y, b.x, 0) + dud); + } + + return points; +} + +real_t CapsuleShape3D::get_enclosing_radius() const { + return height * 0.5; +} + +void CapsuleShape3D::_update_shape() { + Dictionary d; + d["radius"] = radius; + d["height"] = height; + PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), d); + Shape3D::_update_shape(); +} + +void CapsuleShape3D::set_radius(float p_radius) { + ERR_FAIL_COND_MSG(p_radius < 0, "CapsuleShape3D radius cannot be negative."); + radius = p_radius; + if (radius > height * 0.5) { + height = radius * 2.0; + } + _update_shape(); + emit_changed(); +} + +float CapsuleShape3D::get_radius() const { + return radius; +} + +void CapsuleShape3D::set_height(float p_height) { + ERR_FAIL_COND_MSG(p_height < 0, "CapsuleShape3D height cannot be negative."); + height = p_height; + if (radius > height * 0.5) { + radius = height * 0.5; + } + _update_shape(); + emit_changed(); +} + +float CapsuleShape3D::get_height() const { + return height; +} + +void CapsuleShape3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CapsuleShape3D::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &CapsuleShape3D::get_radius); + ClassDB::bind_method(D_METHOD("set_height", "height"), &CapsuleShape3D::set_height); + ClassDB::bind_method(D_METHOD("get_height"), &CapsuleShape3D::get_height); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_height", "get_height"); + ADD_LINKED_PROPERTY("radius", "height"); + ADD_LINKED_PROPERTY("height", "radius"); +} + +CapsuleShape3D::CapsuleShape3D() : + Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_CAPSULE)) { + _update_shape(); +} diff --git a/scene/resources/3d/capsule_shape_3d.h b/scene/resources/3d/capsule_shape_3d.h new file mode 100644 index 0000000000..90ee3b584a --- /dev/null +++ b/scene/resources/3d/capsule_shape_3d.h @@ -0,0 +1,58 @@ +/**************************************************************************/ +/* capsule_shape_3d.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 CAPSULE_SHAPE_3D_H +#define CAPSULE_SHAPE_3D_H + +#include "scene/resources/3d/shape_3d.h" + +class CapsuleShape3D : public Shape3D { + GDCLASS(CapsuleShape3D, Shape3D); + float radius = 0.5; + float height = 2.0; + +protected: + static void _bind_methods(); + + virtual void _update_shape() override; + +public: + void set_radius(float p_radius); + float get_radius() const; + void set_height(float p_height); + float get_height() const; + + virtual Vector get_debug_mesh_lines() const override; + virtual real_t get_enclosing_radius() const override; + + CapsuleShape3D(); +}; + +#endif // CAPSULE_SHAPE_3D_H diff --git a/scene/resources/3d/concave_polygon_shape_3d.cpp b/scene/resources/3d/concave_polygon_shape_3d.cpp new file mode 100644 index 0000000000..82b125905f --- /dev/null +++ b/scene/resources/3d/concave_polygon_shape_3d.cpp @@ -0,0 +1,118 @@ +/**************************************************************************/ +/* concave_polygon_shape_3d.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 "concave_polygon_shape_3d.h" + +#include "servers/physics_server_3d.h" + +Vector ConcavePolygonShape3D::get_debug_mesh_lines() const { + HashSet edges; + + int index_count = faces.size(); + ERR_FAIL_COND_V((index_count % 3) != 0, Vector()); + + const Vector3 *r = faces.ptr(); + + for (int i = 0; i < index_count; i += 3) { + for (int j = 0; j < 3; j++) { + DrawEdge de(r[i + j], r[i + ((j + 1) % 3)]); + edges.insert(de); + } + } + + Vector points; + points.resize(edges.size() * 2); + int idx = 0; + for (const DrawEdge &E : edges) { + points.write[idx + 0] = E.a; + points.write[idx + 1] = E.b; + idx += 2; + } + + return points; +} + +real_t ConcavePolygonShape3D::get_enclosing_radius() const { + Vector data = get_faces(); + const Vector3 *read = data.ptr(); + real_t r = 0.0; + for (int i(0); i < data.size(); i++) { + r = MAX(read[i].length_squared(), r); + } + return Math::sqrt(r); +} + +void ConcavePolygonShape3D::_update_shape() { + Dictionary d; + d["faces"] = faces; + d["backface_collision"] = backface_collision; + PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), d); + + Shape3D::_update_shape(); +} + +void ConcavePolygonShape3D::set_faces(const Vector &p_faces) { + faces = p_faces; + _update_shape(); + emit_changed(); +} + +Vector ConcavePolygonShape3D::get_faces() const { + return faces; +} + +void ConcavePolygonShape3D::set_backface_collision_enabled(bool p_enabled) { + backface_collision = p_enabled; + + if (!faces.is_empty()) { + _update_shape(); + emit_changed(); + } +} + +bool ConcavePolygonShape3D::is_backface_collision_enabled() const { + return backface_collision; +} + +void ConcavePolygonShape3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_faces", "faces"), &ConcavePolygonShape3D::set_faces); + ClassDB::bind_method(D_METHOD("get_faces"), &ConcavePolygonShape3D::get_faces); + + ClassDB::bind_method(D_METHOD("set_backface_collision_enabled", "enabled"), &ConcavePolygonShape3D::set_backface_collision_enabled); + ClassDB::bind_method(D_METHOD("is_backface_collision_enabled"), &ConcavePolygonShape3D::is_backface_collision_enabled); + + ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_faces", "get_faces"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "backface_collision"), "set_backface_collision_enabled", "is_backface_collision_enabled"); +} + +ConcavePolygonShape3D::ConcavePolygonShape3D() : + Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_CONCAVE_POLYGON)) { + //set_planes(Vector3(1,1,1)); +} diff --git a/scene/resources/3d/concave_polygon_shape_3d.h b/scene/resources/3d/concave_polygon_shape_3d.h new file mode 100644 index 0000000000..a5e46474d5 --- /dev/null +++ b/scene/resources/3d/concave_polygon_shape_3d.h @@ -0,0 +1,80 @@ +/**************************************************************************/ +/* concave_polygon_shape_3d.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 CONCAVE_POLYGON_SHAPE_3D_H +#define CONCAVE_POLYGON_SHAPE_3D_H + +#include "scene/resources/3d/shape_3d.h" + +class ConcavePolygonShape3D : public Shape3D { + GDCLASS(ConcavePolygonShape3D, Shape3D); + + Vector faces; + bool backface_collision = false; + + struct DrawEdge { + Vector3 a; + Vector3 b; + static uint32_t hash(const DrawEdge &p_edge) { + uint32_t h = hash_murmur3_one_32(HashMapHasherDefault::hash(p_edge.a)); + return hash_murmur3_one_32(HashMapHasherDefault::hash(p_edge.b), h); + } + bool operator==(const DrawEdge &p_edge) const { + return (a == p_edge.a && b == p_edge.b); + } + + DrawEdge(const Vector3 &p_a = Vector3(), const Vector3 &p_b = Vector3()) { + a = p_a; + b = p_b; + if (a < b) { + SWAP(a, b); + } + } + }; + +protected: + static void _bind_methods(); + + virtual void _update_shape() override; + +public: + void set_faces(const Vector &p_faces); + Vector get_faces() const; + + void set_backface_collision_enabled(bool p_enabled); + bool is_backface_collision_enabled() const; + + virtual Vector get_debug_mesh_lines() const override; + virtual real_t get_enclosing_radius() const override; + + ConcavePolygonShape3D(); +}; + +#endif // CONCAVE_POLYGON_SHAPE_3D_H diff --git a/scene/resources/3d/convex_polygon_shape_3d.cpp b/scene/resources/3d/convex_polygon_shape_3d.cpp new file mode 100644 index 0000000000..3bfeeca461 --- /dev/null +++ b/scene/resources/3d/convex_polygon_shape_3d.cpp @@ -0,0 +1,90 @@ +/**************************************************************************/ +/* convex_polygon_shape_3d.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 "convex_polygon_shape_3d.h" +#include "core/math/convex_hull.h" +#include "servers/physics_server_3d.h" + +Vector ConvexPolygonShape3D::get_debug_mesh_lines() const { + Vector poly_points = get_points(); + + if (poly_points.size() > 3) { + Vector varr = Variant(poly_points); + Geometry3D::MeshData md; + Error err = ConvexHullComputer::convex_hull(varr, md); + if (err == OK) { + Vector lines; + lines.resize(md.edges.size() * 2); + for (uint32_t i = 0; i < md.edges.size(); i++) { + lines.write[i * 2 + 0] = md.vertices[md.edges[i].vertex_a]; + lines.write[i * 2 + 1] = md.vertices[md.edges[i].vertex_b]; + } + return lines; + } + } + + return Vector(); +} + +real_t ConvexPolygonShape3D::get_enclosing_radius() const { + Vector data = get_points(); + const Vector3 *read = data.ptr(); + real_t r = 0.0; + for (int i(0); i < data.size(); i++) { + r = MAX(read[i].length_squared(), r); + } + return Math::sqrt(r); +} + +void ConvexPolygonShape3D::_update_shape() { + PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), points); + Shape3D::_update_shape(); +} + +void ConvexPolygonShape3D::set_points(const Vector &p_points) { + points = p_points; + _update_shape(); + emit_changed(); +} + +Vector ConvexPolygonShape3D::get_points() const { + return points; +} + +void ConvexPolygonShape3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_points", "points"), &ConvexPolygonShape3D::set_points); + ClassDB::bind_method(D_METHOD("get_points"), &ConvexPolygonShape3D::get_points); + + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "points"), "set_points", "get_points"); +} + +ConvexPolygonShape3D::ConvexPolygonShape3D() : + Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_CONVEX_POLYGON)) { +} diff --git a/scene/resources/3d/convex_polygon_shape_3d.h b/scene/resources/3d/convex_polygon_shape_3d.h new file mode 100644 index 0000000000..7d1ac123c6 --- /dev/null +++ b/scene/resources/3d/convex_polygon_shape_3d.h @@ -0,0 +1,55 @@ +/**************************************************************************/ +/* convex_polygon_shape_3d.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 CONVEX_POLYGON_SHAPE_3D_H +#define CONVEX_POLYGON_SHAPE_3D_H + +#include "scene/resources/3d/shape_3d.h" + +class ConvexPolygonShape3D : public Shape3D { + GDCLASS(ConvexPolygonShape3D, Shape3D); + Vector points; + +protected: + static void _bind_methods(); + + virtual void _update_shape() override; + +public: + void set_points(const Vector &p_points); + Vector get_points() const; + + virtual Vector get_debug_mesh_lines() const override; + virtual real_t get_enclosing_radius() const override; + + ConvexPolygonShape3D(); +}; + +#endif // CONVEX_POLYGON_SHAPE_3D_H diff --git a/scene/resources/3d/cylinder_shape_3d.cpp b/scene/resources/3d/cylinder_shape_3d.cpp new file mode 100644 index 0000000000..a91282fd33 --- /dev/null +++ b/scene/resources/3d/cylinder_shape_3d.cpp @@ -0,0 +1,110 @@ +/**************************************************************************/ +/* cylinder_shape_3d.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 "cylinder_shape_3d.h" + +#include "servers/physics_server_3d.h" + +Vector CylinderShape3D::get_debug_mesh_lines() const { + float c_radius = get_radius(); + float c_height = get_height(); + + Vector points; + + Vector3 d(0, c_height * 0.5, 0); + for (int i = 0; i < 360; i++) { + float ra = Math::deg_to_rad((float)i); + float rb = Math::deg_to_rad((float)i + 1); + Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * c_radius; + Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * c_radius; + + points.push_back(Vector3(a.x, 0, a.y) + d); + points.push_back(Vector3(b.x, 0, b.y) + d); + + points.push_back(Vector3(a.x, 0, a.y) - d); + points.push_back(Vector3(b.x, 0, b.y) - d); + + if (i % 90 == 0) { + points.push_back(Vector3(a.x, 0, a.y) + d); + points.push_back(Vector3(a.x, 0, a.y) - d); + } + } + + return points; +} + +real_t CylinderShape3D::get_enclosing_radius() const { + return Vector2(radius, height * 0.5).length(); +} + +void CylinderShape3D::_update_shape() { + Dictionary d; + d["radius"] = radius; + d["height"] = height; + PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), d); + Shape3D::_update_shape(); +} + +void CylinderShape3D::set_radius(float p_radius) { + ERR_FAIL_COND_MSG(p_radius < 0, "CylinderShape3D radius cannot be negative."); + radius = p_radius; + _update_shape(); + emit_changed(); +} + +float CylinderShape3D::get_radius() const { + return radius; +} + +void CylinderShape3D::set_height(float p_height) { + ERR_FAIL_COND_MSG(p_height < 0, "CylinderShape3D height cannot be negative."); + height = p_height; + _update_shape(); + emit_changed(); +} + +float CylinderShape3D::get_height() const { + return height; +} + +void CylinderShape3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CylinderShape3D::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &CylinderShape3D::get_radius); + ClassDB::bind_method(D_METHOD("set_height", "height"), &CylinderShape3D::set_height); + ClassDB::bind_method(D_METHOD("get_height"), &CylinderShape3D::get_height); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_radius", "get_radius"); +} + +CylinderShape3D::CylinderShape3D() : + Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_CYLINDER)) { + _update_shape(); +} diff --git a/scene/resources/3d/cylinder_shape_3d.h b/scene/resources/3d/cylinder_shape_3d.h new file mode 100644 index 0000000000..bd57bc2a97 --- /dev/null +++ b/scene/resources/3d/cylinder_shape_3d.h @@ -0,0 +1,57 @@ +/**************************************************************************/ +/* cylinder_shape_3d.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 CYLINDER_SHAPE_3D_H +#define CYLINDER_SHAPE_3D_H + +#include "scene/resources/3d/shape_3d.h" + +class CylinderShape3D : public Shape3D { + GDCLASS(CylinderShape3D, Shape3D); + float radius = 0.5; + float height = 2.0; + +protected: + static void _bind_methods(); + virtual void _update_shape() override; + +public: + void set_radius(float p_radius); + float get_radius() const; + void set_height(float p_height); + float get_height() const; + + virtual Vector get_debug_mesh_lines() const override; + virtual real_t get_enclosing_radius() const override; + + CylinderShape3D(); +}; + +#endif // CYLINDER_SHAPE_3D_H diff --git a/scene/resources/3d/fog_material.cpp b/scene/resources/3d/fog_material.cpp new file mode 100644 index 0000000000..5e4f1970ee --- /dev/null +++ b/scene/resources/3d/fog_material.cpp @@ -0,0 +1,182 @@ +/**************************************************************************/ +/* fog_material.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 "fog_material.h" + +#include "core/version.h" + +Mutex FogMaterial::shader_mutex; +RID FogMaterial::shader; + +void FogMaterial::set_density(float p_density) { + density = p_density; + RS::get_singleton()->material_set_param(_get_material(), "density", density); +} + +float FogMaterial::get_density() const { + return density; +} + +void FogMaterial::set_albedo(Color p_albedo) { + albedo = p_albedo; + RS::get_singleton()->material_set_param(_get_material(), "albedo", albedo); +} + +Color FogMaterial::get_albedo() const { + return albedo; +} + +void FogMaterial::set_emission(Color p_emission) { + emission = p_emission; + RS::get_singleton()->material_set_param(_get_material(), "emission", emission); +} + +Color FogMaterial::get_emission() const { + return emission; +} + +void FogMaterial::set_height_falloff(float p_falloff) { + height_falloff = MAX(p_falloff, 0.0f); + RS::get_singleton()->material_set_param(_get_material(), "height_falloff", height_falloff); +} + +float FogMaterial::get_height_falloff() const { + return height_falloff; +} + +void FogMaterial::set_edge_fade(float p_edge_fade) { + edge_fade = MAX(p_edge_fade, 0.0f); + RS::get_singleton()->material_set_param(_get_material(), "edge_fade", edge_fade); +} + +float FogMaterial::get_edge_fade() const { + return edge_fade; +} + +void FogMaterial::set_density_texture(const Ref &p_texture) { + density_texture = p_texture; + Variant tex_rid = p_texture.is_valid() ? Variant(p_texture->get_rid()) : Variant(); + RS::get_singleton()->material_set_param(_get_material(), "density_texture", tex_rid); +} + +Ref FogMaterial::get_density_texture() const { + return density_texture; +} + +Shader::Mode FogMaterial::get_shader_mode() const { + return Shader::MODE_FOG; +} + +RID FogMaterial::get_shader_rid() const { + _update_shader(); + return shader; +} + +RID FogMaterial::get_rid() const { + _update_shader(); + if (!shader_set) { + RS::get_singleton()->material_set_shader(_get_material(), shader); + shader_set = true; + } + return _get_material(); +} + +void FogMaterial::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_density", "density"), &FogMaterial::set_density); + ClassDB::bind_method(D_METHOD("get_density"), &FogMaterial::get_density); + ClassDB::bind_method(D_METHOD("set_albedo", "albedo"), &FogMaterial::set_albedo); + ClassDB::bind_method(D_METHOD("get_albedo"), &FogMaterial::get_albedo); + ClassDB::bind_method(D_METHOD("set_emission", "emission"), &FogMaterial::set_emission); + ClassDB::bind_method(D_METHOD("get_emission"), &FogMaterial::get_emission); + ClassDB::bind_method(D_METHOD("set_height_falloff", "height_falloff"), &FogMaterial::set_height_falloff); + ClassDB::bind_method(D_METHOD("get_height_falloff"), &FogMaterial::get_height_falloff); + ClassDB::bind_method(D_METHOD("set_edge_fade", "edge_fade"), &FogMaterial::set_edge_fade); + ClassDB::bind_method(D_METHOD("get_edge_fade"), &FogMaterial::get_edge_fade); + ClassDB::bind_method(D_METHOD("set_density_texture", "density_texture"), &FogMaterial::set_density_texture); + ClassDB::bind_method(D_METHOD("get_density_texture"), &FogMaterial::get_density_texture); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "density", PROPERTY_HINT_RANGE, "-8.0,8.0,0.0001,or_greater,or_less"), "set_density", "get_density"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "albedo", PROPERTY_HINT_COLOR_NO_ALPHA), "set_albedo", "get_albedo"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "emission", PROPERTY_HINT_COLOR_NO_ALPHA), "set_emission", "get_emission"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height_falloff", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_height_falloff", "get_height_falloff"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "edge_fade", PROPERTY_HINT_EXP_EASING), "set_edge_fade", "get_edge_fade"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "density_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture3D"), "set_density_texture", "get_density_texture"); +} + +void FogMaterial::cleanup_shader() { + if (shader.is_valid()) { + ERR_FAIL_NULL(RenderingServer::get_singleton()); + RS::get_singleton()->free(shader); + } +} + +void FogMaterial::_update_shader() { + shader_mutex.lock(); + if (shader.is_null()) { + shader = RS::get_singleton()->shader_create(); + + // Add a comment to describe the shader origin (useful when converting to ShaderMaterial). + RS::get_singleton()->shader_set_code(shader, R"( +// NOTE: Shader automatically converted from )" VERSION_NAME " " VERSION_FULL_CONFIG R"('s FogMaterial. + +shader_type fog; + +uniform float density : hint_range(0, 1, 0.0001) = 1.0; +uniform vec4 albedo : source_color = vec4(1.0); +uniform vec4 emission : source_color = vec4(0, 0, 0, 1); +uniform float height_falloff = 0.0; +uniform float edge_fade = 0.1; +uniform sampler3D density_texture: hint_default_white; + + +void fog() { + DENSITY = density * clamp(exp2(-height_falloff * (WORLD_POSITION.y - OBJECT_POSITION.y)), 0.0, 1.0); + DENSITY *= texture(density_texture, UVW).r; + DENSITY *= pow(clamp(-2.0 * SDF / min(min(SIZE.x, SIZE.y), SIZE.z), 0.0, 1.0), edge_fade); + ALBEDO = albedo.rgb; + EMISSION = emission.rgb; +} +)"); + } + shader_mutex.unlock(); +} + +FogMaterial::FogMaterial() { + set_density(1.0); + set_albedo(Color(1, 1, 1, 1)); + set_emission(Color(0, 0, 0, 1)); + + set_height_falloff(0.0); + set_edge_fade(0.1); +} + +FogMaterial::~FogMaterial() { + RS::get_singleton()->material_set_shader(_get_material(), RID()); +} diff --git a/scene/resources/3d/fog_material.h b/scene/resources/3d/fog_material.h new file mode 100644 index 0000000000..7557fd5114 --- /dev/null +++ b/scene/resources/3d/fog_material.h @@ -0,0 +1,87 @@ +/**************************************************************************/ +/* fog_material.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 FOG_MATERIAL_H +#define FOG_MATERIAL_H + +#include "scene/resources/material.h" + +class FogMaterial : public Material { + GDCLASS(FogMaterial, Material); + +private: + float density = 1.0; + Color albedo = Color(1, 1, 1, 1); + Color emission = Color(0, 0, 0, 0); + + float height_falloff = 0.0; + + float edge_fade = 0.1; + + Ref density_texture; + + static Mutex shader_mutex; + static RID shader; + static void _update_shader(); + mutable bool shader_set = false; + +protected: + static void _bind_methods(); + +public: + void set_density(float p_density); + float get_density() const; + + void set_albedo(Color p_color); + Color get_albedo() const; + + void set_emission(Color p_color); + Color get_emission() const; + + void set_height_falloff(float p_falloff); + float get_height_falloff() const; + + void set_edge_fade(float p_edge_fade); + float get_edge_fade() const; + + void set_density_texture(const Ref &p_texture); + Ref get_density_texture() const; + + virtual Shader::Mode get_shader_mode() const override; + virtual RID get_shader_rid() const override; + virtual RID get_rid() const override; + + static void cleanup_shader(); + + FogMaterial(); + virtual ~FogMaterial(); +}; + +#endif // FOG_MATERIAL_H diff --git a/scene/resources/3d/height_map_shape_3d.cpp b/scene/resources/3d/height_map_shape_3d.cpp new file mode 100644 index 0000000000..35c905bd43 --- /dev/null +++ b/scene/resources/3d/height_map_shape_3d.cpp @@ -0,0 +1,215 @@ +/**************************************************************************/ +/* height_map_shape_3d.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 "height_map_shape_3d.h" + +#include "servers/physics_server_3d.h" + +Vector HeightMapShape3D::get_debug_mesh_lines() const { + Vector points; + + if ((map_width != 0) && (map_depth != 0)) { + // This will be slow for large maps... + // also we'll have to figure out how well bullet centers this shape... + + Vector2 size(map_width - 1, map_depth - 1); + Vector2 start = size * -0.5; + + const real_t *r = map_data.ptr(); + + // reserve some memory for our points.. + points.resize(((map_width - 1) * map_depth * 2) + (map_width * (map_depth - 1) * 2) + ((map_width - 1) * (map_depth - 1) * 2)); + + // now set our points + int r_offset = 0; + int w_offset = 0; + for (int d = 0; d < map_depth; d++) { + Vector3 height(start.x, 0.0, start.y); + + for (int w = 0; w < map_width; w++) { + height.y = r[r_offset++]; + + if (w != map_width - 1) { + points.write[w_offset++] = height; + points.write[w_offset++] = Vector3(height.x + 1.0, r[r_offset], height.z); + } + + if (d != map_depth - 1) { + points.write[w_offset++] = height; + points.write[w_offset++] = Vector3(height.x, r[r_offset + map_width - 1], height.z + 1.0); + } + + if ((w != map_width - 1) && (d != map_depth - 1)) { + points.write[w_offset++] = Vector3(height.x + 1.0, r[r_offset], height.z); + points.write[w_offset++] = Vector3(height.x, r[r_offset + map_width - 1], height.z + 1.0); + } + + height.x += 1.0; + } + + start.y += 1.0; + } + } + + return points; +} + +real_t HeightMapShape3D::get_enclosing_radius() const { + return Vector3(real_t(map_width), max_height - min_height, real_t(map_depth)).length(); +} + +void HeightMapShape3D::_update_shape() { + Dictionary d; + d["width"] = map_width; + d["depth"] = map_depth; + d["heights"] = map_data; + d["min_height"] = min_height; + d["max_height"] = max_height; + PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), d); + Shape3D::_update_shape(); +} + +void HeightMapShape3D::set_map_width(int p_new) { + if (p_new < 1) { + // ignore + } else if (map_width != p_new) { + int was_size = map_width * map_depth; + map_width = p_new; + + int new_size = map_width * map_depth; + map_data.resize(map_width * map_depth); + + real_t *w = map_data.ptrw(); + while (was_size < new_size) { + w[was_size++] = 0.0; + } + + _update_shape(); + emit_changed(); + } +} + +int HeightMapShape3D::get_map_width() const { + return map_width; +} + +void HeightMapShape3D::set_map_depth(int p_new) { + if (p_new < 1) { + // ignore + } else if (map_depth != p_new) { + int was_size = map_width * map_depth; + map_depth = p_new; + + int new_size = map_width * map_depth; + map_data.resize(new_size); + + real_t *w = map_data.ptrw(); + while (was_size < new_size) { + w[was_size++] = 0.0; + } + + _update_shape(); + emit_changed(); + } +} + +int HeightMapShape3D::get_map_depth() const { + return map_depth; +} + +void HeightMapShape3D::set_map_data(Vector p_new) { + int size = (map_width * map_depth); + if (p_new.size() != size) { + // fail + return; + } + + // copy + real_t *w = map_data.ptrw(); + const real_t *r = p_new.ptr(); + for (int i = 0; i < size; i++) { + real_t val = r[i]; + w[i] = val; + if (i == 0) { + min_height = val; + max_height = val; + } else { + if (min_height > val) { + min_height = val; + } + + if (max_height < val) { + max_height = val; + } + } + } + + _update_shape(); + emit_changed(); +} + +Vector HeightMapShape3D::get_map_data() const { + return map_data; +} + +real_t HeightMapShape3D::get_min_height() const { + return min_height; +} + +real_t HeightMapShape3D::get_max_height() const { + return max_height; +} + +void HeightMapShape3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_map_width", "width"), &HeightMapShape3D::set_map_width); + ClassDB::bind_method(D_METHOD("get_map_width"), &HeightMapShape3D::get_map_width); + ClassDB::bind_method(D_METHOD("set_map_depth", "height"), &HeightMapShape3D::set_map_depth); + ClassDB::bind_method(D_METHOD("get_map_depth"), &HeightMapShape3D::get_map_depth); + ClassDB::bind_method(D_METHOD("set_map_data", "data"), &HeightMapShape3D::set_map_data); + ClassDB::bind_method(D_METHOD("get_map_data"), &HeightMapShape3D::get_map_data); + ClassDB::bind_method(D_METHOD("get_min_height"), &HeightMapShape3D::get_min_height); + ClassDB::bind_method(D_METHOD("get_max_height"), &HeightMapShape3D::get_max_height); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "map_width", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), "set_map_width", "get_map_width"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "map_depth", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), "set_map_depth", "get_map_depth"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "map_data"), "set_map_data", "get_map_data"); +} + +HeightMapShape3D::HeightMapShape3D() : + Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_HEIGHTMAP)) { + map_data.resize(map_width * map_depth); + real_t *w = map_data.ptrw(); + w[0] = 0.0; + w[1] = 0.0; + w[2] = 0.0; + w[3] = 0.0; + + _update_shape(); +} diff --git a/scene/resources/3d/height_map_shape_3d.h b/scene/resources/3d/height_map_shape_3d.h new file mode 100644 index 0000000000..363d9ec0e9 --- /dev/null +++ b/scene/resources/3d/height_map_shape_3d.h @@ -0,0 +1,66 @@ +/**************************************************************************/ +/* height_map_shape_3d.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 HEIGHT_MAP_SHAPE_3D_H +#define HEIGHT_MAP_SHAPE_3D_H + +#include "scene/resources/3d/shape_3d.h" + +class HeightMapShape3D : public Shape3D { + GDCLASS(HeightMapShape3D, Shape3D); + + int map_width = 2; + int map_depth = 2; + Vector map_data; + real_t min_height = 0.0; + real_t max_height = 0.0; + +protected: + static void _bind_methods(); + virtual void _update_shape() override; + +public: + void set_map_width(int p_new); + int get_map_width() const; + void set_map_depth(int p_new); + int get_map_depth() const; + void set_map_data(Vector p_new); + Vector get_map_data() const; + + real_t get_min_height() const; + real_t get_max_height() const; + + virtual Vector get_debug_mesh_lines() const override; + virtual real_t get_enclosing_radius() const override; + + HeightMapShape3D(); +}; + +#endif // HEIGHT_MAP_SHAPE_3D_H diff --git a/scene/resources/3d/importer_mesh.cpp b/scene/resources/3d/importer_mesh.cpp new file mode 100644 index 0000000000..952e99608d --- /dev/null +++ b/scene/resources/3d/importer_mesh.cpp @@ -0,0 +1,1378 @@ +/**************************************************************************/ +/* importer_mesh.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 "importer_mesh.h" + +#include "core/io/marshalls.h" +#include "core/math/convex_hull.h" +#include "core/math/random_pcg.h" +#include "core/math/static_raycaster.h" +#include "scene/resources/surface_tool.h" + +#include + +void ImporterMesh::Surface::split_normals(const LocalVector &p_indices, const LocalVector &p_normals) { + _split_normals(arrays, p_indices, p_normals); + + for (BlendShape &blend_shape : blend_shape_data) { + _split_normals(blend_shape.arrays, p_indices, p_normals); + } +} + +void ImporterMesh::Surface::_split_normals(Array &r_arrays, const LocalVector &p_indices, const LocalVector &p_normals) { + ERR_FAIL_COND(r_arrays.size() != RS::ARRAY_MAX); + + const PackedVector3Array &vertices = r_arrays[RS::ARRAY_VERTEX]; + int current_vertex_count = vertices.size(); + int new_vertex_count = p_indices.size(); + int final_vertex_count = current_vertex_count + new_vertex_count; + const int *indices_ptr = p_indices.ptr(); + + for (int i = 0; i < r_arrays.size(); i++) { + if (i == RS::ARRAY_INDEX) { + continue; + } + + if (r_arrays[i].get_type() == Variant::NIL) { + continue; + } + + switch (r_arrays[i].get_type()) { + case Variant::PACKED_VECTOR3_ARRAY: { + PackedVector3Array data = r_arrays[i]; + data.resize(final_vertex_count); + Vector3 *data_ptr = data.ptrw(); + if (i == RS::ARRAY_NORMAL) { + const Vector3 *normals_ptr = p_normals.ptr(); + memcpy(&data_ptr[current_vertex_count], normals_ptr, sizeof(Vector3) * new_vertex_count); + } else { + for (int j = 0; j < new_vertex_count; j++) { + data_ptr[current_vertex_count + j] = data_ptr[indices_ptr[j]]; + } + } + r_arrays[i] = data; + } break; + case Variant::PACKED_VECTOR2_ARRAY: { + PackedVector2Array data = r_arrays[i]; + data.resize(final_vertex_count); + Vector2 *data_ptr = data.ptrw(); + for (int j = 0; j < new_vertex_count; j++) { + data_ptr[current_vertex_count + j] = data_ptr[indices_ptr[j]]; + } + r_arrays[i] = data; + } break; + case Variant::PACKED_FLOAT32_ARRAY: { + PackedFloat32Array data = r_arrays[i]; + int elements = data.size() / current_vertex_count; + data.resize(final_vertex_count * elements); + float *data_ptr = data.ptrw(); + for (int j = 0; j < new_vertex_count; j++) { + memcpy(&data_ptr[(current_vertex_count + j) * elements], &data_ptr[indices_ptr[j] * elements], sizeof(float) * elements); + } + r_arrays[i] = data; + } break; + case Variant::PACKED_INT32_ARRAY: { + PackedInt32Array data = r_arrays[i]; + int elements = data.size() / current_vertex_count; + data.resize(final_vertex_count * elements); + int32_t *data_ptr = data.ptrw(); + for (int j = 0; j < new_vertex_count; j++) { + memcpy(&data_ptr[(current_vertex_count + j) * elements], &data_ptr[indices_ptr[j] * elements], sizeof(int32_t) * elements); + } + r_arrays[i] = data; + } break; + case Variant::PACKED_BYTE_ARRAY: { + PackedByteArray data = r_arrays[i]; + int elements = data.size() / current_vertex_count; + data.resize(final_vertex_count * elements); + uint8_t *data_ptr = data.ptrw(); + for (int j = 0; j < new_vertex_count; j++) { + memcpy(&data_ptr[(current_vertex_count + j) * elements], &data_ptr[indices_ptr[j] * elements], sizeof(uint8_t) * elements); + } + r_arrays[i] = data; + } break; + case Variant::PACKED_COLOR_ARRAY: { + PackedColorArray data = r_arrays[i]; + data.resize(final_vertex_count); + Color *data_ptr = data.ptrw(); + for (int j = 0; j < new_vertex_count; j++) { + data_ptr[current_vertex_count + j] = data_ptr[indices_ptr[j]]; + } + r_arrays[i] = data; + } break; + default: { + ERR_FAIL_MSG("Unhandled array type."); + } break; + } + } +} + +void ImporterMesh::add_blend_shape(const String &p_name) { + ERR_FAIL_COND(surfaces.size() > 0); + blend_shapes.push_back(p_name); +} + +int ImporterMesh::get_blend_shape_count() const { + return blend_shapes.size(); +} + +String ImporterMesh::get_blend_shape_name(int p_blend_shape) const { + ERR_FAIL_INDEX_V(p_blend_shape, blend_shapes.size(), String()); + return blend_shapes[p_blend_shape]; +} + +void ImporterMesh::set_blend_shape_mode(Mesh::BlendShapeMode p_blend_shape_mode) { + blend_shape_mode = p_blend_shape_mode; +} + +Mesh::BlendShapeMode ImporterMesh::get_blend_shape_mode() const { + return blend_shape_mode; +} + +void ImporterMesh::add_surface(Mesh::PrimitiveType p_primitive, const Array &p_arrays, const TypedArray &p_blend_shapes, const Dictionary &p_lods, const Ref &p_material, const String &p_name, const uint64_t p_flags) { + ERR_FAIL_COND(p_blend_shapes.size() != blend_shapes.size()); + ERR_FAIL_COND(p_arrays.size() != Mesh::ARRAY_MAX); + Surface s; + s.primitive = p_primitive; + s.arrays = p_arrays; + s.name = p_name; + s.flags = p_flags; + + Vector vertex_array = p_arrays[Mesh::ARRAY_VERTEX]; + int vertex_count = vertex_array.size(); + ERR_FAIL_COND(vertex_count == 0); + + for (int i = 0; i < blend_shapes.size(); i++) { + Array bsdata = p_blend_shapes[i]; + ERR_FAIL_COND(bsdata.size() != Mesh::ARRAY_MAX); + Vector vertex_data = bsdata[Mesh::ARRAY_VERTEX]; + ERR_FAIL_COND(vertex_data.size() != vertex_count); + Surface::BlendShape bs; + bs.arrays = bsdata; + s.blend_shape_data.push_back(bs); + } + + List lods; + p_lods.get_key_list(&lods); + for (const Variant &E : lods) { + ERR_CONTINUE(!E.is_num()); + Surface::LOD lod; + lod.distance = E; + lod.indices = p_lods[E]; + ERR_CONTINUE(lod.indices.is_empty()); + s.lods.push_back(lod); + } + + s.material = p_material; + + surfaces.push_back(s); + mesh.unref(); +} + +int ImporterMesh::get_surface_count() const { + return surfaces.size(); +} + +Mesh::PrimitiveType ImporterMesh::get_surface_primitive_type(int p_surface) { + ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Mesh::PRIMITIVE_MAX); + return surfaces[p_surface].primitive; +} +Array ImporterMesh::get_surface_arrays(int p_surface) const { + ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Array()); + return surfaces[p_surface].arrays; +} +String ImporterMesh::get_surface_name(int p_surface) const { + ERR_FAIL_INDEX_V(p_surface, surfaces.size(), String()); + return surfaces[p_surface].name; +} +void ImporterMesh::set_surface_name(int p_surface, const String &p_name) { + ERR_FAIL_INDEX(p_surface, surfaces.size()); + surfaces.write[p_surface].name = p_name; + mesh.unref(); +} + +Array ImporterMesh::get_surface_blend_shape_arrays(int p_surface, int p_blend_shape) const { + ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Array()); + ERR_FAIL_INDEX_V(p_blend_shape, surfaces[p_surface].blend_shape_data.size(), Array()); + return surfaces[p_surface].blend_shape_data[p_blend_shape].arrays; +} +int ImporterMesh::get_surface_lod_count(int p_surface) const { + ERR_FAIL_INDEX_V(p_surface, surfaces.size(), 0); + return surfaces[p_surface].lods.size(); +} +Vector ImporterMesh::get_surface_lod_indices(int p_surface, int p_lod) const { + ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Vector()); + ERR_FAIL_INDEX_V(p_lod, surfaces[p_surface].lods.size(), Vector()); + + return surfaces[p_surface].lods[p_lod].indices; +} + +float ImporterMesh::get_surface_lod_size(int p_surface, int p_lod) const { + ERR_FAIL_INDEX_V(p_surface, surfaces.size(), 0); + ERR_FAIL_INDEX_V(p_lod, surfaces[p_surface].lods.size(), 0); + return surfaces[p_surface].lods[p_lod].distance; +} + +uint64_t ImporterMesh::get_surface_format(int p_surface) const { + ERR_FAIL_INDEX_V(p_surface, surfaces.size(), 0); + return surfaces[p_surface].flags; +} + +Ref ImporterMesh::get_surface_material(int p_surface) const { + ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Ref()); + return surfaces[p_surface].material; +} + +void ImporterMesh::set_surface_material(int p_surface, const Ref &p_material) { + ERR_FAIL_INDEX(p_surface, surfaces.size()); + surfaces.write[p_surface].material = p_material; + mesh.unref(); +} + +#define VERTEX_SKIN_FUNC(bone_count, vert_idx, read_array, write_array, transform_array, bone_array, weight_array) \ + Vector3 transformed_vert; \ + for (unsigned int weight_idx = 0; weight_idx < bone_count; weight_idx++) { \ + int bone_idx = bone_array[vert_idx * bone_count + weight_idx]; \ + float w = weight_array[vert_idx * bone_count + weight_idx]; \ + if (w < FLT_EPSILON) { \ + continue; \ + } \ + ERR_FAIL_INDEX(bone_idx, static_cast(transform_array.size())); \ + transformed_vert += transform_array[bone_idx].xform(read_array[vert_idx]) * w; \ + } \ + write_array[vert_idx] = transformed_vert; + +void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_split_angle, Array p_bone_transform_array) { + if (!SurfaceTool::simplify_scale_func) { + return; + } + if (!SurfaceTool::simplify_with_attrib_func) { + return; + } + if (!SurfaceTool::optimize_vertex_cache_func) { + return; + } + + LocalVector bone_transform_vector; + for (int i = 0; i < p_bone_transform_array.size(); i++) { + ERR_FAIL_COND(p_bone_transform_array[i].get_type() != Variant::TRANSFORM3D); + bone_transform_vector.push_back(p_bone_transform_array[i]); + } + + for (int i = 0; i < surfaces.size(); i++) { + if (surfaces[i].primitive != Mesh::PRIMITIVE_TRIANGLES) { + continue; + } + + surfaces.write[i].lods.clear(); + Vector vertices = surfaces[i].arrays[RS::ARRAY_VERTEX]; + PackedInt32Array indices = surfaces[i].arrays[RS::ARRAY_INDEX]; + Vector normals = surfaces[i].arrays[RS::ARRAY_NORMAL]; + Vector uvs = surfaces[i].arrays[RS::ARRAY_TEX_UV]; + Vector uv2s = surfaces[i].arrays[RS::ARRAY_TEX_UV2]; + Vector bones = surfaces[i].arrays[RS::ARRAY_BONES]; + Vector weights = surfaces[i].arrays[RS::ARRAY_WEIGHTS]; + + unsigned int index_count = indices.size(); + unsigned int vertex_count = vertices.size(); + + if (index_count == 0) { + continue; //no lods if no indices + } + + const Vector3 *vertices_ptr = vertices.ptr(); + const int *indices_ptr = indices.ptr(); + + if (normals.is_empty()) { + normals.resize(index_count); + Vector3 *n_ptr = normals.ptrw(); + for (unsigned int j = 0; j < index_count; j += 3) { + const Vector3 &v0 = vertices_ptr[indices_ptr[j + 0]]; + const Vector3 &v1 = vertices_ptr[indices_ptr[j + 1]]; + const Vector3 &v2 = vertices_ptr[indices_ptr[j + 2]]; + Vector3 n = vec3_cross(v0 - v2, v0 - v1).normalized(); + n_ptr[j + 0] = n; + n_ptr[j + 1] = n; + n_ptr[j + 2] = n; + } + } + + if (bones.size() > 0 && weights.size() && bone_transform_vector.size() > 0) { + Vector3 *vertices_ptrw = vertices.ptrw(); + + // Apply bone transforms to regular surface. + unsigned int bone_weight_length = surfaces[i].flags & Mesh::ARRAY_FLAG_USE_8_BONE_WEIGHTS ? 8 : 4; + + const int *bo = bones.ptr(); + const float *we = weights.ptr(); + + for (unsigned int j = 0; j < vertex_count; j++) { + VERTEX_SKIN_FUNC(bone_weight_length, j, vertices_ptr, vertices_ptrw, bone_transform_vector, bo, we) + } + + vertices_ptr = vertices.ptr(); + } + + float normal_merge_threshold = Math::cos(Math::deg_to_rad(p_normal_merge_angle)); + float normal_pre_split_threshold = Math::cos(Math::deg_to_rad(MIN(180.0f, p_normal_split_angle * 2.0f))); + float normal_split_threshold = Math::cos(Math::deg_to_rad(p_normal_split_angle)); + const Vector3 *normals_ptr = normals.ptr(); + + HashMap>> unique_vertices; + + LocalVector vertex_remap; + LocalVector vertex_inverse_remap; + LocalVector merged_vertices; + LocalVector merged_normals; + LocalVector merged_normals_counts; + const Vector2 *uvs_ptr = uvs.ptr(); + const Vector2 *uv2s_ptr = uv2s.ptr(); + + for (unsigned int j = 0; j < vertex_count; j++) { + const Vector3 &v = vertices_ptr[j]; + const Vector3 &n = normals_ptr[j]; + + HashMap>>::Iterator E = unique_vertices.find(v); + + if (E) { + const LocalVector> &close_verts = E->value; + + bool found = false; + for (const Pair &idx : close_verts) { + bool is_uvs_close = (!uvs_ptr || uvs_ptr[j].distance_squared_to(uvs_ptr[idx.second]) < CMP_EPSILON2); + bool is_uv2s_close = (!uv2s_ptr || uv2s_ptr[j].distance_squared_to(uv2s_ptr[idx.second]) < CMP_EPSILON2); + ERR_FAIL_INDEX(idx.second, normals.size()); + bool is_normals_close = normals[idx.second].dot(n) > normal_merge_threshold; + if (is_uvs_close && is_uv2s_close && is_normals_close) { + vertex_remap.push_back(idx.first); + merged_normals[idx.first] += normals[idx.second]; + merged_normals_counts[idx.first]++; + found = true; + break; + } + } + + if (!found) { + int vcount = merged_vertices.size(); + unique_vertices[v].push_back(Pair(vcount, j)); + vertex_inverse_remap.push_back(j); + merged_vertices.push_back(v); + vertex_remap.push_back(vcount); + merged_normals.push_back(normals_ptr[j]); + merged_normals_counts.push_back(1); + } + } else { + int vcount = merged_vertices.size(); + unique_vertices[v] = LocalVector>(); + unique_vertices[v].push_back(Pair(vcount, j)); + vertex_inverse_remap.push_back(j); + merged_vertices.push_back(v); + vertex_remap.push_back(vcount); + merged_normals.push_back(normals_ptr[j]); + merged_normals_counts.push_back(1); + } + } + + LocalVector merged_indices; + merged_indices.resize(index_count); + for (unsigned int j = 0; j < index_count; j++) { + merged_indices[j] = vertex_remap[indices[j]]; + } + + unsigned int merged_vertex_count = merged_vertices.size(); + const Vector3 *merged_vertices_ptr = merged_vertices.ptr(); + const int32_t *merged_indices_ptr = merged_indices.ptr(); + + { + const int *counts_ptr = merged_normals_counts.ptr(); + Vector3 *merged_normals_ptrw = merged_normals.ptr(); + for (unsigned int j = 0; j < merged_vertex_count; j++) { + merged_normals_ptrw[j] /= counts_ptr[j]; + } + } + + const float normal_weights[3] = { + // Give some weight to normal preservation, may be worth exposing as an import setting + 2.0f, 2.0f, 2.0f + }; + + Vector merged_vertices_f32 = vector3_to_float32_array(merged_vertices_ptr, merged_vertex_count); + float scale = SurfaceTool::simplify_scale_func(merged_vertices_f32.ptr(), merged_vertex_count, sizeof(float) * 3); + + unsigned int index_target = 12; // Start with the smallest target, 4 triangles + unsigned int last_index_count = 0; + + int split_vertex_count = vertex_count; + LocalVector split_vertex_normals; + LocalVector split_vertex_indices; + split_vertex_normals.reserve(index_count / 3); + split_vertex_indices.reserve(index_count / 3); + + RandomPCG pcg; + pcg.seed(123456789); // Keep seed constant across imports + + Ref raycaster = StaticRaycaster::create(); + if (raycaster.is_valid()) { + raycaster->add_mesh(vertices, indices, 0); + raycaster->commit(); + } + + const float max_mesh_error = FLT_MAX; // We don't want to limit by error, just by index target + float mesh_error = 0.0f; + + while (index_target < index_count) { + PackedInt32Array new_indices; + new_indices.resize(index_count); + + Vector merged_normals_f32 = vector3_to_float32_array(merged_normals.ptr(), merged_normals.size()); + const int simplify_options = SurfaceTool::SIMPLIFY_LOCK_BORDER; + + size_t new_index_count = SurfaceTool::simplify_with_attrib_func( + (unsigned int *)new_indices.ptrw(), + (const uint32_t *)merged_indices_ptr, index_count, + merged_vertices_f32.ptr(), merged_vertex_count, + sizeof(float) * 3, // Vertex stride + merged_normals_f32.ptr(), + sizeof(float) * 3, // Attribute stride + normal_weights, 3, + index_target, + max_mesh_error, + simplify_options, + &mesh_error); + + if (new_index_count < last_index_count * 1.5f) { + index_target = index_target * 1.5f; + continue; + } + + if (new_index_count == 0 || (new_index_count >= (index_count * 0.75f))) { + break; + } + if (new_index_count > 5000000) { + // This limit theoretically shouldn't be needed, but it's here + // as an ad-hoc fix to prevent a crash with complex meshes. + // The crash still happens with limit of 6000000, but 5000000 works. + // In the future, identify what's causing that crash and fix it. + WARN_PRINT("Mesh LOD generation failed for mesh " + get_name() + " surface " + itos(i) + ", mesh is too complex. Some automatic LODs were not generated."); + break; + } + + new_indices.resize(new_index_count); + + LocalVector> vertex_corners; + vertex_corners.resize(vertex_count); + { + int *ptrw = new_indices.ptrw(); + for (unsigned int j = 0; j < new_index_count; j++) { + const int &remapped = vertex_inverse_remap[ptrw[j]]; + vertex_corners[remapped].push_back(j); + ptrw[j] = remapped; + } + } + + if (raycaster.is_valid()) { + float error_factor = 1.0f / (scale * MAX(mesh_error, 0.15)); + const float ray_bias = 0.05; + float ray_length = ray_bias + mesh_error * scale * 3.0f; + + Vector rays; + LocalVector ray_uvs; + + int32_t *new_indices_ptr = new_indices.ptrw(); + + int current_ray_count = 0; + for (unsigned int j = 0; j < new_index_count; j += 3) { + const Vector3 &v0 = vertices_ptr[new_indices_ptr[j + 0]]; + const Vector3 &v1 = vertices_ptr[new_indices_ptr[j + 1]]; + const Vector3 &v2 = vertices_ptr[new_indices_ptr[j + 2]]; + Vector3 face_normal = vec3_cross(v0 - v2, v0 - v1); + float face_area = face_normal.length(); // Actually twice the face area, since it's the same error_factor on all faces, we don't care + if (!Math::is_finite(face_area) || face_area == 0) { + WARN_PRINT_ONCE("Ignoring face with non-finite normal in LOD generation."); + continue; + } + + Vector3 dir = face_normal / face_area; + int ray_count = CLAMP(5.0 * face_area * error_factor, 16, 64); + + rays.resize(current_ray_count + ray_count); + StaticRaycaster::Ray *rays_ptr = rays.ptrw(); + + ray_uvs.resize(current_ray_count + ray_count); + Vector2 *ray_uvs_ptr = ray_uvs.ptr(); + + for (int k = 0; k < ray_count; k++) { + float u = pcg.randf(); + float v = pcg.randf(); + + if (u + v >= 1.0f) { + u = 1.0f - u; + v = 1.0f - v; + } + + u = 0.9f * u + 0.05f / 3.0f; // Give barycentric coordinates some padding, we don't want to sample right on the edge + v = 0.9f * v + 0.05f / 3.0f; // v = (v - one_third) * 0.95f + one_third; + float w = 1.0f - u - v; + + Vector3 org = v0 * w + v1 * u + v2 * v; + org -= dir * ray_bias; + rays_ptr[current_ray_count + k] = StaticRaycaster::Ray(org, dir, 0.0f, ray_length); + rays_ptr[current_ray_count + k].id = j / 3; + ray_uvs_ptr[current_ray_count + k] = Vector2(u, v); + } + + current_ray_count += ray_count; + } + + raycaster->intersect(rays); + + LocalVector ray_normals; + LocalVector ray_normal_weights; + + ray_normals.resize(new_index_count); + ray_normal_weights.resize(new_index_count); + + for (unsigned int j = 0; j < new_index_count; j++) { + ray_normal_weights[j] = 0.0f; + } + + const StaticRaycaster::Ray *rp = rays.ptr(); + for (int j = 0; j < rays.size(); j++) { + if (rp[j].geomID != 0) { // Ray missed + continue; + } + + if (rp[j].normal.normalized().dot(rp[j].dir) > 0.0f) { // Hit a back face. + continue; + } + + const float &u = rp[j].u; + const float &v = rp[j].v; + const float w = 1.0f - u - v; + + const unsigned int &hit_tri_id = rp[j].primID; + const unsigned int &orig_tri_id = rp[j].id; + + const Vector3 &n0 = normals_ptr[indices_ptr[hit_tri_id * 3 + 0]]; + const Vector3 &n1 = normals_ptr[indices_ptr[hit_tri_id * 3 + 1]]; + const Vector3 &n2 = normals_ptr[indices_ptr[hit_tri_id * 3 + 2]]; + Vector3 normal = n0 * w + n1 * u + n2 * v; + + Vector2 orig_uv = ray_uvs[j]; + const real_t orig_bary[3] = { 1.0f - orig_uv.x - orig_uv.y, orig_uv.x, orig_uv.y }; + for (int k = 0; k < 3; k++) { + int idx = orig_tri_id * 3 + k; + real_t weight = orig_bary[k]; + ray_normals[idx] += normal * weight; + ray_normal_weights[idx] += weight; + } + } + + for (unsigned int j = 0; j < new_index_count; j++) { + if (ray_normal_weights[j] < 1.0f) { // Not enough data, the new normal would be just a bad guess + ray_normals[j] = Vector3(); + } else { + ray_normals[j] /= ray_normal_weights[j]; + } + } + + LocalVector> normal_group_indices; + LocalVector normal_group_averages; + normal_group_indices.reserve(24); + normal_group_averages.reserve(24); + + for (unsigned int j = 0; j < vertex_count; j++) { + const LocalVector &corners = vertex_corners[j]; + const Vector3 &vertex_normal = normals_ptr[j]; + + for (const int &corner_idx : corners) { + const Vector3 &ray_normal = ray_normals[corner_idx]; + + if (ray_normal.length_squared() < CMP_EPSILON2) { + continue; + } + + bool found = false; + for (unsigned int l = 0; l < normal_group_indices.size(); l++) { + LocalVector &group_indices = normal_group_indices[l]; + Vector3 n = normal_group_averages[l] / group_indices.size(); + if (n.dot(ray_normal) > normal_pre_split_threshold) { + found = true; + group_indices.push_back(corner_idx); + normal_group_averages[l] += ray_normal; + break; + } + } + + if (!found) { + normal_group_indices.push_back({ corner_idx }); + normal_group_averages.push_back(ray_normal); + } + } + + for (unsigned int k = 0; k < normal_group_indices.size(); k++) { + LocalVector &group_indices = normal_group_indices[k]; + Vector3 n = normal_group_averages[k] / group_indices.size(); + + if (vertex_normal.dot(n) < normal_split_threshold) { + split_vertex_indices.push_back(j); + split_vertex_normals.push_back(n); + int new_idx = split_vertex_count++; + for (const int &index : group_indices) { + new_indices_ptr[index] = new_idx; + } + } + } + + normal_group_indices.clear(); + normal_group_averages.clear(); + } + } + + Surface::LOD lod; + lod.distance = MAX(mesh_error * scale, CMP_EPSILON2); + lod.indices = new_indices; + surfaces.write[i].lods.push_back(lod); + index_target = MAX(new_index_count, index_target) * 2; + last_index_count = new_index_count; + + if (mesh_error == 0.0f) { + break; + } + } + + surfaces.write[i].split_normals(split_vertex_indices, split_vertex_normals); + surfaces.write[i].lods.sort_custom(); + + for (int j = 0; j < surfaces.write[i].lods.size(); j++) { + Surface::LOD &lod = surfaces.write[i].lods.write[j]; + unsigned int *lod_indices_ptr = (unsigned int *)lod.indices.ptrw(); + SurfaceTool::optimize_vertex_cache_func(lod_indices_ptr, lod_indices_ptr, lod.indices.size(), split_vertex_count); + } + } +} + +bool ImporterMesh::has_mesh() const { + return mesh.is_valid(); +} + +Ref ImporterMesh::get_mesh(const Ref &p_base) { + ERR_FAIL_COND_V(surfaces.is_empty(), Ref()); + + if (mesh.is_null()) { + if (p_base.is_valid()) { + mesh = p_base; + } + if (mesh.is_null()) { + mesh.instantiate(); + } + mesh->set_name(get_name()); + if (has_meta("import_id")) { + mesh->set_meta("import_id", get_meta("import_id")); + } + for (int i = 0; i < blend_shapes.size(); i++) { + mesh->add_blend_shape(blend_shapes[i]); + } + mesh->set_blend_shape_mode(blend_shape_mode); + for (int i = 0; i < surfaces.size(); i++) { + Array bs_data; + if (surfaces[i].blend_shape_data.size()) { + for (int j = 0; j < surfaces[i].blend_shape_data.size(); j++) { + bs_data.push_back(surfaces[i].blend_shape_data[j].arrays); + } + } + Dictionary lods; + if (surfaces[i].lods.size()) { + for (int j = 0; j < surfaces[i].lods.size(); j++) { + lods[surfaces[i].lods[j].distance] = surfaces[i].lods[j].indices; + } + } + + mesh->add_surface_from_arrays(surfaces[i].primitive, surfaces[i].arrays, bs_data, lods, surfaces[i].flags); + if (surfaces[i].material.is_valid()) { + mesh->surface_set_material(mesh->get_surface_count() - 1, surfaces[i].material); + } + if (!surfaces[i].name.is_empty()) { + mesh->surface_set_name(mesh->get_surface_count() - 1, surfaces[i].name); + } + } + + mesh->set_lightmap_size_hint(lightmap_size_hint); + + if (shadow_mesh.is_valid()) { + Ref shadow = shadow_mesh->get_mesh(); + mesh->set_shadow_mesh(shadow); + } + } + + return mesh; +} + +void ImporterMesh::clear() { + surfaces.clear(); + blend_shapes.clear(); + mesh.unref(); +} + +void ImporterMesh::create_shadow_mesh() { + if (shadow_mesh.is_valid()) { + shadow_mesh.unref(); + } + + //no shadow mesh for blendshapes + if (blend_shapes.size() > 0) { + return; + } + //no shadow mesh for skeletons + for (int i = 0; i < surfaces.size(); i++) { + if (surfaces[i].arrays[RS::ARRAY_BONES].get_type() != Variant::NIL) { + return; + } + if (surfaces[i].arrays[RS::ARRAY_WEIGHTS].get_type() != Variant::NIL) { + return; + } + } + + shadow_mesh.instantiate(); + + for (int i = 0; i < surfaces.size(); i++) { + LocalVector vertex_remap; + Vector new_vertices; + Vector vertices = surfaces[i].arrays[RS::ARRAY_VERTEX]; + int vertex_count = vertices.size(); + { + HashMap unique_vertices; + const Vector3 *vptr = vertices.ptr(); + for (int j = 0; j < vertex_count; j++) { + const Vector3 &v = vptr[j]; + + HashMap::Iterator E = unique_vertices.find(v); + + if (E) { + vertex_remap.push_back(E->value); + } else { + int vcount = unique_vertices.size(); + unique_vertices[v] = vcount; + vertex_remap.push_back(vcount); + new_vertices.push_back(v); + } + } + } + + Array new_surface; + new_surface.resize(RS::ARRAY_MAX); + Dictionary lods; + + // print_line("original vertex count: " + itos(vertices.size()) + " new vertex count: " + itos(new_vertices.size())); + + new_surface[RS::ARRAY_VERTEX] = new_vertices; + + Vector indices = surfaces[i].arrays[RS::ARRAY_INDEX]; + if (indices.size()) { + int index_count = indices.size(); + const int *index_rptr = indices.ptr(); + Vector new_indices; + new_indices.resize(indices.size()); + int *index_wptr = new_indices.ptrw(); + + for (int j = 0; j < index_count; j++) { + int index = index_rptr[j]; + ERR_FAIL_INDEX(index, vertex_count); + index_wptr[j] = vertex_remap[index]; + } + + new_surface[RS::ARRAY_INDEX] = new_indices; + + // Make sure the same LODs as the full version are used. + // This makes it more coherent between rendered model and its shadows. + for (int j = 0; j < surfaces[i].lods.size(); j++) { + indices = surfaces[i].lods[j].indices; + + index_count = indices.size(); + index_rptr = indices.ptr(); + new_indices.resize(indices.size()); + index_wptr = new_indices.ptrw(); + + for (int k = 0; k < index_count; k++) { + int index = index_rptr[k]; + ERR_FAIL_INDEX(index, vertex_count); + index_wptr[k] = vertex_remap[index]; + } + + lods[surfaces[i].lods[j].distance] = new_indices; + } + } + + shadow_mesh->add_surface(surfaces[i].primitive, new_surface, Array(), lods, Ref(), surfaces[i].name, surfaces[i].flags); + } +} + +Ref ImporterMesh::get_shadow_mesh() const { + return shadow_mesh; +} + +void ImporterMesh::_set_data(const Dictionary &p_data) { + clear(); + if (p_data.has("blend_shape_names")) { + blend_shapes = p_data["blend_shape_names"]; + } + if (p_data.has("surfaces")) { + Array surface_arr = p_data["surfaces"]; + for (int i = 0; i < surface_arr.size(); i++) { + Dictionary s = surface_arr[i]; + ERR_CONTINUE(!s.has("primitive")); + ERR_CONTINUE(!s.has("arrays")); + Mesh::PrimitiveType prim = Mesh::PrimitiveType(int(s["primitive"])); + ERR_CONTINUE(prim >= Mesh::PRIMITIVE_MAX); + Array arr = s["arrays"]; + Dictionary lods; + String surf_name; + if (s.has("name")) { + surf_name = s["name"]; + } + if (s.has("lods")) { + lods = s["lods"]; + } + Array b_shapes; + if (s.has("b_shapes")) { + b_shapes = s["b_shapes"]; + } + Ref material; + if (s.has("material")) { + material = s["material"]; + } + uint64_t flags = 0; + if (s.has("flags")) { + flags = s["flags"]; + } + add_surface(prim, arr, b_shapes, lods, material, surf_name, flags); + } + } +} +Dictionary ImporterMesh::_get_data() const { + Dictionary data; + if (blend_shapes.size()) { + data["blend_shape_names"] = blend_shapes; + } + Array surface_arr; + for (int i = 0; i < surfaces.size(); i++) { + Dictionary d; + d["primitive"] = surfaces[i].primitive; + d["arrays"] = surfaces[i].arrays; + if (surfaces[i].blend_shape_data.size()) { + Array bs_data; + for (int j = 0; j < surfaces[i].blend_shape_data.size(); j++) { + bs_data.push_back(surfaces[i].blend_shape_data[j].arrays); + } + d["blend_shapes"] = bs_data; + } + if (surfaces[i].lods.size()) { + Dictionary lods; + for (int j = 0; j < surfaces[i].lods.size(); j++) { + lods[surfaces[i].lods[j].distance] = surfaces[i].lods[j].indices; + } + d["lods"] = lods; + } + + if (surfaces[i].material.is_valid()) { + d["material"] = surfaces[i].material; + } + + if (!surfaces[i].name.is_empty()) { + d["name"] = surfaces[i].name; + } + + d["flags"] = surfaces[i].flags; + + surface_arr.push_back(d); + } + data["surfaces"] = surface_arr; + return data; +} + +Vector ImporterMesh::get_faces() const { + Vector faces; + for (int i = 0; i < surfaces.size(); i++) { + if (surfaces[i].primitive == Mesh::PRIMITIVE_TRIANGLES) { + Vector vertices = surfaces[i].arrays[Mesh::ARRAY_VERTEX]; + Vector indices = surfaces[i].arrays[Mesh::ARRAY_INDEX]; + if (indices.size()) { + for (int j = 0; j < indices.size(); j += 3) { + Face3 f; + f.vertex[0] = vertices[indices[j + 0]]; + f.vertex[1] = vertices[indices[j + 1]]; + f.vertex[2] = vertices[indices[j + 2]]; + faces.push_back(f); + } + } else { + for (int j = 0; j < vertices.size(); j += 3) { + Face3 f; + f.vertex[0] = vertices[j + 0]; + f.vertex[1] = vertices[j + 1]; + f.vertex[2] = vertices[j + 2]; + faces.push_back(f); + } + } + } + } + + return faces; +} + +Vector> ImporterMesh::convex_decompose(const Ref &p_settings) const { + ERR_FAIL_NULL_V(Mesh::convex_decomposition_function, Vector>()); + + const Vector faces = get_faces(); + int face_count = faces.size(); + + Vector vertices; + uint32_t vertex_count = 0; + vertices.resize(face_count * 3); + Vector indices; + indices.resize(face_count * 3); + { + HashMap vertex_map; + Vector3 *vertex_w = vertices.ptrw(); + uint32_t *index_w = indices.ptrw(); + for (int i = 0; i < face_count; i++) { + for (int j = 0; j < 3; j++) { + const Vector3 &vertex = faces[i].vertex[j]; + HashMap::Iterator found_vertex = vertex_map.find(vertex); + uint32_t index; + if (found_vertex) { + index = found_vertex->value; + } else { + index = vertex_count++; + vertex_map[vertex] = index; + vertex_w[index] = vertex; + } + index_w[i * 3 + j] = index; + } + } + } + vertices.resize(vertex_count); + + Vector> decomposed = Mesh::convex_decomposition_function((real_t *)vertices.ptr(), vertex_count, indices.ptr(), face_count, p_settings, nullptr); + + Vector> ret; + + for (int i = 0; i < decomposed.size(); i++) { + Ref shape; + shape.instantiate(); + shape->set_points(decomposed[i]); + ret.push_back(shape); + } + + return ret; +} + +Ref ImporterMesh::create_convex_shape(bool p_clean, bool p_simplify) const { + if (p_simplify) { + Ref settings; + settings.instantiate(); + settings->set_max_convex_hulls(1); + Vector> decomposed = convex_decompose(settings); + if (decomposed.size() == 1) { + return decomposed[0]; + } else { + ERR_PRINT("Convex shape simplification failed, falling back to simpler process."); + } + } + + Vector vertices; + for (int i = 0; i < get_surface_count(); i++) { + Array a = get_surface_arrays(i); + ERR_FAIL_COND_V(a.is_empty(), Ref()); + Vector v = a[Mesh::ARRAY_VERTEX]; + vertices.append_array(v); + } + + Ref shape = memnew(ConvexPolygonShape3D); + + if (p_clean) { + Geometry3D::MeshData md; + Error err = ConvexHullComputer::convex_hull(vertices, md); + if (err == OK) { + shape->set_points(md.vertices); + return shape; + } else { + ERR_PRINT("Convex shape cleaning failed, falling back to simpler process."); + } + } + + shape->set_points(vertices); + return shape; +} + +Ref ImporterMesh::create_trimesh_shape() const { + Vector faces = get_faces(); + if (faces.size() == 0) { + return Ref(); + } + + Vector face_points; + face_points.resize(faces.size() * 3); + + for (int i = 0; i < face_points.size(); i += 3) { + Face3 f = faces.get(i / 3); + face_points.set(i, f.vertex[0]); + face_points.set(i + 1, f.vertex[1]); + face_points.set(i + 2, f.vertex[2]); + } + + Ref shape = memnew(ConcavePolygonShape3D); + shape->set_faces(face_points); + return shape; +} + +Ref ImporterMesh::create_navigation_mesh() { + Vector faces = get_faces(); + if (faces.size() == 0) { + return Ref(); + } + + HashMap unique_vertices; + LocalVector face_indices; + + for (int i = 0; i < faces.size(); i++) { + for (int j = 0; j < 3; j++) { + Vector3 v = faces[i].vertex[j]; + int idx; + if (unique_vertices.has(v)) { + idx = unique_vertices[v]; + } else { + idx = unique_vertices.size(); + unique_vertices[v] = idx; + } + face_indices.push_back(idx); + } + } + + Vector vertices; + vertices.resize(unique_vertices.size()); + for (const KeyValue &E : unique_vertices) { + vertices.write[E.value] = E.key; + } + + Ref nm; + nm.instantiate(); + nm->set_vertices(vertices); + + Vector v3; + v3.resize(3); + for (uint32_t i = 0; i < face_indices.size(); i += 3) { + v3.write[0] = face_indices[i + 0]; + v3.write[1] = face_indices[i + 1]; + v3.write[2] = face_indices[i + 2]; + nm->add_polygon(v3); + } + + return nm; +} + +extern bool (*array_mesh_lightmap_unwrap_callback)(float p_texel_size, const float *p_vertices, const float *p_normals, int p_vertex_count, const int *p_indices, int p_index_count, const uint8_t *p_cache_data, bool *r_use_cache, uint8_t **r_mesh_cache, int *r_mesh_cache_size, float **r_uv, int **r_vertex, int *r_vertex_count, int **r_index, int *r_index_count, int *r_size_hint_x, int *r_size_hint_y); + +struct EditorSceneFormatImporterMeshLightmapSurface { + Ref material; + LocalVector vertices; + Mesh::PrimitiveType primitive = Mesh::PrimitiveType::PRIMITIVE_MAX; + uint64_t format = 0; + String name; +}; + +static const uint32_t custom_shift[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM1_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM2_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM3_SHIFT }; + +Error ImporterMesh::lightmap_unwrap_cached(const Transform3D &p_base_transform, float p_texel_size, const Vector &p_src_cache, Vector &r_dst_cache) { + ERR_FAIL_NULL_V(array_mesh_lightmap_unwrap_callback, ERR_UNCONFIGURED); + ERR_FAIL_COND_V_MSG(blend_shapes.size() != 0, ERR_UNAVAILABLE, "Can't unwrap mesh with blend shapes."); + + LocalVector vertices; + LocalVector normals; + LocalVector indices; + LocalVector uv; + LocalVector> uv_indices; + + Vector lightmap_surfaces; + + // Keep only the scale + Basis basis = p_base_transform.get_basis(); + Vector3 scale = Vector3(basis.get_column(0).length(), basis.get_column(1).length(), basis.get_column(2).length()); + + Transform3D transform; + transform.scale(scale); + + Basis normal_basis = transform.basis.inverse().transposed(); + + for (int i = 0; i < get_surface_count(); i++) { + EditorSceneFormatImporterMeshLightmapSurface s; + s.primitive = get_surface_primitive_type(i); + + ERR_FAIL_COND_V_MSG(s.primitive != Mesh::PRIMITIVE_TRIANGLES, ERR_UNAVAILABLE, "Only triangles are supported for lightmap unwrap."); + Array arrays = get_surface_arrays(i); + s.material = get_surface_material(i); + s.name = get_surface_name(i); + + SurfaceTool::create_vertex_array_from_triangle_arrays(arrays, s.vertices, &s.format); + + PackedVector3Array rvertices = arrays[Mesh::ARRAY_VERTEX]; + int vc = rvertices.size(); + + PackedVector3Array rnormals = arrays[Mesh::ARRAY_NORMAL]; + + if (!rnormals.size()) { + continue; + } + + int vertex_ofs = vertices.size() / 3; + + vertices.resize((vertex_ofs + vc) * 3); + normals.resize((vertex_ofs + vc) * 3); + uv_indices.resize(vertex_ofs + vc); + + for (int j = 0; j < vc; j++) { + Vector3 v = transform.xform(rvertices[j]); + Vector3 n = normal_basis.xform(rnormals[j]).normalized(); + + vertices[(j + vertex_ofs) * 3 + 0] = v.x; + vertices[(j + vertex_ofs) * 3 + 1] = v.y; + vertices[(j + vertex_ofs) * 3 + 2] = v.z; + normals[(j + vertex_ofs) * 3 + 0] = n.x; + normals[(j + vertex_ofs) * 3 + 1] = n.y; + normals[(j + vertex_ofs) * 3 + 2] = n.z; + uv_indices[j + vertex_ofs] = Pair(i, j); + } + + PackedInt32Array rindices = arrays[Mesh::ARRAY_INDEX]; + int ic = rindices.size(); + + float eps = 1.19209290e-7F; // Taken from xatlas.h + if (ic == 0) { + for (int j = 0; j < vc / 3; j++) { + Vector3 p0 = transform.xform(rvertices[j * 3 + 0]); + Vector3 p1 = transform.xform(rvertices[j * 3 + 1]); + Vector3 p2 = transform.xform(rvertices[j * 3 + 2]); + + if ((p0 - p1).length_squared() < eps || (p1 - p2).length_squared() < eps || (p2 - p0).length_squared() < eps) { + continue; + } + + indices.push_back(vertex_ofs + j * 3 + 0); + indices.push_back(vertex_ofs + j * 3 + 1); + indices.push_back(vertex_ofs + j * 3 + 2); + } + + } else { + for (int j = 0; j < ic / 3; j++) { + ERR_FAIL_INDEX_V(rindices[j * 3 + 0], rvertices.size(), ERR_INVALID_DATA); + ERR_FAIL_INDEX_V(rindices[j * 3 + 1], rvertices.size(), ERR_INVALID_DATA); + ERR_FAIL_INDEX_V(rindices[j * 3 + 2], rvertices.size(), ERR_INVALID_DATA); + Vector3 p0 = transform.xform(rvertices[rindices[j * 3 + 0]]); + Vector3 p1 = transform.xform(rvertices[rindices[j * 3 + 1]]); + Vector3 p2 = transform.xform(rvertices[rindices[j * 3 + 2]]); + + if ((p0 - p1).length_squared() < eps || (p1 - p2).length_squared() < eps || (p2 - p0).length_squared() < eps) { + continue; + } + + indices.push_back(vertex_ofs + rindices[j * 3 + 0]); + indices.push_back(vertex_ofs + rindices[j * 3 + 1]); + indices.push_back(vertex_ofs + rindices[j * 3 + 2]); + } + } + + lightmap_surfaces.push_back(s); + } + + //unwrap + + bool use_cache = true; // Used to request cache generation and to know if cache was used + uint8_t *gen_cache; + int gen_cache_size; + float *gen_uvs; + int *gen_vertices; + int *gen_indices; + int gen_vertex_count; + int gen_index_count; + int size_x; + int size_y; + + bool ok = array_mesh_lightmap_unwrap_callback(p_texel_size, vertices.ptr(), normals.ptr(), vertices.size() / 3, indices.ptr(), indices.size(), p_src_cache.ptr(), &use_cache, &gen_cache, &gen_cache_size, &gen_uvs, &gen_vertices, &gen_vertex_count, &gen_indices, &gen_index_count, &size_x, &size_y); + + if (!ok) { + return ERR_CANT_CREATE; + } + + //create surfacetools for each surface.. + LocalVector> surfaces_tools; + + for (int i = 0; i < lightmap_surfaces.size(); i++) { + Ref st; + st.instantiate(); + st->set_skin_weight_count((lightmap_surfaces[i].format & Mesh::ARRAY_FLAG_USE_8_BONE_WEIGHTS) ? SurfaceTool::SKIN_8_WEIGHTS : SurfaceTool::SKIN_4_WEIGHTS); + st->begin(Mesh::PRIMITIVE_TRIANGLES); + st->set_material(lightmap_surfaces[i].material); + st->set_meta("name", lightmap_surfaces[i].name); + + for (int custom_i = 0; custom_i < RS::ARRAY_CUSTOM_COUNT; custom_i++) { + st->set_custom_format(custom_i, (SurfaceTool::CustomFormat)((lightmap_surfaces[i].format >> custom_shift[custom_i]) & RS::ARRAY_FORMAT_CUSTOM_MASK)); + } + surfaces_tools.push_back(st); //stay there + } + + //remove surfaces + clear(); + + print_verbose("Mesh: Gen indices: " + itos(gen_index_count)); + + //go through all indices + for (int i = 0; i < gen_index_count; i += 3) { + ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 0]], (int)uv_indices.size(), ERR_BUG); + ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 1]], (int)uv_indices.size(), ERR_BUG); + ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 2]], (int)uv_indices.size(), ERR_BUG); + + ERR_FAIL_COND_V(uv_indices[gen_vertices[gen_indices[i + 0]]].first != uv_indices[gen_vertices[gen_indices[i + 1]]].first || uv_indices[gen_vertices[gen_indices[i + 0]]].first != uv_indices[gen_vertices[gen_indices[i + 2]]].first, ERR_BUG); + + int surface = uv_indices[gen_vertices[gen_indices[i + 0]]].first; + + for (int j = 0; j < 3; j++) { + SurfaceTool::Vertex v = lightmap_surfaces[surface].vertices[uv_indices[gen_vertices[gen_indices[i + j]]].second]; + + if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_COLOR) { + surfaces_tools[surface]->set_color(v.color); + } + if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_TEX_UV) { + surfaces_tools[surface]->set_uv(v.uv); + } + if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_NORMAL) { + surfaces_tools[surface]->set_normal(v.normal); + } + if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_TANGENT) { + Plane t; + t.normal = v.tangent; + t.d = v.binormal.dot(v.normal.cross(v.tangent)) < 0 ? -1 : 1; + surfaces_tools[surface]->set_tangent(t); + } + if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_BONES) { + surfaces_tools[surface]->set_bones(v.bones); + } + if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_WEIGHTS) { + surfaces_tools[surface]->set_weights(v.weights); + } + for (int custom_i = 0; custom_i < RS::ARRAY_CUSTOM_COUNT; custom_i++) { + if ((lightmap_surfaces[surface].format >> custom_shift[custom_i]) & RS::ARRAY_FORMAT_CUSTOM_MASK) { + surfaces_tools[surface]->set_custom(custom_i, v.custom[custom_i]); + } + } + + Vector2 uv2(gen_uvs[gen_indices[i + j] * 2 + 0], gen_uvs[gen_indices[i + j] * 2 + 1]); + surfaces_tools[surface]->set_uv2(uv2); + + surfaces_tools[surface]->add_vertex(v.vertex); + } + } + + //generate surfaces + for (int i = 0; i < lightmap_surfaces.size(); i++) { + Ref &tool = surfaces_tools[i]; + tool->index(); + Array arrays = tool->commit_to_arrays(); + + uint64_t format = lightmap_surfaces[i].format; + if (tool->get_skin_weight_count() == SurfaceTool::SKIN_8_WEIGHTS) { + format |= RS::ARRAY_FLAG_USE_8_BONE_WEIGHTS; + } else { + format &= ~RS::ARRAY_FLAG_USE_8_BONE_WEIGHTS; + } + + add_surface(tool->get_primitive_type(), arrays, Array(), Dictionary(), tool->get_material(), tool->get_meta("name"), format); + } + + set_lightmap_size_hint(Size2(size_x, size_y)); + + if (gen_cache_size > 0) { + r_dst_cache.resize(gen_cache_size); + memcpy(r_dst_cache.ptrw(), gen_cache, gen_cache_size); + memfree(gen_cache); + } + + if (!use_cache) { + // Cache was not used, free the buffers + memfree(gen_vertices); + memfree(gen_indices); + memfree(gen_uvs); + } + + return OK; +} + +void ImporterMesh::set_lightmap_size_hint(const Size2i &p_size) { + lightmap_size_hint = p_size; +} + +Size2i ImporterMesh::get_lightmap_size_hint() const { + return lightmap_size_hint; +} + +void ImporterMesh::_bind_methods() { + ClassDB::bind_method(D_METHOD("add_blend_shape", "name"), &ImporterMesh::add_blend_shape); + ClassDB::bind_method(D_METHOD("get_blend_shape_count"), &ImporterMesh::get_blend_shape_count); + ClassDB::bind_method(D_METHOD("get_blend_shape_name", "blend_shape_idx"), &ImporterMesh::get_blend_shape_name); + + ClassDB::bind_method(D_METHOD("set_blend_shape_mode", "mode"), &ImporterMesh::set_blend_shape_mode); + ClassDB::bind_method(D_METHOD("get_blend_shape_mode"), &ImporterMesh::get_blend_shape_mode); + + ClassDB::bind_method(D_METHOD("add_surface", "primitive", "arrays", "blend_shapes", "lods", "material", "name", "flags"), &ImporterMesh::add_surface, DEFVAL(TypedArray()), DEFVAL(Dictionary()), DEFVAL(Ref()), DEFVAL(String()), DEFVAL(0)); + + ClassDB::bind_method(D_METHOD("get_surface_count"), &ImporterMesh::get_surface_count); + ClassDB::bind_method(D_METHOD("get_surface_primitive_type", "surface_idx"), &ImporterMesh::get_surface_primitive_type); + ClassDB::bind_method(D_METHOD("get_surface_name", "surface_idx"), &ImporterMesh::get_surface_name); + ClassDB::bind_method(D_METHOD("get_surface_arrays", "surface_idx"), &ImporterMesh::get_surface_arrays); + ClassDB::bind_method(D_METHOD("get_surface_blend_shape_arrays", "surface_idx", "blend_shape_idx"), &ImporterMesh::get_surface_blend_shape_arrays); + ClassDB::bind_method(D_METHOD("get_surface_lod_count", "surface_idx"), &ImporterMesh::get_surface_lod_count); + ClassDB::bind_method(D_METHOD("get_surface_lod_size", "surface_idx", "lod_idx"), &ImporterMesh::get_surface_lod_size); + ClassDB::bind_method(D_METHOD("get_surface_lod_indices", "surface_idx", "lod_idx"), &ImporterMesh::get_surface_lod_indices); + ClassDB::bind_method(D_METHOD("get_surface_material", "surface_idx"), &ImporterMesh::get_surface_material); + ClassDB::bind_method(D_METHOD("get_surface_format", "surface_idx"), &ImporterMesh::get_surface_format); + + ClassDB::bind_method(D_METHOD("set_surface_name", "surface_idx", "name"), &ImporterMesh::set_surface_name); + ClassDB::bind_method(D_METHOD("set_surface_material", "surface_idx", "material"), &ImporterMesh::set_surface_material); + + ClassDB::bind_method(D_METHOD("generate_lods", "normal_merge_angle", "normal_split_angle", "bone_transform_array"), &ImporterMesh::generate_lods); + ClassDB::bind_method(D_METHOD("get_mesh", "base_mesh"), &ImporterMesh::get_mesh, DEFVAL(Ref())); + ClassDB::bind_method(D_METHOD("clear"), &ImporterMesh::clear); + + ClassDB::bind_method(D_METHOD("_set_data", "data"), &ImporterMesh::_set_data); + ClassDB::bind_method(D_METHOD("_get_data"), &ImporterMesh::_get_data); + + ClassDB::bind_method(D_METHOD("set_lightmap_size_hint", "size"), &ImporterMesh::set_lightmap_size_hint); + ClassDB::bind_method(D_METHOD("get_lightmap_size_hint"), &ImporterMesh::get_lightmap_size_hint); + + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "_set_data", "_get_data"); +} diff --git a/scene/resources/3d/importer_mesh.h b/scene/resources/3d/importer_mesh.h new file mode 100644 index 0000000000..ff8683449b --- /dev/null +++ b/scene/resources/3d/importer_mesh.h @@ -0,0 +1,135 @@ +/**************************************************************************/ +/* importer_mesh.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 IMPORTER_MESH_H +#define IMPORTER_MESH_H + +#include "core/io/resource.h" +#include "core/templates/local_vector.h" +#include "scene/resources/3d/concave_polygon_shape_3d.h" +#include "scene/resources/3d/convex_polygon_shape_3d.h" +#include "scene/resources/mesh.h" +#include "scene/resources/navigation_mesh.h" + +#include + +// The following classes are used by importers instead of ArrayMesh and MeshInstance3D +// so the data is not registered (hence, quality loss), importing happens faster and +// its easier to modify before saving + +class ImporterMesh : public Resource { + GDCLASS(ImporterMesh, Resource) + + struct Surface { + Mesh::PrimitiveType primitive; + Array arrays; + struct BlendShape { + Array arrays; + }; + Vector blend_shape_data; + struct LOD { + Vector indices; + float distance = 0.0f; + }; + Vector lods; + Ref material; + String name; + uint64_t flags = 0; + + struct LODComparator { + _FORCE_INLINE_ bool operator()(const LOD &l, const LOD &r) const { + return l.distance < r.distance; + } + }; + + void split_normals(const LocalVector &p_indices, const LocalVector &p_normals); + static void _split_normals(Array &r_arrays, const LocalVector &p_indices, const LocalVector &p_normals); + }; + Vector surfaces; + Vector blend_shapes; + Mesh::BlendShapeMode blend_shape_mode = Mesh::BLEND_SHAPE_MODE_NORMALIZED; + + Ref mesh; + + Ref shadow_mesh; + + Size2i lightmap_size_hint; + +protected: + void _set_data(const Dictionary &p_data); + Dictionary _get_data() const; + + static void _bind_methods(); + +public: + void add_blend_shape(const String &p_name); + int get_blend_shape_count() const; + String get_blend_shape_name(int p_blend_shape) const; + + void add_surface(Mesh::PrimitiveType p_primitive, const Array &p_arrays, const TypedArray &p_blend_shapes = Array(), const Dictionary &p_lods = Dictionary(), const Ref &p_material = Ref(), const String &p_name = String(), const uint64_t p_flags = 0); + int get_surface_count() const; + + void set_blend_shape_mode(Mesh::BlendShapeMode p_blend_shape_mode); + Mesh::BlendShapeMode get_blend_shape_mode() const; + + Mesh::PrimitiveType get_surface_primitive_type(int p_surface); + String get_surface_name(int p_surface) const; + void set_surface_name(int p_surface, const String &p_name); + Array get_surface_arrays(int p_surface) const; + Array get_surface_blend_shape_arrays(int p_surface, int p_blend_shape) const; + int get_surface_lod_count(int p_surface) const; + Vector get_surface_lod_indices(int p_surface, int p_lod) const; + float get_surface_lod_size(int p_surface, int p_lod) const; + Ref get_surface_material(int p_surface) const; + uint64_t get_surface_format(int p_surface) const; + + void set_surface_material(int p_surface, const Ref &p_material); + + void generate_lods(float p_normal_merge_angle, float p_normal_split_angle, Array p_skin_pose_transform_array); + + void create_shadow_mesh(); + Ref get_shadow_mesh() const; + + Vector get_faces() const; + Vector> convex_decompose(const Ref &p_settings) const; + Ref create_convex_shape(bool p_clean = true, bool p_simplify = false) const; + Ref create_trimesh_shape() const; + Ref create_navigation_mesh(); + Error lightmap_unwrap_cached(const Transform3D &p_base_transform, float p_texel_size, const Vector &p_src_cache, Vector &r_dst_cache); + + void set_lightmap_size_hint(const Size2i &p_size); + Size2i get_lightmap_size_hint() const; + + bool has_mesh() const; + Ref get_mesh(const Ref &p_base = Ref()); + void clear(); +}; + +#endif // IMPORTER_MESH_H diff --git a/scene/resources/3d/mesh_library.cpp b/scene/resources/3d/mesh_library.cpp new file mode 100644 index 0000000000..c2f721a80f --- /dev/null +++ b/scene/resources/3d/mesh_library.cpp @@ -0,0 +1,356 @@ +/**************************************************************************/ +/* mesh_library.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 "mesh_library.h" + +#include "box_shape_3d.h" + +bool MeshLibrary::_set(const StringName &p_name, const Variant &p_value) { + String prop_name = p_name; + if (prop_name.begins_with("item/")) { + int idx = prop_name.get_slicec('/', 1).to_int(); + String what = prop_name.get_slicec('/', 2); + if (!item_map.has(idx)) { + create_item(idx); + } + + if (what == "name") { + set_item_name(idx, p_value); + } else if (what == "mesh") { + set_item_mesh(idx, p_value); + } else if (what == "mesh_transform") { + set_item_mesh_transform(idx, p_value); + } else if (what == "shape") { + Vector shapes; + ShapeData sd; + sd.shape = p_value; + shapes.push_back(sd); + set_item_shapes(idx, shapes); + } else if (what == "shapes") { + _set_item_shapes(idx, p_value); + } else if (what == "preview") { + set_item_preview(idx, p_value); + } else if (what == "navigation_mesh") { + set_item_navigation_mesh(idx, p_value); + } else if (what == "navigation_mesh_transform") { + set_item_navigation_mesh_transform(idx, p_value); +#ifndef DISABLE_DEPRECATED + } else if (what == "navmesh") { // Renamed in 4.0 beta 9. + set_item_navigation_mesh(idx, p_value); + } else if (what == "navmesh_transform") { // Renamed in 4.0 beta 9. + set_item_navigation_mesh_transform(idx, p_value); +#endif // DISABLE_DEPRECATED + } else if (what == "navigation_layers") { + set_item_navigation_layers(idx, p_value); + } else { + return false; + } + + return true; + } + + return false; +} + +bool MeshLibrary::_get(const StringName &p_name, Variant &r_ret) const { + String prop_name = p_name; + int idx = prop_name.get_slicec('/', 1).to_int(); + ERR_FAIL_COND_V(!item_map.has(idx), false); + String what = prop_name.get_slicec('/', 2); + + if (what == "name") { + r_ret = get_item_name(idx); + } else if (what == "mesh") { + r_ret = get_item_mesh(idx); + } else if (what == "mesh_transform") { + r_ret = get_item_mesh_transform(idx); + } else if (what == "shapes") { + r_ret = _get_item_shapes(idx); + } else if (what == "navigation_mesh") { + r_ret = get_item_navigation_mesh(idx); + } else if (what == "navigation_mesh_transform") { + r_ret = get_item_navigation_mesh_transform(idx); +#ifndef DISABLE_DEPRECATED + } else if (what == "navmesh") { // Renamed in 4.0 beta 9. + r_ret = get_item_navigation_mesh(idx); + } else if (what == "navmesh_transform") { // Renamed in 4.0 beta 9. + r_ret = get_item_navigation_mesh_transform(idx); +#endif // DISABLE_DEPRECATED + } else if (what == "navigation_layers") { + r_ret = get_item_navigation_layers(idx); + } else if (what == "preview") { + r_ret = get_item_preview(idx); + } else { + return false; + } + + return true; +} + +void MeshLibrary::_get_property_list(List *p_list) const { + for (const KeyValue &E : item_map) { + String prop_name = vformat("%s/%d/", PNAME("item"), E.key); + p_list->push_back(PropertyInfo(Variant::STRING, prop_name + PNAME("name"))); + p_list->push_back(PropertyInfo(Variant::OBJECT, prop_name + PNAME("mesh"), PROPERTY_HINT_RESOURCE_TYPE, "Mesh")); + p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, prop_name + PNAME("mesh_transform"), PROPERTY_HINT_NONE, "suffix:m")); + p_list->push_back(PropertyInfo(Variant::ARRAY, prop_name + PNAME("shapes"))); + p_list->push_back(PropertyInfo(Variant::OBJECT, prop_name + PNAME("navigation_mesh"), PROPERTY_HINT_RESOURCE_TYPE, "NavigationMesh")); + p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, prop_name + PNAME("navigation_mesh_transform"), PROPERTY_HINT_NONE, "suffix:m")); + p_list->push_back(PropertyInfo(Variant::INT, prop_name + PNAME("navigation_layers"), PROPERTY_HINT_LAYERS_3D_NAVIGATION)); + p_list->push_back(PropertyInfo(Variant::OBJECT, prop_name + PNAME("preview"), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_DEFAULT)); + } +} + +void MeshLibrary::create_item(int p_item) { + ERR_FAIL_COND(p_item < 0); + ERR_FAIL_COND(item_map.has(p_item)); + item_map[p_item] = Item(); + emit_changed(); + notify_property_list_changed(); +} + +void MeshLibrary::set_item_name(int p_item, const String &p_name) { + ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + item_map[p_item].name = p_name; + emit_changed(); +} + +void MeshLibrary::set_item_mesh(int p_item, const Ref &p_mesh) { + ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + item_map[p_item].mesh = p_mesh; + emit_changed(); +} + +void MeshLibrary::set_item_mesh_transform(int p_item, const Transform3D &p_transform) { + ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + item_map[p_item].mesh_transform = p_transform; + emit_changed(); +} + +void MeshLibrary::set_item_shapes(int p_item, const Vector &p_shapes) { + ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + item_map[p_item].shapes = p_shapes; + emit_changed(); + notify_property_list_changed(); +} + +void MeshLibrary::set_item_navigation_mesh(int p_item, const Ref &p_navigation_mesh) { + ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + item_map[p_item].navigation_mesh = p_navigation_mesh; + emit_changed(); +} + +void MeshLibrary::set_item_navigation_mesh_transform(int p_item, const Transform3D &p_transform) { + ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + item_map[p_item].navigation_mesh_transform = p_transform; + emit_changed(); +} + +void MeshLibrary::set_item_navigation_layers(int p_item, uint32_t p_navigation_layers) { + ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + item_map[p_item].navigation_layers = p_navigation_layers; + emit_changed(); +} + +void MeshLibrary::set_item_preview(int p_item, const Ref &p_preview) { + ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + item_map[p_item].preview = p_preview; + emit_changed(); +} + +String MeshLibrary::get_item_name(int p_item) const { + ERR_FAIL_COND_V_MSG(!item_map.has(p_item), "", "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + return item_map[p_item].name; +} + +Ref MeshLibrary::get_item_mesh(int p_item) const { + ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Ref(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + return item_map[p_item].mesh; +} + +Transform3D MeshLibrary::get_item_mesh_transform(int p_item) const { + ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Transform3D(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + return item_map[p_item].mesh_transform; +} + +Vector MeshLibrary::get_item_shapes(int p_item) const { + ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Vector(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + return item_map[p_item].shapes; +} + +Ref MeshLibrary::get_item_navigation_mesh(int p_item) const { + ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Ref(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + return item_map[p_item].navigation_mesh; +} + +Transform3D MeshLibrary::get_item_navigation_mesh_transform(int p_item) const { + ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Transform3D(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + return item_map[p_item].navigation_mesh_transform; +} + +uint32_t MeshLibrary::get_item_navigation_layers(int p_item) const { + ERR_FAIL_COND_V_MSG(!item_map.has(p_item), 0, "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + return item_map[p_item].navigation_layers; +} + +Ref MeshLibrary::get_item_preview(int p_item) const { + ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Ref(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + return item_map[p_item].preview; +} + +bool MeshLibrary::has_item(int p_item) const { + return item_map.has(p_item); +} + +void MeshLibrary::remove_item(int p_item) { + ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + item_map.erase(p_item); + notify_property_list_changed(); + emit_changed(); +} + +void MeshLibrary::clear() { + item_map.clear(); + notify_property_list_changed(); + emit_changed(); +} + +Vector MeshLibrary::get_item_list() const { + Vector ret; + ret.resize(item_map.size()); + int idx = 0; + for (const KeyValue &E : item_map) { + ret.write[idx++] = E.key; + } + + return ret; +} + +int MeshLibrary::find_item_by_name(const String &p_name) const { + for (const KeyValue &E : item_map) { + if (E.value.name == p_name) { + return E.key; + } + } + return -1; +} + +int MeshLibrary::get_last_unused_item_id() const { + if (!item_map.size()) { + return 0; + } else { + return item_map.back()->key() + 1; + } +} + +void MeshLibrary::_set_item_shapes(int p_item, const Array &p_shapes) { + Array arr_shapes = p_shapes; + int size = p_shapes.size(); + if (size & 1) { + ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + int prev_size = item_map[p_item].shapes.size() * 2; + + if (prev_size < size) { + // Check if last element is a shape. + Ref shape = arr_shapes[size - 1]; + if (shape.is_null()) { + Ref box_shape; + box_shape.instantiate(); + arr_shapes[size - 1] = box_shape; + } + + // Make sure the added element is a Transform3D. + arr_shapes.push_back(Transform3D()); + size++; + } else { + size--; + arr_shapes.resize(size); + } + } + + Vector shapes; + for (int i = 0; i < size; i += 2) { + ShapeData sd; + sd.shape = arr_shapes[i + 0]; + sd.local_transform = arr_shapes[i + 1]; + + if (sd.shape.is_valid()) { + shapes.push_back(sd); + } + } + + set_item_shapes(p_item, shapes); +} + +Array MeshLibrary::_get_item_shapes(int p_item) const { + Vector shapes = get_item_shapes(p_item); + Array ret; + for (int i = 0; i < shapes.size(); i++) { + ret.push_back(shapes[i].shape); + ret.push_back(shapes[i].local_transform); + } + + return ret; +} + +void MeshLibrary::reset_state() { + clear(); +} +void MeshLibrary::_bind_methods() { + ClassDB::bind_method(D_METHOD("create_item", "id"), &MeshLibrary::create_item); + ClassDB::bind_method(D_METHOD("set_item_name", "id", "name"), &MeshLibrary::set_item_name); + ClassDB::bind_method(D_METHOD("set_item_mesh", "id", "mesh"), &MeshLibrary::set_item_mesh); + ClassDB::bind_method(D_METHOD("set_item_mesh_transform", "id", "mesh_transform"), &MeshLibrary::set_item_mesh_transform); + ClassDB::bind_method(D_METHOD("set_item_navigation_mesh", "id", "navigation_mesh"), &MeshLibrary::set_item_navigation_mesh); + ClassDB::bind_method(D_METHOD("set_item_navigation_mesh_transform", "id", "navigation_mesh"), &MeshLibrary::set_item_navigation_mesh_transform); + ClassDB::bind_method(D_METHOD("set_item_navigation_layers", "id", "navigation_layers"), &MeshLibrary::set_item_navigation_layers); + ClassDB::bind_method(D_METHOD("set_item_shapes", "id", "shapes"), &MeshLibrary::_set_item_shapes); + ClassDB::bind_method(D_METHOD("set_item_preview", "id", "texture"), &MeshLibrary::set_item_preview); + ClassDB::bind_method(D_METHOD("get_item_name", "id"), &MeshLibrary::get_item_name); + ClassDB::bind_method(D_METHOD("get_item_mesh", "id"), &MeshLibrary::get_item_mesh); + ClassDB::bind_method(D_METHOD("get_item_mesh_transform", "id"), &MeshLibrary::get_item_mesh_transform); + ClassDB::bind_method(D_METHOD("get_item_navigation_mesh", "id"), &MeshLibrary::get_item_navigation_mesh); + ClassDB::bind_method(D_METHOD("get_item_navigation_mesh_transform", "id"), &MeshLibrary::get_item_navigation_mesh_transform); + ClassDB::bind_method(D_METHOD("get_item_navigation_layers", "id"), &MeshLibrary::get_item_navigation_layers); + ClassDB::bind_method(D_METHOD("get_item_shapes", "id"), &MeshLibrary::_get_item_shapes); + ClassDB::bind_method(D_METHOD("get_item_preview", "id"), &MeshLibrary::get_item_preview); + ClassDB::bind_method(D_METHOD("remove_item", "id"), &MeshLibrary::remove_item); + ClassDB::bind_method(D_METHOD("find_item_by_name", "name"), &MeshLibrary::find_item_by_name); + + ClassDB::bind_method(D_METHOD("clear"), &MeshLibrary::clear); + ClassDB::bind_method(D_METHOD("get_item_list"), &MeshLibrary::get_item_list); + ClassDB::bind_method(D_METHOD("get_last_unused_item_id"), &MeshLibrary::get_last_unused_item_id); +} + +MeshLibrary::MeshLibrary() { +} + +MeshLibrary::~MeshLibrary() { +} diff --git a/scene/resources/3d/mesh_library.h b/scene/resources/3d/mesh_library.h new file mode 100644 index 0000000000..f1a1e3e273 --- /dev/null +++ b/scene/resources/3d/mesh_library.h @@ -0,0 +1,106 @@ +/**************************************************************************/ +/* mesh_library.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 MESH_LIBRARY_H +#define MESH_LIBRARY_H + +#include "core/io/resource.h" +#include "core/templates/rb_map.h" +#include "scene/3d/navigation_region_3d.h" +#include "scene/resources/mesh.h" +#include "shape_3d.h" + +class MeshLibrary : public Resource { + GDCLASS(MeshLibrary, Resource); + RES_BASE_EXTENSION("meshlib"); + +public: + struct ShapeData { + Ref shape; + Transform3D local_transform; + }; + struct Item { + String name; + Ref mesh; + Transform3D mesh_transform; + Vector shapes; + Ref preview; + Ref navigation_mesh; + Transform3D navigation_mesh_transform; + uint32_t navigation_layers = 1; + }; + + RBMap item_map; + + void _set_item_shapes(int p_item, const Array &p_shapes); + Array _get_item_shapes(int p_item) const; + +protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List *p_list) const; + + virtual void reset_state() override; + static void _bind_methods(); + +public: + void create_item(int p_item); + void set_item_name(int p_item, const String &p_name); + void set_item_mesh(int p_item, const Ref &p_mesh); + void set_item_mesh_transform(int p_item, const Transform3D &p_transform); + void set_item_navigation_mesh(int p_item, const Ref &p_navigation_mesh); + void set_item_navigation_mesh_transform(int p_item, const Transform3D &p_transform); + void set_item_navigation_layers(int p_item, uint32_t p_navigation_layers); + void set_item_shapes(int p_item, const Vector &p_shapes); + void set_item_preview(int p_item, const Ref &p_preview); + String get_item_name(int p_item) const; + Ref get_item_mesh(int p_item) const; + Transform3D get_item_mesh_transform(int p_item) const; + Ref get_item_navigation_mesh(int p_item) const; + Transform3D get_item_navigation_mesh_transform(int p_item) const; + uint32_t get_item_navigation_layers(int p_item) const; + Vector get_item_shapes(int p_item) const; + Ref get_item_preview(int p_item) const; + + void remove_item(int p_item); + bool has_item(int p_item) const; + + void clear(); + + int find_item_by_name(const String &p_name) const; + + Vector get_item_list() const; + int get_last_unused_item_id() const; + + MeshLibrary(); + ~MeshLibrary(); +}; + +#endif // MESH_LIBRARY_H diff --git a/scene/resources/3d/primitive_meshes.cpp b/scene/resources/3d/primitive_meshes.cpp new file mode 100644 index 0000000000..b8b4854109 --- /dev/null +++ b/scene/resources/3d/primitive_meshes.cpp @@ -0,0 +1,3655 @@ +/**************************************************************************/ +/* primitive_meshes.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 "primitive_meshes.h" + +#include "core/config/project_settings.h" +#include "scene/resources/theme.h" +#include "scene/theme/theme_db.h" +#include "servers/rendering_server.h" +#include "thirdparty/misc/polypartition.h" + +#define PADDING_REF_SIZE 1024.0 + +/** + PrimitiveMesh +*/ +void PrimitiveMesh::_update() const { + Array arr; + if (GDVIRTUAL_CALL(_create_mesh_array, arr)) { + ERR_FAIL_COND_MSG(arr.size() != RS::ARRAY_MAX, "_create_mesh_array must return an array of Mesh.ARRAY_MAX elements."); + } else { + arr.resize(RS::ARRAY_MAX); + _create_mesh_array(arr); + } + + Vector points = arr[RS::ARRAY_VERTEX]; + + ERR_FAIL_COND_MSG(points.is_empty(), "_create_mesh_array must return at least a vertex array."); + + aabb = AABB(); + + int pc = points.size(); + ERR_FAIL_COND(pc == 0); + { + const Vector3 *r = points.ptr(); + for (int i = 0; i < pc; i++) { + if (i == 0) { + aabb.position = r[i]; + } else { + aabb.expand_to(r[i]); + } + } + } + + Vector indices = arr[RS::ARRAY_INDEX]; + + if (flip_faces) { + Vector normals = arr[RS::ARRAY_NORMAL]; + + if (normals.size() && indices.size()) { + { + int nc = normals.size(); + Vector3 *w = normals.ptrw(); + for (int i = 0; i < nc; i++) { + w[i] = -w[i]; + } + } + + { + int ic = indices.size(); + int *w = indices.ptrw(); + for (int i = 0; i < ic; i += 3) { + SWAP(w[i + 0], w[i + 1]); + } + } + arr[RS::ARRAY_NORMAL] = normals; + arr[RS::ARRAY_INDEX] = indices; + } + } + + if (add_uv2) { + // _create_mesh_array should populate our UV2, this is a fallback in case it doesn't. + // As we don't know anything about the geometry we only pad the right and bottom edge + // of our texture. + Vector uv = arr[RS::ARRAY_TEX_UV]; + Vector uv2 = arr[RS::ARRAY_TEX_UV2]; + + if (uv.size() > 0 && uv2.size() == 0) { + Vector2 uv2_scale = get_uv2_scale(); + uv2.resize(uv.size()); + + Vector2 *uv2w = uv2.ptrw(); + for (int i = 0; i < uv.size(); i++) { + uv2w[i] = uv[i] * uv2_scale; + } + } + + arr[RS::ARRAY_TEX_UV2] = uv2; + } + + array_len = pc; + index_array_len = indices.size(); + // in with the new + RenderingServer::get_singleton()->mesh_clear(mesh); + RenderingServer::get_singleton()->mesh_add_surface_from_arrays(mesh, (RenderingServer::PrimitiveType)primitive_type, arr); + RenderingServer::get_singleton()->mesh_surface_set_material(mesh, 0, material.is_null() ? RID() : material->get_rid()); + + pending_request = false; + + clear_cache(); + + const_cast(this)->emit_changed(); +} + +void PrimitiveMesh::_request_update() { + if (pending_request) { + return; + } + _update(); +} + +int PrimitiveMesh::get_surface_count() const { + if (pending_request) { + _update(); + } + return 1; +} + +int PrimitiveMesh::surface_get_array_len(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, 1, -1); + if (pending_request) { + _update(); + } + + return array_len; +} + +int PrimitiveMesh::surface_get_array_index_len(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, 1, -1); + if (pending_request) { + _update(); + } + + return index_array_len; +} + +Array PrimitiveMesh::surface_get_arrays(int p_surface) const { + ERR_FAIL_INDEX_V(p_surface, 1, Array()); + if (pending_request) { + _update(); + } + + return RenderingServer::get_singleton()->mesh_surface_get_arrays(mesh, 0); +} + +Dictionary PrimitiveMesh::surface_get_lods(int p_surface) const { + return Dictionary(); //not really supported +} + +TypedArray PrimitiveMesh::surface_get_blend_shape_arrays(int p_surface) const { + return TypedArray(); //not really supported +} + +BitField PrimitiveMesh::surface_get_format(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, 1, 0); + + uint64_t mesh_format = RS::ARRAY_FORMAT_VERTEX | RS::ARRAY_FORMAT_NORMAL | RS::ARRAY_FORMAT_TANGENT | RS::ARRAY_FORMAT_TEX_UV | RS::ARRAY_FORMAT_INDEX; + if (add_uv2) { + mesh_format |= RS::ARRAY_FORMAT_TEX_UV2; + } + + return mesh_format; +} + +Mesh::PrimitiveType PrimitiveMesh::surface_get_primitive_type(int p_idx) const { + return primitive_type; +} + +void PrimitiveMesh::surface_set_material(int p_idx, const Ref &p_material) { + ERR_FAIL_INDEX(p_idx, 1); + + set_material(p_material); +} + +Ref PrimitiveMesh::surface_get_material(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, 1, nullptr); + + return material; +} + +int PrimitiveMesh::get_blend_shape_count() const { + return 0; +} + +StringName PrimitiveMesh::get_blend_shape_name(int p_index) const { + return StringName(); +} + +void PrimitiveMesh::set_blend_shape_name(int p_index, const StringName &p_name) { +} + +AABB PrimitiveMesh::get_aabb() const { + if (pending_request) { + _update(); + } + + return aabb; +} + +RID PrimitiveMesh::get_rid() const { + if (pending_request) { + _update(); + } + return mesh; +} + +void PrimitiveMesh::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_material", "material"), &PrimitiveMesh::set_material); + ClassDB::bind_method(D_METHOD("get_material"), &PrimitiveMesh::get_material); + + ClassDB::bind_method(D_METHOD("get_mesh_arrays"), &PrimitiveMesh::get_mesh_arrays); + + ClassDB::bind_method(D_METHOD("set_custom_aabb", "aabb"), &PrimitiveMesh::set_custom_aabb); + ClassDB::bind_method(D_METHOD("get_custom_aabb"), &PrimitiveMesh::get_custom_aabb); + + ClassDB::bind_method(D_METHOD("set_flip_faces", "flip_faces"), &PrimitiveMesh::set_flip_faces); + ClassDB::bind_method(D_METHOD("get_flip_faces"), &PrimitiveMesh::get_flip_faces); + + ClassDB::bind_method(D_METHOD("set_add_uv2", "add_uv2"), &PrimitiveMesh::set_add_uv2); + ClassDB::bind_method(D_METHOD("get_add_uv2"), &PrimitiveMesh::get_add_uv2); + + ClassDB::bind_method(D_METHOD("set_uv2_padding", "uv2_padding"), &PrimitiveMesh::set_uv2_padding); + ClassDB::bind_method(D_METHOD("get_uv2_padding"), &PrimitiveMesh::get_uv2_padding); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial"), "set_material", "get_material"); + ADD_PROPERTY(PropertyInfo(Variant::AABB, "custom_aabb", PROPERTY_HINT_NONE, "suffix:m"), "set_custom_aabb", "get_custom_aabb"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_faces"), "set_flip_faces", "get_flip_faces"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "add_uv2"), "set_add_uv2", "get_add_uv2"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "uv2_padding", PROPERTY_HINT_RANGE, "0,10,0.01,or_greater"), "set_uv2_padding", "get_uv2_padding"); + + GDVIRTUAL_BIND(_create_mesh_array); +} + +void PrimitiveMesh::set_material(const Ref &p_material) { + material = p_material; + if (!pending_request) { + // just apply it, else it'll happen when _update is called. + RenderingServer::get_singleton()->mesh_surface_set_material(mesh, 0, material.is_null() ? RID() : material->get_rid()); + notify_property_list_changed(); + emit_changed(); + } +} + +Ref PrimitiveMesh::get_material() const { + return material; +} + +Array PrimitiveMesh::get_mesh_arrays() const { + return surface_get_arrays(0); +} + +void PrimitiveMesh::set_custom_aabb(const AABB &p_custom) { + custom_aabb = p_custom; + RS::get_singleton()->mesh_set_custom_aabb(mesh, custom_aabb); + emit_changed(); +} + +AABB PrimitiveMesh::get_custom_aabb() const { + return custom_aabb; +} + +void PrimitiveMesh::set_flip_faces(bool p_enable) { + flip_faces = p_enable; + _request_update(); +} + +bool PrimitiveMesh::get_flip_faces() const { + return flip_faces; +} + +void PrimitiveMesh::set_add_uv2(bool p_enable) { + add_uv2 = p_enable; + _update_lightmap_size(); + _request_update(); +} + +void PrimitiveMesh::set_uv2_padding(float p_padding) { + uv2_padding = p_padding; + _update_lightmap_size(); + _request_update(); +} + +Vector2 PrimitiveMesh::get_uv2_scale(Vector2 p_margin_scale) const { + Vector2 uv2_scale; + Vector2 lightmap_size = get_lightmap_size_hint(); + + // Calculate it as a margin, if no lightmap size hint is given we assume "PADDING_REF_SIZE" as our texture size. + uv2_scale.x = p_margin_scale.x * uv2_padding / (lightmap_size.x == 0.0 ? PADDING_REF_SIZE : lightmap_size.x); + uv2_scale.y = p_margin_scale.y * uv2_padding / (lightmap_size.y == 0.0 ? PADDING_REF_SIZE : lightmap_size.y); + + // Inverse it to turn our margin into a scale + uv2_scale = Vector2(1.0, 1.0) - uv2_scale; + + return uv2_scale; +} + +float PrimitiveMesh::get_lightmap_texel_size() const { + float texel_size = GLOBAL_GET("rendering/lightmapping/primitive_meshes/texel_size"); + + if (texel_size <= 0.0) { + texel_size = 0.2; + } + + return texel_size; +} + +PrimitiveMesh::PrimitiveMesh() { + mesh = RenderingServer::get_singleton()->mesh_create(); +} + +PrimitiveMesh::~PrimitiveMesh() { + ERR_FAIL_NULL(RenderingServer::get_singleton()); + RenderingServer::get_singleton()->free(mesh); +} + +/** + CapsuleMesh +*/ + +void CapsuleMesh::_update_lightmap_size() { + if (get_add_uv2()) { + // size must have changed, update lightmap size hint + Size2i _lightmap_size_hint; + float texel_size = get_lightmap_texel_size(); + float padding = get_uv2_padding(); + + float radial_length = radius * Math_PI * 0.5; // circumference of 90 degree bend + float vertical_length = radial_length * 2 + (height - 2.0 * radius); // total vertical length + + _lightmap_size_hint.x = MAX(1.0, 4.0 * radial_length / texel_size) + padding; + _lightmap_size_hint.y = MAX(1.0, vertical_length / texel_size) + padding; + + set_lightmap_size_hint(_lightmap_size_hint); + } +} + +void CapsuleMesh::_create_mesh_array(Array &p_arr) const { + bool _add_uv2 = get_add_uv2(); + float texel_size = get_lightmap_texel_size(); + float _uv2_padding = get_uv2_padding() * texel_size; + + create_mesh_array(p_arr, radius, height, radial_segments, rings, _add_uv2, _uv2_padding); +} + +void CapsuleMesh::create_mesh_array(Array &p_arr, const float radius, const float height, const int radial_segments, const int rings, bool p_add_uv2, const float p_uv2_padding) { + int i, j, prevrow, thisrow, point; + float x, y, z, u, v, w; + float onethird = 1.0 / 3.0; + float twothirds = 2.0 / 3.0; + + // Only used if we calculate UV2 + float radial_width = 2.0 * radius * Math_PI; + float radial_h = radial_width / (radial_width + p_uv2_padding); + float radial_length = radius * Math_PI * 0.5; // circumference of 90 degree bend + float vertical_length = radial_length * 2 + (height - 2.0 * radius) + p_uv2_padding; // total vertical length + float radial_v = radial_length / vertical_length; // v size of top and bottom section + float height_v = (height - 2.0 * radius) / vertical_length; // v size of height section + + // note, this has been aligned with our collision shape but I've left the descriptions as top/middle/bottom + + Vector points; + Vector normals; + Vector tangents; + Vector uvs; + Vector uv2s; + Vector indices; + point = 0; + +#define ADD_TANGENT(m_x, m_y, m_z, m_d) \ + tangents.push_back(m_x); \ + tangents.push_back(m_y); \ + tangents.push_back(m_z); \ + tangents.push_back(m_d); + + /* top hemisphere */ + thisrow = 0; + prevrow = 0; + for (j = 0; j <= (rings + 1); j++) { + v = j; + + v /= (rings + 1); + w = sin(0.5 * Math_PI * v); + y = radius * cos(0.5 * Math_PI * v); + + for (i = 0; i <= radial_segments; i++) { + u = i; + u /= radial_segments; + + x = -sin(u * Math_TAU); + z = cos(u * Math_TAU); + + Vector3 p = Vector3(x * radius * w, y, -z * radius * w); + points.push_back(p + Vector3(0.0, 0.5 * height - radius, 0.0)); + normals.push_back(p.normalized()); + ADD_TANGENT(-z, 0.0, -x, 1.0) + uvs.push_back(Vector2(u, v * onethird)); + if (p_add_uv2) { + uv2s.push_back(Vector2(u * radial_h, v * radial_v)); + } + point++; + + if (i > 0 && j > 0) { + indices.push_back(prevrow + i - 1); + indices.push_back(prevrow + i); + indices.push_back(thisrow + i - 1); + + indices.push_back(prevrow + i); + indices.push_back(thisrow + i); + indices.push_back(thisrow + i - 1); + } + } + + prevrow = thisrow; + thisrow = point; + } + + /* cylinder */ + thisrow = point; + prevrow = 0; + for (j = 0; j <= (rings + 1); j++) { + v = j; + v /= (rings + 1); + + y = (height - 2.0 * radius) * v; + y = (0.5 * height - radius) - y; + + for (i = 0; i <= radial_segments; i++) { + u = i; + u /= radial_segments; + + x = -sin(u * Math_TAU); + z = cos(u * Math_TAU); + + Vector3 p = Vector3(x * radius, y, -z * radius); + points.push_back(p); + normals.push_back(Vector3(x, 0.0, -z)); + ADD_TANGENT(-z, 0.0, -x, 1.0) + uvs.push_back(Vector2(u, onethird + (v * onethird))); + if (p_add_uv2) { + uv2s.push_back(Vector2(u * radial_h, radial_v + (v * height_v))); + } + point++; + + if (i > 0 && j > 0) { + indices.push_back(prevrow + i - 1); + indices.push_back(prevrow + i); + indices.push_back(thisrow + i - 1); + + indices.push_back(prevrow + i); + indices.push_back(thisrow + i); + indices.push_back(thisrow + i - 1); + } + } + + prevrow = thisrow; + thisrow = point; + } + + /* bottom hemisphere */ + thisrow = point; + prevrow = 0; + for (j = 0; j <= (rings + 1); j++) { + v = j; + + v /= (rings + 1); + v += 1.0; + w = sin(0.5 * Math_PI * v); + y = radius * cos(0.5 * Math_PI * v); + + for (i = 0; i <= radial_segments; i++) { + u = i; + u /= radial_segments; + + x = -sin(u * Math_TAU); + z = cos(u * Math_TAU); + + Vector3 p = Vector3(x * radius * w, y, -z * radius * w); + points.push_back(p + Vector3(0.0, -0.5 * height + radius, 0.0)); + normals.push_back(p.normalized()); + ADD_TANGENT(-z, 0.0, -x, 1.0) + uvs.push_back(Vector2(u, twothirds + ((v - 1.0) * onethird))); + if (p_add_uv2) { + uv2s.push_back(Vector2(u * radial_h, radial_v + height_v + ((v - 1.0) * radial_v))); + } + point++; + + if (i > 0 && j > 0) { + indices.push_back(prevrow + i - 1); + indices.push_back(prevrow + i); + indices.push_back(thisrow + i - 1); + + indices.push_back(prevrow + i); + indices.push_back(thisrow + i); + indices.push_back(thisrow + i - 1); + } + } + + prevrow = thisrow; + thisrow = point; + } + + p_arr[RS::ARRAY_VERTEX] = points; + p_arr[RS::ARRAY_NORMAL] = normals; + p_arr[RS::ARRAY_TANGENT] = tangents; + p_arr[RS::ARRAY_TEX_UV] = uvs; + if (p_add_uv2) { + p_arr[RS::ARRAY_TEX_UV2] = uv2s; + } + p_arr[RS::ARRAY_INDEX] = indices; +} + +void CapsuleMesh::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CapsuleMesh::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &CapsuleMesh::get_radius); + ClassDB::bind_method(D_METHOD("set_height", "height"), &CapsuleMesh::set_height); + ClassDB::bind_method(D_METHOD("get_height"), &CapsuleMesh::get_height); + + ClassDB::bind_method(D_METHOD("set_radial_segments", "segments"), &CapsuleMesh::set_radial_segments); + ClassDB::bind_method(D_METHOD("get_radial_segments"), &CapsuleMesh::get_radial_segments); + ClassDB::bind_method(D_METHOD("set_rings", "rings"), &CapsuleMesh::set_rings); + ClassDB::bind_method(D_METHOD("get_rings"), &CapsuleMesh::get_rings); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater,suffix:m"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater,suffix:m"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_radial_segments", "get_radial_segments"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_rings", "get_rings"); + + ADD_LINKED_PROPERTY("radius", "height"); + ADD_LINKED_PROPERTY("height", "radius"); +} + +void CapsuleMesh::set_radius(const float p_radius) { + radius = p_radius; + if (radius > height * 0.5) { + height = radius * 2.0; + } + _update_lightmap_size(); + _request_update(); +} + +float CapsuleMesh::get_radius() const { + return radius; +} + +void CapsuleMesh::set_height(const float p_height) { + height = p_height; + if (radius > height * 0.5) { + radius = height * 0.5; + } + _update_lightmap_size(); + _request_update(); +} + +float CapsuleMesh::get_height() const { + return height; +} + +void CapsuleMesh::set_radial_segments(const int p_segments) { + radial_segments = p_segments > 4 ? p_segments : 4; + _request_update(); +} + +int CapsuleMesh::get_radial_segments() const { + return radial_segments; +} + +void CapsuleMesh::set_rings(const int p_rings) { + ERR_FAIL_COND(p_rings < 0); + rings = p_rings; + _request_update(); +} + +int CapsuleMesh::get_rings() const { + return rings; +} + +CapsuleMesh::CapsuleMesh() {} + +/** + BoxMesh +*/ + +void BoxMesh::_update_lightmap_size() { + if (get_add_uv2()) { + // size must have changed, update lightmap size hint + Size2i _lightmap_size_hint; + float texel_size = get_lightmap_texel_size(); + float padding = get_uv2_padding(); + + float width = (size.x + size.z) / texel_size; + float length = (size.y + size.y + MAX(size.x, size.z)) / texel_size; + + _lightmap_size_hint.x = MAX(1.0, width) + 2.0 * padding; + _lightmap_size_hint.y = MAX(1.0, length) + 3.0 * padding; + + set_lightmap_size_hint(_lightmap_size_hint); + } +} + +void BoxMesh::_create_mesh_array(Array &p_arr) const { + // Note about padding, with our box each face of the box faces a different direction so we want a seam + // around every face. We thus add our padding to the right and bottom of each face. + // With 3 faces along the width and 2 along the height of the texture we need to adjust our scale + // accordingly. + bool _add_uv2 = get_add_uv2(); + float texel_size = get_lightmap_texel_size(); + float _uv2_padding = get_uv2_padding() * texel_size; + + BoxMesh::create_mesh_array(p_arr, size, subdivide_w, subdivide_h, subdivide_d, _add_uv2, _uv2_padding); +} + +void BoxMesh::create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w, int subdivide_h, int subdivide_d, bool p_add_uv2, const float p_uv2_padding) { + int i, j, prevrow, thisrow, point; + float x, y, z; + float onethird = 1.0 / 3.0; + float twothirds = 2.0 / 3.0; + + // Only used if we calculate UV2 + // TODO this could be improved by changing the order depending on which side is the longest (basically the below works best if size.y is the longest) + float total_h = (size.x + size.z + (2.0 * p_uv2_padding)); + float padding_h = p_uv2_padding / total_h; + float width_h = size.x / total_h; + float depth_h = size.z / total_h; + float total_v = (size.y + size.y + MAX(size.x, size.z) + (3.0 * p_uv2_padding)); + float padding_v = p_uv2_padding / total_v; + float width_v = size.x / total_v; + float height_v = size.y / total_v; + float depth_v = size.z / total_v; + + Vector3 start_pos = size * -0.5; + + // set our bounding box + + Vector points; + Vector normals; + Vector tangents; + Vector uvs; + Vector uv2s; + Vector indices; + point = 0; + +#define ADD_TANGENT(m_x, m_y, m_z, m_d) \ + tangents.push_back(m_x); \ + tangents.push_back(m_y); \ + tangents.push_back(m_z); \ + tangents.push_back(m_d); + + // front + back + y = start_pos.y; + thisrow = point; + prevrow = 0; + for (j = 0; j <= subdivide_h + 1; j++) { + float v = j; + float v2 = v / (subdivide_w + 1.0); + v /= (2.0 * (subdivide_h + 1.0)); + + x = start_pos.x; + for (i = 0; i <= subdivide_w + 1; i++) { + float u = i; + float u2 = u / (subdivide_w + 1.0); + u /= (3.0 * (subdivide_w + 1.0)); + + // front + points.push_back(Vector3(x, -y, -start_pos.z)); // double negative on the Z! + normals.push_back(Vector3(0.0, 0.0, 1.0)); + ADD_TANGENT(1.0, 0.0, 0.0, 1.0); + uvs.push_back(Vector2(u, v)); + if (p_add_uv2) { + uv2s.push_back(Vector2(u2 * width_h, v2 * height_v)); + } + point++; + + // back + points.push_back(Vector3(-x, -y, start_pos.z)); + normals.push_back(Vector3(0.0, 0.0, -1.0)); + ADD_TANGENT(-1.0, 0.0, 0.0, 1.0); + uvs.push_back(Vector2(twothirds + u, v)); + if (p_add_uv2) { + uv2s.push_back(Vector2(u2 * width_h, height_v + padding_v + (v2 * height_v))); + } + point++; + + if (i > 0 && j > 0) { + int i2 = i * 2; + + // front + indices.push_back(prevrow + i2 - 2); + indices.push_back(prevrow + i2); + indices.push_back(thisrow + i2 - 2); + indices.push_back(prevrow + i2); + indices.push_back(thisrow + i2); + indices.push_back(thisrow + i2 - 2); + + // back + indices.push_back(prevrow + i2 - 1); + indices.push_back(prevrow + i2 + 1); + indices.push_back(thisrow + i2 - 1); + indices.push_back(prevrow + i2 + 1); + indices.push_back(thisrow + i2 + 1); + indices.push_back(thisrow + i2 - 1); + } + + x += size.x / (subdivide_w + 1.0); + } + + y += size.y / (subdivide_h + 1.0); + prevrow = thisrow; + thisrow = point; + } + + // left + right + y = start_pos.y; + thisrow = point; + prevrow = 0; + for (j = 0; j <= (subdivide_h + 1); j++) { + float v = j; + float v2 = v / (subdivide_h + 1.0); + v /= (2.0 * (subdivide_h + 1.0)); + + z = start_pos.z; + for (i = 0; i <= (subdivide_d + 1); i++) { + float u = i; + float u2 = u / (subdivide_d + 1.0); + u /= (3.0 * (subdivide_d + 1.0)); + + // right + points.push_back(Vector3(-start_pos.x, -y, -z)); + normals.push_back(Vector3(1.0, 0.0, 0.0)); + ADD_TANGENT(0.0, 0.0, -1.0, 1.0); + uvs.push_back(Vector2(onethird + u, v)); + if (p_add_uv2) { + uv2s.push_back(Vector2(width_h + padding_h + (u2 * depth_h), v2 * height_v)); + } + point++; + + // left + points.push_back(Vector3(start_pos.x, -y, z)); + normals.push_back(Vector3(-1.0, 0.0, 0.0)); + ADD_TANGENT(0.0, 0.0, 1.0, 1.0); + uvs.push_back(Vector2(u, 0.5 + v)); + if (p_add_uv2) { + uv2s.push_back(Vector2(width_h + padding_h + (u2 * depth_h), height_v + padding_v + (v2 * height_v))); + } + point++; + + if (i > 0 && j > 0) { + int i2 = i * 2; + + // right + indices.push_back(prevrow + i2 - 2); + indices.push_back(prevrow + i2); + indices.push_back(thisrow + i2 - 2); + indices.push_back(prevrow + i2); + indices.push_back(thisrow + i2); + indices.push_back(thisrow + i2 - 2); + + // left + indices.push_back(prevrow + i2 - 1); + indices.push_back(prevrow + i2 + 1); + indices.push_back(thisrow + i2 - 1); + indices.push_back(prevrow + i2 + 1); + indices.push_back(thisrow + i2 + 1); + indices.push_back(thisrow + i2 - 1); + } + + z += size.z / (subdivide_d + 1.0); + } + + y += size.y / (subdivide_h + 1.0); + prevrow = thisrow; + thisrow = point; + } + + // top + bottom + z = start_pos.z; + thisrow = point; + prevrow = 0; + for (j = 0; j <= (subdivide_d + 1); j++) { + float v = j; + float v2 = v / (subdivide_d + 1.0); + v /= (2.0 * (subdivide_d + 1.0)); + + x = start_pos.x; + for (i = 0; i <= (subdivide_w + 1); i++) { + float u = i; + float u2 = u / (subdivide_w + 1.0); + u /= (3.0 * (subdivide_w + 1.0)); + + // top + points.push_back(Vector3(-x, -start_pos.y, -z)); + normals.push_back(Vector3(0.0, 1.0, 0.0)); + ADD_TANGENT(-1.0, 0.0, 0.0, 1.0); + uvs.push_back(Vector2(onethird + u, 0.5 + v)); + if (p_add_uv2) { + uv2s.push_back(Vector2(u2 * width_h, ((height_v + padding_v) * 2.0) + (v2 * depth_v))); + } + point++; + + // bottom + points.push_back(Vector3(x, start_pos.y, -z)); + normals.push_back(Vector3(0.0, -1.0, 0.0)); + ADD_TANGENT(1.0, 0.0, 0.0, 1.0); + uvs.push_back(Vector2(twothirds + u, 0.5 + v)); + if (p_add_uv2) { + uv2s.push_back(Vector2(width_h + padding_h + (u2 * depth_h), ((height_v + padding_v) * 2.0) + (v2 * width_v))); + } + point++; + + if (i > 0 && j > 0) { + int i2 = i * 2; + + // top + indices.push_back(prevrow + i2 - 2); + indices.push_back(prevrow + i2); + indices.push_back(thisrow + i2 - 2); + indices.push_back(prevrow + i2); + indices.push_back(thisrow + i2); + indices.push_back(thisrow + i2 - 2); + + // bottom + indices.push_back(prevrow + i2 - 1); + indices.push_back(prevrow + i2 + 1); + indices.push_back(thisrow + i2 - 1); + indices.push_back(prevrow + i2 + 1); + indices.push_back(thisrow + i2 + 1); + indices.push_back(thisrow + i2 - 1); + } + + x += size.x / (subdivide_w + 1.0); + } + + z += size.z / (subdivide_d + 1.0); + prevrow = thisrow; + thisrow = point; + } + + p_arr[RS::ARRAY_VERTEX] = points; + p_arr[RS::ARRAY_NORMAL] = normals; + p_arr[RS::ARRAY_TANGENT] = tangents; + p_arr[RS::ARRAY_TEX_UV] = uvs; + if (p_add_uv2) { + p_arr[RS::ARRAY_TEX_UV2] = uv2s; + } + p_arr[RS::ARRAY_INDEX] = indices; +} + +void BoxMesh::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_size", "size"), &BoxMesh::set_size); + ClassDB::bind_method(D_METHOD("get_size"), &BoxMesh::get_size); + + ClassDB::bind_method(D_METHOD("set_subdivide_width", "subdivide"), &BoxMesh::set_subdivide_width); + ClassDB::bind_method(D_METHOD("get_subdivide_width"), &BoxMesh::get_subdivide_width); + ClassDB::bind_method(D_METHOD("set_subdivide_height", "divisions"), &BoxMesh::set_subdivide_height); + ClassDB::bind_method(D_METHOD("get_subdivide_height"), &BoxMesh::get_subdivide_height); + ClassDB::bind_method(D_METHOD("set_subdivide_depth", "divisions"), &BoxMesh::set_subdivide_depth); + ClassDB::bind_method(D_METHOD("get_subdivide_depth"), &BoxMesh::get_subdivide_depth); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size", PROPERTY_HINT_NONE, "suffix:m"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_width", "get_subdivide_width"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_height", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_height", "get_subdivide_height"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_depth", "get_subdivide_depth"); +} + +void BoxMesh::set_size(const Vector3 &p_size) { + size = p_size; + _update_lightmap_size(); + _request_update(); +} + +Vector3 BoxMesh::get_size() const { + return size; +} + +void BoxMesh::set_subdivide_width(const int p_divisions) { + subdivide_w = p_divisions > 0 ? p_divisions : 0; + _request_update(); +} + +int BoxMesh::get_subdivide_width() const { + return subdivide_w; +} + +void BoxMesh::set_subdivide_height(const int p_divisions) { + subdivide_h = p_divisions > 0 ? p_divisions : 0; + _request_update(); +} + +int BoxMesh::get_subdivide_height() const { + return subdivide_h; +} + +void BoxMesh::set_subdivide_depth(const int p_divisions) { + subdivide_d = p_divisions > 0 ? p_divisions : 0; + _request_update(); +} + +int BoxMesh::get_subdivide_depth() const { + return subdivide_d; +} + +BoxMesh::BoxMesh() {} + +/** + CylinderMesh +*/ + +void CylinderMesh::_update_lightmap_size() { + if (get_add_uv2()) { + // size must have changed, update lightmap size hint + Size2i _lightmap_size_hint; + float texel_size = get_lightmap_texel_size(); + float padding = get_uv2_padding(); + + float top_circumference = top_radius * Math_PI * 2.0; + float bottom_circumference = bottom_radius * Math_PI * 2.0; + + float _width = MAX(top_circumference, bottom_circumference) / texel_size + padding; + _width = MAX(_width, (((top_radius + bottom_radius) / texel_size) + padding) * 2.0); // this is extremely unlikely to be larger, will only happen if padding is larger then our diameter. + _lightmap_size_hint.x = MAX(1.0, _width); + + float _height = ((height + (MAX(top_radius, bottom_radius) * 2.0)) / texel_size) + (2.0 * padding); + + _lightmap_size_hint.y = MAX(1.0, _height); + + set_lightmap_size_hint(_lightmap_size_hint); + } +} + +void CylinderMesh::_create_mesh_array(Array &p_arr) const { + bool _add_uv2 = get_add_uv2(); + float texel_size = get_lightmap_texel_size(); + float _uv2_padding = get_uv2_padding() * texel_size; + + create_mesh_array(p_arr, top_radius, bottom_radius, height, radial_segments, rings, cap_top, cap_bottom, _add_uv2, _uv2_padding); +} + +void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float bottom_radius, float height, int radial_segments, int rings, bool cap_top, bool cap_bottom, bool p_add_uv2, const float p_uv2_padding) { + int i, j, prevrow, thisrow, point; + float x, y, z, u, v, radius, radius_h; + + // Only used if we calculate UV2 + float top_circumference = top_radius * Math_PI * 2.0; + float bottom_circumference = bottom_radius * Math_PI * 2.0; + float vertical_length = height + MAX(2.0 * top_radius, 2.0 * bottom_radius) + (2.0 * p_uv2_padding); + float height_v = height / vertical_length; + float padding_v = p_uv2_padding / vertical_length; + + float horizonal_length = MAX(MAX(2.0 * (top_radius + bottom_radius + p_uv2_padding), top_circumference + p_uv2_padding), bottom_circumference + p_uv2_padding); + float center_h = 0.5 * (horizonal_length - p_uv2_padding) / horizonal_length; + float top_h = top_circumference / horizonal_length; + float bottom_h = bottom_circumference / horizonal_length; + float padding_h = p_uv2_padding / horizonal_length; + + Vector points; + Vector normals; + Vector tangents; + Vector uvs; + Vector uv2s; + Vector indices; + point = 0; + +#define ADD_TANGENT(m_x, m_y, m_z, m_d) \ + tangents.push_back(m_x); \ + tangents.push_back(m_y); \ + tangents.push_back(m_z); \ + tangents.push_back(m_d); + + thisrow = 0; + prevrow = 0; + const real_t side_normal_y = (bottom_radius - top_radius) / height; + for (j = 0; j <= (rings + 1); j++) { + v = j; + v /= (rings + 1); + + radius = top_radius + ((bottom_radius - top_radius) * v); + radius_h = top_h + ((bottom_h - top_h) * v); + + y = height * v; + y = (height * 0.5) - y; + + for (i = 0; i <= radial_segments; i++) { + u = i; + u /= radial_segments; + + x = sin(u * Math_TAU); + z = cos(u * Math_TAU); + + Vector3 p = Vector3(x * radius, y, z * radius); + points.push_back(p); + normals.push_back(Vector3(x, side_normal_y, z).normalized()); + ADD_TANGENT(z, 0.0, -x, 1.0) + uvs.push_back(Vector2(u, v * 0.5)); + if (p_add_uv2) { + uv2s.push_back(Vector2(center_h + (u - 0.5) * radius_h, v * height_v)); + } + point++; + + if (i > 0 && j > 0) { + indices.push_back(prevrow + i - 1); + indices.push_back(prevrow + i); + indices.push_back(thisrow + i - 1); + + indices.push_back(prevrow + i); + indices.push_back(thisrow + i); + indices.push_back(thisrow + i - 1); + } + } + + prevrow = thisrow; + thisrow = point; + } + + // Adjust for bottom section, only used if we calculate UV2s. + top_h = top_radius / horizonal_length; + float top_v = top_radius / vertical_length; + bottom_h = bottom_radius / horizonal_length; + float bottom_v = bottom_radius / vertical_length; + + // Add top. + if (cap_top && top_radius > 0.0) { + y = height * 0.5; + + thisrow = point; + points.push_back(Vector3(0.0, y, 0.0)); + normals.push_back(Vector3(0.0, 1.0, 0.0)); + ADD_TANGENT(1.0, 0.0, 0.0, 1.0) + uvs.push_back(Vector2(0.25, 0.75)); + if (p_add_uv2) { + uv2s.push_back(Vector2(top_h, height_v + padding_v + MAX(top_v, bottom_v))); + } + point++; + + for (i = 0; i <= radial_segments; i++) { + float r = i; + r /= radial_segments; + + x = sin(r * Math_TAU); + z = cos(r * Math_TAU); + + u = ((x + 1.0) * 0.25); + v = 0.5 + ((z + 1.0) * 0.25); + + Vector3 p = Vector3(x * top_radius, y, z * top_radius); + points.push_back(p); + normals.push_back(Vector3(0.0, 1.0, 0.0)); + ADD_TANGENT(1.0, 0.0, 0.0, 1.0) + uvs.push_back(Vector2(u, v)); + if (p_add_uv2) { + uv2s.push_back(Vector2(top_h + (x * top_h), height_v + padding_v + MAX(top_v, bottom_v) + (z * top_v))); + } + point++; + + if (i > 0) { + indices.push_back(thisrow); + indices.push_back(point - 1); + indices.push_back(point - 2); + } + } + } + + // Add bottom. + if (cap_bottom && bottom_radius > 0.0) { + y = height * -0.5; + + thisrow = point; + points.push_back(Vector3(0.0, y, 0.0)); + normals.push_back(Vector3(0.0, -1.0, 0.0)); + ADD_TANGENT(1.0, 0.0, 0.0, 1.0) + uvs.push_back(Vector2(0.75, 0.75)); + if (p_add_uv2) { + uv2s.push_back(Vector2(top_h + top_h + padding_h + bottom_h, height_v + padding_v + MAX(top_v, bottom_v))); + } + point++; + + for (i = 0; i <= radial_segments; i++) { + float r = i; + r /= radial_segments; + + x = sin(r * Math_TAU); + z = cos(r * Math_TAU); + + u = 0.5 + ((x + 1.0) * 0.25); + v = 1.0 - ((z + 1.0) * 0.25); + + Vector3 p = Vector3(x * bottom_radius, y, z * bottom_radius); + points.push_back(p); + normals.push_back(Vector3(0.0, -1.0, 0.0)); + ADD_TANGENT(1.0, 0.0, 0.0, 1.0) + uvs.push_back(Vector2(u, v)); + if (p_add_uv2) { + uv2s.push_back(Vector2(top_h + top_h + padding_h + bottom_h + (x * bottom_h), height_v + padding_v + MAX(top_v, bottom_v) - (z * bottom_v))); + } + point++; + + if (i > 0) { + indices.push_back(thisrow); + indices.push_back(point - 2); + indices.push_back(point - 1); + } + } + } + + p_arr[RS::ARRAY_VERTEX] = points; + p_arr[RS::ARRAY_NORMAL] = normals; + p_arr[RS::ARRAY_TANGENT] = tangents; + p_arr[RS::ARRAY_TEX_UV] = uvs; + if (p_add_uv2) { + p_arr[RS::ARRAY_TEX_UV2] = uv2s; + } + p_arr[RS::ARRAY_INDEX] = indices; +} + +void CylinderMesh::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_top_radius", "radius"), &CylinderMesh::set_top_radius); + ClassDB::bind_method(D_METHOD("get_top_radius"), &CylinderMesh::get_top_radius); + ClassDB::bind_method(D_METHOD("set_bottom_radius", "radius"), &CylinderMesh::set_bottom_radius); + ClassDB::bind_method(D_METHOD("get_bottom_radius"), &CylinderMesh::get_bottom_radius); + ClassDB::bind_method(D_METHOD("set_height", "height"), &CylinderMesh::set_height); + ClassDB::bind_method(D_METHOD("get_height"), &CylinderMesh::get_height); + + ClassDB::bind_method(D_METHOD("set_radial_segments", "segments"), &CylinderMesh::set_radial_segments); + ClassDB::bind_method(D_METHOD("get_radial_segments"), &CylinderMesh::get_radial_segments); + ClassDB::bind_method(D_METHOD("set_rings", "rings"), &CylinderMesh::set_rings); + ClassDB::bind_method(D_METHOD("get_rings"), &CylinderMesh::get_rings); + + ClassDB::bind_method(D_METHOD("set_cap_top", "cap_top"), &CylinderMesh::set_cap_top); + ClassDB::bind_method(D_METHOD("is_cap_top"), &CylinderMesh::is_cap_top); + + ClassDB::bind_method(D_METHOD("set_cap_bottom", "cap_bottom"), &CylinderMesh::set_cap_bottom); + ClassDB::bind_method(D_METHOD("is_cap_bottom"), &CylinderMesh::is_cap_bottom); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "top_radius", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater,suffix:m"), "set_top_radius", "get_top_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bottom_radius", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater,suffix:m"), "set_bottom_radius", "get_bottom_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_radial_segments", "get_radial_segments"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_rings", "get_rings"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cap_top"), "set_cap_top", "is_cap_top"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cap_bottom"), "set_cap_bottom", "is_cap_bottom"); +} + +void CylinderMesh::set_top_radius(const float p_radius) { + top_radius = p_radius; + _update_lightmap_size(); + _request_update(); +} + +float CylinderMesh::get_top_radius() const { + return top_radius; +} + +void CylinderMesh::set_bottom_radius(const float p_radius) { + bottom_radius = p_radius; + _update_lightmap_size(); + _request_update(); +} + +float CylinderMesh::get_bottom_radius() const { + return bottom_radius; +} + +void CylinderMesh::set_height(const float p_height) { + height = p_height; + _update_lightmap_size(); + _request_update(); +} + +float CylinderMesh::get_height() const { + return height; +} + +void CylinderMesh::set_radial_segments(const int p_segments) { + radial_segments = p_segments > 4 ? p_segments : 4; + _request_update(); +} + +int CylinderMesh::get_radial_segments() const { + return radial_segments; +} + +void CylinderMesh::set_rings(const int p_rings) { + ERR_FAIL_COND(p_rings < 0); + rings = p_rings; + _request_update(); +} + +int CylinderMesh::get_rings() const { + return rings; +} + +void CylinderMesh::set_cap_top(bool p_cap_top) { + cap_top = p_cap_top; + _request_update(); +} + +bool CylinderMesh::is_cap_top() const { + return cap_top; +} + +void CylinderMesh::set_cap_bottom(bool p_cap_bottom) { + cap_bottom = p_cap_bottom; + _request_update(); +} + +bool CylinderMesh::is_cap_bottom() const { + return cap_bottom; +} + +CylinderMesh::CylinderMesh() {} + +/** + PlaneMesh +*/ + +void PlaneMesh::_update_lightmap_size() { + if (get_add_uv2()) { + // size must have changed, update lightmap size hint + Size2i _lightmap_size_hint; + float texel_size = get_lightmap_texel_size(); + float padding = get_uv2_padding(); + + _lightmap_size_hint.x = MAX(1.0, (size.x / texel_size) + padding); + _lightmap_size_hint.y = MAX(1.0, (size.y / texel_size) + padding); + + set_lightmap_size_hint(_lightmap_size_hint); + } +} + +void PlaneMesh::_create_mesh_array(Array &p_arr) const { + int i, j, prevrow, thisrow, point; + float x, z; + + // Plane mesh can use default UV2 calculation as implemented in Primitive Mesh + + Size2 start_pos = size * -0.5; + + Vector3 normal = Vector3(0.0, 1.0, 0.0); + if (orientation == FACE_X) { + normal = Vector3(1.0, 0.0, 0.0); + } else if (orientation == FACE_Z) { + normal = Vector3(0.0, 0.0, 1.0); + } + + Vector points; + Vector normals; + Vector tangents; + Vector uvs; + Vector indices; + point = 0; + +#define ADD_TANGENT(m_x, m_y, m_z, m_d) \ + tangents.push_back(m_x); \ + tangents.push_back(m_y); \ + tangents.push_back(m_z); \ + tangents.push_back(m_d); + + /* top + bottom */ + z = start_pos.y; + thisrow = point; + prevrow = 0; + for (j = 0; j <= (subdivide_d + 1); j++) { + x = start_pos.x; + for (i = 0; i <= (subdivide_w + 1); i++) { + float u = i; + float v = j; + u /= (subdivide_w + 1.0); + v /= (subdivide_d + 1.0); + + if (orientation == FACE_X) { + points.push_back(Vector3(0.0, z, x) + center_offset); + } else if (orientation == FACE_Y) { + points.push_back(Vector3(-x, 0.0, -z) + center_offset); + } else if (orientation == FACE_Z) { + points.push_back(Vector3(-x, z, 0.0) + center_offset); + } + normals.push_back(normal); + if (orientation == FACE_X) { + ADD_TANGENT(0.0, 0.0, -1.0, 1.0); + } else { + ADD_TANGENT(1.0, 0.0, 0.0, 1.0); + } + uvs.push_back(Vector2(1.0 - u, 1.0 - v)); /* 1.0 - uv to match orientation with Quad */ + point++; + + if (i > 0 && j > 0) { + indices.push_back(prevrow + i - 1); + indices.push_back(prevrow + i); + indices.push_back(thisrow + i - 1); + indices.push_back(prevrow + i); + indices.push_back(thisrow + i); + indices.push_back(thisrow + i - 1); + } + + x += size.x / (subdivide_w + 1.0); + } + + z += size.y / (subdivide_d + 1.0); + prevrow = thisrow; + thisrow = point; + } + + p_arr[RS::ARRAY_VERTEX] = points; + p_arr[RS::ARRAY_NORMAL] = normals; + p_arr[RS::ARRAY_TANGENT] = tangents; + p_arr[RS::ARRAY_TEX_UV] = uvs; + p_arr[RS::ARRAY_INDEX] = indices; +} + +void PlaneMesh::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_size", "size"), &PlaneMesh::set_size); + ClassDB::bind_method(D_METHOD("get_size"), &PlaneMesh::get_size); + + ClassDB::bind_method(D_METHOD("set_subdivide_width", "subdivide"), &PlaneMesh::set_subdivide_width); + ClassDB::bind_method(D_METHOD("get_subdivide_width"), &PlaneMesh::get_subdivide_width); + ClassDB::bind_method(D_METHOD("set_subdivide_depth", "subdivide"), &PlaneMesh::set_subdivide_depth); + ClassDB::bind_method(D_METHOD("get_subdivide_depth"), &PlaneMesh::get_subdivide_depth); + + ClassDB::bind_method(D_METHOD("set_center_offset", "offset"), &PlaneMesh::set_center_offset); + ClassDB::bind_method(D_METHOD("get_center_offset"), &PlaneMesh::get_center_offset); + + ClassDB::bind_method(D_METHOD("set_orientation", "orientation"), &PlaneMesh::set_orientation); + ClassDB::bind_method(D_METHOD("get_orientation"), &PlaneMesh::get_orientation); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size", PROPERTY_HINT_NONE, "suffix:m"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_width", "get_subdivide_width"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_depth", "get_subdivide_depth"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "center_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_center_offset", "get_center_offset"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "orientation", PROPERTY_HINT_ENUM, "Face X,Face Y,Face Z"), "set_orientation", "get_orientation"); + + BIND_ENUM_CONSTANT(FACE_X) + BIND_ENUM_CONSTANT(FACE_Y) + BIND_ENUM_CONSTANT(FACE_Z) +} + +void PlaneMesh::set_size(const Size2 &p_size) { + size = p_size; + _update_lightmap_size(); + _request_update(); +} + +Size2 PlaneMesh::get_size() const { + return size; +} + +void PlaneMesh::set_subdivide_width(const int p_divisions) { + subdivide_w = p_divisions > 0 ? p_divisions : 0; + _request_update(); +} + +int PlaneMesh::get_subdivide_width() const { + return subdivide_w; +} + +void PlaneMesh::set_subdivide_depth(const int p_divisions) { + subdivide_d = p_divisions > 0 ? p_divisions : 0; + _request_update(); +} + +int PlaneMesh::get_subdivide_depth() const { + return subdivide_d; +} + +void PlaneMesh::set_center_offset(const Vector3 p_offset) { + center_offset = p_offset; + _request_update(); +} + +Vector3 PlaneMesh::get_center_offset() const { + return center_offset; +} + +void PlaneMesh::set_orientation(const Orientation p_orientation) { + orientation = p_orientation; + _request_update(); +} + +PlaneMesh::Orientation PlaneMesh::get_orientation() const { + return orientation; +} + +PlaneMesh::PlaneMesh() {} + +/** + PrismMesh +*/ + +void PrismMesh::_update_lightmap_size() { + if (get_add_uv2()) { + // size must have changed, update lightmap size hint + Size2i _lightmap_size_hint; + float texel_size = get_lightmap_texel_size(); + float padding = get_uv2_padding(); + + // left_to_right does not effect the surface area of the prism so we ignore that. + // TODO we could combine the two triangles and save some space but we need to re-align the uv1 and adjust the tangent. + + float width = (size.x + size.z) / texel_size; + float length = (size.y + size.y + size.z) / texel_size; + + _lightmap_size_hint.x = MAX(1.0, width) + 2.0 * padding; + _lightmap_size_hint.y = MAX(1.0, length) + 3.0 * padding; + + set_lightmap_size_hint(_lightmap_size_hint); + } +} + +void PrismMesh::_create_mesh_array(Array &p_arr) const { + int i, j, prevrow, thisrow, point; + float x, y, z; + float onethird = 1.0 / 3.0; + float twothirds = 2.0 / 3.0; + + // Only used if we calculate UV2 + bool _add_uv2 = get_add_uv2(); + float texel_size = get_lightmap_texel_size(); + float _uv2_padding = get_uv2_padding() * texel_size; + + float horizontal_total = size.x + size.z + 2.0 * _uv2_padding; + float width_h = size.x / horizontal_total; + float depth_h = size.z / horizontal_total; + float padding_h = _uv2_padding / horizontal_total; + + float vertical_total = (size.y + size.y + size.z) + (3.0 * _uv2_padding); + float height_v = size.y / vertical_total; + float depth_v = size.z / vertical_total; + float padding_v = _uv2_padding / vertical_total; + + // and start building + + Vector3 start_pos = size * -0.5; + + // set our bounding box + + Vector points; + Vector normals; + Vector tangents; + Vector uvs; + Vector uv2s; + Vector indices; + point = 0; + +#define ADD_TANGENT(m_x, m_y, m_z, m_d) \ + tangents.push_back(m_x); \ + tangents.push_back(m_y); \ + tangents.push_back(m_z); \ + tangents.push_back(m_d); + + /* front + back */ + y = start_pos.y; + thisrow = point; + prevrow = 0; + for (j = 0; j <= (subdivide_h + 1); j++) { + float scale = j / (subdivide_h + 1.0); + float scaled_size_x = size.x * scale; + float start_x = start_pos.x + (1.0 - scale) * size.x * left_to_right; + float offset_front = (1.0 - scale) * onethird * left_to_right; + float offset_back = (1.0 - scale) * onethird * (1.0 - left_to_right); + + float v = j; + float v2 = scale; + v /= 2.0 * (subdivide_h + 1.0); + + x = 0.0; + for (i = 0; i <= (subdivide_w + 1); i++) { + float u = i; + float u2 = i / (subdivide_w + 1.0); + u /= (3.0 * (subdivide_w + 1.0)); + + u *= scale; + + /* front */ + points.push_back(Vector3(start_x + x, -y, -start_pos.z)); // double negative on the Z! + normals.push_back(Vector3(0.0, 0.0, 1.0)); + ADD_TANGENT(1.0, 0.0, 0.0, 1.0); + uvs.push_back(Vector2(offset_front + u, v)); + if (_add_uv2) { + uv2s.push_back(Vector2(u2 * scale * width_h, v2 * height_v)); + } + point++; + + /* back */ + points.push_back(Vector3(start_x + scaled_size_x - x, -y, start_pos.z)); + normals.push_back(Vector3(0.0, 0.0, -1.0)); + ADD_TANGENT(-1.0, 0.0, 0.0, 1.0); + uvs.push_back(Vector2(twothirds + offset_back + u, v)); + if (_add_uv2) { + uv2s.push_back(Vector2(u2 * scale * width_h, height_v + padding_v + v2 * height_v)); + } + point++; + + if (i > 0 && j == 1) { + int i2 = i * 2; + + /* front */ + indices.push_back(prevrow + i2); + indices.push_back(thisrow + i2); + indices.push_back(thisrow + i2 - 2); + + /* back */ + indices.push_back(prevrow + i2 + 1); + indices.push_back(thisrow + i2 + 1); + indices.push_back(thisrow + i2 - 1); + } else if (i > 0 && j > 0) { + int i2 = i * 2; + + /* front */ + indices.push_back(prevrow + i2 - 2); + indices.push_back(prevrow + i2); + indices.push_back(thisrow + i2 - 2); + indices.push_back(prevrow + i2); + indices.push_back(thisrow + i2); + indices.push_back(thisrow + i2 - 2); + + /* back */ + indices.push_back(prevrow + i2 - 1); + indices.push_back(prevrow + i2 + 1); + indices.push_back(thisrow + i2 - 1); + indices.push_back(prevrow + i2 + 1); + indices.push_back(thisrow + i2 + 1); + indices.push_back(thisrow + i2 - 1); + } + + x += scale * size.x / (subdivide_w + 1.0); + } + + y += size.y / (subdivide_h + 1.0); + prevrow = thisrow; + thisrow = point; + } + + /* left + right */ + Vector3 normal_left, normal_right; + + normal_left = Vector3(-size.y, size.x * left_to_right, 0.0); + normal_right = Vector3(size.y, size.x * (1.0 - left_to_right), 0.0); + normal_left.normalize(); + normal_right.normalize(); + + y = start_pos.y; + thisrow = point; + prevrow = 0; + for (j = 0; j <= (subdivide_h + 1); j++) { + float left, right; + float scale = j / (subdivide_h + 1.0); + + left = start_pos.x + (size.x * (1.0 - scale) * left_to_right); + right = left + (size.x * scale); + + float v = j; + float v2 = scale; + v /= 2.0 * (subdivide_h + 1.0); + + z = start_pos.z; + for (i = 0; i <= (subdivide_d + 1); i++) { + float u = i; + float u2 = u / (subdivide_d + 1.0); + u /= (3.0 * (subdivide_d + 1.0)); + + /* right */ + points.push_back(Vector3(right, -y, -z)); + normals.push_back(normal_right); + ADD_TANGENT(0.0, 0.0, -1.0, 1.0); + uvs.push_back(Vector2(onethird + u, v)); + if (_add_uv2) { + uv2s.push_back(Vector2(width_h + padding_h + u2 * depth_h, v2 * height_v)); + } + point++; + + /* left */ + points.push_back(Vector3(left, -y, z)); + normals.push_back(normal_left); + ADD_TANGENT(0.0, 0.0, 1.0, 1.0); + uvs.push_back(Vector2(u, 0.5 + v)); + if (_add_uv2) { + uv2s.push_back(Vector2(width_h + padding_h + u2 * depth_h, height_v + padding_v + v2 * height_v)); + } + point++; + + if (i > 0 && j > 0) { + int i2 = i * 2; + + /* right */ + indices.push_back(prevrow + i2 - 2); + indices.push_back(prevrow + i2); + indices.push_back(thisrow + i2 - 2); + indices.push_back(prevrow + i2); + indices.push_back(thisrow + i2); + indices.push_back(thisrow + i2 - 2); + + /* left */ + indices.push_back(prevrow + i2 - 1); + indices.push_back(prevrow + i2 + 1); + indices.push_back(thisrow + i2 - 1); + indices.push_back(prevrow + i2 + 1); + indices.push_back(thisrow + i2 + 1); + indices.push_back(thisrow + i2 - 1); + } + + z += size.z / (subdivide_d + 1.0); + } + + y += size.y / (subdivide_h + 1.0); + prevrow = thisrow; + thisrow = point; + } + + /* bottom */ + z = start_pos.z; + thisrow = point; + prevrow = 0; + for (j = 0; j <= (subdivide_d + 1); j++) { + float v = j; + float v2 = v / (subdivide_d + 1.0); + v /= (2.0 * (subdivide_d + 1.0)); + + x = start_pos.x; + for (i = 0; i <= (subdivide_w + 1); i++) { + float u = i; + float u2 = u / (subdivide_w + 1.0); + u /= (3.0 * (subdivide_w + 1.0)); + + /* bottom */ + points.push_back(Vector3(x, start_pos.y, -z)); + normals.push_back(Vector3(0.0, -1.0, 0.0)); + ADD_TANGENT(1.0, 0.0, 0.0, 1.0); + uvs.push_back(Vector2(twothirds + u, 0.5 + v)); + if (_add_uv2) { + uv2s.push_back(Vector2(u2 * width_h, 2.0 * (height_v + padding_v) + v2 * depth_v)); + } + point++; + + if (i > 0 && j > 0) { + /* bottom */ + indices.push_back(prevrow + i - 1); + indices.push_back(prevrow + i); + indices.push_back(thisrow + i - 1); + indices.push_back(prevrow + i); + indices.push_back(thisrow + i); + indices.push_back(thisrow + i - 1); + } + + x += size.x / (subdivide_w + 1.0); + } + + z += size.z / (subdivide_d + 1.0); + prevrow = thisrow; + thisrow = point; + } + + p_arr[RS::ARRAY_VERTEX] = points; + p_arr[RS::ARRAY_NORMAL] = normals; + p_arr[RS::ARRAY_TANGENT] = tangents; + p_arr[RS::ARRAY_TEX_UV] = uvs; + if (_add_uv2) { + p_arr[RS::ARRAY_TEX_UV2] = uv2s; + } + p_arr[RS::ARRAY_INDEX] = indices; +} + +void PrismMesh::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_left_to_right", "left_to_right"), &PrismMesh::set_left_to_right); + ClassDB::bind_method(D_METHOD("get_left_to_right"), &PrismMesh::get_left_to_right); + + ClassDB::bind_method(D_METHOD("set_size", "size"), &PrismMesh::set_size); + ClassDB::bind_method(D_METHOD("get_size"), &PrismMesh::get_size); + + ClassDB::bind_method(D_METHOD("set_subdivide_width", "segments"), &PrismMesh::set_subdivide_width); + ClassDB::bind_method(D_METHOD("get_subdivide_width"), &PrismMesh::get_subdivide_width); + ClassDB::bind_method(D_METHOD("set_subdivide_height", "segments"), &PrismMesh::set_subdivide_height); + ClassDB::bind_method(D_METHOD("get_subdivide_height"), &PrismMesh::get_subdivide_height); + ClassDB::bind_method(D_METHOD("set_subdivide_depth", "segments"), &PrismMesh::set_subdivide_depth); + ClassDB::bind_method(D_METHOD("get_subdivide_depth"), &PrismMesh::get_subdivide_depth); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "left_to_right", PROPERTY_HINT_RANGE, "-2.0,2.0,0.1"), "set_left_to_right", "get_left_to_right"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size", PROPERTY_HINT_NONE, "suffix:m"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_width", "get_subdivide_width"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_height", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_height", "get_subdivide_height"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_depth", "get_subdivide_depth"); +} + +void PrismMesh::set_left_to_right(const float p_left_to_right) { + left_to_right = p_left_to_right; + _request_update(); +} + +float PrismMesh::get_left_to_right() const { + return left_to_right; +} + +void PrismMesh::set_size(const Vector3 &p_size) { + size = p_size; + _update_lightmap_size(); + _request_update(); +} + +Vector3 PrismMesh::get_size() const { + return size; +} + +void PrismMesh::set_subdivide_width(const int p_divisions) { + subdivide_w = p_divisions > 0 ? p_divisions : 0; + _request_update(); +} + +int PrismMesh::get_subdivide_width() const { + return subdivide_w; +} + +void PrismMesh::set_subdivide_height(const int p_divisions) { + subdivide_h = p_divisions > 0 ? p_divisions : 0; + _request_update(); +} + +int PrismMesh::get_subdivide_height() const { + return subdivide_h; +} + +void PrismMesh::set_subdivide_depth(const int p_divisions) { + subdivide_d = p_divisions > 0 ? p_divisions : 0; + _request_update(); +} + +int PrismMesh::get_subdivide_depth() const { + return subdivide_d; +} + +PrismMesh::PrismMesh() {} + +/** + SphereMesh +*/ + +void SphereMesh::_update_lightmap_size() { + if (get_add_uv2()) { + // size must have changed, update lightmap size hint + Size2i _lightmap_size_hint; + float texel_size = get_lightmap_texel_size(); + float padding = get_uv2_padding(); + + float _width = radius * Math_TAU; + _lightmap_size_hint.x = MAX(1.0, (_width / texel_size) + padding); + float _height = (is_hemisphere ? 1.0 : 0.5) * height * Math_PI; // note, with hemisphere height is our radius, while with a full sphere it is the diameter.. + _lightmap_size_hint.y = MAX(1.0, (_height / texel_size) + padding); + + set_lightmap_size_hint(_lightmap_size_hint); + } +} + +void SphereMesh::_create_mesh_array(Array &p_arr) const { + bool _add_uv2 = get_add_uv2(); + float texel_size = get_lightmap_texel_size(); + float _uv2_padding = get_uv2_padding() * texel_size; + + create_mesh_array(p_arr, radius, height, radial_segments, rings, is_hemisphere, _add_uv2, _uv2_padding); +} + +void SphereMesh::create_mesh_array(Array &p_arr, float radius, float height, int radial_segments, int rings, bool is_hemisphere, bool p_add_uv2, const float p_uv2_padding) { + int i, j, prevrow, thisrow, point; + float x, y, z; + + float scale = height * (is_hemisphere ? 1.0 : 0.5); + + // Only used if we calculate UV2 + float circumference = radius * Math_TAU; + float horizontal_length = circumference + p_uv2_padding; + float center_h = 0.5 * circumference / horizontal_length; + + float height_v = scale * Math_PI / ((scale * Math_PI) + p_uv2_padding); + + // set our bounding box + + Vector points; + Vector normals; + Vector tangents; + Vector uvs; + Vector uv2s; + Vector indices; + point = 0; + +#define ADD_TANGENT(m_x, m_y, m_z, m_d) \ + tangents.push_back(m_x); \ + tangents.push_back(m_y); \ + tangents.push_back(m_z); \ + tangents.push_back(m_d); + + thisrow = 0; + prevrow = 0; + for (j = 0; j <= (rings + 1); j++) { + float v = j; + float w; + + v /= (rings + 1); + w = sin(Math_PI * v); + y = scale * cos(Math_PI * v); + + for (i = 0; i <= radial_segments; i++) { + float u = i; + u /= radial_segments; + + x = sin(u * Math_TAU); + z = cos(u * Math_TAU); + + if (is_hemisphere && y < 0.0) { + points.push_back(Vector3(x * radius * w, 0.0, z * radius * w)); + normals.push_back(Vector3(0.0, -1.0, 0.0)); + } else { + Vector3 p = Vector3(x * radius * w, y, z * radius * w); + points.push_back(p); + Vector3 normal = Vector3(x * w * scale, radius * (y / scale), z * w * scale); + normals.push_back(normal.normalized()); + } + ADD_TANGENT(z, 0.0, -x, 1.0) + uvs.push_back(Vector2(u, v)); + if (p_add_uv2) { + float w_h = w * 2.0 * center_h; + uv2s.push_back(Vector2(center_h + ((u - 0.5) * w_h), v * height_v)); + } + point++; + + if (i > 0 && j > 0) { + indices.push_back(prevrow + i - 1); + indices.push_back(prevrow + i); + indices.push_back(thisrow + i - 1); + + indices.push_back(prevrow + i); + indices.push_back(thisrow + i); + indices.push_back(thisrow + i - 1); + } + } + + prevrow = thisrow; + thisrow = point; + } + + p_arr[RS::ARRAY_VERTEX] = points; + p_arr[RS::ARRAY_NORMAL] = normals; + p_arr[RS::ARRAY_TANGENT] = tangents; + p_arr[RS::ARRAY_TEX_UV] = uvs; + if (p_add_uv2) { + p_arr[RS::ARRAY_TEX_UV2] = uv2s; + } + p_arr[RS::ARRAY_INDEX] = indices; +} + +void SphereMesh::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &SphereMesh::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &SphereMesh::get_radius); + ClassDB::bind_method(D_METHOD("set_height", "height"), &SphereMesh::set_height); + ClassDB::bind_method(D_METHOD("get_height"), &SphereMesh::get_height); + + ClassDB::bind_method(D_METHOD("set_radial_segments", "radial_segments"), &SphereMesh::set_radial_segments); + ClassDB::bind_method(D_METHOD("get_radial_segments"), &SphereMesh::get_radial_segments); + ClassDB::bind_method(D_METHOD("set_rings", "rings"), &SphereMesh::set_rings); + ClassDB::bind_method(D_METHOD("get_rings"), &SphereMesh::get_rings); + + ClassDB::bind_method(D_METHOD("set_is_hemisphere", "is_hemisphere"), &SphereMesh::set_is_hemisphere); + ClassDB::bind_method(D_METHOD("get_is_hemisphere"), &SphereMesh::get_is_hemisphere); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater,suffix:m"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater,suffix:m"), "set_height", "get_height"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_radial_segments", "get_radial_segments"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_rings", "get_rings"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "is_hemisphere"), "set_is_hemisphere", "get_is_hemisphere"); +} + +void SphereMesh::set_radius(const float p_radius) { + radius = p_radius; + _update_lightmap_size(); + _request_update(); +} + +float SphereMesh::get_radius() const { + return radius; +} + +void SphereMesh::set_height(const float p_height) { + height = p_height; + _update_lightmap_size(); + _request_update(); +} + +float SphereMesh::get_height() const { + return height; +} + +void SphereMesh::set_radial_segments(const int p_radial_segments) { + radial_segments = p_radial_segments > 4 ? p_radial_segments : 4; + _request_update(); +} + +int SphereMesh::get_radial_segments() const { + return radial_segments; +} + +void SphereMesh::set_rings(const int p_rings) { + ERR_FAIL_COND(p_rings < 1); + rings = p_rings; + _request_update(); +} + +int SphereMesh::get_rings() const { + return rings; +} + +void SphereMesh::set_is_hemisphere(const bool p_is_hemisphere) { + is_hemisphere = p_is_hemisphere; + _update_lightmap_size(); + _request_update(); +} + +bool SphereMesh::get_is_hemisphere() const { + return is_hemisphere; +} + +SphereMesh::SphereMesh() {} + +/** + TorusMesh +*/ + +void TorusMesh::_update_lightmap_size() { + if (get_add_uv2()) { + // size must have changed, update lightmap size hint + Size2i _lightmap_size_hint; + float texel_size = get_lightmap_texel_size(); + float padding = get_uv2_padding(); + + float min_radius = inner_radius; + float max_radius = outer_radius; + + if (min_radius > max_radius) { + SWAP(min_radius, max_radius); + } + + float radius = (max_radius - min_radius) * 0.5; + + float _width = max_radius * Math_TAU; + _lightmap_size_hint.x = MAX(1.0, (_width / texel_size) + padding); + float _height = radius * Math_TAU; + _lightmap_size_hint.y = MAX(1.0, (_height / texel_size) + padding); + + set_lightmap_size_hint(_lightmap_size_hint); + } +} + +void TorusMesh::_create_mesh_array(Array &p_arr) const { + // set our bounding box + + Vector points; + Vector normals; + Vector tangents; + Vector uvs; + Vector uv2s; + Vector indices; + +#define ADD_TANGENT(m_x, m_y, m_z, m_d) \ + tangents.push_back(m_x); \ + tangents.push_back(m_y); \ + tangents.push_back(m_z); \ + tangents.push_back(m_d); + + ERR_FAIL_COND_MSG(inner_radius == outer_radius, "Inner radius and outer radius cannot be the same."); + + float min_radius = inner_radius; + float max_radius = outer_radius; + + if (min_radius > max_radius) { + SWAP(min_radius, max_radius); + } + + float radius = (max_radius - min_radius) * 0.5; + + // Only used if we calculate UV2 + bool _add_uv2 = get_add_uv2(); + float texel_size = get_lightmap_texel_size(); + float _uv2_padding = get_uv2_padding() * texel_size; + + float horizontal_total = max_radius * Math_TAU + _uv2_padding; + float max_h = max_radius * Math_TAU / horizontal_total; + float delta_h = (max_radius - min_radius) * Math_TAU / horizontal_total; + + float height_v = radius * Math_TAU / (radius * Math_TAU + _uv2_padding); + + for (int i = 0; i <= rings; i++) { + int prevrow = (i - 1) * (ring_segments + 1); + int thisrow = i * (ring_segments + 1); + float inci = float(i) / rings; + float angi = inci * Math_TAU; + + Vector2 normali = Vector2(-Math::sin(angi), -Math::cos(angi)); + + for (int j = 0; j <= ring_segments; j++) { + float incj = float(j) / ring_segments; + float angj = incj * Math_TAU; + + Vector2 normalj = Vector2(-Math::cos(angj), Math::sin(angj)); + Vector2 normalk = normalj * radius + Vector2(min_radius + radius, 0); + + float offset_h = 0.5 * (1.0 - normalj.x) * delta_h; + float adj_h = max_h - offset_h; + offset_h *= 0.5; + + points.push_back(Vector3(normali.x * normalk.x, normalk.y, normali.y * normalk.x)); + normals.push_back(Vector3(normali.x * normalj.x, normalj.y, normali.y * normalj.x)); + ADD_TANGENT(-Math::cos(angi), 0.0, Math::sin(angi), 1.0); + uvs.push_back(Vector2(inci, incj)); + if (_add_uv2) { + uv2s.push_back(Vector2(offset_h + inci * adj_h, incj * height_v)); + } + + if (i > 0 && j > 0) { + indices.push_back(thisrow + j - 1); + indices.push_back(prevrow + j); + indices.push_back(prevrow + j - 1); + + indices.push_back(thisrow + j - 1); + indices.push_back(thisrow + j); + indices.push_back(prevrow + j); + } + } + } + + p_arr[RS::ARRAY_VERTEX] = points; + p_arr[RS::ARRAY_NORMAL] = normals; + p_arr[RS::ARRAY_TANGENT] = tangents; + p_arr[RS::ARRAY_TEX_UV] = uvs; + if (_add_uv2) { + p_arr[RS::ARRAY_TEX_UV2] = uv2s; + } + p_arr[RS::ARRAY_INDEX] = indices; +} + +void TorusMesh::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_inner_radius", "radius"), &TorusMesh::set_inner_radius); + ClassDB::bind_method(D_METHOD("get_inner_radius"), &TorusMesh::get_inner_radius); + + ClassDB::bind_method(D_METHOD("set_outer_radius", "radius"), &TorusMesh::set_outer_radius); + ClassDB::bind_method(D_METHOD("get_outer_radius"), &TorusMesh::get_outer_radius); + + ClassDB::bind_method(D_METHOD("set_rings", "rings"), &TorusMesh::set_rings); + ClassDB::bind_method(D_METHOD("get_rings"), &TorusMesh::get_rings); + + ClassDB::bind_method(D_METHOD("set_ring_segments", "rings"), &TorusMesh::set_ring_segments); + ClassDB::bind_method(D_METHOD("get_ring_segments"), &TorusMesh::get_ring_segments); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "inner_radius", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001,or_greater,exp"), "set_inner_radius", "get_inner_radius"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "outer_radius", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001,or_greater,exp"), "set_outer_radius", "get_outer_radius"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "3,128,1,or_greater"), "set_rings", "get_rings"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "ring_segments", PROPERTY_HINT_RANGE, "3,64,1,or_greater"), "set_ring_segments", "get_ring_segments"); +} + +void TorusMesh::set_inner_radius(const float p_inner_radius) { + inner_radius = p_inner_radius; + _request_update(); +} + +float TorusMesh::get_inner_radius() const { + return inner_radius; +} + +void TorusMesh::set_outer_radius(const float p_outer_radius) { + outer_radius = p_outer_radius; + _request_update(); +} + +float TorusMesh::get_outer_radius() const { + return outer_radius; +} + +void TorusMesh::set_rings(const int p_rings) { + ERR_FAIL_COND(p_rings < 3); + rings = p_rings; + _request_update(); +} + +int TorusMesh::get_rings() const { + return rings; +} + +void TorusMesh::set_ring_segments(const int p_ring_segments) { + ERR_FAIL_COND(p_ring_segments < 3); + ring_segments = p_ring_segments; + _request_update(); +} + +int TorusMesh::get_ring_segments() const { + return ring_segments; +} + +TorusMesh::TorusMesh() {} + +/** + PointMesh +*/ + +void PointMesh::_create_mesh_array(Array &p_arr) const { + Vector faces; + faces.resize(1); + faces.set(0, Vector3(0.0, 0.0, 0.0)); + + p_arr[RS::ARRAY_VERTEX] = faces; +} + +PointMesh::PointMesh() { + primitive_type = PRIMITIVE_POINTS; +} +// TUBE TRAIL + +void TubeTrailMesh::set_radius(const float p_radius) { + radius = p_radius; + _request_update(); +} +float TubeTrailMesh::get_radius() const { + return radius; +} + +void TubeTrailMesh::set_radial_steps(const int p_radial_steps) { + ERR_FAIL_COND(p_radial_steps < 3 || p_radial_steps > 128); + radial_steps = p_radial_steps; + _request_update(); +} +int TubeTrailMesh::get_radial_steps() const { + return radial_steps; +} + +void TubeTrailMesh::set_sections(const int p_sections) { + ERR_FAIL_COND(p_sections < 2 || p_sections > 128); + sections = p_sections; + _request_update(); +} +int TubeTrailMesh::get_sections() const { + return sections; +} + +void TubeTrailMesh::set_section_length(float p_section_length) { + section_length = p_section_length; + _request_update(); +} +float TubeTrailMesh::get_section_length() const { + return section_length; +} + +void TubeTrailMesh::set_section_rings(const int p_section_rings) { + ERR_FAIL_COND(p_section_rings < 1 || p_section_rings > 1024); + section_rings = p_section_rings; + _request_update(); +} +int TubeTrailMesh::get_section_rings() const { + return section_rings; +} + +void TubeTrailMesh::set_cap_top(bool p_cap_top) { + cap_top = p_cap_top; + _request_update(); +} + +bool TubeTrailMesh::is_cap_top() const { + return cap_top; +} + +void TubeTrailMesh::set_cap_bottom(bool p_cap_bottom) { + cap_bottom = p_cap_bottom; + _request_update(); +} + +bool TubeTrailMesh::is_cap_bottom() const { + return cap_bottom; +} + +void TubeTrailMesh::set_curve(const Ref &p_curve) { + if (curve == p_curve) { + return; + } + if (curve.is_valid()) { + curve->disconnect_changed(callable_mp(this, &TubeTrailMesh::_curve_changed)); + } + curve = p_curve; + if (curve.is_valid()) { + curve->connect_changed(callable_mp(this, &TubeTrailMesh::_curve_changed)); + } + _request_update(); +} +Ref TubeTrailMesh::get_curve() const { + return curve; +} + +void TubeTrailMesh::_curve_changed() { + _request_update(); +} +int TubeTrailMesh::get_builtin_bind_pose_count() const { + return sections + 1; +} + +Transform3D TubeTrailMesh::get_builtin_bind_pose(int p_index) const { + float depth = section_length * sections; + + Transform3D xform; + xform.origin.y = depth / 2.0 - section_length * float(p_index); + xform.origin.y = -xform.origin.y; //bind is an inverse transform, so negate y + + return xform; +} + +void TubeTrailMesh::_create_mesh_array(Array &p_arr) const { + // Seeing use case for TubeTrailMesh, no need to do anything more then default UV2 calculation + + PackedVector3Array points; + PackedVector3Array normals; + PackedFloat32Array tangents; + PackedVector2Array uvs; + PackedInt32Array bone_indices; + PackedFloat32Array bone_weights; + PackedInt32Array indices; + + int point = 0; + +#define ADD_TANGENT(m_x, m_y, m_z, m_d) \ + tangents.push_back(m_x); \ + tangents.push_back(m_y); \ + tangents.push_back(m_z); \ + tangents.push_back(m_d); + + int thisrow = 0; + int prevrow = 0; + + int total_rings = section_rings * sections; + float depth = section_length * sections; + + for (int j = 0; j <= total_rings; j++) { + float v = j; + v /= total_rings; + + float y = depth * v; + y = (depth * 0.5) - y; + + int bone = j / section_rings; + float blend = 1.0 - float(j % section_rings) / float(section_rings); + + for (int i = 0; i <= radial_steps; i++) { + float u = i; + u /= radial_steps; + + float r = radius; + if (curve.is_valid() && curve->get_point_count() > 0) { + r *= curve->sample_baked(v); + } + float x = sin(u * Math_TAU); + float z = cos(u * Math_TAU); + + Vector3 p = Vector3(x * r, y, z * r); + points.push_back(p); + normals.push_back(Vector3(x, 0, z)); + ADD_TANGENT(z, 0.0, -x, 1.0) + uvs.push_back(Vector2(u, v * 0.5)); + point++; + { + bone_indices.push_back(bone); + bone_indices.push_back(MIN(sections, bone + 1)); + bone_indices.push_back(0); + bone_indices.push_back(0); + + bone_weights.push_back(blend); + bone_weights.push_back(1.0 - blend); + bone_weights.push_back(0); + bone_weights.push_back(0); + } + + if (i > 0 && j > 0) { + indices.push_back(prevrow + i - 1); + indices.push_back(prevrow + i); + indices.push_back(thisrow + i - 1); + + indices.push_back(prevrow + i); + indices.push_back(thisrow + i); + indices.push_back(thisrow + i - 1); + } + } + + prevrow = thisrow; + thisrow = point; + } + + if (cap_top) { + // add top + float scale_pos = 1.0; + if (curve.is_valid() && curve->get_point_count() > 0) { + scale_pos = curve->sample_baked(0); + } + + if (scale_pos > CMP_EPSILON) { + float y = depth * 0.5; + + thisrow = point; + points.push_back(Vector3(0.0, y, 0)); + normals.push_back(Vector3(0.0, 1.0, 0.0)); + ADD_TANGENT(1.0, 0.0, 0.0, 1.0) + uvs.push_back(Vector2(0.25, 0.75)); + point++; + + bone_indices.push_back(0); + bone_indices.push_back(0); + bone_indices.push_back(0); + bone_indices.push_back(0); + + bone_weights.push_back(1.0); + bone_weights.push_back(0); + bone_weights.push_back(0); + bone_weights.push_back(0); + + float rm = radius * scale_pos; + + for (int i = 0; i <= radial_steps; i++) { + float r = i; + r /= radial_steps; + + float x = sin(r * Math_TAU); + float z = cos(r * Math_TAU); + + float u = ((x + 1.0) * 0.25); + float v = 0.5 + ((z + 1.0) * 0.25); + + Vector3 p = Vector3(x * rm, y, z * rm); + points.push_back(p); + normals.push_back(Vector3(0.0, 1.0, 0.0)); + ADD_TANGENT(1.0, 0.0, 0.0, 1.0) + uvs.push_back(Vector2(u, v)); + point++; + + bone_indices.push_back(0); + bone_indices.push_back(0); + bone_indices.push_back(0); + bone_indices.push_back(0); + + bone_weights.push_back(1.0); + bone_weights.push_back(0); + bone_weights.push_back(0); + bone_weights.push_back(0); + + if (i > 0) { + indices.push_back(thisrow); + indices.push_back(point - 1); + indices.push_back(point - 2); + } + } + } + } + + if (cap_bottom) { + float scale_neg = 1.0; + if (curve.is_valid() && curve->get_point_count() > 0) { + scale_neg = curve->sample_baked(1.0); + } + + if (scale_neg > CMP_EPSILON) { + // add bottom + float y = depth * -0.5; + + thisrow = point; + points.push_back(Vector3(0.0, y, 0.0)); + normals.push_back(Vector3(0.0, -1.0, 0.0)); + ADD_TANGENT(1.0, 0.0, 0.0, 1.0) + uvs.push_back(Vector2(0.75, 0.75)); + point++; + + bone_indices.push_back(sections); + bone_indices.push_back(0); + bone_indices.push_back(0); + bone_indices.push_back(0); + + bone_weights.push_back(1.0); + bone_weights.push_back(0); + bone_weights.push_back(0); + bone_weights.push_back(0); + + float rm = radius * scale_neg; + + for (int i = 0; i <= radial_steps; i++) { + float r = i; + r /= radial_steps; + + float x = sin(r * Math_TAU); + float z = cos(r * Math_TAU); + + float u = 0.5 + ((x + 1.0) * 0.25); + float v = 1.0 - ((z + 1.0) * 0.25); + + Vector3 p = Vector3(x * rm, y, z * rm); + points.push_back(p); + normals.push_back(Vector3(0.0, -1.0, 0.0)); + ADD_TANGENT(1.0, 0.0, 0.0, 1.0) + uvs.push_back(Vector2(u, v)); + point++; + + bone_indices.push_back(sections); + bone_indices.push_back(0); + bone_indices.push_back(0); + bone_indices.push_back(0); + + bone_weights.push_back(1.0); + bone_weights.push_back(0); + bone_weights.push_back(0); + bone_weights.push_back(0); + + if (i > 0) { + indices.push_back(thisrow); + indices.push_back(point - 2); + indices.push_back(point - 1); + } + } + } + } + + p_arr[RS::ARRAY_VERTEX] = points; + p_arr[RS::ARRAY_NORMAL] = normals; + p_arr[RS::ARRAY_TANGENT] = tangents; + p_arr[RS::ARRAY_TEX_UV] = uvs; + p_arr[RS::ARRAY_BONES] = bone_indices; + p_arr[RS::ARRAY_WEIGHTS] = bone_weights; + p_arr[RS::ARRAY_INDEX] = indices; +} + +void TubeTrailMesh::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &TubeTrailMesh::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &TubeTrailMesh::get_radius); + + ClassDB::bind_method(D_METHOD("set_radial_steps", "radial_steps"), &TubeTrailMesh::set_radial_steps); + ClassDB::bind_method(D_METHOD("get_radial_steps"), &TubeTrailMesh::get_radial_steps); + + ClassDB::bind_method(D_METHOD("set_sections", "sections"), &TubeTrailMesh::set_sections); + ClassDB::bind_method(D_METHOD("get_sections"), &TubeTrailMesh::get_sections); + + ClassDB::bind_method(D_METHOD("set_section_length", "section_length"), &TubeTrailMesh::set_section_length); + ClassDB::bind_method(D_METHOD("get_section_length"), &TubeTrailMesh::get_section_length); + + ClassDB::bind_method(D_METHOD("set_section_rings", "section_rings"), &TubeTrailMesh::set_section_rings); + ClassDB::bind_method(D_METHOD("get_section_rings"), &TubeTrailMesh::get_section_rings); + + ClassDB::bind_method(D_METHOD("set_cap_top", "cap_top"), &TubeTrailMesh::set_cap_top); + ClassDB::bind_method(D_METHOD("is_cap_top"), &TubeTrailMesh::is_cap_top); + + ClassDB::bind_method(D_METHOD("set_cap_bottom", "cap_bottom"), &TubeTrailMesh::set_cap_bottom); + ClassDB::bind_method(D_METHOD("is_cap_bottom"), &TubeTrailMesh::is_cap_bottom); + + ClassDB::bind_method(D_METHOD("set_curve", "curve"), &TubeTrailMesh::set_curve); + ClassDB::bind_method(D_METHOD("get_curve"), &TubeTrailMesh::get_curve); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater,suffix:m"), "set_radius", "get_radius"); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_steps", PROPERTY_HINT_RANGE, "3,128,1"), "set_radial_steps", "get_radial_steps"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "sections", PROPERTY_HINT_RANGE, "2,128,1"), "set_sections", "get_sections"); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "section_length", PROPERTY_HINT_RANGE, "0.001,1024.0,0.001,or_greater,suffix:m"), "set_section_length", "get_section_length"); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "section_rings", PROPERTY_HINT_RANGE, "1,128,1"), "set_section_rings", "get_section_rings"); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cap_top"), "set_cap_top", "is_cap_top"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cap_bottom"), "set_cap_bottom", "is_cap_bottom"); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve", "get_curve"); +} + +TubeTrailMesh::TubeTrailMesh() { +} + +// RIBBON TRAIL + +void RibbonTrailMesh::set_shape(Shape p_shape) { + shape = p_shape; + _request_update(); +} +RibbonTrailMesh::Shape RibbonTrailMesh::get_shape() const { + return shape; +} + +void RibbonTrailMesh::set_size(const float p_size) { + size = p_size; + _request_update(); +} +float RibbonTrailMesh::get_size() const { + return size; +} + +void RibbonTrailMesh::set_sections(const int p_sections) { + ERR_FAIL_COND(p_sections < 2 || p_sections > 128); + sections = p_sections; + _request_update(); +} +int RibbonTrailMesh::get_sections() const { + return sections; +} + +void RibbonTrailMesh::set_section_length(float p_section_length) { + section_length = p_section_length; + _request_update(); +} +float RibbonTrailMesh::get_section_length() const { + return section_length; +} + +void RibbonTrailMesh::set_section_segments(const int p_section_segments) { + ERR_FAIL_COND(p_section_segments < 1 || p_section_segments > 1024); + section_segments = p_section_segments; + _request_update(); +} +int RibbonTrailMesh::get_section_segments() const { + return section_segments; +} + +void RibbonTrailMesh::set_curve(const Ref &p_curve) { + if (curve == p_curve) { + return; + } + if (curve.is_valid()) { + curve->disconnect_changed(callable_mp(this, &RibbonTrailMesh::_curve_changed)); + } + curve = p_curve; + if (curve.is_valid()) { + curve->connect_changed(callable_mp(this, &RibbonTrailMesh::_curve_changed)); + } + _request_update(); +} +Ref RibbonTrailMesh::get_curve() const { + return curve; +} + +void RibbonTrailMesh::_curve_changed() { + _request_update(); +} +int RibbonTrailMesh::get_builtin_bind_pose_count() const { + return sections + 1; +} + +Transform3D RibbonTrailMesh::get_builtin_bind_pose(int p_index) const { + float depth = section_length * sections; + + Transform3D xform; + xform.origin.y = depth / 2.0 - section_length * float(p_index); + xform.origin.y = -xform.origin.y; //bind is an inverse transform, so negate y + + return xform; +} + +void RibbonTrailMesh::_create_mesh_array(Array &p_arr) const { + // Seeing use case of ribbon trail mesh, no need to implement special UV2 calculation + + PackedVector3Array points; + PackedVector3Array normals; + PackedFloat32Array tangents; + PackedVector2Array uvs; + PackedInt32Array bone_indices; + PackedFloat32Array bone_weights; + PackedInt32Array indices; + +#define ADD_TANGENT(m_x, m_y, m_z, m_d) \ + tangents.push_back(m_x); \ + tangents.push_back(m_y); \ + tangents.push_back(m_z); \ + tangents.push_back(m_d); + + int total_segments = section_segments * sections; + float depth = section_length * sections; + + for (int j = 0; j <= total_segments; j++) { + float v = j; + v /= total_segments; + + float y = depth * v; + y = (depth * 0.5) - y; + + int bone = j / section_segments; + float blend = 1.0 - float(j % section_segments) / float(section_segments); + + float s = size; + + if (curve.is_valid() && curve->get_point_count() > 0) { + s *= curve->sample_baked(v); + } + + points.push_back(Vector3(-s * 0.5, y, 0)); + points.push_back(Vector3(+s * 0.5, y, 0)); + if (shape == SHAPE_CROSS) { + points.push_back(Vector3(0, y, -s * 0.5)); + points.push_back(Vector3(0, y, +s * 0.5)); + } + + normals.push_back(Vector3(0, 0, 1)); + normals.push_back(Vector3(0, 0, 1)); + if (shape == SHAPE_CROSS) { + normals.push_back(Vector3(1, 0, 0)); + normals.push_back(Vector3(1, 0, 0)); + } + + uvs.push_back(Vector2(0, v)); + uvs.push_back(Vector2(1, v)); + if (shape == SHAPE_CROSS) { + uvs.push_back(Vector2(0, v)); + uvs.push_back(Vector2(1, v)); + } + + ADD_TANGENT(0.0, 1.0, 0.0, 1.0) + ADD_TANGENT(0.0, 1.0, 0.0, 1.0) + if (shape == SHAPE_CROSS) { + ADD_TANGENT(0.0, 1.0, 0.0, 1.0) + ADD_TANGENT(0.0, 1.0, 0.0, 1.0) + } + + for (int i = 0; i < (shape == SHAPE_CROSS ? 4 : 2); i++) { + bone_indices.push_back(bone); + bone_indices.push_back(MIN(sections, bone + 1)); + bone_indices.push_back(0); + bone_indices.push_back(0); + + bone_weights.push_back(blend); + bone_weights.push_back(1.0 - blend); + bone_weights.push_back(0); + bone_weights.push_back(0); + } + + if (j > 0) { + if (shape == SHAPE_CROSS) { + int base = j * 4 - 4; + indices.push_back(base + 0); + indices.push_back(base + 1); + indices.push_back(base + 4); + + indices.push_back(base + 1); + indices.push_back(base + 5); + indices.push_back(base + 4); + + indices.push_back(base + 2); + indices.push_back(base + 3); + indices.push_back(base + 6); + + indices.push_back(base + 3); + indices.push_back(base + 7); + indices.push_back(base + 6); + } else { + int base = j * 2 - 2; + indices.push_back(base + 0); + indices.push_back(base + 1); + indices.push_back(base + 2); + + indices.push_back(base + 1); + indices.push_back(base + 3); + indices.push_back(base + 2); + } + } + } + + p_arr[RS::ARRAY_VERTEX] = points; + p_arr[RS::ARRAY_NORMAL] = normals; + p_arr[RS::ARRAY_TANGENT] = tangents; + p_arr[RS::ARRAY_TEX_UV] = uvs; + p_arr[RS::ARRAY_BONES] = bone_indices; + p_arr[RS::ARRAY_WEIGHTS] = bone_weights; + p_arr[RS::ARRAY_INDEX] = indices; +} + +void RibbonTrailMesh::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_size", "size"), &RibbonTrailMesh::set_size); + ClassDB::bind_method(D_METHOD("get_size"), &RibbonTrailMesh::get_size); + + ClassDB::bind_method(D_METHOD("set_sections", "sections"), &RibbonTrailMesh::set_sections); + ClassDB::bind_method(D_METHOD("get_sections"), &RibbonTrailMesh::get_sections); + + ClassDB::bind_method(D_METHOD("set_section_length", "section_length"), &RibbonTrailMesh::set_section_length); + ClassDB::bind_method(D_METHOD("get_section_length"), &RibbonTrailMesh::get_section_length); + + ClassDB::bind_method(D_METHOD("set_section_segments", "section_segments"), &RibbonTrailMesh::set_section_segments); + ClassDB::bind_method(D_METHOD("get_section_segments"), &RibbonTrailMesh::get_section_segments); + + ClassDB::bind_method(D_METHOD("set_curve", "curve"), &RibbonTrailMesh::set_curve); + ClassDB::bind_method(D_METHOD("get_curve"), &RibbonTrailMesh::get_curve); + + ClassDB::bind_method(D_METHOD("set_shape", "shape"), &RibbonTrailMesh::set_shape); + ClassDB::bind_method(D_METHOD("get_shape"), &RibbonTrailMesh::get_shape); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "shape", PROPERTY_HINT_ENUM, "Flat,Cross"), "set_shape", "get_shape"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "size", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater,suffix:m"), "set_size", "get_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "sections", PROPERTY_HINT_RANGE, "2,128,1"), "set_sections", "get_sections"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "section_length", PROPERTY_HINT_RANGE, "0.001,1024.0,0.001,or_greater,suffix:m"), "set_section_length", "get_section_length"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "section_segments", PROPERTY_HINT_RANGE, "1,128,1"), "set_section_segments", "get_section_segments"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve", "get_curve"); + + BIND_ENUM_CONSTANT(SHAPE_FLAT) + BIND_ENUM_CONSTANT(SHAPE_CROSS) +} + +RibbonTrailMesh::RibbonTrailMesh() { +} + +/*************************************************************************/ +/* TextMesh */ +/*************************************************************************/ + +void TextMesh::_generate_glyph_mesh_data(const GlyphMeshKey &p_key, const Glyph &p_gl) const { + if (cache.has(p_key)) { + return; + } + + GlyphMeshData &gl_data = cache[p_key]; + + Dictionary d = TS->font_get_glyph_contours(p_gl.font_rid, p_gl.font_size, p_gl.index); + + PackedVector3Array points = d["points"]; + PackedInt32Array contours = d["contours"]; + bool orientation = d["orientation"]; + + if (points.size() < 3 || contours.size() < 1) { + return; // No full contours, only glyph control points (or nothing), ignore. + } + + // Approximate Bezier curves as polygons. + // See https://freetype.org/freetype2/docs/glyphs/glyphs-6.html, for more info. + for (int i = 0; i < contours.size(); i++) { + int32_t start = (i == 0) ? 0 : (contours[i - 1] + 1); + int32_t end = contours[i]; + Vector polygon; + + for (int32_t j = start; j <= end; j++) { + if (points[j].z == TextServer::CONTOUR_CURVE_TAG_ON) { + // Point on the curve. + Vector2 p = Vector2(points[j].x, points[j].y) * pixel_size; + polygon.push_back(ContourPoint(p, true)); + } else if (points[j].z == TextServer::CONTOUR_CURVE_TAG_OFF_CONIC) { + // Conic Bezier arc. + int32_t next = (j == end) ? start : (j + 1); + int32_t prev = (j == start) ? end : (j - 1); + Vector2 p0; + Vector2 p1 = Vector2(points[j].x, points[j].y); + Vector2 p2; + + // For successive conic OFF points add a virtual ON point in the middle. + if (points[prev].z == TextServer::CONTOUR_CURVE_TAG_OFF_CONIC) { + p0 = (Vector2(points[prev].x, points[prev].y) + Vector2(points[j].x, points[j].y)) / 2.0; + } else if (points[prev].z == TextServer::CONTOUR_CURVE_TAG_ON) { + p0 = Vector2(points[prev].x, points[prev].y); + } else { + ERR_FAIL_MSG(vformat("Invalid conic arc point sequence at %d:%d", i, j)); + } + if (points[next].z == TextServer::CONTOUR_CURVE_TAG_OFF_CONIC) { + p2 = (Vector2(points[j].x, points[j].y) + Vector2(points[next].x, points[next].y)) / 2.0; + } else if (points[next].z == TextServer::CONTOUR_CURVE_TAG_ON) { + p2 = Vector2(points[next].x, points[next].y); + } else { + ERR_FAIL_MSG(vformat("Invalid conic arc point sequence at %d:%d", i, j)); + } + + real_t step = CLAMP(curve_step / (p0 - p2).length(), 0.01, 0.5); + real_t t = step; + while (t < 1.0) { + real_t omt = (1.0 - t); + real_t omt2 = omt * omt; + real_t t2 = t * t; + + Vector2 point = p1 + omt2 * (p0 - p1) + t2 * (p2 - p1); + Vector2 p = point * pixel_size; + polygon.push_back(ContourPoint(p, false)); + t += step; + } + } else if (points[j].z == TextServer::CONTOUR_CURVE_TAG_OFF_CUBIC) { + // Cubic Bezier arc. + int32_t cur = j; + int32_t next1 = (j == end) ? start : (j + 1); + int32_t next2 = (next1 == end) ? start : (next1 + 1); + int32_t prev = (j == start) ? end : (j - 1); + + // There must be exactly two OFF points and two ON points for each cubic arc. + if (points[prev].z != TextServer::CONTOUR_CURVE_TAG_ON) { + cur = (cur == 0) ? end : cur - 1; + next1 = (next1 == 0) ? end : next1 - 1; + next2 = (next2 == 0) ? end : next2 - 1; + prev = (prev == 0) ? end : prev - 1; + } else { + j++; + } + ERR_FAIL_COND_MSG(points[prev].z != TextServer::CONTOUR_CURVE_TAG_ON, vformat("Invalid cubic arc point sequence at %d:%d", i, prev)); + ERR_FAIL_COND_MSG(points[cur].z != TextServer::CONTOUR_CURVE_TAG_OFF_CUBIC, vformat("Invalid cubic arc point sequence at %d:%d", i, cur)); + ERR_FAIL_COND_MSG(points[next1].z != TextServer::CONTOUR_CURVE_TAG_OFF_CUBIC, vformat("Invalid cubic arc point sequence at %d:%d", i, next1)); + ERR_FAIL_COND_MSG(points[next2].z != TextServer::CONTOUR_CURVE_TAG_ON, vformat("Invalid cubic arc point sequence at %d:%d", i, next2)); + + Vector2 p0 = Vector2(points[prev].x, points[prev].y); + Vector2 p1 = Vector2(points[cur].x, points[cur].y); + Vector2 p2 = Vector2(points[next1].x, points[next1].y); + Vector2 p3 = Vector2(points[next2].x, points[next2].y); + + real_t step = CLAMP(curve_step / (p0 - p3).length(), 0.01, 0.5); + real_t t = step; + while (t < 1.0) { + Vector2 point = p0.bezier_interpolate(p1, p2, p3, t); + Vector2 p = point * pixel_size; + polygon.push_back(ContourPoint(p, false)); + t += step; + } + } else { + ERR_FAIL_MSG(vformat("Unknown point tag at %d:%d", i, j)); + } + } + + if (polygon.size() < 3) { + continue; // Skip glyph control points. + } + + if (!orientation) { + polygon.reverse(); + } + + gl_data.contours.push_back(polygon); + } + + // Calculate bounds. + List in_poly; + for (int i = 0; i < gl_data.contours.size(); i++) { + TPPLPoly inp; + inp.Init(gl_data.contours[i].size()); + real_t length = 0.0; + for (int j = 0; j < gl_data.contours[i].size(); j++) { + int next = (j + 1 == gl_data.contours[i].size()) ? 0 : (j + 1); + + gl_data.min_p.x = MIN(gl_data.min_p.x, gl_data.contours[i][j].point.x); + gl_data.min_p.y = MIN(gl_data.min_p.y, gl_data.contours[i][j].point.y); + gl_data.max_p.x = MAX(gl_data.max_p.x, gl_data.contours[i][j].point.x); + gl_data.max_p.y = MAX(gl_data.max_p.y, gl_data.contours[i][j].point.y); + length += (gl_data.contours[i][next].point - gl_data.contours[i][j].point).length(); + + inp.GetPoint(j) = gl_data.contours[i][j].point; + } + TPPLOrientation poly_orient = inp.GetOrientation(); + if (poly_orient == TPPL_ORIENTATION_CW) { + inp.SetHole(true); + } + in_poly.push_back(inp); + gl_data.contours_info.push_back(ContourInfo(length, poly_orient == TPPL_ORIENTATION_CCW)); + } + + TPPLPartition tpart; + + //Decompose and triangulate. + List out_poly; + if (tpart.ConvexPartition_HM(&in_poly, &out_poly) == 0) { + ERR_FAIL_MSG("Convex decomposing failed. Make sure the font doesn't contain self-intersecting lines, as these are not supported in TextMesh."); + } + List out_tris; + for (List::Element *I = out_poly.front(); I; I = I->next()) { + if (tpart.Triangulate_OPT(&(I->get()), &out_tris) == 0) { + ERR_FAIL_MSG("Triangulation failed. Make sure the font doesn't contain self-intersecting lines, as these are not supported in TextMesh."); + } + } + + for (List::Element *I = out_tris.front(); I; I = I->next()) { + TPPLPoly &tp = I->get(); + ERR_FAIL_COND(tp.GetNumPoints() != 3); // Triangles only. + + for (int i = 0; i < 3; i++) { + gl_data.triangles.push_back(Vector2(tp.GetPoint(i).x, tp.GetPoint(i).y)); + } + } +} + +void TextMesh::_create_mesh_array(Array &p_arr) const { + Ref font = _get_font_or_default(); + ERR_FAIL_COND(font.is_null()); + + if (dirty_cache) { + cache.clear(); + dirty_cache = false; + } + + // When a shaped text is invalidated by an external source, we want to reshape it. + if (!TS->shaped_text_is_ready(text_rid)) { + dirty_text = true; + } + + for (const RID &line_rid : lines_rid) { + if (!TS->shaped_text_is_ready(line_rid)) { + dirty_lines = true; + break; + } + } + + // Update text buffer. + if (dirty_text) { + TS->shaped_text_clear(text_rid); + TS->shaped_text_set_direction(text_rid, text_direction); + + String txt = (uppercase) ? TS->string_to_upper(xl_text, language) : xl_text; + TS->shaped_text_add_string(text_rid, txt, font->get_rids(), font_size, font->get_opentype_features(), language); + + TypedArray stt; + if (st_parser == TextServer::STRUCTURED_TEXT_CUSTOM) { + GDVIRTUAL_CALL(_structured_text_parser, st_args, txt, stt); + } else { + stt = TS->parse_structured_text(st_parser, st_args, txt); + } + TS->shaped_text_set_bidi_override(text_rid, stt); + + dirty_text = false; + dirty_font = false; + dirty_lines = true; + } else if (dirty_font) { + int spans = TS->shaped_get_span_count(text_rid); + for (int i = 0; i < spans; i++) { + TS->shaped_set_span_update_font(text_rid, i, font->get_rids(), font_size, font->get_opentype_features()); + } + + dirty_font = false; + dirty_lines = true; + } + + if (dirty_lines) { + for (int i = 0; i < lines_rid.size(); i++) { + TS->free_rid(lines_rid[i]); + } + lines_rid.clear(); + + BitField autowrap_flags = TextServer::BREAK_MANDATORY; + switch (autowrap_mode) { + case TextServer::AUTOWRAP_WORD_SMART: + autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_ADAPTIVE | TextServer::BREAK_MANDATORY; + break; + case TextServer::AUTOWRAP_WORD: + autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_MANDATORY; + break; + case TextServer::AUTOWRAP_ARBITRARY: + autowrap_flags = TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_MANDATORY; + break; + case TextServer::AUTOWRAP_OFF: + break; + } + PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(text_rid, width, 0, autowrap_flags); + + float max_line_w = 0.0; + for (int i = 0; i < line_breaks.size(); i = i + 2) { + RID line = TS->shaped_text_substr(text_rid, line_breaks[i], line_breaks[i + 1] - line_breaks[i]); + max_line_w = MAX(max_line_w, TS->shaped_text_get_width(line)); + lines_rid.push_back(line); + } + + if (horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) { + int jst_to_line = lines_rid.size(); + if (lines_rid.size() == 1 && jst_flags.has_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE)) { + jst_to_line = lines_rid.size(); + } else { + if (jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE)) { + jst_to_line = lines_rid.size() - 1; + } + if (jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS)) { + for (int i = lines_rid.size() - 1; i >= 0; i--) { + if (TS->shaped_text_has_visible_chars(lines_rid[i])) { + jst_to_line = i; + break; + } + } + } + } + for (int i = 0; i < jst_to_line; i++) { + TS->shaped_text_fit_to_width(lines_rid[i], (width > 0) ? width : max_line_w, jst_flags); + } + } + dirty_lines = false; + } + + float total_h = 0.0; + for (int i = 0; i < lines_rid.size(); i++) { + total_h += (TS->shaped_text_get_size(lines_rid[i]).y + line_spacing) * pixel_size; + } + + float vbegin = 0.0; + switch (vertical_alignment) { + case VERTICAL_ALIGNMENT_FILL: + case VERTICAL_ALIGNMENT_TOP: { + // Nothing. + } break; + case VERTICAL_ALIGNMENT_CENTER: { + vbegin = (total_h - line_spacing * pixel_size) / 2.0; + } break; + case VERTICAL_ALIGNMENT_BOTTOM: { + vbegin = (total_h - line_spacing * pixel_size); + } break; + } + + Vector vertices; + Vector normals; + Vector tangents; + Vector uvs; + Vector indices; + + Vector2 min_p = Vector2(INFINITY, INFINITY); + Vector2 max_p = Vector2(-INFINITY, -INFINITY); + + int32_t p_size = 0; + int32_t i_size = 0; + + Vector2 offset = Vector2(0, vbegin + lbl_offset.y * pixel_size); + for (int i = 0; i < lines_rid.size(); i++) { + const Glyph *glyphs = TS->shaped_text_get_glyphs(lines_rid[i]); + int gl_size = TS->shaped_text_get_glyph_count(lines_rid[i]); + float line_width = TS->shaped_text_get_width(lines_rid[i]) * pixel_size; + + switch (horizontal_alignment) { + case HORIZONTAL_ALIGNMENT_LEFT: + offset.x = 0.0; + break; + case HORIZONTAL_ALIGNMENT_FILL: + case HORIZONTAL_ALIGNMENT_CENTER: { + offset.x = -line_width / 2.0; + } break; + case HORIZONTAL_ALIGNMENT_RIGHT: { + offset.x = -line_width; + } break; + } + offset.x += lbl_offset.x * pixel_size; + offset.y -= TS->shaped_text_get_ascent(lines_rid[i]) * pixel_size; + + bool has_depth = !Math::is_zero_approx(depth); + + for (int j = 0; j < gl_size; j++) { + if (glyphs[j].index == 0) { + offset.x += glyphs[j].advance * pixel_size * glyphs[j].repeat; + continue; + } + if (glyphs[j].font_rid != RID()) { + GlyphMeshKey key = GlyphMeshKey(glyphs[j].font_rid.get_id(), glyphs[j].index); + _generate_glyph_mesh_data(key, glyphs[j]); + GlyphMeshData &gl_data = cache[key]; + const Vector2 gl_of = Vector2(glyphs[j].x_off, glyphs[j].y_off) * pixel_size; + + p_size += glyphs[j].repeat * gl_data.triangles.size() * ((has_depth) ? 2 : 1); + i_size += glyphs[j].repeat * gl_data.triangles.size() * ((has_depth) ? 2 : 1); + + if (has_depth) { + for (int k = 0; k < gl_data.contours.size(); k++) { + p_size += glyphs[j].repeat * gl_data.contours[k].size() * 4; + i_size += glyphs[j].repeat * gl_data.contours[k].size() * 6; + } + } + + for (int r = 0; r < glyphs[j].repeat; r++) { + min_p.x = MIN(gl_data.min_p.x + offset.x + gl_of.x, min_p.x); + min_p.y = MIN(gl_data.min_p.y - offset.y + gl_of.y, min_p.y); + max_p.x = MAX(gl_data.max_p.x + offset.x + gl_of.x, max_p.x); + max_p.y = MAX(gl_data.max_p.y - offset.y + gl_of.y, max_p.y); + + offset.x += glyphs[j].advance * pixel_size; + } + } else { + p_size += glyphs[j].repeat * 4; + i_size += glyphs[j].repeat * 6; + + offset.x += glyphs[j].advance * pixel_size * glyphs[j].repeat; + } + } + offset.y -= (TS->shaped_text_get_descent(lines_rid[i]) + line_spacing) * pixel_size; + } + + vertices.resize(p_size); + normals.resize(p_size); + uvs.resize(p_size); + tangents.resize(p_size * 4); + indices.resize(i_size); + + Vector3 *vertices_ptr = vertices.ptrw(); + Vector3 *normals_ptr = normals.ptrw(); + float *tangents_ptr = tangents.ptrw(); + Vector2 *uvs_ptr = uvs.ptrw(); + int32_t *indices_ptr = indices.ptrw(); + + // Generate mesh. + int32_t p_idx = 0; + int32_t i_idx = 0; + + offset = Vector2(0, vbegin + lbl_offset.y * pixel_size); + for (int i = 0; i < lines_rid.size(); i++) { + const Glyph *glyphs = TS->shaped_text_get_glyphs(lines_rid[i]); + int gl_size = TS->shaped_text_get_glyph_count(lines_rid[i]); + float line_width = TS->shaped_text_get_width(lines_rid[i]) * pixel_size; + + switch (horizontal_alignment) { + case HORIZONTAL_ALIGNMENT_LEFT: + offset.x = 0.0; + break; + case HORIZONTAL_ALIGNMENT_FILL: + case HORIZONTAL_ALIGNMENT_CENTER: { + offset.x = -line_width / 2.0; + } break; + case HORIZONTAL_ALIGNMENT_RIGHT: { + offset.x = -line_width; + } break; + } + offset.x += lbl_offset.x * pixel_size; + offset.y -= TS->shaped_text_get_ascent(lines_rid[i]) * pixel_size; + + bool has_depth = !Math::is_zero_approx(depth); + + // Generate glyph data, precalculate size of the arrays and mesh bounds for UV. + for (int j = 0; j < gl_size; j++) { + if (glyphs[j].index == 0) { + offset.x += glyphs[j].advance * pixel_size * glyphs[j].repeat; + continue; + } + if (glyphs[j].font_rid != RID()) { + GlyphMeshKey key = GlyphMeshKey(glyphs[j].font_rid.get_id(), glyphs[j].index); + _generate_glyph_mesh_data(key, glyphs[j]); + const GlyphMeshData &gl_data = cache[key]; + + int64_t ts = gl_data.triangles.size(); + const Vector2 *ts_ptr = gl_data.triangles.ptr(); + const Vector2 gl_of = Vector2(glyphs[j].x_off, glyphs[j].y_off) * pixel_size; + + for (int r = 0; r < glyphs[j].repeat; r++) { + for (int k = 0; k < ts; k += 3) { + // Add front face. + for (int l = 0; l < 3; l++) { + Vector3 point = Vector3(ts_ptr[k + l].x + offset.x + gl_of.x, -ts_ptr[k + l].y + offset.y - gl_of.y, depth / 2.0); + vertices_ptr[p_idx] = point; + normals_ptr[p_idx] = Vector3(0.0, 0.0, 1.0); + if (has_depth) { + uvs_ptr[p_idx] = Vector2(Math::remap(point.x, min_p.x, max_p.x, real_t(0.0), real_t(1.0)), Math::remap(point.y, -max_p.y, -min_p.y, real_t(0.4), real_t(0.0))); + } else { + uvs_ptr[p_idx] = Vector2(Math::remap(point.x, min_p.x, max_p.x, real_t(0.0), real_t(1.0)), Math::remap(point.y, -max_p.y, -min_p.y, real_t(1.0), real_t(0.0))); + } + tangents_ptr[p_idx * 4 + 0] = 1.0; + tangents_ptr[p_idx * 4 + 1] = 0.0; + tangents_ptr[p_idx * 4 + 2] = 0.0; + tangents_ptr[p_idx * 4 + 3] = 1.0; + indices_ptr[i_idx++] = p_idx; + p_idx++; + } + if (has_depth) { + // Add back face. + for (int l = 2; l >= 0; l--) { + Vector3 point = Vector3(ts_ptr[k + l].x + offset.x + gl_of.x, -ts_ptr[k + l].y + offset.y - gl_of.y, -depth / 2.0); + vertices_ptr[p_idx] = point; + normals_ptr[p_idx] = Vector3(0.0, 0.0, -1.0); + uvs_ptr[p_idx] = Vector2(Math::remap(point.x, min_p.x, max_p.x, real_t(0.0), real_t(1.0)), Math::remap(point.y, -max_p.y, -min_p.y, real_t(0.8), real_t(0.4))); + tangents_ptr[p_idx * 4 + 0] = -1.0; + tangents_ptr[p_idx * 4 + 1] = 0.0; + tangents_ptr[p_idx * 4 + 2] = 0.0; + tangents_ptr[p_idx * 4 + 3] = 1.0; + indices_ptr[i_idx++] = p_idx; + p_idx++; + } + } + } + // Add sides. + if (has_depth) { + for (int k = 0; k < gl_data.contours.size(); k++) { + int64_t ps = gl_data.contours[k].size(); + const ContourPoint *ps_ptr = gl_data.contours[k].ptr(); + const ContourInfo &ps_info = gl_data.contours_info[k]; + real_t length = 0.0; + for (int l = 0; l < ps; l++) { + int prev = (l == 0) ? (ps - 1) : (l - 1); + int next = (l + 1 == ps) ? 0 : (l + 1); + Vector2 d1; + Vector2 d2 = (ps_ptr[next].point - ps_ptr[l].point).normalized(); + if (ps_ptr[l].sharp) { + d1 = d2; + } else { + d1 = (ps_ptr[l].point - ps_ptr[prev].point).normalized(); + } + real_t seg_len = (ps_ptr[next].point - ps_ptr[l].point).length(); + + Vector3 quad_faces[4] = { + Vector3(ps_ptr[l].point.x + offset.x + gl_of.x, -ps_ptr[l].point.y + offset.y - gl_of.y, -depth / 2.0), + Vector3(ps_ptr[next].point.x + offset.x + gl_of.x, -ps_ptr[next].point.y + offset.y - gl_of.y, -depth / 2.0), + Vector3(ps_ptr[l].point.x + offset.x + gl_of.x, -ps_ptr[l].point.y + offset.y - gl_of.y, depth / 2.0), + Vector3(ps_ptr[next].point.x + offset.x + gl_of.x, -ps_ptr[next].point.y + offset.y - gl_of.y, depth / 2.0), + }; + for (int m = 0; m < 4; m++) { + const Vector2 &d = ((m % 2) == 0) ? d1 : d2; + real_t u_pos = ((m % 2) == 0) ? length : length + seg_len; + vertices_ptr[p_idx + m] = quad_faces[m]; + normals_ptr[p_idx + m] = Vector3(d.y, d.x, 0.0); + if (m < 2) { + uvs_ptr[p_idx + m] = Vector2(Math::remap(u_pos, 0, ps_info.length, real_t(0.0), real_t(1.0)), (ps_info.ccw) ? 0.8 : 0.9); + } else { + uvs_ptr[p_idx + m] = Vector2(Math::remap(u_pos, 0, ps_info.length, real_t(0.0), real_t(1.0)), (ps_info.ccw) ? 0.9 : 1.0); + } + tangents_ptr[(p_idx + m) * 4 + 0] = d.x; + tangents_ptr[(p_idx + m) * 4 + 1] = -d.y; + tangents_ptr[(p_idx + m) * 4 + 2] = 0.0; + tangents_ptr[(p_idx + m) * 4 + 3] = 1.0; + } + + indices_ptr[i_idx++] = p_idx; + indices_ptr[i_idx++] = p_idx + 1; + indices_ptr[i_idx++] = p_idx + 2; + + indices_ptr[i_idx++] = p_idx + 1; + indices_ptr[i_idx++] = p_idx + 3; + indices_ptr[i_idx++] = p_idx + 2; + + length += seg_len; + p_idx += 4; + } + } + } + offset.x += glyphs[j].advance * pixel_size; + } + } else { + // Add fallback quad for missing glyphs. + for (int r = 0; r < glyphs[j].repeat; r++) { + Size2 sz = TS->get_hex_code_box_size(glyphs[j].font_size, glyphs[j].index) * pixel_size; + Vector3 quad_faces[4] = { + Vector3(offset.x, offset.y, 0.0), + Vector3(offset.x, sz.y + offset.y, 0.0), + Vector3(sz.x + offset.x, sz.y + offset.y, 0.0), + Vector3(sz.x + offset.x, offset.y, 0.0), + }; + for (int k = 0; k < 4; k++) { + vertices_ptr[p_idx + k] = quad_faces[k]; + normals_ptr[p_idx + k] = Vector3(0.0, 0.0, 1.0); + if (has_depth) { + uvs_ptr[p_idx + k] = Vector2(Math::remap(quad_faces[k].x, min_p.x, max_p.x, real_t(0.0), real_t(1.0)), Math::remap(quad_faces[k].y, -max_p.y, -min_p.y, real_t(0.4), real_t(0.0))); + } else { + uvs_ptr[p_idx + k] = Vector2(Math::remap(quad_faces[k].x, min_p.x, max_p.x, real_t(0.0), real_t(1.0)), Math::remap(quad_faces[k].y, -max_p.y, -min_p.y, real_t(1.0), real_t(0.0))); + } + tangents_ptr[(p_idx + k) * 4 + 0] = 1.0; + tangents_ptr[(p_idx + k) * 4 + 1] = 0.0; + tangents_ptr[(p_idx + k) * 4 + 2] = 0.0; + tangents_ptr[(p_idx + k) * 4 + 3] = 1.0; + } + + indices_ptr[i_idx++] = p_idx; + indices_ptr[i_idx++] = p_idx + 1; + indices_ptr[i_idx++] = p_idx + 2; + + indices_ptr[i_idx++] = p_idx + 0; + indices_ptr[i_idx++] = p_idx + 2; + indices_ptr[i_idx++] = p_idx + 3; + p_idx += 4; + + offset.x += glyphs[j].advance * pixel_size; + } + } + } + offset.y -= (TS->shaped_text_get_descent(lines_rid[i]) + line_spacing) * pixel_size; + } + + if (indices.is_empty()) { + // If empty, add single triangle to suppress errors. + vertices.push_back(Vector3()); + normals.push_back(Vector3()); + uvs.push_back(Vector2()); + tangents.push_back(1.0); + tangents.push_back(0.0); + tangents.push_back(0.0); + tangents.push_back(1.0); + indices.push_back(0); + indices.push_back(0); + indices.push_back(0); + } + + p_arr[RS::ARRAY_VERTEX] = vertices; + p_arr[RS::ARRAY_NORMAL] = normals; + p_arr[RS::ARRAY_TANGENT] = tangents; + p_arr[RS::ARRAY_TEX_UV] = uvs; + p_arr[RS::ARRAY_INDEX] = indices; +} + +void TextMesh::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &TextMesh::set_horizontal_alignment); + ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &TextMesh::get_horizontal_alignment); + + ClassDB::bind_method(D_METHOD("set_vertical_alignment", "alignment"), &TextMesh::set_vertical_alignment); + ClassDB::bind_method(D_METHOD("get_vertical_alignment"), &TextMesh::get_vertical_alignment); + + ClassDB::bind_method(D_METHOD("set_text", "text"), &TextMesh::set_text); + ClassDB::bind_method(D_METHOD("get_text"), &TextMesh::get_text); + + ClassDB::bind_method(D_METHOD("set_font", "font"), &TextMesh::set_font); + ClassDB::bind_method(D_METHOD("get_font"), &TextMesh::get_font); + + ClassDB::bind_method(D_METHOD("set_font_size", "font_size"), &TextMesh::set_font_size); + ClassDB::bind_method(D_METHOD("get_font_size"), &TextMesh::get_font_size); + + ClassDB::bind_method(D_METHOD("set_line_spacing", "line_spacing"), &TextMesh::set_line_spacing); + ClassDB::bind_method(D_METHOD("get_line_spacing"), &TextMesh::get_line_spacing); + + ClassDB::bind_method(D_METHOD("set_autowrap_mode", "autowrap_mode"), &TextMesh::set_autowrap_mode); + ClassDB::bind_method(D_METHOD("get_autowrap_mode"), &TextMesh::get_autowrap_mode); + + ClassDB::bind_method(D_METHOD("set_justification_flags", "justification_flags"), &TextMesh::set_justification_flags); + ClassDB::bind_method(D_METHOD("get_justification_flags"), &TextMesh::get_justification_flags); + + ClassDB::bind_method(D_METHOD("set_depth", "depth"), &TextMesh::set_depth); + ClassDB::bind_method(D_METHOD("get_depth"), &TextMesh::get_depth); + + ClassDB::bind_method(D_METHOD("set_width", "width"), &TextMesh::set_width); + ClassDB::bind_method(D_METHOD("get_width"), &TextMesh::get_width); + + ClassDB::bind_method(D_METHOD("set_pixel_size", "pixel_size"), &TextMesh::set_pixel_size); + ClassDB::bind_method(D_METHOD("get_pixel_size"), &TextMesh::get_pixel_size); + + ClassDB::bind_method(D_METHOD("set_offset", "offset"), &TextMesh::set_offset); + ClassDB::bind_method(D_METHOD("get_offset"), &TextMesh::get_offset); + + ClassDB::bind_method(D_METHOD("set_curve_step", "curve_step"), &TextMesh::set_curve_step); + ClassDB::bind_method(D_METHOD("get_curve_step"), &TextMesh::get_curve_step); + + ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &TextMesh::set_text_direction); + ClassDB::bind_method(D_METHOD("get_text_direction"), &TextMesh::get_text_direction); + + ClassDB::bind_method(D_METHOD("set_language", "language"), &TextMesh::set_language); + ClassDB::bind_method(D_METHOD("get_language"), &TextMesh::get_language); + + ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override", "parser"), &TextMesh::set_structured_text_bidi_override); + ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override"), &TextMesh::get_structured_text_bidi_override); + + ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override_options", "args"), &TextMesh::set_structured_text_bidi_override_options); + ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override_options"), &TextMesh::get_structured_text_bidi_override_options); + + ClassDB::bind_method(D_METHOD("set_uppercase", "enable"), &TextMesh::set_uppercase); + ClassDB::bind_method(D_METHOD("is_uppercase"), &TextMesh::is_uppercase); + + ClassDB::bind_method(D_METHOD("_request_update"), &TextMesh::_request_update); + + ADD_GROUP("Text", ""); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT, ""), "set_text", "get_text"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "font", PROPERTY_HINT_RESOURCE_TYPE, "Font"), "set_font", "get_font"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "font_size", PROPERTY_HINT_RANGE, "1,256,1,or_greater,suffix:px"), "set_font_size", "get_font_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "horizontal_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_horizontal_alignment", "get_horizontal_alignment"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "vertical_alignment", PROPERTY_HINT_ENUM, "Top,Center,Bottom"), "set_vertical_alignment", "get_vertical_alignment"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uppercase"), "set_uppercase", "is_uppercase"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "line_spacing", PROPERTY_HINT_NONE, "suffix:px"), "set_line_spacing", "get_line_spacing"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Off,Arbitrary,Word,Word (Smart)"), "set_autowrap_mode", "get_autowrap_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "justification_flags", PROPERTY_HINT_FLAGS, "Kashida Justification:1,Word Justification:2,Justify Only After Last Tab:8,Skip Last Line:32,Skip Last Line With Visible Characters:64,Do Not Skip Single Line:128"), "set_justification_flags", "get_justification_flags"); + + ADD_GROUP("Mesh", ""); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pixel_size", PROPERTY_HINT_RANGE, "0.0001,128,0.0001,suffix:m"), "set_pixel_size", "get_pixel_size"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "curve_step", PROPERTY_HINT_RANGE, "0.1,10,0.1,suffix:px"), "set_curve_step", "get_curve_step"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "depth", PROPERTY_HINT_RANGE, "0.0,100.0,0.001,or_greater,suffix:m"), "set_depth", "get_depth"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "width", PROPERTY_HINT_NONE, "suffix:px"), "set_width", "get_width"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset", PROPERTY_HINT_NONE, "suffix:px"), "set_offset", "get_offset"); + + ADD_GROUP("BiDi", ""); + ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left"), "set_text_direction", "get_text_direction"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options"); +} + +void TextMesh::_notification(int p_what) { + switch (p_what) { + case MainLoop::NOTIFICATION_TRANSLATION_CHANGED: { + String new_text = tr(text); + if (new_text == xl_text) { + return; // Nothing new. + } + xl_text = new_text; + dirty_text = true; + _request_update(); + } break; + } +} + +TextMesh::TextMesh() { + primitive_type = PRIMITIVE_TRIANGLES; + text_rid = TS->create_shaped_text(); +} + +TextMesh::~TextMesh() { + for (int i = 0; i < lines_rid.size(); i++) { + TS->free_rid(lines_rid[i]); + } + lines_rid.clear(); + + TS->free_rid(text_rid); +} + +void TextMesh::set_horizontal_alignment(HorizontalAlignment p_alignment) { + ERR_FAIL_INDEX((int)p_alignment, 4); + if (horizontal_alignment != p_alignment) { + if (horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL || p_alignment == HORIZONTAL_ALIGNMENT_FILL) { + dirty_lines = true; + } + horizontal_alignment = p_alignment; + _request_update(); + } +} + +HorizontalAlignment TextMesh::get_horizontal_alignment() const { + return horizontal_alignment; +} + +void TextMesh::set_vertical_alignment(VerticalAlignment p_alignment) { + ERR_FAIL_INDEX((int)p_alignment, 4); + if (vertical_alignment != p_alignment) { + vertical_alignment = p_alignment; + _request_update(); + } +} + +VerticalAlignment TextMesh::get_vertical_alignment() const { + return vertical_alignment; +} + +void TextMesh::set_text(const String &p_string) { + if (text != p_string) { + text = p_string; + xl_text = tr(text); + dirty_text = true; + _request_update(); + } +} + +String TextMesh::get_text() const { + return text; +} + +void TextMesh::_font_changed() { + dirty_font = true; + dirty_cache = true; + call_deferred(SNAME("_request_update")); +} + +void TextMesh::set_font(const Ref &p_font) { + if (font_override != p_font) { + const Callable font_changed = callable_mp(this, &TextMesh::_font_changed); + + if (font_override.is_valid()) { + font_override->disconnect_changed(font_changed); + } + font_override = p_font; + dirty_font = true; + dirty_cache = true; + if (font_override.is_valid()) { + font_override->connect_changed(font_changed); + } + _request_update(); + } +} + +Ref TextMesh::get_font() const { + return font_override; +} + +Ref TextMesh::_get_font_or_default() const { + if (font_override.is_valid()) { + return font_override; + } + + StringName theme_name = "font"; + List theme_types; + ThemeDB::get_singleton()->get_native_type_dependencies(get_class_name(), &theme_types); + + ThemeContext *global_context = ThemeDB::get_singleton()->get_default_theme_context(); + for (const Ref &theme : global_context->get_themes()) { + if (theme.is_null()) { + continue; + } + + for (const StringName &E : theme_types) { + if (theme->has_font(theme_name, E)) { + return theme->get_font(theme_name, E); + } + } + } + + return global_context->get_fallback_theme()->get_font(theme_name, StringName()); +} + +void TextMesh::set_font_size(int p_size) { + if (font_size != p_size) { + font_size = CLAMP(p_size, 1, 127); + dirty_font = true; + dirty_cache = true; + _request_update(); + } +} + +int TextMesh::get_font_size() const { + return font_size; +} + +void TextMesh::set_line_spacing(float p_line_spacing) { + if (line_spacing != p_line_spacing) { + line_spacing = p_line_spacing; + _request_update(); + } +} + +float TextMesh::get_line_spacing() const { + return line_spacing; +} + +void TextMesh::set_autowrap_mode(TextServer::AutowrapMode p_mode) { + if (autowrap_mode != p_mode) { + autowrap_mode = p_mode; + dirty_lines = true; + _request_update(); + } +} + +TextServer::AutowrapMode TextMesh::get_autowrap_mode() const { + return autowrap_mode; +} + +void TextMesh::set_justification_flags(BitField p_flags) { + if (jst_flags != p_flags) { + jst_flags = p_flags; + dirty_lines = true; + _request_update(); + } +} + +BitField TextMesh::get_justification_flags() const { + return jst_flags; +} + +void TextMesh::set_depth(real_t p_depth) { + if (depth != p_depth) { + depth = MAX(p_depth, 0.0); + _request_update(); + } +} + +real_t TextMesh::get_depth() const { + return depth; +} + +void TextMesh::set_width(real_t p_width) { + if (width != p_width) { + width = p_width; + dirty_lines = true; + _request_update(); + } +} + +real_t TextMesh::get_width() const { + return width; +} + +void TextMesh::set_pixel_size(real_t p_amount) { + if (pixel_size != p_amount) { + pixel_size = CLAMP(p_amount, 0.0001, 128.0); + dirty_cache = true; + _request_update(); + } +} + +real_t TextMesh::get_pixel_size() const { + return pixel_size; +} + +void TextMesh::set_offset(const Point2 &p_offset) { + if (lbl_offset != p_offset) { + lbl_offset = p_offset; + _request_update(); + } +} + +Point2 TextMesh::get_offset() const { + return lbl_offset; +} + +void TextMesh::set_curve_step(real_t p_step) { + if (curve_step != p_step) { + curve_step = CLAMP(p_step, 0.1, 10.0); + dirty_cache = true; + _request_update(); + } +} + +real_t TextMesh::get_curve_step() const { + return curve_step; +} + +void TextMesh::set_text_direction(TextServer::Direction p_text_direction) { + ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3); + if (text_direction != p_text_direction) { + text_direction = p_text_direction; + dirty_text = true; + _request_update(); + } +} + +TextServer::Direction TextMesh::get_text_direction() const { + return text_direction; +} + +void TextMesh::set_language(const String &p_language) { + if (language != p_language) { + language = p_language; + dirty_text = true; + _request_update(); + } +} + +String TextMesh::get_language() const { + return language; +} + +void TextMesh::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) { + if (st_parser != p_parser) { + st_parser = p_parser; + dirty_text = true; + _request_update(); + } +} + +TextServer::StructuredTextParser TextMesh::get_structured_text_bidi_override() const { + return st_parser; +} + +void TextMesh::set_structured_text_bidi_override_options(Array p_args) { + if (st_args != p_args) { + st_args = p_args; + dirty_text = true; + _request_update(); + } +} + +Array TextMesh::get_structured_text_bidi_override_options() const { + return st_args; +} + +void TextMesh::set_uppercase(bool p_uppercase) { + if (uppercase != p_uppercase) { + uppercase = p_uppercase; + dirty_text = true; + _request_update(); + } +} + +bool TextMesh::is_uppercase() const { + return uppercase; +} diff --git a/scene/resources/3d/primitive_meshes.h b/scene/resources/3d/primitive_meshes.h new file mode 100644 index 0000000000..5b788b726e --- /dev/null +++ b/scene/resources/3d/primitive_meshes.h @@ -0,0 +1,689 @@ +/**************************************************************************/ +/* primitive_meshes.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 PRIMITIVE_MESHES_H +#define PRIMITIVE_MESHES_H + +#include "scene/resources/font.h" +#include "scene/resources/mesh.h" +#include "servers/text_server.h" + +///@TODO probably should change a few integers to unsigned integers... + +/** + Base class for all the classes in this file, handles a number of code functions that are shared among all meshes. + This class is set apart that it assumes a single surface is always generated for our mesh. +*/ + +class PrimitiveMesh : public Mesh { + GDCLASS(PrimitiveMesh, Mesh); + +private: + RID mesh; + mutable AABB aabb; + AABB custom_aabb; + + mutable int array_len = 0; + mutable int index_array_len = 0; + + Ref material; + bool flip_faces = false; + + bool add_uv2 = false; + float uv2_padding = 2.0; + + // make sure we do an update after we've finished constructing our object + mutable bool pending_request = true; + void _update() const; + +protected: + // assume primitive triangles as the type, correct for all but one and it will change this :) + Mesh::PrimitiveType primitive_type = Mesh::PRIMITIVE_TRIANGLES; + + static void _bind_methods(); + + virtual void _create_mesh_array(Array &p_arr) const {} + void _request_update(); + GDVIRTUAL0RC(Array, _create_mesh_array) + + Vector2 get_uv2_scale(Vector2 p_margin_scale = Vector2(1.0, 1.0)) const; + float get_lightmap_texel_size() const; + virtual void _update_lightmap_size(){}; + +public: + virtual int get_surface_count() const override; + virtual int surface_get_array_len(int p_idx) const override; + virtual int surface_get_array_index_len(int p_idx) const override; + virtual Array surface_get_arrays(int p_surface) const override; + virtual TypedArray surface_get_blend_shape_arrays(int p_surface) const override; + virtual Dictionary surface_get_lods(int p_surface) const override; + virtual BitField surface_get_format(int p_idx) const override; + virtual Mesh::PrimitiveType surface_get_primitive_type(int p_idx) const override; + virtual void surface_set_material(int p_idx, const Ref &p_material) override; + virtual Ref surface_get_material(int p_idx) const override; + virtual int get_blend_shape_count() const override; + virtual StringName get_blend_shape_name(int p_index) const override; + virtual void set_blend_shape_name(int p_index, const StringName &p_name) override; + virtual AABB get_aabb() const override; + virtual RID get_rid() const override; + + void set_material(const Ref &p_material); + Ref get_material() const; + + Array get_mesh_arrays() const; + + void set_custom_aabb(const AABB &p_custom); + AABB get_custom_aabb() const; + + void set_flip_faces(bool p_enable); + bool get_flip_faces() const; + + void set_add_uv2(bool p_enable); + bool get_add_uv2() const { return add_uv2; } + + void set_uv2_padding(float p_padding); + float get_uv2_padding() const { return uv2_padding; } + + PrimitiveMesh(); + ~PrimitiveMesh(); +}; + +/** + Mesh for a simple capsule +*/ +class CapsuleMesh : public PrimitiveMesh { + GDCLASS(CapsuleMesh, PrimitiveMesh); + +private: + float radius = 0.5; + float height = 2.0; + int radial_segments = 64; + int rings = 8; + +protected: + static void _bind_methods(); + virtual void _create_mesh_array(Array &p_arr) const override; + + virtual void _update_lightmap_size() override; + +public: + static void create_mesh_array(Array &p_arr, float radius, float height, int radial_segments = 64, int rings = 8, bool p_add_uv2 = false, const float p_uv2_padding = 1.0); + + void set_radius(const float p_radius); + float get_radius() const; + + void set_height(const float p_height); + float get_height() const; + + void set_radial_segments(const int p_segments); + int get_radial_segments() const; + + void set_rings(const int p_rings); + int get_rings() const; + + CapsuleMesh(); +}; + +/** + A box +*/ +class BoxMesh : public PrimitiveMesh { + GDCLASS(BoxMesh, PrimitiveMesh); + +private: + Vector3 size = Vector3(1, 1, 1); + int subdivide_w = 0; + int subdivide_h = 0; + int subdivide_d = 0; + +protected: + static void _bind_methods(); + virtual void _create_mesh_array(Array &p_arr) const override; + + virtual void _update_lightmap_size() override; + +public: + static void create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w = 0, int subdivide_h = 0, int subdivide_d = 0, bool p_add_uv2 = false, const float p_uv2_padding = 1.0); + + void set_size(const Vector3 &p_size); + Vector3 get_size() const; + + void set_subdivide_width(const int p_divisions); + int get_subdivide_width() const; + + void set_subdivide_height(const int p_divisions); + int get_subdivide_height() const; + + void set_subdivide_depth(const int p_divisions); + int get_subdivide_depth() const; + + BoxMesh(); +}; + +/** + A cylinder +*/ + +class CylinderMesh : public PrimitiveMesh { + GDCLASS(CylinderMesh, PrimitiveMesh); + +private: + float top_radius = 0.5; + float bottom_radius = 0.5; + float height = 2.0; + int radial_segments = 64; + int rings = 4; + bool cap_top = true; + bool cap_bottom = true; + +protected: + static void _bind_methods(); + virtual void _create_mesh_array(Array &p_arr) const override; + + virtual void _update_lightmap_size() override; + +public: + static void create_mesh_array(Array &p_arr, float top_radius, float bottom_radius, float height, int radial_segments = 64, int rings = 4, bool cap_top = true, bool cap_bottom = true, bool p_add_uv2 = false, const float p_uv2_padding = 1.0); + + void set_top_radius(const float p_radius); + float get_top_radius() const; + + void set_bottom_radius(const float p_radius); + float get_bottom_radius() const; + + void set_height(const float p_height); + float get_height() const; + + void set_radial_segments(const int p_segments); + int get_radial_segments() const; + + void set_rings(const int p_rings); + int get_rings() const; + + void set_cap_top(bool p_cap_top); + bool is_cap_top() const; + + void set_cap_bottom(bool p_cap_bottom); + bool is_cap_bottom() const; + + CylinderMesh(); +}; + +/* + A flat rectangle, can be used as quad or heightmap. +*/ +class PlaneMesh : public PrimitiveMesh { + GDCLASS(PlaneMesh, PrimitiveMesh); + +public: + enum Orientation { + FACE_X, + FACE_Y, + FACE_Z, + }; + +private: + Size2 size = Size2(2.0, 2.0); + int subdivide_w = 0; + int subdivide_d = 0; + Vector3 center_offset; + Orientation orientation = FACE_Y; + +protected: + static void _bind_methods(); + virtual void _create_mesh_array(Array &p_arr) const override; + + virtual void _update_lightmap_size() override; + +public: + void set_size(const Size2 &p_size); + Size2 get_size() const; + + void set_subdivide_width(const int p_divisions); + int get_subdivide_width() const; + + void set_subdivide_depth(const int p_divisions); + int get_subdivide_depth() const; + + void set_center_offset(const Vector3 p_offset); + Vector3 get_center_offset() const; + + void set_orientation(const Orientation p_orientation); + Orientation get_orientation() const; + + PlaneMesh(); +}; + +VARIANT_ENUM_CAST(PlaneMesh::Orientation) + +/* + A flat rectangle, inherits from PlaneMesh but defaults to facing the Z-plane. +*/ +class QuadMesh : public PlaneMesh { + GDCLASS(QuadMesh, PlaneMesh); + +public: + QuadMesh() { + set_orientation(FACE_Z); + set_size(Size2(1, 1)); + } +}; + +/** + A prism shapen, handy for ramps, triangles, etc. +*/ +class PrismMesh : public PrimitiveMesh { + GDCLASS(PrismMesh, PrimitiveMesh); + +private: + float left_to_right = 0.5; + Vector3 size = Vector3(1.0, 1.0, 1.0); + int subdivide_w = 0; + int subdivide_h = 0; + int subdivide_d = 0; + +protected: + static void _bind_methods(); + virtual void _create_mesh_array(Array &p_arr) const override; + + virtual void _update_lightmap_size() override; + +public: + void set_left_to_right(const float p_left_to_right); + float get_left_to_right() const; + + void set_size(const Vector3 &p_size); + Vector3 get_size() const; + + void set_subdivide_width(const int p_divisions); + int get_subdivide_width() const; + + void set_subdivide_height(const int p_divisions); + int get_subdivide_height() const; + + void set_subdivide_depth(const int p_divisions); + int get_subdivide_depth() const; + + PrismMesh(); +}; + +/** + A sphere.. +*/ +class SphereMesh : public PrimitiveMesh { + GDCLASS(SphereMesh, PrimitiveMesh); + +private: + float radius = 0.5; + float height = 1.0; + int radial_segments = 64; + int rings = 32; + bool is_hemisphere = false; + +protected: + static void _bind_methods(); + virtual void _create_mesh_array(Array &p_arr) const override; + + virtual void _update_lightmap_size() override; + +public: + static void create_mesh_array(Array &p_arr, float radius, float height, int radial_segments = 64, int rings = 32, bool is_hemisphere = false, bool p_add_uv2 = false, const float p_uv2_padding = 1.0); + + void set_radius(const float p_radius); + float get_radius() const; + + void set_height(const float p_height); + float get_height() const; + + void set_radial_segments(const int p_radial_segments); + int get_radial_segments() const; + + void set_rings(const int p_rings); + int get_rings() const; + + void set_is_hemisphere(const bool p_is_hemisphere); + bool get_is_hemisphere() const; + + SphereMesh(); +}; + +/** + Big donut +*/ +class TorusMesh : public PrimitiveMesh { + GDCLASS(TorusMesh, PrimitiveMesh); + +private: + float inner_radius = 0.5; + float outer_radius = 1.0; + int rings = 64; + int ring_segments = 32; + +protected: + static void _bind_methods(); + virtual void _create_mesh_array(Array &p_arr) const override; + + virtual void _update_lightmap_size() override; + +public: + void set_inner_radius(const float p_inner_radius); + float get_inner_radius() const; + + void set_outer_radius(const float p_outer_radius); + float get_outer_radius() const; + + void set_rings(const int p_rings); + int get_rings() const; + + void set_ring_segments(const int p_ring_segments); + int get_ring_segments() const; + + TorusMesh(); +}; + +/** + A single point for use in particle systems +*/ + +class PointMesh : public PrimitiveMesh { + GDCLASS(PointMesh, PrimitiveMesh) + +protected: + virtual void _create_mesh_array(Array &p_arr) const override; + +public: + PointMesh(); +}; + +class TubeTrailMesh : public PrimitiveMesh { + GDCLASS(TubeTrailMesh, PrimitiveMesh); + +private: + float radius = 0.5; + int radial_steps = 8; + int sections = 5; + float section_length = 0.2; + int section_rings = 3; + bool cap_top = true; + bool cap_bottom = true; + + Ref curve; + + void _curve_changed(); + +protected: + static void _bind_methods(); + virtual void _create_mesh_array(Array &p_arr) const override; + +public: + void set_radius(const float p_radius); + float get_radius() const; + + void set_radial_steps(const int p_radial_steps); + int get_radial_steps() const; + + void set_sections(const int p_sections); + int get_sections() const; + + void set_section_length(float p_sectionlength); + float get_section_length() const; + + void set_section_rings(const int p_section_rings); + int get_section_rings() const; + + void set_cap_top(bool p_cap_top); + bool is_cap_top() const; + + void set_cap_bottom(bool p_cap_bottom); + bool is_cap_bottom() const; + + void set_curve(const Ref &p_curve); + Ref get_curve() const; + + virtual int get_builtin_bind_pose_count() const override; + virtual Transform3D get_builtin_bind_pose(int p_index) const override; + + TubeTrailMesh(); +}; + +class RibbonTrailMesh : public PrimitiveMesh { + GDCLASS(RibbonTrailMesh, PrimitiveMesh); + +public: + enum Shape { + SHAPE_FLAT, + SHAPE_CROSS + }; + +private: + float size = 1.0; + int sections = 5; + float section_length = 0.2; + int section_segments = 3; + + Shape shape = SHAPE_CROSS; + + Ref curve; + + void _curve_changed(); + +protected: + static void _bind_methods(); + virtual void _create_mesh_array(Array &p_arr) const override; + +public: + void set_shape(Shape p_shape); + Shape get_shape() const; + + void set_size(const float p_size); + float get_size() const; + + void set_sections(const int p_sections); + int get_sections() const; + + void set_section_length(float p_sectionlength); + float get_section_length() const; + + void set_section_segments(const int p_section_segments); + int get_section_segments() const; + + void set_curve(const Ref &p_curve); + Ref get_curve() const; + + virtual int get_builtin_bind_pose_count() const override; + virtual Transform3D get_builtin_bind_pose(int p_index) const override; + + RibbonTrailMesh(); +}; + +/** + Text... +*/ + +class TextMesh : public PrimitiveMesh { + GDCLASS(TextMesh, PrimitiveMesh); + +private: + struct ContourPoint { + Vector2 point; + bool sharp = false; + + ContourPoint(){}; + ContourPoint(const Vector2 &p_pt, bool p_sharp) { + point = p_pt; + sharp = p_sharp; + }; + }; + + struct ContourInfo { + real_t length = 0.0; + bool ccw = true; + ContourInfo(){}; + ContourInfo(real_t p_len, bool p_ccw) { + length = p_len; + ccw = p_ccw; + } + }; + + struct GlyphMeshKey { + uint64_t font_id; + uint32_t gl_id; + + bool operator==(const GlyphMeshKey &p_b) const { + return (font_id == p_b.font_id) && (gl_id == p_b.gl_id); + } + + GlyphMeshKey(uint64_t p_font_id, uint32_t p_gl_id) { + font_id = p_font_id; + gl_id = p_gl_id; + } + }; + + struct GlyphMeshKeyHasher { + _FORCE_INLINE_ static uint32_t hash(const GlyphMeshKey &p_a) { + return hash_murmur3_buffer(&p_a, sizeof(GlyphMeshKey)); + } + }; + + struct GlyphMeshData { + Vector triangles; + Vector> contours; + Vector contours_info; + Vector2 min_p = Vector2(INFINITY, INFINITY); + Vector2 max_p = Vector2(-INFINITY, -INFINITY); + }; + mutable HashMap cache; + + RID text_rid; + mutable Vector lines_rid; + + String text; + String xl_text; + + int font_size = 16; + Ref font_override; + + TextServer::AutowrapMode autowrap_mode = TextServer::AUTOWRAP_OFF; + BitField jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE; + float width = 500.0; + float line_spacing = 0.f; + Point2 lbl_offset; + + HorizontalAlignment horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER; + VerticalAlignment vertical_alignment = VERTICAL_ALIGNMENT_CENTER; + bool uppercase = false; + String language; + TextServer::Direction text_direction = TextServer::DIRECTION_AUTO; + TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; + Array st_args; + + real_t depth = 0.05; + real_t pixel_size = 0.01; + real_t curve_step = 0.5; + + mutable bool dirty_lines = true; + mutable bool dirty_text = true; + mutable bool dirty_font = true; + mutable bool dirty_cache = true; + + void _generate_glyph_mesh_data(const GlyphMeshKey &p_key, const Glyph &p_glyph) const; + void _font_changed(); + +protected: + static void _bind_methods(); + void _notification(int p_what); + + virtual void _create_mesh_array(Array &p_arr) const override; + +public: + GDVIRTUAL2RC(TypedArray, _structured_text_parser, Array, String) + + TextMesh(); + ~TextMesh(); + + void set_horizontal_alignment(HorizontalAlignment p_alignment); + HorizontalAlignment get_horizontal_alignment() const; + + void set_vertical_alignment(VerticalAlignment p_alignment); + VerticalAlignment get_vertical_alignment() const; + + void set_text(const String &p_string); + String get_text() const; + + void set_font(const Ref &p_font); + Ref get_font() const; + Ref _get_font_or_default() const; + + void set_font_size(int p_size); + int get_font_size() const; + + void set_line_spacing(float p_size); + float get_line_spacing() const; + + void set_autowrap_mode(TextServer::AutowrapMode p_mode); + TextServer::AutowrapMode get_autowrap_mode() const; + + void set_justification_flags(BitField p_flags); + BitField get_justification_flags() const; + + void set_text_direction(TextServer::Direction p_text_direction); + TextServer::Direction get_text_direction() const; + + void set_language(const String &p_language); + String get_language() const; + + void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser); + TextServer::StructuredTextParser get_structured_text_bidi_override() const; + + void set_structured_text_bidi_override_options(Array p_args); + Array get_structured_text_bidi_override_options() const; + + void set_uppercase(bool p_uppercase); + bool is_uppercase() const; + + void set_width(real_t p_width); + real_t get_width() const; + + void set_depth(real_t p_depth); + real_t get_depth() const; + + void set_curve_step(real_t p_step); + real_t get_curve_step() const; + + void set_pixel_size(real_t p_amount); + real_t get_pixel_size() const; + + void set_offset(const Point2 &p_offset); + Point2 get_offset() const; +}; + +VARIANT_ENUM_CAST(RibbonTrailMesh::Shape) + +#endif // PRIMITIVE_MESHES_H diff --git a/scene/resources/3d/separation_ray_shape_3d.cpp b/scene/resources/3d/separation_ray_shape_3d.cpp new file mode 100644 index 0000000000..07e93b8b79 --- /dev/null +++ b/scene/resources/3d/separation_ray_shape_3d.cpp @@ -0,0 +1,92 @@ +/**************************************************************************/ +/* separation_ray_shape_3d.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 "separation_ray_shape_3d.h" + +#include "servers/physics_server_3d.h" + +Vector SeparationRayShape3D::get_debug_mesh_lines() const { + Vector points = { + Vector3(), + Vector3(0, 0, get_length()) + }; + + return points; +} + +real_t SeparationRayShape3D::get_enclosing_radius() const { + return length; +} + +void SeparationRayShape3D::_update_shape() { + Dictionary d; + d["length"] = length; + d["slide_on_slope"] = slide_on_slope; + PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), d); + Shape3D::_update_shape(); +} + +void SeparationRayShape3D::set_length(float p_length) { + length = p_length; + _update_shape(); + emit_changed(); +} + +float SeparationRayShape3D::get_length() const { + return length; +} + +void SeparationRayShape3D::set_slide_on_slope(bool p_active) { + slide_on_slope = p_active; + _update_shape(); + emit_changed(); +} + +bool SeparationRayShape3D::get_slide_on_slope() const { + return slide_on_slope; +} + +void SeparationRayShape3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_length", "length"), &SeparationRayShape3D::set_length); + ClassDB::bind_method(D_METHOD("get_length"), &SeparationRayShape3D::get_length); + + ClassDB::bind_method(D_METHOD("set_slide_on_slope", "active"), &SeparationRayShape3D::set_slide_on_slope); + ClassDB::bind_method(D_METHOD("get_slide_on_slope"), &SeparationRayShape3D::get_slide_on_slope); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_length", "get_length"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_slope"), "set_slide_on_slope", "get_slide_on_slope"); +} + +SeparationRayShape3D::SeparationRayShape3D() : + Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_SEPARATION_RAY)) { + /* Code copied from setters to prevent the use of uninitialized variables */ + _update_shape(); + emit_changed(); +} diff --git a/scene/resources/3d/separation_ray_shape_3d.h b/scene/resources/3d/separation_ray_shape_3d.h new file mode 100644 index 0000000000..f24f0eae9e --- /dev/null +++ b/scene/resources/3d/separation_ray_shape_3d.h @@ -0,0 +1,58 @@ +/**************************************************************************/ +/* separation_ray_shape_3d.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 SEPARATION_RAY_SHAPE_3D_H +#define SEPARATION_RAY_SHAPE_3D_H + +#include "scene/resources/3d/shape_3d.h" + +class SeparationRayShape3D : public Shape3D { + GDCLASS(SeparationRayShape3D, Shape3D); + float length = 1.0; + bool slide_on_slope = false; + +protected: + static void _bind_methods(); + virtual void _update_shape() override; + +public: + void set_length(float p_length); + float get_length() const; + + void set_slide_on_slope(bool p_active); + bool get_slide_on_slope() const; + + virtual Vector get_debug_mesh_lines() const override; + virtual real_t get_enclosing_radius() const override; + + SeparationRayShape3D(); +}; + +#endif // SEPARATION_RAY_SHAPE_3D_H diff --git a/scene/resources/3d/shape_3d.cpp b/scene/resources/3d/shape_3d.cpp new file mode 100644 index 0000000000..5a79392ba5 --- /dev/null +++ b/scene/resources/3d/shape_3d.cpp @@ -0,0 +1,133 @@ +/**************************************************************************/ +/* shape_3d.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 "shape_3d.h" + +#include "core/os/os.h" +#include "scene/main/scene_tree.h" +#include "scene/resources/mesh.h" +#include "servers/physics_server_3d.h" + +void Shape3D::add_vertices_to_array(Vector &array, const Transform3D &p_xform) { + Vector toadd = get_debug_mesh_lines(); + + if (toadd.size()) { + int base = array.size(); + array.resize(base + toadd.size()); + Vector3 *w = array.ptrw(); + for (int i = 0; i < toadd.size(); i++) { + w[i + base] = p_xform.xform(toadd[i]); + } + } +} + +void Shape3D::set_custom_solver_bias(real_t p_bias) { + custom_bias = p_bias; + PhysicsServer3D::get_singleton()->shape_set_custom_solver_bias(shape, custom_bias); +} + +real_t Shape3D::get_custom_solver_bias() const { + return custom_bias; +} + +real_t Shape3D::get_margin() const { + return margin; +} + +void Shape3D::set_margin(real_t p_margin) { + margin = p_margin; + PhysicsServer3D::get_singleton()->shape_set_margin(shape, margin); +} + +Ref Shape3D::get_debug_mesh() { + if (debug_mesh_cache.is_valid()) { + return debug_mesh_cache; + } + + Vector lines = get_debug_mesh_lines(); + + debug_mesh_cache = Ref(memnew(ArrayMesh)); + + if (!lines.is_empty()) { + //make mesh + Vector array; + array.resize(lines.size()); + { + Vector3 *w = array.ptrw(); + for (int i = 0; i < lines.size(); i++) { + w[i] = lines[i]; + } + } + + Array arr; + arr.resize(Mesh::ARRAY_MAX); + arr[Mesh::ARRAY_VERTEX] = array; + + SceneTree *st = Object::cast_to(OS::get_singleton()->get_main_loop()); + + debug_mesh_cache->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, arr); + + if (st) { + debug_mesh_cache->surface_set_material(0, st->get_debug_collision_material()); + } + } + + return debug_mesh_cache; +} + +void Shape3D::_update_shape() { + emit_changed(); + debug_mesh_cache.unref(); +} + +void Shape3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_custom_solver_bias", "bias"), &Shape3D::set_custom_solver_bias); + ClassDB::bind_method(D_METHOD("get_custom_solver_bias"), &Shape3D::get_custom_solver_bias); + + ClassDB::bind_method(D_METHOD("set_margin", "margin"), &Shape3D::set_margin); + ClassDB::bind_method(D_METHOD("get_margin"), &Shape3D::get_margin); + + ClassDB::bind_method(D_METHOD("get_debug_mesh"), &Shape3D::get_debug_mesh); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "custom_solver_bias", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_custom_solver_bias", "get_custom_solver_bias"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "margin", PROPERTY_HINT_RANGE, "0,10,0.001,or_greater,suffix:m"), "set_margin", "get_margin"); +} + +Shape3D::Shape3D() { + ERR_PRINT("Default constructor must not be called!"); +} + +Shape3D::Shape3D(RID p_shape) : + shape(p_shape) {} + +Shape3D::~Shape3D() { + ERR_FAIL_NULL(PhysicsServer3D::get_singleton()); + PhysicsServer3D::get_singleton()->free(shape); +} diff --git a/scene/resources/3d/shape_3d.h b/scene/resources/3d/shape_3d.h new file mode 100644 index 0000000000..5e6cdbe421 --- /dev/null +++ b/scene/resources/3d/shape_3d.h @@ -0,0 +1,76 @@ +/**************************************************************************/ +/* shape_3d.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 SHAPE_3D_H +#define SHAPE_3D_H + +#include "core/io/resource.h" + +class ArrayMesh; + +class Shape3D : public Resource { + GDCLASS(Shape3D, Resource); + OBJ_SAVE_TYPE(Shape3D); + RES_BASE_EXTENSION("shape"); + RID shape; + real_t custom_bias = 0.0; + real_t margin = 0.04; + + Ref debug_mesh_cache; + +protected: + static void _bind_methods(); + + _FORCE_INLINE_ RID get_shape() const { return shape; } + Shape3D(RID p_shape); + + virtual void _update_shape(); + +public: + virtual RID get_rid() const override { return shape; } + + Ref get_debug_mesh(); + virtual Vector get_debug_mesh_lines() const = 0; // { return Vector(); } + /// Returns the radius of a sphere that fully enclose this shape + virtual real_t get_enclosing_radius() const = 0; + + void add_vertices_to_array(Vector &array, const Transform3D &p_xform); + + void set_custom_solver_bias(real_t p_bias); + real_t get_custom_solver_bias() const; + + real_t get_margin() const; + void set_margin(real_t p_margin); + + Shape3D(); + ~Shape3D(); +}; + +#endif // SHAPE_3D_H diff --git a/scene/resources/3d/skin.cpp b/scene/resources/3d/skin.cpp new file mode 100644 index 0000000000..a4fb371ece --- /dev/null +++ b/scene/resources/3d/skin.cpp @@ -0,0 +1,162 @@ +/**************************************************************************/ +/* skin.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 "skin.h" + +void Skin::set_bind_count(int p_size) { + ERR_FAIL_COND(p_size < 0); + binds.resize(p_size); + binds_ptr = binds.ptrw(); + bind_count = p_size; + emit_changed(); +} + +void Skin::add_bind(int p_bone, const Transform3D &p_pose) { + uint32_t index = bind_count; + set_bind_count(bind_count + 1); + set_bind_bone(index, p_bone); + set_bind_pose(index, p_pose); +} + +void Skin::add_named_bind(const String &p_name, const Transform3D &p_pose) { + uint32_t index = bind_count; + set_bind_count(bind_count + 1); + set_bind_name(index, p_name); + set_bind_pose(index, p_pose); +} + +void Skin::set_bind_name(int p_index, const StringName &p_name) { + ERR_FAIL_INDEX(p_index, bind_count); + bool notify_change = (binds_ptr[p_index].name != StringName()) != (p_name != StringName()); + binds_ptr[p_index].name = p_name; + emit_changed(); + if (notify_change) { + notify_property_list_changed(); + } +} + +void Skin::set_bind_bone(int p_index, int p_bone) { + ERR_FAIL_INDEX(p_index, bind_count); + binds_ptr[p_index].bone = p_bone; + emit_changed(); +} + +void Skin::set_bind_pose(int p_index, const Transform3D &p_pose) { + ERR_FAIL_INDEX(p_index, bind_count); + binds_ptr[p_index].pose = p_pose; + emit_changed(); +} + +void Skin::clear_binds() { + binds.clear(); + binds_ptr = nullptr; + bind_count = 0; + emit_changed(); +} + +void Skin::reset_state() { + clear_binds(); +} + +bool Skin::_set(const StringName &p_name, const Variant &p_value) { + String prop_name = p_name; + if (prop_name == "bind_count") { + set_bind_count(p_value); + return true; + } else if (prop_name.begins_with("bind/")) { + int index = prop_name.get_slicec('/', 1).to_int(); + String what = prop_name.get_slicec('/', 2); + if (what == "bone") { + set_bind_bone(index, p_value); + return true; + } else if (what == "name") { + set_bind_name(index, p_value); + return true; + } else if (what == "pose") { + set_bind_pose(index, p_value); + return true; + } + } + return false; +} + +bool Skin::_get(const StringName &p_name, Variant &r_ret) const { + String prop_name = p_name; + if (prop_name == "bind_count") { + r_ret = get_bind_count(); + return true; + } else if (prop_name.begins_with("bind/")) { + int index = prop_name.get_slicec('/', 1).to_int(); + String what = prop_name.get_slicec('/', 2); + if (what == "bone") { + r_ret = get_bind_bone(index); + return true; + } else if (what == "name") { + r_ret = get_bind_name(index); + return true; + } else if (what == "pose") { + r_ret = get_bind_pose(index); + return true; + } + } + return false; +} + +void Skin::_get_property_list(List *p_list) const { + p_list->push_back(PropertyInfo(Variant::INT, PNAME("bind_count"), PROPERTY_HINT_RANGE, "0,16384,1,or_greater")); + for (int i = 0; i < get_bind_count(); i++) { + const String prefix = vformat("%s/%d/", PNAME("bind"), i); + p_list->push_back(PropertyInfo(Variant::STRING_NAME, prefix + PNAME("name"))); + p_list->push_back(PropertyInfo(Variant::INT, prefix + PNAME("bone"), PROPERTY_HINT_RANGE, "0,16384,1,or_greater", get_bind_name(i) != StringName() ? PROPERTY_USAGE_NO_EDITOR : PROPERTY_USAGE_DEFAULT)); + p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, prefix + PNAME("pose"))); + } +} + +void Skin::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_bind_count", "bind_count"), &Skin::set_bind_count); + ClassDB::bind_method(D_METHOD("get_bind_count"), &Skin::get_bind_count); + + ClassDB::bind_method(D_METHOD("add_bind", "bone", "pose"), &Skin::add_bind); + ClassDB::bind_method(D_METHOD("add_named_bind", "name", "pose"), &Skin::add_named_bind); + + ClassDB::bind_method(D_METHOD("set_bind_pose", "bind_index", "pose"), &Skin::set_bind_pose); + ClassDB::bind_method(D_METHOD("get_bind_pose", "bind_index"), &Skin::get_bind_pose); + + ClassDB::bind_method(D_METHOD("set_bind_name", "bind_index", "name"), &Skin::set_bind_name); + ClassDB::bind_method(D_METHOD("get_bind_name", "bind_index"), &Skin::get_bind_name); + + ClassDB::bind_method(D_METHOD("set_bind_bone", "bind_index", "bone"), &Skin::set_bind_bone); + ClassDB::bind_method(D_METHOD("get_bind_bone", "bind_index"), &Skin::get_bind_bone); + + ClassDB::bind_method(D_METHOD("clear_binds"), &Skin::clear_binds); +} + +Skin::Skin() { +} diff --git a/scene/resources/3d/skin.h b/scene/resources/3d/skin.h new file mode 100644 index 0000000000..5df820660c --- /dev/null +++ b/scene/resources/3d/skin.h @@ -0,0 +1,89 @@ +/**************************************************************************/ +/* skin.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 SKIN_H +#define SKIN_H + +#include "core/io/resource.h" + +class Skin : public Resource { + GDCLASS(Skin, Resource) + + struct Bind { + int bone = -1; + StringName name; + Transform3D pose; + }; + + Vector binds; + + Bind *binds_ptr = nullptr; + int bind_count = 0; + +protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List *p_list) const; + + virtual void reset_state() override; + static void _bind_methods(); + +public: + void set_bind_count(int p_size); + inline int get_bind_count() const { return bind_count; } + + void add_bind(int p_bone, const Transform3D &p_pose); + void add_named_bind(const String &p_name, const Transform3D &p_pose); + + void set_bind_bone(int p_index, int p_bone); + void set_bind_pose(int p_index, const Transform3D &p_pose); + void set_bind_name(int p_index, const StringName &p_name); + + inline int get_bind_bone(int p_index) const { + ERR_FAIL_INDEX_V(p_index, bind_count, -1); + return binds_ptr[p_index].bone; + } + + inline StringName get_bind_name(int p_index) const { + ERR_FAIL_INDEX_V(p_index, bind_count, StringName()); + return binds_ptr[p_index].name; + } + + inline Transform3D get_bind_pose(int p_index) const { + ERR_FAIL_INDEX_V(p_index, bind_count, Transform3D()); + return binds_ptr[p_index].pose; + } + + void clear_binds(); + + Skin(); +}; + +#endif // SKIN_H diff --git a/scene/resources/3d/sky_material.cpp b/scene/resources/3d/sky_material.cpp new file mode 100644 index 0000000000..640261d615 --- /dev/null +++ b/scene/resources/3d/sky_material.cpp @@ -0,0 +1,806 @@ +/**************************************************************************/ +/* sky_material.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 "sky_material.h" + +#include "core/config/project_settings.h" +#include "core/version.h" + +Mutex ProceduralSkyMaterial::shader_mutex; +RID ProceduralSkyMaterial::shader_cache[2]; + +void ProceduralSkyMaterial::set_sky_top_color(const Color &p_sky_top) { + sky_top_color = p_sky_top; + RS::get_singleton()->material_set_param(_get_material(), "sky_top_color", sky_top_color); +} + +Color ProceduralSkyMaterial::get_sky_top_color() const { + return sky_top_color; +} + +void ProceduralSkyMaterial::set_sky_horizon_color(const Color &p_sky_horizon) { + sky_horizon_color = p_sky_horizon; + RS::get_singleton()->material_set_param(_get_material(), "sky_horizon_color", sky_horizon_color); +} + +Color ProceduralSkyMaterial::get_sky_horizon_color() const { + return sky_horizon_color; +} + +void ProceduralSkyMaterial::set_sky_curve(float p_curve) { + sky_curve = p_curve; + RS::get_singleton()->material_set_param(_get_material(), "sky_curve", sky_curve); +} + +float ProceduralSkyMaterial::get_sky_curve() const { + return sky_curve; +} + +void ProceduralSkyMaterial::set_sky_energy_multiplier(float p_multiplier) { + sky_energy_multiplier = p_multiplier; + RS::get_singleton()->material_set_param(_get_material(), "sky_energy", sky_energy_multiplier); +} + +float ProceduralSkyMaterial::get_sky_energy_multiplier() const { + return sky_energy_multiplier; +} + +void ProceduralSkyMaterial::set_sky_cover(const Ref &p_sky_cover) { + sky_cover = p_sky_cover; + if (p_sky_cover.is_valid()) { + RS::get_singleton()->material_set_param(_get_material(), "sky_cover", p_sky_cover->get_rid()); + } else { + RS::get_singleton()->material_set_param(_get_material(), "sky_cover", Variant()); + } +} + +Ref ProceduralSkyMaterial::get_sky_cover() const { + return sky_cover; +} + +void ProceduralSkyMaterial::set_sky_cover_modulate(const Color &p_sky_cover_modulate) { + sky_cover_modulate = p_sky_cover_modulate; + RS::get_singleton()->material_set_param(_get_material(), "sky_cover_modulate", sky_cover_modulate); +} + +Color ProceduralSkyMaterial::get_sky_cover_modulate() const { + return sky_cover_modulate; +} + +void ProceduralSkyMaterial::set_ground_bottom_color(const Color &p_ground_bottom) { + ground_bottom_color = p_ground_bottom; + RS::get_singleton()->material_set_param(_get_material(), "ground_bottom_color", ground_bottom_color); +} + +Color ProceduralSkyMaterial::get_ground_bottom_color() const { + return ground_bottom_color; +} + +void ProceduralSkyMaterial::set_ground_horizon_color(const Color &p_ground_horizon) { + ground_horizon_color = p_ground_horizon; + RS::get_singleton()->material_set_param(_get_material(), "ground_horizon_color", ground_horizon_color); +} + +Color ProceduralSkyMaterial::get_ground_horizon_color() const { + return ground_horizon_color; +} + +void ProceduralSkyMaterial::set_ground_curve(float p_curve) { + ground_curve = p_curve; + RS::get_singleton()->material_set_param(_get_material(), "ground_curve", ground_curve); +} + +float ProceduralSkyMaterial::get_ground_curve() const { + return ground_curve; +} + +void ProceduralSkyMaterial::set_ground_energy_multiplier(float p_multiplier) { + ground_energy_multiplier = p_multiplier; + RS::get_singleton()->material_set_param(_get_material(), "ground_energy", ground_energy_multiplier); +} + +float ProceduralSkyMaterial::get_ground_energy_multiplier() const { + return ground_energy_multiplier; +} + +void ProceduralSkyMaterial::set_sun_angle_max(float p_angle) { + sun_angle_max = p_angle; + RS::get_singleton()->material_set_param(_get_material(), "sun_angle_max", Math::deg_to_rad(sun_angle_max)); +} + +float ProceduralSkyMaterial::get_sun_angle_max() const { + return sun_angle_max; +} + +void ProceduralSkyMaterial::set_sun_curve(float p_curve) { + sun_curve = p_curve; + RS::get_singleton()->material_set_param(_get_material(), "sun_curve", sun_curve); +} + +float ProceduralSkyMaterial::get_sun_curve() const { + return sun_curve; +} + +void ProceduralSkyMaterial::set_use_debanding(bool p_use_debanding) { + use_debanding = p_use_debanding; + _update_shader(); + // Only set if shader already compiled + if (shader_set) { + RS::get_singleton()->material_set_shader(_get_material(), shader_cache[int(use_debanding)]); + } +} + +bool ProceduralSkyMaterial::get_use_debanding() const { + return use_debanding; +} + +void ProceduralSkyMaterial::set_energy_multiplier(float p_multiplier) { + global_energy_multiplier = p_multiplier; + RS::get_singleton()->material_set_param(_get_material(), "exposure", global_energy_multiplier); +} + +float ProceduralSkyMaterial::get_energy_multiplier() const { + return global_energy_multiplier; +} + +Shader::Mode ProceduralSkyMaterial::get_shader_mode() const { + return Shader::MODE_SKY; +} + +RID ProceduralSkyMaterial::get_rid() const { + _update_shader(); + if (!shader_set) { + RS::get_singleton()->material_set_shader(_get_material(), shader_cache[1 - int(use_debanding)]); + RS::get_singleton()->material_set_shader(_get_material(), shader_cache[int(use_debanding)]); + shader_set = true; + } + return _get_material(); +} + +RID ProceduralSkyMaterial::get_shader_rid() const { + _update_shader(); + return shader_cache[int(use_debanding)]; +} + +void ProceduralSkyMaterial::_validate_property(PropertyInfo &p_property) const { + if ((p_property.name == "sky_luminance" || p_property.name == "ground_luminance") && !GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } +} + +void ProceduralSkyMaterial::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_sky_top_color", "color"), &ProceduralSkyMaterial::set_sky_top_color); + ClassDB::bind_method(D_METHOD("get_sky_top_color"), &ProceduralSkyMaterial::get_sky_top_color); + + ClassDB::bind_method(D_METHOD("set_sky_horizon_color", "color"), &ProceduralSkyMaterial::set_sky_horizon_color); + ClassDB::bind_method(D_METHOD("get_sky_horizon_color"), &ProceduralSkyMaterial::get_sky_horizon_color); + + ClassDB::bind_method(D_METHOD("set_sky_curve", "curve"), &ProceduralSkyMaterial::set_sky_curve); + ClassDB::bind_method(D_METHOD("get_sky_curve"), &ProceduralSkyMaterial::get_sky_curve); + + ClassDB::bind_method(D_METHOD("set_sky_energy_multiplier", "multiplier"), &ProceduralSkyMaterial::set_sky_energy_multiplier); + ClassDB::bind_method(D_METHOD("get_sky_energy_multiplier"), &ProceduralSkyMaterial::get_sky_energy_multiplier); + + ClassDB::bind_method(D_METHOD("set_sky_cover", "sky_cover"), &ProceduralSkyMaterial::set_sky_cover); + ClassDB::bind_method(D_METHOD("get_sky_cover"), &ProceduralSkyMaterial::get_sky_cover); + + ClassDB::bind_method(D_METHOD("set_sky_cover_modulate", "color"), &ProceduralSkyMaterial::set_sky_cover_modulate); + ClassDB::bind_method(D_METHOD("get_sky_cover_modulate"), &ProceduralSkyMaterial::get_sky_cover_modulate); + + ClassDB::bind_method(D_METHOD("set_ground_bottom_color", "color"), &ProceduralSkyMaterial::set_ground_bottom_color); + ClassDB::bind_method(D_METHOD("get_ground_bottom_color"), &ProceduralSkyMaterial::get_ground_bottom_color); + + ClassDB::bind_method(D_METHOD("set_ground_horizon_color", "color"), &ProceduralSkyMaterial::set_ground_horizon_color); + ClassDB::bind_method(D_METHOD("get_ground_horizon_color"), &ProceduralSkyMaterial::get_ground_horizon_color); + + ClassDB::bind_method(D_METHOD("set_ground_curve", "curve"), &ProceduralSkyMaterial::set_ground_curve); + ClassDB::bind_method(D_METHOD("get_ground_curve"), &ProceduralSkyMaterial::get_ground_curve); + + ClassDB::bind_method(D_METHOD("set_ground_energy_multiplier", "energy"), &ProceduralSkyMaterial::set_ground_energy_multiplier); + ClassDB::bind_method(D_METHOD("get_ground_energy_multiplier"), &ProceduralSkyMaterial::get_ground_energy_multiplier); + + ClassDB::bind_method(D_METHOD("set_sun_angle_max", "degrees"), &ProceduralSkyMaterial::set_sun_angle_max); + ClassDB::bind_method(D_METHOD("get_sun_angle_max"), &ProceduralSkyMaterial::get_sun_angle_max); + + ClassDB::bind_method(D_METHOD("set_sun_curve", "curve"), &ProceduralSkyMaterial::set_sun_curve); + ClassDB::bind_method(D_METHOD("get_sun_curve"), &ProceduralSkyMaterial::get_sun_curve); + + ClassDB::bind_method(D_METHOD("set_use_debanding", "use_debanding"), &ProceduralSkyMaterial::set_use_debanding); + ClassDB::bind_method(D_METHOD("get_use_debanding"), &ProceduralSkyMaterial::get_use_debanding); + + ClassDB::bind_method(D_METHOD("set_energy_multiplier", "multiplier"), &ProceduralSkyMaterial::set_energy_multiplier); + ClassDB::bind_method(D_METHOD("get_energy_multiplier"), &ProceduralSkyMaterial::get_energy_multiplier); + + ADD_GROUP("Sky", "sky_"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "sky_top_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_sky_top_color", "get_sky_top_color"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "sky_horizon_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_sky_horizon_color", "get_sky_horizon_color"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sky_curve", PROPERTY_HINT_EXP_EASING), "set_sky_curve", "get_sky_curve"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sky_energy_multiplier", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_sky_energy_multiplier", "get_sky_energy_multiplier"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "sky_cover", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_sky_cover", "get_sky_cover"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "sky_cover_modulate"), "set_sky_cover_modulate", "get_sky_cover_modulate"); + + ADD_GROUP("Ground", "ground_"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "ground_bottom_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_ground_bottom_color", "get_ground_bottom_color"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "ground_horizon_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_ground_horizon_color", "get_ground_horizon_color"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ground_curve", PROPERTY_HINT_EXP_EASING), "set_ground_curve", "get_ground_curve"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ground_energy_multiplier", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_ground_energy_multiplier", "get_ground_energy_multiplier"); + + ADD_GROUP("Sun", "sun_"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sun_angle_max", PROPERTY_HINT_RANGE, "0,360,0.01,degrees"), "set_sun_angle_max", "get_sun_angle_max"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sun_curve", PROPERTY_HINT_EXP_EASING), "set_sun_curve", "get_sun_curve"); + + ADD_GROUP("", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_debanding"), "set_use_debanding", "get_use_debanding"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "energy_multiplier", PROPERTY_HINT_RANGE, "0,128,0.01"), "set_energy_multiplier", "get_energy_multiplier"); +} + +void ProceduralSkyMaterial::cleanup_shader() { + if (shader_cache[0].is_valid()) { + RS::get_singleton()->free(shader_cache[0]); + RS::get_singleton()->free(shader_cache[1]); + } +} + +void ProceduralSkyMaterial::_update_shader() { + shader_mutex.lock(); + if (shader_cache[0].is_null()) { + for (int i = 0; i < 2; i++) { + shader_cache[i] = RS::get_singleton()->shader_create(); + + // Add a comment to describe the shader origin (useful when converting to ShaderMaterial). + RS::get_singleton()->shader_set_code(shader_cache[i], vformat(R"( +// NOTE: Shader automatically converted from )" VERSION_NAME " " VERSION_FULL_CONFIG R"('s ProceduralSkyMaterial. + +shader_type sky; +%s + +uniform vec4 sky_top_color : source_color = vec4(0.385, 0.454, 0.55, 1.0); +uniform vec4 sky_horizon_color : source_color = vec4(0.646, 0.656, 0.67, 1.0); +uniform float sky_curve : hint_range(0, 1) = 0.15; +uniform float sky_energy = 1.0; // In Lux. +uniform sampler2D sky_cover : filter_linear, source_color, hint_default_black; +uniform vec4 sky_cover_modulate : source_color = vec4(1.0, 1.0, 1.0, 1.0); +uniform vec4 ground_bottom_color : source_color = vec4(0.2, 0.169, 0.133, 1.0); +uniform vec4 ground_horizon_color : source_color = vec4(0.646, 0.656, 0.67, 1.0); +uniform float ground_curve : hint_range(0, 1) = 0.02; +uniform float ground_energy = 1.0; +uniform float sun_angle_max = 30.0; +uniform float sun_curve : hint_range(0, 1) = 0.15; +uniform float exposure : hint_range(0, 128) = 1.0; + +void sky() { + float v_angle = acos(clamp(EYEDIR.y, -1.0, 1.0)); + float c = (1.0 - v_angle / (PI * 0.5)); + vec3 sky = mix(sky_horizon_color.rgb, sky_top_color.rgb, clamp(1.0 - pow(1.0 - c, 1.0 / sky_curve), 0.0, 1.0)); + sky *= sky_energy; + + if (LIGHT0_ENABLED) { + float sun_angle = acos(dot(LIGHT0_DIRECTION, EYEDIR)); + if (sun_angle < LIGHT0_SIZE) { + sky = LIGHT0_COLOR * LIGHT0_ENERGY; + } else if (sun_angle < sun_angle_max) { + float c2 = (sun_angle - LIGHT0_SIZE) / (sun_angle_max - LIGHT0_SIZE); + sky = mix(LIGHT0_COLOR * LIGHT0_ENERGY, sky, clamp(1.0 - pow(1.0 - c2, 1.0 / sun_curve), 0.0, 1.0)); + } + } + + if (LIGHT1_ENABLED) { + float sun_angle = acos(dot(LIGHT1_DIRECTION, EYEDIR)); + if (sun_angle < LIGHT1_SIZE) { + sky = LIGHT1_COLOR * LIGHT1_ENERGY; + } else if (sun_angle < sun_angle_max) { + float c2 = (sun_angle - LIGHT1_SIZE) / (sun_angle_max - LIGHT1_SIZE); + sky = mix(LIGHT1_COLOR * LIGHT1_ENERGY, sky, clamp(1.0 - pow(1.0 - c2, 1.0 / sun_curve), 0.0, 1.0)); + } + } + + if (LIGHT2_ENABLED) { + float sun_angle = acos(dot(LIGHT2_DIRECTION, EYEDIR)); + if (sun_angle < LIGHT2_SIZE) { + sky = LIGHT2_COLOR * LIGHT2_ENERGY; + } else if (sun_angle < sun_angle_max) { + float c2 = (sun_angle - LIGHT2_SIZE) / (sun_angle_max - LIGHT2_SIZE); + sky = mix(LIGHT2_COLOR * LIGHT2_ENERGY, sky, clamp(1.0 - pow(1.0 - c2, 1.0 / sun_curve), 0.0, 1.0)); + } + } + + if (LIGHT3_ENABLED) { + float sun_angle = acos(dot(LIGHT3_DIRECTION, EYEDIR)); + if (sun_angle < LIGHT3_SIZE) { + sky = LIGHT3_COLOR * LIGHT3_ENERGY; + } else if (sun_angle < sun_angle_max) { + float c2 = (sun_angle - LIGHT3_SIZE) / (sun_angle_max - LIGHT3_SIZE); + sky = mix(LIGHT3_COLOR * LIGHT3_ENERGY, sky, clamp(1.0 - pow(1.0 - c2, 1.0 / sun_curve), 0.0, 1.0)); + } + } + + vec4 sky_cover_texture = texture(sky_cover, SKY_COORDS); + sky += (sky_cover_texture.rgb * sky_cover_modulate.rgb) * sky_cover_texture.a * sky_cover_modulate.a * sky_energy; + + c = (v_angle - (PI * 0.5)) / (PI * 0.5); + vec3 ground = mix(ground_horizon_color.rgb, ground_bottom_color.rgb, clamp(1.0 - pow(1.0 - c, 1.0 / ground_curve), 0.0, 1.0)); + ground *= ground_energy; + + COLOR = mix(ground, sky, step(0.0, EYEDIR.y)) * exposure; +} +)", + i ? "render_mode use_debanding;" : "")); + } + } + shader_mutex.unlock(); +} + +ProceduralSkyMaterial::ProceduralSkyMaterial() { + set_sky_top_color(Color(0.385, 0.454, 0.55)); + set_sky_horizon_color(Color(0.6463, 0.6558, 0.6708)); + set_sky_curve(0.15); + set_sky_energy_multiplier(1.0); + set_sky_cover_modulate(Color(1, 1, 1)); + + set_ground_bottom_color(Color(0.2, 0.169, 0.133)); + set_ground_horizon_color(Color(0.6463, 0.6558, 0.6708)); + set_ground_curve(0.02); + set_ground_energy_multiplier(1.0); + + set_sun_angle_max(30.0); + set_sun_curve(0.15); + set_use_debanding(true); + set_energy_multiplier(1.0); +} + +ProceduralSkyMaterial::~ProceduralSkyMaterial() { +} + +///////////////////////////////////////// +/* PanoramaSkyMaterial */ + +void PanoramaSkyMaterial::set_panorama(const Ref &p_panorama) { + panorama = p_panorama; + if (p_panorama.is_valid()) { + RS::get_singleton()->material_set_param(_get_material(), "source_panorama", p_panorama->get_rid()); + } else { + RS::get_singleton()->material_set_param(_get_material(), "source_panorama", Variant()); + } +} + +Ref PanoramaSkyMaterial::get_panorama() const { + return panorama; +} + +void PanoramaSkyMaterial::set_filtering_enabled(bool p_enabled) { + filter = p_enabled; + notify_property_list_changed(); + _update_shader(); + // Only set if shader already compiled + if (shader_set) { + RS::get_singleton()->material_set_shader(_get_material(), shader_cache[int(filter)]); + } +} + +bool PanoramaSkyMaterial::is_filtering_enabled() const { + return filter; +} + +void PanoramaSkyMaterial::set_energy_multiplier(float p_multiplier) { + energy_multiplier = p_multiplier; + RS::get_singleton()->material_set_param(_get_material(), "exposure", energy_multiplier); +} + +float PanoramaSkyMaterial::get_energy_multiplier() const { + return energy_multiplier; +} + +Shader::Mode PanoramaSkyMaterial::get_shader_mode() const { + return Shader::MODE_SKY; +} + +RID PanoramaSkyMaterial::get_rid() const { + _update_shader(); + // Don't compile shaders until first use, then compile both + if (!shader_set) { + RS::get_singleton()->material_set_shader(_get_material(), shader_cache[1 - int(filter)]); + RS::get_singleton()->material_set_shader(_get_material(), shader_cache[int(filter)]); + shader_set = true; + } + return _get_material(); +} + +RID PanoramaSkyMaterial::get_shader_rid() const { + _update_shader(); + return shader_cache[int(filter)]; +} + +void PanoramaSkyMaterial::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_panorama", "texture"), &PanoramaSkyMaterial::set_panorama); + ClassDB::bind_method(D_METHOD("get_panorama"), &PanoramaSkyMaterial::get_panorama); + + ClassDB::bind_method(D_METHOD("set_filtering_enabled", "enabled"), &PanoramaSkyMaterial::set_filtering_enabled); + ClassDB::bind_method(D_METHOD("is_filtering_enabled"), &PanoramaSkyMaterial::is_filtering_enabled); + + ClassDB::bind_method(D_METHOD("set_energy_multiplier", "multiplier"), &PanoramaSkyMaterial::set_energy_multiplier); + ClassDB::bind_method(D_METHOD("get_energy_multiplier"), &PanoramaSkyMaterial::get_energy_multiplier); + + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "panorama", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_panorama", "get_panorama"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter"), "set_filtering_enabled", "is_filtering_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "energy_multiplier", PROPERTY_HINT_RANGE, "0,128,0.01"), "set_energy_multiplier", "get_energy_multiplier"); +} + +Mutex PanoramaSkyMaterial::shader_mutex; +RID PanoramaSkyMaterial::shader_cache[2]; + +void PanoramaSkyMaterial::cleanup_shader() { + if (shader_cache[0].is_valid()) { + RS::get_singleton()->free(shader_cache[0]); + RS::get_singleton()->free(shader_cache[1]); + } +} + +void PanoramaSkyMaterial::_update_shader() { + shader_mutex.lock(); + if (shader_cache[0].is_null()) { + for (int i = 0; i < 2; i++) { + shader_cache[i] = RS::get_singleton()->shader_create(); + + // Add a comment to describe the shader origin (useful when converting to ShaderMaterial). + RS::get_singleton()->shader_set_code(shader_cache[i], vformat(R"( +// NOTE: Shader automatically converted from )" VERSION_NAME " " VERSION_FULL_CONFIG R"('s PanoramaSkyMaterial. + +shader_type sky; + +uniform sampler2D source_panorama : %s, source_color, hint_default_black; +uniform float exposure : hint_range(0, 128) = 1.0; + +void sky() { + COLOR = texture(source_panorama, SKY_COORDS).rgb * exposure; +} +)", + i ? "filter_linear" : "filter_nearest")); + } + } + + shader_mutex.unlock(); +} + +PanoramaSkyMaterial::PanoramaSkyMaterial() { + set_energy_multiplier(1.0); +} + +PanoramaSkyMaterial::~PanoramaSkyMaterial() { +} + +////////////////////////////////// +/* PhysicalSkyMaterial */ + +void PhysicalSkyMaterial::set_rayleigh_coefficient(float p_rayleigh) { + rayleigh = p_rayleigh; + RS::get_singleton()->material_set_param(_get_material(), "rayleigh", rayleigh); +} + +float PhysicalSkyMaterial::get_rayleigh_coefficient() const { + return rayleigh; +} + +void PhysicalSkyMaterial::set_rayleigh_color(Color p_rayleigh_color) { + rayleigh_color = p_rayleigh_color; + RS::get_singleton()->material_set_param(_get_material(), "rayleigh_color", rayleigh_color); +} + +Color PhysicalSkyMaterial::get_rayleigh_color() const { + return rayleigh_color; +} + +void PhysicalSkyMaterial::set_mie_coefficient(float p_mie) { + mie = p_mie; + RS::get_singleton()->material_set_param(_get_material(), "mie", mie); +} + +float PhysicalSkyMaterial::get_mie_coefficient() const { + return mie; +} + +void PhysicalSkyMaterial::set_mie_eccentricity(float p_eccentricity) { + mie_eccentricity = p_eccentricity; + RS::get_singleton()->material_set_param(_get_material(), "mie_eccentricity", mie_eccentricity); +} + +float PhysicalSkyMaterial::get_mie_eccentricity() const { + return mie_eccentricity; +} + +void PhysicalSkyMaterial::set_mie_color(Color p_mie_color) { + mie_color = p_mie_color; + RS::get_singleton()->material_set_param(_get_material(), "mie_color", mie_color); +} + +Color PhysicalSkyMaterial::get_mie_color() const { + return mie_color; +} + +void PhysicalSkyMaterial::set_turbidity(float p_turbidity) { + turbidity = p_turbidity; + RS::get_singleton()->material_set_param(_get_material(), "turbidity", turbidity); +} + +float PhysicalSkyMaterial::get_turbidity() const { + return turbidity; +} + +void PhysicalSkyMaterial::set_sun_disk_scale(float p_sun_disk_scale) { + sun_disk_scale = p_sun_disk_scale; + RS::get_singleton()->material_set_param(_get_material(), "sun_disk_scale", sun_disk_scale); +} + +float PhysicalSkyMaterial::get_sun_disk_scale() const { + return sun_disk_scale; +} + +void PhysicalSkyMaterial::set_ground_color(Color p_ground_color) { + ground_color = p_ground_color; + RS::get_singleton()->material_set_param(_get_material(), "ground_color", ground_color); +} + +Color PhysicalSkyMaterial::get_ground_color() const { + return ground_color; +} + +void PhysicalSkyMaterial::set_energy_multiplier(float p_multiplier) { + energy_multiplier = p_multiplier; + RS::get_singleton()->material_set_param(_get_material(), "exposure", energy_multiplier); +} + +float PhysicalSkyMaterial::get_energy_multiplier() const { + return energy_multiplier; +} + +void PhysicalSkyMaterial::set_use_debanding(bool p_use_debanding) { + use_debanding = p_use_debanding; + _update_shader(); + // Only set if shader already compiled + if (shader_set) { + RS::get_singleton()->material_set_shader(_get_material(), shader_cache[int(use_debanding)]); + } +} + +bool PhysicalSkyMaterial::get_use_debanding() const { + return use_debanding; +} + +void PhysicalSkyMaterial::set_night_sky(const Ref &p_night_sky) { + night_sky = p_night_sky; + if (p_night_sky.is_valid()) { + RS::get_singleton()->material_set_param(_get_material(), "night_sky", p_night_sky->get_rid()); + } else { + RS::get_singleton()->material_set_param(_get_material(), "night_sky", Variant()); + } +} + +Ref PhysicalSkyMaterial::get_night_sky() const { + return night_sky; +} + +Shader::Mode PhysicalSkyMaterial::get_shader_mode() const { + return Shader::MODE_SKY; +} + +RID PhysicalSkyMaterial::get_rid() const { + _update_shader(); + if (!shader_set) { + RS::get_singleton()->material_set_shader(_get_material(), shader_cache[1 - int(use_debanding)]); + RS::get_singleton()->material_set_shader(_get_material(), shader_cache[int(use_debanding)]); + shader_set = true; + } + return _get_material(); +} + +RID PhysicalSkyMaterial::get_shader_rid() const { + _update_shader(); + return shader_cache[int(use_debanding)]; +} + +void PhysicalSkyMaterial::_validate_property(PropertyInfo &p_property) const { + if (p_property.name == "exposure_value" && !GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } +} + +Mutex PhysicalSkyMaterial::shader_mutex; +RID PhysicalSkyMaterial::shader_cache[2]; + +void PhysicalSkyMaterial::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_rayleigh_coefficient", "rayleigh"), &PhysicalSkyMaterial::set_rayleigh_coefficient); + ClassDB::bind_method(D_METHOD("get_rayleigh_coefficient"), &PhysicalSkyMaterial::get_rayleigh_coefficient); + + ClassDB::bind_method(D_METHOD("set_rayleigh_color", "color"), &PhysicalSkyMaterial::set_rayleigh_color); + ClassDB::bind_method(D_METHOD("get_rayleigh_color"), &PhysicalSkyMaterial::get_rayleigh_color); + + ClassDB::bind_method(D_METHOD("set_mie_coefficient", "mie"), &PhysicalSkyMaterial::set_mie_coefficient); + ClassDB::bind_method(D_METHOD("get_mie_coefficient"), &PhysicalSkyMaterial::get_mie_coefficient); + + ClassDB::bind_method(D_METHOD("set_mie_eccentricity", "eccentricity"), &PhysicalSkyMaterial::set_mie_eccentricity); + ClassDB::bind_method(D_METHOD("get_mie_eccentricity"), &PhysicalSkyMaterial::get_mie_eccentricity); + + ClassDB::bind_method(D_METHOD("set_mie_color", "color"), &PhysicalSkyMaterial::set_mie_color); + ClassDB::bind_method(D_METHOD("get_mie_color"), &PhysicalSkyMaterial::get_mie_color); + + ClassDB::bind_method(D_METHOD("set_turbidity", "turbidity"), &PhysicalSkyMaterial::set_turbidity); + ClassDB::bind_method(D_METHOD("get_turbidity"), &PhysicalSkyMaterial::get_turbidity); + + ClassDB::bind_method(D_METHOD("set_sun_disk_scale", "scale"), &PhysicalSkyMaterial::set_sun_disk_scale); + ClassDB::bind_method(D_METHOD("get_sun_disk_scale"), &PhysicalSkyMaterial::get_sun_disk_scale); + + ClassDB::bind_method(D_METHOD("set_ground_color", "color"), &PhysicalSkyMaterial::set_ground_color); + ClassDB::bind_method(D_METHOD("get_ground_color"), &PhysicalSkyMaterial::get_ground_color); + + ClassDB::bind_method(D_METHOD("set_energy_multiplier", "multiplier"), &PhysicalSkyMaterial::set_energy_multiplier); + ClassDB::bind_method(D_METHOD("get_energy_multiplier"), &PhysicalSkyMaterial::get_energy_multiplier); + + ClassDB::bind_method(D_METHOD("set_use_debanding", "use_debanding"), &PhysicalSkyMaterial::set_use_debanding); + ClassDB::bind_method(D_METHOD("get_use_debanding"), &PhysicalSkyMaterial::get_use_debanding); + + ClassDB::bind_method(D_METHOD("set_night_sky", "night_sky"), &PhysicalSkyMaterial::set_night_sky); + ClassDB::bind_method(D_METHOD("get_night_sky"), &PhysicalSkyMaterial::get_night_sky); + + ADD_GROUP("Rayleigh", "rayleigh_"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rayleigh_coefficient", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_rayleigh_coefficient", "get_rayleigh_coefficient"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "rayleigh_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_rayleigh_color", "get_rayleigh_color"); + + ADD_GROUP("Mie", "mie_"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mie_coefficient", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_mie_coefficient", "get_mie_coefficient"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mie_eccentricity", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_mie_eccentricity", "get_mie_eccentricity"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "mie_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_mie_color", "get_mie_color"); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "turbidity", PROPERTY_HINT_RANGE, "0,1000,0.01"), "set_turbidity", "get_turbidity"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sun_disk_scale", PROPERTY_HINT_RANGE, "0,360,0.01"), "set_sun_disk_scale", "get_sun_disk_scale"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "ground_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_ground_color", "get_ground_color"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "energy_multiplier", PROPERTY_HINT_RANGE, "0,128,0.01"), "set_energy_multiplier", "get_energy_multiplier"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_debanding"), "set_use_debanding", "get_use_debanding"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "night_sky", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_night_sky", "get_night_sky"); +} + +void PhysicalSkyMaterial::cleanup_shader() { + if (shader_cache[0].is_valid()) { + RS::get_singleton()->free(shader_cache[0]); + RS::get_singleton()->free(shader_cache[1]); + } +} + +void PhysicalSkyMaterial::_update_shader() { + shader_mutex.lock(); + if (shader_cache[0].is_null()) { + for (int i = 0; i < 2; i++) { + shader_cache[i] = RS::get_singleton()->shader_create(); + + // Add a comment to describe the shader origin (useful when converting to ShaderMaterial). + RS::get_singleton()->shader_set_code(shader_cache[i], vformat(R"( +// NOTE: Shader automatically converted from )" VERSION_NAME " " VERSION_FULL_CONFIG R"('s PhysicalSkyMaterial. + +shader_type sky; +%s + +uniform float rayleigh : hint_range(0, 64) = 2.0; +uniform vec4 rayleigh_color : source_color = vec4(0.3, 0.405, 0.6, 1.0); +uniform float mie : hint_range(0, 1) = 0.005; +uniform float mie_eccentricity : hint_range(-1, 1) = 0.8; +uniform vec4 mie_color : source_color = vec4(0.69, 0.729, 0.812, 1.0); + +uniform float turbidity : hint_range(0, 1000) = 10.0; +uniform float sun_disk_scale : hint_range(0, 360) = 1.0; +uniform vec4 ground_color : source_color = vec4(0.1, 0.07, 0.034, 1.0); +uniform float exposure : hint_range(0, 128) = 1.0; + +uniform sampler2D night_sky : filter_linear, source_color, hint_default_black; + +const vec3 UP = vec3( 0.0, 1.0, 0.0 ); + +// Optical length at zenith for molecules. +const float rayleigh_zenith_size = 8.4e3; +const float mie_zenith_size = 1.25e3; + +float henyey_greenstein(float cos_theta, float g) { + const float k = 0.0795774715459; + return k * (1.0 - g * g) / (pow(1.0 + g * g - 2.0 * g * cos_theta, 1.5)); +} + +void sky() { + if (LIGHT0_ENABLED) { + float zenith_angle = clamp( dot(UP, normalize(LIGHT0_DIRECTION)), -1.0, 1.0 ); + float sun_energy = max(0.0, 1.0 - exp(-((PI * 0.5) - acos(zenith_angle)))) * LIGHT0_ENERGY; + float sun_fade = 1.0 - clamp(1.0 - exp(LIGHT0_DIRECTION.y), 0.0, 1.0); + + // Rayleigh coefficients. + float rayleigh_coefficient = rayleigh - ( 1.0 * ( 1.0 - sun_fade ) ); + vec3 rayleigh_beta = rayleigh_coefficient * rayleigh_color.rgb * 0.0001; + // mie coefficients from Preetham + vec3 mie_beta = turbidity * mie * mie_color.rgb * 0.000434; + + // Optical length. + float zenith = acos(max(0.0, dot(UP, EYEDIR))); + float optical_mass = 1.0 / (cos(zenith) + 0.15 * pow(93.885 - degrees(zenith), -1.253)); + float rayleigh_scatter = rayleigh_zenith_size * optical_mass; + float mie_scatter = mie_zenith_size * optical_mass; + + // Light extinction based on thickness of atmosphere. + vec3 extinction = exp(-(rayleigh_beta * rayleigh_scatter + mie_beta * mie_scatter)); + + // In scattering. + float cos_theta = dot(EYEDIR, normalize(LIGHT0_DIRECTION)); + + float rayleigh_phase = (3.0 / (16.0 * PI)) * (1.0 + pow(cos_theta * 0.5 + 0.5, 2.0)); + vec3 betaRTheta = rayleigh_beta * rayleigh_phase; + + float mie_phase = henyey_greenstein(cos_theta, mie_eccentricity); + vec3 betaMTheta = mie_beta * mie_phase; + + vec3 Lin = pow(sun_energy * ((betaRTheta + betaMTheta) / (rayleigh_beta + mie_beta)) * (1.0 - extinction), vec3(1.5)); + // Hack from https://github.com/mrdoob/three.js/blob/master/examples/jsm/objects/Sky.js + Lin *= mix(vec3(1.0), pow(sun_energy * ((betaRTheta + betaMTheta) / (rayleigh_beta + mie_beta)) * extinction, vec3(0.5)), clamp(pow(1.0 - zenith_angle, 5.0), 0.0, 1.0)); + + // Hack in the ground color. + Lin *= mix(ground_color.rgb, vec3(1.0), smoothstep(-0.1, 0.1, dot(UP, EYEDIR))); + + // Solar disk and out-scattering. + float sunAngularDiameterCos = cos(LIGHT0_SIZE * sun_disk_scale); + float sunAngularDiameterCos2 = cos(LIGHT0_SIZE * sun_disk_scale*0.5); + float sundisk = smoothstep(sunAngularDiameterCos, sunAngularDiameterCos2, cos_theta); + vec3 L0 = (sun_energy * extinction) * sundisk * LIGHT0_COLOR; + L0 += texture(night_sky, SKY_COORDS).xyz * extinction; + + vec3 color = Lin + L0; + COLOR = pow(color, vec3(1.0 / (1.2 + (1.2 * sun_fade)))); + COLOR *= exposure; + } else { + // There is no sun, so display night_sky and nothing else. + COLOR = texture(night_sky, SKY_COORDS).xyz; + COLOR *= exposure; + } +} +)", + i ? "render_mode use_debanding;" : "")); + } + } + + shader_mutex.unlock(); +} + +PhysicalSkyMaterial::PhysicalSkyMaterial() { + set_rayleigh_coefficient(2.0); + set_rayleigh_color(Color(0.3, 0.405, 0.6)); + set_mie_coefficient(0.005); + set_mie_eccentricity(0.8); + set_mie_color(Color(0.69, 0.729, 0.812)); + set_turbidity(10.0); + set_sun_disk_scale(1.0); + set_ground_color(Color(0.1, 0.07, 0.034)); + set_energy_multiplier(1.0); + set_use_debanding(true); +} + +PhysicalSkyMaterial::~PhysicalSkyMaterial() { +} diff --git a/scene/resources/3d/sky_material.h b/scene/resources/3d/sky_material.h new file mode 100644 index 0000000000..4ffb18c6b4 --- /dev/null +++ b/scene/resources/3d/sky_material.h @@ -0,0 +1,235 @@ +/**************************************************************************/ +/* sky_material.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 SKY_MATERIAL_H +#define SKY_MATERIAL_H + +#include "core/templates/rid.h" +#include "scene/resources/material.h" + +class ProceduralSkyMaterial : public Material { + GDCLASS(ProceduralSkyMaterial, Material); + +private: + Color sky_top_color; + Color sky_horizon_color; + float sky_curve = 0.0f; + float sky_energy_multiplier = 0.0f; + Ref sky_cover; + Color sky_cover_modulate; + + Color ground_bottom_color; + Color ground_horizon_color; + float ground_curve = 0.0f; + float ground_energy_multiplier = 0.0f; + + float sun_angle_max = 0.0f; + float sun_curve = 0.0f; + bool use_debanding = true; + float global_energy_multiplier = 1.0f; + + static Mutex shader_mutex; + static RID shader_cache[2]; + static void _update_shader(); + mutable bool shader_set = false; + +protected: + static void _bind_methods(); + void _validate_property(PropertyInfo &property) const; + +public: + void set_sky_top_color(const Color &p_sky_top); + Color get_sky_top_color() const; + + void set_sky_horizon_color(const Color &p_sky_horizon); + Color get_sky_horizon_color() const; + + void set_sky_curve(float p_curve); + float get_sky_curve() const; + + void set_sky_energy_multiplier(float p_multiplier); + float get_sky_energy_multiplier() const; + + void set_sky_cover(const Ref &p_sky_cover); + Ref get_sky_cover() const; + + void set_sky_cover_modulate(const Color &p_sky_cover_modulate); + Color get_sky_cover_modulate() const; + + void set_ground_bottom_color(const Color &p_ground_bottom); + Color get_ground_bottom_color() const; + + void set_ground_horizon_color(const Color &p_ground_horizon); + Color get_ground_horizon_color() const; + + void set_ground_curve(float p_curve); + float get_ground_curve() const; + + void set_ground_energy_multiplier(float p_energy); + float get_ground_energy_multiplier() const; + + void set_sun_angle_max(float p_angle); + float get_sun_angle_max() const; + + void set_sun_curve(float p_curve); + float get_sun_curve() const; + + void set_use_debanding(bool p_use_debanding); + bool get_use_debanding() const; + + void set_energy_multiplier(float p_multiplier); + float get_energy_multiplier() const; + + virtual Shader::Mode get_shader_mode() const override; + virtual RID get_shader_rid() const override; + virtual RID get_rid() const override; + + static void cleanup_shader(); + + ProceduralSkyMaterial(); + ~ProceduralSkyMaterial(); +}; + +////////////////////////////////////////////////////// +/* PanoramaSkyMaterial */ + +class PanoramaSkyMaterial : public Material { + GDCLASS(PanoramaSkyMaterial, Material); + +private: + Ref panorama; + float energy_multiplier = 1.0f; + + static Mutex shader_mutex; + static RID shader_cache[2]; + static void _update_shader(); + mutable bool shader_set = false; + + bool filter = true; + +protected: + static void _bind_methods(); + +public: + void set_panorama(const Ref &p_panorama); + Ref get_panorama() const; + + void set_filtering_enabled(bool p_enabled); + bool is_filtering_enabled() const; + + void set_energy_multiplier(float p_multiplier); + float get_energy_multiplier() const; + + virtual Shader::Mode get_shader_mode() const override; + virtual RID get_shader_rid() const override; + virtual RID get_rid() const override; + + static void cleanup_shader(); + + PanoramaSkyMaterial(); + ~PanoramaSkyMaterial(); +}; + +////////////////////////////////////////////////////// +/* PanoramaSkyMaterial */ + +class PhysicalSkyMaterial : public Material { + GDCLASS(PhysicalSkyMaterial, Material); + +private: + static Mutex shader_mutex; + static RID shader_cache[2]; + + float rayleigh = 0.0f; + Color rayleigh_color; + float mie = 0.0f; + float mie_eccentricity = 0.0f; + Color mie_color; + float turbidity = 0.0f; + float sun_disk_scale = 0.0f; + Color ground_color; + float energy_multiplier = 1.0f; + bool use_debanding = true; + Ref night_sky; + static void _update_shader(); + mutable bool shader_set = false; + +protected: + static void _bind_methods(); + void _validate_property(PropertyInfo &property) const; + +public: + void set_rayleigh_coefficient(float p_rayleigh); + float get_rayleigh_coefficient() const; + + void set_rayleigh_color(Color p_rayleigh_color); + Color get_rayleigh_color() const; + + void set_turbidity(float p_turbidity); + float get_turbidity() const; + + void set_mie_coefficient(float p_mie); + float get_mie_coefficient() const; + + void set_mie_eccentricity(float p_eccentricity); + float get_mie_eccentricity() const; + + void set_mie_color(Color p_mie_color); + Color get_mie_color() const; + + void set_sun_disk_scale(float p_sun_disk_scale); + float get_sun_disk_scale() const; + + void set_ground_color(Color p_ground_color); + Color get_ground_color() const; + + void set_energy_multiplier(float p_multiplier); + float get_energy_multiplier() const; + + void set_exposure_value(float p_exposure); + float get_exposure_value() const; + + void set_use_debanding(bool p_use_debanding); + bool get_use_debanding() const; + + void set_night_sky(const Ref &p_night_sky); + Ref get_night_sky() const; + + virtual Shader::Mode get_shader_mode() const override; + virtual RID get_shader_rid() const override; + + static void cleanup_shader(); + virtual RID get_rid() const override; + + PhysicalSkyMaterial(); + ~PhysicalSkyMaterial(); +}; + +#endif // SKY_MATERIAL_H diff --git a/scene/resources/3d/sphere_shape_3d.cpp b/scene/resources/3d/sphere_shape_3d.cpp new file mode 100644 index 0000000000..56b78471ec --- /dev/null +++ b/scene/resources/3d/sphere_shape_3d.cpp @@ -0,0 +1,87 @@ +/**************************************************************************/ +/* sphere_shape_3d.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 "sphere_shape_3d.h" + +#include "servers/physics_server_3d.h" + +Vector SphereShape3D::get_debug_mesh_lines() const { + float r = get_radius(); + + Vector points; + + for (int i = 0; i <= 360; i++) { + float ra = Math::deg_to_rad((float)i); + float rb = Math::deg_to_rad((float)i + 1); + Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r; + Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r; + + points.push_back(Vector3(a.x, 0, a.y)); + points.push_back(Vector3(b.x, 0, b.y)); + points.push_back(Vector3(0, a.x, a.y)); + points.push_back(Vector3(0, b.x, b.y)); + points.push_back(Vector3(a.x, a.y, 0)); + points.push_back(Vector3(b.x, b.y, 0)); + } + + return points; +} + +real_t SphereShape3D::get_enclosing_radius() const { + return radius; +} + +void SphereShape3D::_update_shape() { + PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), radius); + Shape3D::_update_shape(); +} + +void SphereShape3D::set_radius(float p_radius) { + ERR_FAIL_COND_MSG(p_radius < 0, "SphereShape3D radius cannot be negative."); + radius = p_radius; + _update_shape(); + emit_changed(); +} + +float SphereShape3D::get_radius() const { + return radius; +} + +void SphereShape3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_radius", "radius"), &SphereShape3D::set_radius); + ClassDB::bind_method(D_METHOD("get_radius"), &SphereShape3D::get_radius); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_radius", "get_radius"); +} + +SphereShape3D::SphereShape3D() : + Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_SPHERE)) { + set_radius(0.5); +} diff --git a/scene/resources/3d/sphere_shape_3d.h b/scene/resources/3d/sphere_shape_3d.h new file mode 100644 index 0000000000..8e95cea608 --- /dev/null +++ b/scene/resources/3d/sphere_shape_3d.h @@ -0,0 +1,55 @@ +/**************************************************************************/ +/* sphere_shape_3d.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 SPHERE_SHAPE_3D_H +#define SPHERE_SHAPE_3D_H + +#include "scene/resources/3d/shape_3d.h" + +class SphereShape3D : public Shape3D { + GDCLASS(SphereShape3D, Shape3D); + float radius = 0.5f; + +protected: + static void _bind_methods(); + + virtual void _update_shape() override; + +public: + void set_radius(float p_radius); + float get_radius() const; + + virtual Vector get_debug_mesh_lines() const override; + virtual real_t get_enclosing_radius() const override; + + SphereShape3D(); +}; + +#endif // SPHERE_SHAPE_3D_H diff --git a/scene/resources/3d/world_3d.cpp b/scene/resources/3d/world_3d.cpp new file mode 100644 index 0000000000..bf91b1eb33 --- /dev/null +++ b/scene/resources/3d/world_3d.cpp @@ -0,0 +1,188 @@ +/**************************************************************************/ +/* world_3d.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 "world_3d.h" + +#include "core/config/project_settings.h" +#include "scene/3d/camera_3d.h" +#include "scene/3d/visible_on_screen_notifier_3d.h" +#include "scene/resources/camera_attributes.h" +#include "scene/resources/environment.h" +#include "scene/scene_string_names.h" +#include "servers/navigation_server_3d.h" + +void World3D::_register_camera(Camera3D *p_camera) { +#ifndef _3D_DISABLED + cameras.insert(p_camera); +#endif +} + +void World3D::_remove_camera(Camera3D *p_camera) { +#ifndef _3D_DISABLED + cameras.erase(p_camera); +#endif +} + +RID World3D::get_space() const { + if (space.is_null()) { + space = PhysicsServer3D::get_singleton()->space_create(); + PhysicsServer3D::get_singleton()->space_set_active(space, true); + PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_GRAVITY, GLOBAL_GET("physics/3d/default_gravity")); + PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_GRAVITY_VECTOR, GLOBAL_GET("physics/3d/default_gravity_vector")); + PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_LINEAR_DAMP, GLOBAL_GET("physics/3d/default_linear_damp")); + PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_ANGULAR_DAMP, GLOBAL_GET("physics/3d/default_angular_damp")); + } + return space; +} + +RID World3D::get_navigation_map() const { + if (navigation_map.is_null()) { + navigation_map = NavigationServer3D::get_singleton()->map_create(); + NavigationServer3D::get_singleton()->map_set_active(navigation_map, true); + NavigationServer3D::get_singleton()->map_set_cell_size(navigation_map, GLOBAL_GET("navigation/3d/default_cell_size")); + NavigationServer3D::get_singleton()->map_set_cell_height(navigation_map, GLOBAL_GET("navigation/3d/default_cell_height")); + NavigationServer3D::get_singleton()->map_set_up(navigation_map, GLOBAL_GET("navigation/3d/default_up")); + NavigationServer3D::get_singleton()->map_set_merge_rasterizer_cell_scale(navigation_map, GLOBAL_GET("navigation/3d/merge_rasterizer_cell_scale")); + NavigationServer3D::get_singleton()->map_set_use_edge_connections(navigation_map, GLOBAL_GET("navigation/3d/use_edge_connections")); + NavigationServer3D::get_singleton()->map_set_edge_connection_margin(navigation_map, GLOBAL_GET("navigation/3d/default_edge_connection_margin")); + NavigationServer3D::get_singleton()->map_set_link_connection_radius(navigation_map, GLOBAL_GET("navigation/3d/default_link_connection_radius")); + } + return navigation_map; +} + +RID World3D::get_scenario() const { + return scenario; +} + +void World3D::set_environment(const Ref &p_environment) { + if (environment == p_environment) { + return; + } + + environment = p_environment; + if (environment.is_valid()) { + RS::get_singleton()->scenario_set_environment(scenario, environment->get_rid()); + } else { + RS::get_singleton()->scenario_set_environment(scenario, RID()); + } + + emit_changed(); +} + +Ref World3D::get_environment() const { + return environment; +} + +void World3D::set_fallback_environment(const Ref &p_environment) { + if (fallback_environment == p_environment) { + return; + } + + fallback_environment = p_environment; + if (fallback_environment.is_valid()) { + RS::get_singleton()->scenario_set_fallback_environment(scenario, p_environment->get_rid()); + } else { + RS::get_singleton()->scenario_set_fallback_environment(scenario, RID()); + } + + emit_changed(); +} + +Ref World3D::get_fallback_environment() const { + return fallback_environment; +} + +void World3D::set_camera_attributes(const Ref &p_camera_attributes) { + camera_attributes = p_camera_attributes; + if (camera_attributes.is_valid()) { + RS::get_singleton()->scenario_set_camera_attributes(scenario, camera_attributes->get_rid()); + } else { + RS::get_singleton()->scenario_set_camera_attributes(scenario, RID()); + } +} + +Ref World3D::get_camera_attributes() const { + return camera_attributes; +} + +void World3D::set_compositor(const Ref &p_compositor) { + compositor = p_compositor; + if (compositor.is_valid()) { + RS::get_singleton()->scenario_set_compositor(scenario, compositor->get_rid()); + } else { + RS::get_singleton()->scenario_set_compositor(scenario, RID()); + } +} + +Ref World3D::get_compositor() const { + return compositor; +} + +PhysicsDirectSpaceState3D *World3D::get_direct_space_state() { + return PhysicsServer3D::get_singleton()->space_get_direct_state(get_space()); +} + +void World3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_space"), &World3D::get_space); + ClassDB::bind_method(D_METHOD("get_navigation_map"), &World3D::get_navigation_map); + ClassDB::bind_method(D_METHOD("get_scenario"), &World3D::get_scenario); + ClassDB::bind_method(D_METHOD("set_environment", "env"), &World3D::set_environment); + ClassDB::bind_method(D_METHOD("get_environment"), &World3D::get_environment); + ClassDB::bind_method(D_METHOD("set_fallback_environment", "env"), &World3D::set_fallback_environment); + ClassDB::bind_method(D_METHOD("get_fallback_environment"), &World3D::get_fallback_environment); + ClassDB::bind_method(D_METHOD("set_camera_attributes", "attributes"), &World3D::set_camera_attributes); + ClassDB::bind_method(D_METHOD("get_camera_attributes"), &World3D::get_camera_attributes); + ClassDB::bind_method(D_METHOD("get_direct_space_state"), &World3D::get_direct_space_state); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "environment", PROPERTY_HINT_RESOURCE_TYPE, "Environment"), "set_environment", "get_environment"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "fallback_environment", PROPERTY_HINT_RESOURCE_TYPE, "Environment"), "set_fallback_environment", "get_fallback_environment"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "camera_attributes", PROPERTY_HINT_RESOURCE_TYPE, "CameraAttributesPractical,CameraAttributesPhysical"), "set_camera_attributes", "get_camera_attributes"); + ADD_PROPERTY(PropertyInfo(Variant::RID, "space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_space"); + ADD_PROPERTY(PropertyInfo(Variant::RID, "navigation_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_navigation_map"); + ADD_PROPERTY(PropertyInfo(Variant::RID, "scenario", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_scenario"); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "direct_space_state", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsDirectSpaceState3D", PROPERTY_USAGE_NONE), "", "get_direct_space_state"); +} + +World3D::World3D() { + scenario = RenderingServer::get_singleton()->scenario_create(); +} + +World3D::~World3D() { + ERR_FAIL_NULL(RenderingServer::get_singleton()); + ERR_FAIL_NULL(PhysicsServer3D::get_singleton()); + ERR_FAIL_NULL(NavigationServer3D::get_singleton()); + + RenderingServer::get_singleton()->free(scenario); + if (space.is_valid()) { + PhysicsServer3D::get_singleton()->free(space); + } + if (navigation_map.is_valid()) { + NavigationServer3D::get_singleton()->free(navigation_map); + } +} diff --git a/scene/resources/3d/world_3d.h b/scene/resources/3d/world_3d.h new file mode 100644 index 0000000000..06b06aea98 --- /dev/null +++ b/scene/resources/3d/world_3d.h @@ -0,0 +1,93 @@ +/**************************************************************************/ +/* world_3d.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 WORLD_3D_H +#define WORLD_3D_H + +#include "core/io/resource.h" +#include "scene/resources/compositor.h" +#include "scene/resources/environment.h" +#include "servers/physics_server_3d.h" +#include "servers/rendering_server.h" + +class CameraAttributes; +class Camera3D; +class VisibleOnScreenNotifier3D; +struct SpatialIndexer; + +class World3D : public Resource { + GDCLASS(World3D, Resource); + +private: + RID scenario; + mutable RID space; + mutable RID navigation_map; + + Ref environment; + Ref fallback_environment; + Ref camera_attributes; + Ref compositor; + + HashSet cameras; + +protected: + static void _bind_methods(); + + friend class Camera3D; + + void _register_camera(Camera3D *p_camera); + void _remove_camera(Camera3D *p_camera); + +public: + RID get_space() const; + RID get_navigation_map() const; + RID get_scenario() const; + + void set_environment(const Ref &p_environment); + Ref get_environment() const; + + void set_fallback_environment(const Ref &p_environment); + Ref get_fallback_environment() const; + + void set_camera_attributes(const Ref &p_camera_attributes); + Ref get_camera_attributes() const; + + void set_compositor(const Ref &p_compositor); + Ref get_compositor() const; + + _FORCE_INLINE_ const HashSet &get_cameras() const { return cameras; } + + PhysicsDirectSpaceState3D *get_direct_space_state(); + + World3D(); + ~World3D(); +}; + +#endif // WORLD_3D_H diff --git a/scene/resources/3d/world_boundary_shape_3d.cpp b/scene/resources/3d/world_boundary_shape_3d.cpp new file mode 100644 index 0000000000..beaaddc95e --- /dev/null +++ b/scene/resources/3d/world_boundary_shape_3d.cpp @@ -0,0 +1,89 @@ +/**************************************************************************/ +/* world_boundary_shape_3d.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 "world_boundary_shape_3d.h" + +#include "servers/physics_server_3d.h" + +Vector WorldBoundaryShape3D::get_debug_mesh_lines() const { + Plane p = get_plane(); + + Vector3 n1 = p.get_any_perpendicular_normal(); + Vector3 n2 = p.normal.cross(n1).normalized(); + + Vector3 pface[4] = { + p.normal * p.d + n1 * 10.0 + n2 * 10.0, + p.normal * p.d + n1 * 10.0 + n2 * -10.0, + p.normal * p.d + n1 * -10.0 + n2 * -10.0, + p.normal * p.d + n1 * -10.0 + n2 * 10.0, + }; + + Vector points = { + pface[0], + pface[1], + pface[1], + pface[2], + pface[2], + pface[3], + pface[3], + pface[0], + p.normal * p.d, + p.normal * p.d + p.normal * 3 + }; + + return points; +} + +void WorldBoundaryShape3D::_update_shape() { + PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), plane); + Shape3D::_update_shape(); +} + +void WorldBoundaryShape3D::set_plane(const Plane &p_plane) { + plane = p_plane; + _update_shape(); + emit_changed(); +} + +const Plane &WorldBoundaryShape3D::get_plane() const { + return plane; +} + +void WorldBoundaryShape3D::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_plane", "plane"), &WorldBoundaryShape3D::set_plane); + ClassDB::bind_method(D_METHOD("get_plane"), &WorldBoundaryShape3D::get_plane); + + ADD_PROPERTY(PropertyInfo(Variant::PLANE, "plane", PROPERTY_HINT_NONE, "suffix:m"), "set_plane", "get_plane"); +} + +WorldBoundaryShape3D::WorldBoundaryShape3D() : + Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_WORLD_BOUNDARY)) { + set_plane(Plane(0, 1, 0, 0)); +} diff --git a/scene/resources/3d/world_boundary_shape_3d.h b/scene/resources/3d/world_boundary_shape_3d.h new file mode 100644 index 0000000000..06cff6aa9a --- /dev/null +++ b/scene/resources/3d/world_boundary_shape_3d.h @@ -0,0 +1,57 @@ +/**************************************************************************/ +/* world_boundary_shape_3d.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 WORLD_BOUNDARY_SHAPE_3D_H +#define WORLD_BOUNDARY_SHAPE_3D_H + +#include "scene/resources/3d/shape_3d.h" + +class WorldBoundaryShape3D : public Shape3D { + GDCLASS(WorldBoundaryShape3D, Shape3D); + Plane plane; + +protected: + static void _bind_methods(); + virtual void _update_shape() override; + +public: + void set_plane(const Plane &p_plane); + const Plane &get_plane() const; + + virtual Vector get_debug_mesh_lines() const override; + virtual real_t get_enclosing_radius() const override { + // Should be infinite? + return 0; + } + + WorldBoundaryShape3D(); +}; + +#endif // WORLD_BOUNDARY_SHAPE_3D_H diff --git a/scene/resources/SCsub b/scene/resources/SCsub index 060b47d687..ceff213a02 100644 --- a/scene/resources/SCsub +++ b/scene/resources/SCsub @@ -24,3 +24,4 @@ env.scene_sources += scene_obj env.Depends(scene_obj, thirdparty_obj) SConscript("2d/SCsub") +SConscript("3d/SCsub") diff --git a/scene/resources/box_shape_3d.cpp b/scene/resources/box_shape_3d.cpp deleted file mode 100644 index 313aeb1bca..0000000000 --- a/scene/resources/box_shape_3d.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/**************************************************************************/ -/* box_shape_3d.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 "box_shape_3d.h" -#include "servers/physics_server_3d.h" - -Vector BoxShape3D::get_debug_mesh_lines() const { - Vector lines; - AABB aabb; - aabb.position = -size / 2; - aabb.size = size; - - for (int i = 0; i < 12; i++) { - Vector3 a, b; - aabb.get_edge(i, a, b); - lines.push_back(a); - lines.push_back(b); - } - - return lines; -} - -real_t BoxShape3D::get_enclosing_radius() const { - return size.length() / 2; -} - -void BoxShape3D::_update_shape() { - PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), size / 2); - Shape3D::_update_shape(); -} - -#ifndef DISABLE_DEPRECATED -bool BoxShape3D::_set(const StringName &p_name, const Variant &p_value) { - if (p_name == "extents") { // Compatibility with Godot 3.x. - // Convert to `size`, twice as big. - set_size((Vector3)p_value * 2); - return true; - } - return false; -} - -bool BoxShape3D::_get(const StringName &p_name, Variant &r_property) const { - if (p_name == "extents") { // Compatibility with Godot 3.x. - // Convert to `extents`, half as big. - r_property = size / 2; - return true; - } - return false; -} -#endif // DISABLE_DEPRECATED - -void BoxShape3D::set_size(const Vector3 &p_size) { - ERR_FAIL_COND_MSG(p_size.x < 0 || p_size.y < 0 || p_size.z < 0, "BoxShape3D size cannot be negative."); - size = p_size; - _update_shape(); - emit_changed(); -} - -Vector3 BoxShape3D::get_size() const { - return size; -} - -void BoxShape3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_size", "size"), &BoxShape3D::set_size); - ClassDB::bind_method(D_METHOD("get_size"), &BoxShape3D::get_size); - - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size", PROPERTY_HINT_NONE, "suffix:m"), "set_size", "get_size"); -} - -BoxShape3D::BoxShape3D() : - Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_BOX)) { - set_size(Vector3(1, 1, 1)); -} diff --git a/scene/resources/box_shape_3d.h b/scene/resources/box_shape_3d.h deleted file mode 100644 index 8903a049e1..0000000000 --- a/scene/resources/box_shape_3d.h +++ /dev/null @@ -1,59 +0,0 @@ -/**************************************************************************/ -/* box_shape_3d.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 BOX_SHAPE_3D_H -#define BOX_SHAPE_3D_H - -#include "scene/resources/shape_3d.h" - -class BoxShape3D : public Shape3D { - GDCLASS(BoxShape3D, Shape3D); - Vector3 size; - -protected: - static void _bind_methods(); -#ifndef DISABLE_DEPRECATED - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_property) const; -#endif // DISABLE_DEPRECATED - - virtual void _update_shape() override; - -public: - void set_size(const Vector3 &p_size); - Vector3 get_size() const; - - virtual Vector get_debug_mesh_lines() const override; - virtual real_t get_enclosing_radius() const override; - - BoxShape3D(); -}; - -#endif // BOX_SHAPE_3D_H diff --git a/scene/resources/capsule_shape_3d.cpp b/scene/resources/capsule_shape_3d.cpp deleted file mode 100644 index 9e16801060..0000000000 --- a/scene/resources/capsule_shape_3d.cpp +++ /dev/null @@ -1,125 +0,0 @@ -/**************************************************************************/ -/* capsule_shape_3d.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 "capsule_shape_3d.h" - -#include "servers/physics_server_3d.h" - -Vector CapsuleShape3D::get_debug_mesh_lines() const { - float c_radius = get_radius(); - float c_height = get_height(); - - Vector points; - - Vector3 d(0, c_height * 0.5 - c_radius, 0); - for (int i = 0; i < 360; i++) { - float ra = Math::deg_to_rad((float)i); - float rb = Math::deg_to_rad((float)i + 1); - Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * c_radius; - Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * c_radius; - - points.push_back(Vector3(a.x, 0, a.y) + d); - points.push_back(Vector3(b.x, 0, b.y) + d); - - points.push_back(Vector3(a.x, 0, a.y) - d); - points.push_back(Vector3(b.x, 0, b.y) - d); - - if (i % 90 == 0) { - points.push_back(Vector3(a.x, 0, a.y) + d); - points.push_back(Vector3(a.x, 0, a.y) - d); - } - - Vector3 dud = i < 180 ? d : -d; - - points.push_back(Vector3(0, a.x, a.y) + dud); - points.push_back(Vector3(0, b.x, b.y) + dud); - points.push_back(Vector3(a.y, a.x, 0) + dud); - points.push_back(Vector3(b.y, b.x, 0) + dud); - } - - return points; -} - -real_t CapsuleShape3D::get_enclosing_radius() const { - return height * 0.5; -} - -void CapsuleShape3D::_update_shape() { - Dictionary d; - d["radius"] = radius; - d["height"] = height; - PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), d); - Shape3D::_update_shape(); -} - -void CapsuleShape3D::set_radius(float p_radius) { - ERR_FAIL_COND_MSG(p_radius < 0, "CapsuleShape3D radius cannot be negative."); - radius = p_radius; - if (radius > height * 0.5) { - height = radius * 2.0; - } - _update_shape(); - emit_changed(); -} - -float CapsuleShape3D::get_radius() const { - return radius; -} - -void CapsuleShape3D::set_height(float p_height) { - ERR_FAIL_COND_MSG(p_height < 0, "CapsuleShape3D height cannot be negative."); - height = p_height; - if (radius > height * 0.5) { - radius = height * 0.5; - } - _update_shape(); - emit_changed(); -} - -float CapsuleShape3D::get_height() const { - return height; -} - -void CapsuleShape3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CapsuleShape3D::set_radius); - ClassDB::bind_method(D_METHOD("get_radius"), &CapsuleShape3D::get_radius); - ClassDB::bind_method(D_METHOD("set_height", "height"), &CapsuleShape3D::set_height); - ClassDB::bind_method(D_METHOD("get_height"), &CapsuleShape3D::get_height); - - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_radius", "get_radius"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_height", "get_height"); - ADD_LINKED_PROPERTY("radius", "height"); - ADD_LINKED_PROPERTY("height", "radius"); -} - -CapsuleShape3D::CapsuleShape3D() : - Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_CAPSULE)) { - _update_shape(); -} diff --git a/scene/resources/capsule_shape_3d.h b/scene/resources/capsule_shape_3d.h deleted file mode 100644 index 48e5851c18..0000000000 --- a/scene/resources/capsule_shape_3d.h +++ /dev/null @@ -1,58 +0,0 @@ -/**************************************************************************/ -/* capsule_shape_3d.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 CAPSULE_SHAPE_3D_H -#define CAPSULE_SHAPE_3D_H - -#include "scene/resources/shape_3d.h" - -class CapsuleShape3D : public Shape3D { - GDCLASS(CapsuleShape3D, Shape3D); - float radius = 0.5; - float height = 2.0; - -protected: - static void _bind_methods(); - - virtual void _update_shape() override; - -public: - void set_radius(float p_radius); - float get_radius() const; - void set_height(float p_height); - float get_height() const; - - virtual Vector get_debug_mesh_lines() const override; - virtual real_t get_enclosing_radius() const override; - - CapsuleShape3D(); -}; - -#endif // CAPSULE_SHAPE_3D_H diff --git a/scene/resources/concave_polygon_shape_3d.cpp b/scene/resources/concave_polygon_shape_3d.cpp deleted file mode 100644 index 82b125905f..0000000000 --- a/scene/resources/concave_polygon_shape_3d.cpp +++ /dev/null @@ -1,118 +0,0 @@ -/**************************************************************************/ -/* concave_polygon_shape_3d.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 "concave_polygon_shape_3d.h" - -#include "servers/physics_server_3d.h" - -Vector ConcavePolygonShape3D::get_debug_mesh_lines() const { - HashSet edges; - - int index_count = faces.size(); - ERR_FAIL_COND_V((index_count % 3) != 0, Vector()); - - const Vector3 *r = faces.ptr(); - - for (int i = 0; i < index_count; i += 3) { - for (int j = 0; j < 3; j++) { - DrawEdge de(r[i + j], r[i + ((j + 1) % 3)]); - edges.insert(de); - } - } - - Vector points; - points.resize(edges.size() * 2); - int idx = 0; - for (const DrawEdge &E : edges) { - points.write[idx + 0] = E.a; - points.write[idx + 1] = E.b; - idx += 2; - } - - return points; -} - -real_t ConcavePolygonShape3D::get_enclosing_radius() const { - Vector data = get_faces(); - const Vector3 *read = data.ptr(); - real_t r = 0.0; - for (int i(0); i < data.size(); i++) { - r = MAX(read[i].length_squared(), r); - } - return Math::sqrt(r); -} - -void ConcavePolygonShape3D::_update_shape() { - Dictionary d; - d["faces"] = faces; - d["backface_collision"] = backface_collision; - PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), d); - - Shape3D::_update_shape(); -} - -void ConcavePolygonShape3D::set_faces(const Vector &p_faces) { - faces = p_faces; - _update_shape(); - emit_changed(); -} - -Vector ConcavePolygonShape3D::get_faces() const { - return faces; -} - -void ConcavePolygonShape3D::set_backface_collision_enabled(bool p_enabled) { - backface_collision = p_enabled; - - if (!faces.is_empty()) { - _update_shape(); - emit_changed(); - } -} - -bool ConcavePolygonShape3D::is_backface_collision_enabled() const { - return backface_collision; -} - -void ConcavePolygonShape3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_faces", "faces"), &ConcavePolygonShape3D::set_faces); - ClassDB::bind_method(D_METHOD("get_faces"), &ConcavePolygonShape3D::get_faces); - - ClassDB::bind_method(D_METHOD("set_backface_collision_enabled", "enabled"), &ConcavePolygonShape3D::set_backface_collision_enabled); - ClassDB::bind_method(D_METHOD("is_backface_collision_enabled"), &ConcavePolygonShape3D::is_backface_collision_enabled); - - ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_faces", "get_faces"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "backface_collision"), "set_backface_collision_enabled", "is_backface_collision_enabled"); -} - -ConcavePolygonShape3D::ConcavePolygonShape3D() : - Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_CONCAVE_POLYGON)) { - //set_planes(Vector3(1,1,1)); -} diff --git a/scene/resources/concave_polygon_shape_3d.h b/scene/resources/concave_polygon_shape_3d.h deleted file mode 100644 index 89ff742cb1..0000000000 --- a/scene/resources/concave_polygon_shape_3d.h +++ /dev/null @@ -1,80 +0,0 @@ -/**************************************************************************/ -/* concave_polygon_shape_3d.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 CONCAVE_POLYGON_SHAPE_3D_H -#define CONCAVE_POLYGON_SHAPE_3D_H - -#include "scene/resources/shape_3d.h" - -class ConcavePolygonShape3D : public Shape3D { - GDCLASS(ConcavePolygonShape3D, Shape3D); - - Vector faces; - bool backface_collision = false; - - struct DrawEdge { - Vector3 a; - Vector3 b; - static uint32_t hash(const DrawEdge &p_edge) { - uint32_t h = hash_murmur3_one_32(HashMapHasherDefault::hash(p_edge.a)); - return hash_murmur3_one_32(HashMapHasherDefault::hash(p_edge.b), h); - } - bool operator==(const DrawEdge &p_edge) const { - return (a == p_edge.a && b == p_edge.b); - } - - DrawEdge(const Vector3 &p_a = Vector3(), const Vector3 &p_b = Vector3()) { - a = p_a; - b = p_b; - if (a < b) { - SWAP(a, b); - } - } - }; - -protected: - static void _bind_methods(); - - virtual void _update_shape() override; - -public: - void set_faces(const Vector &p_faces); - Vector get_faces() const; - - void set_backface_collision_enabled(bool p_enabled); - bool is_backface_collision_enabled() const; - - virtual Vector get_debug_mesh_lines() const override; - virtual real_t get_enclosing_radius() const override; - - ConcavePolygonShape3D(); -}; - -#endif // CONCAVE_POLYGON_SHAPE_3D_H diff --git a/scene/resources/convex_polygon_shape_3d.cpp b/scene/resources/convex_polygon_shape_3d.cpp deleted file mode 100644 index 3bfeeca461..0000000000 --- a/scene/resources/convex_polygon_shape_3d.cpp +++ /dev/null @@ -1,90 +0,0 @@ -/**************************************************************************/ -/* convex_polygon_shape_3d.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 "convex_polygon_shape_3d.h" -#include "core/math/convex_hull.h" -#include "servers/physics_server_3d.h" - -Vector ConvexPolygonShape3D::get_debug_mesh_lines() const { - Vector poly_points = get_points(); - - if (poly_points.size() > 3) { - Vector varr = Variant(poly_points); - Geometry3D::MeshData md; - Error err = ConvexHullComputer::convex_hull(varr, md); - if (err == OK) { - Vector lines; - lines.resize(md.edges.size() * 2); - for (uint32_t i = 0; i < md.edges.size(); i++) { - lines.write[i * 2 + 0] = md.vertices[md.edges[i].vertex_a]; - lines.write[i * 2 + 1] = md.vertices[md.edges[i].vertex_b]; - } - return lines; - } - } - - return Vector(); -} - -real_t ConvexPolygonShape3D::get_enclosing_radius() const { - Vector data = get_points(); - const Vector3 *read = data.ptr(); - real_t r = 0.0; - for (int i(0); i < data.size(); i++) { - r = MAX(read[i].length_squared(), r); - } - return Math::sqrt(r); -} - -void ConvexPolygonShape3D::_update_shape() { - PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), points); - Shape3D::_update_shape(); -} - -void ConvexPolygonShape3D::set_points(const Vector &p_points) { - points = p_points; - _update_shape(); - emit_changed(); -} - -Vector ConvexPolygonShape3D::get_points() const { - return points; -} - -void ConvexPolygonShape3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_points", "points"), &ConvexPolygonShape3D::set_points); - ClassDB::bind_method(D_METHOD("get_points"), &ConvexPolygonShape3D::get_points); - - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "points"), "set_points", "get_points"); -} - -ConvexPolygonShape3D::ConvexPolygonShape3D() : - Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_CONVEX_POLYGON)) { -} diff --git a/scene/resources/convex_polygon_shape_3d.h b/scene/resources/convex_polygon_shape_3d.h deleted file mode 100644 index deb3d30f9b..0000000000 --- a/scene/resources/convex_polygon_shape_3d.h +++ /dev/null @@ -1,55 +0,0 @@ -/**************************************************************************/ -/* convex_polygon_shape_3d.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 CONVEX_POLYGON_SHAPE_3D_H -#define CONVEX_POLYGON_SHAPE_3D_H - -#include "scene/resources/shape_3d.h" - -class ConvexPolygonShape3D : public Shape3D { - GDCLASS(ConvexPolygonShape3D, Shape3D); - Vector points; - -protected: - static void _bind_methods(); - - virtual void _update_shape() override; - -public: - void set_points(const Vector &p_points); - Vector get_points() const; - - virtual Vector get_debug_mesh_lines() const override; - virtual real_t get_enclosing_radius() const override; - - ConvexPolygonShape3D(); -}; - -#endif // CONVEX_POLYGON_SHAPE_3D_H diff --git a/scene/resources/cylinder_shape_3d.cpp b/scene/resources/cylinder_shape_3d.cpp deleted file mode 100644 index a91282fd33..0000000000 --- a/scene/resources/cylinder_shape_3d.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/**************************************************************************/ -/* cylinder_shape_3d.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 "cylinder_shape_3d.h" - -#include "servers/physics_server_3d.h" - -Vector CylinderShape3D::get_debug_mesh_lines() const { - float c_radius = get_radius(); - float c_height = get_height(); - - Vector points; - - Vector3 d(0, c_height * 0.5, 0); - for (int i = 0; i < 360; i++) { - float ra = Math::deg_to_rad((float)i); - float rb = Math::deg_to_rad((float)i + 1); - Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * c_radius; - Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * c_radius; - - points.push_back(Vector3(a.x, 0, a.y) + d); - points.push_back(Vector3(b.x, 0, b.y) + d); - - points.push_back(Vector3(a.x, 0, a.y) - d); - points.push_back(Vector3(b.x, 0, b.y) - d); - - if (i % 90 == 0) { - points.push_back(Vector3(a.x, 0, a.y) + d); - points.push_back(Vector3(a.x, 0, a.y) - d); - } - } - - return points; -} - -real_t CylinderShape3D::get_enclosing_radius() const { - return Vector2(radius, height * 0.5).length(); -} - -void CylinderShape3D::_update_shape() { - Dictionary d; - d["radius"] = radius; - d["height"] = height; - PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), d); - Shape3D::_update_shape(); -} - -void CylinderShape3D::set_radius(float p_radius) { - ERR_FAIL_COND_MSG(p_radius < 0, "CylinderShape3D radius cannot be negative."); - radius = p_radius; - _update_shape(); - emit_changed(); -} - -float CylinderShape3D::get_radius() const { - return radius; -} - -void CylinderShape3D::set_height(float p_height) { - ERR_FAIL_COND_MSG(p_height < 0, "CylinderShape3D height cannot be negative."); - height = p_height; - _update_shape(); - emit_changed(); -} - -float CylinderShape3D::get_height() const { - return height; -} - -void CylinderShape3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CylinderShape3D::set_radius); - ClassDB::bind_method(D_METHOD("get_radius"), &CylinderShape3D::get_radius); - ClassDB::bind_method(D_METHOD("set_height", "height"), &CylinderShape3D::set_height); - ClassDB::bind_method(D_METHOD("get_height"), &CylinderShape3D::get_height); - - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_height", "get_height"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_radius", "get_radius"); -} - -CylinderShape3D::CylinderShape3D() : - Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_CYLINDER)) { - _update_shape(); -} diff --git a/scene/resources/cylinder_shape_3d.h b/scene/resources/cylinder_shape_3d.h deleted file mode 100644 index ea4b3bb96c..0000000000 --- a/scene/resources/cylinder_shape_3d.h +++ /dev/null @@ -1,57 +0,0 @@ -/**************************************************************************/ -/* cylinder_shape_3d.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 CYLINDER_SHAPE_3D_H -#define CYLINDER_SHAPE_3D_H - -#include "scene/resources/shape_3d.h" - -class CylinderShape3D : public Shape3D { - GDCLASS(CylinderShape3D, Shape3D); - float radius = 0.5; - float height = 2.0; - -protected: - static void _bind_methods(); - virtual void _update_shape() override; - -public: - void set_radius(float p_radius); - float get_radius() const; - void set_height(float p_height); - float get_height() const; - - virtual Vector get_debug_mesh_lines() const override; - virtual real_t get_enclosing_radius() const override; - - CylinderShape3D(); -}; - -#endif // CYLINDER_SHAPE_3D_H diff --git a/scene/resources/fog_material.cpp b/scene/resources/fog_material.cpp deleted file mode 100644 index 5e4f1970ee..0000000000 --- a/scene/resources/fog_material.cpp +++ /dev/null @@ -1,182 +0,0 @@ -/**************************************************************************/ -/* fog_material.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 "fog_material.h" - -#include "core/version.h" - -Mutex FogMaterial::shader_mutex; -RID FogMaterial::shader; - -void FogMaterial::set_density(float p_density) { - density = p_density; - RS::get_singleton()->material_set_param(_get_material(), "density", density); -} - -float FogMaterial::get_density() const { - return density; -} - -void FogMaterial::set_albedo(Color p_albedo) { - albedo = p_albedo; - RS::get_singleton()->material_set_param(_get_material(), "albedo", albedo); -} - -Color FogMaterial::get_albedo() const { - return albedo; -} - -void FogMaterial::set_emission(Color p_emission) { - emission = p_emission; - RS::get_singleton()->material_set_param(_get_material(), "emission", emission); -} - -Color FogMaterial::get_emission() const { - return emission; -} - -void FogMaterial::set_height_falloff(float p_falloff) { - height_falloff = MAX(p_falloff, 0.0f); - RS::get_singleton()->material_set_param(_get_material(), "height_falloff", height_falloff); -} - -float FogMaterial::get_height_falloff() const { - return height_falloff; -} - -void FogMaterial::set_edge_fade(float p_edge_fade) { - edge_fade = MAX(p_edge_fade, 0.0f); - RS::get_singleton()->material_set_param(_get_material(), "edge_fade", edge_fade); -} - -float FogMaterial::get_edge_fade() const { - return edge_fade; -} - -void FogMaterial::set_density_texture(const Ref &p_texture) { - density_texture = p_texture; - Variant tex_rid = p_texture.is_valid() ? Variant(p_texture->get_rid()) : Variant(); - RS::get_singleton()->material_set_param(_get_material(), "density_texture", tex_rid); -} - -Ref FogMaterial::get_density_texture() const { - return density_texture; -} - -Shader::Mode FogMaterial::get_shader_mode() const { - return Shader::MODE_FOG; -} - -RID FogMaterial::get_shader_rid() const { - _update_shader(); - return shader; -} - -RID FogMaterial::get_rid() const { - _update_shader(); - if (!shader_set) { - RS::get_singleton()->material_set_shader(_get_material(), shader); - shader_set = true; - } - return _get_material(); -} - -void FogMaterial::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_density", "density"), &FogMaterial::set_density); - ClassDB::bind_method(D_METHOD("get_density"), &FogMaterial::get_density); - ClassDB::bind_method(D_METHOD("set_albedo", "albedo"), &FogMaterial::set_albedo); - ClassDB::bind_method(D_METHOD("get_albedo"), &FogMaterial::get_albedo); - ClassDB::bind_method(D_METHOD("set_emission", "emission"), &FogMaterial::set_emission); - ClassDB::bind_method(D_METHOD("get_emission"), &FogMaterial::get_emission); - ClassDB::bind_method(D_METHOD("set_height_falloff", "height_falloff"), &FogMaterial::set_height_falloff); - ClassDB::bind_method(D_METHOD("get_height_falloff"), &FogMaterial::get_height_falloff); - ClassDB::bind_method(D_METHOD("set_edge_fade", "edge_fade"), &FogMaterial::set_edge_fade); - ClassDB::bind_method(D_METHOD("get_edge_fade"), &FogMaterial::get_edge_fade); - ClassDB::bind_method(D_METHOD("set_density_texture", "density_texture"), &FogMaterial::set_density_texture); - ClassDB::bind_method(D_METHOD("get_density_texture"), &FogMaterial::get_density_texture); - - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "density", PROPERTY_HINT_RANGE, "-8.0,8.0,0.0001,or_greater,or_less"), "set_density", "get_density"); - ADD_PROPERTY(PropertyInfo(Variant::COLOR, "albedo", PROPERTY_HINT_COLOR_NO_ALPHA), "set_albedo", "get_albedo"); - ADD_PROPERTY(PropertyInfo(Variant::COLOR, "emission", PROPERTY_HINT_COLOR_NO_ALPHA), "set_emission", "get_emission"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height_falloff", PROPERTY_HINT_EXP_EASING, "attenuation"), "set_height_falloff", "get_height_falloff"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "edge_fade", PROPERTY_HINT_EXP_EASING), "set_edge_fade", "get_edge_fade"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "density_texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture3D"), "set_density_texture", "get_density_texture"); -} - -void FogMaterial::cleanup_shader() { - if (shader.is_valid()) { - ERR_FAIL_NULL(RenderingServer::get_singleton()); - RS::get_singleton()->free(shader); - } -} - -void FogMaterial::_update_shader() { - shader_mutex.lock(); - if (shader.is_null()) { - shader = RS::get_singleton()->shader_create(); - - // Add a comment to describe the shader origin (useful when converting to ShaderMaterial). - RS::get_singleton()->shader_set_code(shader, R"( -// NOTE: Shader automatically converted from )" VERSION_NAME " " VERSION_FULL_CONFIG R"('s FogMaterial. - -shader_type fog; - -uniform float density : hint_range(0, 1, 0.0001) = 1.0; -uniform vec4 albedo : source_color = vec4(1.0); -uniform vec4 emission : source_color = vec4(0, 0, 0, 1); -uniform float height_falloff = 0.0; -uniform float edge_fade = 0.1; -uniform sampler3D density_texture: hint_default_white; - - -void fog() { - DENSITY = density * clamp(exp2(-height_falloff * (WORLD_POSITION.y - OBJECT_POSITION.y)), 0.0, 1.0); - DENSITY *= texture(density_texture, UVW).r; - DENSITY *= pow(clamp(-2.0 * SDF / min(min(SIZE.x, SIZE.y), SIZE.z), 0.0, 1.0), edge_fade); - ALBEDO = albedo.rgb; - EMISSION = emission.rgb; -} -)"); - } - shader_mutex.unlock(); -} - -FogMaterial::FogMaterial() { - set_density(1.0); - set_albedo(Color(1, 1, 1, 1)); - set_emission(Color(0, 0, 0, 1)); - - set_height_falloff(0.0); - set_edge_fade(0.1); -} - -FogMaterial::~FogMaterial() { - RS::get_singleton()->material_set_shader(_get_material(), RID()); -} diff --git a/scene/resources/fog_material.h b/scene/resources/fog_material.h deleted file mode 100644 index 7557fd5114..0000000000 --- a/scene/resources/fog_material.h +++ /dev/null @@ -1,87 +0,0 @@ -/**************************************************************************/ -/* fog_material.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 FOG_MATERIAL_H -#define FOG_MATERIAL_H - -#include "scene/resources/material.h" - -class FogMaterial : public Material { - GDCLASS(FogMaterial, Material); - -private: - float density = 1.0; - Color albedo = Color(1, 1, 1, 1); - Color emission = Color(0, 0, 0, 0); - - float height_falloff = 0.0; - - float edge_fade = 0.1; - - Ref density_texture; - - static Mutex shader_mutex; - static RID shader; - static void _update_shader(); - mutable bool shader_set = false; - -protected: - static void _bind_methods(); - -public: - void set_density(float p_density); - float get_density() const; - - void set_albedo(Color p_color); - Color get_albedo() const; - - void set_emission(Color p_color); - Color get_emission() const; - - void set_height_falloff(float p_falloff); - float get_height_falloff() const; - - void set_edge_fade(float p_edge_fade); - float get_edge_fade() const; - - void set_density_texture(const Ref &p_texture); - Ref get_density_texture() const; - - virtual Shader::Mode get_shader_mode() const override; - virtual RID get_shader_rid() const override; - virtual RID get_rid() const override; - - static void cleanup_shader(); - - FogMaterial(); - virtual ~FogMaterial(); -}; - -#endif // FOG_MATERIAL_H diff --git a/scene/resources/height_map_shape_3d.cpp b/scene/resources/height_map_shape_3d.cpp deleted file mode 100644 index 35c905bd43..0000000000 --- a/scene/resources/height_map_shape_3d.cpp +++ /dev/null @@ -1,215 +0,0 @@ -/**************************************************************************/ -/* height_map_shape_3d.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 "height_map_shape_3d.h" - -#include "servers/physics_server_3d.h" - -Vector HeightMapShape3D::get_debug_mesh_lines() const { - Vector points; - - if ((map_width != 0) && (map_depth != 0)) { - // This will be slow for large maps... - // also we'll have to figure out how well bullet centers this shape... - - Vector2 size(map_width - 1, map_depth - 1); - Vector2 start = size * -0.5; - - const real_t *r = map_data.ptr(); - - // reserve some memory for our points.. - points.resize(((map_width - 1) * map_depth * 2) + (map_width * (map_depth - 1) * 2) + ((map_width - 1) * (map_depth - 1) * 2)); - - // now set our points - int r_offset = 0; - int w_offset = 0; - for (int d = 0; d < map_depth; d++) { - Vector3 height(start.x, 0.0, start.y); - - for (int w = 0; w < map_width; w++) { - height.y = r[r_offset++]; - - if (w != map_width - 1) { - points.write[w_offset++] = height; - points.write[w_offset++] = Vector3(height.x + 1.0, r[r_offset], height.z); - } - - if (d != map_depth - 1) { - points.write[w_offset++] = height; - points.write[w_offset++] = Vector3(height.x, r[r_offset + map_width - 1], height.z + 1.0); - } - - if ((w != map_width - 1) && (d != map_depth - 1)) { - points.write[w_offset++] = Vector3(height.x + 1.0, r[r_offset], height.z); - points.write[w_offset++] = Vector3(height.x, r[r_offset + map_width - 1], height.z + 1.0); - } - - height.x += 1.0; - } - - start.y += 1.0; - } - } - - return points; -} - -real_t HeightMapShape3D::get_enclosing_radius() const { - return Vector3(real_t(map_width), max_height - min_height, real_t(map_depth)).length(); -} - -void HeightMapShape3D::_update_shape() { - Dictionary d; - d["width"] = map_width; - d["depth"] = map_depth; - d["heights"] = map_data; - d["min_height"] = min_height; - d["max_height"] = max_height; - PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), d); - Shape3D::_update_shape(); -} - -void HeightMapShape3D::set_map_width(int p_new) { - if (p_new < 1) { - // ignore - } else if (map_width != p_new) { - int was_size = map_width * map_depth; - map_width = p_new; - - int new_size = map_width * map_depth; - map_data.resize(map_width * map_depth); - - real_t *w = map_data.ptrw(); - while (was_size < new_size) { - w[was_size++] = 0.0; - } - - _update_shape(); - emit_changed(); - } -} - -int HeightMapShape3D::get_map_width() const { - return map_width; -} - -void HeightMapShape3D::set_map_depth(int p_new) { - if (p_new < 1) { - // ignore - } else if (map_depth != p_new) { - int was_size = map_width * map_depth; - map_depth = p_new; - - int new_size = map_width * map_depth; - map_data.resize(new_size); - - real_t *w = map_data.ptrw(); - while (was_size < new_size) { - w[was_size++] = 0.0; - } - - _update_shape(); - emit_changed(); - } -} - -int HeightMapShape3D::get_map_depth() const { - return map_depth; -} - -void HeightMapShape3D::set_map_data(Vector p_new) { - int size = (map_width * map_depth); - if (p_new.size() != size) { - // fail - return; - } - - // copy - real_t *w = map_data.ptrw(); - const real_t *r = p_new.ptr(); - for (int i = 0; i < size; i++) { - real_t val = r[i]; - w[i] = val; - if (i == 0) { - min_height = val; - max_height = val; - } else { - if (min_height > val) { - min_height = val; - } - - if (max_height < val) { - max_height = val; - } - } - } - - _update_shape(); - emit_changed(); -} - -Vector HeightMapShape3D::get_map_data() const { - return map_data; -} - -real_t HeightMapShape3D::get_min_height() const { - return min_height; -} - -real_t HeightMapShape3D::get_max_height() const { - return max_height; -} - -void HeightMapShape3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_map_width", "width"), &HeightMapShape3D::set_map_width); - ClassDB::bind_method(D_METHOD("get_map_width"), &HeightMapShape3D::get_map_width); - ClassDB::bind_method(D_METHOD("set_map_depth", "height"), &HeightMapShape3D::set_map_depth); - ClassDB::bind_method(D_METHOD("get_map_depth"), &HeightMapShape3D::get_map_depth); - ClassDB::bind_method(D_METHOD("set_map_data", "data"), &HeightMapShape3D::set_map_data); - ClassDB::bind_method(D_METHOD("get_map_data"), &HeightMapShape3D::get_map_data); - ClassDB::bind_method(D_METHOD("get_min_height"), &HeightMapShape3D::get_min_height); - ClassDB::bind_method(D_METHOD("get_max_height"), &HeightMapShape3D::get_max_height); - - ADD_PROPERTY(PropertyInfo(Variant::INT, "map_width", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), "set_map_width", "get_map_width"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "map_depth", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater"), "set_map_depth", "get_map_depth"); - ADD_PROPERTY(PropertyInfo(Variant::PACKED_FLOAT32_ARRAY, "map_data"), "set_map_data", "get_map_data"); -} - -HeightMapShape3D::HeightMapShape3D() : - Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_HEIGHTMAP)) { - map_data.resize(map_width * map_depth); - real_t *w = map_data.ptrw(); - w[0] = 0.0; - w[1] = 0.0; - w[2] = 0.0; - w[3] = 0.0; - - _update_shape(); -} diff --git a/scene/resources/height_map_shape_3d.h b/scene/resources/height_map_shape_3d.h deleted file mode 100644 index eff025c816..0000000000 --- a/scene/resources/height_map_shape_3d.h +++ /dev/null @@ -1,66 +0,0 @@ -/**************************************************************************/ -/* height_map_shape_3d.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 HEIGHT_MAP_SHAPE_3D_H -#define HEIGHT_MAP_SHAPE_3D_H - -#include "scene/resources/shape_3d.h" - -class HeightMapShape3D : public Shape3D { - GDCLASS(HeightMapShape3D, Shape3D); - - int map_width = 2; - int map_depth = 2; - Vector map_data; - real_t min_height = 0.0; - real_t max_height = 0.0; - -protected: - static void _bind_methods(); - virtual void _update_shape() override; - -public: - void set_map_width(int p_new); - int get_map_width() const; - void set_map_depth(int p_new); - int get_map_depth() const; - void set_map_data(Vector p_new); - Vector get_map_data() const; - - real_t get_min_height() const; - real_t get_max_height() const; - - virtual Vector get_debug_mesh_lines() const override; - virtual real_t get_enclosing_radius() const override; - - HeightMapShape3D(); -}; - -#endif // HEIGHT_MAP_SHAPE_3D_H diff --git a/scene/resources/importer_mesh.cpp b/scene/resources/importer_mesh.cpp deleted file mode 100644 index 952e99608d..0000000000 --- a/scene/resources/importer_mesh.cpp +++ /dev/null @@ -1,1378 +0,0 @@ -/**************************************************************************/ -/* importer_mesh.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 "importer_mesh.h" - -#include "core/io/marshalls.h" -#include "core/math/convex_hull.h" -#include "core/math/random_pcg.h" -#include "core/math/static_raycaster.h" -#include "scene/resources/surface_tool.h" - -#include - -void ImporterMesh::Surface::split_normals(const LocalVector &p_indices, const LocalVector &p_normals) { - _split_normals(arrays, p_indices, p_normals); - - for (BlendShape &blend_shape : blend_shape_data) { - _split_normals(blend_shape.arrays, p_indices, p_normals); - } -} - -void ImporterMesh::Surface::_split_normals(Array &r_arrays, const LocalVector &p_indices, const LocalVector &p_normals) { - ERR_FAIL_COND(r_arrays.size() != RS::ARRAY_MAX); - - const PackedVector3Array &vertices = r_arrays[RS::ARRAY_VERTEX]; - int current_vertex_count = vertices.size(); - int new_vertex_count = p_indices.size(); - int final_vertex_count = current_vertex_count + new_vertex_count; - const int *indices_ptr = p_indices.ptr(); - - for (int i = 0; i < r_arrays.size(); i++) { - if (i == RS::ARRAY_INDEX) { - continue; - } - - if (r_arrays[i].get_type() == Variant::NIL) { - continue; - } - - switch (r_arrays[i].get_type()) { - case Variant::PACKED_VECTOR3_ARRAY: { - PackedVector3Array data = r_arrays[i]; - data.resize(final_vertex_count); - Vector3 *data_ptr = data.ptrw(); - if (i == RS::ARRAY_NORMAL) { - const Vector3 *normals_ptr = p_normals.ptr(); - memcpy(&data_ptr[current_vertex_count], normals_ptr, sizeof(Vector3) * new_vertex_count); - } else { - for (int j = 0; j < new_vertex_count; j++) { - data_ptr[current_vertex_count + j] = data_ptr[indices_ptr[j]]; - } - } - r_arrays[i] = data; - } break; - case Variant::PACKED_VECTOR2_ARRAY: { - PackedVector2Array data = r_arrays[i]; - data.resize(final_vertex_count); - Vector2 *data_ptr = data.ptrw(); - for (int j = 0; j < new_vertex_count; j++) { - data_ptr[current_vertex_count + j] = data_ptr[indices_ptr[j]]; - } - r_arrays[i] = data; - } break; - case Variant::PACKED_FLOAT32_ARRAY: { - PackedFloat32Array data = r_arrays[i]; - int elements = data.size() / current_vertex_count; - data.resize(final_vertex_count * elements); - float *data_ptr = data.ptrw(); - for (int j = 0; j < new_vertex_count; j++) { - memcpy(&data_ptr[(current_vertex_count + j) * elements], &data_ptr[indices_ptr[j] * elements], sizeof(float) * elements); - } - r_arrays[i] = data; - } break; - case Variant::PACKED_INT32_ARRAY: { - PackedInt32Array data = r_arrays[i]; - int elements = data.size() / current_vertex_count; - data.resize(final_vertex_count * elements); - int32_t *data_ptr = data.ptrw(); - for (int j = 0; j < new_vertex_count; j++) { - memcpy(&data_ptr[(current_vertex_count + j) * elements], &data_ptr[indices_ptr[j] * elements], sizeof(int32_t) * elements); - } - r_arrays[i] = data; - } break; - case Variant::PACKED_BYTE_ARRAY: { - PackedByteArray data = r_arrays[i]; - int elements = data.size() / current_vertex_count; - data.resize(final_vertex_count * elements); - uint8_t *data_ptr = data.ptrw(); - for (int j = 0; j < new_vertex_count; j++) { - memcpy(&data_ptr[(current_vertex_count + j) * elements], &data_ptr[indices_ptr[j] * elements], sizeof(uint8_t) * elements); - } - r_arrays[i] = data; - } break; - case Variant::PACKED_COLOR_ARRAY: { - PackedColorArray data = r_arrays[i]; - data.resize(final_vertex_count); - Color *data_ptr = data.ptrw(); - for (int j = 0; j < new_vertex_count; j++) { - data_ptr[current_vertex_count + j] = data_ptr[indices_ptr[j]]; - } - r_arrays[i] = data; - } break; - default: { - ERR_FAIL_MSG("Unhandled array type."); - } break; - } - } -} - -void ImporterMesh::add_blend_shape(const String &p_name) { - ERR_FAIL_COND(surfaces.size() > 0); - blend_shapes.push_back(p_name); -} - -int ImporterMesh::get_blend_shape_count() const { - return blend_shapes.size(); -} - -String ImporterMesh::get_blend_shape_name(int p_blend_shape) const { - ERR_FAIL_INDEX_V(p_blend_shape, blend_shapes.size(), String()); - return blend_shapes[p_blend_shape]; -} - -void ImporterMesh::set_blend_shape_mode(Mesh::BlendShapeMode p_blend_shape_mode) { - blend_shape_mode = p_blend_shape_mode; -} - -Mesh::BlendShapeMode ImporterMesh::get_blend_shape_mode() const { - return blend_shape_mode; -} - -void ImporterMesh::add_surface(Mesh::PrimitiveType p_primitive, const Array &p_arrays, const TypedArray &p_blend_shapes, const Dictionary &p_lods, const Ref &p_material, const String &p_name, const uint64_t p_flags) { - ERR_FAIL_COND(p_blend_shapes.size() != blend_shapes.size()); - ERR_FAIL_COND(p_arrays.size() != Mesh::ARRAY_MAX); - Surface s; - s.primitive = p_primitive; - s.arrays = p_arrays; - s.name = p_name; - s.flags = p_flags; - - Vector vertex_array = p_arrays[Mesh::ARRAY_VERTEX]; - int vertex_count = vertex_array.size(); - ERR_FAIL_COND(vertex_count == 0); - - for (int i = 0; i < blend_shapes.size(); i++) { - Array bsdata = p_blend_shapes[i]; - ERR_FAIL_COND(bsdata.size() != Mesh::ARRAY_MAX); - Vector vertex_data = bsdata[Mesh::ARRAY_VERTEX]; - ERR_FAIL_COND(vertex_data.size() != vertex_count); - Surface::BlendShape bs; - bs.arrays = bsdata; - s.blend_shape_data.push_back(bs); - } - - List lods; - p_lods.get_key_list(&lods); - for (const Variant &E : lods) { - ERR_CONTINUE(!E.is_num()); - Surface::LOD lod; - lod.distance = E; - lod.indices = p_lods[E]; - ERR_CONTINUE(lod.indices.is_empty()); - s.lods.push_back(lod); - } - - s.material = p_material; - - surfaces.push_back(s); - mesh.unref(); -} - -int ImporterMesh::get_surface_count() const { - return surfaces.size(); -} - -Mesh::PrimitiveType ImporterMesh::get_surface_primitive_type(int p_surface) { - ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Mesh::PRIMITIVE_MAX); - return surfaces[p_surface].primitive; -} -Array ImporterMesh::get_surface_arrays(int p_surface) const { - ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Array()); - return surfaces[p_surface].arrays; -} -String ImporterMesh::get_surface_name(int p_surface) const { - ERR_FAIL_INDEX_V(p_surface, surfaces.size(), String()); - return surfaces[p_surface].name; -} -void ImporterMesh::set_surface_name(int p_surface, const String &p_name) { - ERR_FAIL_INDEX(p_surface, surfaces.size()); - surfaces.write[p_surface].name = p_name; - mesh.unref(); -} - -Array ImporterMesh::get_surface_blend_shape_arrays(int p_surface, int p_blend_shape) const { - ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Array()); - ERR_FAIL_INDEX_V(p_blend_shape, surfaces[p_surface].blend_shape_data.size(), Array()); - return surfaces[p_surface].blend_shape_data[p_blend_shape].arrays; -} -int ImporterMesh::get_surface_lod_count(int p_surface) const { - ERR_FAIL_INDEX_V(p_surface, surfaces.size(), 0); - return surfaces[p_surface].lods.size(); -} -Vector ImporterMesh::get_surface_lod_indices(int p_surface, int p_lod) const { - ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Vector()); - ERR_FAIL_INDEX_V(p_lod, surfaces[p_surface].lods.size(), Vector()); - - return surfaces[p_surface].lods[p_lod].indices; -} - -float ImporterMesh::get_surface_lod_size(int p_surface, int p_lod) const { - ERR_FAIL_INDEX_V(p_surface, surfaces.size(), 0); - ERR_FAIL_INDEX_V(p_lod, surfaces[p_surface].lods.size(), 0); - return surfaces[p_surface].lods[p_lod].distance; -} - -uint64_t ImporterMesh::get_surface_format(int p_surface) const { - ERR_FAIL_INDEX_V(p_surface, surfaces.size(), 0); - return surfaces[p_surface].flags; -} - -Ref ImporterMesh::get_surface_material(int p_surface) const { - ERR_FAIL_INDEX_V(p_surface, surfaces.size(), Ref()); - return surfaces[p_surface].material; -} - -void ImporterMesh::set_surface_material(int p_surface, const Ref &p_material) { - ERR_FAIL_INDEX(p_surface, surfaces.size()); - surfaces.write[p_surface].material = p_material; - mesh.unref(); -} - -#define VERTEX_SKIN_FUNC(bone_count, vert_idx, read_array, write_array, transform_array, bone_array, weight_array) \ - Vector3 transformed_vert; \ - for (unsigned int weight_idx = 0; weight_idx < bone_count; weight_idx++) { \ - int bone_idx = bone_array[vert_idx * bone_count + weight_idx]; \ - float w = weight_array[vert_idx * bone_count + weight_idx]; \ - if (w < FLT_EPSILON) { \ - continue; \ - } \ - ERR_FAIL_INDEX(bone_idx, static_cast(transform_array.size())); \ - transformed_vert += transform_array[bone_idx].xform(read_array[vert_idx]) * w; \ - } \ - write_array[vert_idx] = transformed_vert; - -void ImporterMesh::generate_lods(float p_normal_merge_angle, float p_normal_split_angle, Array p_bone_transform_array) { - if (!SurfaceTool::simplify_scale_func) { - return; - } - if (!SurfaceTool::simplify_with_attrib_func) { - return; - } - if (!SurfaceTool::optimize_vertex_cache_func) { - return; - } - - LocalVector bone_transform_vector; - for (int i = 0; i < p_bone_transform_array.size(); i++) { - ERR_FAIL_COND(p_bone_transform_array[i].get_type() != Variant::TRANSFORM3D); - bone_transform_vector.push_back(p_bone_transform_array[i]); - } - - for (int i = 0; i < surfaces.size(); i++) { - if (surfaces[i].primitive != Mesh::PRIMITIVE_TRIANGLES) { - continue; - } - - surfaces.write[i].lods.clear(); - Vector vertices = surfaces[i].arrays[RS::ARRAY_VERTEX]; - PackedInt32Array indices = surfaces[i].arrays[RS::ARRAY_INDEX]; - Vector normals = surfaces[i].arrays[RS::ARRAY_NORMAL]; - Vector uvs = surfaces[i].arrays[RS::ARRAY_TEX_UV]; - Vector uv2s = surfaces[i].arrays[RS::ARRAY_TEX_UV2]; - Vector bones = surfaces[i].arrays[RS::ARRAY_BONES]; - Vector weights = surfaces[i].arrays[RS::ARRAY_WEIGHTS]; - - unsigned int index_count = indices.size(); - unsigned int vertex_count = vertices.size(); - - if (index_count == 0) { - continue; //no lods if no indices - } - - const Vector3 *vertices_ptr = vertices.ptr(); - const int *indices_ptr = indices.ptr(); - - if (normals.is_empty()) { - normals.resize(index_count); - Vector3 *n_ptr = normals.ptrw(); - for (unsigned int j = 0; j < index_count; j += 3) { - const Vector3 &v0 = vertices_ptr[indices_ptr[j + 0]]; - const Vector3 &v1 = vertices_ptr[indices_ptr[j + 1]]; - const Vector3 &v2 = vertices_ptr[indices_ptr[j + 2]]; - Vector3 n = vec3_cross(v0 - v2, v0 - v1).normalized(); - n_ptr[j + 0] = n; - n_ptr[j + 1] = n; - n_ptr[j + 2] = n; - } - } - - if (bones.size() > 0 && weights.size() && bone_transform_vector.size() > 0) { - Vector3 *vertices_ptrw = vertices.ptrw(); - - // Apply bone transforms to regular surface. - unsigned int bone_weight_length = surfaces[i].flags & Mesh::ARRAY_FLAG_USE_8_BONE_WEIGHTS ? 8 : 4; - - const int *bo = bones.ptr(); - const float *we = weights.ptr(); - - for (unsigned int j = 0; j < vertex_count; j++) { - VERTEX_SKIN_FUNC(bone_weight_length, j, vertices_ptr, vertices_ptrw, bone_transform_vector, bo, we) - } - - vertices_ptr = vertices.ptr(); - } - - float normal_merge_threshold = Math::cos(Math::deg_to_rad(p_normal_merge_angle)); - float normal_pre_split_threshold = Math::cos(Math::deg_to_rad(MIN(180.0f, p_normal_split_angle * 2.0f))); - float normal_split_threshold = Math::cos(Math::deg_to_rad(p_normal_split_angle)); - const Vector3 *normals_ptr = normals.ptr(); - - HashMap>> unique_vertices; - - LocalVector vertex_remap; - LocalVector vertex_inverse_remap; - LocalVector merged_vertices; - LocalVector merged_normals; - LocalVector merged_normals_counts; - const Vector2 *uvs_ptr = uvs.ptr(); - const Vector2 *uv2s_ptr = uv2s.ptr(); - - for (unsigned int j = 0; j < vertex_count; j++) { - const Vector3 &v = vertices_ptr[j]; - const Vector3 &n = normals_ptr[j]; - - HashMap>>::Iterator E = unique_vertices.find(v); - - if (E) { - const LocalVector> &close_verts = E->value; - - bool found = false; - for (const Pair &idx : close_verts) { - bool is_uvs_close = (!uvs_ptr || uvs_ptr[j].distance_squared_to(uvs_ptr[idx.second]) < CMP_EPSILON2); - bool is_uv2s_close = (!uv2s_ptr || uv2s_ptr[j].distance_squared_to(uv2s_ptr[idx.second]) < CMP_EPSILON2); - ERR_FAIL_INDEX(idx.second, normals.size()); - bool is_normals_close = normals[idx.second].dot(n) > normal_merge_threshold; - if (is_uvs_close && is_uv2s_close && is_normals_close) { - vertex_remap.push_back(idx.first); - merged_normals[idx.first] += normals[idx.second]; - merged_normals_counts[idx.first]++; - found = true; - break; - } - } - - if (!found) { - int vcount = merged_vertices.size(); - unique_vertices[v].push_back(Pair(vcount, j)); - vertex_inverse_remap.push_back(j); - merged_vertices.push_back(v); - vertex_remap.push_back(vcount); - merged_normals.push_back(normals_ptr[j]); - merged_normals_counts.push_back(1); - } - } else { - int vcount = merged_vertices.size(); - unique_vertices[v] = LocalVector>(); - unique_vertices[v].push_back(Pair(vcount, j)); - vertex_inverse_remap.push_back(j); - merged_vertices.push_back(v); - vertex_remap.push_back(vcount); - merged_normals.push_back(normals_ptr[j]); - merged_normals_counts.push_back(1); - } - } - - LocalVector merged_indices; - merged_indices.resize(index_count); - for (unsigned int j = 0; j < index_count; j++) { - merged_indices[j] = vertex_remap[indices[j]]; - } - - unsigned int merged_vertex_count = merged_vertices.size(); - const Vector3 *merged_vertices_ptr = merged_vertices.ptr(); - const int32_t *merged_indices_ptr = merged_indices.ptr(); - - { - const int *counts_ptr = merged_normals_counts.ptr(); - Vector3 *merged_normals_ptrw = merged_normals.ptr(); - for (unsigned int j = 0; j < merged_vertex_count; j++) { - merged_normals_ptrw[j] /= counts_ptr[j]; - } - } - - const float normal_weights[3] = { - // Give some weight to normal preservation, may be worth exposing as an import setting - 2.0f, 2.0f, 2.0f - }; - - Vector merged_vertices_f32 = vector3_to_float32_array(merged_vertices_ptr, merged_vertex_count); - float scale = SurfaceTool::simplify_scale_func(merged_vertices_f32.ptr(), merged_vertex_count, sizeof(float) * 3); - - unsigned int index_target = 12; // Start with the smallest target, 4 triangles - unsigned int last_index_count = 0; - - int split_vertex_count = vertex_count; - LocalVector split_vertex_normals; - LocalVector split_vertex_indices; - split_vertex_normals.reserve(index_count / 3); - split_vertex_indices.reserve(index_count / 3); - - RandomPCG pcg; - pcg.seed(123456789); // Keep seed constant across imports - - Ref raycaster = StaticRaycaster::create(); - if (raycaster.is_valid()) { - raycaster->add_mesh(vertices, indices, 0); - raycaster->commit(); - } - - const float max_mesh_error = FLT_MAX; // We don't want to limit by error, just by index target - float mesh_error = 0.0f; - - while (index_target < index_count) { - PackedInt32Array new_indices; - new_indices.resize(index_count); - - Vector merged_normals_f32 = vector3_to_float32_array(merged_normals.ptr(), merged_normals.size()); - const int simplify_options = SurfaceTool::SIMPLIFY_LOCK_BORDER; - - size_t new_index_count = SurfaceTool::simplify_with_attrib_func( - (unsigned int *)new_indices.ptrw(), - (const uint32_t *)merged_indices_ptr, index_count, - merged_vertices_f32.ptr(), merged_vertex_count, - sizeof(float) * 3, // Vertex stride - merged_normals_f32.ptr(), - sizeof(float) * 3, // Attribute stride - normal_weights, 3, - index_target, - max_mesh_error, - simplify_options, - &mesh_error); - - if (new_index_count < last_index_count * 1.5f) { - index_target = index_target * 1.5f; - continue; - } - - if (new_index_count == 0 || (new_index_count >= (index_count * 0.75f))) { - break; - } - if (new_index_count > 5000000) { - // This limit theoretically shouldn't be needed, but it's here - // as an ad-hoc fix to prevent a crash with complex meshes. - // The crash still happens with limit of 6000000, but 5000000 works. - // In the future, identify what's causing that crash and fix it. - WARN_PRINT("Mesh LOD generation failed for mesh " + get_name() + " surface " + itos(i) + ", mesh is too complex. Some automatic LODs were not generated."); - break; - } - - new_indices.resize(new_index_count); - - LocalVector> vertex_corners; - vertex_corners.resize(vertex_count); - { - int *ptrw = new_indices.ptrw(); - for (unsigned int j = 0; j < new_index_count; j++) { - const int &remapped = vertex_inverse_remap[ptrw[j]]; - vertex_corners[remapped].push_back(j); - ptrw[j] = remapped; - } - } - - if (raycaster.is_valid()) { - float error_factor = 1.0f / (scale * MAX(mesh_error, 0.15)); - const float ray_bias = 0.05; - float ray_length = ray_bias + mesh_error * scale * 3.0f; - - Vector rays; - LocalVector ray_uvs; - - int32_t *new_indices_ptr = new_indices.ptrw(); - - int current_ray_count = 0; - for (unsigned int j = 0; j < new_index_count; j += 3) { - const Vector3 &v0 = vertices_ptr[new_indices_ptr[j + 0]]; - const Vector3 &v1 = vertices_ptr[new_indices_ptr[j + 1]]; - const Vector3 &v2 = vertices_ptr[new_indices_ptr[j + 2]]; - Vector3 face_normal = vec3_cross(v0 - v2, v0 - v1); - float face_area = face_normal.length(); // Actually twice the face area, since it's the same error_factor on all faces, we don't care - if (!Math::is_finite(face_area) || face_area == 0) { - WARN_PRINT_ONCE("Ignoring face with non-finite normal in LOD generation."); - continue; - } - - Vector3 dir = face_normal / face_area; - int ray_count = CLAMP(5.0 * face_area * error_factor, 16, 64); - - rays.resize(current_ray_count + ray_count); - StaticRaycaster::Ray *rays_ptr = rays.ptrw(); - - ray_uvs.resize(current_ray_count + ray_count); - Vector2 *ray_uvs_ptr = ray_uvs.ptr(); - - for (int k = 0; k < ray_count; k++) { - float u = pcg.randf(); - float v = pcg.randf(); - - if (u + v >= 1.0f) { - u = 1.0f - u; - v = 1.0f - v; - } - - u = 0.9f * u + 0.05f / 3.0f; // Give barycentric coordinates some padding, we don't want to sample right on the edge - v = 0.9f * v + 0.05f / 3.0f; // v = (v - one_third) * 0.95f + one_third; - float w = 1.0f - u - v; - - Vector3 org = v0 * w + v1 * u + v2 * v; - org -= dir * ray_bias; - rays_ptr[current_ray_count + k] = StaticRaycaster::Ray(org, dir, 0.0f, ray_length); - rays_ptr[current_ray_count + k].id = j / 3; - ray_uvs_ptr[current_ray_count + k] = Vector2(u, v); - } - - current_ray_count += ray_count; - } - - raycaster->intersect(rays); - - LocalVector ray_normals; - LocalVector ray_normal_weights; - - ray_normals.resize(new_index_count); - ray_normal_weights.resize(new_index_count); - - for (unsigned int j = 0; j < new_index_count; j++) { - ray_normal_weights[j] = 0.0f; - } - - const StaticRaycaster::Ray *rp = rays.ptr(); - for (int j = 0; j < rays.size(); j++) { - if (rp[j].geomID != 0) { // Ray missed - continue; - } - - if (rp[j].normal.normalized().dot(rp[j].dir) > 0.0f) { // Hit a back face. - continue; - } - - const float &u = rp[j].u; - const float &v = rp[j].v; - const float w = 1.0f - u - v; - - const unsigned int &hit_tri_id = rp[j].primID; - const unsigned int &orig_tri_id = rp[j].id; - - const Vector3 &n0 = normals_ptr[indices_ptr[hit_tri_id * 3 + 0]]; - const Vector3 &n1 = normals_ptr[indices_ptr[hit_tri_id * 3 + 1]]; - const Vector3 &n2 = normals_ptr[indices_ptr[hit_tri_id * 3 + 2]]; - Vector3 normal = n0 * w + n1 * u + n2 * v; - - Vector2 orig_uv = ray_uvs[j]; - const real_t orig_bary[3] = { 1.0f - orig_uv.x - orig_uv.y, orig_uv.x, orig_uv.y }; - for (int k = 0; k < 3; k++) { - int idx = orig_tri_id * 3 + k; - real_t weight = orig_bary[k]; - ray_normals[idx] += normal * weight; - ray_normal_weights[idx] += weight; - } - } - - for (unsigned int j = 0; j < new_index_count; j++) { - if (ray_normal_weights[j] < 1.0f) { // Not enough data, the new normal would be just a bad guess - ray_normals[j] = Vector3(); - } else { - ray_normals[j] /= ray_normal_weights[j]; - } - } - - LocalVector> normal_group_indices; - LocalVector normal_group_averages; - normal_group_indices.reserve(24); - normal_group_averages.reserve(24); - - for (unsigned int j = 0; j < vertex_count; j++) { - const LocalVector &corners = vertex_corners[j]; - const Vector3 &vertex_normal = normals_ptr[j]; - - for (const int &corner_idx : corners) { - const Vector3 &ray_normal = ray_normals[corner_idx]; - - if (ray_normal.length_squared() < CMP_EPSILON2) { - continue; - } - - bool found = false; - for (unsigned int l = 0; l < normal_group_indices.size(); l++) { - LocalVector &group_indices = normal_group_indices[l]; - Vector3 n = normal_group_averages[l] / group_indices.size(); - if (n.dot(ray_normal) > normal_pre_split_threshold) { - found = true; - group_indices.push_back(corner_idx); - normal_group_averages[l] += ray_normal; - break; - } - } - - if (!found) { - normal_group_indices.push_back({ corner_idx }); - normal_group_averages.push_back(ray_normal); - } - } - - for (unsigned int k = 0; k < normal_group_indices.size(); k++) { - LocalVector &group_indices = normal_group_indices[k]; - Vector3 n = normal_group_averages[k] / group_indices.size(); - - if (vertex_normal.dot(n) < normal_split_threshold) { - split_vertex_indices.push_back(j); - split_vertex_normals.push_back(n); - int new_idx = split_vertex_count++; - for (const int &index : group_indices) { - new_indices_ptr[index] = new_idx; - } - } - } - - normal_group_indices.clear(); - normal_group_averages.clear(); - } - } - - Surface::LOD lod; - lod.distance = MAX(mesh_error * scale, CMP_EPSILON2); - lod.indices = new_indices; - surfaces.write[i].lods.push_back(lod); - index_target = MAX(new_index_count, index_target) * 2; - last_index_count = new_index_count; - - if (mesh_error == 0.0f) { - break; - } - } - - surfaces.write[i].split_normals(split_vertex_indices, split_vertex_normals); - surfaces.write[i].lods.sort_custom(); - - for (int j = 0; j < surfaces.write[i].lods.size(); j++) { - Surface::LOD &lod = surfaces.write[i].lods.write[j]; - unsigned int *lod_indices_ptr = (unsigned int *)lod.indices.ptrw(); - SurfaceTool::optimize_vertex_cache_func(lod_indices_ptr, lod_indices_ptr, lod.indices.size(), split_vertex_count); - } - } -} - -bool ImporterMesh::has_mesh() const { - return mesh.is_valid(); -} - -Ref ImporterMesh::get_mesh(const Ref &p_base) { - ERR_FAIL_COND_V(surfaces.is_empty(), Ref()); - - if (mesh.is_null()) { - if (p_base.is_valid()) { - mesh = p_base; - } - if (mesh.is_null()) { - mesh.instantiate(); - } - mesh->set_name(get_name()); - if (has_meta("import_id")) { - mesh->set_meta("import_id", get_meta("import_id")); - } - for (int i = 0; i < blend_shapes.size(); i++) { - mesh->add_blend_shape(blend_shapes[i]); - } - mesh->set_blend_shape_mode(blend_shape_mode); - for (int i = 0; i < surfaces.size(); i++) { - Array bs_data; - if (surfaces[i].blend_shape_data.size()) { - for (int j = 0; j < surfaces[i].blend_shape_data.size(); j++) { - bs_data.push_back(surfaces[i].blend_shape_data[j].arrays); - } - } - Dictionary lods; - if (surfaces[i].lods.size()) { - for (int j = 0; j < surfaces[i].lods.size(); j++) { - lods[surfaces[i].lods[j].distance] = surfaces[i].lods[j].indices; - } - } - - mesh->add_surface_from_arrays(surfaces[i].primitive, surfaces[i].arrays, bs_data, lods, surfaces[i].flags); - if (surfaces[i].material.is_valid()) { - mesh->surface_set_material(mesh->get_surface_count() - 1, surfaces[i].material); - } - if (!surfaces[i].name.is_empty()) { - mesh->surface_set_name(mesh->get_surface_count() - 1, surfaces[i].name); - } - } - - mesh->set_lightmap_size_hint(lightmap_size_hint); - - if (shadow_mesh.is_valid()) { - Ref shadow = shadow_mesh->get_mesh(); - mesh->set_shadow_mesh(shadow); - } - } - - return mesh; -} - -void ImporterMesh::clear() { - surfaces.clear(); - blend_shapes.clear(); - mesh.unref(); -} - -void ImporterMesh::create_shadow_mesh() { - if (shadow_mesh.is_valid()) { - shadow_mesh.unref(); - } - - //no shadow mesh for blendshapes - if (blend_shapes.size() > 0) { - return; - } - //no shadow mesh for skeletons - for (int i = 0; i < surfaces.size(); i++) { - if (surfaces[i].arrays[RS::ARRAY_BONES].get_type() != Variant::NIL) { - return; - } - if (surfaces[i].arrays[RS::ARRAY_WEIGHTS].get_type() != Variant::NIL) { - return; - } - } - - shadow_mesh.instantiate(); - - for (int i = 0; i < surfaces.size(); i++) { - LocalVector vertex_remap; - Vector new_vertices; - Vector vertices = surfaces[i].arrays[RS::ARRAY_VERTEX]; - int vertex_count = vertices.size(); - { - HashMap unique_vertices; - const Vector3 *vptr = vertices.ptr(); - for (int j = 0; j < vertex_count; j++) { - const Vector3 &v = vptr[j]; - - HashMap::Iterator E = unique_vertices.find(v); - - if (E) { - vertex_remap.push_back(E->value); - } else { - int vcount = unique_vertices.size(); - unique_vertices[v] = vcount; - vertex_remap.push_back(vcount); - new_vertices.push_back(v); - } - } - } - - Array new_surface; - new_surface.resize(RS::ARRAY_MAX); - Dictionary lods; - - // print_line("original vertex count: " + itos(vertices.size()) + " new vertex count: " + itos(new_vertices.size())); - - new_surface[RS::ARRAY_VERTEX] = new_vertices; - - Vector indices = surfaces[i].arrays[RS::ARRAY_INDEX]; - if (indices.size()) { - int index_count = indices.size(); - const int *index_rptr = indices.ptr(); - Vector new_indices; - new_indices.resize(indices.size()); - int *index_wptr = new_indices.ptrw(); - - for (int j = 0; j < index_count; j++) { - int index = index_rptr[j]; - ERR_FAIL_INDEX(index, vertex_count); - index_wptr[j] = vertex_remap[index]; - } - - new_surface[RS::ARRAY_INDEX] = new_indices; - - // Make sure the same LODs as the full version are used. - // This makes it more coherent between rendered model and its shadows. - for (int j = 0; j < surfaces[i].lods.size(); j++) { - indices = surfaces[i].lods[j].indices; - - index_count = indices.size(); - index_rptr = indices.ptr(); - new_indices.resize(indices.size()); - index_wptr = new_indices.ptrw(); - - for (int k = 0; k < index_count; k++) { - int index = index_rptr[k]; - ERR_FAIL_INDEX(index, vertex_count); - index_wptr[k] = vertex_remap[index]; - } - - lods[surfaces[i].lods[j].distance] = new_indices; - } - } - - shadow_mesh->add_surface(surfaces[i].primitive, new_surface, Array(), lods, Ref(), surfaces[i].name, surfaces[i].flags); - } -} - -Ref ImporterMesh::get_shadow_mesh() const { - return shadow_mesh; -} - -void ImporterMesh::_set_data(const Dictionary &p_data) { - clear(); - if (p_data.has("blend_shape_names")) { - blend_shapes = p_data["blend_shape_names"]; - } - if (p_data.has("surfaces")) { - Array surface_arr = p_data["surfaces"]; - for (int i = 0; i < surface_arr.size(); i++) { - Dictionary s = surface_arr[i]; - ERR_CONTINUE(!s.has("primitive")); - ERR_CONTINUE(!s.has("arrays")); - Mesh::PrimitiveType prim = Mesh::PrimitiveType(int(s["primitive"])); - ERR_CONTINUE(prim >= Mesh::PRIMITIVE_MAX); - Array arr = s["arrays"]; - Dictionary lods; - String surf_name; - if (s.has("name")) { - surf_name = s["name"]; - } - if (s.has("lods")) { - lods = s["lods"]; - } - Array b_shapes; - if (s.has("b_shapes")) { - b_shapes = s["b_shapes"]; - } - Ref material; - if (s.has("material")) { - material = s["material"]; - } - uint64_t flags = 0; - if (s.has("flags")) { - flags = s["flags"]; - } - add_surface(prim, arr, b_shapes, lods, material, surf_name, flags); - } - } -} -Dictionary ImporterMesh::_get_data() const { - Dictionary data; - if (blend_shapes.size()) { - data["blend_shape_names"] = blend_shapes; - } - Array surface_arr; - for (int i = 0; i < surfaces.size(); i++) { - Dictionary d; - d["primitive"] = surfaces[i].primitive; - d["arrays"] = surfaces[i].arrays; - if (surfaces[i].blend_shape_data.size()) { - Array bs_data; - for (int j = 0; j < surfaces[i].blend_shape_data.size(); j++) { - bs_data.push_back(surfaces[i].blend_shape_data[j].arrays); - } - d["blend_shapes"] = bs_data; - } - if (surfaces[i].lods.size()) { - Dictionary lods; - for (int j = 0; j < surfaces[i].lods.size(); j++) { - lods[surfaces[i].lods[j].distance] = surfaces[i].lods[j].indices; - } - d["lods"] = lods; - } - - if (surfaces[i].material.is_valid()) { - d["material"] = surfaces[i].material; - } - - if (!surfaces[i].name.is_empty()) { - d["name"] = surfaces[i].name; - } - - d["flags"] = surfaces[i].flags; - - surface_arr.push_back(d); - } - data["surfaces"] = surface_arr; - return data; -} - -Vector ImporterMesh::get_faces() const { - Vector faces; - for (int i = 0; i < surfaces.size(); i++) { - if (surfaces[i].primitive == Mesh::PRIMITIVE_TRIANGLES) { - Vector vertices = surfaces[i].arrays[Mesh::ARRAY_VERTEX]; - Vector indices = surfaces[i].arrays[Mesh::ARRAY_INDEX]; - if (indices.size()) { - for (int j = 0; j < indices.size(); j += 3) { - Face3 f; - f.vertex[0] = vertices[indices[j + 0]]; - f.vertex[1] = vertices[indices[j + 1]]; - f.vertex[2] = vertices[indices[j + 2]]; - faces.push_back(f); - } - } else { - for (int j = 0; j < vertices.size(); j += 3) { - Face3 f; - f.vertex[0] = vertices[j + 0]; - f.vertex[1] = vertices[j + 1]; - f.vertex[2] = vertices[j + 2]; - faces.push_back(f); - } - } - } - } - - return faces; -} - -Vector> ImporterMesh::convex_decompose(const Ref &p_settings) const { - ERR_FAIL_NULL_V(Mesh::convex_decomposition_function, Vector>()); - - const Vector faces = get_faces(); - int face_count = faces.size(); - - Vector vertices; - uint32_t vertex_count = 0; - vertices.resize(face_count * 3); - Vector indices; - indices.resize(face_count * 3); - { - HashMap vertex_map; - Vector3 *vertex_w = vertices.ptrw(); - uint32_t *index_w = indices.ptrw(); - for (int i = 0; i < face_count; i++) { - for (int j = 0; j < 3; j++) { - const Vector3 &vertex = faces[i].vertex[j]; - HashMap::Iterator found_vertex = vertex_map.find(vertex); - uint32_t index; - if (found_vertex) { - index = found_vertex->value; - } else { - index = vertex_count++; - vertex_map[vertex] = index; - vertex_w[index] = vertex; - } - index_w[i * 3 + j] = index; - } - } - } - vertices.resize(vertex_count); - - Vector> decomposed = Mesh::convex_decomposition_function((real_t *)vertices.ptr(), vertex_count, indices.ptr(), face_count, p_settings, nullptr); - - Vector> ret; - - for (int i = 0; i < decomposed.size(); i++) { - Ref shape; - shape.instantiate(); - shape->set_points(decomposed[i]); - ret.push_back(shape); - } - - return ret; -} - -Ref ImporterMesh::create_convex_shape(bool p_clean, bool p_simplify) const { - if (p_simplify) { - Ref settings; - settings.instantiate(); - settings->set_max_convex_hulls(1); - Vector> decomposed = convex_decompose(settings); - if (decomposed.size() == 1) { - return decomposed[0]; - } else { - ERR_PRINT("Convex shape simplification failed, falling back to simpler process."); - } - } - - Vector vertices; - for (int i = 0; i < get_surface_count(); i++) { - Array a = get_surface_arrays(i); - ERR_FAIL_COND_V(a.is_empty(), Ref()); - Vector v = a[Mesh::ARRAY_VERTEX]; - vertices.append_array(v); - } - - Ref shape = memnew(ConvexPolygonShape3D); - - if (p_clean) { - Geometry3D::MeshData md; - Error err = ConvexHullComputer::convex_hull(vertices, md); - if (err == OK) { - shape->set_points(md.vertices); - return shape; - } else { - ERR_PRINT("Convex shape cleaning failed, falling back to simpler process."); - } - } - - shape->set_points(vertices); - return shape; -} - -Ref ImporterMesh::create_trimesh_shape() const { - Vector faces = get_faces(); - if (faces.size() == 0) { - return Ref(); - } - - Vector face_points; - face_points.resize(faces.size() * 3); - - for (int i = 0; i < face_points.size(); i += 3) { - Face3 f = faces.get(i / 3); - face_points.set(i, f.vertex[0]); - face_points.set(i + 1, f.vertex[1]); - face_points.set(i + 2, f.vertex[2]); - } - - Ref shape = memnew(ConcavePolygonShape3D); - shape->set_faces(face_points); - return shape; -} - -Ref ImporterMesh::create_navigation_mesh() { - Vector faces = get_faces(); - if (faces.size() == 0) { - return Ref(); - } - - HashMap unique_vertices; - LocalVector face_indices; - - for (int i = 0; i < faces.size(); i++) { - for (int j = 0; j < 3; j++) { - Vector3 v = faces[i].vertex[j]; - int idx; - if (unique_vertices.has(v)) { - idx = unique_vertices[v]; - } else { - idx = unique_vertices.size(); - unique_vertices[v] = idx; - } - face_indices.push_back(idx); - } - } - - Vector vertices; - vertices.resize(unique_vertices.size()); - for (const KeyValue &E : unique_vertices) { - vertices.write[E.value] = E.key; - } - - Ref nm; - nm.instantiate(); - nm->set_vertices(vertices); - - Vector v3; - v3.resize(3); - for (uint32_t i = 0; i < face_indices.size(); i += 3) { - v3.write[0] = face_indices[i + 0]; - v3.write[1] = face_indices[i + 1]; - v3.write[2] = face_indices[i + 2]; - nm->add_polygon(v3); - } - - return nm; -} - -extern bool (*array_mesh_lightmap_unwrap_callback)(float p_texel_size, const float *p_vertices, const float *p_normals, int p_vertex_count, const int *p_indices, int p_index_count, const uint8_t *p_cache_data, bool *r_use_cache, uint8_t **r_mesh_cache, int *r_mesh_cache_size, float **r_uv, int **r_vertex, int *r_vertex_count, int **r_index, int *r_index_count, int *r_size_hint_x, int *r_size_hint_y); - -struct EditorSceneFormatImporterMeshLightmapSurface { - Ref material; - LocalVector vertices; - Mesh::PrimitiveType primitive = Mesh::PrimitiveType::PRIMITIVE_MAX; - uint64_t format = 0; - String name; -}; - -static const uint32_t custom_shift[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM1_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM2_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM3_SHIFT }; - -Error ImporterMesh::lightmap_unwrap_cached(const Transform3D &p_base_transform, float p_texel_size, const Vector &p_src_cache, Vector &r_dst_cache) { - ERR_FAIL_NULL_V(array_mesh_lightmap_unwrap_callback, ERR_UNCONFIGURED); - ERR_FAIL_COND_V_MSG(blend_shapes.size() != 0, ERR_UNAVAILABLE, "Can't unwrap mesh with blend shapes."); - - LocalVector vertices; - LocalVector normals; - LocalVector indices; - LocalVector uv; - LocalVector> uv_indices; - - Vector lightmap_surfaces; - - // Keep only the scale - Basis basis = p_base_transform.get_basis(); - Vector3 scale = Vector3(basis.get_column(0).length(), basis.get_column(1).length(), basis.get_column(2).length()); - - Transform3D transform; - transform.scale(scale); - - Basis normal_basis = transform.basis.inverse().transposed(); - - for (int i = 0; i < get_surface_count(); i++) { - EditorSceneFormatImporterMeshLightmapSurface s; - s.primitive = get_surface_primitive_type(i); - - ERR_FAIL_COND_V_MSG(s.primitive != Mesh::PRIMITIVE_TRIANGLES, ERR_UNAVAILABLE, "Only triangles are supported for lightmap unwrap."); - Array arrays = get_surface_arrays(i); - s.material = get_surface_material(i); - s.name = get_surface_name(i); - - SurfaceTool::create_vertex_array_from_triangle_arrays(arrays, s.vertices, &s.format); - - PackedVector3Array rvertices = arrays[Mesh::ARRAY_VERTEX]; - int vc = rvertices.size(); - - PackedVector3Array rnormals = arrays[Mesh::ARRAY_NORMAL]; - - if (!rnormals.size()) { - continue; - } - - int vertex_ofs = vertices.size() / 3; - - vertices.resize((vertex_ofs + vc) * 3); - normals.resize((vertex_ofs + vc) * 3); - uv_indices.resize(vertex_ofs + vc); - - for (int j = 0; j < vc; j++) { - Vector3 v = transform.xform(rvertices[j]); - Vector3 n = normal_basis.xform(rnormals[j]).normalized(); - - vertices[(j + vertex_ofs) * 3 + 0] = v.x; - vertices[(j + vertex_ofs) * 3 + 1] = v.y; - vertices[(j + vertex_ofs) * 3 + 2] = v.z; - normals[(j + vertex_ofs) * 3 + 0] = n.x; - normals[(j + vertex_ofs) * 3 + 1] = n.y; - normals[(j + vertex_ofs) * 3 + 2] = n.z; - uv_indices[j + vertex_ofs] = Pair(i, j); - } - - PackedInt32Array rindices = arrays[Mesh::ARRAY_INDEX]; - int ic = rindices.size(); - - float eps = 1.19209290e-7F; // Taken from xatlas.h - if (ic == 0) { - for (int j = 0; j < vc / 3; j++) { - Vector3 p0 = transform.xform(rvertices[j * 3 + 0]); - Vector3 p1 = transform.xform(rvertices[j * 3 + 1]); - Vector3 p2 = transform.xform(rvertices[j * 3 + 2]); - - if ((p0 - p1).length_squared() < eps || (p1 - p2).length_squared() < eps || (p2 - p0).length_squared() < eps) { - continue; - } - - indices.push_back(vertex_ofs + j * 3 + 0); - indices.push_back(vertex_ofs + j * 3 + 1); - indices.push_back(vertex_ofs + j * 3 + 2); - } - - } else { - for (int j = 0; j < ic / 3; j++) { - ERR_FAIL_INDEX_V(rindices[j * 3 + 0], rvertices.size(), ERR_INVALID_DATA); - ERR_FAIL_INDEX_V(rindices[j * 3 + 1], rvertices.size(), ERR_INVALID_DATA); - ERR_FAIL_INDEX_V(rindices[j * 3 + 2], rvertices.size(), ERR_INVALID_DATA); - Vector3 p0 = transform.xform(rvertices[rindices[j * 3 + 0]]); - Vector3 p1 = transform.xform(rvertices[rindices[j * 3 + 1]]); - Vector3 p2 = transform.xform(rvertices[rindices[j * 3 + 2]]); - - if ((p0 - p1).length_squared() < eps || (p1 - p2).length_squared() < eps || (p2 - p0).length_squared() < eps) { - continue; - } - - indices.push_back(vertex_ofs + rindices[j * 3 + 0]); - indices.push_back(vertex_ofs + rindices[j * 3 + 1]); - indices.push_back(vertex_ofs + rindices[j * 3 + 2]); - } - } - - lightmap_surfaces.push_back(s); - } - - //unwrap - - bool use_cache = true; // Used to request cache generation and to know if cache was used - uint8_t *gen_cache; - int gen_cache_size; - float *gen_uvs; - int *gen_vertices; - int *gen_indices; - int gen_vertex_count; - int gen_index_count; - int size_x; - int size_y; - - bool ok = array_mesh_lightmap_unwrap_callback(p_texel_size, vertices.ptr(), normals.ptr(), vertices.size() / 3, indices.ptr(), indices.size(), p_src_cache.ptr(), &use_cache, &gen_cache, &gen_cache_size, &gen_uvs, &gen_vertices, &gen_vertex_count, &gen_indices, &gen_index_count, &size_x, &size_y); - - if (!ok) { - return ERR_CANT_CREATE; - } - - //create surfacetools for each surface.. - LocalVector> surfaces_tools; - - for (int i = 0; i < lightmap_surfaces.size(); i++) { - Ref st; - st.instantiate(); - st->set_skin_weight_count((lightmap_surfaces[i].format & Mesh::ARRAY_FLAG_USE_8_BONE_WEIGHTS) ? SurfaceTool::SKIN_8_WEIGHTS : SurfaceTool::SKIN_4_WEIGHTS); - st->begin(Mesh::PRIMITIVE_TRIANGLES); - st->set_material(lightmap_surfaces[i].material); - st->set_meta("name", lightmap_surfaces[i].name); - - for (int custom_i = 0; custom_i < RS::ARRAY_CUSTOM_COUNT; custom_i++) { - st->set_custom_format(custom_i, (SurfaceTool::CustomFormat)((lightmap_surfaces[i].format >> custom_shift[custom_i]) & RS::ARRAY_FORMAT_CUSTOM_MASK)); - } - surfaces_tools.push_back(st); //stay there - } - - //remove surfaces - clear(); - - print_verbose("Mesh: Gen indices: " + itos(gen_index_count)); - - //go through all indices - for (int i = 0; i < gen_index_count; i += 3) { - ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 0]], (int)uv_indices.size(), ERR_BUG); - ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 1]], (int)uv_indices.size(), ERR_BUG); - ERR_FAIL_INDEX_V(gen_vertices[gen_indices[i + 2]], (int)uv_indices.size(), ERR_BUG); - - ERR_FAIL_COND_V(uv_indices[gen_vertices[gen_indices[i + 0]]].first != uv_indices[gen_vertices[gen_indices[i + 1]]].first || uv_indices[gen_vertices[gen_indices[i + 0]]].first != uv_indices[gen_vertices[gen_indices[i + 2]]].first, ERR_BUG); - - int surface = uv_indices[gen_vertices[gen_indices[i + 0]]].first; - - for (int j = 0; j < 3; j++) { - SurfaceTool::Vertex v = lightmap_surfaces[surface].vertices[uv_indices[gen_vertices[gen_indices[i + j]]].second]; - - if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_COLOR) { - surfaces_tools[surface]->set_color(v.color); - } - if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_TEX_UV) { - surfaces_tools[surface]->set_uv(v.uv); - } - if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_NORMAL) { - surfaces_tools[surface]->set_normal(v.normal); - } - if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_TANGENT) { - Plane t; - t.normal = v.tangent; - t.d = v.binormal.dot(v.normal.cross(v.tangent)) < 0 ? -1 : 1; - surfaces_tools[surface]->set_tangent(t); - } - if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_BONES) { - surfaces_tools[surface]->set_bones(v.bones); - } - if (lightmap_surfaces[surface].format & Mesh::ARRAY_FORMAT_WEIGHTS) { - surfaces_tools[surface]->set_weights(v.weights); - } - for (int custom_i = 0; custom_i < RS::ARRAY_CUSTOM_COUNT; custom_i++) { - if ((lightmap_surfaces[surface].format >> custom_shift[custom_i]) & RS::ARRAY_FORMAT_CUSTOM_MASK) { - surfaces_tools[surface]->set_custom(custom_i, v.custom[custom_i]); - } - } - - Vector2 uv2(gen_uvs[gen_indices[i + j] * 2 + 0], gen_uvs[gen_indices[i + j] * 2 + 1]); - surfaces_tools[surface]->set_uv2(uv2); - - surfaces_tools[surface]->add_vertex(v.vertex); - } - } - - //generate surfaces - for (int i = 0; i < lightmap_surfaces.size(); i++) { - Ref &tool = surfaces_tools[i]; - tool->index(); - Array arrays = tool->commit_to_arrays(); - - uint64_t format = lightmap_surfaces[i].format; - if (tool->get_skin_weight_count() == SurfaceTool::SKIN_8_WEIGHTS) { - format |= RS::ARRAY_FLAG_USE_8_BONE_WEIGHTS; - } else { - format &= ~RS::ARRAY_FLAG_USE_8_BONE_WEIGHTS; - } - - add_surface(tool->get_primitive_type(), arrays, Array(), Dictionary(), tool->get_material(), tool->get_meta("name"), format); - } - - set_lightmap_size_hint(Size2(size_x, size_y)); - - if (gen_cache_size > 0) { - r_dst_cache.resize(gen_cache_size); - memcpy(r_dst_cache.ptrw(), gen_cache, gen_cache_size); - memfree(gen_cache); - } - - if (!use_cache) { - // Cache was not used, free the buffers - memfree(gen_vertices); - memfree(gen_indices); - memfree(gen_uvs); - } - - return OK; -} - -void ImporterMesh::set_lightmap_size_hint(const Size2i &p_size) { - lightmap_size_hint = p_size; -} - -Size2i ImporterMesh::get_lightmap_size_hint() const { - return lightmap_size_hint; -} - -void ImporterMesh::_bind_methods() { - ClassDB::bind_method(D_METHOD("add_blend_shape", "name"), &ImporterMesh::add_blend_shape); - ClassDB::bind_method(D_METHOD("get_blend_shape_count"), &ImporterMesh::get_blend_shape_count); - ClassDB::bind_method(D_METHOD("get_blend_shape_name", "blend_shape_idx"), &ImporterMesh::get_blend_shape_name); - - ClassDB::bind_method(D_METHOD("set_blend_shape_mode", "mode"), &ImporterMesh::set_blend_shape_mode); - ClassDB::bind_method(D_METHOD("get_blend_shape_mode"), &ImporterMesh::get_blend_shape_mode); - - ClassDB::bind_method(D_METHOD("add_surface", "primitive", "arrays", "blend_shapes", "lods", "material", "name", "flags"), &ImporterMesh::add_surface, DEFVAL(TypedArray()), DEFVAL(Dictionary()), DEFVAL(Ref()), DEFVAL(String()), DEFVAL(0)); - - ClassDB::bind_method(D_METHOD("get_surface_count"), &ImporterMesh::get_surface_count); - ClassDB::bind_method(D_METHOD("get_surface_primitive_type", "surface_idx"), &ImporterMesh::get_surface_primitive_type); - ClassDB::bind_method(D_METHOD("get_surface_name", "surface_idx"), &ImporterMesh::get_surface_name); - ClassDB::bind_method(D_METHOD("get_surface_arrays", "surface_idx"), &ImporterMesh::get_surface_arrays); - ClassDB::bind_method(D_METHOD("get_surface_blend_shape_arrays", "surface_idx", "blend_shape_idx"), &ImporterMesh::get_surface_blend_shape_arrays); - ClassDB::bind_method(D_METHOD("get_surface_lod_count", "surface_idx"), &ImporterMesh::get_surface_lod_count); - ClassDB::bind_method(D_METHOD("get_surface_lod_size", "surface_idx", "lod_idx"), &ImporterMesh::get_surface_lod_size); - ClassDB::bind_method(D_METHOD("get_surface_lod_indices", "surface_idx", "lod_idx"), &ImporterMesh::get_surface_lod_indices); - ClassDB::bind_method(D_METHOD("get_surface_material", "surface_idx"), &ImporterMesh::get_surface_material); - ClassDB::bind_method(D_METHOD("get_surface_format", "surface_idx"), &ImporterMesh::get_surface_format); - - ClassDB::bind_method(D_METHOD("set_surface_name", "surface_idx", "name"), &ImporterMesh::set_surface_name); - ClassDB::bind_method(D_METHOD("set_surface_material", "surface_idx", "material"), &ImporterMesh::set_surface_material); - - ClassDB::bind_method(D_METHOD("generate_lods", "normal_merge_angle", "normal_split_angle", "bone_transform_array"), &ImporterMesh::generate_lods); - ClassDB::bind_method(D_METHOD("get_mesh", "base_mesh"), &ImporterMesh::get_mesh, DEFVAL(Ref())); - ClassDB::bind_method(D_METHOD("clear"), &ImporterMesh::clear); - - ClassDB::bind_method(D_METHOD("_set_data", "data"), &ImporterMesh::_set_data); - ClassDB::bind_method(D_METHOD("_get_data"), &ImporterMesh::_get_data); - - ClassDB::bind_method(D_METHOD("set_lightmap_size_hint", "size"), &ImporterMesh::set_lightmap_size_hint); - ClassDB::bind_method(D_METHOD("get_lightmap_size_hint"), &ImporterMesh::get_lightmap_size_hint); - - ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "_set_data", "_get_data"); -} diff --git a/scene/resources/importer_mesh.h b/scene/resources/importer_mesh.h deleted file mode 100644 index 7a19aabd29..0000000000 --- a/scene/resources/importer_mesh.h +++ /dev/null @@ -1,135 +0,0 @@ -/**************************************************************************/ -/* importer_mesh.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 IMPORTER_MESH_H -#define IMPORTER_MESH_H - -#include "core/io/resource.h" -#include "core/templates/local_vector.h" -#include "scene/resources/concave_polygon_shape_3d.h" -#include "scene/resources/convex_polygon_shape_3d.h" -#include "scene/resources/mesh.h" -#include "scene/resources/navigation_mesh.h" - -#include - -// The following classes are used by importers instead of ArrayMesh and MeshInstance3D -// so the data is not registered (hence, quality loss), importing happens faster and -// its easier to modify before saving - -class ImporterMesh : public Resource { - GDCLASS(ImporterMesh, Resource) - - struct Surface { - Mesh::PrimitiveType primitive; - Array arrays; - struct BlendShape { - Array arrays; - }; - Vector blend_shape_data; - struct LOD { - Vector indices; - float distance = 0.0f; - }; - Vector lods; - Ref material; - String name; - uint64_t flags = 0; - - struct LODComparator { - _FORCE_INLINE_ bool operator()(const LOD &l, const LOD &r) const { - return l.distance < r.distance; - } - }; - - void split_normals(const LocalVector &p_indices, const LocalVector &p_normals); - static void _split_normals(Array &r_arrays, const LocalVector &p_indices, const LocalVector &p_normals); - }; - Vector surfaces; - Vector blend_shapes; - Mesh::BlendShapeMode blend_shape_mode = Mesh::BLEND_SHAPE_MODE_NORMALIZED; - - Ref mesh; - - Ref shadow_mesh; - - Size2i lightmap_size_hint; - -protected: - void _set_data(const Dictionary &p_data); - Dictionary _get_data() const; - - static void _bind_methods(); - -public: - void add_blend_shape(const String &p_name); - int get_blend_shape_count() const; - String get_blend_shape_name(int p_blend_shape) const; - - void add_surface(Mesh::PrimitiveType p_primitive, const Array &p_arrays, const TypedArray &p_blend_shapes = Array(), const Dictionary &p_lods = Dictionary(), const Ref &p_material = Ref(), const String &p_name = String(), const uint64_t p_flags = 0); - int get_surface_count() const; - - void set_blend_shape_mode(Mesh::BlendShapeMode p_blend_shape_mode); - Mesh::BlendShapeMode get_blend_shape_mode() const; - - Mesh::PrimitiveType get_surface_primitive_type(int p_surface); - String get_surface_name(int p_surface) const; - void set_surface_name(int p_surface, const String &p_name); - Array get_surface_arrays(int p_surface) const; - Array get_surface_blend_shape_arrays(int p_surface, int p_blend_shape) const; - int get_surface_lod_count(int p_surface) const; - Vector get_surface_lod_indices(int p_surface, int p_lod) const; - float get_surface_lod_size(int p_surface, int p_lod) const; - Ref get_surface_material(int p_surface) const; - uint64_t get_surface_format(int p_surface) const; - - void set_surface_material(int p_surface, const Ref &p_material); - - void generate_lods(float p_normal_merge_angle, float p_normal_split_angle, Array p_skin_pose_transform_array); - - void create_shadow_mesh(); - Ref get_shadow_mesh() const; - - Vector get_faces() const; - Vector> convex_decompose(const Ref &p_settings) const; - Ref create_convex_shape(bool p_clean = true, bool p_simplify = false) const; - Ref create_trimesh_shape() const; - Ref create_navigation_mesh(); - Error lightmap_unwrap_cached(const Transform3D &p_base_transform, float p_texel_size, const Vector &p_src_cache, Vector &r_dst_cache); - - void set_lightmap_size_hint(const Size2i &p_size); - Size2i get_lightmap_size_hint() const; - - bool has_mesh() const; - Ref get_mesh(const Ref &p_base = Ref()); - void clear(); -}; - -#endif // IMPORTER_MESH_H diff --git a/scene/resources/mesh.cpp b/scene/resources/mesh.cpp index dcad58e6e2..a4e634af36 100644 --- a/scene/resources/mesh.cpp +++ b/scene/resources/mesh.cpp @@ -34,8 +34,8 @@ #include "core/templates/pair.h" #include "scene/resources/surface_tool.h" -#include "scene/resources/concave_polygon_shape_3d.h" -#include "scene/resources/convex_polygon_shape_3d.h" +#include "scene/resources/3d/concave_polygon_shape_3d.h" +#include "scene/resources/3d/convex_polygon_shape_3d.h" void MeshConvexDecompositionSettings::set_max_concavity(real_t p_max_concavity) { max_concavity = CLAMP(p_max_concavity, 0.001, 1.0); diff --git a/scene/resources/mesh.h b/scene/resources/mesh.h index 5233d75009..5c0e4e60aa 100644 --- a/scene/resources/mesh.h +++ b/scene/resources/mesh.h @@ -34,6 +34,7 @@ #include "core/io/resource.h" #include "core/math/face3.h" #include "core/math/triangle_mesh.h" +#include "scene/resources/3d/shape_3d.h" #include "scene/resources/material.h" #include "servers/rendering_server.h" diff --git a/scene/resources/mesh_library.cpp b/scene/resources/mesh_library.cpp deleted file mode 100644 index c2f721a80f..0000000000 --- a/scene/resources/mesh_library.cpp +++ /dev/null @@ -1,356 +0,0 @@ -/**************************************************************************/ -/* mesh_library.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 "mesh_library.h" - -#include "box_shape_3d.h" - -bool MeshLibrary::_set(const StringName &p_name, const Variant &p_value) { - String prop_name = p_name; - if (prop_name.begins_with("item/")) { - int idx = prop_name.get_slicec('/', 1).to_int(); - String what = prop_name.get_slicec('/', 2); - if (!item_map.has(idx)) { - create_item(idx); - } - - if (what == "name") { - set_item_name(idx, p_value); - } else if (what == "mesh") { - set_item_mesh(idx, p_value); - } else if (what == "mesh_transform") { - set_item_mesh_transform(idx, p_value); - } else if (what == "shape") { - Vector shapes; - ShapeData sd; - sd.shape = p_value; - shapes.push_back(sd); - set_item_shapes(idx, shapes); - } else if (what == "shapes") { - _set_item_shapes(idx, p_value); - } else if (what == "preview") { - set_item_preview(idx, p_value); - } else if (what == "navigation_mesh") { - set_item_navigation_mesh(idx, p_value); - } else if (what == "navigation_mesh_transform") { - set_item_navigation_mesh_transform(idx, p_value); -#ifndef DISABLE_DEPRECATED - } else if (what == "navmesh") { // Renamed in 4.0 beta 9. - set_item_navigation_mesh(idx, p_value); - } else if (what == "navmesh_transform") { // Renamed in 4.0 beta 9. - set_item_navigation_mesh_transform(idx, p_value); -#endif // DISABLE_DEPRECATED - } else if (what == "navigation_layers") { - set_item_navigation_layers(idx, p_value); - } else { - return false; - } - - return true; - } - - return false; -} - -bool MeshLibrary::_get(const StringName &p_name, Variant &r_ret) const { - String prop_name = p_name; - int idx = prop_name.get_slicec('/', 1).to_int(); - ERR_FAIL_COND_V(!item_map.has(idx), false); - String what = prop_name.get_slicec('/', 2); - - if (what == "name") { - r_ret = get_item_name(idx); - } else if (what == "mesh") { - r_ret = get_item_mesh(idx); - } else if (what == "mesh_transform") { - r_ret = get_item_mesh_transform(idx); - } else if (what == "shapes") { - r_ret = _get_item_shapes(idx); - } else if (what == "navigation_mesh") { - r_ret = get_item_navigation_mesh(idx); - } else if (what == "navigation_mesh_transform") { - r_ret = get_item_navigation_mesh_transform(idx); -#ifndef DISABLE_DEPRECATED - } else if (what == "navmesh") { // Renamed in 4.0 beta 9. - r_ret = get_item_navigation_mesh(idx); - } else if (what == "navmesh_transform") { // Renamed in 4.0 beta 9. - r_ret = get_item_navigation_mesh_transform(idx); -#endif // DISABLE_DEPRECATED - } else if (what == "navigation_layers") { - r_ret = get_item_navigation_layers(idx); - } else if (what == "preview") { - r_ret = get_item_preview(idx); - } else { - return false; - } - - return true; -} - -void MeshLibrary::_get_property_list(List *p_list) const { - for (const KeyValue &E : item_map) { - String prop_name = vformat("%s/%d/", PNAME("item"), E.key); - p_list->push_back(PropertyInfo(Variant::STRING, prop_name + PNAME("name"))); - p_list->push_back(PropertyInfo(Variant::OBJECT, prop_name + PNAME("mesh"), PROPERTY_HINT_RESOURCE_TYPE, "Mesh")); - p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, prop_name + PNAME("mesh_transform"), PROPERTY_HINT_NONE, "suffix:m")); - p_list->push_back(PropertyInfo(Variant::ARRAY, prop_name + PNAME("shapes"))); - p_list->push_back(PropertyInfo(Variant::OBJECT, prop_name + PNAME("navigation_mesh"), PROPERTY_HINT_RESOURCE_TYPE, "NavigationMesh")); - p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, prop_name + PNAME("navigation_mesh_transform"), PROPERTY_HINT_NONE, "suffix:m")); - p_list->push_back(PropertyInfo(Variant::INT, prop_name + PNAME("navigation_layers"), PROPERTY_HINT_LAYERS_3D_NAVIGATION)); - p_list->push_back(PropertyInfo(Variant::OBJECT, prop_name + PNAME("preview"), PROPERTY_HINT_RESOURCE_TYPE, "Texture2D", PROPERTY_USAGE_DEFAULT)); - } -} - -void MeshLibrary::create_item(int p_item) { - ERR_FAIL_COND(p_item < 0); - ERR_FAIL_COND(item_map.has(p_item)); - item_map[p_item] = Item(); - emit_changed(); - notify_property_list_changed(); -} - -void MeshLibrary::set_item_name(int p_item, const String &p_name) { - ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); - item_map[p_item].name = p_name; - emit_changed(); -} - -void MeshLibrary::set_item_mesh(int p_item, const Ref &p_mesh) { - ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); - item_map[p_item].mesh = p_mesh; - emit_changed(); -} - -void MeshLibrary::set_item_mesh_transform(int p_item, const Transform3D &p_transform) { - ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); - item_map[p_item].mesh_transform = p_transform; - emit_changed(); -} - -void MeshLibrary::set_item_shapes(int p_item, const Vector &p_shapes) { - ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); - item_map[p_item].shapes = p_shapes; - emit_changed(); - notify_property_list_changed(); -} - -void MeshLibrary::set_item_navigation_mesh(int p_item, const Ref &p_navigation_mesh) { - ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); - item_map[p_item].navigation_mesh = p_navigation_mesh; - emit_changed(); -} - -void MeshLibrary::set_item_navigation_mesh_transform(int p_item, const Transform3D &p_transform) { - ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); - item_map[p_item].navigation_mesh_transform = p_transform; - emit_changed(); -} - -void MeshLibrary::set_item_navigation_layers(int p_item, uint32_t p_navigation_layers) { - ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); - item_map[p_item].navigation_layers = p_navigation_layers; - emit_changed(); -} - -void MeshLibrary::set_item_preview(int p_item, const Ref &p_preview) { - ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); - item_map[p_item].preview = p_preview; - emit_changed(); -} - -String MeshLibrary::get_item_name(int p_item) const { - ERR_FAIL_COND_V_MSG(!item_map.has(p_item), "", "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); - return item_map[p_item].name; -} - -Ref MeshLibrary::get_item_mesh(int p_item) const { - ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Ref(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); - return item_map[p_item].mesh; -} - -Transform3D MeshLibrary::get_item_mesh_transform(int p_item) const { - ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Transform3D(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); - return item_map[p_item].mesh_transform; -} - -Vector MeshLibrary::get_item_shapes(int p_item) const { - ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Vector(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); - return item_map[p_item].shapes; -} - -Ref MeshLibrary::get_item_navigation_mesh(int p_item) const { - ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Ref(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); - return item_map[p_item].navigation_mesh; -} - -Transform3D MeshLibrary::get_item_navigation_mesh_transform(int p_item) const { - ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Transform3D(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); - return item_map[p_item].navigation_mesh_transform; -} - -uint32_t MeshLibrary::get_item_navigation_layers(int p_item) const { - ERR_FAIL_COND_V_MSG(!item_map.has(p_item), 0, "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); - return item_map[p_item].navigation_layers; -} - -Ref MeshLibrary::get_item_preview(int p_item) const { - ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Ref(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); - return item_map[p_item].preview; -} - -bool MeshLibrary::has_item(int p_item) const { - return item_map.has(p_item); -} - -void MeshLibrary::remove_item(int p_item) { - ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); - item_map.erase(p_item); - notify_property_list_changed(); - emit_changed(); -} - -void MeshLibrary::clear() { - item_map.clear(); - notify_property_list_changed(); - emit_changed(); -} - -Vector MeshLibrary::get_item_list() const { - Vector ret; - ret.resize(item_map.size()); - int idx = 0; - for (const KeyValue &E : item_map) { - ret.write[idx++] = E.key; - } - - return ret; -} - -int MeshLibrary::find_item_by_name(const String &p_name) const { - for (const KeyValue &E : item_map) { - if (E.value.name == p_name) { - return E.key; - } - } - return -1; -} - -int MeshLibrary::get_last_unused_item_id() const { - if (!item_map.size()) { - return 0; - } else { - return item_map.back()->key() + 1; - } -} - -void MeshLibrary::_set_item_shapes(int p_item, const Array &p_shapes) { - Array arr_shapes = p_shapes; - int size = p_shapes.size(); - if (size & 1) { - ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); - int prev_size = item_map[p_item].shapes.size() * 2; - - if (prev_size < size) { - // Check if last element is a shape. - Ref shape = arr_shapes[size - 1]; - if (shape.is_null()) { - Ref box_shape; - box_shape.instantiate(); - arr_shapes[size - 1] = box_shape; - } - - // Make sure the added element is a Transform3D. - arr_shapes.push_back(Transform3D()); - size++; - } else { - size--; - arr_shapes.resize(size); - } - } - - Vector shapes; - for (int i = 0; i < size; i += 2) { - ShapeData sd; - sd.shape = arr_shapes[i + 0]; - sd.local_transform = arr_shapes[i + 1]; - - if (sd.shape.is_valid()) { - shapes.push_back(sd); - } - } - - set_item_shapes(p_item, shapes); -} - -Array MeshLibrary::_get_item_shapes(int p_item) const { - Vector shapes = get_item_shapes(p_item); - Array ret; - for (int i = 0; i < shapes.size(); i++) { - ret.push_back(shapes[i].shape); - ret.push_back(shapes[i].local_transform); - } - - return ret; -} - -void MeshLibrary::reset_state() { - clear(); -} -void MeshLibrary::_bind_methods() { - ClassDB::bind_method(D_METHOD("create_item", "id"), &MeshLibrary::create_item); - ClassDB::bind_method(D_METHOD("set_item_name", "id", "name"), &MeshLibrary::set_item_name); - ClassDB::bind_method(D_METHOD("set_item_mesh", "id", "mesh"), &MeshLibrary::set_item_mesh); - ClassDB::bind_method(D_METHOD("set_item_mesh_transform", "id", "mesh_transform"), &MeshLibrary::set_item_mesh_transform); - ClassDB::bind_method(D_METHOD("set_item_navigation_mesh", "id", "navigation_mesh"), &MeshLibrary::set_item_navigation_mesh); - ClassDB::bind_method(D_METHOD("set_item_navigation_mesh_transform", "id", "navigation_mesh"), &MeshLibrary::set_item_navigation_mesh_transform); - ClassDB::bind_method(D_METHOD("set_item_navigation_layers", "id", "navigation_layers"), &MeshLibrary::set_item_navigation_layers); - ClassDB::bind_method(D_METHOD("set_item_shapes", "id", "shapes"), &MeshLibrary::_set_item_shapes); - ClassDB::bind_method(D_METHOD("set_item_preview", "id", "texture"), &MeshLibrary::set_item_preview); - ClassDB::bind_method(D_METHOD("get_item_name", "id"), &MeshLibrary::get_item_name); - ClassDB::bind_method(D_METHOD("get_item_mesh", "id"), &MeshLibrary::get_item_mesh); - ClassDB::bind_method(D_METHOD("get_item_mesh_transform", "id"), &MeshLibrary::get_item_mesh_transform); - ClassDB::bind_method(D_METHOD("get_item_navigation_mesh", "id"), &MeshLibrary::get_item_navigation_mesh); - ClassDB::bind_method(D_METHOD("get_item_navigation_mesh_transform", "id"), &MeshLibrary::get_item_navigation_mesh_transform); - ClassDB::bind_method(D_METHOD("get_item_navigation_layers", "id"), &MeshLibrary::get_item_navigation_layers); - ClassDB::bind_method(D_METHOD("get_item_shapes", "id"), &MeshLibrary::_get_item_shapes); - ClassDB::bind_method(D_METHOD("get_item_preview", "id"), &MeshLibrary::get_item_preview); - ClassDB::bind_method(D_METHOD("remove_item", "id"), &MeshLibrary::remove_item); - ClassDB::bind_method(D_METHOD("find_item_by_name", "name"), &MeshLibrary::find_item_by_name); - - ClassDB::bind_method(D_METHOD("clear"), &MeshLibrary::clear); - ClassDB::bind_method(D_METHOD("get_item_list"), &MeshLibrary::get_item_list); - ClassDB::bind_method(D_METHOD("get_last_unused_item_id"), &MeshLibrary::get_last_unused_item_id); -} - -MeshLibrary::MeshLibrary() { -} - -MeshLibrary::~MeshLibrary() { -} diff --git a/scene/resources/mesh_library.h b/scene/resources/mesh_library.h deleted file mode 100644 index f1a1e3e273..0000000000 --- a/scene/resources/mesh_library.h +++ /dev/null @@ -1,106 +0,0 @@ -/**************************************************************************/ -/* mesh_library.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 MESH_LIBRARY_H -#define MESH_LIBRARY_H - -#include "core/io/resource.h" -#include "core/templates/rb_map.h" -#include "scene/3d/navigation_region_3d.h" -#include "scene/resources/mesh.h" -#include "shape_3d.h" - -class MeshLibrary : public Resource { - GDCLASS(MeshLibrary, Resource); - RES_BASE_EXTENSION("meshlib"); - -public: - struct ShapeData { - Ref shape; - Transform3D local_transform; - }; - struct Item { - String name; - Ref mesh; - Transform3D mesh_transform; - Vector shapes; - Ref preview; - Ref navigation_mesh; - Transform3D navigation_mesh_transform; - uint32_t navigation_layers = 1; - }; - - RBMap item_map; - - void _set_item_shapes(int p_item, const Array &p_shapes); - Array _get_item_shapes(int p_item) const; - -protected: - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List *p_list) const; - - virtual void reset_state() override; - static void _bind_methods(); - -public: - void create_item(int p_item); - void set_item_name(int p_item, const String &p_name); - void set_item_mesh(int p_item, const Ref &p_mesh); - void set_item_mesh_transform(int p_item, const Transform3D &p_transform); - void set_item_navigation_mesh(int p_item, const Ref &p_navigation_mesh); - void set_item_navigation_mesh_transform(int p_item, const Transform3D &p_transform); - void set_item_navigation_layers(int p_item, uint32_t p_navigation_layers); - void set_item_shapes(int p_item, const Vector &p_shapes); - void set_item_preview(int p_item, const Ref &p_preview); - String get_item_name(int p_item) const; - Ref get_item_mesh(int p_item) const; - Transform3D get_item_mesh_transform(int p_item) const; - Ref get_item_navigation_mesh(int p_item) const; - Transform3D get_item_navigation_mesh_transform(int p_item) const; - uint32_t get_item_navigation_layers(int p_item) const; - Vector get_item_shapes(int p_item) const; - Ref get_item_preview(int p_item) const; - - void remove_item(int p_item); - bool has_item(int p_item) const; - - void clear(); - - int find_item_by_name(const String &p_name) const; - - Vector get_item_list() const; - int get_last_unused_item_id() const; - - MeshLibrary(); - ~MeshLibrary(); -}; - -#endif // MESH_LIBRARY_H diff --git a/scene/resources/primitive_meshes.cpp b/scene/resources/primitive_meshes.cpp deleted file mode 100644 index b8b4854109..0000000000 --- a/scene/resources/primitive_meshes.cpp +++ /dev/null @@ -1,3655 +0,0 @@ -/**************************************************************************/ -/* primitive_meshes.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 "primitive_meshes.h" - -#include "core/config/project_settings.h" -#include "scene/resources/theme.h" -#include "scene/theme/theme_db.h" -#include "servers/rendering_server.h" -#include "thirdparty/misc/polypartition.h" - -#define PADDING_REF_SIZE 1024.0 - -/** - PrimitiveMesh -*/ -void PrimitiveMesh::_update() const { - Array arr; - if (GDVIRTUAL_CALL(_create_mesh_array, arr)) { - ERR_FAIL_COND_MSG(arr.size() != RS::ARRAY_MAX, "_create_mesh_array must return an array of Mesh.ARRAY_MAX elements."); - } else { - arr.resize(RS::ARRAY_MAX); - _create_mesh_array(arr); - } - - Vector points = arr[RS::ARRAY_VERTEX]; - - ERR_FAIL_COND_MSG(points.is_empty(), "_create_mesh_array must return at least a vertex array."); - - aabb = AABB(); - - int pc = points.size(); - ERR_FAIL_COND(pc == 0); - { - const Vector3 *r = points.ptr(); - for (int i = 0; i < pc; i++) { - if (i == 0) { - aabb.position = r[i]; - } else { - aabb.expand_to(r[i]); - } - } - } - - Vector indices = arr[RS::ARRAY_INDEX]; - - if (flip_faces) { - Vector normals = arr[RS::ARRAY_NORMAL]; - - if (normals.size() && indices.size()) { - { - int nc = normals.size(); - Vector3 *w = normals.ptrw(); - for (int i = 0; i < nc; i++) { - w[i] = -w[i]; - } - } - - { - int ic = indices.size(); - int *w = indices.ptrw(); - for (int i = 0; i < ic; i += 3) { - SWAP(w[i + 0], w[i + 1]); - } - } - arr[RS::ARRAY_NORMAL] = normals; - arr[RS::ARRAY_INDEX] = indices; - } - } - - if (add_uv2) { - // _create_mesh_array should populate our UV2, this is a fallback in case it doesn't. - // As we don't know anything about the geometry we only pad the right and bottom edge - // of our texture. - Vector uv = arr[RS::ARRAY_TEX_UV]; - Vector uv2 = arr[RS::ARRAY_TEX_UV2]; - - if (uv.size() > 0 && uv2.size() == 0) { - Vector2 uv2_scale = get_uv2_scale(); - uv2.resize(uv.size()); - - Vector2 *uv2w = uv2.ptrw(); - for (int i = 0; i < uv.size(); i++) { - uv2w[i] = uv[i] * uv2_scale; - } - } - - arr[RS::ARRAY_TEX_UV2] = uv2; - } - - array_len = pc; - index_array_len = indices.size(); - // in with the new - RenderingServer::get_singleton()->mesh_clear(mesh); - RenderingServer::get_singleton()->mesh_add_surface_from_arrays(mesh, (RenderingServer::PrimitiveType)primitive_type, arr); - RenderingServer::get_singleton()->mesh_surface_set_material(mesh, 0, material.is_null() ? RID() : material->get_rid()); - - pending_request = false; - - clear_cache(); - - const_cast(this)->emit_changed(); -} - -void PrimitiveMesh::_request_update() { - if (pending_request) { - return; - } - _update(); -} - -int PrimitiveMesh::get_surface_count() const { - if (pending_request) { - _update(); - } - return 1; -} - -int PrimitiveMesh::surface_get_array_len(int p_idx) const { - ERR_FAIL_INDEX_V(p_idx, 1, -1); - if (pending_request) { - _update(); - } - - return array_len; -} - -int PrimitiveMesh::surface_get_array_index_len(int p_idx) const { - ERR_FAIL_INDEX_V(p_idx, 1, -1); - if (pending_request) { - _update(); - } - - return index_array_len; -} - -Array PrimitiveMesh::surface_get_arrays(int p_surface) const { - ERR_FAIL_INDEX_V(p_surface, 1, Array()); - if (pending_request) { - _update(); - } - - return RenderingServer::get_singleton()->mesh_surface_get_arrays(mesh, 0); -} - -Dictionary PrimitiveMesh::surface_get_lods(int p_surface) const { - return Dictionary(); //not really supported -} - -TypedArray PrimitiveMesh::surface_get_blend_shape_arrays(int p_surface) const { - return TypedArray(); //not really supported -} - -BitField PrimitiveMesh::surface_get_format(int p_idx) const { - ERR_FAIL_INDEX_V(p_idx, 1, 0); - - uint64_t mesh_format = RS::ARRAY_FORMAT_VERTEX | RS::ARRAY_FORMAT_NORMAL | RS::ARRAY_FORMAT_TANGENT | RS::ARRAY_FORMAT_TEX_UV | RS::ARRAY_FORMAT_INDEX; - if (add_uv2) { - mesh_format |= RS::ARRAY_FORMAT_TEX_UV2; - } - - return mesh_format; -} - -Mesh::PrimitiveType PrimitiveMesh::surface_get_primitive_type(int p_idx) const { - return primitive_type; -} - -void PrimitiveMesh::surface_set_material(int p_idx, const Ref &p_material) { - ERR_FAIL_INDEX(p_idx, 1); - - set_material(p_material); -} - -Ref PrimitiveMesh::surface_get_material(int p_idx) const { - ERR_FAIL_INDEX_V(p_idx, 1, nullptr); - - return material; -} - -int PrimitiveMesh::get_blend_shape_count() const { - return 0; -} - -StringName PrimitiveMesh::get_blend_shape_name(int p_index) const { - return StringName(); -} - -void PrimitiveMesh::set_blend_shape_name(int p_index, const StringName &p_name) { -} - -AABB PrimitiveMesh::get_aabb() const { - if (pending_request) { - _update(); - } - - return aabb; -} - -RID PrimitiveMesh::get_rid() const { - if (pending_request) { - _update(); - } - return mesh; -} - -void PrimitiveMesh::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_material", "material"), &PrimitiveMesh::set_material); - ClassDB::bind_method(D_METHOD("get_material"), &PrimitiveMesh::get_material); - - ClassDB::bind_method(D_METHOD("get_mesh_arrays"), &PrimitiveMesh::get_mesh_arrays); - - ClassDB::bind_method(D_METHOD("set_custom_aabb", "aabb"), &PrimitiveMesh::set_custom_aabb); - ClassDB::bind_method(D_METHOD("get_custom_aabb"), &PrimitiveMesh::get_custom_aabb); - - ClassDB::bind_method(D_METHOD("set_flip_faces", "flip_faces"), &PrimitiveMesh::set_flip_faces); - ClassDB::bind_method(D_METHOD("get_flip_faces"), &PrimitiveMesh::get_flip_faces); - - ClassDB::bind_method(D_METHOD("set_add_uv2", "add_uv2"), &PrimitiveMesh::set_add_uv2); - ClassDB::bind_method(D_METHOD("get_add_uv2"), &PrimitiveMesh::get_add_uv2); - - ClassDB::bind_method(D_METHOD("set_uv2_padding", "uv2_padding"), &PrimitiveMesh::set_uv2_padding); - ClassDB::bind_method(D_METHOD("get_uv2_padding"), &PrimitiveMesh::get_uv2_padding); - - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "material", PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial"), "set_material", "get_material"); - ADD_PROPERTY(PropertyInfo(Variant::AABB, "custom_aabb", PROPERTY_HINT_NONE, "suffix:m"), "set_custom_aabb", "get_custom_aabb"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "flip_faces"), "set_flip_faces", "get_flip_faces"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "add_uv2"), "set_add_uv2", "get_add_uv2"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "uv2_padding", PROPERTY_HINT_RANGE, "0,10,0.01,or_greater"), "set_uv2_padding", "get_uv2_padding"); - - GDVIRTUAL_BIND(_create_mesh_array); -} - -void PrimitiveMesh::set_material(const Ref &p_material) { - material = p_material; - if (!pending_request) { - // just apply it, else it'll happen when _update is called. - RenderingServer::get_singleton()->mesh_surface_set_material(mesh, 0, material.is_null() ? RID() : material->get_rid()); - notify_property_list_changed(); - emit_changed(); - } -} - -Ref PrimitiveMesh::get_material() const { - return material; -} - -Array PrimitiveMesh::get_mesh_arrays() const { - return surface_get_arrays(0); -} - -void PrimitiveMesh::set_custom_aabb(const AABB &p_custom) { - custom_aabb = p_custom; - RS::get_singleton()->mesh_set_custom_aabb(mesh, custom_aabb); - emit_changed(); -} - -AABB PrimitiveMesh::get_custom_aabb() const { - return custom_aabb; -} - -void PrimitiveMesh::set_flip_faces(bool p_enable) { - flip_faces = p_enable; - _request_update(); -} - -bool PrimitiveMesh::get_flip_faces() const { - return flip_faces; -} - -void PrimitiveMesh::set_add_uv2(bool p_enable) { - add_uv2 = p_enable; - _update_lightmap_size(); - _request_update(); -} - -void PrimitiveMesh::set_uv2_padding(float p_padding) { - uv2_padding = p_padding; - _update_lightmap_size(); - _request_update(); -} - -Vector2 PrimitiveMesh::get_uv2_scale(Vector2 p_margin_scale) const { - Vector2 uv2_scale; - Vector2 lightmap_size = get_lightmap_size_hint(); - - // Calculate it as a margin, if no lightmap size hint is given we assume "PADDING_REF_SIZE" as our texture size. - uv2_scale.x = p_margin_scale.x * uv2_padding / (lightmap_size.x == 0.0 ? PADDING_REF_SIZE : lightmap_size.x); - uv2_scale.y = p_margin_scale.y * uv2_padding / (lightmap_size.y == 0.0 ? PADDING_REF_SIZE : lightmap_size.y); - - // Inverse it to turn our margin into a scale - uv2_scale = Vector2(1.0, 1.0) - uv2_scale; - - return uv2_scale; -} - -float PrimitiveMesh::get_lightmap_texel_size() const { - float texel_size = GLOBAL_GET("rendering/lightmapping/primitive_meshes/texel_size"); - - if (texel_size <= 0.0) { - texel_size = 0.2; - } - - return texel_size; -} - -PrimitiveMesh::PrimitiveMesh() { - mesh = RenderingServer::get_singleton()->mesh_create(); -} - -PrimitiveMesh::~PrimitiveMesh() { - ERR_FAIL_NULL(RenderingServer::get_singleton()); - RenderingServer::get_singleton()->free(mesh); -} - -/** - CapsuleMesh -*/ - -void CapsuleMesh::_update_lightmap_size() { - if (get_add_uv2()) { - // size must have changed, update lightmap size hint - Size2i _lightmap_size_hint; - float texel_size = get_lightmap_texel_size(); - float padding = get_uv2_padding(); - - float radial_length = radius * Math_PI * 0.5; // circumference of 90 degree bend - float vertical_length = radial_length * 2 + (height - 2.0 * radius); // total vertical length - - _lightmap_size_hint.x = MAX(1.0, 4.0 * radial_length / texel_size) + padding; - _lightmap_size_hint.y = MAX(1.0, vertical_length / texel_size) + padding; - - set_lightmap_size_hint(_lightmap_size_hint); - } -} - -void CapsuleMesh::_create_mesh_array(Array &p_arr) const { - bool _add_uv2 = get_add_uv2(); - float texel_size = get_lightmap_texel_size(); - float _uv2_padding = get_uv2_padding() * texel_size; - - create_mesh_array(p_arr, radius, height, radial_segments, rings, _add_uv2, _uv2_padding); -} - -void CapsuleMesh::create_mesh_array(Array &p_arr, const float radius, const float height, const int radial_segments, const int rings, bool p_add_uv2, const float p_uv2_padding) { - int i, j, prevrow, thisrow, point; - float x, y, z, u, v, w; - float onethird = 1.0 / 3.0; - float twothirds = 2.0 / 3.0; - - // Only used if we calculate UV2 - float radial_width = 2.0 * radius * Math_PI; - float radial_h = radial_width / (radial_width + p_uv2_padding); - float radial_length = radius * Math_PI * 0.5; // circumference of 90 degree bend - float vertical_length = radial_length * 2 + (height - 2.0 * radius) + p_uv2_padding; // total vertical length - float radial_v = radial_length / vertical_length; // v size of top and bottom section - float height_v = (height - 2.0 * radius) / vertical_length; // v size of height section - - // note, this has been aligned with our collision shape but I've left the descriptions as top/middle/bottom - - Vector points; - Vector normals; - Vector tangents; - Vector uvs; - Vector uv2s; - Vector indices; - point = 0; - -#define ADD_TANGENT(m_x, m_y, m_z, m_d) \ - tangents.push_back(m_x); \ - tangents.push_back(m_y); \ - tangents.push_back(m_z); \ - tangents.push_back(m_d); - - /* top hemisphere */ - thisrow = 0; - prevrow = 0; - for (j = 0; j <= (rings + 1); j++) { - v = j; - - v /= (rings + 1); - w = sin(0.5 * Math_PI * v); - y = radius * cos(0.5 * Math_PI * v); - - for (i = 0; i <= radial_segments; i++) { - u = i; - u /= radial_segments; - - x = -sin(u * Math_TAU); - z = cos(u * Math_TAU); - - Vector3 p = Vector3(x * radius * w, y, -z * radius * w); - points.push_back(p + Vector3(0.0, 0.5 * height - radius, 0.0)); - normals.push_back(p.normalized()); - ADD_TANGENT(-z, 0.0, -x, 1.0) - uvs.push_back(Vector2(u, v * onethird)); - if (p_add_uv2) { - uv2s.push_back(Vector2(u * radial_h, v * radial_v)); - } - point++; - - if (i > 0 && j > 0) { - indices.push_back(prevrow + i - 1); - indices.push_back(prevrow + i); - indices.push_back(thisrow + i - 1); - - indices.push_back(prevrow + i); - indices.push_back(thisrow + i); - indices.push_back(thisrow + i - 1); - } - } - - prevrow = thisrow; - thisrow = point; - } - - /* cylinder */ - thisrow = point; - prevrow = 0; - for (j = 0; j <= (rings + 1); j++) { - v = j; - v /= (rings + 1); - - y = (height - 2.0 * radius) * v; - y = (0.5 * height - radius) - y; - - for (i = 0; i <= radial_segments; i++) { - u = i; - u /= radial_segments; - - x = -sin(u * Math_TAU); - z = cos(u * Math_TAU); - - Vector3 p = Vector3(x * radius, y, -z * radius); - points.push_back(p); - normals.push_back(Vector3(x, 0.0, -z)); - ADD_TANGENT(-z, 0.0, -x, 1.0) - uvs.push_back(Vector2(u, onethird + (v * onethird))); - if (p_add_uv2) { - uv2s.push_back(Vector2(u * radial_h, radial_v + (v * height_v))); - } - point++; - - if (i > 0 && j > 0) { - indices.push_back(prevrow + i - 1); - indices.push_back(prevrow + i); - indices.push_back(thisrow + i - 1); - - indices.push_back(prevrow + i); - indices.push_back(thisrow + i); - indices.push_back(thisrow + i - 1); - } - } - - prevrow = thisrow; - thisrow = point; - } - - /* bottom hemisphere */ - thisrow = point; - prevrow = 0; - for (j = 0; j <= (rings + 1); j++) { - v = j; - - v /= (rings + 1); - v += 1.0; - w = sin(0.5 * Math_PI * v); - y = radius * cos(0.5 * Math_PI * v); - - for (i = 0; i <= radial_segments; i++) { - u = i; - u /= radial_segments; - - x = -sin(u * Math_TAU); - z = cos(u * Math_TAU); - - Vector3 p = Vector3(x * radius * w, y, -z * radius * w); - points.push_back(p + Vector3(0.0, -0.5 * height + radius, 0.0)); - normals.push_back(p.normalized()); - ADD_TANGENT(-z, 0.0, -x, 1.0) - uvs.push_back(Vector2(u, twothirds + ((v - 1.0) * onethird))); - if (p_add_uv2) { - uv2s.push_back(Vector2(u * radial_h, radial_v + height_v + ((v - 1.0) * radial_v))); - } - point++; - - if (i > 0 && j > 0) { - indices.push_back(prevrow + i - 1); - indices.push_back(prevrow + i); - indices.push_back(thisrow + i - 1); - - indices.push_back(prevrow + i); - indices.push_back(thisrow + i); - indices.push_back(thisrow + i - 1); - } - } - - prevrow = thisrow; - thisrow = point; - } - - p_arr[RS::ARRAY_VERTEX] = points; - p_arr[RS::ARRAY_NORMAL] = normals; - p_arr[RS::ARRAY_TANGENT] = tangents; - p_arr[RS::ARRAY_TEX_UV] = uvs; - if (p_add_uv2) { - p_arr[RS::ARRAY_TEX_UV2] = uv2s; - } - p_arr[RS::ARRAY_INDEX] = indices; -} - -void CapsuleMesh::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_radius", "radius"), &CapsuleMesh::set_radius); - ClassDB::bind_method(D_METHOD("get_radius"), &CapsuleMesh::get_radius); - ClassDB::bind_method(D_METHOD("set_height", "height"), &CapsuleMesh::set_height); - ClassDB::bind_method(D_METHOD("get_height"), &CapsuleMesh::get_height); - - ClassDB::bind_method(D_METHOD("set_radial_segments", "segments"), &CapsuleMesh::set_radial_segments); - ClassDB::bind_method(D_METHOD("get_radial_segments"), &CapsuleMesh::get_radial_segments); - ClassDB::bind_method(D_METHOD("set_rings", "rings"), &CapsuleMesh::set_rings); - ClassDB::bind_method(D_METHOD("get_rings"), &CapsuleMesh::get_rings); - - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater,suffix:m"), "set_radius", "get_radius"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater,suffix:m"), "set_height", "get_height"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_radial_segments", "get_radial_segments"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_rings", "get_rings"); - - ADD_LINKED_PROPERTY("radius", "height"); - ADD_LINKED_PROPERTY("height", "radius"); -} - -void CapsuleMesh::set_radius(const float p_radius) { - radius = p_radius; - if (radius > height * 0.5) { - height = radius * 2.0; - } - _update_lightmap_size(); - _request_update(); -} - -float CapsuleMesh::get_radius() const { - return radius; -} - -void CapsuleMesh::set_height(const float p_height) { - height = p_height; - if (radius > height * 0.5) { - radius = height * 0.5; - } - _update_lightmap_size(); - _request_update(); -} - -float CapsuleMesh::get_height() const { - return height; -} - -void CapsuleMesh::set_radial_segments(const int p_segments) { - radial_segments = p_segments > 4 ? p_segments : 4; - _request_update(); -} - -int CapsuleMesh::get_radial_segments() const { - return radial_segments; -} - -void CapsuleMesh::set_rings(const int p_rings) { - ERR_FAIL_COND(p_rings < 0); - rings = p_rings; - _request_update(); -} - -int CapsuleMesh::get_rings() const { - return rings; -} - -CapsuleMesh::CapsuleMesh() {} - -/** - BoxMesh -*/ - -void BoxMesh::_update_lightmap_size() { - if (get_add_uv2()) { - // size must have changed, update lightmap size hint - Size2i _lightmap_size_hint; - float texel_size = get_lightmap_texel_size(); - float padding = get_uv2_padding(); - - float width = (size.x + size.z) / texel_size; - float length = (size.y + size.y + MAX(size.x, size.z)) / texel_size; - - _lightmap_size_hint.x = MAX(1.0, width) + 2.0 * padding; - _lightmap_size_hint.y = MAX(1.0, length) + 3.0 * padding; - - set_lightmap_size_hint(_lightmap_size_hint); - } -} - -void BoxMesh::_create_mesh_array(Array &p_arr) const { - // Note about padding, with our box each face of the box faces a different direction so we want a seam - // around every face. We thus add our padding to the right and bottom of each face. - // With 3 faces along the width and 2 along the height of the texture we need to adjust our scale - // accordingly. - bool _add_uv2 = get_add_uv2(); - float texel_size = get_lightmap_texel_size(); - float _uv2_padding = get_uv2_padding() * texel_size; - - BoxMesh::create_mesh_array(p_arr, size, subdivide_w, subdivide_h, subdivide_d, _add_uv2, _uv2_padding); -} - -void BoxMesh::create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w, int subdivide_h, int subdivide_d, bool p_add_uv2, const float p_uv2_padding) { - int i, j, prevrow, thisrow, point; - float x, y, z; - float onethird = 1.0 / 3.0; - float twothirds = 2.0 / 3.0; - - // Only used if we calculate UV2 - // TODO this could be improved by changing the order depending on which side is the longest (basically the below works best if size.y is the longest) - float total_h = (size.x + size.z + (2.0 * p_uv2_padding)); - float padding_h = p_uv2_padding / total_h; - float width_h = size.x / total_h; - float depth_h = size.z / total_h; - float total_v = (size.y + size.y + MAX(size.x, size.z) + (3.0 * p_uv2_padding)); - float padding_v = p_uv2_padding / total_v; - float width_v = size.x / total_v; - float height_v = size.y / total_v; - float depth_v = size.z / total_v; - - Vector3 start_pos = size * -0.5; - - // set our bounding box - - Vector points; - Vector normals; - Vector tangents; - Vector uvs; - Vector uv2s; - Vector indices; - point = 0; - -#define ADD_TANGENT(m_x, m_y, m_z, m_d) \ - tangents.push_back(m_x); \ - tangents.push_back(m_y); \ - tangents.push_back(m_z); \ - tangents.push_back(m_d); - - // front + back - y = start_pos.y; - thisrow = point; - prevrow = 0; - for (j = 0; j <= subdivide_h + 1; j++) { - float v = j; - float v2 = v / (subdivide_w + 1.0); - v /= (2.0 * (subdivide_h + 1.0)); - - x = start_pos.x; - for (i = 0; i <= subdivide_w + 1; i++) { - float u = i; - float u2 = u / (subdivide_w + 1.0); - u /= (3.0 * (subdivide_w + 1.0)); - - // front - points.push_back(Vector3(x, -y, -start_pos.z)); // double negative on the Z! - normals.push_back(Vector3(0.0, 0.0, 1.0)); - ADD_TANGENT(1.0, 0.0, 0.0, 1.0); - uvs.push_back(Vector2(u, v)); - if (p_add_uv2) { - uv2s.push_back(Vector2(u2 * width_h, v2 * height_v)); - } - point++; - - // back - points.push_back(Vector3(-x, -y, start_pos.z)); - normals.push_back(Vector3(0.0, 0.0, -1.0)); - ADD_TANGENT(-1.0, 0.0, 0.0, 1.0); - uvs.push_back(Vector2(twothirds + u, v)); - if (p_add_uv2) { - uv2s.push_back(Vector2(u2 * width_h, height_v + padding_v + (v2 * height_v))); - } - point++; - - if (i > 0 && j > 0) { - int i2 = i * 2; - - // front - indices.push_back(prevrow + i2 - 2); - indices.push_back(prevrow + i2); - indices.push_back(thisrow + i2 - 2); - indices.push_back(prevrow + i2); - indices.push_back(thisrow + i2); - indices.push_back(thisrow + i2 - 2); - - // back - indices.push_back(prevrow + i2 - 1); - indices.push_back(prevrow + i2 + 1); - indices.push_back(thisrow + i2 - 1); - indices.push_back(prevrow + i2 + 1); - indices.push_back(thisrow + i2 + 1); - indices.push_back(thisrow + i2 - 1); - } - - x += size.x / (subdivide_w + 1.0); - } - - y += size.y / (subdivide_h + 1.0); - prevrow = thisrow; - thisrow = point; - } - - // left + right - y = start_pos.y; - thisrow = point; - prevrow = 0; - for (j = 0; j <= (subdivide_h + 1); j++) { - float v = j; - float v2 = v / (subdivide_h + 1.0); - v /= (2.0 * (subdivide_h + 1.0)); - - z = start_pos.z; - for (i = 0; i <= (subdivide_d + 1); i++) { - float u = i; - float u2 = u / (subdivide_d + 1.0); - u /= (3.0 * (subdivide_d + 1.0)); - - // right - points.push_back(Vector3(-start_pos.x, -y, -z)); - normals.push_back(Vector3(1.0, 0.0, 0.0)); - ADD_TANGENT(0.0, 0.0, -1.0, 1.0); - uvs.push_back(Vector2(onethird + u, v)); - if (p_add_uv2) { - uv2s.push_back(Vector2(width_h + padding_h + (u2 * depth_h), v2 * height_v)); - } - point++; - - // left - points.push_back(Vector3(start_pos.x, -y, z)); - normals.push_back(Vector3(-1.0, 0.0, 0.0)); - ADD_TANGENT(0.0, 0.0, 1.0, 1.0); - uvs.push_back(Vector2(u, 0.5 + v)); - if (p_add_uv2) { - uv2s.push_back(Vector2(width_h + padding_h + (u2 * depth_h), height_v + padding_v + (v2 * height_v))); - } - point++; - - if (i > 0 && j > 0) { - int i2 = i * 2; - - // right - indices.push_back(prevrow + i2 - 2); - indices.push_back(prevrow + i2); - indices.push_back(thisrow + i2 - 2); - indices.push_back(prevrow + i2); - indices.push_back(thisrow + i2); - indices.push_back(thisrow + i2 - 2); - - // left - indices.push_back(prevrow + i2 - 1); - indices.push_back(prevrow + i2 + 1); - indices.push_back(thisrow + i2 - 1); - indices.push_back(prevrow + i2 + 1); - indices.push_back(thisrow + i2 + 1); - indices.push_back(thisrow + i2 - 1); - } - - z += size.z / (subdivide_d + 1.0); - } - - y += size.y / (subdivide_h + 1.0); - prevrow = thisrow; - thisrow = point; - } - - // top + bottom - z = start_pos.z; - thisrow = point; - prevrow = 0; - for (j = 0; j <= (subdivide_d + 1); j++) { - float v = j; - float v2 = v / (subdivide_d + 1.0); - v /= (2.0 * (subdivide_d + 1.0)); - - x = start_pos.x; - for (i = 0; i <= (subdivide_w + 1); i++) { - float u = i; - float u2 = u / (subdivide_w + 1.0); - u /= (3.0 * (subdivide_w + 1.0)); - - // top - points.push_back(Vector3(-x, -start_pos.y, -z)); - normals.push_back(Vector3(0.0, 1.0, 0.0)); - ADD_TANGENT(-1.0, 0.0, 0.0, 1.0); - uvs.push_back(Vector2(onethird + u, 0.5 + v)); - if (p_add_uv2) { - uv2s.push_back(Vector2(u2 * width_h, ((height_v + padding_v) * 2.0) + (v2 * depth_v))); - } - point++; - - // bottom - points.push_back(Vector3(x, start_pos.y, -z)); - normals.push_back(Vector3(0.0, -1.0, 0.0)); - ADD_TANGENT(1.0, 0.0, 0.0, 1.0); - uvs.push_back(Vector2(twothirds + u, 0.5 + v)); - if (p_add_uv2) { - uv2s.push_back(Vector2(width_h + padding_h + (u2 * depth_h), ((height_v + padding_v) * 2.0) + (v2 * width_v))); - } - point++; - - if (i > 0 && j > 0) { - int i2 = i * 2; - - // top - indices.push_back(prevrow + i2 - 2); - indices.push_back(prevrow + i2); - indices.push_back(thisrow + i2 - 2); - indices.push_back(prevrow + i2); - indices.push_back(thisrow + i2); - indices.push_back(thisrow + i2 - 2); - - // bottom - indices.push_back(prevrow + i2 - 1); - indices.push_back(prevrow + i2 + 1); - indices.push_back(thisrow + i2 - 1); - indices.push_back(prevrow + i2 + 1); - indices.push_back(thisrow + i2 + 1); - indices.push_back(thisrow + i2 - 1); - } - - x += size.x / (subdivide_w + 1.0); - } - - z += size.z / (subdivide_d + 1.0); - prevrow = thisrow; - thisrow = point; - } - - p_arr[RS::ARRAY_VERTEX] = points; - p_arr[RS::ARRAY_NORMAL] = normals; - p_arr[RS::ARRAY_TANGENT] = tangents; - p_arr[RS::ARRAY_TEX_UV] = uvs; - if (p_add_uv2) { - p_arr[RS::ARRAY_TEX_UV2] = uv2s; - } - p_arr[RS::ARRAY_INDEX] = indices; -} - -void BoxMesh::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_size", "size"), &BoxMesh::set_size); - ClassDB::bind_method(D_METHOD("get_size"), &BoxMesh::get_size); - - ClassDB::bind_method(D_METHOD("set_subdivide_width", "subdivide"), &BoxMesh::set_subdivide_width); - ClassDB::bind_method(D_METHOD("get_subdivide_width"), &BoxMesh::get_subdivide_width); - ClassDB::bind_method(D_METHOD("set_subdivide_height", "divisions"), &BoxMesh::set_subdivide_height); - ClassDB::bind_method(D_METHOD("get_subdivide_height"), &BoxMesh::get_subdivide_height); - ClassDB::bind_method(D_METHOD("set_subdivide_depth", "divisions"), &BoxMesh::set_subdivide_depth); - ClassDB::bind_method(D_METHOD("get_subdivide_depth"), &BoxMesh::get_subdivide_depth); - - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size", PROPERTY_HINT_NONE, "suffix:m"), "set_size", "get_size"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_width", "get_subdivide_width"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_height", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_height", "get_subdivide_height"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_depth", "get_subdivide_depth"); -} - -void BoxMesh::set_size(const Vector3 &p_size) { - size = p_size; - _update_lightmap_size(); - _request_update(); -} - -Vector3 BoxMesh::get_size() const { - return size; -} - -void BoxMesh::set_subdivide_width(const int p_divisions) { - subdivide_w = p_divisions > 0 ? p_divisions : 0; - _request_update(); -} - -int BoxMesh::get_subdivide_width() const { - return subdivide_w; -} - -void BoxMesh::set_subdivide_height(const int p_divisions) { - subdivide_h = p_divisions > 0 ? p_divisions : 0; - _request_update(); -} - -int BoxMesh::get_subdivide_height() const { - return subdivide_h; -} - -void BoxMesh::set_subdivide_depth(const int p_divisions) { - subdivide_d = p_divisions > 0 ? p_divisions : 0; - _request_update(); -} - -int BoxMesh::get_subdivide_depth() const { - return subdivide_d; -} - -BoxMesh::BoxMesh() {} - -/** - CylinderMesh -*/ - -void CylinderMesh::_update_lightmap_size() { - if (get_add_uv2()) { - // size must have changed, update lightmap size hint - Size2i _lightmap_size_hint; - float texel_size = get_lightmap_texel_size(); - float padding = get_uv2_padding(); - - float top_circumference = top_radius * Math_PI * 2.0; - float bottom_circumference = bottom_radius * Math_PI * 2.0; - - float _width = MAX(top_circumference, bottom_circumference) / texel_size + padding; - _width = MAX(_width, (((top_radius + bottom_radius) / texel_size) + padding) * 2.0); // this is extremely unlikely to be larger, will only happen if padding is larger then our diameter. - _lightmap_size_hint.x = MAX(1.0, _width); - - float _height = ((height + (MAX(top_radius, bottom_radius) * 2.0)) / texel_size) + (2.0 * padding); - - _lightmap_size_hint.y = MAX(1.0, _height); - - set_lightmap_size_hint(_lightmap_size_hint); - } -} - -void CylinderMesh::_create_mesh_array(Array &p_arr) const { - bool _add_uv2 = get_add_uv2(); - float texel_size = get_lightmap_texel_size(); - float _uv2_padding = get_uv2_padding() * texel_size; - - create_mesh_array(p_arr, top_radius, bottom_radius, height, radial_segments, rings, cap_top, cap_bottom, _add_uv2, _uv2_padding); -} - -void CylinderMesh::create_mesh_array(Array &p_arr, float top_radius, float bottom_radius, float height, int radial_segments, int rings, bool cap_top, bool cap_bottom, bool p_add_uv2, const float p_uv2_padding) { - int i, j, prevrow, thisrow, point; - float x, y, z, u, v, radius, radius_h; - - // Only used if we calculate UV2 - float top_circumference = top_radius * Math_PI * 2.0; - float bottom_circumference = bottom_radius * Math_PI * 2.0; - float vertical_length = height + MAX(2.0 * top_radius, 2.0 * bottom_radius) + (2.0 * p_uv2_padding); - float height_v = height / vertical_length; - float padding_v = p_uv2_padding / vertical_length; - - float horizonal_length = MAX(MAX(2.0 * (top_radius + bottom_radius + p_uv2_padding), top_circumference + p_uv2_padding), bottom_circumference + p_uv2_padding); - float center_h = 0.5 * (horizonal_length - p_uv2_padding) / horizonal_length; - float top_h = top_circumference / horizonal_length; - float bottom_h = bottom_circumference / horizonal_length; - float padding_h = p_uv2_padding / horizonal_length; - - Vector points; - Vector normals; - Vector tangents; - Vector uvs; - Vector uv2s; - Vector indices; - point = 0; - -#define ADD_TANGENT(m_x, m_y, m_z, m_d) \ - tangents.push_back(m_x); \ - tangents.push_back(m_y); \ - tangents.push_back(m_z); \ - tangents.push_back(m_d); - - thisrow = 0; - prevrow = 0; - const real_t side_normal_y = (bottom_radius - top_radius) / height; - for (j = 0; j <= (rings + 1); j++) { - v = j; - v /= (rings + 1); - - radius = top_radius + ((bottom_radius - top_radius) * v); - radius_h = top_h + ((bottom_h - top_h) * v); - - y = height * v; - y = (height * 0.5) - y; - - for (i = 0; i <= radial_segments; i++) { - u = i; - u /= radial_segments; - - x = sin(u * Math_TAU); - z = cos(u * Math_TAU); - - Vector3 p = Vector3(x * radius, y, z * radius); - points.push_back(p); - normals.push_back(Vector3(x, side_normal_y, z).normalized()); - ADD_TANGENT(z, 0.0, -x, 1.0) - uvs.push_back(Vector2(u, v * 0.5)); - if (p_add_uv2) { - uv2s.push_back(Vector2(center_h + (u - 0.5) * radius_h, v * height_v)); - } - point++; - - if (i > 0 && j > 0) { - indices.push_back(prevrow + i - 1); - indices.push_back(prevrow + i); - indices.push_back(thisrow + i - 1); - - indices.push_back(prevrow + i); - indices.push_back(thisrow + i); - indices.push_back(thisrow + i - 1); - } - } - - prevrow = thisrow; - thisrow = point; - } - - // Adjust for bottom section, only used if we calculate UV2s. - top_h = top_radius / horizonal_length; - float top_v = top_radius / vertical_length; - bottom_h = bottom_radius / horizonal_length; - float bottom_v = bottom_radius / vertical_length; - - // Add top. - if (cap_top && top_radius > 0.0) { - y = height * 0.5; - - thisrow = point; - points.push_back(Vector3(0.0, y, 0.0)); - normals.push_back(Vector3(0.0, 1.0, 0.0)); - ADD_TANGENT(1.0, 0.0, 0.0, 1.0) - uvs.push_back(Vector2(0.25, 0.75)); - if (p_add_uv2) { - uv2s.push_back(Vector2(top_h, height_v + padding_v + MAX(top_v, bottom_v))); - } - point++; - - for (i = 0; i <= radial_segments; i++) { - float r = i; - r /= radial_segments; - - x = sin(r * Math_TAU); - z = cos(r * Math_TAU); - - u = ((x + 1.0) * 0.25); - v = 0.5 + ((z + 1.0) * 0.25); - - Vector3 p = Vector3(x * top_radius, y, z * top_radius); - points.push_back(p); - normals.push_back(Vector3(0.0, 1.0, 0.0)); - ADD_TANGENT(1.0, 0.0, 0.0, 1.0) - uvs.push_back(Vector2(u, v)); - if (p_add_uv2) { - uv2s.push_back(Vector2(top_h + (x * top_h), height_v + padding_v + MAX(top_v, bottom_v) + (z * top_v))); - } - point++; - - if (i > 0) { - indices.push_back(thisrow); - indices.push_back(point - 1); - indices.push_back(point - 2); - } - } - } - - // Add bottom. - if (cap_bottom && bottom_radius > 0.0) { - y = height * -0.5; - - thisrow = point; - points.push_back(Vector3(0.0, y, 0.0)); - normals.push_back(Vector3(0.0, -1.0, 0.0)); - ADD_TANGENT(1.0, 0.0, 0.0, 1.0) - uvs.push_back(Vector2(0.75, 0.75)); - if (p_add_uv2) { - uv2s.push_back(Vector2(top_h + top_h + padding_h + bottom_h, height_v + padding_v + MAX(top_v, bottom_v))); - } - point++; - - for (i = 0; i <= radial_segments; i++) { - float r = i; - r /= radial_segments; - - x = sin(r * Math_TAU); - z = cos(r * Math_TAU); - - u = 0.5 + ((x + 1.0) * 0.25); - v = 1.0 - ((z + 1.0) * 0.25); - - Vector3 p = Vector3(x * bottom_radius, y, z * bottom_radius); - points.push_back(p); - normals.push_back(Vector3(0.0, -1.0, 0.0)); - ADD_TANGENT(1.0, 0.0, 0.0, 1.0) - uvs.push_back(Vector2(u, v)); - if (p_add_uv2) { - uv2s.push_back(Vector2(top_h + top_h + padding_h + bottom_h + (x * bottom_h), height_v + padding_v + MAX(top_v, bottom_v) - (z * bottom_v))); - } - point++; - - if (i > 0) { - indices.push_back(thisrow); - indices.push_back(point - 2); - indices.push_back(point - 1); - } - } - } - - p_arr[RS::ARRAY_VERTEX] = points; - p_arr[RS::ARRAY_NORMAL] = normals; - p_arr[RS::ARRAY_TANGENT] = tangents; - p_arr[RS::ARRAY_TEX_UV] = uvs; - if (p_add_uv2) { - p_arr[RS::ARRAY_TEX_UV2] = uv2s; - } - p_arr[RS::ARRAY_INDEX] = indices; -} - -void CylinderMesh::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_top_radius", "radius"), &CylinderMesh::set_top_radius); - ClassDB::bind_method(D_METHOD("get_top_radius"), &CylinderMesh::get_top_radius); - ClassDB::bind_method(D_METHOD("set_bottom_radius", "radius"), &CylinderMesh::set_bottom_radius); - ClassDB::bind_method(D_METHOD("get_bottom_radius"), &CylinderMesh::get_bottom_radius); - ClassDB::bind_method(D_METHOD("set_height", "height"), &CylinderMesh::set_height); - ClassDB::bind_method(D_METHOD("get_height"), &CylinderMesh::get_height); - - ClassDB::bind_method(D_METHOD("set_radial_segments", "segments"), &CylinderMesh::set_radial_segments); - ClassDB::bind_method(D_METHOD("get_radial_segments"), &CylinderMesh::get_radial_segments); - ClassDB::bind_method(D_METHOD("set_rings", "rings"), &CylinderMesh::set_rings); - ClassDB::bind_method(D_METHOD("get_rings"), &CylinderMesh::get_rings); - - ClassDB::bind_method(D_METHOD("set_cap_top", "cap_top"), &CylinderMesh::set_cap_top); - ClassDB::bind_method(D_METHOD("is_cap_top"), &CylinderMesh::is_cap_top); - - ClassDB::bind_method(D_METHOD("set_cap_bottom", "cap_bottom"), &CylinderMesh::set_cap_bottom); - ClassDB::bind_method(D_METHOD("is_cap_bottom"), &CylinderMesh::is_cap_bottom); - - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "top_radius", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater,suffix:m"), "set_top_radius", "get_top_radius"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bottom_radius", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater,suffix:m"), "set_bottom_radius", "get_bottom_radius"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_height", "get_height"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_radial_segments", "get_radial_segments"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_rings", "get_rings"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cap_top"), "set_cap_top", "is_cap_top"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cap_bottom"), "set_cap_bottom", "is_cap_bottom"); -} - -void CylinderMesh::set_top_radius(const float p_radius) { - top_radius = p_radius; - _update_lightmap_size(); - _request_update(); -} - -float CylinderMesh::get_top_radius() const { - return top_radius; -} - -void CylinderMesh::set_bottom_radius(const float p_radius) { - bottom_radius = p_radius; - _update_lightmap_size(); - _request_update(); -} - -float CylinderMesh::get_bottom_radius() const { - return bottom_radius; -} - -void CylinderMesh::set_height(const float p_height) { - height = p_height; - _update_lightmap_size(); - _request_update(); -} - -float CylinderMesh::get_height() const { - return height; -} - -void CylinderMesh::set_radial_segments(const int p_segments) { - radial_segments = p_segments > 4 ? p_segments : 4; - _request_update(); -} - -int CylinderMesh::get_radial_segments() const { - return radial_segments; -} - -void CylinderMesh::set_rings(const int p_rings) { - ERR_FAIL_COND(p_rings < 0); - rings = p_rings; - _request_update(); -} - -int CylinderMesh::get_rings() const { - return rings; -} - -void CylinderMesh::set_cap_top(bool p_cap_top) { - cap_top = p_cap_top; - _request_update(); -} - -bool CylinderMesh::is_cap_top() const { - return cap_top; -} - -void CylinderMesh::set_cap_bottom(bool p_cap_bottom) { - cap_bottom = p_cap_bottom; - _request_update(); -} - -bool CylinderMesh::is_cap_bottom() const { - return cap_bottom; -} - -CylinderMesh::CylinderMesh() {} - -/** - PlaneMesh -*/ - -void PlaneMesh::_update_lightmap_size() { - if (get_add_uv2()) { - // size must have changed, update lightmap size hint - Size2i _lightmap_size_hint; - float texel_size = get_lightmap_texel_size(); - float padding = get_uv2_padding(); - - _lightmap_size_hint.x = MAX(1.0, (size.x / texel_size) + padding); - _lightmap_size_hint.y = MAX(1.0, (size.y / texel_size) + padding); - - set_lightmap_size_hint(_lightmap_size_hint); - } -} - -void PlaneMesh::_create_mesh_array(Array &p_arr) const { - int i, j, prevrow, thisrow, point; - float x, z; - - // Plane mesh can use default UV2 calculation as implemented in Primitive Mesh - - Size2 start_pos = size * -0.5; - - Vector3 normal = Vector3(0.0, 1.0, 0.0); - if (orientation == FACE_X) { - normal = Vector3(1.0, 0.0, 0.0); - } else if (orientation == FACE_Z) { - normal = Vector3(0.0, 0.0, 1.0); - } - - Vector points; - Vector normals; - Vector tangents; - Vector uvs; - Vector indices; - point = 0; - -#define ADD_TANGENT(m_x, m_y, m_z, m_d) \ - tangents.push_back(m_x); \ - tangents.push_back(m_y); \ - tangents.push_back(m_z); \ - tangents.push_back(m_d); - - /* top + bottom */ - z = start_pos.y; - thisrow = point; - prevrow = 0; - for (j = 0; j <= (subdivide_d + 1); j++) { - x = start_pos.x; - for (i = 0; i <= (subdivide_w + 1); i++) { - float u = i; - float v = j; - u /= (subdivide_w + 1.0); - v /= (subdivide_d + 1.0); - - if (orientation == FACE_X) { - points.push_back(Vector3(0.0, z, x) + center_offset); - } else if (orientation == FACE_Y) { - points.push_back(Vector3(-x, 0.0, -z) + center_offset); - } else if (orientation == FACE_Z) { - points.push_back(Vector3(-x, z, 0.0) + center_offset); - } - normals.push_back(normal); - if (orientation == FACE_X) { - ADD_TANGENT(0.0, 0.0, -1.0, 1.0); - } else { - ADD_TANGENT(1.0, 0.0, 0.0, 1.0); - } - uvs.push_back(Vector2(1.0 - u, 1.0 - v)); /* 1.0 - uv to match orientation with Quad */ - point++; - - if (i > 0 && j > 0) { - indices.push_back(prevrow + i - 1); - indices.push_back(prevrow + i); - indices.push_back(thisrow + i - 1); - indices.push_back(prevrow + i); - indices.push_back(thisrow + i); - indices.push_back(thisrow + i - 1); - } - - x += size.x / (subdivide_w + 1.0); - } - - z += size.y / (subdivide_d + 1.0); - prevrow = thisrow; - thisrow = point; - } - - p_arr[RS::ARRAY_VERTEX] = points; - p_arr[RS::ARRAY_NORMAL] = normals; - p_arr[RS::ARRAY_TANGENT] = tangents; - p_arr[RS::ARRAY_TEX_UV] = uvs; - p_arr[RS::ARRAY_INDEX] = indices; -} - -void PlaneMesh::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_size", "size"), &PlaneMesh::set_size); - ClassDB::bind_method(D_METHOD("get_size"), &PlaneMesh::get_size); - - ClassDB::bind_method(D_METHOD("set_subdivide_width", "subdivide"), &PlaneMesh::set_subdivide_width); - ClassDB::bind_method(D_METHOD("get_subdivide_width"), &PlaneMesh::get_subdivide_width); - ClassDB::bind_method(D_METHOD("set_subdivide_depth", "subdivide"), &PlaneMesh::set_subdivide_depth); - ClassDB::bind_method(D_METHOD("get_subdivide_depth"), &PlaneMesh::get_subdivide_depth); - - ClassDB::bind_method(D_METHOD("set_center_offset", "offset"), &PlaneMesh::set_center_offset); - ClassDB::bind_method(D_METHOD("get_center_offset"), &PlaneMesh::get_center_offset); - - ClassDB::bind_method(D_METHOD("set_orientation", "orientation"), &PlaneMesh::set_orientation); - ClassDB::bind_method(D_METHOD("get_orientation"), &PlaneMesh::get_orientation); - - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size", PROPERTY_HINT_NONE, "suffix:m"), "set_size", "get_size"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_width", "get_subdivide_width"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_depth", "get_subdivide_depth"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "center_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_center_offset", "get_center_offset"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "orientation", PROPERTY_HINT_ENUM, "Face X,Face Y,Face Z"), "set_orientation", "get_orientation"); - - BIND_ENUM_CONSTANT(FACE_X) - BIND_ENUM_CONSTANT(FACE_Y) - BIND_ENUM_CONSTANT(FACE_Z) -} - -void PlaneMesh::set_size(const Size2 &p_size) { - size = p_size; - _update_lightmap_size(); - _request_update(); -} - -Size2 PlaneMesh::get_size() const { - return size; -} - -void PlaneMesh::set_subdivide_width(const int p_divisions) { - subdivide_w = p_divisions > 0 ? p_divisions : 0; - _request_update(); -} - -int PlaneMesh::get_subdivide_width() const { - return subdivide_w; -} - -void PlaneMesh::set_subdivide_depth(const int p_divisions) { - subdivide_d = p_divisions > 0 ? p_divisions : 0; - _request_update(); -} - -int PlaneMesh::get_subdivide_depth() const { - return subdivide_d; -} - -void PlaneMesh::set_center_offset(const Vector3 p_offset) { - center_offset = p_offset; - _request_update(); -} - -Vector3 PlaneMesh::get_center_offset() const { - return center_offset; -} - -void PlaneMesh::set_orientation(const Orientation p_orientation) { - orientation = p_orientation; - _request_update(); -} - -PlaneMesh::Orientation PlaneMesh::get_orientation() const { - return orientation; -} - -PlaneMesh::PlaneMesh() {} - -/** - PrismMesh -*/ - -void PrismMesh::_update_lightmap_size() { - if (get_add_uv2()) { - // size must have changed, update lightmap size hint - Size2i _lightmap_size_hint; - float texel_size = get_lightmap_texel_size(); - float padding = get_uv2_padding(); - - // left_to_right does not effect the surface area of the prism so we ignore that. - // TODO we could combine the two triangles and save some space but we need to re-align the uv1 and adjust the tangent. - - float width = (size.x + size.z) / texel_size; - float length = (size.y + size.y + size.z) / texel_size; - - _lightmap_size_hint.x = MAX(1.0, width) + 2.0 * padding; - _lightmap_size_hint.y = MAX(1.0, length) + 3.0 * padding; - - set_lightmap_size_hint(_lightmap_size_hint); - } -} - -void PrismMesh::_create_mesh_array(Array &p_arr) const { - int i, j, prevrow, thisrow, point; - float x, y, z; - float onethird = 1.0 / 3.0; - float twothirds = 2.0 / 3.0; - - // Only used if we calculate UV2 - bool _add_uv2 = get_add_uv2(); - float texel_size = get_lightmap_texel_size(); - float _uv2_padding = get_uv2_padding() * texel_size; - - float horizontal_total = size.x + size.z + 2.0 * _uv2_padding; - float width_h = size.x / horizontal_total; - float depth_h = size.z / horizontal_total; - float padding_h = _uv2_padding / horizontal_total; - - float vertical_total = (size.y + size.y + size.z) + (3.0 * _uv2_padding); - float height_v = size.y / vertical_total; - float depth_v = size.z / vertical_total; - float padding_v = _uv2_padding / vertical_total; - - // and start building - - Vector3 start_pos = size * -0.5; - - // set our bounding box - - Vector points; - Vector normals; - Vector tangents; - Vector uvs; - Vector uv2s; - Vector indices; - point = 0; - -#define ADD_TANGENT(m_x, m_y, m_z, m_d) \ - tangents.push_back(m_x); \ - tangents.push_back(m_y); \ - tangents.push_back(m_z); \ - tangents.push_back(m_d); - - /* front + back */ - y = start_pos.y; - thisrow = point; - prevrow = 0; - for (j = 0; j <= (subdivide_h + 1); j++) { - float scale = j / (subdivide_h + 1.0); - float scaled_size_x = size.x * scale; - float start_x = start_pos.x + (1.0 - scale) * size.x * left_to_right; - float offset_front = (1.0 - scale) * onethird * left_to_right; - float offset_back = (1.0 - scale) * onethird * (1.0 - left_to_right); - - float v = j; - float v2 = scale; - v /= 2.0 * (subdivide_h + 1.0); - - x = 0.0; - for (i = 0; i <= (subdivide_w + 1); i++) { - float u = i; - float u2 = i / (subdivide_w + 1.0); - u /= (3.0 * (subdivide_w + 1.0)); - - u *= scale; - - /* front */ - points.push_back(Vector3(start_x + x, -y, -start_pos.z)); // double negative on the Z! - normals.push_back(Vector3(0.0, 0.0, 1.0)); - ADD_TANGENT(1.0, 0.0, 0.0, 1.0); - uvs.push_back(Vector2(offset_front + u, v)); - if (_add_uv2) { - uv2s.push_back(Vector2(u2 * scale * width_h, v2 * height_v)); - } - point++; - - /* back */ - points.push_back(Vector3(start_x + scaled_size_x - x, -y, start_pos.z)); - normals.push_back(Vector3(0.0, 0.0, -1.0)); - ADD_TANGENT(-1.0, 0.0, 0.0, 1.0); - uvs.push_back(Vector2(twothirds + offset_back + u, v)); - if (_add_uv2) { - uv2s.push_back(Vector2(u2 * scale * width_h, height_v + padding_v + v2 * height_v)); - } - point++; - - if (i > 0 && j == 1) { - int i2 = i * 2; - - /* front */ - indices.push_back(prevrow + i2); - indices.push_back(thisrow + i2); - indices.push_back(thisrow + i2 - 2); - - /* back */ - indices.push_back(prevrow + i2 + 1); - indices.push_back(thisrow + i2 + 1); - indices.push_back(thisrow + i2 - 1); - } else if (i > 0 && j > 0) { - int i2 = i * 2; - - /* front */ - indices.push_back(prevrow + i2 - 2); - indices.push_back(prevrow + i2); - indices.push_back(thisrow + i2 - 2); - indices.push_back(prevrow + i2); - indices.push_back(thisrow + i2); - indices.push_back(thisrow + i2 - 2); - - /* back */ - indices.push_back(prevrow + i2 - 1); - indices.push_back(prevrow + i2 + 1); - indices.push_back(thisrow + i2 - 1); - indices.push_back(prevrow + i2 + 1); - indices.push_back(thisrow + i2 + 1); - indices.push_back(thisrow + i2 - 1); - } - - x += scale * size.x / (subdivide_w + 1.0); - } - - y += size.y / (subdivide_h + 1.0); - prevrow = thisrow; - thisrow = point; - } - - /* left + right */ - Vector3 normal_left, normal_right; - - normal_left = Vector3(-size.y, size.x * left_to_right, 0.0); - normal_right = Vector3(size.y, size.x * (1.0 - left_to_right), 0.0); - normal_left.normalize(); - normal_right.normalize(); - - y = start_pos.y; - thisrow = point; - prevrow = 0; - for (j = 0; j <= (subdivide_h + 1); j++) { - float left, right; - float scale = j / (subdivide_h + 1.0); - - left = start_pos.x + (size.x * (1.0 - scale) * left_to_right); - right = left + (size.x * scale); - - float v = j; - float v2 = scale; - v /= 2.0 * (subdivide_h + 1.0); - - z = start_pos.z; - for (i = 0; i <= (subdivide_d + 1); i++) { - float u = i; - float u2 = u / (subdivide_d + 1.0); - u /= (3.0 * (subdivide_d + 1.0)); - - /* right */ - points.push_back(Vector3(right, -y, -z)); - normals.push_back(normal_right); - ADD_TANGENT(0.0, 0.0, -1.0, 1.0); - uvs.push_back(Vector2(onethird + u, v)); - if (_add_uv2) { - uv2s.push_back(Vector2(width_h + padding_h + u2 * depth_h, v2 * height_v)); - } - point++; - - /* left */ - points.push_back(Vector3(left, -y, z)); - normals.push_back(normal_left); - ADD_TANGENT(0.0, 0.0, 1.0, 1.0); - uvs.push_back(Vector2(u, 0.5 + v)); - if (_add_uv2) { - uv2s.push_back(Vector2(width_h + padding_h + u2 * depth_h, height_v + padding_v + v2 * height_v)); - } - point++; - - if (i > 0 && j > 0) { - int i2 = i * 2; - - /* right */ - indices.push_back(prevrow + i2 - 2); - indices.push_back(prevrow + i2); - indices.push_back(thisrow + i2 - 2); - indices.push_back(prevrow + i2); - indices.push_back(thisrow + i2); - indices.push_back(thisrow + i2 - 2); - - /* left */ - indices.push_back(prevrow + i2 - 1); - indices.push_back(prevrow + i2 + 1); - indices.push_back(thisrow + i2 - 1); - indices.push_back(prevrow + i2 + 1); - indices.push_back(thisrow + i2 + 1); - indices.push_back(thisrow + i2 - 1); - } - - z += size.z / (subdivide_d + 1.0); - } - - y += size.y / (subdivide_h + 1.0); - prevrow = thisrow; - thisrow = point; - } - - /* bottom */ - z = start_pos.z; - thisrow = point; - prevrow = 0; - for (j = 0; j <= (subdivide_d + 1); j++) { - float v = j; - float v2 = v / (subdivide_d + 1.0); - v /= (2.0 * (subdivide_d + 1.0)); - - x = start_pos.x; - for (i = 0; i <= (subdivide_w + 1); i++) { - float u = i; - float u2 = u / (subdivide_w + 1.0); - u /= (3.0 * (subdivide_w + 1.0)); - - /* bottom */ - points.push_back(Vector3(x, start_pos.y, -z)); - normals.push_back(Vector3(0.0, -1.0, 0.0)); - ADD_TANGENT(1.0, 0.0, 0.0, 1.0); - uvs.push_back(Vector2(twothirds + u, 0.5 + v)); - if (_add_uv2) { - uv2s.push_back(Vector2(u2 * width_h, 2.0 * (height_v + padding_v) + v2 * depth_v)); - } - point++; - - if (i > 0 && j > 0) { - /* bottom */ - indices.push_back(prevrow + i - 1); - indices.push_back(prevrow + i); - indices.push_back(thisrow + i - 1); - indices.push_back(prevrow + i); - indices.push_back(thisrow + i); - indices.push_back(thisrow + i - 1); - } - - x += size.x / (subdivide_w + 1.0); - } - - z += size.z / (subdivide_d + 1.0); - prevrow = thisrow; - thisrow = point; - } - - p_arr[RS::ARRAY_VERTEX] = points; - p_arr[RS::ARRAY_NORMAL] = normals; - p_arr[RS::ARRAY_TANGENT] = tangents; - p_arr[RS::ARRAY_TEX_UV] = uvs; - if (_add_uv2) { - p_arr[RS::ARRAY_TEX_UV2] = uv2s; - } - p_arr[RS::ARRAY_INDEX] = indices; -} - -void PrismMesh::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_left_to_right", "left_to_right"), &PrismMesh::set_left_to_right); - ClassDB::bind_method(D_METHOD("get_left_to_right"), &PrismMesh::get_left_to_right); - - ClassDB::bind_method(D_METHOD("set_size", "size"), &PrismMesh::set_size); - ClassDB::bind_method(D_METHOD("get_size"), &PrismMesh::get_size); - - ClassDB::bind_method(D_METHOD("set_subdivide_width", "segments"), &PrismMesh::set_subdivide_width); - ClassDB::bind_method(D_METHOD("get_subdivide_width"), &PrismMesh::get_subdivide_width); - ClassDB::bind_method(D_METHOD("set_subdivide_height", "segments"), &PrismMesh::set_subdivide_height); - ClassDB::bind_method(D_METHOD("get_subdivide_height"), &PrismMesh::get_subdivide_height); - ClassDB::bind_method(D_METHOD("set_subdivide_depth", "segments"), &PrismMesh::set_subdivide_depth); - ClassDB::bind_method(D_METHOD("get_subdivide_depth"), &PrismMesh::get_subdivide_depth); - - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "left_to_right", PROPERTY_HINT_RANGE, "-2.0,2.0,0.1"), "set_left_to_right", "get_left_to_right"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "size", PROPERTY_HINT_NONE, "suffix:m"), "set_size", "get_size"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_width", "get_subdivide_width"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_height", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_height", "get_subdivide_height"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_depth", "get_subdivide_depth"); -} - -void PrismMesh::set_left_to_right(const float p_left_to_right) { - left_to_right = p_left_to_right; - _request_update(); -} - -float PrismMesh::get_left_to_right() const { - return left_to_right; -} - -void PrismMesh::set_size(const Vector3 &p_size) { - size = p_size; - _update_lightmap_size(); - _request_update(); -} - -Vector3 PrismMesh::get_size() const { - return size; -} - -void PrismMesh::set_subdivide_width(const int p_divisions) { - subdivide_w = p_divisions > 0 ? p_divisions : 0; - _request_update(); -} - -int PrismMesh::get_subdivide_width() const { - return subdivide_w; -} - -void PrismMesh::set_subdivide_height(const int p_divisions) { - subdivide_h = p_divisions > 0 ? p_divisions : 0; - _request_update(); -} - -int PrismMesh::get_subdivide_height() const { - return subdivide_h; -} - -void PrismMesh::set_subdivide_depth(const int p_divisions) { - subdivide_d = p_divisions > 0 ? p_divisions : 0; - _request_update(); -} - -int PrismMesh::get_subdivide_depth() const { - return subdivide_d; -} - -PrismMesh::PrismMesh() {} - -/** - SphereMesh -*/ - -void SphereMesh::_update_lightmap_size() { - if (get_add_uv2()) { - // size must have changed, update lightmap size hint - Size2i _lightmap_size_hint; - float texel_size = get_lightmap_texel_size(); - float padding = get_uv2_padding(); - - float _width = radius * Math_TAU; - _lightmap_size_hint.x = MAX(1.0, (_width / texel_size) + padding); - float _height = (is_hemisphere ? 1.0 : 0.5) * height * Math_PI; // note, with hemisphere height is our radius, while with a full sphere it is the diameter.. - _lightmap_size_hint.y = MAX(1.0, (_height / texel_size) + padding); - - set_lightmap_size_hint(_lightmap_size_hint); - } -} - -void SphereMesh::_create_mesh_array(Array &p_arr) const { - bool _add_uv2 = get_add_uv2(); - float texel_size = get_lightmap_texel_size(); - float _uv2_padding = get_uv2_padding() * texel_size; - - create_mesh_array(p_arr, radius, height, radial_segments, rings, is_hemisphere, _add_uv2, _uv2_padding); -} - -void SphereMesh::create_mesh_array(Array &p_arr, float radius, float height, int radial_segments, int rings, bool is_hemisphere, bool p_add_uv2, const float p_uv2_padding) { - int i, j, prevrow, thisrow, point; - float x, y, z; - - float scale = height * (is_hemisphere ? 1.0 : 0.5); - - // Only used if we calculate UV2 - float circumference = radius * Math_TAU; - float horizontal_length = circumference + p_uv2_padding; - float center_h = 0.5 * circumference / horizontal_length; - - float height_v = scale * Math_PI / ((scale * Math_PI) + p_uv2_padding); - - // set our bounding box - - Vector points; - Vector normals; - Vector tangents; - Vector uvs; - Vector uv2s; - Vector indices; - point = 0; - -#define ADD_TANGENT(m_x, m_y, m_z, m_d) \ - tangents.push_back(m_x); \ - tangents.push_back(m_y); \ - tangents.push_back(m_z); \ - tangents.push_back(m_d); - - thisrow = 0; - prevrow = 0; - for (j = 0; j <= (rings + 1); j++) { - float v = j; - float w; - - v /= (rings + 1); - w = sin(Math_PI * v); - y = scale * cos(Math_PI * v); - - for (i = 0; i <= radial_segments; i++) { - float u = i; - u /= radial_segments; - - x = sin(u * Math_TAU); - z = cos(u * Math_TAU); - - if (is_hemisphere && y < 0.0) { - points.push_back(Vector3(x * radius * w, 0.0, z * radius * w)); - normals.push_back(Vector3(0.0, -1.0, 0.0)); - } else { - Vector3 p = Vector3(x * radius * w, y, z * radius * w); - points.push_back(p); - Vector3 normal = Vector3(x * w * scale, radius * (y / scale), z * w * scale); - normals.push_back(normal.normalized()); - } - ADD_TANGENT(z, 0.0, -x, 1.0) - uvs.push_back(Vector2(u, v)); - if (p_add_uv2) { - float w_h = w * 2.0 * center_h; - uv2s.push_back(Vector2(center_h + ((u - 0.5) * w_h), v * height_v)); - } - point++; - - if (i > 0 && j > 0) { - indices.push_back(prevrow + i - 1); - indices.push_back(prevrow + i); - indices.push_back(thisrow + i - 1); - - indices.push_back(prevrow + i); - indices.push_back(thisrow + i); - indices.push_back(thisrow + i - 1); - } - } - - prevrow = thisrow; - thisrow = point; - } - - p_arr[RS::ARRAY_VERTEX] = points; - p_arr[RS::ARRAY_NORMAL] = normals; - p_arr[RS::ARRAY_TANGENT] = tangents; - p_arr[RS::ARRAY_TEX_UV] = uvs; - if (p_add_uv2) { - p_arr[RS::ARRAY_TEX_UV2] = uv2s; - } - p_arr[RS::ARRAY_INDEX] = indices; -} - -void SphereMesh::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_radius", "radius"), &SphereMesh::set_radius); - ClassDB::bind_method(D_METHOD("get_radius"), &SphereMesh::get_radius); - ClassDB::bind_method(D_METHOD("set_height", "height"), &SphereMesh::set_height); - ClassDB::bind_method(D_METHOD("get_height"), &SphereMesh::get_height); - - ClassDB::bind_method(D_METHOD("set_radial_segments", "radial_segments"), &SphereMesh::set_radial_segments); - ClassDB::bind_method(D_METHOD("get_radial_segments"), &SphereMesh::get_radial_segments); - ClassDB::bind_method(D_METHOD("set_rings", "rings"), &SphereMesh::set_rings); - ClassDB::bind_method(D_METHOD("get_rings"), &SphereMesh::get_rings); - - ClassDB::bind_method(D_METHOD("set_is_hemisphere", "is_hemisphere"), &SphereMesh::set_is_hemisphere); - ClassDB::bind_method(D_METHOD("get_is_hemisphere"), &SphereMesh::get_is_hemisphere); - - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater,suffix:m"), "set_radius", "get_radius"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater,suffix:m"), "set_height", "get_height"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_segments", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_radial_segments", "get_radial_segments"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "1,100,1,or_greater"), "set_rings", "get_rings"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "is_hemisphere"), "set_is_hemisphere", "get_is_hemisphere"); -} - -void SphereMesh::set_radius(const float p_radius) { - radius = p_radius; - _update_lightmap_size(); - _request_update(); -} - -float SphereMesh::get_radius() const { - return radius; -} - -void SphereMesh::set_height(const float p_height) { - height = p_height; - _update_lightmap_size(); - _request_update(); -} - -float SphereMesh::get_height() const { - return height; -} - -void SphereMesh::set_radial_segments(const int p_radial_segments) { - radial_segments = p_radial_segments > 4 ? p_radial_segments : 4; - _request_update(); -} - -int SphereMesh::get_radial_segments() const { - return radial_segments; -} - -void SphereMesh::set_rings(const int p_rings) { - ERR_FAIL_COND(p_rings < 1); - rings = p_rings; - _request_update(); -} - -int SphereMesh::get_rings() const { - return rings; -} - -void SphereMesh::set_is_hemisphere(const bool p_is_hemisphere) { - is_hemisphere = p_is_hemisphere; - _update_lightmap_size(); - _request_update(); -} - -bool SphereMesh::get_is_hemisphere() const { - return is_hemisphere; -} - -SphereMesh::SphereMesh() {} - -/** - TorusMesh -*/ - -void TorusMesh::_update_lightmap_size() { - if (get_add_uv2()) { - // size must have changed, update lightmap size hint - Size2i _lightmap_size_hint; - float texel_size = get_lightmap_texel_size(); - float padding = get_uv2_padding(); - - float min_radius = inner_radius; - float max_radius = outer_radius; - - if (min_radius > max_radius) { - SWAP(min_radius, max_radius); - } - - float radius = (max_radius - min_radius) * 0.5; - - float _width = max_radius * Math_TAU; - _lightmap_size_hint.x = MAX(1.0, (_width / texel_size) + padding); - float _height = radius * Math_TAU; - _lightmap_size_hint.y = MAX(1.0, (_height / texel_size) + padding); - - set_lightmap_size_hint(_lightmap_size_hint); - } -} - -void TorusMesh::_create_mesh_array(Array &p_arr) const { - // set our bounding box - - Vector points; - Vector normals; - Vector tangents; - Vector uvs; - Vector uv2s; - Vector indices; - -#define ADD_TANGENT(m_x, m_y, m_z, m_d) \ - tangents.push_back(m_x); \ - tangents.push_back(m_y); \ - tangents.push_back(m_z); \ - tangents.push_back(m_d); - - ERR_FAIL_COND_MSG(inner_radius == outer_radius, "Inner radius and outer radius cannot be the same."); - - float min_radius = inner_radius; - float max_radius = outer_radius; - - if (min_radius > max_radius) { - SWAP(min_radius, max_radius); - } - - float radius = (max_radius - min_radius) * 0.5; - - // Only used if we calculate UV2 - bool _add_uv2 = get_add_uv2(); - float texel_size = get_lightmap_texel_size(); - float _uv2_padding = get_uv2_padding() * texel_size; - - float horizontal_total = max_radius * Math_TAU + _uv2_padding; - float max_h = max_radius * Math_TAU / horizontal_total; - float delta_h = (max_radius - min_radius) * Math_TAU / horizontal_total; - - float height_v = radius * Math_TAU / (radius * Math_TAU + _uv2_padding); - - for (int i = 0; i <= rings; i++) { - int prevrow = (i - 1) * (ring_segments + 1); - int thisrow = i * (ring_segments + 1); - float inci = float(i) / rings; - float angi = inci * Math_TAU; - - Vector2 normali = Vector2(-Math::sin(angi), -Math::cos(angi)); - - for (int j = 0; j <= ring_segments; j++) { - float incj = float(j) / ring_segments; - float angj = incj * Math_TAU; - - Vector2 normalj = Vector2(-Math::cos(angj), Math::sin(angj)); - Vector2 normalk = normalj * radius + Vector2(min_radius + radius, 0); - - float offset_h = 0.5 * (1.0 - normalj.x) * delta_h; - float adj_h = max_h - offset_h; - offset_h *= 0.5; - - points.push_back(Vector3(normali.x * normalk.x, normalk.y, normali.y * normalk.x)); - normals.push_back(Vector3(normali.x * normalj.x, normalj.y, normali.y * normalj.x)); - ADD_TANGENT(-Math::cos(angi), 0.0, Math::sin(angi), 1.0); - uvs.push_back(Vector2(inci, incj)); - if (_add_uv2) { - uv2s.push_back(Vector2(offset_h + inci * adj_h, incj * height_v)); - } - - if (i > 0 && j > 0) { - indices.push_back(thisrow + j - 1); - indices.push_back(prevrow + j); - indices.push_back(prevrow + j - 1); - - indices.push_back(thisrow + j - 1); - indices.push_back(thisrow + j); - indices.push_back(prevrow + j); - } - } - } - - p_arr[RS::ARRAY_VERTEX] = points; - p_arr[RS::ARRAY_NORMAL] = normals; - p_arr[RS::ARRAY_TANGENT] = tangents; - p_arr[RS::ARRAY_TEX_UV] = uvs; - if (_add_uv2) { - p_arr[RS::ARRAY_TEX_UV2] = uv2s; - } - p_arr[RS::ARRAY_INDEX] = indices; -} - -void TorusMesh::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_inner_radius", "radius"), &TorusMesh::set_inner_radius); - ClassDB::bind_method(D_METHOD("get_inner_radius"), &TorusMesh::get_inner_radius); - - ClassDB::bind_method(D_METHOD("set_outer_radius", "radius"), &TorusMesh::set_outer_radius); - ClassDB::bind_method(D_METHOD("get_outer_radius"), &TorusMesh::get_outer_radius); - - ClassDB::bind_method(D_METHOD("set_rings", "rings"), &TorusMesh::set_rings); - ClassDB::bind_method(D_METHOD("get_rings"), &TorusMesh::get_rings); - - ClassDB::bind_method(D_METHOD("set_ring_segments", "rings"), &TorusMesh::set_ring_segments); - ClassDB::bind_method(D_METHOD("get_ring_segments"), &TorusMesh::get_ring_segments); - - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "inner_radius", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001,or_greater,exp"), "set_inner_radius", "get_inner_radius"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "outer_radius", PROPERTY_HINT_RANGE, "0.001,1000.0,0.001,or_greater,exp"), "set_outer_radius", "get_outer_radius"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "rings", PROPERTY_HINT_RANGE, "3,128,1,or_greater"), "set_rings", "get_rings"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "ring_segments", PROPERTY_HINT_RANGE, "3,64,1,or_greater"), "set_ring_segments", "get_ring_segments"); -} - -void TorusMesh::set_inner_radius(const float p_inner_radius) { - inner_radius = p_inner_radius; - _request_update(); -} - -float TorusMesh::get_inner_radius() const { - return inner_radius; -} - -void TorusMesh::set_outer_radius(const float p_outer_radius) { - outer_radius = p_outer_radius; - _request_update(); -} - -float TorusMesh::get_outer_radius() const { - return outer_radius; -} - -void TorusMesh::set_rings(const int p_rings) { - ERR_FAIL_COND(p_rings < 3); - rings = p_rings; - _request_update(); -} - -int TorusMesh::get_rings() const { - return rings; -} - -void TorusMesh::set_ring_segments(const int p_ring_segments) { - ERR_FAIL_COND(p_ring_segments < 3); - ring_segments = p_ring_segments; - _request_update(); -} - -int TorusMesh::get_ring_segments() const { - return ring_segments; -} - -TorusMesh::TorusMesh() {} - -/** - PointMesh -*/ - -void PointMesh::_create_mesh_array(Array &p_arr) const { - Vector faces; - faces.resize(1); - faces.set(0, Vector3(0.0, 0.0, 0.0)); - - p_arr[RS::ARRAY_VERTEX] = faces; -} - -PointMesh::PointMesh() { - primitive_type = PRIMITIVE_POINTS; -} -// TUBE TRAIL - -void TubeTrailMesh::set_radius(const float p_radius) { - radius = p_radius; - _request_update(); -} -float TubeTrailMesh::get_radius() const { - return radius; -} - -void TubeTrailMesh::set_radial_steps(const int p_radial_steps) { - ERR_FAIL_COND(p_radial_steps < 3 || p_radial_steps > 128); - radial_steps = p_radial_steps; - _request_update(); -} -int TubeTrailMesh::get_radial_steps() const { - return radial_steps; -} - -void TubeTrailMesh::set_sections(const int p_sections) { - ERR_FAIL_COND(p_sections < 2 || p_sections > 128); - sections = p_sections; - _request_update(); -} -int TubeTrailMesh::get_sections() const { - return sections; -} - -void TubeTrailMesh::set_section_length(float p_section_length) { - section_length = p_section_length; - _request_update(); -} -float TubeTrailMesh::get_section_length() const { - return section_length; -} - -void TubeTrailMesh::set_section_rings(const int p_section_rings) { - ERR_FAIL_COND(p_section_rings < 1 || p_section_rings > 1024); - section_rings = p_section_rings; - _request_update(); -} -int TubeTrailMesh::get_section_rings() const { - return section_rings; -} - -void TubeTrailMesh::set_cap_top(bool p_cap_top) { - cap_top = p_cap_top; - _request_update(); -} - -bool TubeTrailMesh::is_cap_top() const { - return cap_top; -} - -void TubeTrailMesh::set_cap_bottom(bool p_cap_bottom) { - cap_bottom = p_cap_bottom; - _request_update(); -} - -bool TubeTrailMesh::is_cap_bottom() const { - return cap_bottom; -} - -void TubeTrailMesh::set_curve(const Ref &p_curve) { - if (curve == p_curve) { - return; - } - if (curve.is_valid()) { - curve->disconnect_changed(callable_mp(this, &TubeTrailMesh::_curve_changed)); - } - curve = p_curve; - if (curve.is_valid()) { - curve->connect_changed(callable_mp(this, &TubeTrailMesh::_curve_changed)); - } - _request_update(); -} -Ref TubeTrailMesh::get_curve() const { - return curve; -} - -void TubeTrailMesh::_curve_changed() { - _request_update(); -} -int TubeTrailMesh::get_builtin_bind_pose_count() const { - return sections + 1; -} - -Transform3D TubeTrailMesh::get_builtin_bind_pose(int p_index) const { - float depth = section_length * sections; - - Transform3D xform; - xform.origin.y = depth / 2.0 - section_length * float(p_index); - xform.origin.y = -xform.origin.y; //bind is an inverse transform, so negate y - - return xform; -} - -void TubeTrailMesh::_create_mesh_array(Array &p_arr) const { - // Seeing use case for TubeTrailMesh, no need to do anything more then default UV2 calculation - - PackedVector3Array points; - PackedVector3Array normals; - PackedFloat32Array tangents; - PackedVector2Array uvs; - PackedInt32Array bone_indices; - PackedFloat32Array bone_weights; - PackedInt32Array indices; - - int point = 0; - -#define ADD_TANGENT(m_x, m_y, m_z, m_d) \ - tangents.push_back(m_x); \ - tangents.push_back(m_y); \ - tangents.push_back(m_z); \ - tangents.push_back(m_d); - - int thisrow = 0; - int prevrow = 0; - - int total_rings = section_rings * sections; - float depth = section_length * sections; - - for (int j = 0; j <= total_rings; j++) { - float v = j; - v /= total_rings; - - float y = depth * v; - y = (depth * 0.5) - y; - - int bone = j / section_rings; - float blend = 1.0 - float(j % section_rings) / float(section_rings); - - for (int i = 0; i <= radial_steps; i++) { - float u = i; - u /= radial_steps; - - float r = radius; - if (curve.is_valid() && curve->get_point_count() > 0) { - r *= curve->sample_baked(v); - } - float x = sin(u * Math_TAU); - float z = cos(u * Math_TAU); - - Vector3 p = Vector3(x * r, y, z * r); - points.push_back(p); - normals.push_back(Vector3(x, 0, z)); - ADD_TANGENT(z, 0.0, -x, 1.0) - uvs.push_back(Vector2(u, v * 0.5)); - point++; - { - bone_indices.push_back(bone); - bone_indices.push_back(MIN(sections, bone + 1)); - bone_indices.push_back(0); - bone_indices.push_back(0); - - bone_weights.push_back(blend); - bone_weights.push_back(1.0 - blend); - bone_weights.push_back(0); - bone_weights.push_back(0); - } - - if (i > 0 && j > 0) { - indices.push_back(prevrow + i - 1); - indices.push_back(prevrow + i); - indices.push_back(thisrow + i - 1); - - indices.push_back(prevrow + i); - indices.push_back(thisrow + i); - indices.push_back(thisrow + i - 1); - } - } - - prevrow = thisrow; - thisrow = point; - } - - if (cap_top) { - // add top - float scale_pos = 1.0; - if (curve.is_valid() && curve->get_point_count() > 0) { - scale_pos = curve->sample_baked(0); - } - - if (scale_pos > CMP_EPSILON) { - float y = depth * 0.5; - - thisrow = point; - points.push_back(Vector3(0.0, y, 0)); - normals.push_back(Vector3(0.0, 1.0, 0.0)); - ADD_TANGENT(1.0, 0.0, 0.0, 1.0) - uvs.push_back(Vector2(0.25, 0.75)); - point++; - - bone_indices.push_back(0); - bone_indices.push_back(0); - bone_indices.push_back(0); - bone_indices.push_back(0); - - bone_weights.push_back(1.0); - bone_weights.push_back(0); - bone_weights.push_back(0); - bone_weights.push_back(0); - - float rm = radius * scale_pos; - - for (int i = 0; i <= radial_steps; i++) { - float r = i; - r /= radial_steps; - - float x = sin(r * Math_TAU); - float z = cos(r * Math_TAU); - - float u = ((x + 1.0) * 0.25); - float v = 0.5 + ((z + 1.0) * 0.25); - - Vector3 p = Vector3(x * rm, y, z * rm); - points.push_back(p); - normals.push_back(Vector3(0.0, 1.0, 0.0)); - ADD_TANGENT(1.0, 0.0, 0.0, 1.0) - uvs.push_back(Vector2(u, v)); - point++; - - bone_indices.push_back(0); - bone_indices.push_back(0); - bone_indices.push_back(0); - bone_indices.push_back(0); - - bone_weights.push_back(1.0); - bone_weights.push_back(0); - bone_weights.push_back(0); - bone_weights.push_back(0); - - if (i > 0) { - indices.push_back(thisrow); - indices.push_back(point - 1); - indices.push_back(point - 2); - } - } - } - } - - if (cap_bottom) { - float scale_neg = 1.0; - if (curve.is_valid() && curve->get_point_count() > 0) { - scale_neg = curve->sample_baked(1.0); - } - - if (scale_neg > CMP_EPSILON) { - // add bottom - float y = depth * -0.5; - - thisrow = point; - points.push_back(Vector3(0.0, y, 0.0)); - normals.push_back(Vector3(0.0, -1.0, 0.0)); - ADD_TANGENT(1.0, 0.0, 0.0, 1.0) - uvs.push_back(Vector2(0.75, 0.75)); - point++; - - bone_indices.push_back(sections); - bone_indices.push_back(0); - bone_indices.push_back(0); - bone_indices.push_back(0); - - bone_weights.push_back(1.0); - bone_weights.push_back(0); - bone_weights.push_back(0); - bone_weights.push_back(0); - - float rm = radius * scale_neg; - - for (int i = 0; i <= radial_steps; i++) { - float r = i; - r /= radial_steps; - - float x = sin(r * Math_TAU); - float z = cos(r * Math_TAU); - - float u = 0.5 + ((x + 1.0) * 0.25); - float v = 1.0 - ((z + 1.0) * 0.25); - - Vector3 p = Vector3(x * rm, y, z * rm); - points.push_back(p); - normals.push_back(Vector3(0.0, -1.0, 0.0)); - ADD_TANGENT(1.0, 0.0, 0.0, 1.0) - uvs.push_back(Vector2(u, v)); - point++; - - bone_indices.push_back(sections); - bone_indices.push_back(0); - bone_indices.push_back(0); - bone_indices.push_back(0); - - bone_weights.push_back(1.0); - bone_weights.push_back(0); - bone_weights.push_back(0); - bone_weights.push_back(0); - - if (i > 0) { - indices.push_back(thisrow); - indices.push_back(point - 2); - indices.push_back(point - 1); - } - } - } - } - - p_arr[RS::ARRAY_VERTEX] = points; - p_arr[RS::ARRAY_NORMAL] = normals; - p_arr[RS::ARRAY_TANGENT] = tangents; - p_arr[RS::ARRAY_TEX_UV] = uvs; - p_arr[RS::ARRAY_BONES] = bone_indices; - p_arr[RS::ARRAY_WEIGHTS] = bone_weights; - p_arr[RS::ARRAY_INDEX] = indices; -} - -void TubeTrailMesh::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_radius", "radius"), &TubeTrailMesh::set_radius); - ClassDB::bind_method(D_METHOD("get_radius"), &TubeTrailMesh::get_radius); - - ClassDB::bind_method(D_METHOD("set_radial_steps", "radial_steps"), &TubeTrailMesh::set_radial_steps); - ClassDB::bind_method(D_METHOD("get_radial_steps"), &TubeTrailMesh::get_radial_steps); - - ClassDB::bind_method(D_METHOD("set_sections", "sections"), &TubeTrailMesh::set_sections); - ClassDB::bind_method(D_METHOD("get_sections"), &TubeTrailMesh::get_sections); - - ClassDB::bind_method(D_METHOD("set_section_length", "section_length"), &TubeTrailMesh::set_section_length); - ClassDB::bind_method(D_METHOD("get_section_length"), &TubeTrailMesh::get_section_length); - - ClassDB::bind_method(D_METHOD("set_section_rings", "section_rings"), &TubeTrailMesh::set_section_rings); - ClassDB::bind_method(D_METHOD("get_section_rings"), &TubeTrailMesh::get_section_rings); - - ClassDB::bind_method(D_METHOD("set_cap_top", "cap_top"), &TubeTrailMesh::set_cap_top); - ClassDB::bind_method(D_METHOD("is_cap_top"), &TubeTrailMesh::is_cap_top); - - ClassDB::bind_method(D_METHOD("set_cap_bottom", "cap_bottom"), &TubeTrailMesh::set_cap_bottom); - ClassDB::bind_method(D_METHOD("is_cap_bottom"), &TubeTrailMesh::is_cap_bottom); - - ClassDB::bind_method(D_METHOD("set_curve", "curve"), &TubeTrailMesh::set_curve); - ClassDB::bind_method(D_METHOD("get_curve"), &TubeTrailMesh::get_curve); - - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater,suffix:m"), "set_radius", "get_radius"); - - ADD_PROPERTY(PropertyInfo(Variant::INT, "radial_steps", PROPERTY_HINT_RANGE, "3,128,1"), "set_radial_steps", "get_radial_steps"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "sections", PROPERTY_HINT_RANGE, "2,128,1"), "set_sections", "get_sections"); - - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "section_length", PROPERTY_HINT_RANGE, "0.001,1024.0,0.001,or_greater,suffix:m"), "set_section_length", "get_section_length"); - - ADD_PROPERTY(PropertyInfo(Variant::INT, "section_rings", PROPERTY_HINT_RANGE, "1,128,1"), "set_section_rings", "get_section_rings"); - - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cap_top"), "set_cap_top", "is_cap_top"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "cap_bottom"), "set_cap_bottom", "is_cap_bottom"); - - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve", "get_curve"); -} - -TubeTrailMesh::TubeTrailMesh() { -} - -// RIBBON TRAIL - -void RibbonTrailMesh::set_shape(Shape p_shape) { - shape = p_shape; - _request_update(); -} -RibbonTrailMesh::Shape RibbonTrailMesh::get_shape() const { - return shape; -} - -void RibbonTrailMesh::set_size(const float p_size) { - size = p_size; - _request_update(); -} -float RibbonTrailMesh::get_size() const { - return size; -} - -void RibbonTrailMesh::set_sections(const int p_sections) { - ERR_FAIL_COND(p_sections < 2 || p_sections > 128); - sections = p_sections; - _request_update(); -} -int RibbonTrailMesh::get_sections() const { - return sections; -} - -void RibbonTrailMesh::set_section_length(float p_section_length) { - section_length = p_section_length; - _request_update(); -} -float RibbonTrailMesh::get_section_length() const { - return section_length; -} - -void RibbonTrailMesh::set_section_segments(const int p_section_segments) { - ERR_FAIL_COND(p_section_segments < 1 || p_section_segments > 1024); - section_segments = p_section_segments; - _request_update(); -} -int RibbonTrailMesh::get_section_segments() const { - return section_segments; -} - -void RibbonTrailMesh::set_curve(const Ref &p_curve) { - if (curve == p_curve) { - return; - } - if (curve.is_valid()) { - curve->disconnect_changed(callable_mp(this, &RibbonTrailMesh::_curve_changed)); - } - curve = p_curve; - if (curve.is_valid()) { - curve->connect_changed(callable_mp(this, &RibbonTrailMesh::_curve_changed)); - } - _request_update(); -} -Ref RibbonTrailMesh::get_curve() const { - return curve; -} - -void RibbonTrailMesh::_curve_changed() { - _request_update(); -} -int RibbonTrailMesh::get_builtin_bind_pose_count() const { - return sections + 1; -} - -Transform3D RibbonTrailMesh::get_builtin_bind_pose(int p_index) const { - float depth = section_length * sections; - - Transform3D xform; - xform.origin.y = depth / 2.0 - section_length * float(p_index); - xform.origin.y = -xform.origin.y; //bind is an inverse transform, so negate y - - return xform; -} - -void RibbonTrailMesh::_create_mesh_array(Array &p_arr) const { - // Seeing use case of ribbon trail mesh, no need to implement special UV2 calculation - - PackedVector3Array points; - PackedVector3Array normals; - PackedFloat32Array tangents; - PackedVector2Array uvs; - PackedInt32Array bone_indices; - PackedFloat32Array bone_weights; - PackedInt32Array indices; - -#define ADD_TANGENT(m_x, m_y, m_z, m_d) \ - tangents.push_back(m_x); \ - tangents.push_back(m_y); \ - tangents.push_back(m_z); \ - tangents.push_back(m_d); - - int total_segments = section_segments * sections; - float depth = section_length * sections; - - for (int j = 0; j <= total_segments; j++) { - float v = j; - v /= total_segments; - - float y = depth * v; - y = (depth * 0.5) - y; - - int bone = j / section_segments; - float blend = 1.0 - float(j % section_segments) / float(section_segments); - - float s = size; - - if (curve.is_valid() && curve->get_point_count() > 0) { - s *= curve->sample_baked(v); - } - - points.push_back(Vector3(-s * 0.5, y, 0)); - points.push_back(Vector3(+s * 0.5, y, 0)); - if (shape == SHAPE_CROSS) { - points.push_back(Vector3(0, y, -s * 0.5)); - points.push_back(Vector3(0, y, +s * 0.5)); - } - - normals.push_back(Vector3(0, 0, 1)); - normals.push_back(Vector3(0, 0, 1)); - if (shape == SHAPE_CROSS) { - normals.push_back(Vector3(1, 0, 0)); - normals.push_back(Vector3(1, 0, 0)); - } - - uvs.push_back(Vector2(0, v)); - uvs.push_back(Vector2(1, v)); - if (shape == SHAPE_CROSS) { - uvs.push_back(Vector2(0, v)); - uvs.push_back(Vector2(1, v)); - } - - ADD_TANGENT(0.0, 1.0, 0.0, 1.0) - ADD_TANGENT(0.0, 1.0, 0.0, 1.0) - if (shape == SHAPE_CROSS) { - ADD_TANGENT(0.0, 1.0, 0.0, 1.0) - ADD_TANGENT(0.0, 1.0, 0.0, 1.0) - } - - for (int i = 0; i < (shape == SHAPE_CROSS ? 4 : 2); i++) { - bone_indices.push_back(bone); - bone_indices.push_back(MIN(sections, bone + 1)); - bone_indices.push_back(0); - bone_indices.push_back(0); - - bone_weights.push_back(blend); - bone_weights.push_back(1.0 - blend); - bone_weights.push_back(0); - bone_weights.push_back(0); - } - - if (j > 0) { - if (shape == SHAPE_CROSS) { - int base = j * 4 - 4; - indices.push_back(base + 0); - indices.push_back(base + 1); - indices.push_back(base + 4); - - indices.push_back(base + 1); - indices.push_back(base + 5); - indices.push_back(base + 4); - - indices.push_back(base + 2); - indices.push_back(base + 3); - indices.push_back(base + 6); - - indices.push_back(base + 3); - indices.push_back(base + 7); - indices.push_back(base + 6); - } else { - int base = j * 2 - 2; - indices.push_back(base + 0); - indices.push_back(base + 1); - indices.push_back(base + 2); - - indices.push_back(base + 1); - indices.push_back(base + 3); - indices.push_back(base + 2); - } - } - } - - p_arr[RS::ARRAY_VERTEX] = points; - p_arr[RS::ARRAY_NORMAL] = normals; - p_arr[RS::ARRAY_TANGENT] = tangents; - p_arr[RS::ARRAY_TEX_UV] = uvs; - p_arr[RS::ARRAY_BONES] = bone_indices; - p_arr[RS::ARRAY_WEIGHTS] = bone_weights; - p_arr[RS::ARRAY_INDEX] = indices; -} - -void RibbonTrailMesh::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_size", "size"), &RibbonTrailMesh::set_size); - ClassDB::bind_method(D_METHOD("get_size"), &RibbonTrailMesh::get_size); - - ClassDB::bind_method(D_METHOD("set_sections", "sections"), &RibbonTrailMesh::set_sections); - ClassDB::bind_method(D_METHOD("get_sections"), &RibbonTrailMesh::get_sections); - - ClassDB::bind_method(D_METHOD("set_section_length", "section_length"), &RibbonTrailMesh::set_section_length); - ClassDB::bind_method(D_METHOD("get_section_length"), &RibbonTrailMesh::get_section_length); - - ClassDB::bind_method(D_METHOD("set_section_segments", "section_segments"), &RibbonTrailMesh::set_section_segments); - ClassDB::bind_method(D_METHOD("get_section_segments"), &RibbonTrailMesh::get_section_segments); - - ClassDB::bind_method(D_METHOD("set_curve", "curve"), &RibbonTrailMesh::set_curve); - ClassDB::bind_method(D_METHOD("get_curve"), &RibbonTrailMesh::get_curve); - - ClassDB::bind_method(D_METHOD("set_shape", "shape"), &RibbonTrailMesh::set_shape); - ClassDB::bind_method(D_METHOD("get_shape"), &RibbonTrailMesh::get_shape); - - ADD_PROPERTY(PropertyInfo(Variant::INT, "shape", PROPERTY_HINT_ENUM, "Flat,Cross"), "set_shape", "get_shape"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "size", PROPERTY_HINT_RANGE, "0.001,100.0,0.001,or_greater,suffix:m"), "set_size", "get_size"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "sections", PROPERTY_HINT_RANGE, "2,128,1"), "set_sections", "get_sections"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "section_length", PROPERTY_HINT_RANGE, "0.001,1024.0,0.001,or_greater,suffix:m"), "set_section_length", "get_section_length"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "section_segments", PROPERTY_HINT_RANGE, "1,128,1"), "set_section_segments", "get_section_segments"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_curve", "get_curve"); - - BIND_ENUM_CONSTANT(SHAPE_FLAT) - BIND_ENUM_CONSTANT(SHAPE_CROSS) -} - -RibbonTrailMesh::RibbonTrailMesh() { -} - -/*************************************************************************/ -/* TextMesh */ -/*************************************************************************/ - -void TextMesh::_generate_glyph_mesh_data(const GlyphMeshKey &p_key, const Glyph &p_gl) const { - if (cache.has(p_key)) { - return; - } - - GlyphMeshData &gl_data = cache[p_key]; - - Dictionary d = TS->font_get_glyph_contours(p_gl.font_rid, p_gl.font_size, p_gl.index); - - PackedVector3Array points = d["points"]; - PackedInt32Array contours = d["contours"]; - bool orientation = d["orientation"]; - - if (points.size() < 3 || contours.size() < 1) { - return; // No full contours, only glyph control points (or nothing), ignore. - } - - // Approximate Bezier curves as polygons. - // See https://freetype.org/freetype2/docs/glyphs/glyphs-6.html, for more info. - for (int i = 0; i < contours.size(); i++) { - int32_t start = (i == 0) ? 0 : (contours[i - 1] + 1); - int32_t end = contours[i]; - Vector polygon; - - for (int32_t j = start; j <= end; j++) { - if (points[j].z == TextServer::CONTOUR_CURVE_TAG_ON) { - // Point on the curve. - Vector2 p = Vector2(points[j].x, points[j].y) * pixel_size; - polygon.push_back(ContourPoint(p, true)); - } else if (points[j].z == TextServer::CONTOUR_CURVE_TAG_OFF_CONIC) { - // Conic Bezier arc. - int32_t next = (j == end) ? start : (j + 1); - int32_t prev = (j == start) ? end : (j - 1); - Vector2 p0; - Vector2 p1 = Vector2(points[j].x, points[j].y); - Vector2 p2; - - // For successive conic OFF points add a virtual ON point in the middle. - if (points[prev].z == TextServer::CONTOUR_CURVE_TAG_OFF_CONIC) { - p0 = (Vector2(points[prev].x, points[prev].y) + Vector2(points[j].x, points[j].y)) / 2.0; - } else if (points[prev].z == TextServer::CONTOUR_CURVE_TAG_ON) { - p0 = Vector2(points[prev].x, points[prev].y); - } else { - ERR_FAIL_MSG(vformat("Invalid conic arc point sequence at %d:%d", i, j)); - } - if (points[next].z == TextServer::CONTOUR_CURVE_TAG_OFF_CONIC) { - p2 = (Vector2(points[j].x, points[j].y) + Vector2(points[next].x, points[next].y)) / 2.0; - } else if (points[next].z == TextServer::CONTOUR_CURVE_TAG_ON) { - p2 = Vector2(points[next].x, points[next].y); - } else { - ERR_FAIL_MSG(vformat("Invalid conic arc point sequence at %d:%d", i, j)); - } - - real_t step = CLAMP(curve_step / (p0 - p2).length(), 0.01, 0.5); - real_t t = step; - while (t < 1.0) { - real_t omt = (1.0 - t); - real_t omt2 = omt * omt; - real_t t2 = t * t; - - Vector2 point = p1 + omt2 * (p0 - p1) + t2 * (p2 - p1); - Vector2 p = point * pixel_size; - polygon.push_back(ContourPoint(p, false)); - t += step; - } - } else if (points[j].z == TextServer::CONTOUR_CURVE_TAG_OFF_CUBIC) { - // Cubic Bezier arc. - int32_t cur = j; - int32_t next1 = (j == end) ? start : (j + 1); - int32_t next2 = (next1 == end) ? start : (next1 + 1); - int32_t prev = (j == start) ? end : (j - 1); - - // There must be exactly two OFF points and two ON points for each cubic arc. - if (points[prev].z != TextServer::CONTOUR_CURVE_TAG_ON) { - cur = (cur == 0) ? end : cur - 1; - next1 = (next1 == 0) ? end : next1 - 1; - next2 = (next2 == 0) ? end : next2 - 1; - prev = (prev == 0) ? end : prev - 1; - } else { - j++; - } - ERR_FAIL_COND_MSG(points[prev].z != TextServer::CONTOUR_CURVE_TAG_ON, vformat("Invalid cubic arc point sequence at %d:%d", i, prev)); - ERR_FAIL_COND_MSG(points[cur].z != TextServer::CONTOUR_CURVE_TAG_OFF_CUBIC, vformat("Invalid cubic arc point sequence at %d:%d", i, cur)); - ERR_FAIL_COND_MSG(points[next1].z != TextServer::CONTOUR_CURVE_TAG_OFF_CUBIC, vformat("Invalid cubic arc point sequence at %d:%d", i, next1)); - ERR_FAIL_COND_MSG(points[next2].z != TextServer::CONTOUR_CURVE_TAG_ON, vformat("Invalid cubic arc point sequence at %d:%d", i, next2)); - - Vector2 p0 = Vector2(points[prev].x, points[prev].y); - Vector2 p1 = Vector2(points[cur].x, points[cur].y); - Vector2 p2 = Vector2(points[next1].x, points[next1].y); - Vector2 p3 = Vector2(points[next2].x, points[next2].y); - - real_t step = CLAMP(curve_step / (p0 - p3).length(), 0.01, 0.5); - real_t t = step; - while (t < 1.0) { - Vector2 point = p0.bezier_interpolate(p1, p2, p3, t); - Vector2 p = point * pixel_size; - polygon.push_back(ContourPoint(p, false)); - t += step; - } - } else { - ERR_FAIL_MSG(vformat("Unknown point tag at %d:%d", i, j)); - } - } - - if (polygon.size() < 3) { - continue; // Skip glyph control points. - } - - if (!orientation) { - polygon.reverse(); - } - - gl_data.contours.push_back(polygon); - } - - // Calculate bounds. - List in_poly; - for (int i = 0; i < gl_data.contours.size(); i++) { - TPPLPoly inp; - inp.Init(gl_data.contours[i].size()); - real_t length = 0.0; - for (int j = 0; j < gl_data.contours[i].size(); j++) { - int next = (j + 1 == gl_data.contours[i].size()) ? 0 : (j + 1); - - gl_data.min_p.x = MIN(gl_data.min_p.x, gl_data.contours[i][j].point.x); - gl_data.min_p.y = MIN(gl_data.min_p.y, gl_data.contours[i][j].point.y); - gl_data.max_p.x = MAX(gl_data.max_p.x, gl_data.contours[i][j].point.x); - gl_data.max_p.y = MAX(gl_data.max_p.y, gl_data.contours[i][j].point.y); - length += (gl_data.contours[i][next].point - gl_data.contours[i][j].point).length(); - - inp.GetPoint(j) = gl_data.contours[i][j].point; - } - TPPLOrientation poly_orient = inp.GetOrientation(); - if (poly_orient == TPPL_ORIENTATION_CW) { - inp.SetHole(true); - } - in_poly.push_back(inp); - gl_data.contours_info.push_back(ContourInfo(length, poly_orient == TPPL_ORIENTATION_CCW)); - } - - TPPLPartition tpart; - - //Decompose and triangulate. - List out_poly; - if (tpart.ConvexPartition_HM(&in_poly, &out_poly) == 0) { - ERR_FAIL_MSG("Convex decomposing failed. Make sure the font doesn't contain self-intersecting lines, as these are not supported in TextMesh."); - } - List out_tris; - for (List::Element *I = out_poly.front(); I; I = I->next()) { - if (tpart.Triangulate_OPT(&(I->get()), &out_tris) == 0) { - ERR_FAIL_MSG("Triangulation failed. Make sure the font doesn't contain self-intersecting lines, as these are not supported in TextMesh."); - } - } - - for (List::Element *I = out_tris.front(); I; I = I->next()) { - TPPLPoly &tp = I->get(); - ERR_FAIL_COND(tp.GetNumPoints() != 3); // Triangles only. - - for (int i = 0; i < 3; i++) { - gl_data.triangles.push_back(Vector2(tp.GetPoint(i).x, tp.GetPoint(i).y)); - } - } -} - -void TextMesh::_create_mesh_array(Array &p_arr) const { - Ref font = _get_font_or_default(); - ERR_FAIL_COND(font.is_null()); - - if (dirty_cache) { - cache.clear(); - dirty_cache = false; - } - - // When a shaped text is invalidated by an external source, we want to reshape it. - if (!TS->shaped_text_is_ready(text_rid)) { - dirty_text = true; - } - - for (const RID &line_rid : lines_rid) { - if (!TS->shaped_text_is_ready(line_rid)) { - dirty_lines = true; - break; - } - } - - // Update text buffer. - if (dirty_text) { - TS->shaped_text_clear(text_rid); - TS->shaped_text_set_direction(text_rid, text_direction); - - String txt = (uppercase) ? TS->string_to_upper(xl_text, language) : xl_text; - TS->shaped_text_add_string(text_rid, txt, font->get_rids(), font_size, font->get_opentype_features(), language); - - TypedArray stt; - if (st_parser == TextServer::STRUCTURED_TEXT_CUSTOM) { - GDVIRTUAL_CALL(_structured_text_parser, st_args, txt, stt); - } else { - stt = TS->parse_structured_text(st_parser, st_args, txt); - } - TS->shaped_text_set_bidi_override(text_rid, stt); - - dirty_text = false; - dirty_font = false; - dirty_lines = true; - } else if (dirty_font) { - int spans = TS->shaped_get_span_count(text_rid); - for (int i = 0; i < spans; i++) { - TS->shaped_set_span_update_font(text_rid, i, font->get_rids(), font_size, font->get_opentype_features()); - } - - dirty_font = false; - dirty_lines = true; - } - - if (dirty_lines) { - for (int i = 0; i < lines_rid.size(); i++) { - TS->free_rid(lines_rid[i]); - } - lines_rid.clear(); - - BitField autowrap_flags = TextServer::BREAK_MANDATORY; - switch (autowrap_mode) { - case TextServer::AUTOWRAP_WORD_SMART: - autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_ADAPTIVE | TextServer::BREAK_MANDATORY; - break; - case TextServer::AUTOWRAP_WORD: - autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_MANDATORY; - break; - case TextServer::AUTOWRAP_ARBITRARY: - autowrap_flags = TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_MANDATORY; - break; - case TextServer::AUTOWRAP_OFF: - break; - } - PackedInt32Array line_breaks = TS->shaped_text_get_line_breaks(text_rid, width, 0, autowrap_flags); - - float max_line_w = 0.0; - for (int i = 0; i < line_breaks.size(); i = i + 2) { - RID line = TS->shaped_text_substr(text_rid, line_breaks[i], line_breaks[i + 1] - line_breaks[i]); - max_line_w = MAX(max_line_w, TS->shaped_text_get_width(line)); - lines_rid.push_back(line); - } - - if (horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL) { - int jst_to_line = lines_rid.size(); - if (lines_rid.size() == 1 && jst_flags.has_flag(TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE)) { - jst_to_line = lines_rid.size(); - } else { - if (jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE)) { - jst_to_line = lines_rid.size() - 1; - } - if (jst_flags.has_flag(TextServer::JUSTIFICATION_SKIP_LAST_LINE_WITH_VISIBLE_CHARS)) { - for (int i = lines_rid.size() - 1; i >= 0; i--) { - if (TS->shaped_text_has_visible_chars(lines_rid[i])) { - jst_to_line = i; - break; - } - } - } - } - for (int i = 0; i < jst_to_line; i++) { - TS->shaped_text_fit_to_width(lines_rid[i], (width > 0) ? width : max_line_w, jst_flags); - } - } - dirty_lines = false; - } - - float total_h = 0.0; - for (int i = 0; i < lines_rid.size(); i++) { - total_h += (TS->shaped_text_get_size(lines_rid[i]).y + line_spacing) * pixel_size; - } - - float vbegin = 0.0; - switch (vertical_alignment) { - case VERTICAL_ALIGNMENT_FILL: - case VERTICAL_ALIGNMENT_TOP: { - // Nothing. - } break; - case VERTICAL_ALIGNMENT_CENTER: { - vbegin = (total_h - line_spacing * pixel_size) / 2.0; - } break; - case VERTICAL_ALIGNMENT_BOTTOM: { - vbegin = (total_h - line_spacing * pixel_size); - } break; - } - - Vector vertices; - Vector normals; - Vector tangents; - Vector uvs; - Vector indices; - - Vector2 min_p = Vector2(INFINITY, INFINITY); - Vector2 max_p = Vector2(-INFINITY, -INFINITY); - - int32_t p_size = 0; - int32_t i_size = 0; - - Vector2 offset = Vector2(0, vbegin + lbl_offset.y * pixel_size); - for (int i = 0; i < lines_rid.size(); i++) { - const Glyph *glyphs = TS->shaped_text_get_glyphs(lines_rid[i]); - int gl_size = TS->shaped_text_get_glyph_count(lines_rid[i]); - float line_width = TS->shaped_text_get_width(lines_rid[i]) * pixel_size; - - switch (horizontal_alignment) { - case HORIZONTAL_ALIGNMENT_LEFT: - offset.x = 0.0; - break; - case HORIZONTAL_ALIGNMENT_FILL: - case HORIZONTAL_ALIGNMENT_CENTER: { - offset.x = -line_width / 2.0; - } break; - case HORIZONTAL_ALIGNMENT_RIGHT: { - offset.x = -line_width; - } break; - } - offset.x += lbl_offset.x * pixel_size; - offset.y -= TS->shaped_text_get_ascent(lines_rid[i]) * pixel_size; - - bool has_depth = !Math::is_zero_approx(depth); - - for (int j = 0; j < gl_size; j++) { - if (glyphs[j].index == 0) { - offset.x += glyphs[j].advance * pixel_size * glyphs[j].repeat; - continue; - } - if (glyphs[j].font_rid != RID()) { - GlyphMeshKey key = GlyphMeshKey(glyphs[j].font_rid.get_id(), glyphs[j].index); - _generate_glyph_mesh_data(key, glyphs[j]); - GlyphMeshData &gl_data = cache[key]; - const Vector2 gl_of = Vector2(glyphs[j].x_off, glyphs[j].y_off) * pixel_size; - - p_size += glyphs[j].repeat * gl_data.triangles.size() * ((has_depth) ? 2 : 1); - i_size += glyphs[j].repeat * gl_data.triangles.size() * ((has_depth) ? 2 : 1); - - if (has_depth) { - for (int k = 0; k < gl_data.contours.size(); k++) { - p_size += glyphs[j].repeat * gl_data.contours[k].size() * 4; - i_size += glyphs[j].repeat * gl_data.contours[k].size() * 6; - } - } - - for (int r = 0; r < glyphs[j].repeat; r++) { - min_p.x = MIN(gl_data.min_p.x + offset.x + gl_of.x, min_p.x); - min_p.y = MIN(gl_data.min_p.y - offset.y + gl_of.y, min_p.y); - max_p.x = MAX(gl_data.max_p.x + offset.x + gl_of.x, max_p.x); - max_p.y = MAX(gl_data.max_p.y - offset.y + gl_of.y, max_p.y); - - offset.x += glyphs[j].advance * pixel_size; - } - } else { - p_size += glyphs[j].repeat * 4; - i_size += glyphs[j].repeat * 6; - - offset.x += glyphs[j].advance * pixel_size * glyphs[j].repeat; - } - } - offset.y -= (TS->shaped_text_get_descent(lines_rid[i]) + line_spacing) * pixel_size; - } - - vertices.resize(p_size); - normals.resize(p_size); - uvs.resize(p_size); - tangents.resize(p_size * 4); - indices.resize(i_size); - - Vector3 *vertices_ptr = vertices.ptrw(); - Vector3 *normals_ptr = normals.ptrw(); - float *tangents_ptr = tangents.ptrw(); - Vector2 *uvs_ptr = uvs.ptrw(); - int32_t *indices_ptr = indices.ptrw(); - - // Generate mesh. - int32_t p_idx = 0; - int32_t i_idx = 0; - - offset = Vector2(0, vbegin + lbl_offset.y * pixel_size); - for (int i = 0; i < lines_rid.size(); i++) { - const Glyph *glyphs = TS->shaped_text_get_glyphs(lines_rid[i]); - int gl_size = TS->shaped_text_get_glyph_count(lines_rid[i]); - float line_width = TS->shaped_text_get_width(lines_rid[i]) * pixel_size; - - switch (horizontal_alignment) { - case HORIZONTAL_ALIGNMENT_LEFT: - offset.x = 0.0; - break; - case HORIZONTAL_ALIGNMENT_FILL: - case HORIZONTAL_ALIGNMENT_CENTER: { - offset.x = -line_width / 2.0; - } break; - case HORIZONTAL_ALIGNMENT_RIGHT: { - offset.x = -line_width; - } break; - } - offset.x += lbl_offset.x * pixel_size; - offset.y -= TS->shaped_text_get_ascent(lines_rid[i]) * pixel_size; - - bool has_depth = !Math::is_zero_approx(depth); - - // Generate glyph data, precalculate size of the arrays and mesh bounds for UV. - for (int j = 0; j < gl_size; j++) { - if (glyphs[j].index == 0) { - offset.x += glyphs[j].advance * pixel_size * glyphs[j].repeat; - continue; - } - if (glyphs[j].font_rid != RID()) { - GlyphMeshKey key = GlyphMeshKey(glyphs[j].font_rid.get_id(), glyphs[j].index); - _generate_glyph_mesh_data(key, glyphs[j]); - const GlyphMeshData &gl_data = cache[key]; - - int64_t ts = gl_data.triangles.size(); - const Vector2 *ts_ptr = gl_data.triangles.ptr(); - const Vector2 gl_of = Vector2(glyphs[j].x_off, glyphs[j].y_off) * pixel_size; - - for (int r = 0; r < glyphs[j].repeat; r++) { - for (int k = 0; k < ts; k += 3) { - // Add front face. - for (int l = 0; l < 3; l++) { - Vector3 point = Vector3(ts_ptr[k + l].x + offset.x + gl_of.x, -ts_ptr[k + l].y + offset.y - gl_of.y, depth / 2.0); - vertices_ptr[p_idx] = point; - normals_ptr[p_idx] = Vector3(0.0, 0.0, 1.0); - if (has_depth) { - uvs_ptr[p_idx] = Vector2(Math::remap(point.x, min_p.x, max_p.x, real_t(0.0), real_t(1.0)), Math::remap(point.y, -max_p.y, -min_p.y, real_t(0.4), real_t(0.0))); - } else { - uvs_ptr[p_idx] = Vector2(Math::remap(point.x, min_p.x, max_p.x, real_t(0.0), real_t(1.0)), Math::remap(point.y, -max_p.y, -min_p.y, real_t(1.0), real_t(0.0))); - } - tangents_ptr[p_idx * 4 + 0] = 1.0; - tangents_ptr[p_idx * 4 + 1] = 0.0; - tangents_ptr[p_idx * 4 + 2] = 0.0; - tangents_ptr[p_idx * 4 + 3] = 1.0; - indices_ptr[i_idx++] = p_idx; - p_idx++; - } - if (has_depth) { - // Add back face. - for (int l = 2; l >= 0; l--) { - Vector3 point = Vector3(ts_ptr[k + l].x + offset.x + gl_of.x, -ts_ptr[k + l].y + offset.y - gl_of.y, -depth / 2.0); - vertices_ptr[p_idx] = point; - normals_ptr[p_idx] = Vector3(0.0, 0.0, -1.0); - uvs_ptr[p_idx] = Vector2(Math::remap(point.x, min_p.x, max_p.x, real_t(0.0), real_t(1.0)), Math::remap(point.y, -max_p.y, -min_p.y, real_t(0.8), real_t(0.4))); - tangents_ptr[p_idx * 4 + 0] = -1.0; - tangents_ptr[p_idx * 4 + 1] = 0.0; - tangents_ptr[p_idx * 4 + 2] = 0.0; - tangents_ptr[p_idx * 4 + 3] = 1.0; - indices_ptr[i_idx++] = p_idx; - p_idx++; - } - } - } - // Add sides. - if (has_depth) { - for (int k = 0; k < gl_data.contours.size(); k++) { - int64_t ps = gl_data.contours[k].size(); - const ContourPoint *ps_ptr = gl_data.contours[k].ptr(); - const ContourInfo &ps_info = gl_data.contours_info[k]; - real_t length = 0.0; - for (int l = 0; l < ps; l++) { - int prev = (l == 0) ? (ps - 1) : (l - 1); - int next = (l + 1 == ps) ? 0 : (l + 1); - Vector2 d1; - Vector2 d2 = (ps_ptr[next].point - ps_ptr[l].point).normalized(); - if (ps_ptr[l].sharp) { - d1 = d2; - } else { - d1 = (ps_ptr[l].point - ps_ptr[prev].point).normalized(); - } - real_t seg_len = (ps_ptr[next].point - ps_ptr[l].point).length(); - - Vector3 quad_faces[4] = { - Vector3(ps_ptr[l].point.x + offset.x + gl_of.x, -ps_ptr[l].point.y + offset.y - gl_of.y, -depth / 2.0), - Vector3(ps_ptr[next].point.x + offset.x + gl_of.x, -ps_ptr[next].point.y + offset.y - gl_of.y, -depth / 2.0), - Vector3(ps_ptr[l].point.x + offset.x + gl_of.x, -ps_ptr[l].point.y + offset.y - gl_of.y, depth / 2.0), - Vector3(ps_ptr[next].point.x + offset.x + gl_of.x, -ps_ptr[next].point.y + offset.y - gl_of.y, depth / 2.0), - }; - for (int m = 0; m < 4; m++) { - const Vector2 &d = ((m % 2) == 0) ? d1 : d2; - real_t u_pos = ((m % 2) == 0) ? length : length + seg_len; - vertices_ptr[p_idx + m] = quad_faces[m]; - normals_ptr[p_idx + m] = Vector3(d.y, d.x, 0.0); - if (m < 2) { - uvs_ptr[p_idx + m] = Vector2(Math::remap(u_pos, 0, ps_info.length, real_t(0.0), real_t(1.0)), (ps_info.ccw) ? 0.8 : 0.9); - } else { - uvs_ptr[p_idx + m] = Vector2(Math::remap(u_pos, 0, ps_info.length, real_t(0.0), real_t(1.0)), (ps_info.ccw) ? 0.9 : 1.0); - } - tangents_ptr[(p_idx + m) * 4 + 0] = d.x; - tangents_ptr[(p_idx + m) * 4 + 1] = -d.y; - tangents_ptr[(p_idx + m) * 4 + 2] = 0.0; - tangents_ptr[(p_idx + m) * 4 + 3] = 1.0; - } - - indices_ptr[i_idx++] = p_idx; - indices_ptr[i_idx++] = p_idx + 1; - indices_ptr[i_idx++] = p_idx + 2; - - indices_ptr[i_idx++] = p_idx + 1; - indices_ptr[i_idx++] = p_idx + 3; - indices_ptr[i_idx++] = p_idx + 2; - - length += seg_len; - p_idx += 4; - } - } - } - offset.x += glyphs[j].advance * pixel_size; - } - } else { - // Add fallback quad for missing glyphs. - for (int r = 0; r < glyphs[j].repeat; r++) { - Size2 sz = TS->get_hex_code_box_size(glyphs[j].font_size, glyphs[j].index) * pixel_size; - Vector3 quad_faces[4] = { - Vector3(offset.x, offset.y, 0.0), - Vector3(offset.x, sz.y + offset.y, 0.0), - Vector3(sz.x + offset.x, sz.y + offset.y, 0.0), - Vector3(sz.x + offset.x, offset.y, 0.0), - }; - for (int k = 0; k < 4; k++) { - vertices_ptr[p_idx + k] = quad_faces[k]; - normals_ptr[p_idx + k] = Vector3(0.0, 0.0, 1.0); - if (has_depth) { - uvs_ptr[p_idx + k] = Vector2(Math::remap(quad_faces[k].x, min_p.x, max_p.x, real_t(0.0), real_t(1.0)), Math::remap(quad_faces[k].y, -max_p.y, -min_p.y, real_t(0.4), real_t(0.0))); - } else { - uvs_ptr[p_idx + k] = Vector2(Math::remap(quad_faces[k].x, min_p.x, max_p.x, real_t(0.0), real_t(1.0)), Math::remap(quad_faces[k].y, -max_p.y, -min_p.y, real_t(1.0), real_t(0.0))); - } - tangents_ptr[(p_idx + k) * 4 + 0] = 1.0; - tangents_ptr[(p_idx + k) * 4 + 1] = 0.0; - tangents_ptr[(p_idx + k) * 4 + 2] = 0.0; - tangents_ptr[(p_idx + k) * 4 + 3] = 1.0; - } - - indices_ptr[i_idx++] = p_idx; - indices_ptr[i_idx++] = p_idx + 1; - indices_ptr[i_idx++] = p_idx + 2; - - indices_ptr[i_idx++] = p_idx + 0; - indices_ptr[i_idx++] = p_idx + 2; - indices_ptr[i_idx++] = p_idx + 3; - p_idx += 4; - - offset.x += glyphs[j].advance * pixel_size; - } - } - } - offset.y -= (TS->shaped_text_get_descent(lines_rid[i]) + line_spacing) * pixel_size; - } - - if (indices.is_empty()) { - // If empty, add single triangle to suppress errors. - vertices.push_back(Vector3()); - normals.push_back(Vector3()); - uvs.push_back(Vector2()); - tangents.push_back(1.0); - tangents.push_back(0.0); - tangents.push_back(0.0); - tangents.push_back(1.0); - indices.push_back(0); - indices.push_back(0); - indices.push_back(0); - } - - p_arr[RS::ARRAY_VERTEX] = vertices; - p_arr[RS::ARRAY_NORMAL] = normals; - p_arr[RS::ARRAY_TANGENT] = tangents; - p_arr[RS::ARRAY_TEX_UV] = uvs; - p_arr[RS::ARRAY_INDEX] = indices; -} - -void TextMesh::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_horizontal_alignment", "alignment"), &TextMesh::set_horizontal_alignment); - ClassDB::bind_method(D_METHOD("get_horizontal_alignment"), &TextMesh::get_horizontal_alignment); - - ClassDB::bind_method(D_METHOD("set_vertical_alignment", "alignment"), &TextMesh::set_vertical_alignment); - ClassDB::bind_method(D_METHOD("get_vertical_alignment"), &TextMesh::get_vertical_alignment); - - ClassDB::bind_method(D_METHOD("set_text", "text"), &TextMesh::set_text); - ClassDB::bind_method(D_METHOD("get_text"), &TextMesh::get_text); - - ClassDB::bind_method(D_METHOD("set_font", "font"), &TextMesh::set_font); - ClassDB::bind_method(D_METHOD("get_font"), &TextMesh::get_font); - - ClassDB::bind_method(D_METHOD("set_font_size", "font_size"), &TextMesh::set_font_size); - ClassDB::bind_method(D_METHOD("get_font_size"), &TextMesh::get_font_size); - - ClassDB::bind_method(D_METHOD("set_line_spacing", "line_spacing"), &TextMesh::set_line_spacing); - ClassDB::bind_method(D_METHOD("get_line_spacing"), &TextMesh::get_line_spacing); - - ClassDB::bind_method(D_METHOD("set_autowrap_mode", "autowrap_mode"), &TextMesh::set_autowrap_mode); - ClassDB::bind_method(D_METHOD("get_autowrap_mode"), &TextMesh::get_autowrap_mode); - - ClassDB::bind_method(D_METHOD("set_justification_flags", "justification_flags"), &TextMesh::set_justification_flags); - ClassDB::bind_method(D_METHOD("get_justification_flags"), &TextMesh::get_justification_flags); - - ClassDB::bind_method(D_METHOD("set_depth", "depth"), &TextMesh::set_depth); - ClassDB::bind_method(D_METHOD("get_depth"), &TextMesh::get_depth); - - ClassDB::bind_method(D_METHOD("set_width", "width"), &TextMesh::set_width); - ClassDB::bind_method(D_METHOD("get_width"), &TextMesh::get_width); - - ClassDB::bind_method(D_METHOD("set_pixel_size", "pixel_size"), &TextMesh::set_pixel_size); - ClassDB::bind_method(D_METHOD("get_pixel_size"), &TextMesh::get_pixel_size); - - ClassDB::bind_method(D_METHOD("set_offset", "offset"), &TextMesh::set_offset); - ClassDB::bind_method(D_METHOD("get_offset"), &TextMesh::get_offset); - - ClassDB::bind_method(D_METHOD("set_curve_step", "curve_step"), &TextMesh::set_curve_step); - ClassDB::bind_method(D_METHOD("get_curve_step"), &TextMesh::get_curve_step); - - ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &TextMesh::set_text_direction); - ClassDB::bind_method(D_METHOD("get_text_direction"), &TextMesh::get_text_direction); - - ClassDB::bind_method(D_METHOD("set_language", "language"), &TextMesh::set_language); - ClassDB::bind_method(D_METHOD("get_language"), &TextMesh::get_language); - - ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override", "parser"), &TextMesh::set_structured_text_bidi_override); - ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override"), &TextMesh::get_structured_text_bidi_override); - - ClassDB::bind_method(D_METHOD("set_structured_text_bidi_override_options", "args"), &TextMesh::set_structured_text_bidi_override_options); - ClassDB::bind_method(D_METHOD("get_structured_text_bidi_override_options"), &TextMesh::get_structured_text_bidi_override_options); - - ClassDB::bind_method(D_METHOD("set_uppercase", "enable"), &TextMesh::set_uppercase); - ClassDB::bind_method(D_METHOD("is_uppercase"), &TextMesh::is_uppercase); - - ClassDB::bind_method(D_METHOD("_request_update"), &TextMesh::_request_update); - - ADD_GROUP("Text", ""); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "text", PROPERTY_HINT_MULTILINE_TEXT, ""), "set_text", "get_text"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "font", PROPERTY_HINT_RESOURCE_TYPE, "Font"), "set_font", "get_font"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "font_size", PROPERTY_HINT_RANGE, "1,256,1,or_greater,suffix:px"), "set_font_size", "get_font_size"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "horizontal_alignment", PROPERTY_HINT_ENUM, "Left,Center,Right,Fill"), "set_horizontal_alignment", "get_horizontal_alignment"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "vertical_alignment", PROPERTY_HINT_ENUM, "Top,Center,Bottom"), "set_vertical_alignment", "get_vertical_alignment"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uppercase"), "set_uppercase", "is_uppercase"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "line_spacing", PROPERTY_HINT_NONE, "suffix:px"), "set_line_spacing", "get_line_spacing"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Off,Arbitrary,Word,Word (Smart)"), "set_autowrap_mode", "get_autowrap_mode"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "justification_flags", PROPERTY_HINT_FLAGS, "Kashida Justification:1,Word Justification:2,Justify Only After Last Tab:8,Skip Last Line:32,Skip Last Line With Visible Characters:64,Do Not Skip Single Line:128"), "set_justification_flags", "get_justification_flags"); - - ADD_GROUP("Mesh", ""); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "pixel_size", PROPERTY_HINT_RANGE, "0.0001,128,0.0001,suffix:m"), "set_pixel_size", "get_pixel_size"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "curve_step", PROPERTY_HINT_RANGE, "0.1,10,0.1,suffix:px"), "set_curve_step", "get_curve_step"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "depth", PROPERTY_HINT_RANGE, "0.0,100.0,0.001,or_greater,suffix:m"), "set_depth", "get_depth"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "width", PROPERTY_HINT_NONE, "suffix:px"), "set_width", "get_width"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset", PROPERTY_HINT_NONE, "suffix:px"), "set_offset", "get_offset"); - - ADD_GROUP("BiDi", ""); - ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left"), "set_text_direction", "get_text_direction"); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override"); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options"); -} - -void TextMesh::_notification(int p_what) { - switch (p_what) { - case MainLoop::NOTIFICATION_TRANSLATION_CHANGED: { - String new_text = tr(text); - if (new_text == xl_text) { - return; // Nothing new. - } - xl_text = new_text; - dirty_text = true; - _request_update(); - } break; - } -} - -TextMesh::TextMesh() { - primitive_type = PRIMITIVE_TRIANGLES; - text_rid = TS->create_shaped_text(); -} - -TextMesh::~TextMesh() { - for (int i = 0; i < lines_rid.size(); i++) { - TS->free_rid(lines_rid[i]); - } - lines_rid.clear(); - - TS->free_rid(text_rid); -} - -void TextMesh::set_horizontal_alignment(HorizontalAlignment p_alignment) { - ERR_FAIL_INDEX((int)p_alignment, 4); - if (horizontal_alignment != p_alignment) { - if (horizontal_alignment == HORIZONTAL_ALIGNMENT_FILL || p_alignment == HORIZONTAL_ALIGNMENT_FILL) { - dirty_lines = true; - } - horizontal_alignment = p_alignment; - _request_update(); - } -} - -HorizontalAlignment TextMesh::get_horizontal_alignment() const { - return horizontal_alignment; -} - -void TextMesh::set_vertical_alignment(VerticalAlignment p_alignment) { - ERR_FAIL_INDEX((int)p_alignment, 4); - if (vertical_alignment != p_alignment) { - vertical_alignment = p_alignment; - _request_update(); - } -} - -VerticalAlignment TextMesh::get_vertical_alignment() const { - return vertical_alignment; -} - -void TextMesh::set_text(const String &p_string) { - if (text != p_string) { - text = p_string; - xl_text = tr(text); - dirty_text = true; - _request_update(); - } -} - -String TextMesh::get_text() const { - return text; -} - -void TextMesh::_font_changed() { - dirty_font = true; - dirty_cache = true; - call_deferred(SNAME("_request_update")); -} - -void TextMesh::set_font(const Ref &p_font) { - if (font_override != p_font) { - const Callable font_changed = callable_mp(this, &TextMesh::_font_changed); - - if (font_override.is_valid()) { - font_override->disconnect_changed(font_changed); - } - font_override = p_font; - dirty_font = true; - dirty_cache = true; - if (font_override.is_valid()) { - font_override->connect_changed(font_changed); - } - _request_update(); - } -} - -Ref TextMesh::get_font() const { - return font_override; -} - -Ref TextMesh::_get_font_or_default() const { - if (font_override.is_valid()) { - return font_override; - } - - StringName theme_name = "font"; - List theme_types; - ThemeDB::get_singleton()->get_native_type_dependencies(get_class_name(), &theme_types); - - ThemeContext *global_context = ThemeDB::get_singleton()->get_default_theme_context(); - for (const Ref &theme : global_context->get_themes()) { - if (theme.is_null()) { - continue; - } - - for (const StringName &E : theme_types) { - if (theme->has_font(theme_name, E)) { - return theme->get_font(theme_name, E); - } - } - } - - return global_context->get_fallback_theme()->get_font(theme_name, StringName()); -} - -void TextMesh::set_font_size(int p_size) { - if (font_size != p_size) { - font_size = CLAMP(p_size, 1, 127); - dirty_font = true; - dirty_cache = true; - _request_update(); - } -} - -int TextMesh::get_font_size() const { - return font_size; -} - -void TextMesh::set_line_spacing(float p_line_spacing) { - if (line_spacing != p_line_spacing) { - line_spacing = p_line_spacing; - _request_update(); - } -} - -float TextMesh::get_line_spacing() const { - return line_spacing; -} - -void TextMesh::set_autowrap_mode(TextServer::AutowrapMode p_mode) { - if (autowrap_mode != p_mode) { - autowrap_mode = p_mode; - dirty_lines = true; - _request_update(); - } -} - -TextServer::AutowrapMode TextMesh::get_autowrap_mode() const { - return autowrap_mode; -} - -void TextMesh::set_justification_flags(BitField p_flags) { - if (jst_flags != p_flags) { - jst_flags = p_flags; - dirty_lines = true; - _request_update(); - } -} - -BitField TextMesh::get_justification_flags() const { - return jst_flags; -} - -void TextMesh::set_depth(real_t p_depth) { - if (depth != p_depth) { - depth = MAX(p_depth, 0.0); - _request_update(); - } -} - -real_t TextMesh::get_depth() const { - return depth; -} - -void TextMesh::set_width(real_t p_width) { - if (width != p_width) { - width = p_width; - dirty_lines = true; - _request_update(); - } -} - -real_t TextMesh::get_width() const { - return width; -} - -void TextMesh::set_pixel_size(real_t p_amount) { - if (pixel_size != p_amount) { - pixel_size = CLAMP(p_amount, 0.0001, 128.0); - dirty_cache = true; - _request_update(); - } -} - -real_t TextMesh::get_pixel_size() const { - return pixel_size; -} - -void TextMesh::set_offset(const Point2 &p_offset) { - if (lbl_offset != p_offset) { - lbl_offset = p_offset; - _request_update(); - } -} - -Point2 TextMesh::get_offset() const { - return lbl_offset; -} - -void TextMesh::set_curve_step(real_t p_step) { - if (curve_step != p_step) { - curve_step = CLAMP(p_step, 0.1, 10.0); - dirty_cache = true; - _request_update(); - } -} - -real_t TextMesh::get_curve_step() const { - return curve_step; -} - -void TextMesh::set_text_direction(TextServer::Direction p_text_direction) { - ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3); - if (text_direction != p_text_direction) { - text_direction = p_text_direction; - dirty_text = true; - _request_update(); - } -} - -TextServer::Direction TextMesh::get_text_direction() const { - return text_direction; -} - -void TextMesh::set_language(const String &p_language) { - if (language != p_language) { - language = p_language; - dirty_text = true; - _request_update(); - } -} - -String TextMesh::get_language() const { - return language; -} - -void TextMesh::set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser) { - if (st_parser != p_parser) { - st_parser = p_parser; - dirty_text = true; - _request_update(); - } -} - -TextServer::StructuredTextParser TextMesh::get_structured_text_bidi_override() const { - return st_parser; -} - -void TextMesh::set_structured_text_bidi_override_options(Array p_args) { - if (st_args != p_args) { - st_args = p_args; - dirty_text = true; - _request_update(); - } -} - -Array TextMesh::get_structured_text_bidi_override_options() const { - return st_args; -} - -void TextMesh::set_uppercase(bool p_uppercase) { - if (uppercase != p_uppercase) { - uppercase = p_uppercase; - dirty_text = true; - _request_update(); - } -} - -bool TextMesh::is_uppercase() const { - return uppercase; -} diff --git a/scene/resources/primitive_meshes.h b/scene/resources/primitive_meshes.h deleted file mode 100644 index 5b788b726e..0000000000 --- a/scene/resources/primitive_meshes.h +++ /dev/null @@ -1,689 +0,0 @@ -/**************************************************************************/ -/* primitive_meshes.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 PRIMITIVE_MESHES_H -#define PRIMITIVE_MESHES_H - -#include "scene/resources/font.h" -#include "scene/resources/mesh.h" -#include "servers/text_server.h" - -///@TODO probably should change a few integers to unsigned integers... - -/** - Base class for all the classes in this file, handles a number of code functions that are shared among all meshes. - This class is set apart that it assumes a single surface is always generated for our mesh. -*/ - -class PrimitiveMesh : public Mesh { - GDCLASS(PrimitiveMesh, Mesh); - -private: - RID mesh; - mutable AABB aabb; - AABB custom_aabb; - - mutable int array_len = 0; - mutable int index_array_len = 0; - - Ref material; - bool flip_faces = false; - - bool add_uv2 = false; - float uv2_padding = 2.0; - - // make sure we do an update after we've finished constructing our object - mutable bool pending_request = true; - void _update() const; - -protected: - // assume primitive triangles as the type, correct for all but one and it will change this :) - Mesh::PrimitiveType primitive_type = Mesh::PRIMITIVE_TRIANGLES; - - static void _bind_methods(); - - virtual void _create_mesh_array(Array &p_arr) const {} - void _request_update(); - GDVIRTUAL0RC(Array, _create_mesh_array) - - Vector2 get_uv2_scale(Vector2 p_margin_scale = Vector2(1.0, 1.0)) const; - float get_lightmap_texel_size() const; - virtual void _update_lightmap_size(){}; - -public: - virtual int get_surface_count() const override; - virtual int surface_get_array_len(int p_idx) const override; - virtual int surface_get_array_index_len(int p_idx) const override; - virtual Array surface_get_arrays(int p_surface) const override; - virtual TypedArray surface_get_blend_shape_arrays(int p_surface) const override; - virtual Dictionary surface_get_lods(int p_surface) const override; - virtual BitField surface_get_format(int p_idx) const override; - virtual Mesh::PrimitiveType surface_get_primitive_type(int p_idx) const override; - virtual void surface_set_material(int p_idx, const Ref &p_material) override; - virtual Ref surface_get_material(int p_idx) const override; - virtual int get_blend_shape_count() const override; - virtual StringName get_blend_shape_name(int p_index) const override; - virtual void set_blend_shape_name(int p_index, const StringName &p_name) override; - virtual AABB get_aabb() const override; - virtual RID get_rid() const override; - - void set_material(const Ref &p_material); - Ref get_material() const; - - Array get_mesh_arrays() const; - - void set_custom_aabb(const AABB &p_custom); - AABB get_custom_aabb() const; - - void set_flip_faces(bool p_enable); - bool get_flip_faces() const; - - void set_add_uv2(bool p_enable); - bool get_add_uv2() const { return add_uv2; } - - void set_uv2_padding(float p_padding); - float get_uv2_padding() const { return uv2_padding; } - - PrimitiveMesh(); - ~PrimitiveMesh(); -}; - -/** - Mesh for a simple capsule -*/ -class CapsuleMesh : public PrimitiveMesh { - GDCLASS(CapsuleMesh, PrimitiveMesh); - -private: - float radius = 0.5; - float height = 2.0; - int radial_segments = 64; - int rings = 8; - -protected: - static void _bind_methods(); - virtual void _create_mesh_array(Array &p_arr) const override; - - virtual void _update_lightmap_size() override; - -public: - static void create_mesh_array(Array &p_arr, float radius, float height, int radial_segments = 64, int rings = 8, bool p_add_uv2 = false, const float p_uv2_padding = 1.0); - - void set_radius(const float p_radius); - float get_radius() const; - - void set_height(const float p_height); - float get_height() const; - - void set_radial_segments(const int p_segments); - int get_radial_segments() const; - - void set_rings(const int p_rings); - int get_rings() const; - - CapsuleMesh(); -}; - -/** - A box -*/ -class BoxMesh : public PrimitiveMesh { - GDCLASS(BoxMesh, PrimitiveMesh); - -private: - Vector3 size = Vector3(1, 1, 1); - int subdivide_w = 0; - int subdivide_h = 0; - int subdivide_d = 0; - -protected: - static void _bind_methods(); - virtual void _create_mesh_array(Array &p_arr) const override; - - virtual void _update_lightmap_size() override; - -public: - static void create_mesh_array(Array &p_arr, Vector3 size, int subdivide_w = 0, int subdivide_h = 0, int subdivide_d = 0, bool p_add_uv2 = false, const float p_uv2_padding = 1.0); - - void set_size(const Vector3 &p_size); - Vector3 get_size() const; - - void set_subdivide_width(const int p_divisions); - int get_subdivide_width() const; - - void set_subdivide_height(const int p_divisions); - int get_subdivide_height() const; - - void set_subdivide_depth(const int p_divisions); - int get_subdivide_depth() const; - - BoxMesh(); -}; - -/** - A cylinder -*/ - -class CylinderMesh : public PrimitiveMesh { - GDCLASS(CylinderMesh, PrimitiveMesh); - -private: - float top_radius = 0.5; - float bottom_radius = 0.5; - float height = 2.0; - int radial_segments = 64; - int rings = 4; - bool cap_top = true; - bool cap_bottom = true; - -protected: - static void _bind_methods(); - virtual void _create_mesh_array(Array &p_arr) const override; - - virtual void _update_lightmap_size() override; - -public: - static void create_mesh_array(Array &p_arr, float top_radius, float bottom_radius, float height, int radial_segments = 64, int rings = 4, bool cap_top = true, bool cap_bottom = true, bool p_add_uv2 = false, const float p_uv2_padding = 1.0); - - void set_top_radius(const float p_radius); - float get_top_radius() const; - - void set_bottom_radius(const float p_radius); - float get_bottom_radius() const; - - void set_height(const float p_height); - float get_height() const; - - void set_radial_segments(const int p_segments); - int get_radial_segments() const; - - void set_rings(const int p_rings); - int get_rings() const; - - void set_cap_top(bool p_cap_top); - bool is_cap_top() const; - - void set_cap_bottom(bool p_cap_bottom); - bool is_cap_bottom() const; - - CylinderMesh(); -}; - -/* - A flat rectangle, can be used as quad or heightmap. -*/ -class PlaneMesh : public PrimitiveMesh { - GDCLASS(PlaneMesh, PrimitiveMesh); - -public: - enum Orientation { - FACE_X, - FACE_Y, - FACE_Z, - }; - -private: - Size2 size = Size2(2.0, 2.0); - int subdivide_w = 0; - int subdivide_d = 0; - Vector3 center_offset; - Orientation orientation = FACE_Y; - -protected: - static void _bind_methods(); - virtual void _create_mesh_array(Array &p_arr) const override; - - virtual void _update_lightmap_size() override; - -public: - void set_size(const Size2 &p_size); - Size2 get_size() const; - - void set_subdivide_width(const int p_divisions); - int get_subdivide_width() const; - - void set_subdivide_depth(const int p_divisions); - int get_subdivide_depth() const; - - void set_center_offset(const Vector3 p_offset); - Vector3 get_center_offset() const; - - void set_orientation(const Orientation p_orientation); - Orientation get_orientation() const; - - PlaneMesh(); -}; - -VARIANT_ENUM_CAST(PlaneMesh::Orientation) - -/* - A flat rectangle, inherits from PlaneMesh but defaults to facing the Z-plane. -*/ -class QuadMesh : public PlaneMesh { - GDCLASS(QuadMesh, PlaneMesh); - -public: - QuadMesh() { - set_orientation(FACE_Z); - set_size(Size2(1, 1)); - } -}; - -/** - A prism shapen, handy for ramps, triangles, etc. -*/ -class PrismMesh : public PrimitiveMesh { - GDCLASS(PrismMesh, PrimitiveMesh); - -private: - float left_to_right = 0.5; - Vector3 size = Vector3(1.0, 1.0, 1.0); - int subdivide_w = 0; - int subdivide_h = 0; - int subdivide_d = 0; - -protected: - static void _bind_methods(); - virtual void _create_mesh_array(Array &p_arr) const override; - - virtual void _update_lightmap_size() override; - -public: - void set_left_to_right(const float p_left_to_right); - float get_left_to_right() const; - - void set_size(const Vector3 &p_size); - Vector3 get_size() const; - - void set_subdivide_width(const int p_divisions); - int get_subdivide_width() const; - - void set_subdivide_height(const int p_divisions); - int get_subdivide_height() const; - - void set_subdivide_depth(const int p_divisions); - int get_subdivide_depth() const; - - PrismMesh(); -}; - -/** - A sphere.. -*/ -class SphereMesh : public PrimitiveMesh { - GDCLASS(SphereMesh, PrimitiveMesh); - -private: - float radius = 0.5; - float height = 1.0; - int radial_segments = 64; - int rings = 32; - bool is_hemisphere = false; - -protected: - static void _bind_methods(); - virtual void _create_mesh_array(Array &p_arr) const override; - - virtual void _update_lightmap_size() override; - -public: - static void create_mesh_array(Array &p_arr, float radius, float height, int radial_segments = 64, int rings = 32, bool is_hemisphere = false, bool p_add_uv2 = false, const float p_uv2_padding = 1.0); - - void set_radius(const float p_radius); - float get_radius() const; - - void set_height(const float p_height); - float get_height() const; - - void set_radial_segments(const int p_radial_segments); - int get_radial_segments() const; - - void set_rings(const int p_rings); - int get_rings() const; - - void set_is_hemisphere(const bool p_is_hemisphere); - bool get_is_hemisphere() const; - - SphereMesh(); -}; - -/** - Big donut -*/ -class TorusMesh : public PrimitiveMesh { - GDCLASS(TorusMesh, PrimitiveMesh); - -private: - float inner_radius = 0.5; - float outer_radius = 1.0; - int rings = 64; - int ring_segments = 32; - -protected: - static void _bind_methods(); - virtual void _create_mesh_array(Array &p_arr) const override; - - virtual void _update_lightmap_size() override; - -public: - void set_inner_radius(const float p_inner_radius); - float get_inner_radius() const; - - void set_outer_radius(const float p_outer_radius); - float get_outer_radius() const; - - void set_rings(const int p_rings); - int get_rings() const; - - void set_ring_segments(const int p_ring_segments); - int get_ring_segments() const; - - TorusMesh(); -}; - -/** - A single point for use in particle systems -*/ - -class PointMesh : public PrimitiveMesh { - GDCLASS(PointMesh, PrimitiveMesh) - -protected: - virtual void _create_mesh_array(Array &p_arr) const override; - -public: - PointMesh(); -}; - -class TubeTrailMesh : public PrimitiveMesh { - GDCLASS(TubeTrailMesh, PrimitiveMesh); - -private: - float radius = 0.5; - int radial_steps = 8; - int sections = 5; - float section_length = 0.2; - int section_rings = 3; - bool cap_top = true; - bool cap_bottom = true; - - Ref curve; - - void _curve_changed(); - -protected: - static void _bind_methods(); - virtual void _create_mesh_array(Array &p_arr) const override; - -public: - void set_radius(const float p_radius); - float get_radius() const; - - void set_radial_steps(const int p_radial_steps); - int get_radial_steps() const; - - void set_sections(const int p_sections); - int get_sections() const; - - void set_section_length(float p_sectionlength); - float get_section_length() const; - - void set_section_rings(const int p_section_rings); - int get_section_rings() const; - - void set_cap_top(bool p_cap_top); - bool is_cap_top() const; - - void set_cap_bottom(bool p_cap_bottom); - bool is_cap_bottom() const; - - void set_curve(const Ref &p_curve); - Ref get_curve() const; - - virtual int get_builtin_bind_pose_count() const override; - virtual Transform3D get_builtin_bind_pose(int p_index) const override; - - TubeTrailMesh(); -}; - -class RibbonTrailMesh : public PrimitiveMesh { - GDCLASS(RibbonTrailMesh, PrimitiveMesh); - -public: - enum Shape { - SHAPE_FLAT, - SHAPE_CROSS - }; - -private: - float size = 1.0; - int sections = 5; - float section_length = 0.2; - int section_segments = 3; - - Shape shape = SHAPE_CROSS; - - Ref curve; - - void _curve_changed(); - -protected: - static void _bind_methods(); - virtual void _create_mesh_array(Array &p_arr) const override; - -public: - void set_shape(Shape p_shape); - Shape get_shape() const; - - void set_size(const float p_size); - float get_size() const; - - void set_sections(const int p_sections); - int get_sections() const; - - void set_section_length(float p_sectionlength); - float get_section_length() const; - - void set_section_segments(const int p_section_segments); - int get_section_segments() const; - - void set_curve(const Ref &p_curve); - Ref get_curve() const; - - virtual int get_builtin_bind_pose_count() const override; - virtual Transform3D get_builtin_bind_pose(int p_index) const override; - - RibbonTrailMesh(); -}; - -/** - Text... -*/ - -class TextMesh : public PrimitiveMesh { - GDCLASS(TextMesh, PrimitiveMesh); - -private: - struct ContourPoint { - Vector2 point; - bool sharp = false; - - ContourPoint(){}; - ContourPoint(const Vector2 &p_pt, bool p_sharp) { - point = p_pt; - sharp = p_sharp; - }; - }; - - struct ContourInfo { - real_t length = 0.0; - bool ccw = true; - ContourInfo(){}; - ContourInfo(real_t p_len, bool p_ccw) { - length = p_len; - ccw = p_ccw; - } - }; - - struct GlyphMeshKey { - uint64_t font_id; - uint32_t gl_id; - - bool operator==(const GlyphMeshKey &p_b) const { - return (font_id == p_b.font_id) && (gl_id == p_b.gl_id); - } - - GlyphMeshKey(uint64_t p_font_id, uint32_t p_gl_id) { - font_id = p_font_id; - gl_id = p_gl_id; - } - }; - - struct GlyphMeshKeyHasher { - _FORCE_INLINE_ static uint32_t hash(const GlyphMeshKey &p_a) { - return hash_murmur3_buffer(&p_a, sizeof(GlyphMeshKey)); - } - }; - - struct GlyphMeshData { - Vector triangles; - Vector> contours; - Vector contours_info; - Vector2 min_p = Vector2(INFINITY, INFINITY); - Vector2 max_p = Vector2(-INFINITY, -INFINITY); - }; - mutable HashMap cache; - - RID text_rid; - mutable Vector lines_rid; - - String text; - String xl_text; - - int font_size = 16; - Ref font_override; - - TextServer::AutowrapMode autowrap_mode = TextServer::AUTOWRAP_OFF; - BitField jst_flags = TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_SKIP_LAST_LINE | TextServer::JUSTIFICATION_DO_NOT_SKIP_SINGLE_LINE; - float width = 500.0; - float line_spacing = 0.f; - Point2 lbl_offset; - - HorizontalAlignment horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER; - VerticalAlignment vertical_alignment = VERTICAL_ALIGNMENT_CENTER; - bool uppercase = false; - String language; - TextServer::Direction text_direction = TextServer::DIRECTION_AUTO; - TextServer::StructuredTextParser st_parser = TextServer::STRUCTURED_TEXT_DEFAULT; - Array st_args; - - real_t depth = 0.05; - real_t pixel_size = 0.01; - real_t curve_step = 0.5; - - mutable bool dirty_lines = true; - mutable bool dirty_text = true; - mutable bool dirty_font = true; - mutable bool dirty_cache = true; - - void _generate_glyph_mesh_data(const GlyphMeshKey &p_key, const Glyph &p_glyph) const; - void _font_changed(); - -protected: - static void _bind_methods(); - void _notification(int p_what); - - virtual void _create_mesh_array(Array &p_arr) const override; - -public: - GDVIRTUAL2RC(TypedArray, _structured_text_parser, Array, String) - - TextMesh(); - ~TextMesh(); - - void set_horizontal_alignment(HorizontalAlignment p_alignment); - HorizontalAlignment get_horizontal_alignment() const; - - void set_vertical_alignment(VerticalAlignment p_alignment); - VerticalAlignment get_vertical_alignment() const; - - void set_text(const String &p_string); - String get_text() const; - - void set_font(const Ref &p_font); - Ref get_font() const; - Ref _get_font_or_default() const; - - void set_font_size(int p_size); - int get_font_size() const; - - void set_line_spacing(float p_size); - float get_line_spacing() const; - - void set_autowrap_mode(TextServer::AutowrapMode p_mode); - TextServer::AutowrapMode get_autowrap_mode() const; - - void set_justification_flags(BitField p_flags); - BitField get_justification_flags() const; - - void set_text_direction(TextServer::Direction p_text_direction); - TextServer::Direction get_text_direction() const; - - void set_language(const String &p_language); - String get_language() const; - - void set_structured_text_bidi_override(TextServer::StructuredTextParser p_parser); - TextServer::StructuredTextParser get_structured_text_bidi_override() const; - - void set_structured_text_bidi_override_options(Array p_args); - Array get_structured_text_bidi_override_options() const; - - void set_uppercase(bool p_uppercase); - bool is_uppercase() const; - - void set_width(real_t p_width); - real_t get_width() const; - - void set_depth(real_t p_depth); - real_t get_depth() const; - - void set_curve_step(real_t p_step); - real_t get_curve_step() const; - - void set_pixel_size(real_t p_amount); - real_t get_pixel_size() const; - - void set_offset(const Point2 &p_offset); - Point2 get_offset() const; -}; - -VARIANT_ENUM_CAST(RibbonTrailMesh::Shape) - -#endif // PRIMITIVE_MESHES_H diff --git a/scene/resources/separation_ray_shape_3d.cpp b/scene/resources/separation_ray_shape_3d.cpp deleted file mode 100644 index 07e93b8b79..0000000000 --- a/scene/resources/separation_ray_shape_3d.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/**************************************************************************/ -/* separation_ray_shape_3d.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 "separation_ray_shape_3d.h" - -#include "servers/physics_server_3d.h" - -Vector SeparationRayShape3D::get_debug_mesh_lines() const { - Vector points = { - Vector3(), - Vector3(0, 0, get_length()) - }; - - return points; -} - -real_t SeparationRayShape3D::get_enclosing_radius() const { - return length; -} - -void SeparationRayShape3D::_update_shape() { - Dictionary d; - d["length"] = length; - d["slide_on_slope"] = slide_on_slope; - PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), d); - Shape3D::_update_shape(); -} - -void SeparationRayShape3D::set_length(float p_length) { - length = p_length; - _update_shape(); - emit_changed(); -} - -float SeparationRayShape3D::get_length() const { - return length; -} - -void SeparationRayShape3D::set_slide_on_slope(bool p_active) { - slide_on_slope = p_active; - _update_shape(); - emit_changed(); -} - -bool SeparationRayShape3D::get_slide_on_slope() const { - return slide_on_slope; -} - -void SeparationRayShape3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_length", "length"), &SeparationRayShape3D::set_length); - ClassDB::bind_method(D_METHOD("get_length"), &SeparationRayShape3D::get_length); - - ClassDB::bind_method(D_METHOD("set_slide_on_slope", "active"), &SeparationRayShape3D::set_slide_on_slope); - ClassDB::bind_method(D_METHOD("get_slide_on_slope"), &SeparationRayShape3D::get_slide_on_slope); - - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "length", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_length", "get_length"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "slide_on_slope"), "set_slide_on_slope", "get_slide_on_slope"); -} - -SeparationRayShape3D::SeparationRayShape3D() : - Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_SEPARATION_RAY)) { - /* Code copied from setters to prevent the use of uninitialized variables */ - _update_shape(); - emit_changed(); -} diff --git a/scene/resources/separation_ray_shape_3d.h b/scene/resources/separation_ray_shape_3d.h deleted file mode 100644 index bf42aba77f..0000000000 --- a/scene/resources/separation_ray_shape_3d.h +++ /dev/null @@ -1,58 +0,0 @@ -/**************************************************************************/ -/* separation_ray_shape_3d.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 SEPARATION_RAY_SHAPE_3D_H -#define SEPARATION_RAY_SHAPE_3D_H - -#include "scene/resources/shape_3d.h" - -class SeparationRayShape3D : public Shape3D { - GDCLASS(SeparationRayShape3D, Shape3D); - float length = 1.0; - bool slide_on_slope = false; - -protected: - static void _bind_methods(); - virtual void _update_shape() override; - -public: - void set_length(float p_length); - float get_length() const; - - void set_slide_on_slope(bool p_active); - bool get_slide_on_slope() const; - - virtual Vector get_debug_mesh_lines() const override; - virtual real_t get_enclosing_radius() const override; - - SeparationRayShape3D(); -}; - -#endif // SEPARATION_RAY_SHAPE_3D_H diff --git a/scene/resources/shape_3d.cpp b/scene/resources/shape_3d.cpp deleted file mode 100644 index 5a79392ba5..0000000000 --- a/scene/resources/shape_3d.cpp +++ /dev/null @@ -1,133 +0,0 @@ -/**************************************************************************/ -/* shape_3d.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 "shape_3d.h" - -#include "core/os/os.h" -#include "scene/main/scene_tree.h" -#include "scene/resources/mesh.h" -#include "servers/physics_server_3d.h" - -void Shape3D::add_vertices_to_array(Vector &array, const Transform3D &p_xform) { - Vector toadd = get_debug_mesh_lines(); - - if (toadd.size()) { - int base = array.size(); - array.resize(base + toadd.size()); - Vector3 *w = array.ptrw(); - for (int i = 0; i < toadd.size(); i++) { - w[i + base] = p_xform.xform(toadd[i]); - } - } -} - -void Shape3D::set_custom_solver_bias(real_t p_bias) { - custom_bias = p_bias; - PhysicsServer3D::get_singleton()->shape_set_custom_solver_bias(shape, custom_bias); -} - -real_t Shape3D::get_custom_solver_bias() const { - return custom_bias; -} - -real_t Shape3D::get_margin() const { - return margin; -} - -void Shape3D::set_margin(real_t p_margin) { - margin = p_margin; - PhysicsServer3D::get_singleton()->shape_set_margin(shape, margin); -} - -Ref Shape3D::get_debug_mesh() { - if (debug_mesh_cache.is_valid()) { - return debug_mesh_cache; - } - - Vector lines = get_debug_mesh_lines(); - - debug_mesh_cache = Ref(memnew(ArrayMesh)); - - if (!lines.is_empty()) { - //make mesh - Vector array; - array.resize(lines.size()); - { - Vector3 *w = array.ptrw(); - for (int i = 0; i < lines.size(); i++) { - w[i] = lines[i]; - } - } - - Array arr; - arr.resize(Mesh::ARRAY_MAX); - arr[Mesh::ARRAY_VERTEX] = array; - - SceneTree *st = Object::cast_to(OS::get_singleton()->get_main_loop()); - - debug_mesh_cache->add_surface_from_arrays(Mesh::PRIMITIVE_LINES, arr); - - if (st) { - debug_mesh_cache->surface_set_material(0, st->get_debug_collision_material()); - } - } - - return debug_mesh_cache; -} - -void Shape3D::_update_shape() { - emit_changed(); - debug_mesh_cache.unref(); -} - -void Shape3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_custom_solver_bias", "bias"), &Shape3D::set_custom_solver_bias); - ClassDB::bind_method(D_METHOD("get_custom_solver_bias"), &Shape3D::get_custom_solver_bias); - - ClassDB::bind_method(D_METHOD("set_margin", "margin"), &Shape3D::set_margin); - ClassDB::bind_method(D_METHOD("get_margin"), &Shape3D::get_margin); - - ClassDB::bind_method(D_METHOD("get_debug_mesh"), &Shape3D::get_debug_mesh); - - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "custom_solver_bias", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_custom_solver_bias", "get_custom_solver_bias"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "margin", PROPERTY_HINT_RANGE, "0,10,0.001,or_greater,suffix:m"), "set_margin", "get_margin"); -} - -Shape3D::Shape3D() { - ERR_PRINT("Default constructor must not be called!"); -} - -Shape3D::Shape3D(RID p_shape) : - shape(p_shape) {} - -Shape3D::~Shape3D() { - ERR_FAIL_NULL(PhysicsServer3D::get_singleton()); - PhysicsServer3D::get_singleton()->free(shape); -} diff --git a/scene/resources/shape_3d.h b/scene/resources/shape_3d.h deleted file mode 100644 index 5e6cdbe421..0000000000 --- a/scene/resources/shape_3d.h +++ /dev/null @@ -1,76 +0,0 @@ -/**************************************************************************/ -/* shape_3d.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 SHAPE_3D_H -#define SHAPE_3D_H - -#include "core/io/resource.h" - -class ArrayMesh; - -class Shape3D : public Resource { - GDCLASS(Shape3D, Resource); - OBJ_SAVE_TYPE(Shape3D); - RES_BASE_EXTENSION("shape"); - RID shape; - real_t custom_bias = 0.0; - real_t margin = 0.04; - - Ref debug_mesh_cache; - -protected: - static void _bind_methods(); - - _FORCE_INLINE_ RID get_shape() const { return shape; } - Shape3D(RID p_shape); - - virtual void _update_shape(); - -public: - virtual RID get_rid() const override { return shape; } - - Ref get_debug_mesh(); - virtual Vector get_debug_mesh_lines() const = 0; // { return Vector(); } - /// Returns the radius of a sphere that fully enclose this shape - virtual real_t get_enclosing_radius() const = 0; - - void add_vertices_to_array(Vector &array, const Transform3D &p_xform); - - void set_custom_solver_bias(real_t p_bias); - real_t get_custom_solver_bias() const; - - real_t get_margin() const; - void set_margin(real_t p_margin); - - Shape3D(); - ~Shape3D(); -}; - -#endif // SHAPE_3D_H diff --git a/scene/resources/skin.cpp b/scene/resources/skin.cpp deleted file mode 100644 index a4fb371ece..0000000000 --- a/scene/resources/skin.cpp +++ /dev/null @@ -1,162 +0,0 @@ -/**************************************************************************/ -/* skin.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 "skin.h" - -void Skin::set_bind_count(int p_size) { - ERR_FAIL_COND(p_size < 0); - binds.resize(p_size); - binds_ptr = binds.ptrw(); - bind_count = p_size; - emit_changed(); -} - -void Skin::add_bind(int p_bone, const Transform3D &p_pose) { - uint32_t index = bind_count; - set_bind_count(bind_count + 1); - set_bind_bone(index, p_bone); - set_bind_pose(index, p_pose); -} - -void Skin::add_named_bind(const String &p_name, const Transform3D &p_pose) { - uint32_t index = bind_count; - set_bind_count(bind_count + 1); - set_bind_name(index, p_name); - set_bind_pose(index, p_pose); -} - -void Skin::set_bind_name(int p_index, const StringName &p_name) { - ERR_FAIL_INDEX(p_index, bind_count); - bool notify_change = (binds_ptr[p_index].name != StringName()) != (p_name != StringName()); - binds_ptr[p_index].name = p_name; - emit_changed(); - if (notify_change) { - notify_property_list_changed(); - } -} - -void Skin::set_bind_bone(int p_index, int p_bone) { - ERR_FAIL_INDEX(p_index, bind_count); - binds_ptr[p_index].bone = p_bone; - emit_changed(); -} - -void Skin::set_bind_pose(int p_index, const Transform3D &p_pose) { - ERR_FAIL_INDEX(p_index, bind_count); - binds_ptr[p_index].pose = p_pose; - emit_changed(); -} - -void Skin::clear_binds() { - binds.clear(); - binds_ptr = nullptr; - bind_count = 0; - emit_changed(); -} - -void Skin::reset_state() { - clear_binds(); -} - -bool Skin::_set(const StringName &p_name, const Variant &p_value) { - String prop_name = p_name; - if (prop_name == "bind_count") { - set_bind_count(p_value); - return true; - } else if (prop_name.begins_with("bind/")) { - int index = prop_name.get_slicec('/', 1).to_int(); - String what = prop_name.get_slicec('/', 2); - if (what == "bone") { - set_bind_bone(index, p_value); - return true; - } else if (what == "name") { - set_bind_name(index, p_value); - return true; - } else if (what == "pose") { - set_bind_pose(index, p_value); - return true; - } - } - return false; -} - -bool Skin::_get(const StringName &p_name, Variant &r_ret) const { - String prop_name = p_name; - if (prop_name == "bind_count") { - r_ret = get_bind_count(); - return true; - } else if (prop_name.begins_with("bind/")) { - int index = prop_name.get_slicec('/', 1).to_int(); - String what = prop_name.get_slicec('/', 2); - if (what == "bone") { - r_ret = get_bind_bone(index); - return true; - } else if (what == "name") { - r_ret = get_bind_name(index); - return true; - } else if (what == "pose") { - r_ret = get_bind_pose(index); - return true; - } - } - return false; -} - -void Skin::_get_property_list(List *p_list) const { - p_list->push_back(PropertyInfo(Variant::INT, PNAME("bind_count"), PROPERTY_HINT_RANGE, "0,16384,1,or_greater")); - for (int i = 0; i < get_bind_count(); i++) { - const String prefix = vformat("%s/%d/", PNAME("bind"), i); - p_list->push_back(PropertyInfo(Variant::STRING_NAME, prefix + PNAME("name"))); - p_list->push_back(PropertyInfo(Variant::INT, prefix + PNAME("bone"), PROPERTY_HINT_RANGE, "0,16384,1,or_greater", get_bind_name(i) != StringName() ? PROPERTY_USAGE_NO_EDITOR : PROPERTY_USAGE_DEFAULT)); - p_list->push_back(PropertyInfo(Variant::TRANSFORM3D, prefix + PNAME("pose"))); - } -} - -void Skin::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_bind_count", "bind_count"), &Skin::set_bind_count); - ClassDB::bind_method(D_METHOD("get_bind_count"), &Skin::get_bind_count); - - ClassDB::bind_method(D_METHOD("add_bind", "bone", "pose"), &Skin::add_bind); - ClassDB::bind_method(D_METHOD("add_named_bind", "name", "pose"), &Skin::add_named_bind); - - ClassDB::bind_method(D_METHOD("set_bind_pose", "bind_index", "pose"), &Skin::set_bind_pose); - ClassDB::bind_method(D_METHOD("get_bind_pose", "bind_index"), &Skin::get_bind_pose); - - ClassDB::bind_method(D_METHOD("set_bind_name", "bind_index", "name"), &Skin::set_bind_name); - ClassDB::bind_method(D_METHOD("get_bind_name", "bind_index"), &Skin::get_bind_name); - - ClassDB::bind_method(D_METHOD("set_bind_bone", "bind_index", "bone"), &Skin::set_bind_bone); - ClassDB::bind_method(D_METHOD("get_bind_bone", "bind_index"), &Skin::get_bind_bone); - - ClassDB::bind_method(D_METHOD("clear_binds"), &Skin::clear_binds); -} - -Skin::Skin() { -} diff --git a/scene/resources/skin.h b/scene/resources/skin.h deleted file mode 100644 index 5df820660c..0000000000 --- a/scene/resources/skin.h +++ /dev/null @@ -1,89 +0,0 @@ -/**************************************************************************/ -/* skin.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 SKIN_H -#define SKIN_H - -#include "core/io/resource.h" - -class Skin : public Resource { - GDCLASS(Skin, Resource) - - struct Bind { - int bone = -1; - StringName name; - Transform3D pose; - }; - - Vector binds; - - Bind *binds_ptr = nullptr; - int bind_count = 0; - -protected: - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List *p_list) const; - - virtual void reset_state() override; - static void _bind_methods(); - -public: - void set_bind_count(int p_size); - inline int get_bind_count() const { return bind_count; } - - void add_bind(int p_bone, const Transform3D &p_pose); - void add_named_bind(const String &p_name, const Transform3D &p_pose); - - void set_bind_bone(int p_index, int p_bone); - void set_bind_pose(int p_index, const Transform3D &p_pose); - void set_bind_name(int p_index, const StringName &p_name); - - inline int get_bind_bone(int p_index) const { - ERR_FAIL_INDEX_V(p_index, bind_count, -1); - return binds_ptr[p_index].bone; - } - - inline StringName get_bind_name(int p_index) const { - ERR_FAIL_INDEX_V(p_index, bind_count, StringName()); - return binds_ptr[p_index].name; - } - - inline Transform3D get_bind_pose(int p_index) const { - ERR_FAIL_INDEX_V(p_index, bind_count, Transform3D()); - return binds_ptr[p_index].pose; - } - - void clear_binds(); - - Skin(); -}; - -#endif // SKIN_H diff --git a/scene/resources/sky_material.cpp b/scene/resources/sky_material.cpp deleted file mode 100644 index 640261d615..0000000000 --- a/scene/resources/sky_material.cpp +++ /dev/null @@ -1,806 +0,0 @@ -/**************************************************************************/ -/* sky_material.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 "sky_material.h" - -#include "core/config/project_settings.h" -#include "core/version.h" - -Mutex ProceduralSkyMaterial::shader_mutex; -RID ProceduralSkyMaterial::shader_cache[2]; - -void ProceduralSkyMaterial::set_sky_top_color(const Color &p_sky_top) { - sky_top_color = p_sky_top; - RS::get_singleton()->material_set_param(_get_material(), "sky_top_color", sky_top_color); -} - -Color ProceduralSkyMaterial::get_sky_top_color() const { - return sky_top_color; -} - -void ProceduralSkyMaterial::set_sky_horizon_color(const Color &p_sky_horizon) { - sky_horizon_color = p_sky_horizon; - RS::get_singleton()->material_set_param(_get_material(), "sky_horizon_color", sky_horizon_color); -} - -Color ProceduralSkyMaterial::get_sky_horizon_color() const { - return sky_horizon_color; -} - -void ProceduralSkyMaterial::set_sky_curve(float p_curve) { - sky_curve = p_curve; - RS::get_singleton()->material_set_param(_get_material(), "sky_curve", sky_curve); -} - -float ProceduralSkyMaterial::get_sky_curve() const { - return sky_curve; -} - -void ProceduralSkyMaterial::set_sky_energy_multiplier(float p_multiplier) { - sky_energy_multiplier = p_multiplier; - RS::get_singleton()->material_set_param(_get_material(), "sky_energy", sky_energy_multiplier); -} - -float ProceduralSkyMaterial::get_sky_energy_multiplier() const { - return sky_energy_multiplier; -} - -void ProceduralSkyMaterial::set_sky_cover(const Ref &p_sky_cover) { - sky_cover = p_sky_cover; - if (p_sky_cover.is_valid()) { - RS::get_singleton()->material_set_param(_get_material(), "sky_cover", p_sky_cover->get_rid()); - } else { - RS::get_singleton()->material_set_param(_get_material(), "sky_cover", Variant()); - } -} - -Ref ProceduralSkyMaterial::get_sky_cover() const { - return sky_cover; -} - -void ProceduralSkyMaterial::set_sky_cover_modulate(const Color &p_sky_cover_modulate) { - sky_cover_modulate = p_sky_cover_modulate; - RS::get_singleton()->material_set_param(_get_material(), "sky_cover_modulate", sky_cover_modulate); -} - -Color ProceduralSkyMaterial::get_sky_cover_modulate() const { - return sky_cover_modulate; -} - -void ProceduralSkyMaterial::set_ground_bottom_color(const Color &p_ground_bottom) { - ground_bottom_color = p_ground_bottom; - RS::get_singleton()->material_set_param(_get_material(), "ground_bottom_color", ground_bottom_color); -} - -Color ProceduralSkyMaterial::get_ground_bottom_color() const { - return ground_bottom_color; -} - -void ProceduralSkyMaterial::set_ground_horizon_color(const Color &p_ground_horizon) { - ground_horizon_color = p_ground_horizon; - RS::get_singleton()->material_set_param(_get_material(), "ground_horizon_color", ground_horizon_color); -} - -Color ProceduralSkyMaterial::get_ground_horizon_color() const { - return ground_horizon_color; -} - -void ProceduralSkyMaterial::set_ground_curve(float p_curve) { - ground_curve = p_curve; - RS::get_singleton()->material_set_param(_get_material(), "ground_curve", ground_curve); -} - -float ProceduralSkyMaterial::get_ground_curve() const { - return ground_curve; -} - -void ProceduralSkyMaterial::set_ground_energy_multiplier(float p_multiplier) { - ground_energy_multiplier = p_multiplier; - RS::get_singleton()->material_set_param(_get_material(), "ground_energy", ground_energy_multiplier); -} - -float ProceduralSkyMaterial::get_ground_energy_multiplier() const { - return ground_energy_multiplier; -} - -void ProceduralSkyMaterial::set_sun_angle_max(float p_angle) { - sun_angle_max = p_angle; - RS::get_singleton()->material_set_param(_get_material(), "sun_angle_max", Math::deg_to_rad(sun_angle_max)); -} - -float ProceduralSkyMaterial::get_sun_angle_max() const { - return sun_angle_max; -} - -void ProceduralSkyMaterial::set_sun_curve(float p_curve) { - sun_curve = p_curve; - RS::get_singleton()->material_set_param(_get_material(), "sun_curve", sun_curve); -} - -float ProceduralSkyMaterial::get_sun_curve() const { - return sun_curve; -} - -void ProceduralSkyMaterial::set_use_debanding(bool p_use_debanding) { - use_debanding = p_use_debanding; - _update_shader(); - // Only set if shader already compiled - if (shader_set) { - RS::get_singleton()->material_set_shader(_get_material(), shader_cache[int(use_debanding)]); - } -} - -bool ProceduralSkyMaterial::get_use_debanding() const { - return use_debanding; -} - -void ProceduralSkyMaterial::set_energy_multiplier(float p_multiplier) { - global_energy_multiplier = p_multiplier; - RS::get_singleton()->material_set_param(_get_material(), "exposure", global_energy_multiplier); -} - -float ProceduralSkyMaterial::get_energy_multiplier() const { - return global_energy_multiplier; -} - -Shader::Mode ProceduralSkyMaterial::get_shader_mode() const { - return Shader::MODE_SKY; -} - -RID ProceduralSkyMaterial::get_rid() const { - _update_shader(); - if (!shader_set) { - RS::get_singleton()->material_set_shader(_get_material(), shader_cache[1 - int(use_debanding)]); - RS::get_singleton()->material_set_shader(_get_material(), shader_cache[int(use_debanding)]); - shader_set = true; - } - return _get_material(); -} - -RID ProceduralSkyMaterial::get_shader_rid() const { - _update_shader(); - return shader_cache[int(use_debanding)]; -} - -void ProceduralSkyMaterial::_validate_property(PropertyInfo &p_property) const { - if ((p_property.name == "sky_luminance" || p_property.name == "ground_luminance") && !GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) { - p_property.usage = PROPERTY_USAGE_NO_EDITOR; - } -} - -void ProceduralSkyMaterial::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_sky_top_color", "color"), &ProceduralSkyMaterial::set_sky_top_color); - ClassDB::bind_method(D_METHOD("get_sky_top_color"), &ProceduralSkyMaterial::get_sky_top_color); - - ClassDB::bind_method(D_METHOD("set_sky_horizon_color", "color"), &ProceduralSkyMaterial::set_sky_horizon_color); - ClassDB::bind_method(D_METHOD("get_sky_horizon_color"), &ProceduralSkyMaterial::get_sky_horizon_color); - - ClassDB::bind_method(D_METHOD("set_sky_curve", "curve"), &ProceduralSkyMaterial::set_sky_curve); - ClassDB::bind_method(D_METHOD("get_sky_curve"), &ProceduralSkyMaterial::get_sky_curve); - - ClassDB::bind_method(D_METHOD("set_sky_energy_multiplier", "multiplier"), &ProceduralSkyMaterial::set_sky_energy_multiplier); - ClassDB::bind_method(D_METHOD("get_sky_energy_multiplier"), &ProceduralSkyMaterial::get_sky_energy_multiplier); - - ClassDB::bind_method(D_METHOD("set_sky_cover", "sky_cover"), &ProceduralSkyMaterial::set_sky_cover); - ClassDB::bind_method(D_METHOD("get_sky_cover"), &ProceduralSkyMaterial::get_sky_cover); - - ClassDB::bind_method(D_METHOD("set_sky_cover_modulate", "color"), &ProceduralSkyMaterial::set_sky_cover_modulate); - ClassDB::bind_method(D_METHOD("get_sky_cover_modulate"), &ProceduralSkyMaterial::get_sky_cover_modulate); - - ClassDB::bind_method(D_METHOD("set_ground_bottom_color", "color"), &ProceduralSkyMaterial::set_ground_bottom_color); - ClassDB::bind_method(D_METHOD("get_ground_bottom_color"), &ProceduralSkyMaterial::get_ground_bottom_color); - - ClassDB::bind_method(D_METHOD("set_ground_horizon_color", "color"), &ProceduralSkyMaterial::set_ground_horizon_color); - ClassDB::bind_method(D_METHOD("get_ground_horizon_color"), &ProceduralSkyMaterial::get_ground_horizon_color); - - ClassDB::bind_method(D_METHOD("set_ground_curve", "curve"), &ProceduralSkyMaterial::set_ground_curve); - ClassDB::bind_method(D_METHOD("get_ground_curve"), &ProceduralSkyMaterial::get_ground_curve); - - ClassDB::bind_method(D_METHOD("set_ground_energy_multiplier", "energy"), &ProceduralSkyMaterial::set_ground_energy_multiplier); - ClassDB::bind_method(D_METHOD("get_ground_energy_multiplier"), &ProceduralSkyMaterial::get_ground_energy_multiplier); - - ClassDB::bind_method(D_METHOD("set_sun_angle_max", "degrees"), &ProceduralSkyMaterial::set_sun_angle_max); - ClassDB::bind_method(D_METHOD("get_sun_angle_max"), &ProceduralSkyMaterial::get_sun_angle_max); - - ClassDB::bind_method(D_METHOD("set_sun_curve", "curve"), &ProceduralSkyMaterial::set_sun_curve); - ClassDB::bind_method(D_METHOD("get_sun_curve"), &ProceduralSkyMaterial::get_sun_curve); - - ClassDB::bind_method(D_METHOD("set_use_debanding", "use_debanding"), &ProceduralSkyMaterial::set_use_debanding); - ClassDB::bind_method(D_METHOD("get_use_debanding"), &ProceduralSkyMaterial::get_use_debanding); - - ClassDB::bind_method(D_METHOD("set_energy_multiplier", "multiplier"), &ProceduralSkyMaterial::set_energy_multiplier); - ClassDB::bind_method(D_METHOD("get_energy_multiplier"), &ProceduralSkyMaterial::get_energy_multiplier); - - ADD_GROUP("Sky", "sky_"); - ADD_PROPERTY(PropertyInfo(Variant::COLOR, "sky_top_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_sky_top_color", "get_sky_top_color"); - ADD_PROPERTY(PropertyInfo(Variant::COLOR, "sky_horizon_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_sky_horizon_color", "get_sky_horizon_color"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sky_curve", PROPERTY_HINT_EXP_EASING), "set_sky_curve", "get_sky_curve"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sky_energy_multiplier", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_sky_energy_multiplier", "get_sky_energy_multiplier"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "sky_cover", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_sky_cover", "get_sky_cover"); - ADD_PROPERTY(PropertyInfo(Variant::COLOR, "sky_cover_modulate"), "set_sky_cover_modulate", "get_sky_cover_modulate"); - - ADD_GROUP("Ground", "ground_"); - ADD_PROPERTY(PropertyInfo(Variant::COLOR, "ground_bottom_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_ground_bottom_color", "get_ground_bottom_color"); - ADD_PROPERTY(PropertyInfo(Variant::COLOR, "ground_horizon_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_ground_horizon_color", "get_ground_horizon_color"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ground_curve", PROPERTY_HINT_EXP_EASING), "set_ground_curve", "get_ground_curve"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "ground_energy_multiplier", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_ground_energy_multiplier", "get_ground_energy_multiplier"); - - ADD_GROUP("Sun", "sun_"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sun_angle_max", PROPERTY_HINT_RANGE, "0,360,0.01,degrees"), "set_sun_angle_max", "get_sun_angle_max"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sun_curve", PROPERTY_HINT_EXP_EASING), "set_sun_curve", "get_sun_curve"); - - ADD_GROUP("", ""); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_debanding"), "set_use_debanding", "get_use_debanding"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "energy_multiplier", PROPERTY_HINT_RANGE, "0,128,0.01"), "set_energy_multiplier", "get_energy_multiplier"); -} - -void ProceduralSkyMaterial::cleanup_shader() { - if (shader_cache[0].is_valid()) { - RS::get_singleton()->free(shader_cache[0]); - RS::get_singleton()->free(shader_cache[1]); - } -} - -void ProceduralSkyMaterial::_update_shader() { - shader_mutex.lock(); - if (shader_cache[0].is_null()) { - for (int i = 0; i < 2; i++) { - shader_cache[i] = RS::get_singleton()->shader_create(); - - // Add a comment to describe the shader origin (useful when converting to ShaderMaterial). - RS::get_singleton()->shader_set_code(shader_cache[i], vformat(R"( -// NOTE: Shader automatically converted from )" VERSION_NAME " " VERSION_FULL_CONFIG R"('s ProceduralSkyMaterial. - -shader_type sky; -%s - -uniform vec4 sky_top_color : source_color = vec4(0.385, 0.454, 0.55, 1.0); -uniform vec4 sky_horizon_color : source_color = vec4(0.646, 0.656, 0.67, 1.0); -uniform float sky_curve : hint_range(0, 1) = 0.15; -uniform float sky_energy = 1.0; // In Lux. -uniform sampler2D sky_cover : filter_linear, source_color, hint_default_black; -uniform vec4 sky_cover_modulate : source_color = vec4(1.0, 1.0, 1.0, 1.0); -uniform vec4 ground_bottom_color : source_color = vec4(0.2, 0.169, 0.133, 1.0); -uniform vec4 ground_horizon_color : source_color = vec4(0.646, 0.656, 0.67, 1.0); -uniform float ground_curve : hint_range(0, 1) = 0.02; -uniform float ground_energy = 1.0; -uniform float sun_angle_max = 30.0; -uniform float sun_curve : hint_range(0, 1) = 0.15; -uniform float exposure : hint_range(0, 128) = 1.0; - -void sky() { - float v_angle = acos(clamp(EYEDIR.y, -1.0, 1.0)); - float c = (1.0 - v_angle / (PI * 0.5)); - vec3 sky = mix(sky_horizon_color.rgb, sky_top_color.rgb, clamp(1.0 - pow(1.0 - c, 1.0 / sky_curve), 0.0, 1.0)); - sky *= sky_energy; - - if (LIGHT0_ENABLED) { - float sun_angle = acos(dot(LIGHT0_DIRECTION, EYEDIR)); - if (sun_angle < LIGHT0_SIZE) { - sky = LIGHT0_COLOR * LIGHT0_ENERGY; - } else if (sun_angle < sun_angle_max) { - float c2 = (sun_angle - LIGHT0_SIZE) / (sun_angle_max - LIGHT0_SIZE); - sky = mix(LIGHT0_COLOR * LIGHT0_ENERGY, sky, clamp(1.0 - pow(1.0 - c2, 1.0 / sun_curve), 0.0, 1.0)); - } - } - - if (LIGHT1_ENABLED) { - float sun_angle = acos(dot(LIGHT1_DIRECTION, EYEDIR)); - if (sun_angle < LIGHT1_SIZE) { - sky = LIGHT1_COLOR * LIGHT1_ENERGY; - } else if (sun_angle < sun_angle_max) { - float c2 = (sun_angle - LIGHT1_SIZE) / (sun_angle_max - LIGHT1_SIZE); - sky = mix(LIGHT1_COLOR * LIGHT1_ENERGY, sky, clamp(1.0 - pow(1.0 - c2, 1.0 / sun_curve), 0.0, 1.0)); - } - } - - if (LIGHT2_ENABLED) { - float sun_angle = acos(dot(LIGHT2_DIRECTION, EYEDIR)); - if (sun_angle < LIGHT2_SIZE) { - sky = LIGHT2_COLOR * LIGHT2_ENERGY; - } else if (sun_angle < sun_angle_max) { - float c2 = (sun_angle - LIGHT2_SIZE) / (sun_angle_max - LIGHT2_SIZE); - sky = mix(LIGHT2_COLOR * LIGHT2_ENERGY, sky, clamp(1.0 - pow(1.0 - c2, 1.0 / sun_curve), 0.0, 1.0)); - } - } - - if (LIGHT3_ENABLED) { - float sun_angle = acos(dot(LIGHT3_DIRECTION, EYEDIR)); - if (sun_angle < LIGHT3_SIZE) { - sky = LIGHT3_COLOR * LIGHT3_ENERGY; - } else if (sun_angle < sun_angle_max) { - float c2 = (sun_angle - LIGHT3_SIZE) / (sun_angle_max - LIGHT3_SIZE); - sky = mix(LIGHT3_COLOR * LIGHT3_ENERGY, sky, clamp(1.0 - pow(1.0 - c2, 1.0 / sun_curve), 0.0, 1.0)); - } - } - - vec4 sky_cover_texture = texture(sky_cover, SKY_COORDS); - sky += (sky_cover_texture.rgb * sky_cover_modulate.rgb) * sky_cover_texture.a * sky_cover_modulate.a * sky_energy; - - c = (v_angle - (PI * 0.5)) / (PI * 0.5); - vec3 ground = mix(ground_horizon_color.rgb, ground_bottom_color.rgb, clamp(1.0 - pow(1.0 - c, 1.0 / ground_curve), 0.0, 1.0)); - ground *= ground_energy; - - COLOR = mix(ground, sky, step(0.0, EYEDIR.y)) * exposure; -} -)", - i ? "render_mode use_debanding;" : "")); - } - } - shader_mutex.unlock(); -} - -ProceduralSkyMaterial::ProceduralSkyMaterial() { - set_sky_top_color(Color(0.385, 0.454, 0.55)); - set_sky_horizon_color(Color(0.6463, 0.6558, 0.6708)); - set_sky_curve(0.15); - set_sky_energy_multiplier(1.0); - set_sky_cover_modulate(Color(1, 1, 1)); - - set_ground_bottom_color(Color(0.2, 0.169, 0.133)); - set_ground_horizon_color(Color(0.6463, 0.6558, 0.6708)); - set_ground_curve(0.02); - set_ground_energy_multiplier(1.0); - - set_sun_angle_max(30.0); - set_sun_curve(0.15); - set_use_debanding(true); - set_energy_multiplier(1.0); -} - -ProceduralSkyMaterial::~ProceduralSkyMaterial() { -} - -///////////////////////////////////////// -/* PanoramaSkyMaterial */ - -void PanoramaSkyMaterial::set_panorama(const Ref &p_panorama) { - panorama = p_panorama; - if (p_panorama.is_valid()) { - RS::get_singleton()->material_set_param(_get_material(), "source_panorama", p_panorama->get_rid()); - } else { - RS::get_singleton()->material_set_param(_get_material(), "source_panorama", Variant()); - } -} - -Ref PanoramaSkyMaterial::get_panorama() const { - return panorama; -} - -void PanoramaSkyMaterial::set_filtering_enabled(bool p_enabled) { - filter = p_enabled; - notify_property_list_changed(); - _update_shader(); - // Only set if shader already compiled - if (shader_set) { - RS::get_singleton()->material_set_shader(_get_material(), shader_cache[int(filter)]); - } -} - -bool PanoramaSkyMaterial::is_filtering_enabled() const { - return filter; -} - -void PanoramaSkyMaterial::set_energy_multiplier(float p_multiplier) { - energy_multiplier = p_multiplier; - RS::get_singleton()->material_set_param(_get_material(), "exposure", energy_multiplier); -} - -float PanoramaSkyMaterial::get_energy_multiplier() const { - return energy_multiplier; -} - -Shader::Mode PanoramaSkyMaterial::get_shader_mode() const { - return Shader::MODE_SKY; -} - -RID PanoramaSkyMaterial::get_rid() const { - _update_shader(); - // Don't compile shaders until first use, then compile both - if (!shader_set) { - RS::get_singleton()->material_set_shader(_get_material(), shader_cache[1 - int(filter)]); - RS::get_singleton()->material_set_shader(_get_material(), shader_cache[int(filter)]); - shader_set = true; - } - return _get_material(); -} - -RID PanoramaSkyMaterial::get_shader_rid() const { - _update_shader(); - return shader_cache[int(filter)]; -} - -void PanoramaSkyMaterial::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_panorama", "texture"), &PanoramaSkyMaterial::set_panorama); - ClassDB::bind_method(D_METHOD("get_panorama"), &PanoramaSkyMaterial::get_panorama); - - ClassDB::bind_method(D_METHOD("set_filtering_enabled", "enabled"), &PanoramaSkyMaterial::set_filtering_enabled); - ClassDB::bind_method(D_METHOD("is_filtering_enabled"), &PanoramaSkyMaterial::is_filtering_enabled); - - ClassDB::bind_method(D_METHOD("set_energy_multiplier", "multiplier"), &PanoramaSkyMaterial::set_energy_multiplier); - ClassDB::bind_method(D_METHOD("get_energy_multiplier"), &PanoramaSkyMaterial::get_energy_multiplier); - - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "panorama", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_panorama", "get_panorama"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "filter"), "set_filtering_enabled", "is_filtering_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "energy_multiplier", PROPERTY_HINT_RANGE, "0,128,0.01"), "set_energy_multiplier", "get_energy_multiplier"); -} - -Mutex PanoramaSkyMaterial::shader_mutex; -RID PanoramaSkyMaterial::shader_cache[2]; - -void PanoramaSkyMaterial::cleanup_shader() { - if (shader_cache[0].is_valid()) { - RS::get_singleton()->free(shader_cache[0]); - RS::get_singleton()->free(shader_cache[1]); - } -} - -void PanoramaSkyMaterial::_update_shader() { - shader_mutex.lock(); - if (shader_cache[0].is_null()) { - for (int i = 0; i < 2; i++) { - shader_cache[i] = RS::get_singleton()->shader_create(); - - // Add a comment to describe the shader origin (useful when converting to ShaderMaterial). - RS::get_singleton()->shader_set_code(shader_cache[i], vformat(R"( -// NOTE: Shader automatically converted from )" VERSION_NAME " " VERSION_FULL_CONFIG R"('s PanoramaSkyMaterial. - -shader_type sky; - -uniform sampler2D source_panorama : %s, source_color, hint_default_black; -uniform float exposure : hint_range(0, 128) = 1.0; - -void sky() { - COLOR = texture(source_panorama, SKY_COORDS).rgb * exposure; -} -)", - i ? "filter_linear" : "filter_nearest")); - } - } - - shader_mutex.unlock(); -} - -PanoramaSkyMaterial::PanoramaSkyMaterial() { - set_energy_multiplier(1.0); -} - -PanoramaSkyMaterial::~PanoramaSkyMaterial() { -} - -////////////////////////////////// -/* PhysicalSkyMaterial */ - -void PhysicalSkyMaterial::set_rayleigh_coefficient(float p_rayleigh) { - rayleigh = p_rayleigh; - RS::get_singleton()->material_set_param(_get_material(), "rayleigh", rayleigh); -} - -float PhysicalSkyMaterial::get_rayleigh_coefficient() const { - return rayleigh; -} - -void PhysicalSkyMaterial::set_rayleigh_color(Color p_rayleigh_color) { - rayleigh_color = p_rayleigh_color; - RS::get_singleton()->material_set_param(_get_material(), "rayleigh_color", rayleigh_color); -} - -Color PhysicalSkyMaterial::get_rayleigh_color() const { - return rayleigh_color; -} - -void PhysicalSkyMaterial::set_mie_coefficient(float p_mie) { - mie = p_mie; - RS::get_singleton()->material_set_param(_get_material(), "mie", mie); -} - -float PhysicalSkyMaterial::get_mie_coefficient() const { - return mie; -} - -void PhysicalSkyMaterial::set_mie_eccentricity(float p_eccentricity) { - mie_eccentricity = p_eccentricity; - RS::get_singleton()->material_set_param(_get_material(), "mie_eccentricity", mie_eccentricity); -} - -float PhysicalSkyMaterial::get_mie_eccentricity() const { - return mie_eccentricity; -} - -void PhysicalSkyMaterial::set_mie_color(Color p_mie_color) { - mie_color = p_mie_color; - RS::get_singleton()->material_set_param(_get_material(), "mie_color", mie_color); -} - -Color PhysicalSkyMaterial::get_mie_color() const { - return mie_color; -} - -void PhysicalSkyMaterial::set_turbidity(float p_turbidity) { - turbidity = p_turbidity; - RS::get_singleton()->material_set_param(_get_material(), "turbidity", turbidity); -} - -float PhysicalSkyMaterial::get_turbidity() const { - return turbidity; -} - -void PhysicalSkyMaterial::set_sun_disk_scale(float p_sun_disk_scale) { - sun_disk_scale = p_sun_disk_scale; - RS::get_singleton()->material_set_param(_get_material(), "sun_disk_scale", sun_disk_scale); -} - -float PhysicalSkyMaterial::get_sun_disk_scale() const { - return sun_disk_scale; -} - -void PhysicalSkyMaterial::set_ground_color(Color p_ground_color) { - ground_color = p_ground_color; - RS::get_singleton()->material_set_param(_get_material(), "ground_color", ground_color); -} - -Color PhysicalSkyMaterial::get_ground_color() const { - return ground_color; -} - -void PhysicalSkyMaterial::set_energy_multiplier(float p_multiplier) { - energy_multiplier = p_multiplier; - RS::get_singleton()->material_set_param(_get_material(), "exposure", energy_multiplier); -} - -float PhysicalSkyMaterial::get_energy_multiplier() const { - return energy_multiplier; -} - -void PhysicalSkyMaterial::set_use_debanding(bool p_use_debanding) { - use_debanding = p_use_debanding; - _update_shader(); - // Only set if shader already compiled - if (shader_set) { - RS::get_singleton()->material_set_shader(_get_material(), shader_cache[int(use_debanding)]); - } -} - -bool PhysicalSkyMaterial::get_use_debanding() const { - return use_debanding; -} - -void PhysicalSkyMaterial::set_night_sky(const Ref &p_night_sky) { - night_sky = p_night_sky; - if (p_night_sky.is_valid()) { - RS::get_singleton()->material_set_param(_get_material(), "night_sky", p_night_sky->get_rid()); - } else { - RS::get_singleton()->material_set_param(_get_material(), "night_sky", Variant()); - } -} - -Ref PhysicalSkyMaterial::get_night_sky() const { - return night_sky; -} - -Shader::Mode PhysicalSkyMaterial::get_shader_mode() const { - return Shader::MODE_SKY; -} - -RID PhysicalSkyMaterial::get_rid() const { - _update_shader(); - if (!shader_set) { - RS::get_singleton()->material_set_shader(_get_material(), shader_cache[1 - int(use_debanding)]); - RS::get_singleton()->material_set_shader(_get_material(), shader_cache[int(use_debanding)]); - shader_set = true; - } - return _get_material(); -} - -RID PhysicalSkyMaterial::get_shader_rid() const { - _update_shader(); - return shader_cache[int(use_debanding)]; -} - -void PhysicalSkyMaterial::_validate_property(PropertyInfo &p_property) const { - if (p_property.name == "exposure_value" && !GLOBAL_GET("rendering/lights_and_shadows/use_physical_light_units")) { - p_property.usage = PROPERTY_USAGE_NO_EDITOR; - } -} - -Mutex PhysicalSkyMaterial::shader_mutex; -RID PhysicalSkyMaterial::shader_cache[2]; - -void PhysicalSkyMaterial::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_rayleigh_coefficient", "rayleigh"), &PhysicalSkyMaterial::set_rayleigh_coefficient); - ClassDB::bind_method(D_METHOD("get_rayleigh_coefficient"), &PhysicalSkyMaterial::get_rayleigh_coefficient); - - ClassDB::bind_method(D_METHOD("set_rayleigh_color", "color"), &PhysicalSkyMaterial::set_rayleigh_color); - ClassDB::bind_method(D_METHOD("get_rayleigh_color"), &PhysicalSkyMaterial::get_rayleigh_color); - - ClassDB::bind_method(D_METHOD("set_mie_coefficient", "mie"), &PhysicalSkyMaterial::set_mie_coefficient); - ClassDB::bind_method(D_METHOD("get_mie_coefficient"), &PhysicalSkyMaterial::get_mie_coefficient); - - ClassDB::bind_method(D_METHOD("set_mie_eccentricity", "eccentricity"), &PhysicalSkyMaterial::set_mie_eccentricity); - ClassDB::bind_method(D_METHOD("get_mie_eccentricity"), &PhysicalSkyMaterial::get_mie_eccentricity); - - ClassDB::bind_method(D_METHOD("set_mie_color", "color"), &PhysicalSkyMaterial::set_mie_color); - ClassDB::bind_method(D_METHOD("get_mie_color"), &PhysicalSkyMaterial::get_mie_color); - - ClassDB::bind_method(D_METHOD("set_turbidity", "turbidity"), &PhysicalSkyMaterial::set_turbidity); - ClassDB::bind_method(D_METHOD("get_turbidity"), &PhysicalSkyMaterial::get_turbidity); - - ClassDB::bind_method(D_METHOD("set_sun_disk_scale", "scale"), &PhysicalSkyMaterial::set_sun_disk_scale); - ClassDB::bind_method(D_METHOD("get_sun_disk_scale"), &PhysicalSkyMaterial::get_sun_disk_scale); - - ClassDB::bind_method(D_METHOD("set_ground_color", "color"), &PhysicalSkyMaterial::set_ground_color); - ClassDB::bind_method(D_METHOD("get_ground_color"), &PhysicalSkyMaterial::get_ground_color); - - ClassDB::bind_method(D_METHOD("set_energy_multiplier", "multiplier"), &PhysicalSkyMaterial::set_energy_multiplier); - ClassDB::bind_method(D_METHOD("get_energy_multiplier"), &PhysicalSkyMaterial::get_energy_multiplier); - - ClassDB::bind_method(D_METHOD("set_use_debanding", "use_debanding"), &PhysicalSkyMaterial::set_use_debanding); - ClassDB::bind_method(D_METHOD("get_use_debanding"), &PhysicalSkyMaterial::get_use_debanding); - - ClassDB::bind_method(D_METHOD("set_night_sky", "night_sky"), &PhysicalSkyMaterial::set_night_sky); - ClassDB::bind_method(D_METHOD("get_night_sky"), &PhysicalSkyMaterial::get_night_sky); - - ADD_GROUP("Rayleigh", "rayleigh_"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "rayleigh_coefficient", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_rayleigh_coefficient", "get_rayleigh_coefficient"); - ADD_PROPERTY(PropertyInfo(Variant::COLOR, "rayleigh_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_rayleigh_color", "get_rayleigh_color"); - - ADD_GROUP("Mie", "mie_"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mie_coefficient", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_mie_coefficient", "get_mie_coefficient"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mie_eccentricity", PROPERTY_HINT_RANGE, "-1,1,0.01"), "set_mie_eccentricity", "get_mie_eccentricity"); - ADD_PROPERTY(PropertyInfo(Variant::COLOR, "mie_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_mie_color", "get_mie_color"); - - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "turbidity", PROPERTY_HINT_RANGE, "0,1000,0.01"), "set_turbidity", "get_turbidity"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "sun_disk_scale", PROPERTY_HINT_RANGE, "0,360,0.01"), "set_sun_disk_scale", "get_sun_disk_scale"); - ADD_PROPERTY(PropertyInfo(Variant::COLOR, "ground_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_ground_color", "get_ground_color"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "energy_multiplier", PROPERTY_HINT_RANGE, "0,128,0.01"), "set_energy_multiplier", "get_energy_multiplier"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_debanding"), "set_use_debanding", "get_use_debanding"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "night_sky", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D"), "set_night_sky", "get_night_sky"); -} - -void PhysicalSkyMaterial::cleanup_shader() { - if (shader_cache[0].is_valid()) { - RS::get_singleton()->free(shader_cache[0]); - RS::get_singleton()->free(shader_cache[1]); - } -} - -void PhysicalSkyMaterial::_update_shader() { - shader_mutex.lock(); - if (shader_cache[0].is_null()) { - for (int i = 0; i < 2; i++) { - shader_cache[i] = RS::get_singleton()->shader_create(); - - // Add a comment to describe the shader origin (useful when converting to ShaderMaterial). - RS::get_singleton()->shader_set_code(shader_cache[i], vformat(R"( -// NOTE: Shader automatically converted from )" VERSION_NAME " " VERSION_FULL_CONFIG R"('s PhysicalSkyMaterial. - -shader_type sky; -%s - -uniform float rayleigh : hint_range(0, 64) = 2.0; -uniform vec4 rayleigh_color : source_color = vec4(0.3, 0.405, 0.6, 1.0); -uniform float mie : hint_range(0, 1) = 0.005; -uniform float mie_eccentricity : hint_range(-1, 1) = 0.8; -uniform vec4 mie_color : source_color = vec4(0.69, 0.729, 0.812, 1.0); - -uniform float turbidity : hint_range(0, 1000) = 10.0; -uniform float sun_disk_scale : hint_range(0, 360) = 1.0; -uniform vec4 ground_color : source_color = vec4(0.1, 0.07, 0.034, 1.0); -uniform float exposure : hint_range(0, 128) = 1.0; - -uniform sampler2D night_sky : filter_linear, source_color, hint_default_black; - -const vec3 UP = vec3( 0.0, 1.0, 0.0 ); - -// Optical length at zenith for molecules. -const float rayleigh_zenith_size = 8.4e3; -const float mie_zenith_size = 1.25e3; - -float henyey_greenstein(float cos_theta, float g) { - const float k = 0.0795774715459; - return k * (1.0 - g * g) / (pow(1.0 + g * g - 2.0 * g * cos_theta, 1.5)); -} - -void sky() { - if (LIGHT0_ENABLED) { - float zenith_angle = clamp( dot(UP, normalize(LIGHT0_DIRECTION)), -1.0, 1.0 ); - float sun_energy = max(0.0, 1.0 - exp(-((PI * 0.5) - acos(zenith_angle)))) * LIGHT0_ENERGY; - float sun_fade = 1.0 - clamp(1.0 - exp(LIGHT0_DIRECTION.y), 0.0, 1.0); - - // Rayleigh coefficients. - float rayleigh_coefficient = rayleigh - ( 1.0 * ( 1.0 - sun_fade ) ); - vec3 rayleigh_beta = rayleigh_coefficient * rayleigh_color.rgb * 0.0001; - // mie coefficients from Preetham - vec3 mie_beta = turbidity * mie * mie_color.rgb * 0.000434; - - // Optical length. - float zenith = acos(max(0.0, dot(UP, EYEDIR))); - float optical_mass = 1.0 / (cos(zenith) + 0.15 * pow(93.885 - degrees(zenith), -1.253)); - float rayleigh_scatter = rayleigh_zenith_size * optical_mass; - float mie_scatter = mie_zenith_size * optical_mass; - - // Light extinction based on thickness of atmosphere. - vec3 extinction = exp(-(rayleigh_beta * rayleigh_scatter + mie_beta * mie_scatter)); - - // In scattering. - float cos_theta = dot(EYEDIR, normalize(LIGHT0_DIRECTION)); - - float rayleigh_phase = (3.0 / (16.0 * PI)) * (1.0 + pow(cos_theta * 0.5 + 0.5, 2.0)); - vec3 betaRTheta = rayleigh_beta * rayleigh_phase; - - float mie_phase = henyey_greenstein(cos_theta, mie_eccentricity); - vec3 betaMTheta = mie_beta * mie_phase; - - vec3 Lin = pow(sun_energy * ((betaRTheta + betaMTheta) / (rayleigh_beta + mie_beta)) * (1.0 - extinction), vec3(1.5)); - // Hack from https://github.com/mrdoob/three.js/blob/master/examples/jsm/objects/Sky.js - Lin *= mix(vec3(1.0), pow(sun_energy * ((betaRTheta + betaMTheta) / (rayleigh_beta + mie_beta)) * extinction, vec3(0.5)), clamp(pow(1.0 - zenith_angle, 5.0), 0.0, 1.0)); - - // Hack in the ground color. - Lin *= mix(ground_color.rgb, vec3(1.0), smoothstep(-0.1, 0.1, dot(UP, EYEDIR))); - - // Solar disk and out-scattering. - float sunAngularDiameterCos = cos(LIGHT0_SIZE * sun_disk_scale); - float sunAngularDiameterCos2 = cos(LIGHT0_SIZE * sun_disk_scale*0.5); - float sundisk = smoothstep(sunAngularDiameterCos, sunAngularDiameterCos2, cos_theta); - vec3 L0 = (sun_energy * extinction) * sundisk * LIGHT0_COLOR; - L0 += texture(night_sky, SKY_COORDS).xyz * extinction; - - vec3 color = Lin + L0; - COLOR = pow(color, vec3(1.0 / (1.2 + (1.2 * sun_fade)))); - COLOR *= exposure; - } else { - // There is no sun, so display night_sky and nothing else. - COLOR = texture(night_sky, SKY_COORDS).xyz; - COLOR *= exposure; - } -} -)", - i ? "render_mode use_debanding;" : "")); - } - } - - shader_mutex.unlock(); -} - -PhysicalSkyMaterial::PhysicalSkyMaterial() { - set_rayleigh_coefficient(2.0); - set_rayleigh_color(Color(0.3, 0.405, 0.6)); - set_mie_coefficient(0.005); - set_mie_eccentricity(0.8); - set_mie_color(Color(0.69, 0.729, 0.812)); - set_turbidity(10.0); - set_sun_disk_scale(1.0); - set_ground_color(Color(0.1, 0.07, 0.034)); - set_energy_multiplier(1.0); - set_use_debanding(true); -} - -PhysicalSkyMaterial::~PhysicalSkyMaterial() { -} diff --git a/scene/resources/sky_material.h b/scene/resources/sky_material.h deleted file mode 100644 index 4ffb18c6b4..0000000000 --- a/scene/resources/sky_material.h +++ /dev/null @@ -1,235 +0,0 @@ -/**************************************************************************/ -/* sky_material.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 SKY_MATERIAL_H -#define SKY_MATERIAL_H - -#include "core/templates/rid.h" -#include "scene/resources/material.h" - -class ProceduralSkyMaterial : public Material { - GDCLASS(ProceduralSkyMaterial, Material); - -private: - Color sky_top_color; - Color sky_horizon_color; - float sky_curve = 0.0f; - float sky_energy_multiplier = 0.0f; - Ref sky_cover; - Color sky_cover_modulate; - - Color ground_bottom_color; - Color ground_horizon_color; - float ground_curve = 0.0f; - float ground_energy_multiplier = 0.0f; - - float sun_angle_max = 0.0f; - float sun_curve = 0.0f; - bool use_debanding = true; - float global_energy_multiplier = 1.0f; - - static Mutex shader_mutex; - static RID shader_cache[2]; - static void _update_shader(); - mutable bool shader_set = false; - -protected: - static void _bind_methods(); - void _validate_property(PropertyInfo &property) const; - -public: - void set_sky_top_color(const Color &p_sky_top); - Color get_sky_top_color() const; - - void set_sky_horizon_color(const Color &p_sky_horizon); - Color get_sky_horizon_color() const; - - void set_sky_curve(float p_curve); - float get_sky_curve() const; - - void set_sky_energy_multiplier(float p_multiplier); - float get_sky_energy_multiplier() const; - - void set_sky_cover(const Ref &p_sky_cover); - Ref get_sky_cover() const; - - void set_sky_cover_modulate(const Color &p_sky_cover_modulate); - Color get_sky_cover_modulate() const; - - void set_ground_bottom_color(const Color &p_ground_bottom); - Color get_ground_bottom_color() const; - - void set_ground_horizon_color(const Color &p_ground_horizon); - Color get_ground_horizon_color() const; - - void set_ground_curve(float p_curve); - float get_ground_curve() const; - - void set_ground_energy_multiplier(float p_energy); - float get_ground_energy_multiplier() const; - - void set_sun_angle_max(float p_angle); - float get_sun_angle_max() const; - - void set_sun_curve(float p_curve); - float get_sun_curve() const; - - void set_use_debanding(bool p_use_debanding); - bool get_use_debanding() const; - - void set_energy_multiplier(float p_multiplier); - float get_energy_multiplier() const; - - virtual Shader::Mode get_shader_mode() const override; - virtual RID get_shader_rid() const override; - virtual RID get_rid() const override; - - static void cleanup_shader(); - - ProceduralSkyMaterial(); - ~ProceduralSkyMaterial(); -}; - -////////////////////////////////////////////////////// -/* PanoramaSkyMaterial */ - -class PanoramaSkyMaterial : public Material { - GDCLASS(PanoramaSkyMaterial, Material); - -private: - Ref panorama; - float energy_multiplier = 1.0f; - - static Mutex shader_mutex; - static RID shader_cache[2]; - static void _update_shader(); - mutable bool shader_set = false; - - bool filter = true; - -protected: - static void _bind_methods(); - -public: - void set_panorama(const Ref &p_panorama); - Ref get_panorama() const; - - void set_filtering_enabled(bool p_enabled); - bool is_filtering_enabled() const; - - void set_energy_multiplier(float p_multiplier); - float get_energy_multiplier() const; - - virtual Shader::Mode get_shader_mode() const override; - virtual RID get_shader_rid() const override; - virtual RID get_rid() const override; - - static void cleanup_shader(); - - PanoramaSkyMaterial(); - ~PanoramaSkyMaterial(); -}; - -////////////////////////////////////////////////////// -/* PanoramaSkyMaterial */ - -class PhysicalSkyMaterial : public Material { - GDCLASS(PhysicalSkyMaterial, Material); - -private: - static Mutex shader_mutex; - static RID shader_cache[2]; - - float rayleigh = 0.0f; - Color rayleigh_color; - float mie = 0.0f; - float mie_eccentricity = 0.0f; - Color mie_color; - float turbidity = 0.0f; - float sun_disk_scale = 0.0f; - Color ground_color; - float energy_multiplier = 1.0f; - bool use_debanding = true; - Ref night_sky; - static void _update_shader(); - mutable bool shader_set = false; - -protected: - static void _bind_methods(); - void _validate_property(PropertyInfo &property) const; - -public: - void set_rayleigh_coefficient(float p_rayleigh); - float get_rayleigh_coefficient() const; - - void set_rayleigh_color(Color p_rayleigh_color); - Color get_rayleigh_color() const; - - void set_turbidity(float p_turbidity); - float get_turbidity() const; - - void set_mie_coefficient(float p_mie); - float get_mie_coefficient() const; - - void set_mie_eccentricity(float p_eccentricity); - float get_mie_eccentricity() const; - - void set_mie_color(Color p_mie_color); - Color get_mie_color() const; - - void set_sun_disk_scale(float p_sun_disk_scale); - float get_sun_disk_scale() const; - - void set_ground_color(Color p_ground_color); - Color get_ground_color() const; - - void set_energy_multiplier(float p_multiplier); - float get_energy_multiplier() const; - - void set_exposure_value(float p_exposure); - float get_exposure_value() const; - - void set_use_debanding(bool p_use_debanding); - bool get_use_debanding() const; - - void set_night_sky(const Ref &p_night_sky); - Ref get_night_sky() const; - - virtual Shader::Mode get_shader_mode() const override; - virtual RID get_shader_rid() const override; - - static void cleanup_shader(); - virtual RID get_rid() const override; - - PhysicalSkyMaterial(); - ~PhysicalSkyMaterial(); -}; - -#endif // SKY_MATERIAL_H diff --git a/scene/resources/sphere_shape_3d.cpp b/scene/resources/sphere_shape_3d.cpp deleted file mode 100644 index 56b78471ec..0000000000 --- a/scene/resources/sphere_shape_3d.cpp +++ /dev/null @@ -1,87 +0,0 @@ -/**************************************************************************/ -/* sphere_shape_3d.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 "sphere_shape_3d.h" - -#include "servers/physics_server_3d.h" - -Vector SphereShape3D::get_debug_mesh_lines() const { - float r = get_radius(); - - Vector points; - - for (int i = 0; i <= 360; i++) { - float ra = Math::deg_to_rad((float)i); - float rb = Math::deg_to_rad((float)i + 1); - Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * r; - Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * r; - - points.push_back(Vector3(a.x, 0, a.y)); - points.push_back(Vector3(b.x, 0, b.y)); - points.push_back(Vector3(0, a.x, a.y)); - points.push_back(Vector3(0, b.x, b.y)); - points.push_back(Vector3(a.x, a.y, 0)); - points.push_back(Vector3(b.x, b.y, 0)); - } - - return points; -} - -real_t SphereShape3D::get_enclosing_radius() const { - return radius; -} - -void SphereShape3D::_update_shape() { - PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), radius); - Shape3D::_update_shape(); -} - -void SphereShape3D::set_radius(float p_radius) { - ERR_FAIL_COND_MSG(p_radius < 0, "SphereShape3D radius cannot be negative."); - radius = p_radius; - _update_shape(); - emit_changed(); -} - -float SphereShape3D::get_radius() const { - return radius; -} - -void SphereShape3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_radius", "radius"), &SphereShape3D::set_radius); - ClassDB::bind_method(D_METHOD("get_radius"), &SphereShape3D::get_radius); - - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_radius", "get_radius"); -} - -SphereShape3D::SphereShape3D() : - Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_SPHERE)) { - set_radius(0.5); -} diff --git a/scene/resources/sphere_shape_3d.h b/scene/resources/sphere_shape_3d.h deleted file mode 100644 index e98eff12e2..0000000000 --- a/scene/resources/sphere_shape_3d.h +++ /dev/null @@ -1,55 +0,0 @@ -/**************************************************************************/ -/* sphere_shape_3d.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 SPHERE_SHAPE_3D_H -#define SPHERE_SHAPE_3D_H - -#include "scene/resources/shape_3d.h" - -class SphereShape3D : public Shape3D { - GDCLASS(SphereShape3D, Shape3D); - float radius = 0.5f; - -protected: - static void _bind_methods(); - - virtual void _update_shape() override; - -public: - void set_radius(float p_radius); - float get_radius() const; - - virtual Vector get_debug_mesh_lines() const override; - virtual real_t get_enclosing_radius() const override; - - SphereShape3D(); -}; - -#endif // SPHERE_SHAPE_3D_H diff --git a/scene/resources/world_3d.cpp b/scene/resources/world_3d.cpp deleted file mode 100644 index bf91b1eb33..0000000000 --- a/scene/resources/world_3d.cpp +++ /dev/null @@ -1,188 +0,0 @@ -/**************************************************************************/ -/* world_3d.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 "world_3d.h" - -#include "core/config/project_settings.h" -#include "scene/3d/camera_3d.h" -#include "scene/3d/visible_on_screen_notifier_3d.h" -#include "scene/resources/camera_attributes.h" -#include "scene/resources/environment.h" -#include "scene/scene_string_names.h" -#include "servers/navigation_server_3d.h" - -void World3D::_register_camera(Camera3D *p_camera) { -#ifndef _3D_DISABLED - cameras.insert(p_camera); -#endif -} - -void World3D::_remove_camera(Camera3D *p_camera) { -#ifndef _3D_DISABLED - cameras.erase(p_camera); -#endif -} - -RID World3D::get_space() const { - if (space.is_null()) { - space = PhysicsServer3D::get_singleton()->space_create(); - PhysicsServer3D::get_singleton()->space_set_active(space, true); - PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_GRAVITY, GLOBAL_GET("physics/3d/default_gravity")); - PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_GRAVITY_VECTOR, GLOBAL_GET("physics/3d/default_gravity_vector")); - PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_LINEAR_DAMP, GLOBAL_GET("physics/3d/default_linear_damp")); - PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_ANGULAR_DAMP, GLOBAL_GET("physics/3d/default_angular_damp")); - } - return space; -} - -RID World3D::get_navigation_map() const { - if (navigation_map.is_null()) { - navigation_map = NavigationServer3D::get_singleton()->map_create(); - NavigationServer3D::get_singleton()->map_set_active(navigation_map, true); - NavigationServer3D::get_singleton()->map_set_cell_size(navigation_map, GLOBAL_GET("navigation/3d/default_cell_size")); - NavigationServer3D::get_singleton()->map_set_cell_height(navigation_map, GLOBAL_GET("navigation/3d/default_cell_height")); - NavigationServer3D::get_singleton()->map_set_up(navigation_map, GLOBAL_GET("navigation/3d/default_up")); - NavigationServer3D::get_singleton()->map_set_merge_rasterizer_cell_scale(navigation_map, GLOBAL_GET("navigation/3d/merge_rasterizer_cell_scale")); - NavigationServer3D::get_singleton()->map_set_use_edge_connections(navigation_map, GLOBAL_GET("navigation/3d/use_edge_connections")); - NavigationServer3D::get_singleton()->map_set_edge_connection_margin(navigation_map, GLOBAL_GET("navigation/3d/default_edge_connection_margin")); - NavigationServer3D::get_singleton()->map_set_link_connection_radius(navigation_map, GLOBAL_GET("navigation/3d/default_link_connection_radius")); - } - return navigation_map; -} - -RID World3D::get_scenario() const { - return scenario; -} - -void World3D::set_environment(const Ref &p_environment) { - if (environment == p_environment) { - return; - } - - environment = p_environment; - if (environment.is_valid()) { - RS::get_singleton()->scenario_set_environment(scenario, environment->get_rid()); - } else { - RS::get_singleton()->scenario_set_environment(scenario, RID()); - } - - emit_changed(); -} - -Ref World3D::get_environment() const { - return environment; -} - -void World3D::set_fallback_environment(const Ref &p_environment) { - if (fallback_environment == p_environment) { - return; - } - - fallback_environment = p_environment; - if (fallback_environment.is_valid()) { - RS::get_singleton()->scenario_set_fallback_environment(scenario, p_environment->get_rid()); - } else { - RS::get_singleton()->scenario_set_fallback_environment(scenario, RID()); - } - - emit_changed(); -} - -Ref World3D::get_fallback_environment() const { - return fallback_environment; -} - -void World3D::set_camera_attributes(const Ref &p_camera_attributes) { - camera_attributes = p_camera_attributes; - if (camera_attributes.is_valid()) { - RS::get_singleton()->scenario_set_camera_attributes(scenario, camera_attributes->get_rid()); - } else { - RS::get_singleton()->scenario_set_camera_attributes(scenario, RID()); - } -} - -Ref World3D::get_camera_attributes() const { - return camera_attributes; -} - -void World3D::set_compositor(const Ref &p_compositor) { - compositor = p_compositor; - if (compositor.is_valid()) { - RS::get_singleton()->scenario_set_compositor(scenario, compositor->get_rid()); - } else { - RS::get_singleton()->scenario_set_compositor(scenario, RID()); - } -} - -Ref World3D::get_compositor() const { - return compositor; -} - -PhysicsDirectSpaceState3D *World3D::get_direct_space_state() { - return PhysicsServer3D::get_singleton()->space_get_direct_state(get_space()); -} - -void World3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("get_space"), &World3D::get_space); - ClassDB::bind_method(D_METHOD("get_navigation_map"), &World3D::get_navigation_map); - ClassDB::bind_method(D_METHOD("get_scenario"), &World3D::get_scenario); - ClassDB::bind_method(D_METHOD("set_environment", "env"), &World3D::set_environment); - ClassDB::bind_method(D_METHOD("get_environment"), &World3D::get_environment); - ClassDB::bind_method(D_METHOD("set_fallback_environment", "env"), &World3D::set_fallback_environment); - ClassDB::bind_method(D_METHOD("get_fallback_environment"), &World3D::get_fallback_environment); - ClassDB::bind_method(D_METHOD("set_camera_attributes", "attributes"), &World3D::set_camera_attributes); - ClassDB::bind_method(D_METHOD("get_camera_attributes"), &World3D::get_camera_attributes); - ClassDB::bind_method(D_METHOD("get_direct_space_state"), &World3D::get_direct_space_state); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "environment", PROPERTY_HINT_RESOURCE_TYPE, "Environment"), "set_environment", "get_environment"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "fallback_environment", PROPERTY_HINT_RESOURCE_TYPE, "Environment"), "set_fallback_environment", "get_fallback_environment"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "camera_attributes", PROPERTY_HINT_RESOURCE_TYPE, "CameraAttributesPractical,CameraAttributesPhysical"), "set_camera_attributes", "get_camera_attributes"); - ADD_PROPERTY(PropertyInfo(Variant::RID, "space", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_space"); - ADD_PROPERTY(PropertyInfo(Variant::RID, "navigation_map", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_navigation_map"); - ADD_PROPERTY(PropertyInfo(Variant::RID, "scenario", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "", "get_scenario"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "direct_space_state", PROPERTY_HINT_RESOURCE_TYPE, "PhysicsDirectSpaceState3D", PROPERTY_USAGE_NONE), "", "get_direct_space_state"); -} - -World3D::World3D() { - scenario = RenderingServer::get_singleton()->scenario_create(); -} - -World3D::~World3D() { - ERR_FAIL_NULL(RenderingServer::get_singleton()); - ERR_FAIL_NULL(PhysicsServer3D::get_singleton()); - ERR_FAIL_NULL(NavigationServer3D::get_singleton()); - - RenderingServer::get_singleton()->free(scenario); - if (space.is_valid()) { - PhysicsServer3D::get_singleton()->free(space); - } - if (navigation_map.is_valid()) { - NavigationServer3D::get_singleton()->free(navigation_map); - } -} diff --git a/scene/resources/world_3d.h b/scene/resources/world_3d.h deleted file mode 100644 index 06b06aea98..0000000000 --- a/scene/resources/world_3d.h +++ /dev/null @@ -1,93 +0,0 @@ -/**************************************************************************/ -/* world_3d.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 WORLD_3D_H -#define WORLD_3D_H - -#include "core/io/resource.h" -#include "scene/resources/compositor.h" -#include "scene/resources/environment.h" -#include "servers/physics_server_3d.h" -#include "servers/rendering_server.h" - -class CameraAttributes; -class Camera3D; -class VisibleOnScreenNotifier3D; -struct SpatialIndexer; - -class World3D : public Resource { - GDCLASS(World3D, Resource); - -private: - RID scenario; - mutable RID space; - mutable RID navigation_map; - - Ref environment; - Ref fallback_environment; - Ref camera_attributes; - Ref compositor; - - HashSet cameras; - -protected: - static void _bind_methods(); - - friend class Camera3D; - - void _register_camera(Camera3D *p_camera); - void _remove_camera(Camera3D *p_camera); - -public: - RID get_space() const; - RID get_navigation_map() const; - RID get_scenario() const; - - void set_environment(const Ref &p_environment); - Ref get_environment() const; - - void set_fallback_environment(const Ref &p_environment); - Ref get_fallback_environment() const; - - void set_camera_attributes(const Ref &p_camera_attributes); - Ref get_camera_attributes() const; - - void set_compositor(const Ref &p_compositor); - Ref get_compositor() const; - - _FORCE_INLINE_ const HashSet &get_cameras() const { return cameras; } - - PhysicsDirectSpaceState3D *get_direct_space_state(); - - World3D(); - ~World3D(); -}; - -#endif // WORLD_3D_H diff --git a/scene/resources/world_boundary_shape_3d.cpp b/scene/resources/world_boundary_shape_3d.cpp deleted file mode 100644 index beaaddc95e..0000000000 --- a/scene/resources/world_boundary_shape_3d.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/**************************************************************************/ -/* world_boundary_shape_3d.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 "world_boundary_shape_3d.h" - -#include "servers/physics_server_3d.h" - -Vector WorldBoundaryShape3D::get_debug_mesh_lines() const { - Plane p = get_plane(); - - Vector3 n1 = p.get_any_perpendicular_normal(); - Vector3 n2 = p.normal.cross(n1).normalized(); - - Vector3 pface[4] = { - p.normal * p.d + n1 * 10.0 + n2 * 10.0, - p.normal * p.d + n1 * 10.0 + n2 * -10.0, - p.normal * p.d + n1 * -10.0 + n2 * -10.0, - p.normal * p.d + n1 * -10.0 + n2 * 10.0, - }; - - Vector points = { - pface[0], - pface[1], - pface[1], - pface[2], - pface[2], - pface[3], - pface[3], - pface[0], - p.normal * p.d, - p.normal * p.d + p.normal * 3 - }; - - return points; -} - -void WorldBoundaryShape3D::_update_shape() { - PhysicsServer3D::get_singleton()->shape_set_data(get_shape(), plane); - Shape3D::_update_shape(); -} - -void WorldBoundaryShape3D::set_plane(const Plane &p_plane) { - plane = p_plane; - _update_shape(); - emit_changed(); -} - -const Plane &WorldBoundaryShape3D::get_plane() const { - return plane; -} - -void WorldBoundaryShape3D::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_plane", "plane"), &WorldBoundaryShape3D::set_plane); - ClassDB::bind_method(D_METHOD("get_plane"), &WorldBoundaryShape3D::get_plane); - - ADD_PROPERTY(PropertyInfo(Variant::PLANE, "plane", PROPERTY_HINT_NONE, "suffix:m"), "set_plane", "get_plane"); -} - -WorldBoundaryShape3D::WorldBoundaryShape3D() : - Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_WORLD_BOUNDARY)) { - set_plane(Plane(0, 1, 0, 0)); -} diff --git a/scene/resources/world_boundary_shape_3d.h b/scene/resources/world_boundary_shape_3d.h deleted file mode 100644 index 1f4cc67475..0000000000 --- a/scene/resources/world_boundary_shape_3d.h +++ /dev/null @@ -1,57 +0,0 @@ -/**************************************************************************/ -/* world_boundary_shape_3d.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 WORLD_BOUNDARY_SHAPE_3D_H -#define WORLD_BOUNDARY_SHAPE_3D_H - -#include "scene/resources/shape_3d.h" - -class WorldBoundaryShape3D : public Shape3D { - GDCLASS(WorldBoundaryShape3D, Shape3D); - Plane plane; - -protected: - static void _bind_methods(); - virtual void _update_shape() override; - -public: - void set_plane(const Plane &p_plane); - const Plane &get_plane() const; - - virtual Vector get_debug_mesh_lines() const override; - virtual real_t get_enclosing_radius() const override { - // Should be infinite? - return 0; - } - - WorldBoundaryShape3D(); -}; - -#endif // WORLD_BOUNDARY_SHAPE_3D_H diff --git a/scu_builders.py b/scu_builders.py index 740b4ca571..1a75f19b30 100644 --- a/scu_builders.py +++ b/scu_builders.py @@ -316,6 +316,7 @@ def generate_scu_files(max_includes_per_scu): process_folder(["scene/main"]) process_folder(["scene/resources"]) process_folder(["scene/resources/2d"]) + process_folder(["scene/resources/3d"]) process_folder(["servers"]) process_folder(["servers/rendering"]) diff --git a/tests/scene/test_arraymesh.h b/tests/scene/test_arraymesh.h index 1623b41300..67aa19c5d3 100644 --- a/tests/scene/test_arraymesh.h +++ b/tests/scene/test_arraymesh.h @@ -31,8 +31,8 @@ #ifndef TEST_ARRAYMESH_H #define TEST_ARRAYMESH_H +#include "scene/resources/3d/primitive_meshes.h" #include "scene/resources/mesh.h" -#include "scene/resources/primitive_meshes.h" #include "tests/test_macros.h" diff --git a/tests/scene/test_navigation_region_3d.h b/tests/scene/test_navigation_region_3d.h index f7cf7c7912..372f6dc505 100644 --- a/tests/scene/test_navigation_region_3d.h +++ b/tests/scene/test_navigation_region_3d.h @@ -34,7 +34,7 @@ #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/navigation_region_3d.h" #include "scene/main/window.h" -#include "scene/resources/primitive_meshes.h" +#include "scene/resources/3d/primitive_meshes.h" #include "tests/test_macros.h" diff --git a/tests/scene/test_primitives.h b/tests/scene/test_primitives.h index 36b9b17b27..f105e1ac04 100644 --- a/tests/scene/test_primitives.h +++ b/tests/scene/test_primitives.h @@ -31,7 +31,7 @@ #ifndef TEST_PRIMITIVES_H #define TEST_PRIMITIVES_H -#include "scene/resources/primitive_meshes.h" +#include "scene/resources/3d/primitive_meshes.h" #include "tests/test_macros.h" diff --git a/tests/servers/test_navigation_server_3d.h b/tests/servers/test_navigation_server_3d.h index 5b5c1faac8..b5547f2c94 100644 --- a/tests/servers/test_navigation_server_3d.h +++ b/tests/servers/test_navigation_server_3d.h @@ -32,7 +32,7 @@ #define TEST_NAVIGATION_SERVER_3D_H #include "scene/3d/mesh_instance_3d.h" -#include "scene/resources/primitive_meshes.h" +#include "scene/resources/3d/primitive_meshes.h" #include "servers/navigation_server_3d.h" #include "tests/test_macros.h" -- cgit v1.2.3