diff options
318 files changed, 6723 insertions, 4533 deletions
diff --git a/.github/actions/godot-build/action.yml b/.github/actions/godot-build/action.yml index 377480b123..0a0899db78 100644 --- a/.github/actions/godot-build/action.yml +++ b/.github/actions/godot-build/action.yml @@ -31,6 +31,18 @@ runs: SCONS_CACHE_LIMIT: ${{ inputs.scons-cache-limit }} run: | echo "Building with flags:" platform=${{ inputs.platform }} target=${{ inputs.target }} tests=${{ inputs.tests }} ${{ env.SCONSFLAGS }} - if [ "${{ inputs.target }}" != "editor" ]; then rm -rf editor; fi # Ensure we don't include editor code. + + if [ "${{ inputs.target }}" != "editor" ]; then + # Ensure we don't include editor code in export template builds. + rm -rf editor + fi + + if [ "${{ github.event.number }}" != "" ]; then + # Set build identifier with pull request number if available. This is displayed throughout the editor. + export BUILD_NAME="gh-${{ github.event.number }}" + else + export BUILD_NAME="gh" + fi + scons platform=${{ inputs.platform }} target=${{ inputs.target }} tests=${{ inputs.tests }} ${{ env.SCONSFLAGS }} ls -l bin/ diff --git a/core/core_bind.cpp b/core/core_bind.cpp index d91c659d1e..e5363f9acc 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -1040,6 +1040,10 @@ Vector<Vector3> Geometry3D::clip_polygon(const Vector<Vector3> &p_points, const return ::Geometry3D::clip_polygon(p_points, p_plane); } +Vector<int32_t> Geometry3D::tetrahedralize_delaunay(const Vector<Vector3> &p_points) { + return ::Geometry3D::tetrahedralize_delaunay(p_points); +} + void Geometry3D::_bind_methods() { ClassDB::bind_method(D_METHOD("compute_convex_mesh_points", "planes"), &Geometry3D::compute_convex_mesh_points); ClassDB::bind_method(D_METHOD("build_box_planes", "extents"), &Geometry3D::build_box_planes); @@ -1061,6 +1065,7 @@ void Geometry3D::_bind_methods() { ClassDB::bind_method(D_METHOD("segment_intersects_convex", "from", "to", "planes"), &Geometry3D::segment_intersects_convex); ClassDB::bind_method(D_METHOD("clip_polygon", "points", "plane"), &Geometry3D::clip_polygon); + ClassDB::bind_method(D_METHOD("tetrahedralize_delaunay", "points"), &Geometry3D::tetrahedralize_delaunay); } ////// Marshalls ////// @@ -1370,11 +1375,11 @@ Variant ClassDB::instantiate(const StringName &p_class) const { } } -bool ClassDB::class_has_signal(StringName p_class, StringName p_signal) const { +bool ClassDB::class_has_signal(const StringName &p_class, const StringName &p_signal) const { return ::ClassDB::has_signal(p_class, p_signal); } -Dictionary ClassDB::class_get_signal(StringName p_class, StringName p_signal) const { +Dictionary ClassDB::class_get_signal(const StringName &p_class, const StringName &p_signal) const { MethodInfo signal; if (::ClassDB::get_signal(p_class, p_signal, &signal)) { return signal.operator Dictionary(); @@ -1383,7 +1388,7 @@ Dictionary ClassDB::class_get_signal(StringName p_class, StringName p_signal) co } } -TypedArray<Dictionary> ClassDB::class_get_signal_list(StringName p_class, bool p_no_inheritance) const { +TypedArray<Dictionary> ClassDB::class_get_signal_list(const StringName &p_class, bool p_no_inheritance) const { List<MethodInfo> signals; ::ClassDB::get_signal_list(p_class, &signals, p_no_inheritance); TypedArray<Dictionary> ret; @@ -1395,7 +1400,7 @@ TypedArray<Dictionary> ClassDB::class_get_signal_list(StringName p_class, bool p return ret; } -TypedArray<Dictionary> ClassDB::class_get_property_list(StringName p_class, bool p_no_inheritance) const { +TypedArray<Dictionary> ClassDB::class_get_property_list(const StringName &p_class, bool p_no_inheritance) const { List<PropertyInfo> plist; ::ClassDB::get_property_list(p_class, &plist, p_no_inheritance); TypedArray<Dictionary> ret; @@ -1423,11 +1428,11 @@ Error ClassDB::class_set_property(Object *p_object, const StringName &p_property return OK; } -bool ClassDB::class_has_method(StringName p_class, StringName p_method, bool p_no_inheritance) const { +bool ClassDB::class_has_method(const StringName &p_class, const StringName &p_method, bool p_no_inheritance) const { return ::ClassDB::has_method(p_class, p_method, p_no_inheritance); } -TypedArray<Dictionary> ClassDB::class_get_method_list(StringName p_class, bool p_no_inheritance) const { +TypedArray<Dictionary> ClassDB::class_get_method_list(const StringName &p_class, bool p_no_inheritance) const { List<MethodInfo> methods; ::ClassDB::get_method_list(p_class, &methods, p_no_inheritance); TypedArray<Dictionary> ret; @@ -1508,7 +1513,7 @@ StringName ClassDB::class_get_integer_constant_enum(const StringName &p_class, c return ::ClassDB::get_integer_constant_enum(p_class, p_name, p_no_inheritance); } -bool ClassDB::is_class_enabled(StringName p_class) const { +bool ClassDB::is_class_enabled(const StringName &p_class) const { return ::ClassDB::is_class_enabled(p_class); } @@ -1718,6 +1723,16 @@ bool Engine::is_printing_error_messages() const { return ::Engine::get_singleton()->is_printing_error_messages(); } +void Engine::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { + String pf = p_function; + if (p_idx == 0 && (pf == "has_singleton" || pf == "get_singleton" || pf == "unregister_singleton")) { + for (const String &E : get_singleton_list()) { + r_options->push_back(E.quote()); + } + } + Object::get_argument_options(p_function, p_idx, r_options); +} + void Engine::_bind_methods() { ClassDB::bind_method(D_METHOD("set_physics_ticks_per_second", "physics_ticks_per_second"), &Engine::set_physics_ticks_per_second); ClassDB::bind_method(D_METHOD("get_physics_ticks_per_second"), &Engine::get_physics_ticks_per_second); diff --git a/core/core_bind.h b/core/core_bind.h index 715e26cf23..94d95f2ce9 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -337,6 +337,7 @@ public: Vector<Vector3> segment_intersects_convex(const Vector3 &p_from, const Vector3 &p_to, const TypedArray<Plane> &p_planes); Vector<Vector3> clip_polygon(const Vector<Vector3> &p_points, const Plane &p_plane); + Vector<int32_t> tetrahedralize_delaunay(const Vector<Vector3> &p_points); Geometry3D() { singleton = this; } }; @@ -434,17 +435,17 @@ public: bool can_instantiate(const StringName &p_class) const; Variant instantiate(const StringName &p_class) const; - bool class_has_signal(StringName p_class, StringName p_signal) const; - Dictionary class_get_signal(StringName p_class, StringName p_signal) const; - TypedArray<Dictionary> class_get_signal_list(StringName p_class, bool p_no_inheritance = false) const; + bool class_has_signal(const StringName &p_class, const StringName &p_signal) const; + Dictionary class_get_signal(const StringName &p_class, const StringName &p_signal) const; + TypedArray<Dictionary> class_get_signal_list(const StringName &p_class, bool p_no_inheritance = false) const; - TypedArray<Dictionary> class_get_property_list(StringName p_class, bool p_no_inheritance = false) const; + TypedArray<Dictionary> class_get_property_list(const StringName &p_class, bool p_no_inheritance = false) const; Variant class_get_property(Object *p_object, const StringName &p_property) const; Error class_set_property(Object *p_object, const StringName &p_property, const Variant &p_value) const; - bool class_has_method(StringName p_class, StringName p_method, bool p_no_inheritance = false) const; + bool class_has_method(const StringName &p_class, const StringName &p_method, bool p_no_inheritance = false) const; - TypedArray<Dictionary> class_get_method_list(StringName p_class, bool p_no_inheritance = false) const; + TypedArray<Dictionary> class_get_method_list(const StringName &p_class, bool p_no_inheritance = false) const; PackedStringArray class_get_integer_constant_list(const StringName &p_class, bool p_no_inheritance = false) const; bool class_has_integer_constant(const StringName &p_class, const StringName &p_name) const; @@ -455,7 +456,7 @@ public: PackedStringArray class_get_enum_constants(const StringName &p_class, const StringName &p_enum, bool p_no_inheritance = false) const; StringName class_get_integer_constant_enum(const StringName &p_class, const StringName &p_name, bool p_no_inheritance = false) const; - bool is_class_enabled(StringName p_class) const; + bool is_class_enabled(const StringName &p_class) const; ClassDB() {} ~ClassDB() {} @@ -527,6 +528,8 @@ public: void set_print_error_messages(bool p_enabled); bool is_printing_error_messages() const; + virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override; + Engine() { singleton = this; } }; diff --git a/core/core_constants.cpp b/core/core_constants.cpp index 2f70fdf219..3b96fc20c6 100644 --- a/core/core_constants.cpp +++ b/core/core_constants.cpp @@ -845,7 +845,7 @@ bool CoreConstants::is_global_enum(const StringName &p_enum) { return _global_enums.has(p_enum); } -void CoreConstants::get_enum_values(StringName p_enum, HashMap<StringName, int64_t> *p_values) { +void CoreConstants::get_enum_values(const StringName &p_enum, HashMap<StringName, int64_t> *p_values) { ERR_FAIL_NULL_MSG(p_values, "Trying to get enum values with null map."); ERR_FAIL_COND_MSG(!_global_enums.has(p_enum), "Trying to get values of non-existing enum."); for (const _CoreConstant &constant : _global_enums[p_enum]) { diff --git a/core/core_constants.h b/core/core_constants.h index 51842490c8..82d626c749 100644 --- a/core/core_constants.h +++ b/core/core_constants.h @@ -45,7 +45,7 @@ public: static bool is_global_constant(const StringName &p_name); static int get_global_constant_index(const StringName &p_name); static bool is_global_enum(const StringName &p_enum); - static void get_enum_values(StringName p_enum, HashMap<StringName, int64_t> *p_values); + static void get_enum_values(const StringName &p_enum, HashMap<StringName, int64_t> *p_values); }; #endif // CORE_CONSTANTS_H diff --git a/core/debugger/engine_debugger.cpp b/core/debugger/engine_debugger.cpp index 32dc060aa2..0cce44d02f 100644 --- a/core/debugger/engine_debugger.cpp +++ b/core/debugger/engine_debugger.cpp @@ -162,7 +162,7 @@ void EngineDebugger::initialize(const String &p_uri, bool p_skip_breakpoints, Ve singleton_script_debugger->set_skip_breakpoints(p_skip_breakpoints); for (int i = 0; i < p_breakpoints.size(); i++) { - String bp = p_breakpoints[i]; + const String &bp = p_breakpoints[i]; int sp = bp.rfind(":"); ERR_CONTINUE_MSG(sp == -1, "Invalid breakpoint: '" + bp + "', expected file:line format."); diff --git a/core/extension/gdextension.cpp b/core/extension/gdextension.cpp index 19ffe96a09..ce01531b5c 100644 --- a/core/extension/gdextension.cpp +++ b/core/extension/gdextension.cpp @@ -666,12 +666,12 @@ void GDExtension::_get_library_path(GDExtensionClassLibraryPtr p_library, GDExte HashMap<StringName, GDExtensionInterfaceFunctionPtr> GDExtension::gdextension_interface_functions; -void GDExtension::register_interface_function(StringName p_function_name, GDExtensionInterfaceFunctionPtr p_function_pointer) { +void GDExtension::register_interface_function(const StringName &p_function_name, GDExtensionInterfaceFunctionPtr p_function_pointer) { ERR_FAIL_COND_MSG(gdextension_interface_functions.has(p_function_name), "Attempt to register interface function '" + p_function_name + "', which appears to be already registered."); gdextension_interface_functions.insert(p_function_name, p_function_pointer); } -GDExtensionInterfaceFunctionPtr GDExtension::get_interface_function(StringName p_function_name) { +GDExtensionInterfaceFunctionPtr GDExtension::get_interface_function(const StringName &p_function_name) { GDExtensionInterfaceFunctionPtr *function = gdextension_interface_functions.getptr(p_function_name); ERR_FAIL_NULL_V_MSG(function, nullptr, "Attempt to get non-existent interface function: " + String(p_function_name) + "."); return *function; diff --git a/core/extension/gdextension.h b/core/extension/gdextension.h index 0d20b8e50c..0b39581751 100644 --- a/core/extension/gdextension.h +++ b/core/extension/gdextension.h @@ -154,8 +154,8 @@ public: void initialize_library(InitializationLevel p_level); void deinitialize_library(InitializationLevel p_level); - static void register_interface_function(StringName p_function_name, GDExtensionInterfaceFunctionPtr p_function_pointer); - static GDExtensionInterfaceFunctionPtr get_interface_function(StringName p_function_name); + static void register_interface_function(const StringName &p_function_name, GDExtensionInterfaceFunctionPtr p_function_pointer); + static GDExtensionInterfaceFunctionPtr get_interface_function(const StringName &p_function_name); static void initialize_gdextensions(); static void finalize_gdextensions(); diff --git a/core/input/input.cpp b/core/input/input.cpp index 7fe850069a..2ba4b1d1e8 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -1519,7 +1519,7 @@ void Input::add_joy_mapping(String p_mapping, bool p_update_existing) { parse_mapping(p_mapping); if (p_update_existing) { Vector<String> entry = p_mapping.split(","); - String uid = entry[0]; + const String &uid = entry[0]; for (KeyValue<int, Joypad> &E : joy_names) { Joypad &joy = E.value; if (joy.uid == uid) { diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index 78b9ada884..70041ecfd6 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -754,7 +754,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins_with_featur String fullname = E.key; Vector<String> split = fullname.split("."); - String name = split[0]; + const String &name = split[0]; String override_for = split.size() > 1 ? split[1] : String(); if (!override_for.is_empty() && OS::get_singleton()->has_feature(override_for)) { @@ -766,7 +766,7 @@ const HashMap<String, List<Ref<InputEvent>>> &InputMap::get_builtins_with_featur String fullname = E.key; Vector<String> split = fullname.split("."); - String name = split[0]; + const String &name = split[0]; String override_for = split.size() > 1 ? split[1] : String(); if (builtins_with_overrides.has(name) && override_for.is_empty()) { diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp index 265d9ef56c..5a4d6dd099 100644 --- a/core/io/file_access_pack.cpp +++ b/core/io/file_access_pack.cpp @@ -491,7 +491,7 @@ PackedData::PackedDir *DirAccessPack::_find_dir(String p_dir) { } for (int i = 0; i < paths.size(); i++) { - String p = paths[i]; + const String &p = paths[i]; if (p == ".") { continue; } else if (p == "..") { diff --git a/core/io/http_client.cpp b/core/io/http_client.cpp index 09505ea05d..833fd1adc3 100644 --- a/core/io/http_client.cpp +++ b/core/io/http_client.cpp @@ -73,7 +73,7 @@ String HTTPClient::query_string_from_dict(const Dictionary &p_dict) { Array keys = p_dict.keys(); for (int i = 0; i < keys.size(); ++i) { String encoded_key = String(keys[i]).uri_encode(); - Variant value = p_dict[keys[i]]; + const Variant &value = p_dict[keys[i]]; switch (value.get_type()) { case Variant::ARRAY: { // Repeat the key with every values diff --git a/core/io/image.cpp b/core/io/image.cpp index c72064e4f7..9aa7c9794a 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -3013,6 +3013,7 @@ void Image::fill_rect(const Rect2i &p_rect, const Color &p_color) { } ImageMemLoadFunc Image::_png_mem_loader_func = nullptr; +ImageMemLoadFunc Image::_png_mem_unpacker_func = nullptr; ImageMemLoadFunc Image::_jpg_mem_loader_func = nullptr; ImageMemLoadFunc Image::_webp_mem_loader_func = nullptr; ImageMemLoadFunc Image::_tga_mem_loader_func = nullptr; diff --git a/core/io/image.h b/core/io/image.h index a21d05187b..a5025ee8a1 100644 --- a/core/io/image.h +++ b/core/io/image.h @@ -145,6 +145,7 @@ public: }; static ImageMemLoadFunc _png_mem_loader_func; + static ImageMemLoadFunc _png_mem_unpacker_func; static ImageMemLoadFunc _jpg_mem_loader_func; static ImageMemLoadFunc _webp_mem_loader_func; static ImageMemLoadFunc _tga_mem_loader_func; diff --git a/core/io/resource_saver.cpp b/core/io/resource_saver.cpp index 1c6c18b015..51ebea7f2c 100644 --- a/core/io/resource_saver.cpp +++ b/core/io/resource_saver.cpp @@ -120,9 +120,8 @@ Error ResourceSaver::save(const Ref<Resource> &p_resource, const String &p_path, String local_path = ProjectSettings::get_singleton()->localize_path(path); - Ref<Resource> rwcopy = p_resource; if (p_flags & FLAG_CHANGE_PATH) { - rwcopy->set_path(local_path); + p_resource->set_path(local_path); } err = saver[i]->save(p_resource, path, p_flags); @@ -139,7 +138,7 @@ Error ResourceSaver::save(const Ref<Resource> &p_resource, const String &p_path, #endif if (p_flags & FLAG_CHANGE_PATH) { - rwcopy->set_path(old_path); + p_resource->set_path(old_path); } if (save_callback && path.begins_with("res://")) { diff --git a/core/math/a_star_grid_2d.cpp b/core/math/a_star_grid_2d.cpp index 379d34aa2a..d17f465ab8 100644 --- a/core/math/a_star_grid_2d.cpp +++ b/core/math/a_star_grid_2d.cpp @@ -106,16 +106,45 @@ Size2 AStarGrid2D::get_cell_size() const { return cell_size; } +void AStarGrid2D::set_cell_shape(CellShape p_cell_shape) { + if (cell_shape == p_cell_shape) { + return; + } + + ERR_FAIL_INDEX(p_cell_shape, CellShape::CELL_SHAPE_MAX); + cell_shape = p_cell_shape; + dirty = true; +} + +AStarGrid2D::CellShape AStarGrid2D::get_cell_shape() const { + return cell_shape; +} + void AStarGrid2D::update() { points.clear(); const int32_t end_x = region.get_end().x; const int32_t end_y = region.get_end().y; + const Vector2 half_cell_size = cell_size / 2; for (int32_t y = region.position.y; y < end_y; y++) { LocalVector<Point> line; for (int32_t x = region.position.x; x < end_x; x++) { - line.push_back(Point(Vector2i(x, y), offset + Vector2(x, y) * cell_size)); + Vector2 v = offset; + switch (cell_shape) { + case CELL_SHAPE_ISOMETRIC_RIGHT: + v += half_cell_size + Vector2(x + y, y - x) * half_cell_size; + break; + case CELL_SHAPE_ISOMETRIC_DOWN: + v += half_cell_size + Vector2(x - y, x + y) * half_cell_size; + break; + case CELL_SHAPE_SQUARE: + v += Vector2(x, y) * cell_size; + break; + default: + break; + } + line.push_back(Point(Vector2i(x, y), v)); } points.push_back(line); } @@ -620,6 +649,8 @@ void AStarGrid2D::_bind_methods() { ClassDB::bind_method(D_METHOD("get_offset"), &AStarGrid2D::get_offset); ClassDB::bind_method(D_METHOD("set_cell_size", "cell_size"), &AStarGrid2D::set_cell_size); ClassDB::bind_method(D_METHOD("get_cell_size"), &AStarGrid2D::get_cell_size); + ClassDB::bind_method(D_METHOD("set_cell_shape", "cell_shape"), &AStarGrid2D::set_cell_shape); + ClassDB::bind_method(D_METHOD("get_cell_shape"), &AStarGrid2D::get_cell_shape); ClassDB::bind_method(D_METHOD("is_in_bounds", "x", "y"), &AStarGrid2D::is_in_bounds); ClassDB::bind_method(D_METHOD("is_in_boundsv", "id"), &AStarGrid2D::is_in_boundsv); ClassDB::bind_method(D_METHOD("is_dirty"), &AStarGrid2D::is_dirty); @@ -651,6 +682,7 @@ void AStarGrid2D::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "size"), "set_size", "get_size"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_offset", "get_offset"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "cell_size"), "set_cell_size", "get_cell_size"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_shape", PROPERTY_HINT_ENUM, "Square,IsometricRight,IsometricDown"), "set_cell_shape", "get_cell_shape"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "jumping_enabled"), "set_jumping_enabled", "is_jumping_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "default_compute_heuristic", PROPERTY_HINT_ENUM, "Euclidean,Manhattan,Octile,Chebyshev"), "set_default_compute_heuristic", "get_default_compute_heuristic"); @@ -668,4 +700,9 @@ void AStarGrid2D::_bind_methods() { BIND_ENUM_CONSTANT(DIAGONAL_MODE_AT_LEAST_ONE_WALKABLE); BIND_ENUM_CONSTANT(DIAGONAL_MODE_ONLY_IF_NO_OBSTACLES); BIND_ENUM_CONSTANT(DIAGONAL_MODE_MAX); + + BIND_ENUM_CONSTANT(CELL_SHAPE_SQUARE); + BIND_ENUM_CONSTANT(CELL_SHAPE_ISOMETRIC_RIGHT); + BIND_ENUM_CONSTANT(CELL_SHAPE_ISOMETRIC_DOWN); + BIND_ENUM_CONSTANT(CELL_SHAPE_MAX); } diff --git a/core/math/a_star_grid_2d.h b/core/math/a_star_grid_2d.h index 619551b754..69cb77dd3e 100644 --- a/core/math/a_star_grid_2d.h +++ b/core/math/a_star_grid_2d.h @@ -56,11 +56,19 @@ public: HEURISTIC_MAX, }; + enum CellShape { + CELL_SHAPE_SQUARE, + CELL_SHAPE_ISOMETRIC_RIGHT, + CELL_SHAPE_ISOMETRIC_DOWN, + CELL_SHAPE_MAX, + }; + private: Rect2i region; Vector2 offset; Size2 cell_size = Size2(1, 1); bool dirty = false; + CellShape cell_shape = CELL_SHAPE_SQUARE; bool jumping_enabled = false; DiagonalMode diagonal_mode = DIAGONAL_MODE_ALWAYS; @@ -157,6 +165,9 @@ public: void set_cell_size(const Size2 &p_cell_size); Size2 get_cell_size() const; + void set_cell_shape(CellShape p_cell_shape); + CellShape get_cell_shape() const; + void update(); bool is_in_bounds(int32_t p_x, int32_t p_y) const; @@ -193,5 +204,6 @@ public: VARIANT_ENUM_CAST(AStarGrid2D::DiagonalMode); VARIANT_ENUM_CAST(AStarGrid2D::Heuristic); +VARIANT_ENUM_CAST(AStarGrid2D::CellShape) #endif // A_STAR_GRID_2D_H diff --git a/core/math/basis.h b/core/math/basis.h index b4d971464e..e3094114e8 100644 --- a/core/math/basis.h +++ b/core/math/basis.h @@ -136,6 +136,8 @@ struct _NO_DISCARD_ Basis { _FORCE_INLINE_ Basis operator-(const Basis &p_matrix) const; _FORCE_INLINE_ void operator*=(const real_t p_val); _FORCE_INLINE_ Basis operator*(const real_t p_val) const; + _FORCE_INLINE_ void operator/=(const real_t p_val); + _FORCE_INLINE_ Basis operator/(const real_t p_val) const; bool is_orthogonal() const; bool is_orthonormal() const; @@ -289,6 +291,18 @@ _FORCE_INLINE_ Basis Basis::operator*(const real_t p_val) const { return ret; } +_FORCE_INLINE_ void Basis::operator/=(const real_t p_val) { + rows[0] /= p_val; + rows[1] /= p_val; + rows[2] /= p_val; +} + +_FORCE_INLINE_ Basis Basis::operator/(const real_t p_val) const { + Basis ret(*this); + ret /= p_val; + return ret; +} + Vector3 Basis::xform(const Vector3 &p_vector) const { return Vector3( rows[0].dot(p_vector), diff --git a/core/math/geometry_3d.h b/core/math/geometry_3d.h index 99c554fe05..305a64e39c 100644 --- a/core/math/geometry_3d.h +++ b/core/math/geometry_3d.h @@ -31,6 +31,7 @@ #ifndef GEOMETRY_3D_H #define GEOMETRY_3D_H +#include "core/math/delaunay_3d.h" #include "core/math/face3.h" #include "core/object/object.h" #include "core/templates/local_vector.h" @@ -532,6 +533,21 @@ public: return clipped; } + static Vector<int32_t> tetrahedralize_delaunay(const Vector<Vector3> &p_points) { + Vector<Delaunay3D::OutputSimplex> tetr = Delaunay3D::tetrahedralize(p_points); + Vector<int32_t> tetrahedrons; + + tetrahedrons.resize(4 * tetr.size()); + int32_t *ptr = tetrahedrons.ptrw(); + for (int i = 0; i < tetr.size(); i++) { + *ptr++ = tetr[i].points[0]; + *ptr++ = tetr[i].points[1]; + *ptr++ = tetr[i].points[2]; + *ptr++ = tetr[i].points[3]; + } + return tetrahedrons; + } + // Create a "wrap" that encloses the given geometry. static Vector<Face3> wrap_geometry(Vector<Face3> p_array, real_t *p_error = nullptr); diff --git a/core/math/transform_2d.cpp b/core/math/transform_2d.cpp index bc4682fd90..a22d075b64 100644 --- a/core/math/transform_2d.cpp +++ b/core/math/transform_2d.cpp @@ -295,6 +295,18 @@ Transform2D Transform2D::operator*(const real_t p_val) const { return ret; } +void Transform2D::operator/=(const real_t p_val) { + columns[0] /= p_val; + columns[1] /= p_val; + columns[2] /= p_val; +} + +Transform2D Transform2D::operator/(const real_t p_val) const { + Transform2D ret(*this); + ret /= p_val; + return ret; +} + Transform2D::operator String() const { return "[X: " + columns[0].operator String() + ", Y: " + columns[1].operator String() + diff --git a/core/math/transform_2d.h b/core/math/transform_2d.h index dd1a33c5d5..9ff925f66f 100644 --- a/core/math/transform_2d.h +++ b/core/math/transform_2d.h @@ -109,6 +109,8 @@ struct _NO_DISCARD_ Transform2D { Transform2D operator*(const Transform2D &p_transform) const; void operator*=(const real_t p_val); Transform2D operator*(const real_t p_val) const; + void operator/=(const real_t p_val); + Transform2D operator/(const real_t p_val) const; Transform2D interpolate_with(const Transform2D &p_transform, const real_t p_c) const; diff --git a/core/math/transform_3d.cpp b/core/math/transform_3d.cpp index cdc94676c9..20713349d7 100644 --- a/core/math/transform_3d.cpp +++ b/core/math/transform_3d.cpp @@ -208,6 +208,17 @@ Transform3D Transform3D::operator*(const real_t p_val) const { return ret; } +void Transform3D::operator/=(const real_t p_val) { + basis /= p_val; + origin /= p_val; +} + +Transform3D Transform3D::operator/(const real_t p_val) const { + Transform3D ret(*this); + ret /= p_val; + return ret; +} + Transform3D::operator String() const { return "[X: " + basis.get_column(0).operator String() + ", Y: " + basis.get_column(1).operator String() + diff --git a/core/math/transform_3d.h b/core/math/transform_3d.h index 70141a3dbe..d1ec34d53f 100644 --- a/core/math/transform_3d.h +++ b/core/math/transform_3d.h @@ -104,6 +104,8 @@ struct _NO_DISCARD_ Transform3D { Transform3D operator*(const Transform3D &p_transform) const; void operator*=(const real_t p_val); Transform3D operator*(const real_t p_val) const; + void operator/=(const real_t p_val); + Transform3D operator/(const real_t p_val) const; Transform3D interpolate_with(const Transform3D &p_transform, real_t p_c) const; diff --git a/core/object/script_language.h b/core/object/script_language.h index 69da50f074..66106bf139 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -243,7 +243,7 @@ public: virtual void get_doc_comment_delimiters(List<String> *p_delimiters) const = 0; virtual void get_string_delimiters(List<String> *p_delimiters) const = 0; virtual Ref<Script> make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const { return Ref<Script>(); } - virtual Vector<ScriptTemplate> get_built_in_templates(StringName p_object) { return Vector<ScriptTemplate>(); } + virtual Vector<ScriptTemplate> get_built_in_templates(const StringName &p_object) { return Vector<ScriptTemplate>(); } virtual bool is_using_templates() { return false; } virtual bool validate(const String &p_script, const String &p_path = "", List<String> *r_functions = nullptr, List<ScriptError> *r_errors = nullptr, List<Warning> *r_warnings = nullptr, HashSet<int> *r_safe_lines = nullptr) const = 0; virtual String validate_path(const String &p_path) const { return ""; } diff --git a/core/object/script_language_extension.h b/core/object/script_language_extension.h index 852b2aebd8..8b01667519 100644 --- a/core/object/script_language_extension.h +++ b/core/object/script_language_extension.h @@ -265,7 +265,7 @@ public: GDVIRTUAL1RC(TypedArray<Dictionary>, _get_built_in_templates, StringName) - virtual Vector<ScriptTemplate> get_built_in_templates(StringName p_object) override { + virtual Vector<ScriptTemplate> get_built_in_templates(const StringName &p_object) override { TypedArray<Dictionary> ret; GDVIRTUAL_REQUIRED_CALL(_get_built_in_templates, p_object, ret); Vector<ScriptTemplate> stret; diff --git a/core/os/keyboard.cpp b/core/os/keyboard.cpp index 6078882839..973c216d15 100644 --- a/core/os/keyboard.cpp +++ b/core/os/keyboard.cpp @@ -410,7 +410,7 @@ Key find_keycode(const String &p_codestr) { return keycode; } - String last_part = code_parts[code_parts.size() - 1]; + const String &last_part = code_parts[code_parts.size() - 1]; const _KeyCodeText *kct = &_keycodes[0]; while (kct->text) { @@ -422,7 +422,7 @@ Key find_keycode(const String &p_codestr) { } for (int part = 0; part < code_parts.size() - 1; part++) { - String code_part = code_parts[part]; + const String &code_part = code_parts[part]; if (code_part.nocasecmp_to(find_keycode_name(Key::SHIFT)) == 0) { keycode |= KeyModifierMask::SHIFT; } else if (code_part.nocasecmp_to(find_keycode_name(Key::CTRL)) == 0) { diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index a24cff4f11..6afe28a6a7 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -1117,7 +1117,7 @@ String String::get_with_code_lines() const { return ret; } -int String::get_slice_count(String p_splitter) const { +int String::get_slice_count(const String &p_splitter) const { if (is_empty()) { return 0; } @@ -1136,7 +1136,7 @@ int String::get_slice_count(String p_splitter) const { return slices; } -String String::get_slice(String p_splitter, int p_slice) const { +String String::get_slice(const String &p_splitter, int p_slice) const { if (is_empty() || p_splitter.is_empty()) { return ""; } @@ -3515,7 +3515,7 @@ bool String::matchn(const String &p_wildcard) const { return _wildcard_match(p_wildcard.get_data(), get_data(), false); } -String String::format(const Variant &values, String placeholder) const { +String String::format(const Variant &values, const String &placeholder) const { String new_string = String(this->ptr()); if (values.get_type() == Variant::ARRAY) { @@ -3961,27 +3961,42 @@ static int _humanize_digits(int p_num) { } String String::humanize_size(uint64_t p_size) { + int magnitude = 0; uint64_t _div = 1; - Vector<String> prefixes; - prefixes.push_back(RTR("B")); - prefixes.push_back(RTR("KiB")); - prefixes.push_back(RTR("MiB")); - prefixes.push_back(RTR("GiB")); - prefixes.push_back(RTR("TiB")); - prefixes.push_back(RTR("PiB")); - prefixes.push_back(RTR("EiB")); - - int prefix_idx = 0; - - while (prefix_idx < prefixes.size() - 1 && p_size > (_div * 1024)) { + while (p_size > _div * 1024 && magnitude < 6) { _div *= 1024; - prefix_idx++; + magnitude++; } - const int digits = prefix_idx > 0 ? _humanize_digits(p_size / _div) : 0; - const double divisor = prefix_idx > 0 ? _div : 1; + if (magnitude == 0) { + return String::num(p_size) + " " + RTR("B"); + } else { + String suffix; + switch (magnitude) { + case 1: + suffix = RTR("KiB"); + break; + case 2: + suffix = RTR("MiB"); + break; + case 3: + suffix = RTR("GiB"); + break; + case 4: + suffix = RTR("TiB"); + break; + case 5: + suffix = RTR("PiB"); + break; + case 6: + suffix = RTR("EiB"); + break; + } - return String::num(p_size / divisor).pad_decimals(digits) + " " + prefixes[prefix_idx]; + const double divisor = _div; + const int digits = _humanize_digits(p_size / _div); + return String::num(p_size / divisor).pad_decimals(digits) + " " + suffix; + } } bool String::is_absolute_path() const { @@ -4569,7 +4584,7 @@ bool String::is_valid_ip_address() const { if (find(":") >= 0) { Vector<String> ip = split(":"); for (int i = 0; i < ip.size(); i++) { - String n = ip[i]; + const String &n = ip[i]; if (n.is_empty()) { continue; } @@ -4591,7 +4606,7 @@ bool String::is_valid_ip_address() const { return false; } for (int i = 0; i < ip.size(); i++) { - String n = ip[i]; + const String &n = ip[i]; if (!n.is_valid_int()) { return false; } @@ -5208,7 +5223,7 @@ String String::sprintf(const Array &values, bool *error) const { return formatted; } -String String::quote(String quotechar) const { +String String::quote(const String "echar) const { return quotechar + *this + quotechar; } diff --git a/core/string/ustring.h b/core/string/ustring.h index 897b06fc6d..5ed20396d6 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -299,7 +299,7 @@ public: bool is_quoted() const; Vector<String> bigrams() const; float similarity(const String &p_string) const; - String format(const Variant &values, String placeholder = "{_}") const; + String format(const Variant &values, const String &placeholder = "{_}") const; String replace_first(const String &p_key, const String &p_with) const; String replace(const String &p_key, const String &p_with) const; String replace(const char *p_key, const char *p_with) const; @@ -315,7 +315,7 @@ public: String lpad(int min_length, const String &character = " ") const; String rpad(int min_length, const String &character = " ") const; String sprintf(const Array &values, bool *error) const; - String quote(String quotechar = "\"") const; + String quote(const String "echar = "\"") const; String unquote() const; static String num(double p_num, int p_decimals = -1); static String num_scientific(double p_num); @@ -349,8 +349,8 @@ public: String to_snake_case() const; String get_with_code_lines() const; - int get_slice_count(String p_splitter) const; - String get_slice(String p_splitter, int p_slice) const; + int get_slice_count(const String &p_splitter) const; + String get_slice(const String &p_splitter, int p_slice) const; String get_slicec(char32_t p_splitter, int p_slice) const; Vector<String> split(const String &p_splitter = "", bool p_allow_empty = true, int p_maxsplit = 0) const; diff --git a/core/templates/self_list.h b/core/templates/self_list.h index fdf91beacc..afa0501c75 100644 --- a/core/templates/self_list.h +++ b/core/templates/self_list.h @@ -159,6 +159,9 @@ public: _FORCE_INLINE_ SelfList<T> *first() { return _first; } _FORCE_INLINE_ const SelfList<T> *first() const { return _first; } + // Forbid copying, which has broken behavior. + void operator=(const List &) = delete; + _FORCE_INLINE_ List() {} _FORCE_INLINE_ ~List() { // A self list must be empty on destruction. @@ -185,6 +188,9 @@ public: _FORCE_INLINE_ const SelfList<T> *prev() const { return _prev; } _FORCE_INLINE_ T *self() const { return _self; } + // Forbid copying, which has broken behavior. + void operator=(const SelfList<T> &) = delete; + _FORCE_INLINE_ SelfList(T *p_self) { _self = p_self; } diff --git a/core/typedefs.h b/core/typedefs.h index 803b2e5ae0..8b04960b9a 100644 --- a/core/typedefs.h +++ b/core/typedefs.h @@ -233,6 +233,10 @@ constexpr T get_num_bits(T x) { #define BSWAP16(x) __builtin_bswap16(x) #define BSWAP32(x) __builtin_bswap32(x) #define BSWAP64(x) __builtin_bswap64(x) +#elif defined(_MSC_VER) +#define BSWAP16(x) _byteswap_ushort(x) +#define BSWAP32(x) _byteswap_ulong(x) +#define BSWAP64(x) _byteswap_uint64(x) #else static inline uint16_t BSWAP16(uint16_t x) { return (x >> 8) | (x << 8); diff --git a/core/variant/variant.h b/core/variant/variant.h index e93733040a..602d287f22 100644 --- a/core/variant/variant.h +++ b/core/variant/variant.h @@ -708,9 +708,20 @@ public: bool has_key(const Variant &p_key, bool &r_valid) const; /* Generic */ - - void set(const Variant &p_index, const Variant &p_value, bool *r_valid = nullptr); - Variant get(const Variant &p_index, bool *r_valid = nullptr) const; + enum VariantSetError { + SET_OK, + SET_KEYED_ERR, + SET_NAMED_ERR, + SET_INDEXED_ERR + }; + enum VariantGetError { + GET_OK, + GET_KEYED_ERR, + GET_NAMED_ERR, + GET_INDEXED_ERR + }; + void set(const Variant &p_index, const Variant &p_value, bool *r_valid = nullptr, VariantSetError *err_code = nullptr); + Variant get(const Variant &p_index, bool *r_valid = nullptr, VariantGetError *err_code = nullptr) const; bool in(const Variant &p_index, bool *r_valid = nullptr) const; bool iter_init(Variant &r_iter, bool &r_valid) const; @@ -770,8 +781,8 @@ public: static Variant get_constant_value(Variant::Type p_type, const StringName &p_value, bool *r_valid = nullptr); static void get_enums_for_type(Variant::Type p_type, List<StringName> *p_enums); - static void get_enumerations_for_enum(Variant::Type p_type, StringName p_enum_name, List<StringName> *p_enumerations); - static int get_enum_value(Variant::Type p_type, StringName p_enum_name, StringName p_enumeration, bool *r_valid = nullptr); + static void get_enumerations_for_enum(Variant::Type p_type, const StringName &p_enum_name, List<StringName> *p_enumerations); + static int get_enum_value(Variant::Type p_type, const StringName &p_enum_name, const StringName &p_enumeration, bool *r_valid = nullptr); typedef String (*ObjectDeConstruct)(const Variant &p_object, void *ud); typedef void (*ObjectConstruct)(const String &p_text, void *ud, Variant &r_value); diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index 1daad2058e..03836985f3 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1071,14 +1071,14 @@ struct _VariantCall { static ConstantData *constant_data; - static void add_constant(int p_type, StringName p_constant_name, int64_t p_constant_value) { + static void add_constant(int p_type, const StringName &p_constant_name, int64_t p_constant_value) { constant_data[p_type].value[p_constant_name] = p_constant_value; #ifdef DEBUG_ENABLED constant_data[p_type].value_ordered.push_back(p_constant_name); #endif } - static void add_variant_constant(int p_type, StringName p_constant_name, const Variant &p_constant_value) { + static void add_variant_constant(int p_type, const StringName &p_constant_name, const Variant &p_constant_value) { constant_data[p_type].variant_value[p_constant_name] = p_constant_value; #ifdef DEBUG_ENABLED constant_data[p_type].variant_value_ordered.push_back(p_constant_name); @@ -1091,7 +1091,7 @@ struct _VariantCall { static EnumData *enum_data; - static void add_enum_constant(int p_type, StringName p_enum_type_name, StringName p_enumeration_name, int p_enum_value) { + static void add_enum_constant(int p_type, const StringName &p_enum_type_name, const StringName &p_enumeration_name, int p_enum_value) { enum_data[p_type].value[p_enum_type_name][p_enumeration_name] = p_enum_value; } }; @@ -1504,7 +1504,7 @@ void Variant::get_enums_for_type(Variant::Type p_type, List<StringName> *p_enums } } -void Variant::get_enumerations_for_enum(Variant::Type p_type, StringName p_enum_name, List<StringName> *p_enumerations) { +void Variant::get_enumerations_for_enum(Variant::Type p_type, const StringName &p_enum_name, List<StringName> *p_enumerations) { ERR_FAIL_INDEX(p_type, Variant::VARIANT_MAX); _VariantCall::EnumData &enum_data = _VariantCall::enum_data[p_type]; @@ -1516,7 +1516,7 @@ void Variant::get_enumerations_for_enum(Variant::Type p_type, StringName p_enum_ } } -int Variant::get_enum_value(Variant::Type p_type, StringName p_enum_name, StringName p_enumeration, bool *r_valid) { +int Variant::get_enum_value(Variant::Type p_type, const StringName &p_enum_name, const StringName &p_enumeration, bool *r_valid) { if (r_valid) { *r_valid = false; } diff --git a/core/variant/variant_op.cpp b/core/variant/variant_op.cpp index aed83ac010..4f9c38dc4c 100644 --- a/core/variant/variant_op.cpp +++ b/core/variant/variant_op.cpp @@ -412,6 +412,15 @@ void Variant::_register_variant_operators() { register_op<OperatorEvaluatorDivNZ<Vector4, Vector4i, double>>(Variant::OP_DIVIDE, Variant::VECTOR4I, Variant::FLOAT); register_op<OperatorEvaluatorDivNZ<Vector4i, Vector4i, int64_t>>(Variant::OP_DIVIDE, Variant::VECTOR4I, Variant::INT); + register_op<OperatorEvaluatorDiv<Transform2D, Transform2D, int64_t>>(Variant::OP_DIVIDE, Variant::TRANSFORM2D, Variant::INT); + register_op<OperatorEvaluatorDiv<Transform2D, Transform2D, double>>(Variant::OP_DIVIDE, Variant::TRANSFORM2D, Variant::FLOAT); + + register_op<OperatorEvaluatorDiv<Transform3D, Transform3D, int64_t>>(Variant::OP_DIVIDE, Variant::TRANSFORM3D, Variant::INT); + register_op<OperatorEvaluatorDiv<Transform3D, Transform3D, double>>(Variant::OP_DIVIDE, Variant::TRANSFORM3D, Variant::FLOAT); + + register_op<OperatorEvaluatorDiv<Basis, Basis, int64_t>>(Variant::OP_DIVIDE, Variant::BASIS, Variant::INT); + register_op<OperatorEvaluatorDiv<Basis, Basis, double>>(Variant::OP_DIVIDE, Variant::BASIS, Variant::FLOAT); + register_op<OperatorEvaluatorDiv<Quaternion, Quaternion, double>>(Variant::OP_DIVIDE, Variant::QUATERNION, Variant::FLOAT); register_op<OperatorEvaluatorDiv<Quaternion, Quaternion, int64_t>>(Variant::OP_DIVIDE, Variant::QUATERNION, Variant::INT); diff --git a/core/variant/variant_setget.cpp b/core/variant/variant_setget.cpp index b0e0a885f4..50c9c10987 100644 --- a/core/variant/variant_setget.cpp +++ b/core/variant/variant_setget.cpp @@ -1171,30 +1171,48 @@ bool Variant::has_key(const Variant &p_key, bool &r_valid) const { } } -void Variant::set(const Variant &p_index, const Variant &p_value, bool *r_valid) { +void Variant::set(const Variant &p_index, const Variant &p_value, bool *r_valid, VariantSetError *err_code) { + if (err_code) { + *err_code = VariantSetError::SET_OK; + } if (type == DICTIONARY || type == OBJECT) { bool valid; set_keyed(p_index, p_value, valid); if (r_valid) { *r_valid = valid; + if (!valid && err_code) { + *err_code = VariantSetError::SET_KEYED_ERR; + } } } else { bool valid = false; if (p_index.get_type() == STRING_NAME) { set_named(*VariantGetInternalPtr<StringName>::get_ptr(&p_index), p_value, valid); + if (!valid && err_code) { + *err_code = VariantSetError::SET_NAMED_ERR; + } } else if (p_index.get_type() == INT) { bool obb; set_indexed(*VariantGetInternalPtr<int64_t>::get_ptr(&p_index), p_value, valid, obb); if (obb) { valid = false; + if (err_code) { + *err_code = VariantSetError::SET_INDEXED_ERR; + } } } else if (p_index.get_type() == STRING) { // less efficient version of named set_named(*VariantGetInternalPtr<String>::get_ptr(&p_index), p_value, valid); + if (!valid && err_code) { + *err_code = VariantSetError::SET_NAMED_ERR; + } } else if (p_index.get_type() == FLOAT) { // less efficient version of indexed bool obb; set_indexed(*VariantGetInternalPtr<double>::get_ptr(&p_index), p_value, valid, obb); if (obb) { valid = false; + if (err_code) { + *err_code = VariantSetError::SET_INDEXED_ERR; + } } } if (r_valid) { @@ -1203,31 +1221,49 @@ void Variant::set(const Variant &p_index, const Variant &p_value, bool *r_valid) } } -Variant Variant::get(const Variant &p_index, bool *r_valid) const { +Variant Variant::get(const Variant &p_index, bool *r_valid, VariantGetError *err_code) const { + if (err_code) { + *err_code = VariantGetError::GET_OK; + } Variant ret; if (type == DICTIONARY || type == OBJECT) { bool valid; ret = get_keyed(p_index, valid); if (r_valid) { *r_valid = valid; + if (!valid && err_code) { + *err_code = VariantGetError::GET_KEYED_ERR; + } } } else { bool valid = false; if (p_index.get_type() == STRING_NAME) { ret = get_named(*VariantGetInternalPtr<StringName>::get_ptr(&p_index), valid); + if (!valid && err_code) { + *err_code = VariantGetError::GET_NAMED_ERR; + } } else if (p_index.get_type() == INT) { bool obb; ret = get_indexed(*VariantGetInternalPtr<int64_t>::get_ptr(&p_index), valid, obb); if (obb) { valid = false; + if (err_code) { + *err_code = VariantGetError::GET_INDEXED_ERR; + } } } else if (p_index.get_type() == STRING) { // less efficient version of named ret = get_named(*VariantGetInternalPtr<String>::get_ptr(&p_index), valid); + if (!valid && err_code) { + *err_code = VariantGetError::GET_NAMED_ERR; + } } else if (p_index.get_type() == FLOAT) { // less efficient version of indexed bool obb; ret = get_indexed(*VariantGetInternalPtr<double>::get_ptr(&p_index), valid, obb); if (obb) { valid = false; + if (err_code) { + *err_code = VariantGetError::GET_INDEXED_ERR; + } } } if (r_valid) { diff --git a/doc/classes/AStarGrid2D.xml b/doc/classes/AStarGrid2D.xml index 277630588a..7764233116 100644 --- a/doc/classes/AStarGrid2D.xml +++ b/doc/classes/AStarGrid2D.xml @@ -157,6 +157,9 @@ </method> </methods> <members> + <member name="cell_shape" type="int" setter="set_cell_shape" getter="get_cell_shape" enum="AStarGrid2D.CellShape" default="0"> + The cell shape. Affects how the positions are placed in the grid. If changed, [method update] needs to be called before finding the next path. + </member> <member name="cell_size" type="Vector2" setter="set_cell_size" getter="get_cell_size" default="Vector2(1, 1)"> The size of the point cell which will be applied to calculate the resulting point position returned by [method get_point_path]. If changed, [method update] needs to be called before finding the next path. </member> @@ -238,5 +241,17 @@ <constant name="DIAGONAL_MODE_MAX" value="4" enum="DiagonalMode"> Represents the size of the [enum DiagonalMode] enum. </constant> + <constant name="CELL_SHAPE_SQUARE" value="0" enum="CellShape"> + Rectangular cell shape. + </constant> + <constant name="CELL_SHAPE_ISOMETRIC_RIGHT" value="1" enum="CellShape"> + Diamond cell shape (for isometric look). Cell coordinates layout where the horizontal axis goes up-right, and the vertical one goes down-right. + </constant> + <constant name="CELL_SHAPE_ISOMETRIC_DOWN" value="2" enum="CellShape"> + Diamond cell shape (for isometric look). Cell coordinates layout where the horizontal axis goes down-right, and the vertical one goes down-left. + </constant> + <constant name="CELL_SHAPE_MAX" value="3" enum="CellShape"> + Represents the size of the [enum CellShape] enum. + </constant> </constants> </class> diff --git a/doc/classes/Array.xml b/doc/classes/Array.xml index fccf16fcaa..3252cbf840 100644 --- a/doc/classes/Array.xml +++ b/doc/classes/Array.xml @@ -544,6 +544,7 @@ <param index="0" name="size" type="int" /> <description> Resizes the array to contain a different number of elements. If the array size is smaller, elements are cleared, if bigger, new elements are [code]null[/code]. Returns [constant OK] on success, or one of the other [enum Error] values if the operation failed. + Calling [method resize] once and assigning the new values is faster than adding new elements one by one. [b]Note:[/b] This method acts in-place and doesn't return a modified array. </description> </method> diff --git a/doc/classes/Basis.xml b/doc/classes/Basis.xml index 7ebbc6c388..c271e23f2e 100644 --- a/doc/classes/Basis.xml +++ b/doc/classes/Basis.xml @@ -71,12 +71,20 @@ <param index="1" name="order" type="int" default="2" /> <description> Constructs a pure rotation Basis matrix from Euler angles in the specified Euler rotation order. By default, use YXZ order (most common). See the [enum EulerOrder] enum for possible values. - [codeblock] + [codeblocks] + [gdscript] # Creates a Basis whose z axis points down. var my_basis = Basis.from_euler(Vector3(TAU / 4, 0, 0)) print(my_basis.z) # Prints (0, -1, 0). - [/codeblock] + [/gdscript] + [csharp] + // Creates a Basis whose z axis points down. + var myBasis = Basis.FromEuler(new Vector3(Mathf.Tau / 4.0f, 0.0f, 0.0f)); + + GD.Print(myBasis.Z); // Prints (0, -1, 0). + [/csharp] + [/codeblocks] </description> </method> <method name="from_scale" qualifiers="static"> @@ -84,13 +92,22 @@ <param index="0" name="scale" type="Vector3" /> <description> Constructs a pure scale basis matrix with no rotation or shearing. The scale values are set as the diagonal of the matrix, and the other parts of the matrix are zero. - [codeblock] + [codeblocks] + [gdscript] var my_basis = Basis.from_scale(Vector3(2, 4, 8)) print(my_basis.x) # Prints (2, 0, 0). print(my_basis.y) # Prints (0, 4, 0). print(my_basis.z) # Prints (0, 0, 8). - [/codeblock] + [/gdscript] + [csharp] + var myBasis = Basis.FromScale(new Vector3(2.0f, 4.0f, 8.0f)); + + GD.Print(myBasis.X); // Prints (2, 0, 0). + GD.Print(myBasis.Y); // Prints (0, 4, 0). + GD.Print(myBasis.Z); // Prints (0, 0, 8). + [/csharp] + [/codeblocks] </description> </method> <method name="get_euler" qualifiers="const"> @@ -111,7 +128,8 @@ <return type="Vector3" /> <description> Assuming that the matrix is the combination of a rotation and scaling, return the absolute value of scaling factors along each axis. - [codeblock] + [codeblocks] + [gdscript] var my_basis = Basis( Vector3(2, 0, 0), Vector3(0, 4, 0), @@ -122,7 +140,20 @@ my_basis = my_basis.rotated(Vector3.RIGHT, TAU / 4) print(my_basis.get_scale()) # Prints (2, 4, 8). - [/codeblock] + [/gdscript] + [csharp] + var myBasis = new Basis( + Vector3(2.0f, 0.0f, 0.0f), + Vector3(0.0f, 4.0f, 0.0f), + Vector3(0.0f, 0.0f, 8.0f) + ); + // Rotating the Basis in any way preserves its scale. + myBasis = myBasis.Rotated(Vector3.Up, Mathf.Tau / 2.0f); + myBasis = myBasis.Rotated(Vector3.Right, Mathf.Tau / 4.0f); + + GD.Print(myBasis.Scale); // Prints (2, 4, 8). + [/csharp] + [/codeblocks] </description> </method> <method name="inverse" qualifiers="const"> @@ -165,14 +196,25 @@ <return type="Basis" /> <description> Returns the orthonormalized version of the matrix (useful to call from time to time to avoid rounding error for orthogonal matrices). This performs a Gram-Schmidt orthonormalization on the basis of the matrix. - [codeblock] + [codeblocks] + [gdscript] # Rotate this Node3D every frame. func _process(delta): basis = basis.rotated(Vector3.UP, TAU * delta) basis = basis.rotated(Vector3.RIGHT, TAU * delta) basis = basis.orthonormalized() - [/codeblock] + [/gdscript] + [csharp] + // Rotate this Node3D every frame. + public override void _Process(double delta) + { + Basis = Basis.Rotated(Vector3.Up, Mathf.Tau * (float)delta) + .Rotated(Vector3.Right, Mathf.Tau * (float)delta) + .Orthonormalized(); + } + [/csharp] + [/codeblocks] </description> </method> <method name="rotated" qualifiers="const"> @@ -181,14 +223,24 @@ <param index="1" name="angle" type="float" /> <description> Introduce an additional rotation around the given axis by [param angle] (in radians). The axis must be a normalized vector. - [codeblock] + [codeblocks] + [gdscript] var my_basis = Basis.IDENTITY var angle = TAU / 2 - my_basis = my_basis.rotated(Vector3.UP, angle) # Rotate around the up axis (yaw) - my_basis = my_basis.rotated(Vector3.RIGHT, angle) # Rotate around the right axis (pitch) - my_basis = my_basis.rotated(Vector3.BACK, angle) # Rotate around the back axis (roll) - [/codeblock] + my_basis = my_basis.rotated(Vector3.UP, angle) # Rotate around the up axis (yaw). + my_basis = my_basis.rotated(Vector3.RIGHT, angle) # Rotate around the right axis (pitch). + my_basis = my_basis.rotated(Vector3.BACK, angle) # Rotate around the back axis (roll). + [/gdscript] + [csharp] + var myBasis = Basis.Identity; + var angle = Mathf.Tau / 2.0f; + + myBasis = myBasis.Rotated(Vector3.Up, angle); // Rotate around the up axis (yaw). + myBasis = myBasis.Rotated(Vector3.Right, angle); // Rotate around the right axis (pitch). + myBasis = myBasis.Rotated(Vector3.Back, angle); // Rotate around the back axis (roll). + [/csharp] + [/codeblocks] </description> </method> <method name="scaled" qualifiers="const"> @@ -196,7 +248,8 @@ <param index="0" name="scale" type="Vector3" /> <description> Introduce an additional scaling specified by the given 3D scaling factor. - [codeblock] + [codeblocks] + [gdscript] var my_basis = Basis( Vector3(1, 1, 1), Vector3(2, 2, 2), @@ -207,7 +260,20 @@ print(my_basis.x) # Prints (0, 2, -2). print(my_basis.y) # Prints (0, 4, -4). print(my_basis.z) # Prints (0, 6, -6). - [/codeblock] + [/gdscript] + [csharp] + var myBasis = new Basis( + new Vector3(1.0f, 1.0f, 1.0f), + new Vector3(2.0f, 2.0f, 2.0f), + new Vector3(3.0f, 3.0f, 3.0f) + ); + myBasis = myBasis.Scaled(new Vector3(0.0f, 2.0f, -2.0f)); + + GD.Print(myBasis.X); // Prints (0, 2, -2). + GD.Print(myBasis.Y); // Prints (0, 4, -4). + GD.Print(myBasis.Z); // Prints (0, 6, -6). + [/csharp] + [/codeblocks] </description> </method> <method name="slerp" qualifiers="const"> @@ -243,7 +309,8 @@ <return type="Basis" /> <description> Returns the transposed version of the matrix. - [codeblock] + [codeblocks] + [gdscript] var my_basis = Basis( Vector3(1, 2, 3), Vector3(4, 5, 6), @@ -254,7 +321,20 @@ print(my_basis.x) # Prints (1, 4, 7). print(my_basis.y) # Prints (2, 5, 8). print(my_basis.z) # Prints (3, 6, 9). - [/codeblock] + [/gdscript] + [csharp] + var myBasis = new Basis( + new Vector3(1.0f, 2.0f, 3.0f), + new Vector3(4.0f, 5.0f, 6.0f), + new Vector3(7.0f, 8.0f, 9.0f) + ); + myBasis = myBasis.Transposed(); + + GD.Print(myBasis.X); // Prints (1, 4, 7). + GD.Print(myBasis.Y); // Prints (2, 5, 8). + GD.Print(myBasis.Z); // Prints (3, 6, 9). + [/csharp] + [/codeblocks] </description> </method> </methods> @@ -321,6 +401,20 @@ This operator multiplies all components of the [Basis], which scales it uniformly. </description> </operator> + <operator name="operator /"> + <return type="Basis" /> + <param index="0" name="right" type="float" /> + <description> + This operator divides all components of the [Basis], which inversely scales it uniformly. + </description> + </operator> + <operator name="operator /"> + <return type="Basis" /> + <param index="0" name="right" type="int" /> + <description> + This operator divides all components of the [Basis], which inversely scales it uniformly. + </description> + </operator> <operator name="operator =="> <return type="bool" /> <param index="0" name="right" type="Basis" /> diff --git a/doc/classes/Curve2D.xml b/doc/classes/Curve2D.xml index 197d03f0d8..0e75c65f50 100644 --- a/doc/classes/Curve2D.xml +++ b/doc/classes/Curve2D.xml @@ -107,16 +107,14 @@ <param index="0" name="offset" type="float" default="0.0" /> <param index="1" name="cubic" type="bool" default="false" /> <description> - Similar to [method sample_baked], but returns [Transform2D] that includes a rotation along the curve, with [member Transform2D.origin] as the point position, [member Transform2D.x] as the sideways vector, and [member Transform2D.y] as the forward vector. Returns an empty transform if the length of the curve is [code]0[/code]. + Similar to [method sample_baked], but returns [Transform2D] that includes a rotation along the curve, with [member Transform2D.origin] as the point position and the [member Transform2D.x] vector pointing in the direction of the path at that point. Returns an empty transform if the length of the curve is [code]0[/code]. [codeblock] var baked = curve.sample_baked_with_rotation(offset) - # This will rotate and position the node with the up direction pointing along the curve. + # The returned Transform2D can be set directly. + transform = baked + # You can also read the origin and rotation separately from the returned Transform2D. position = baked.get_origin() rotation = baked.get_rotation() - # Alternatively, not preserving scale. - transform = baked * Transform2D.FLIP_Y - # To match the rotation of PathFollow2D, not preserving scale. - transform = Transform2D(baked.y, baked.x, baked.origin) [/codeblock] </description> </method> diff --git a/doc/classes/EditorInspector.xml b/doc/classes/EditorInspector.xml index cfdc172fd1..6b25be490e 100644 --- a/doc/classes/EditorInspector.xml +++ b/doc/classes/EditorInspector.xml @@ -28,6 +28,7 @@ </method> </methods> <members> + <member name="follow_focus" type="bool" setter="set_follow_focus" getter="is_following_focus" overrides="ScrollContainer" default="true" /> <member name="horizontal_scroll_mode" type="int" setter="set_horizontal_scroll_mode" getter="get_horizontal_scroll_mode" overrides="ScrollContainer" enum="ScrollContainer.ScrollMode" default="0" /> </members> <signals> diff --git a/doc/classes/Geometry3D.xml b/doc/classes/Geometry3D.xml index a85d17d925..9f87682983 100644 --- a/doc/classes/Geometry3D.xml +++ b/doc/classes/Geometry3D.xml @@ -142,5 +142,12 @@ Tests if the segment ([param from], [param to]) intersects the triangle [param a], [param b], [param c]. If yes, returns the point of intersection as [Vector3]. If no intersection takes place, returns [code]null[/code]. </description> </method> + <method name="tetrahedralize_delaunay"> + <return type="PackedInt32Array" /> + <param index="0" name="points" type="PackedVector3Array" /> + <description> + Tetrahedralizes the volume specified by a discrete set of [param points] in 3D space, ensuring that no point lies within the circumsphere of any resulting tetrahedron. The method returns a [PackedInt32Array] where each tetrahedron consists of four consecutive point indices into the [param points] array (resulting in an array with [code]n * 4[/code] elements, where [code]n[/code] is the number of tetrahedra found). If the tetrahedralization is unsuccessful, an empty [PackedInt32Array] is returned. + </description> + </method> </methods> </class> diff --git a/doc/classes/GraphNode.xml b/doc/classes/GraphNode.xml index 9e1392567a..5bb3a25158 100644 --- a/doc/classes/GraphNode.xml +++ b/doc/classes/GraphNode.xml @@ -116,6 +116,20 @@ Returns the right (output) [Color] of the slot with the given [param slot_index]. </description> </method> + <method name="get_slot_custom_icon_left" qualifiers="const"> + <return type="Texture2D" /> + <param index="0" name="slot_index" type="int" /> + <description> + Returns the left (input) custom [Texture2D] of the slot with the given [param slot_index]. + </description> + </method> + <method name="get_slot_custom_icon_right" qualifiers="const"> + <return type="Texture2D" /> + <param index="0" name="slot_index" type="int" /> + <description> + Returns the right (output) custom [Texture2D] of the slot with the given [param slot_index]. + </description> + </method> <method name="get_slot_type_left" qualifiers="const"> <return type="int" /> <param index="0" name="slot_index" type="int" /> @@ -195,6 +209,22 @@ Sets the [Color] of the right (output) side of the slot with the given [param slot_index] to [param color]. </description> </method> + <method name="set_slot_custom_icon_left"> + <return type="void" /> + <param index="0" name="slot_index" type="int" /> + <param index="1" name="custom_icon" type="Texture2D" /> + <description> + Sets the custom [Texture2D] of the left (input) side of the slot with the given [param slot_index] to [param custom_icon]. + </description> + </method> + <method name="set_slot_custom_icon_right"> + <return type="void" /> + <param index="0" name="slot_index" type="int" /> + <param index="1" name="custom_icon" type="Texture2D" /> + <description> + Sets the custom [Texture2D] of the right (output) side of the slot with the given [param slot_index] to [param custom_icon]. + </description> + </method> <method name="set_slot_draw_stylebox"> <return type="void" /> <param index="0" name="slot_index" type="int" /> diff --git a/doc/classes/Image.xml b/doc/classes/Image.xml index 5677999d5d..872277b033 100644 --- a/doc/classes/Image.xml +++ b/doc/classes/Image.xml @@ -731,7 +731,7 @@ On the other hand, if the image already has mipmaps, they will be used, and a new set will be generated for the resulting image. </constant> <constant name="INTERPOLATE_LANCZOS" value="4" enum="Interpolation"> - Performs Lanczos interpolation. This is the slowest image resizing mode, but it typically gives the best results, especially when downscalng images. + Performs Lanczos interpolation. This is the slowest image resizing mode, but it typically gives the best results, especially when downscaling images. </constant> <constant name="ALPHA_NONE" value="0" enum="AlphaMode"> Image does not have alpha. diff --git a/doc/classes/LightmapGI.xml b/doc/classes/LightmapGI.xml index ec75d85898..fa3bfe513f 100644 --- a/doc/classes/LightmapGI.xml +++ b/doc/classes/LightmapGI.xml @@ -65,6 +65,9 @@ The quality preset to use when baking lightmaps. This affects bake times, but output file sizes remain mostly identical across quality levels. To further speed up bake times, decrease [member bounces], disable [member use_denoiser] and increase the lightmap texel size on 3D scenes in the Import doc. </member> + <member name="texel_scale" type="float" setter="set_texel_scale" getter="get_texel_scale" default="1.0"> + Scales the lightmap texel density of all meshes for the current bake. This is a multiplier that builds upon the existing lightmap texel size defined in each imported 3D scene, along with the per-mesh density multiplier (which is designed to be used when the same mesh is used at different scales). Lower values will result in faster bake times. + </member> <member name="use_denoiser" type="bool" setter="set_use_denoiser" getter="is_using_denoiser" default="true"> If [code]true[/code], uses a GPU-based denoising algorithm on the generated lightmap. This eliminates most noise within the generated lightmap at the cost of longer bake times. File sizes are generally not impacted significantly by the use of a denoiser, although lossless compression may do a better job at compressing a denoised image. </member> diff --git a/doc/classes/NavigationServer3D.xml b/doc/classes/NavigationServer3D.xml index 20af517397..c04017651c 100644 --- a/doc/classes/NavigationServer3D.xml +++ b/doc/classes/NavigationServer3D.xml @@ -278,7 +278,7 @@ <param index="1" name="enabled" type="bool" /> <description> Sets if the agent uses the 2D avoidance or the 3D avoidance while avoidance is enabled. - If [code]true[/code] the agent calculates avoidance velocities in 3D for the xyz-axis, e.g. for games that take place in air, unterwater or space. The 3D using agent only avoids other 3D avoidance using agent's. The 3D using agent only reacts to radius based avoidance obstacles. The 3D using agent ignores any vertices based obstacles. The 3D using agent only avoids other 3D using agent's. + If [code]true[/code] the agent calculates avoidance velocities in 3D for the xyz-axis, e.g. for games that take place in air, underwater or space. The 3D using agent only avoids other 3D avoidance using agent's. The 3D using agent only reacts to radius based avoidance obstacles. The 3D using agent ignores any vertices based obstacles. The 3D using agent only avoids other 3D using agent's. If [code]false[/code] the agent calculates avoidance velocities in 2D along the xz-axis ignoring the y-axis. The 2D using agent only avoids other 2D avoidance using agent's. The 2D using agent reacts to radius avoidance obstacles. The 2D using agent reacts to vertices based avoidance obstacles. The 2D using agent only avoids other 2D using agent's. 2D using agents will ignore other 2D using agents or obstacles that are below their current position or above their current position including the agents height in 2D avoidance. </description> </method> diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index 4d31b5b8ed..8fb2e65faf 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -267,7 +267,7 @@ <param index="0" name="idx" type="int" /> <param index="1" name="include_internal" type="bool" default="false" /> <description> - Fetches a child node by its index. Each child node has an index relative its siblings (see [method get_index]). The first child is at index 0. Negative values can also be used to start from the end of the list. This method can be used in combination with [method get_child_count] to iterate over this node's children. + Fetches a child node by its index. Each child node has an index relative its siblings (see [method get_index]). The first child is at index 0. Negative values can also be used to start from the end of the list. This method can be used in combination with [method get_child_count] to iterate over this node's children. If no child exists at the given index, this method returns [code]null[/code] and an error is generated. If [param include_internal] is [code]false[/code], internal children are ignored (see [method add_child]'s [code]internal[/code] parameter). [codeblock] # Assuming the following are children of this node, in order: diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml index 44197dbd0c..188e846f5e 100644 --- a/doc/classes/OS.xml +++ b/doc/classes/OS.xml @@ -4,8 +4,8 @@ Provides access to common operating system functionalities. </brief_description> <description> - This class wraps the most common functionalities for communicating with the host operating system, such as the video driver, delays, environment variables, execution of binaries, command line, etc. - [b]Note:[/b] In Godot 4, [OS] functions related to window management were moved to the [DisplayServer] singleton. + The [OS] class wraps the most common functionalities for communicating with the host operating system, such as the video driver, delays, environment variables, execution of binaries, command line, etc. + [b]Note:[/b] In Godot 4, [OS] functions related to window management, clipboard, and TTS were moved to the [DisplayServer] singleton (and the [Window] class). Functions related to time were removed and are only available in the [Time] class. </description> <tutorials> <link title="OS Test Demo">https://godotengine.org/asset-library/asset/677</link> @@ -16,13 +16,13 @@ <param index="0" name="text" type="String" /> <param index="1" name="title" type="String" default=""Alert!"" /> <description> - Displays a modal dialog box using the host OS' facilities. Execution is blocked until the dialog is closed. + Displays a modal dialog box using the host platform's implementation. The engine execution is blocked until the dialog is closed. </description> </method> <method name="close_midi_inputs"> <return type="void" /> <description> - Shuts down system MIDI driver. + Shuts down the system MIDI driver. Godot will no longer receive [InputEventMIDI]. See also [method open_midi_inputs] and [method get_connected_midi_inputs]. [b]Note:[/b] This method is implemented on Linux, macOS and Windows. </description> </method> @@ -30,7 +30,8 @@ <return type="void" /> <param index="0" name="message" type="String" /> <description> - Crashes the engine (or the editor if called within a [code]@tool[/code] script). This should [i]only[/i] be used for testing the system's crash handler, not for any other purpose. For general error reporting, use (in order of preference) [method @GDScript.assert], [method @GlobalScope.push_error] or [method alert]. See also [method kill]. + Crashes the engine (or the editor if called within a [code]@tool[/code] script). See also [method kill]. + [b]Note:[/b] This method should [i]only[/i] be used for testing the system's crash handler, not for any other purpose. For general error reporting, use (in order of preference) [method @GDScript.assert], [method @GlobalScope.push_error], or [method alert]. </description> </method> <method name="create_instance"> @@ -38,8 +39,9 @@ <param index="0" name="arguments" type="PackedStringArray" /> <description> Creates a new instance of Godot that runs independently. The [param arguments] are used in the given order and separated by a space. - If the process creation succeeds, the method will return the new process ID, which you can use to monitor the process (and potentially terminate it with [method kill]). If the process creation fails, the method will return [code]-1[/code]. - [b]Note:[/b] This method is implemented on Android, iOS, Linux, macOS and Windows. + If the process is successfully created, the method will return the new process ID, which you can use to monitor the process (and potentially terminate it with [method kill]). If the process cannot be created, the method will return [code]-1[/code]. + See [method create_process] if you wish to run a different process. + [b]Note:[/b] This method is implemented on Android, Linux, macOS and Windows. </description> </method> <method name="create_process"> @@ -48,9 +50,9 @@ <param index="1" name="arguments" type="PackedStringArray" /> <param index="2" name="open_console" type="bool" default="false" /> <description> - Creates a new process that runs independently of Godot. It will not terminate if Godot terminates. The path specified in [param path] must exist and be executable file or macOS .app bundle. Platform path resolution will be used. The [param arguments] are used in the given order and separated by a space. - On Windows, if [param open_console] is [code]true[/code] and the process is a console app, a new terminal window will be opened. This is ignored on other platforms. - If the process creation succeeds, the method will return the new process ID, which you can use to monitor the process (and potentially terminate it with [method kill]). If the process creation fails, the method will return [code]-1[/code]. + Creates a new process that runs independently of Godot. It will not terminate when Godot terminates. The path specified in [param path] must exist and be executable file or macOS .app bundle. Platform path resolution will be used. The [param arguments] are used in the given order and separated by a space. + On Windows, if [param open_console] is [code]true[/code] and the process is a console app, a new terminal window will be opened. + If the process is successfully created, this method returns its process ID, which you can use to monitor the process (and potentially terminate it with [method kill]). Otherwise this method returns [code]-1[/code]. For example, running another instance of the project: [codeblocks] [gdscript] @@ -69,8 +71,8 @@ <return type="void" /> <param index="0" name="msec" type="int" /> <description> - Delays execution of the current thread by [param msec] milliseconds. [param msec] must be greater than or equal to [code]0[/code]. Otherwise, [method delay_msec] will do nothing and will print an error message. - [b]Note:[/b] [method delay_msec] is a [i]blocking[/i] way to delay code execution. To delay code execution in a non-blocking way, see [method SceneTree.create_timer]. Awaiting with [method SceneTree.create_timer] will delay the execution of code placed below the [code]await[/code] without affecting the rest of the project (or editor, for [EditorPlugin]s and [EditorScript]s). + Delays execution of the current thread by [param msec] milliseconds. [param msec] must be greater than or equal to [code]0[/code]. Otherwise, [method delay_msec] does nothing and prints an error message. + [b]Note:[/b] [method delay_msec] is a [i]blocking[/i] way to delay code execution. To delay code execution in a non-blocking way, you may use [method SceneTree.create_timer]. Awaiting with [SceneTreeTimer] delays the execution of code placed below the [code]await[/code] without affecting the rest of the project (or editor, for [EditorPlugin]s and [EditorScript]s). [b]Note:[/b] When [method delay_msec] is called on the main thread, it will freeze the project and will prevent it from redrawing and registering input until the delay has passed. When using [method delay_msec] as part of an [EditorPlugin] or [EditorScript], it will freeze the editor but won't freeze the project if it is currently running (since the project is an independent child process). </description> </method> @@ -78,8 +80,8 @@ <return type="void" /> <param index="0" name="usec" type="int" /> <description> - Delays execution of the current thread by [param usec] microseconds. [param usec] must be greater than or equal to [code]0[/code]. Otherwise, [method delay_usec] will do nothing and will print an error message. - [b]Note:[/b] [method delay_usec] is a [i]blocking[/i] way to delay code execution. To delay code execution in a non-blocking way, see [method SceneTree.create_timer]. Awaiting with [method SceneTree.create_timer] will delay the execution of code placed below the [code]await[/code] without affecting the rest of the project (or editor, for [EditorPlugin]s and [EditorScript]s). + Delays execution of the current thread by [param usec] microseconds. [param usec] must be greater than or equal to [code]0[/code]. Otherwise, [method delay_usec] does nothing and prints an error message. + [b]Note:[/b] [method delay_usec] is a [i]blocking[/i] way to delay code execution. To delay code execution in a non-blocking way, you may use [method SceneTree.create_timer]. Awaiting with a [SceneTreeTimer] delays the execution of code placed below the [code]await[/code] without affecting the rest of the project (or editor, for [EditorPlugin]s and [EditorScript]s). [b]Note:[/b] When [method delay_usec] is called on the main thread, it will freeze the project and will prevent it from redrawing and registering input until the delay has passed. When using [method delay_usec] as part of an [EditorPlugin] or [EditorScript], it will freeze the editor but won't freeze the project if it is currently running (since the project is an independent child process). </description> </method> @@ -91,10 +93,11 @@ <param index="3" name="read_stderr" type="bool" default="false" /> <param index="4" name="open_console" type="bool" default="false" /> <description> - Executes a command. The file specified in [param path] must exist and be executable. Platform path resolution will be used. The [param arguments] are used in the given order, separated by spaces, and wrapped in quotes. If an [param output] [Array] is provided, the complete shell output of the process will be appended as a single [String] element in [param output]. If [param read_stderr] is [code]true[/code], the output to the standard error stream will be included too. - On Windows, if [param open_console] is [code]true[/code] and the process is a console app, a new terminal window will be opened. This is ignored on other platforms. - If the command is successfully executed, the method will return the exit code of the command, or [code]-1[/code] if it fails. - [b]Note:[/b] The Godot thread will pause its execution until the executed command terminates. Use [Thread] to create a separate thread that will not pause the Godot thread, or use [method create_process] to create a completely independent process. + Executes the given process in a [i]blocking[/i] way. The file specified in [param path] must exist and be executable. The system path resolution will be used. The [param arguments] are used in the given order, separated by spaces, and wrapped in quotes. + If an [param output] array is provided, the complete shell output of the process is appended to [param output] as a single [String] element. If [param read_stderr] is [code]true[/code], the output to the standard error stream is also appended to the array. + On Windows, if [param open_console] is [code]true[/code] and the process is a console app, a new terminal window is opened. + This method returns the exit code of the command, or [code]-1[/code] if the process fails to execute. + [b]Note:[/b] The main thread will be blocked until the executed command terminates. Use [Thread] to create a separate thread that will not block the main thread, or use [method create_process] to create a completely independent process. For example, to retrieve a list of the working directory's contents: [codeblocks] [gdscript] @@ -128,13 +131,29 @@ <return type="int" enum="Key" /> <param index="0" name="string" type="String" /> <description> - Returns the keycode of the given string (e.g. "Escape"). + Finds the keycode for the given string. The returned values are equivalent to the [enum Key] constants. + [codeblocks] + [gdscript] + print(OS.find_keycode_from_string("C")) # Prints 67 (KEY_C) + print(OS.find_keycode_from_string("Escape")) # Prints 4194305 (KEY_ESCAPE) + print(OS.find_keycode_from_string("Shift+Tab")) # Prints 37748738 (KEY_MASK_SHIFT | KEY_TAB) + print(OS.find_keycode_from_string("Unknown")) # Prints 0 (KEY_NONE) + [/gdscript] + [csharp] + GD.Print(OS.FindKeycodeFromString("C")); // Prints C (Key.C) + GD.Print(OS.FindKeycodeFromString("Escape")); // Prints Escape (Key.Escape) + GD.Print(OS.FindKeycodeFromString("Shift+Tab")); // Prints 37748738 (KeyModifierMask.MaskShift | Key.Tab) + GD.Print(OS.FindKeycodeFromString("Unknown")); // Prints None (Key.None) + [/csharp] + [/codeblocks] + See also [method get_keycode_string]. </description> </method> <method name="get_cache_dir" qualifiers="const"> <return type="String" /> <description> - Returns the [i]global[/i] cache data directory according to the operating system's standards. On the Linux/BSD platform, this path can be overridden by setting the [code]XDG_CACHE_HOME[/code] environment variable before starting the project. See [url=$DOCS_URL/tutorials/io/data_paths.html]File paths in Godot projects[/url] in the documentation for more information. See also [method get_config_dir] and [method get_data_dir]. + Returns the [i]global[/i] cache data directory according to the operating system's standards. + On the Linux/BSD platform, this path can be overridden by setting the [code]XDG_CACHE_HOME[/code] environment variable before starting the project. See [url=$DOCS_URL/tutorials/io/data_paths.html]File paths in Godot projects[/url] in the documentation for more information. See also [method get_config_dir] and [method get_data_dir]. Not to be confused with [method get_user_data_dir], which returns the [i]project-specific[/i] user data path. </description> </method> @@ -145,12 +164,12 @@ Command-line arguments can be written in any form, including both [code]--key value[/code] and [code]--key=value[/code] forms so they can be properly parsed, as long as custom command-line arguments do not conflict with engine arguments. You can also incorporate environment variables using the [method get_environment] method. You can set [member ProjectSettings.editor/run/main_run_args] to define command-line arguments to be passed by the editor when running the project. - Here's a minimal example on how to parse command-line arguments into a dictionary using the [code]--key=value[/code] form for arguments: + Here's a minimal example on how to parse command-line arguments into a [Dictionary] using the [code]--key=value[/code] form for arguments: [codeblocks] [gdscript] var arguments = {} for argument in OS.get_cmdline_args(): - if argument.find("=") > -1: + if argument.contains("="): var key_value = argument.split("=") arguments[key_value[0].lstrip("--")] = key_value[1] else: @@ -162,7 +181,7 @@ var arguments = new Godot.Collections.Dictionary(); foreach (var argument in OS.GetCmdlineArgs()) { - if (argument.Find("=") > -1) + if (argument.Contains('=')) { string[] keyValue = argument.Split("="); arguments[keyValue[0].LStrip("--")] = keyValue[1]; @@ -176,91 +195,108 @@ } [/csharp] [/codeblocks] - [b]Note:[/b] Passing custom user arguments directly is not recommended, as the engine may discard or modify them. Instead, the best way is to use the standard UNIX double dash ([code]--[/code]) and then pass custom arguments, which the engine itself will ignore. These can be read via [method get_cmdline_user_args]. + [b]Note:[/b] Passing custom user arguments directly is not recommended, as the engine may discard or modify them. Instead, pass the standard UNIX double dash ([code]--[/code]) and then the custom arguments, which the engine will ignore by design. These can be read via [method get_cmdline_user_args]. </description> </method> <method name="get_cmdline_user_args"> <return type="PackedStringArray" /> <description> - Similar to [method get_cmdline_args], but this returns the user arguments (any argument passed after the double dash [code]--[/code] or double plus [code]++[/code] argument). These are left untouched by Godot for the user. [code]++[/code] can be used in situations where [code]--[/code] is intercepted by another program (such as [code]startx[/code]). - For example, in the command line below, [code]--fullscreen[/code] will not be returned in [method get_cmdline_user_args] and [code]--level 1[/code] will only be returned in [method get_cmdline_user_args]: + Returns the command-line user arguments passed to the engine. User arguments are ignored by the engine and reserved for the user. They are passed after the double dash [code]--[/code] argument. [code]++[/code] may be used when [code]--[/code] is intercepted by another program (such as [code]startx[/code]). [codeblock] - godot --fullscreen -- --level 1 - # Or: - godot --fullscreen ++ --level 1 + # Godot has been executed with the following command: + # godot --fullscreen -- --level=2 --hardcore + + OS.get_cmdline_args() # Returns ["--fullscreen", "--level=2", "--hardcore"] + OS.get_cmdline_user_args() # Returns ["--level=2", "--hardcore"] [/codeblock] + To get all passed arguments, use [method get_cmdline_args]. </description> </method> <method name="get_config_dir" qualifiers="const"> <return type="String" /> <description> - Returns the [i]global[/i] user configuration directory according to the operating system's standards. On the Linux/BSD platform, this path can be overridden by setting the [code]XDG_CONFIG_HOME[/code] environment variable before starting the project. See [url=$DOCS_URL/tutorials/io/data_paths.html]File paths in Godot projects[/url] in the documentation for more information. See also [method get_cache_dir] and [method get_data_dir]. + Returns the [i]global[/i] user configuration directory according to the operating system's standards. + On the Linux/BSD platform, this path can be overridden by setting the [code]XDG_CONFIG_HOME[/code] environment variable before starting the project. See [url=$DOCS_URL/tutorials/io/data_paths.html]File paths in Godot projects[/url] in the documentation for more information. See also [method get_cache_dir] and [method get_data_dir]. Not to be confused with [method get_user_data_dir], which returns the [i]project-specific[/i] user data path. </description> </method> <method name="get_connected_midi_inputs"> <return type="PackedStringArray" /> <description> - Returns an array of MIDI device names. - The returned array will be empty if the system MIDI driver has not previously been initialized with [method open_midi_inputs]. + Returns an array of connected MIDI device names, if they exist. Returns an empty array if the system MIDI driver has not previously been initialized with [method open_midi_inputs]. See also [method close_midi_inputs]. [b]Note:[/b] This method is implemented on Linux, macOS and Windows. </description> </method> <method name="get_data_dir" qualifiers="const"> <return type="String" /> <description> - Returns the [i]global[/i] user data directory according to the operating system's standards. On the Linux/BSD platform, this path can be overridden by setting the [code]XDG_DATA_HOME[/code] environment variable before starting the project. See [url=$DOCS_URL/tutorials/io/data_paths.html]File paths in Godot projects[/url] in the documentation for more information. See also [method get_cache_dir] and [method get_config_dir]. + Returns the [i]global[/i] user data directory according to the operating system's standards. + On the Linux/BSD platform, this path can be overridden by setting the [code]XDG_DATA_HOME[/code] environment variable before starting the project. See [url=$DOCS_URL/tutorials/io/data_paths.html]File paths in Godot projects[/url] in the documentation for more information. See also [method get_cache_dir] and [method get_config_dir]. Not to be confused with [method get_user_data_dir], which returns the [i]project-specific[/i] user data path. </description> </method> <method name="get_distribution_name" qualifiers="const"> <return type="String" /> <description> - Returns the name of the distribution for Linux and BSD platforms (e.g. Ubuntu, Manjaro, OpenBSD, etc.). - Returns the same value as [method get_name] for stock Android ROMs, but attempts to return the custom ROM name for popular Android derivatives such as LineageOS. + Returns the name of the distribution for Linux and BSD platforms (e.g. "Ubuntu", "Manjaro", "OpenBSD", etc.). + Returns the same value as [method get_name] for stock Android ROMs, but attempts to return the custom ROM name for popular Android derivatives such as "LineageOS". Returns the same value as [method get_name] for other platforms. - [b]Note:[/b] This method is not supported on the web platform. It returns an empty string. + [b]Note:[/b] This method is not supported on the Web platform. It returns an empty string. </description> </method> <method name="get_environment" qualifiers="const"> <return type="String" /> <param index="0" name="variable" type="String" /> <description> - Returns the value of an environment variable. Returns an empty string if the environment variable doesn't exist. + Returns the value of the given environment variable, or an empty string if [param variable] doesn't exist. [b]Note:[/b] Double-check the casing of [param variable]. Environment variable names are case-sensitive on all platforms except Windows. + [b]Note:[/b] On macOS, applications do not have access to shell environment variables. </description> </method> <method name="get_executable_path" qualifiers="const"> <return type="String" /> <description> - Returns the path to the current engine executable. + Returns the file path to the current engine executable. [b]Note:[/b] On macOS, always use [method create_instance] instead of relying on executable path. </description> </method> <method name="get_granted_permissions" qualifiers="const"> <return type="PackedStringArray" /> <description> - On Android devices: With this function, you can get the list of dangerous permissions that have been granted. - On macOS (sandboxed applications only): This function returns the list of user selected folders accessible to the application. Use native file dialog to request folder access permission. + On Android devices: Returns the list of dangerous permissions that have been granted. + On macOS: Returns the list of user selected folders accessible to the application (sandboxed applications only). Use the native file dialog to request folder access permission. </description> </method> <method name="get_keycode_string" qualifiers="const"> <return type="String" /> <param index="0" name="code" type="int" enum="Key" /> <description> - Returns the given keycode as a string (e.g. Return values: [code]"Escape"[/code], [code]"Shift+Escape"[/code]). - See also [member InputEventKey.keycode] and [method InputEventKey.get_keycode_with_modifiers]. + Returns the given keycode as a [String]. + [codeblocks] + [gdscript] + print(OS.get_keycode_string(KEY_C)) # Prints "C" + print(OS.get_keycode_string(KEY_ESCAPE)) # Prints "Escape" + print(OS.get_keycode_string(KEY_MASK_SHIFT | KEY_TAB)) # Prints "Shift+Tab" + [/gdscript] + [csharp] + GD.Print(OS.GetKeycodeString(Key.C)); // Prints "C" + GD.Print(OS.GetKeycodeString(Key.Escape)); // Prints "Escape" + GD.Print(OS.GetKeycodeString((Key)KeyModifierMask.MaskShift | Key.Tab)); // Prints "Shift+Tab" + [/csharp] + [/codeblocks] + See also [method find_keycode_from_string], [member InputEventKey.keycode], and [method InputEventKey.get_keycode_with_modifiers]. </description> </method> <method name="get_locale" qualifiers="const"> <return type="String" /> <description> - Returns the host OS locale as a string of the form [code]language_Script_COUNTRY_VARIANT@extra[/code]. If you want only the language code and not the fully specified locale from the OS, you can use [method get_locale_language]. - [code]language[/code] - 2 or 3-letter [url=https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes]language code[/url], in lower case. - [code skip-lint]Script[/code] - optional, 4-letter [url=https://en.wikipedia.org/wiki/ISO_15924]script code[/url], in title case. - [code]COUNTRY[/code] - optional, 2 or 3-letter [url=https://en.wikipedia.org/wiki/ISO_3166-1]country code[/url], in upper case. - [code]VARIANT[/code] - optional, language variant, region and sort order. Variant can have any number of underscored keywords. - [code]extra[/code] - optional, semicolon separated list of additional key words. Currency, calendar, sort order and numbering system information. + Returns the host OS locale as a [String] of the form [code]language_Script_COUNTRY_VARIANT@extra[/code]. Every substring after [code]language[/code] is optional and may not exist. + - [code]language[/code] - 2 or 3-letter [url=https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes]language code[/url], in lower case. + - [code skip-lint]Script[/code] - 4-letter [url=https://en.wikipedia.org/wiki/ISO_15924]script code[/url], in title case. + - [code]COUNTRY[/code] - 2 or 3-letter [url=https://en.wikipedia.org/wiki/ISO_3166-1]country code[/url], in upper case. + - [code]VARIANT[/code] - language variant, region and sort order. The variant can have any number of underscored keywords. + - [code]extra[/code] - semicolon separated list of additional key words. This may include currency, calendar, sort order and numbering system information. + If you want only the language code and not the fully specified locale from the OS, you can use [method get_locale_language]. </description> </method> <method name="get_locale_language" qualifiers="const"> @@ -280,11 +316,12 @@ <method name="get_memory_info" qualifiers="const"> <return type="Dictionary" /> <description> - Returns the [Dictionary] with the following keys: - [code]"physical"[/code] - total amount of usable physical memory, in bytes or [code]-1[/code] if unknown. This value can be slightly less than the actual physical memory amount, since it does not include memory reserved by kernel and devices. - [code]"free"[/code] - amount of physical memory, that can be immediately allocated without disk access or other costly operation, in bytes or [code]-1[/code] if unknown. The process might be able to allocate more physical memory, but such allocation will require moving inactive pages to disk and can take some time. - [code]"available"[/code] - amount of memory, that can be allocated without extending the swap file(s), in bytes or [code]-1[/code] if unknown. This value include both physical memory and swap. - [code]"stack"[/code] - size of the current thread stack, in bytes or [code]-1[/code] if unknown. + Returns a [Dictionary] containing information about the current memory with the following entries: + - [code]"physical"[/code] - total amount of usable physical memory in bytes. This value can be slightly less than the actual physical memory amount, since it does not include memory reserved by the kernel and devices. + - [code]"free"[/code] - amount of physical memory, that can be immediately allocated without disk access or other costly operations, in bytes. The process might be able to allocate more physical memory, but this action will require moving inactive pages to disk, which can be expensive. + - [code]"available"[/code] - amount of memory that can be allocated without extending the swap file(s), in bytes. This value includes both physical memory and swap. + - [code]"stack"[/code] - size of the current thread stack in bytes. + [b]Note:[/b] Each entry's value may be [code]-1[/code] if it is unknown. </description> </method> <method name="get_model_name" qualifiers="const"> @@ -297,65 +334,66 @@ <method name="get_name" qualifiers="const"> <return type="String" /> <description> - Returns the name of the host OS. - On Windows, this is [code]"Windows"[/code]. - On macOS, this is [code]"macOS"[/code]. - On Linux-based operating systems, this is [code]"Linux"[/code]. - On BSD-based operating systems, this is [code]"FreeBSD"[/code], [code]"NetBSD"[/code], [code]"OpenBSD"[/code], or [code]"BSD"[/code] as a fallback. - On Android, this is [code]"Android"[/code]. - On iOS, this is [code]"iOS"[/code]. - On the web, this is [code]"Web"[/code]. - [b]Note:[/b] Custom builds of the engine may support additional platforms, such as consoles, yielding other return values. + Returns the name of the host platform. + - On Windows, this is [code]"Windows"[/code]. + - On macOS, this is [code]"macOS"[/code]. + - On Linux-based operating systems, this is [code]"Linux"[/code]. + - On BSD-based operating systems, this is [code]"FreeBSD"[/code], [code]"NetBSD"[/code], [code]"OpenBSD"[/code], or [code]"BSD"[/code] as a fallback. + - On Android, this is [code]"Android"[/code]. + - On iOS, this is [code]"iOS"[/code]. + - On the web, this is [code]"Web"[/code]. + [b]Note:[/b] Custom builds of the engine may support additional platforms, such as consoles, possibly returning other names. [codeblocks] [gdscript] match OS.get_name(): "Windows": - print("Windows") + print("Welcome to Windows!") "macOS": - print("macOS") + print("Welcome to macOS!") "Linux", "FreeBSD", "NetBSD", "OpenBSD", "BSD": - print("Linux/BSD") + print("Welcome to Linux/BSD!") "Android": - print("Android") + print("Welcome to Android!") "iOS": - print("iOS") + print("Welcome to iOS!") "Web": - print("Web") + print("Welcome to the Web!") [/gdscript] [csharp] switch (OS.GetName()) { case "Windows": - GD.Print("Windows"); + GD.Print("Welcome to Windows"); break; case "macOS": - GD.Print("macOS"); + GD.Print("Welcome to macOS!"); break; case "Linux": case "FreeBSD": case "NetBSD": case "OpenBSD": case "BSD": - GD.Print("Linux/BSD"); + GD.Print("Welcome to Linux/BSD!"); break; case "Android": - GD.Print("Android"); + GD.Print("Welcome to Android!"); break; case "iOS": - GD.Print("iOS"); + GD.Print("Welcome to iOS!"); break; case "Web": - GD.Print("Web"); + GD.Print("Welcome to the Web!"); break; } [/csharp] [/codeblocks] + [b]Note:[/b] On Web platforms, it is still possible to determine the host platform's OS with feature tags. See [method has_feature]. </description> </method> <method name="get_process_id" qualifiers="const"> <return type="int" /> <description> - Returns the project's process ID. + Returns the number used by the host machine to uniquely identify this application. [b]Note:[/b] This method is implemented on Android, iOS, Linux, macOS and Windows. </description> </method> @@ -368,7 +406,7 @@ <method name="get_processor_name" qualifiers="const"> <return type="String" /> <description> - Returns the name of the CPU model on the host machine (e.g. "Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz"). + Returns the full name of the CPU model on the host machine (e.g. [code]"Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz"[/code]). [b]Note:[/b] This method is only implemented on Windows, macOS, Linux and iOS. On Android and Web, [method get_processor_name] returns an empty string. </description> </method> @@ -381,13 +419,13 @@ <method name="get_static_memory_peak_usage" qualifiers="const"> <return type="int" /> <description> - Returns the maximum amount of static memory used (only works in debug). + Returns the maximum amount of static memory used. Only works in debug builds. </description> </method> <method name="get_static_memory_usage" qualifiers="const"> <return type="int" /> <description> - Returns the amount of static memory being used by the program in bytes (only works in debug). + Returns the amount of static memory being used by the program in bytes. Only works in debug builds. </description> </method> <method name="get_system_dir" qualifiers="const"> @@ -395,9 +433,9 @@ <param index="0" name="dir" type="int" enum="OS.SystemDir" /> <param index="1" name="shared_storage" type="bool" default="true" /> <description> - Returns the actual path to commonly used folders across different platforms. Available locations are specified in [enum SystemDir]. + Returns the path to commonly used folders across different platforms, as defined by [param dir]. See the [enum SystemDir] constants for available locations. [b]Note:[/b] This method is implemented on Android, Linux, macOS and Windows. - [b]Note:[/b] Shared storage is implemented on Android and allows to differentiate between app specific and shared directories. Shared directories have additional restrictions on Android. + [b]Note:[/b] Shared storage is implemented on Android and allows to differentiate between app specific and shared directories, if [param shared_storage] is [code]true[/code]. Shared directories have additional restrictions on Android. </description> </method> <method name="get_system_font_path" qualifiers="const"> @@ -407,7 +445,7 @@ <param index="2" name="stretch" type="int" default="100" /> <param index="3" name="italic" type="bool" default="false" /> <description> - Returns path to the system font file with [param font_name] and style. Returns empty string if no matching fonts found. + Returns the path to the system font file with [param font_name] and style. Returns an empty string if no matching fonts found. The following aliases can be used to request default fonts: "sans-serif", "serif", "monospace", "cursive", and "fantasy". [b]Note:[/b] Returned font might have different style if the requested style is not available. [b]Note:[/b] This method is implemented on Android, iOS, Linux, macOS and Windows. @@ -423,7 +461,7 @@ <param index="5" name="stretch" type="int" default="100" /> <param index="6" name="italic" type="bool" default="false" /> <description> - Returns an array of the system substitute font file paths, which are similar to the font with [param font_name] and style for the specified text, locale and script. Returns empty array if no matching fonts found. + Returns an array of the system substitute font file paths, which are similar to the font with [param font_name] and style for the specified text, locale, and script. Returns an empty array if no matching fonts found. The following aliases can be used to request default fonts: "sans-serif", "serif", "monospace", "cursive", and "fantasy". [b]Note:[/b] Depending on OS, it's not guaranteed that any of the returned fonts will be suitable for rendering specified text. Fonts should be loaded and checked in the order they are returned, and the first suitable one used. [b]Note:[/b] Returned fonts might have different style if the requested style is not available or belong to a different font family. @@ -433,7 +471,7 @@ <method name="get_system_fonts" qualifiers="const"> <return type="PackedStringArray" /> <description> - Returns list of font family names available. + Returns the list of font family names available. [b]Note:[/b] This method is implemented on Android, iOS, Linux, macOS and Windows. </description> </method> @@ -448,19 +486,19 @@ <return type="String" /> <description> Returns a string that is unique to the device. - [b]Note:[/b] This string may change without notice if the user reinstalls/upgrades their operating system or changes their hardware. This means it should generally not be used to encrypt persistent data as the data saved before an unexpected ID change would become inaccessible. The returned string may also be falsified using external programs, so do not rely on the string returned by [method get_unique_id] for security purposes. - [b]Note:[/b] Returns an empty string and prints an error on Web, as this method cannot be implemented on this platform. + [b]Note:[/b] This string may change without notice if the user reinstalls their operating system, upgrades it, or modifies their hardware. This means it should generally not be used to encrypt persistent data, as the data saved before an unexpected ID change would become inaccessible. The returned string may also be falsified using external programs, so do not rely on the string returned by this method for security purposes. + [b]Note:[/b] On Web, returns an empty string and generates an error, as this method cannot be implemented for security concerns. </description> </method> <method name="get_user_data_dir" qualifiers="const"> <return type="String" /> <description> - Returns the absolute directory path where user data is written ([code]user://[/code]). - On Windows, this is [code]%AppData%\Godot\app_userdata\[project_name][/code], or [code]%AppData%\[custom_name][/code] if [code]use_custom_user_dir[/code] is set. [code]%AppData%[/code] expands to [code]%UserProfile%\AppData\Roaming[/code]. - On macOS, this is [code]~/Library/Application Support/Godot/app_userdata/[project_name][/code], or [code]~/Library/Application Support/[custom_name][/code] if [code]use_custom_user_dir[/code] is set. - On Linux and BSD, this is [code]~/.local/share/godot/app_userdata/[project_name][/code], or [code]~/.local/share/[custom_name][/code] if [code]use_custom_user_dir[/code] is set. - On Android and iOS, this is a sandboxed directory in either internal or external storage, depending on the user's configuration. - On the web, this is a virtual directory managed by the browser. + Returns the absolute directory path where user data is written (the [code]user://[/code] directory in Godot). The path depends on the project name and [member ProjectSettings.application/config/use_custom_user_dir]. + - On Windows, this is [code]%AppData%\Godot\app_userdata\[project_name][/code], or [code]%AppData%\[custom_name][/code] if [code]use_custom_user_dir[/code] is set. [code]%AppData%[/code] expands to [code]%UserProfile%\AppData\Roaming[/code]. + - On macOS, this is [code]~/Library/Application Support/Godot/app_userdata/[project_name][/code], or [code]~/Library/Application Support/[custom_name][/code] if [code]use_custom_user_dir[/code] is set. + - On Linux and BSD, this is [code]~/.local/share/godot/app_userdata/[project_name][/code], or [code]~/.local/share/[custom_name][/code] if [code]use_custom_user_dir[/code] is set. + - On Android and iOS, this is a sandboxed directory in either internal or external storage, depending on the user's configuration. + - On Web, this is a virtual directory managed by the browser. If the project name is empty, [code][project_name][/code] falls back to [code][unnamed project][/code]. Not to be confused with [method get_data_dir], which returns the [i]global[/i] (non-project-specific) user home directory. </description> @@ -469,20 +507,20 @@ <return type="String" /> <description> Returns the exact production and build version of the operating system. This is different from the branded version used in marketing. This helps to distinguish between different releases of operating systems, including minor versions, and insider and custom builds. - For Windows, the major and minor version are returned, as well as the build number. For example, the returned string can look like [code]10.0.9926[/code] for a build of Windows 10, and it can look like [code]6.1.7601[/code] for a build of Windows 7 SP1. - For rolling distributions, such as Arch Linux, an empty string is returned. - For macOS and iOS, the major and minor version are returned, as well as the patch number. - For Android, the SDK version and the incremental build number are returned. If it's a custom ROM, it attempts to return its version instead. - [b]Note:[/b] This method is not supported on the web platform. It returns an empty string. + - For Windows, the major and minor version are returned, as well as the build number. For example, the returned string may look like [code]10.0.9926[/code] for a build of Windows 10, and it may look like [code]6.1.7601[/code] for a build of Windows 7 SP1. + - For rolling distributions, such as Arch Linux, an empty string is returned. + - For macOS and iOS, the major and minor version are returned, as well as the patch number. + - For Android, the SDK version and the incremental build number are returned. If it's a custom ROM, it attempts to return its version instead. + [b]Note:[/b] This method is not supported on the Web platform. It returns an empty string. </description> </method> <method name="get_video_adapter_driver_info" qualifiers="const"> <return type="PackedStringArray" /> <description> - Returns the video adapter driver name and version for the user's currently active graphics card. See also [method RenderingServer.get_video_adapter_api_version]. + Returns the video adapter driver name and version for the user's currently active graphics card, as a [PackedStringArray]. See also [method RenderingServer.get_video_adapter_api_version]. The first element holds the driver name, such as [code]nvidia[/code], [code]amdgpu[/code], etc. - The second element holds the driver version. For e.g. the [code]nvidia[/code] driver on a Linux/BSD platform, the version is in the format [code]510.85.02[/code]. For Windows, the driver's format is [code]31.0.15.1659[/code]. - [b]Note:[/b] This method is only supported on the platforms Linux/BSD and Windows when not running in headless mode. It returns an empty array on other platforms. + The second element holds the driver version. For example, on the [code]nvidia[/code] driver on a Linux/BSD platform, the version is in the format [code]510.85.02[/code]. For Windows, the driver's format is [code]31.0.15.1659[/code]. + [b]Note:[/b] This method is only supported on Linux/BSD and Windows when not running in headless mode. On other platforms, it returns an empty array. </description> </method> <method name="has_environment" qualifiers="const"> @@ -499,8 +537,7 @@ <description> Returns [code]true[/code] if the feature for the given feature tag is supported in the currently running instance, depending on the platform, build, etc. Can be used to check whether you're currently running a debug build, on a certain platform or arch, etc. Refer to the [url=$DOCS_URL/tutorials/export/feature_tags.html]Feature Tags[/url] documentation for more details. [b]Note:[/b] Tag names are case-sensitive. - [b]Note:[/b] On the web platform, one of the following additional tags is defined to indicate host platform: [code]web_android[/code], [code]web_ios[/code], [code]web_linuxbsd[/code], [code]web_macos[/code], or [code]web_windows[/code]. - [b]Note:[/b] On the iOS simulator, the additional [code]simulator[/code] tag is defined. + [b]Note:[/b] On the Web platform, one of the following additional tags is defined to indicate host platform: [code]web_android[/code], [code]web_ios[/code], [code]web_linuxbsd[/code], [code]web_macos[/code], or [code]web_windows[/code]. </description> </method> <method name="is_debug_build" qualifiers="const"> @@ -508,22 +545,35 @@ <description> Returns [code]true[/code] if the Godot binary used to run the project is a [i]debug[/i] export template, or when running in the editor. Returns [code]false[/code] if the Godot binary used to run the project is a [i]release[/i] export template. - To check whether the Godot binary used to run the project is an export template (debug or release), use [code]OS.has_feature("template")[/code] instead. + [b]Note:[/b] To check whether the Godot binary used to run the project is an export template (debug or release), use [code]OS.has_feature("template")[/code] instead. </description> </method> <method name="is_keycode_unicode" qualifiers="const"> <return type="bool" /> <param index="0" name="code" type="int" /> <description> - Returns [code]true[/code] if the input keycode corresponds to a Unicode character. + Returns [code]true[/code] if the input keycode corresponds to a Unicode character. For a list of codes, see the [enum Key] constants. + [codeblocks] + [gdscript] + print(OS.is_keycode_unicode(KEY_G)) # Prints true + print(OS.is_keycode_unicode(KEY_KP_4)) # Prints true + print(OS.is_keycode_unicode(KEY_TAB)) # Prints false + print(OS.is_keycode_unicode(KEY_ESCAPE)) # Prints false + [/gdscript] + [csharp] + GD.Print(OS.IsKeycodeUnicode((long)Key.G)); // Prints true + GD.Print(OS.IsKeycodeUnicode((long)Key.Kp4)); // Prints true + GD.Print(OS.IsKeycodeUnicode((long)Key.Tab)); // Prints false + GD.Print(OS.IsKeycodeUnicode((long)Key.Escape)); // Prints false + [/csharp] + [/codeblocks] </description> </method> <method name="is_process_running" qualifiers="const"> <return type="bool" /> <param index="0" name="pid" type="int" /> <description> - Returns [code]true[/code] if the child process ID ([param pid]) is still running or [code]false[/code] if it has terminated. - Must be a valid ID generated from [method create_process]. + Returns [code]true[/code] if the child process ID ([param pid]) is still running or [code]false[/code] if it has terminated. [param pid] must be a valid ID generated from [method create_process]. [b]Note:[/b] This method is implemented on Android, iOS, Linux, macOS and Windows. </description> </method> @@ -536,8 +586,8 @@ <method name="is_sandboxed" qualifiers="const"> <return type="bool" /> <description> - Returns [code]true[/code] if application is running in the sandbox. - [b]Note:[/b] This method is implemented on macOS and Linux. + Returns [code]true[/code] if the application is running in the sandbox. + [b]Note:[/b] This method is only implemented on macOS and Linux. </description> </method> <method name="is_stdout_verbose" qualifiers="const"> @@ -549,15 +599,15 @@ <method name="is_userfs_persistent" qualifiers="const"> <return type="bool" /> <description> - If [code]true[/code], the [code]user://[/code] file system is persistent, so that its state is the same after a player quits and starts the game again. Relevant to the Web platform, where this persistence may be unavailable. + Returns [code]true[/code] if the [code]user://[/code] file system is persistent, that is, its state is the same after a player quits and starts the game again. Relevant to the Web platform, where this persistence may be unavailable. </description> </method> <method name="kill"> <return type="int" enum="Error" /> <param index="0" name="pid" type="int" /> <description> - Kill (terminate) the process identified by the given process ID ([param pid]), e.g. the one returned by [method execute] in non-blocking mode. See also [method crash]. - [b]Note:[/b] This method can also be used to kill processes that were not spawned by the game. + Kill (terminate) the process identified by the given process ID ([param pid]), such as the ID returned by [method execute] in non-blocking mode. See also [method crash]. + [b]Note:[/b] This method can also be used to kill processes that were not spawned by the engine. [b]Note:[/b] This method is implemented on Android, iOS, Linux, macOS and Windows. </description> </method> @@ -565,9 +615,9 @@ <return type="int" enum="Error" /> <param index="0" name="path" type="String" /> <description> - Moves the file or directory to the system's recycle bin. See also [method DirAccess.remove]. + Moves the file or directory at the given [param path] to the system's recycle bin. See also [method DirAccess.remove]. The method takes only global paths, so you may need to use [method ProjectSettings.globalize_path]. Do not use it for files in [code]res://[/code] as it will not work in exported projects. - [b]Note:[/b] If the user has disabled the recycle bin on their system, the file will be permanently deleted instead. + Returns [constant FAILED] if the file or directory cannot be found, or the system does not support this method. [codeblocks] [gdscript] var file_to_remove = "user://slot1.save" @@ -578,12 +628,14 @@ OS.MoveToTrash(ProjectSettings.GlobalizePath(fileToRemove)); [/csharp] [/codeblocks] + [b]Note:[/b] This method is implemented on Android, Linux, macOS and Windows. + [b]Note:[/b] If the user has disabled the recycle bin on their system, the file will be permanently deleted instead. </description> </method> <method name="open_midi_inputs"> <return type="void" /> <description> - Initializes the singleton for the system MIDI driver. + Initializes the singleton for the system MIDI driver, allowing Godot to receive [InputEventMIDI]. See also [method get_connected_midi_inputs] and [method close_midi_inputs]. [b]Note:[/b] This method is implemented on Linux, macOS and Windows. </description> </method> @@ -598,14 +650,15 @@ <return type="bool" /> <param index="0" name="name" type="String" /> <description> - At the moment this function is only used by [code]AudioDriverOpenSL[/code] to request permission for [code]RECORD_AUDIO[/code] on Android. + Requests permission from the OS for the given [param name]. Returns [code]true[/code] if the permission has been successfully granted. + [b]Note:[/b] This method is currently only implemented on Android, to specifically request permission for [code]"RECORD_AUDIO"[/code] by [code]AudioDriverOpenSL[/code]. </description> </method> <method name="request_permissions"> <return type="bool" /> <description> - With this function, you can request dangerous permissions since normal permissions are automatically granted at install time in Android applications. - [b]Note:[/b] This method is implemented only on Android. + Requests [i]dangerous[/i] permissions from the OS. Returns [code]true[/code] if permissions have been successfully granted. + [b]Note:[/b] This method is only implemented on Android. Normal permissions are automatically granted at install time in Android applications. </description> </method> <method name="revoke_granted_permissions"> @@ -628,8 +681,8 @@ <param index="0" name="restart" type="bool" /> <param index="1" name="arguments" type="PackedStringArray" default="PackedStringArray()" /> <description> - If [param restart] is [code]true[/code], restarts the project automatically when it is exited with [method SceneTree.quit] or [constant Node.NOTIFICATION_WM_CLOSE_REQUEST]. Command line [param arguments] can be supplied. To restart the project with the same command line arguments as originally used to run the project, pass [method get_cmdline_args] as the value for [param arguments]. - [method set_restart_on_exit] can be used to apply setting changes that require a restart. See also [method is_restart_on_exit_set] and [method get_restart_on_exit_arguments]. + If [param restart] is [code]true[/code], restarts the project automatically when it is exited with [method SceneTree.quit] or [constant Node.NOTIFICATION_WM_CLOSE_REQUEST]. Command-line [param arguments] can be supplied. To restart the project with the same command line arguments as originally used to run the project, pass [method get_cmdline_args] as the value for [param arguments]. + This method can be used to apply setting changes that require a restart. See also [method is_restart_on_exit_set] and [method get_restart_on_exit_arguments]. [b]Note:[/b] This method is only effective on desktop platforms, and only when the project isn't started from the editor. It will have no effect on mobile and Web platforms, or when the project is started from the editor. [b]Note:[/b] If the project process crashes or is [i]killed[/i] by the user (by sending [code]SIGKILL[/code] instead of the usual [code]SIGTERM[/code]), the project won't restart automatically. </description> @@ -638,25 +691,26 @@ <return type="int" enum="Error" /> <param index="0" name="name" type="String" /> <description> - Sets the name of the current thread. + Assigns the given name to the current thread. Returns [constant ERR_UNAVAILABLE] if unavailable on the current platform. </description> </method> <method name="set_use_file_access_save_and_swap"> <return type="void" /> <param index="0" name="enabled" type="bool" /> <description> - Enables backup saves if [param enabled] is [code]true[/code]. + If [param enabled] is [code]true[/code], when opening a file for writing, a temporary file is used in its place. When closed, it is automatically applied to the target file. + This can useful when files may be opened by other applications, such as antiviruses, text editors, or even the Godot editor itself. </description> </method> <method name="shell_open"> <return type="int" enum="Error" /> <param index="0" name="uri" type="String" /> <description> - Requests the OS to open a resource with the most appropriate program. For example: + Requests the OS to open a resource identified by [param uri] with the most appropriate program. For example: - [code]OS.shell_open("C:\\Users\name\Downloads")[/code] on Windows opens the file explorer at the user's Downloads folder. - [code]OS.shell_open("https://godotengine.org")[/code] opens the default web browser on the official Godot website. - [code]OS.shell_open("mailto:example@example.com")[/code] opens the default email client with the "To" field set to [code]example@example.com[/code]. See [url=https://datatracker.ietf.org/doc/html/rfc2368]RFC 2368 - The [code]mailto[/code] URL scheme[/url] for a list of fields that can be added. - Use [method ProjectSettings.globalize_path] to convert a [code]res://[/code] or [code]user://[/code] path into a system path for use with this method. + Use [method ProjectSettings.globalize_path] to convert a [code]res://[/code] or [code]user://[/code] project path into a system path for use with this method. [b]Note:[/b] Use [method String.uri_encode] to encode characters within URLs in a URL-safe, portable way. This is especially required for line breaks. Otherwise, [method shell_open] may not work correctly in a project exported to the Web platform. [b]Note:[/b] This method is implemented on Android, iOS, Web, Linux, macOS and Windows. </description> @@ -666,30 +720,33 @@ <param index="0" name="file_or_dir_path" type="String" /> <param index="1" name="open_folder" type="bool" default="true" /> <description> - Requests the OS to open the file manager, then navigate to the given [param file_or_dir_path] and select the target file or folder. - If [param file_or_dir_path] is a valid directory path, and [param open_folder] is [code]true[/code], the method will open the file manager and enter the target folder without selecting anything. - Use [method ProjectSettings.globalize_path] to convert a [code]res://[/code] or [code]user://[/code] path into a system path for use with this method. - [b]Note:[/b] Currently this method is only implemented on Windows and macOS. On other platforms, it will fallback to [method shell_open] with a directory path of [param file_or_dir_path] with prefix [code]file://[/code]. + Requests the OS to open the file manager, navigate to the given [param file_or_dir_path] and select the target file or folder. + If [param open_folder] is [code]true[/code] and [param file_or_dir_path] is a valid directory path, the OS will open the file manager and navigate to the target folder without selecting anything. + Use [method ProjectSettings.globalize_path] to convert a [code]res://[/code] or [code]user://[/code] project path into a system path to use with this method. + [b]Note:[/b] This method is currently only implemented on Windows and macOS. On other platforms, it will fallback to [method shell_open] with a directory path of [param file_or_dir_path] prefixed with [code]file://[/code]. </description> </method> <method name="unset_environment" qualifiers="const"> <return type="void" /> <param index="0" name="variable" type="String" /> <description> - Removes the environment [param variable] from the current environment, if it exists. The environment variable will be removed for the Godot process and any process executed with [method execute] after running [method unset_environment]. The removal of the environment variable will [i]not[/i] persist to processes run after the Godot process was terminated. - [b]Note:[/b] Environment variable names are case-sensitive on all platforms except Windows. The [param variable] name cannot be empty or include the [code]=[/code] character. + Removes the given environment variable from the current environment, if it exists. The [param variable] name cannot be empty or include the [code]=[/code] character. The environment variable will be removed for the Godot process and any process executed with [method execute] after running [method unset_environment]. The removal of the environment variable will [i]not[/i] persist to processes run after the Godot process was terminated. + [b]Note:[/b] Environment variable names are case-sensitive on all platforms except Windows. </description> </method> </methods> <members> <member name="delta_smoothing" type="bool" setter="set_delta_smoothing" getter="is_delta_smoothing_enabled" default="true"> - If [code]true[/code], the engine filters the time delta measured between each frame, and attempts to compensate for random variation. This will only operate on systems where V-Sync is active. + If [code]true[/code], the engine filters the time delta measured between each frame, and attempts to compensate for random variation. This only works on systems where V-Sync is active. + [b]Note:[/b] On start-up, this is the same as [member ProjectSettings.application/run/delta_smoothing]. </member> <member name="low_processor_usage_mode" type="bool" setter="set_low_processor_usage_mode" getter="is_in_low_processor_usage_mode" default="false"> If [code]true[/code], the engine optimizes for low processor usage by only refreshing the screen if needed. Can improve battery consumption on mobile. + [b]Note:[/b] On start-up, this is the same as [member ProjectSettings.application/run/low_processor_mode]. </member> <member name="low_processor_usage_mode_sleep_usec" type="int" setter="set_low_processor_usage_mode_sleep_usec" getter="get_low_processor_usage_mode_sleep_usec" default="6900"> - The amount of sleeping between frames when the low-processor usage mode is enabled (in microseconds). Higher values will result in lower CPU usage. + The amount of sleeping between frames when the low-processor usage mode is enabled, in microseconds. Higher values will result in lower CPU usage. See also [member low_processor_usage_mode]. + [b]Note:[/b] On start-up, this is the same as [member ProjectSettings.application/run/low_processor_mode_sleep_usec]. </member> </members> <constants> @@ -703,28 +760,28 @@ The Direct3D 12 rendering driver. </constant> <constant name="SYSTEM_DIR_DESKTOP" value="0" enum="SystemDir"> - Desktop directory path. + Refers to the Desktop directory path. </constant> <constant name="SYSTEM_DIR_DCIM" value="1" enum="SystemDir"> - DCIM (Digital Camera Images) directory path. + Refers to the DCIM (Digital Camera Images) directory path. </constant> <constant name="SYSTEM_DIR_DOCUMENTS" value="2" enum="SystemDir"> - Documents directory path. + Refers to the Documents directory path. </constant> <constant name="SYSTEM_DIR_DOWNLOADS" value="3" enum="SystemDir"> - Downloads directory path. + Refers to the Downloads directory path. </constant> <constant name="SYSTEM_DIR_MOVIES" value="4" enum="SystemDir"> - Movies directory path. + Refers to the Movies (or Videos) directory path. </constant> <constant name="SYSTEM_DIR_MUSIC" value="5" enum="SystemDir"> - Music directory path. + Refers to the Music directory path. </constant> <constant name="SYSTEM_DIR_PICTURES" value="6" enum="SystemDir"> - Pictures directory path. + Refers to the Pictures directory path. </constant> <constant name="SYSTEM_DIR_RINGTONES" value="7" enum="SystemDir"> - Ringtones directory path. + Refers to the Ringtones directory path. </constant> </constants> </class> diff --git a/doc/classes/PackedByteArray.xml b/doc/classes/PackedByteArray.xml index 516cc9f020..a934d32a6d 100644 --- a/doc/classes/PackedByteArray.xml +++ b/doc/classes/PackedByteArray.xml @@ -397,7 +397,7 @@ <return type="int" /> <param index="0" name="new_size" type="int" /> <description> - Sets the size of the array. If the array is grown, reserves elements at the end of the array. If the array is shrunk, truncates the array to the new size. + Sets the size of the array. If the array is grown, reserves elements at the end of the array. If the array is shrunk, truncates the array to the new size. Calling [method resize] once and assigning the new values is faster than adding new elements one by one. </description> </method> <method name="reverse"> diff --git a/doc/classes/PackedColorArray.xml b/doc/classes/PackedColorArray.xml index 4908a861b3..ad96ba2490 100644 --- a/doc/classes/PackedColorArray.xml +++ b/doc/classes/PackedColorArray.xml @@ -131,7 +131,7 @@ <return type="int" /> <param index="0" name="new_size" type="int" /> <description> - Sets the size of the array. If the array is grown, reserves elements at the end of the array. If the array is shrunk, truncates the array to the new size. + Sets the size of the array. If the array is grown, reserves elements at the end of the array. If the array is shrunk, truncates the array to the new size. Calling [method resize] once and assigning the new values is faster than adding new elements one by one. </description> </method> <method name="reverse"> diff --git a/doc/classes/PackedFloat32Array.xml b/doc/classes/PackedFloat32Array.xml index 56f9633533..6f1ecacca4 100644 --- a/doc/classes/PackedFloat32Array.xml +++ b/doc/classes/PackedFloat32Array.xml @@ -132,7 +132,7 @@ <return type="int" /> <param index="0" name="new_size" type="int" /> <description> - Sets the size of the array. If the array is grown, reserves elements at the end of the array. If the array is shrunk, truncates the array to the new size. + Sets the size of the array. If the array is grown, reserves elements at the end of the array. If the array is shrunk, truncates the array to the new size. Calling [method resize] once and assigning the new values is faster than adding new elements one by one. </description> </method> <method name="reverse"> diff --git a/doc/classes/PackedFloat64Array.xml b/doc/classes/PackedFloat64Array.xml index b08bdafc83..aef5ab90ac 100644 --- a/doc/classes/PackedFloat64Array.xml +++ b/doc/classes/PackedFloat64Array.xml @@ -132,7 +132,7 @@ <return type="int" /> <param index="0" name="new_size" type="int" /> <description> - Sets the size of the array. If the array is grown, reserves elements at the end of the array. If the array is shrunk, truncates the array to the new size. + Sets the size of the array. If the array is grown, reserves elements at the end of the array. If the array is shrunk, truncates the array to the new size. Calling [method resize] once and assigning the new values is faster than adding new elements one by one. </description> </method> <method name="reverse"> diff --git a/doc/classes/PackedInt32Array.xml b/doc/classes/PackedInt32Array.xml index c70cadc2fe..e6396e2a93 100644 --- a/doc/classes/PackedInt32Array.xml +++ b/doc/classes/PackedInt32Array.xml @@ -128,7 +128,7 @@ <return type="int" /> <param index="0" name="new_size" type="int" /> <description> - Sets the size of the array. If the array is grown, reserves elements at the end of the array. If the array is shrunk, truncates the array to the new size. + Sets the size of the array. If the array is grown, reserves elements at the end of the array. If the array is shrunk, truncates the array to the new size. Calling [method resize] once and assigning the new values is faster than adding new elements one by one. </description> </method> <method name="reverse"> diff --git a/doc/classes/PackedInt64Array.xml b/doc/classes/PackedInt64Array.xml index 339ab26a68..55024341c1 100644 --- a/doc/classes/PackedInt64Array.xml +++ b/doc/classes/PackedInt64Array.xml @@ -128,7 +128,7 @@ <return type="int" /> <param index="0" name="new_size" type="int" /> <description> - Sets the size of the array. If the array is grown, reserves elements at the end of the array. If the array is shrunk, truncates the array to the new size. + Sets the size of the array. If the array is grown, reserves elements at the end of the array. If the array is shrunk, truncates the array to the new size. Calling [method resize] once and assigning the new values is faster than adding new elements one by one. </description> </method> <method name="reverse"> diff --git a/doc/classes/PackedStringArray.xml b/doc/classes/PackedStringArray.xml index c2ae3cf3d3..f1b02272f3 100644 --- a/doc/classes/PackedStringArray.xml +++ b/doc/classes/PackedStringArray.xml @@ -134,7 +134,7 @@ <return type="int" /> <param index="0" name="new_size" type="int" /> <description> - Sets the size of the array. If the array is grown, reserves elements at the end of the array. If the array is shrunk, truncates the array to the new size. + Sets the size of the array. If the array is grown, reserves elements at the end of the array. If the array is shrunk, truncates the array to the new size. Calling [method resize] once and assigning the new values is faster than adding new elements one by one. </description> </method> <method name="reverse"> diff --git a/doc/classes/PackedVector2Array.xml b/doc/classes/PackedVector2Array.xml index 0e5bb12d02..c73fea9114 100644 --- a/doc/classes/PackedVector2Array.xml +++ b/doc/classes/PackedVector2Array.xml @@ -136,7 +136,7 @@ <return type="int" /> <param index="0" name="new_size" type="int" /> <description> - Sets the size of the array. If the array is grown, reserves elements at the end of the array. If the array is shrunk, truncates the array to the new size. + Sets the size of the array. If the array is grown, reserves elements at the end of the array. If the array is shrunk, truncates the array to the new size. Calling [method resize] once and assigning the new values is faster than adding new elements one by one. </description> </method> <method name="reverse"> diff --git a/doc/classes/PackedVector3Array.xml b/doc/classes/PackedVector3Array.xml index ce7ae4f396..89f258eaea 100644 --- a/doc/classes/PackedVector3Array.xml +++ b/doc/classes/PackedVector3Array.xml @@ -135,7 +135,7 @@ <return type="int" /> <param index="0" name="new_size" type="int" /> <description> - Sets the size of the array. If the array is grown, reserves elements at the end of the array. If the array is shrunk, truncates the array to the new size. + Sets the size of the array. If the array is grown, reserves elements at the end of the array. If the array is shrunk, truncates the array to the new size. Calling [method resize] once and assigning the new values is faster than adding new elements one by one. </description> </method> <method name="reverse"> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index cb36d28a41..b969e3bb8e 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1108,7 +1108,7 @@ [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_swap_input_direction" type="Dictionary" setter="" getter=""> - Default [InputEventAction] to swap input direction, i.e. change between left-to-right to right-to-left modes. Affects text-editting controls ([LineEdit], [TextEdit]). + Default [InputEventAction] to swap input direction, i.e. change between left-to-right to right-to-left modes. Affects text-editing controls ([LineEdit], [TextEdit]). </member> <member name="input/ui_text_add_selection_for_next_occurrence" type="Dictionary" setter="" getter=""> If a selection is currently active with the last caret in text fields, searches for the next occurrence of the selection, adds a caret and selects the next occurrence. @@ -1219,15 +1219,15 @@ [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_completion_accept" type="Dictionary" setter="" getter=""> - Default [InputEventAction] to accept an autocompetion hint. + Default [InputEventAction] to accept an autocompletion hint. [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_completion_query" type="Dictionary" setter="" getter=""> - Default [InputEventAction] to request autocompetion. + Default [InputEventAction] to request autocompletion. [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_completion_replace" type="Dictionary" setter="" getter=""> - Default [InputEventAction] to accept an autocompetion hint, replacing existing text. + Default [InputEventAction] to accept an autocompletion hint, replacing existing text. [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. </member> <member name="input/ui_text_dedent" type="Dictionary" setter="" getter=""> diff --git a/doc/classes/Quaternion.xml b/doc/classes/Quaternion.xml index 74dfae77fc..61bc4f4720 100644 --- a/doc/classes/Quaternion.xml +++ b/doc/classes/Quaternion.xml @@ -79,6 +79,7 @@ <method name="exp" qualifiers="const"> <return type="Quaternion" /> <description> + Returns the exponential of this quaternion. The rotation axis of the result is the normalized rotation axis of this quaternion, the angle of the result is the length of the vector part of this quaternion. </description> </method> <method name="from_euler" qualifiers="static"> @@ -91,11 +92,14 @@ <method name="get_angle" qualifiers="const"> <return type="float" /> <description> + Returns the angle of the rotation represented by this quaternion. + [b]Note:[/b] The quaternion must be normalized. </description> </method> <method name="get_axis" qualifiers="const"> <return type="Vector3" /> <description> + Returns the rotation axis of the rotation represented by this quaternion. </description> </method> <method name="get_euler" qualifiers="const"> @@ -145,6 +149,7 @@ <method name="log" qualifiers="const"> <return type="Quaternion" /> <description> + Returns the logarithm of this quaternion. The vector part of the result is the rotation axis of this quaternion multiplied by its rotation angle, the real part of the result is zero. </description> </method> <method name="normalized" qualifiers="const"> diff --git a/doc/classes/RenderingDevice.xml b/doc/classes/RenderingDevice.xml index a841453ba0..5ca6d38267 100644 --- a/doc/classes/RenderingDevice.xml +++ b/doc/classes/RenderingDevice.xml @@ -205,7 +205,7 @@ A simple drawing operation might look like this (code is not a complete example): [codeblock] var rd = RenderingDevice.new() - var clear_colors = PackedColorArray([Color(0, 0, 0, 0), Color(0, 0, 0, 0), Color(0, 0, 0, 0)] + var clear_colors = PackedColorArray([Color(0, 0, 0, 0), Color(0, 0, 0, 0), Color(0, 0, 0, 0)]) var draw_list = rd.draw_list_begin(framebuffers[i], RenderingDevice.INITIAL_ACTION_CLEAR, RenderingDevice.FINAL_ACTION_READ, RenderingDevice.INITIAL_ACTION_CLEAR, RenderingDevice.FINAL_ACTION_DISCARD, clear_colors) # Draw opaque. diff --git a/doc/classes/String.xml b/doc/classes/String.xml index af4297afae..277d84ab90 100644 --- a/doc/classes/String.xml +++ b/doc/classes/String.xml @@ -318,7 +318,7 @@ <return type="int" /> <description> Returns the 32-bit hash value representing the string's contents. - [b]Note:[/b] Strings with equal hash values are [i]not[/i] guaranteed to be the same, as a result of hash collisions. On the countrary, strings with different hash values are guaranteed to be different. + [b]Note:[/b] Strings with equal hash values are [i]not[/i] guaranteed to be the same, as a result of hash collisions. On the contrary, strings with different hash values are guaranteed to be different. </description> </method> <method name="hex_decode" qualifiers="const"> diff --git a/doc/classes/StringName.xml b/doc/classes/StringName.xml index 1c5032be9b..4d9587f7aa 100644 --- a/doc/classes/StringName.xml +++ b/doc/classes/StringName.xml @@ -301,7 +301,7 @@ <return type="int" /> <description> Returns the 32-bit hash value representing the string's contents. - [b]Note:[/b] Strings with equal hash values are [i]not[/i] guaranteed to be the same, as a result of hash collisions. On the countrary, strings with different hash values are guaranteed to be different. + [b]Note:[/b] Strings with equal hash values are [i]not[/i] guaranteed to be the same, as a result of hash collisions. On the contrary, strings with different hash values are guaranteed to be different. </description> </method> <method name="hex_decode" qualifiers="const"> diff --git a/doc/classes/Transform2D.xml b/doc/classes/Transform2D.xml index 19a4973f4a..4247ff81ee 100644 --- a/doc/classes/Transform2D.xml +++ b/doc/classes/Transform2D.xml @@ -286,6 +286,20 @@ This operator multiplies all components of the [Transform2D], including the [member origin] vector, which scales it uniformly. </description> </operator> + <operator name="operator /"> + <return type="Transform2D" /> + <param index="0" name="right" type="float" /> + <description> + This operator divides all components of the [Transform2D], including the [member origin] vector, which inversely scales it uniformly. + </description> + </operator> + <operator name="operator /"> + <return type="Transform2D" /> + <param index="0" name="right" type="int" /> + <description> + This operator divides all components of the [Transform2D], including the [member origin] vector, which inversely scales it uniformly. + </description> + </operator> <operator name="operator =="> <return type="bool" /> <param index="0" name="right" type="Transform2D" /> diff --git a/doc/classes/Transform3D.xml b/doc/classes/Transform3D.xml index 91ece6943c..a1ddb75bc2 100644 --- a/doc/classes/Transform3D.xml +++ b/doc/classes/Transform3D.xml @@ -245,6 +245,20 @@ This operator multiplies all components of the [Transform3D], including the [member origin] vector, which scales it uniformly. </description> </operator> + <operator name="operator /"> + <return type="Transform3D" /> + <param index="0" name="right" type="float" /> + <description> + This operator divides all components of the [Transform3D], including the [member origin] vector, which inversely scales it uniformly. + </description> + </operator> + <operator name="operator /"> + <return type="Transform3D" /> + <param index="0" name="right" type="int" /> + <description> + This operator divides all components of the [Transform3D], including the [member origin] vector, which inversely scales it uniformly. + </description> + </operator> <operator name="operator =="> <return type="bool" /> <param index="0" name="right" type="Transform3D" /> diff --git a/drivers/gles3/effects/copy_effects.cpp b/drivers/gles3/effects/copy_effects.cpp index 996e7eee7f..c6fb6ca70b 100644 --- a/drivers/gles3/effects/copy_effects.cpp +++ b/drivers/gles3/effects/copy_effects.cpp @@ -125,6 +125,18 @@ void CopyEffects::copy_to_rect(const Rect2 &p_rect) { draw_screen_quad(); } +void CopyEffects::copy_to_and_from_rect(const Rect2 &p_rect) { + bool success = copy.shader.version_bind_shader(copy.shader_version, CopyShaderGLES3::MODE_COPY_SECTION_SOURCE); + if (!success) { + return; + } + + copy.shader.version_set_uniform(CopyShaderGLES3::COPY_SECTION, p_rect.position.x, p_rect.position.y, p_rect.size.x, p_rect.size.y, copy.shader_version, CopyShaderGLES3::MODE_COPY_SECTION_SOURCE); + copy.shader.version_set_uniform(CopyShaderGLES3::SOURCE_SECTION, p_rect.position.x, p_rect.position.y, p_rect.size.x, p_rect.size.y, copy.shader_version, CopyShaderGLES3::MODE_COPY_SECTION_SOURCE); + + draw_screen_quad(); +} + void CopyEffects::copy_screen() { bool success = copy.shader.version_bind_shader(copy.shader_version, CopyShaderGLES3::MODE_DEFAULT); if (!success) { diff --git a/drivers/gles3/effects/copy_effects.h b/drivers/gles3/effects/copy_effects.h index 6e2cb07382..509d07b955 100644 --- a/drivers/gles3/effects/copy_effects.h +++ b/drivers/gles3/effects/copy_effects.h @@ -62,6 +62,7 @@ public: // These functions assume that a framebuffer and texture are bound already. They only manage the shader, uniforms, and vertex array. void copy_to_rect(const Rect2 &p_rect); + void copy_to_and_from_rect(const Rect2 &p_rect); void copy_screen(); void copy_cube_to_rect(const Rect2 &p_rect); void bilinear_blur(GLuint p_source_texture, int p_mipmap_count, const Rect2i &p_region); diff --git a/drivers/gles3/shader_gles3.cpp b/drivers/gles3/shader_gles3.cpp index 75b2662a1c..8b5aaa6b8d 100644 --- a/drivers/gles3/shader_gles3.cpp +++ b/drivers/gles3/shader_gles3.cpp @@ -49,7 +49,7 @@ void ShaderGLES3::_add_stage(const char *p_code, StageType p_stage_type) { String text; for (int i = 0; i < lines.size(); i++) { - String l = lines[i]; + const String &l = lines[i]; bool push_chunk = false; StageTemplate::Chunk chunk; diff --git a/drivers/gles3/shaders/copy.glsl b/drivers/gles3/shaders/copy.glsl index 265acc1430..f37968a4fd 100644 --- a/drivers/gles3/shaders/copy.glsl +++ b/drivers/gles3/shaders/copy.glsl @@ -3,6 +3,7 @@ mode_default = #define MODE_SIMPLE_COPY mode_copy_section = #define USE_COPY_SECTION \n#define MODE_SIMPLE_COPY +mode_copy_section_source = #define USE_COPY_SECTION \n#define MODE_SIMPLE_COPY \n#define MODE_COPY_FROM mode_gaussian_blur = #define MODE_GAUSSIAN_BLUR mode_mipmap = #define MODE_MIPMAP mode_simple_color = #define MODE_SIMPLE_COLOR \n#define USE_COPY_SECTION @@ -21,7 +22,7 @@ out vec2 uv_interp; // Defined in 0-1 coords. uniform highp vec4 copy_section; #endif -#ifdef MODE_GAUSSIAN_BLUR +#if defined(MODE_GAUSSIAN_BLUR) || defined(MODE_COPY_FROM) uniform highp vec4 source_section; #endif @@ -32,7 +33,7 @@ void main() { #if defined(USE_COPY_SECTION) || defined(MODE_GAUSSIAN_BLUR) gl_Position.xy = (copy_section.xy + uv_interp.xy * copy_section.zw) * 2.0 - 1.0; #endif -#ifdef MODE_GAUSSIAN_BLUR +#if defined(MODE_GAUSSIAN_BLUR) || defined(MODE_COPY_FROM) uv_interp = source_section.xy + uv_interp * source_section.zw; #endif } diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl index 1d9ba623c4..e95d684763 100644 --- a/drivers/gles3/shaders/scene.glsl +++ b/drivers/gles3/shaders/scene.glsl @@ -607,8 +607,7 @@ layout(std140) uniform GlobalShaderUniformData { //ubo:1 vec4 global_shader_uniforms[MAX_GLOBAL_SHADER_UNIFORMS]; }; - /* Material Uniforms */ - +/* Material Uniforms */ #ifdef MATERIAL_UNIFORMS_USED /* clang-format off */ diff --git a/drivers/gles3/storage/texture_storage.cpp b/drivers/gles3/storage/texture_storage.cpp index 4e34fbcf0a..dc47338b05 100644 --- a/drivers/gles3/storage/texture_storage.cpp +++ b/drivers/gles3/storage/texture_storage.cpp @@ -766,7 +766,7 @@ void TextureStorage::texture_2d_layered_initialize(RID p_texture, const Vector<R ERR_FAIL_COND(p_layered_type == RS::TEXTURE_LAYERED_CUBEMAP && p_layers.size() != 6); ERR_FAIL_COND_MSG(p_layered_type == RS::TEXTURE_LAYERED_CUBEMAP_ARRAY, "Cubemap Arrays are not supported in the GL Compatibility backend."); - Ref<Image> image = p_layers[0]; + const Ref<Image> &image = p_layers[0]; { int valid_width = 0; int valid_height = 0; @@ -2647,7 +2647,10 @@ void TextureStorage::render_target_copy_to_back_buffer(RID p_render_target, cons glBindFramebuffer(GL_FRAMEBUFFER, rt->backbuffer_fbo); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, rt->color); - GLES3::CopyEffects::get_singleton()->copy_screen(); + Rect2 normalized_region = region; + normalized_region.position = normalized_region.position / Size2(rt->size); + normalized_region.size = normalized_region.size / Size2(rt->size); + GLES3::CopyEffects::get_singleton()->copy_to_and_from_rect(normalized_region); if (p_gen_mipmaps) { GLES3::CopyEffects::get_singleton()->gaussian_blur(rt->backbuffer, rt->mipmap_count, region, rt->size); diff --git a/drivers/png/image_loader_png.cpp b/drivers/png/image_loader_png.cpp index cbcb54bc11..6f98f072dd 100644 --- a/drivers/png/image_loader_png.cpp +++ b/drivers/png/image_loader_png.cpp @@ -66,12 +66,14 @@ Ref<Image> ImageLoaderPNG::load_mem_png(const uint8_t *p_png, int p_size) { return img; } +Ref<Image> ImageLoaderPNG::unpack_mem_png(const uint8_t *p_png, int p_size) { + ERR_FAIL_COND_V(p_size < 4, Ref<Image>()); + ERR_FAIL_COND_V(p_png[0] != 'P' || p_png[1] != 'N' || p_png[2] != 'G' || p_png[3] != ' ', Ref<Image>()); + return load_mem_png(&p_png[4], p_size - 4); +} + Ref<Image> ImageLoaderPNG::lossless_unpack_png(const Vector<uint8_t> &p_data) { - const int len = p_data.size(); - ERR_FAIL_COND_V(len < 4, Ref<Image>()); - const uint8_t *r = p_data.ptr(); - ERR_FAIL_COND_V(r[0] != 'P' || r[1] != 'N' || r[2] != 'G' || r[3] != ' ', Ref<Image>()); - return load_mem_png(&r[4], len - 4); + return unpack_mem_png(p_data.ptr(), p_data.size()); } Vector<uint8_t> ImageLoaderPNG::lossless_pack_png(const Ref<Image> &p_image) { @@ -99,6 +101,7 @@ Vector<uint8_t> ImageLoaderPNG::lossless_pack_png(const Ref<Image> &p_image) { ImageLoaderPNG::ImageLoaderPNG() { Image::_png_mem_loader_func = load_mem_png; + Image::_png_mem_unpacker_func = unpack_mem_png; Image::png_unpacker = lossless_unpack_png; Image::png_packer = lossless_pack_png; } diff --git a/drivers/png/image_loader_png.h b/drivers/png/image_loader_png.h index d587672dd1..ecce9a405b 100644 --- a/drivers/png/image_loader_png.h +++ b/drivers/png/image_loader_png.h @@ -37,6 +37,7 @@ class ImageLoaderPNG : public ImageFormatLoader { private: static Vector<uint8_t> lossless_pack_png(const Ref<Image> &p_image); static Ref<Image> lossless_unpack_png(const Vector<uint8_t> &p_data); + static Ref<Image> unpack_mem_png(const uint8_t *p_png, int p_size); static Ref<Image> load_mem_png(const uint8_t *p_png, int p_size); public: diff --git a/drivers/unix/file_access_unix.cpp b/drivers/unix/file_access_unix.cpp index ca5a13799e..d1e4d207e7 100644 --- a/drivers/unix/file_access_unix.cpp +++ b/drivers/unix/file_access_unix.cpp @@ -228,6 +228,51 @@ uint8_t FileAccessUnix::get_8() const { return b; } +uint16_t FileAccessUnix::get_16() const { + ERR_FAIL_NULL_V_MSG(f, 0, "File must be opened before use."); + + uint16_t b = 0; + if (fread(&b, 1, 2, f) != 2) { + check_errors(); + } + + if (big_endian) { + b = BSWAP16(b); + } + + return b; +} + +uint32_t FileAccessUnix::get_32() const { + ERR_FAIL_NULL_V_MSG(f, 0, "File must be opened before use."); + + uint32_t b = 0; + if (fread(&b, 1, 4, f) != 4) { + check_errors(); + } + + if (big_endian) { + b = BSWAP32(b); + } + + return b; +} + +uint64_t FileAccessUnix::get_64() const { + ERR_FAIL_NULL_V_MSG(f, 0, "File must be opened before use."); + + uint64_t b = 0; + if (fread(&b, 1, 8, f) != 8) { + check_errors(); + } + + if (big_endian) { + b = BSWAP64(b); + } + + return b; +} + uint64_t FileAccessUnix::get_buffer(uint8_t *p_dst, uint64_t p_length) const { ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); ERR_FAIL_NULL_V_MSG(f, -1, "File must be opened before use."); @@ -251,6 +296,36 @@ void FileAccessUnix::store_8(uint8_t p_dest) { ERR_FAIL_COND(fwrite(&p_dest, 1, 1, f) != 1); } +void FileAccessUnix::store_16(uint16_t p_dest) { + ERR_FAIL_NULL_MSG(f, "File must be opened before use."); + + if (big_endian) { + p_dest = BSWAP16(p_dest); + } + + ERR_FAIL_COND(fwrite(&p_dest, 1, 2, f) != 2); +} + +void FileAccessUnix::store_32(uint32_t p_dest) { + ERR_FAIL_NULL_MSG(f, "File must be opened before use."); + + if (big_endian) { + p_dest = BSWAP32(p_dest); + } + + ERR_FAIL_COND(fwrite(&p_dest, 1, 4, f) != 4); +} + +void FileAccessUnix::store_64(uint64_t p_dest) { + ERR_FAIL_NULL_MSG(f, "File must be opened before use."); + + if (big_endian) { + p_dest = BSWAP64(p_dest); + } + + ERR_FAIL_COND(fwrite(&p_dest, 1, 8, f) != 8); +} + void FileAccessUnix::store_buffer(const uint8_t *p_src, uint64_t p_length) { ERR_FAIL_NULL_MSG(f, "File must be opened before use."); ERR_FAIL_COND(!p_src && p_length > 0); diff --git a/drivers/unix/file_access_unix.h b/drivers/unix/file_access_unix.h index 2bfac27c4f..553fbcf355 100644 --- a/drivers/unix/file_access_unix.h +++ b/drivers/unix/file_access_unix.h @@ -68,12 +68,18 @@ public: virtual bool eof_reached() const override; ///< reading passed EOF virtual uint8_t get_8() const override; ///< get a byte + virtual uint16_t get_16() const override; + virtual uint32_t get_32() const override; + virtual uint64_t get_64() const override; virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; virtual Error get_error() const override; ///< get last error virtual void flush() override; virtual void store_8(uint8_t p_dest) override; ///< store a byte + virtual void store_16(uint16_t p_dest) override; + virtual void store_32(uint32_t p_dest) override; + virtual void store_64(uint64_t p_dest) override; virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes virtual bool file_exists(const String &p_path) override; ///< return true if a file exists diff --git a/drivers/unix/os_unix.cpp b/drivers/unix/os_unix.cpp index 50abd5af42..51ea9234d4 100644 --- a/drivers/unix/os_unix.cpp +++ b/drivers/unix/os_unix.cpp @@ -753,12 +753,27 @@ String OS_Unix::get_executable_path() const { return OS::get_executable_path(); } return b; -#elif defined(__OpenBSD__) || defined(__NetBSD__) +#elif defined(__OpenBSD__) char resolved_path[MAXPATHLEN]; realpath(OS::get_executable_path().utf8().get_data(), resolved_path); return String(resolved_path); +#elif defined(__NetBSD__) + int mib[4] = { CTL_KERN, KERN_PROC_ARGS, -1, KERN_PROC_PATHNAME }; + char buf[MAXPATHLEN]; + size_t len = sizeof(buf); + if (sysctl(mib, 4, buf, &len, nullptr, 0) != 0) { + WARN_PRINT("Couldn't get executable path from sysctl"); + return OS::get_executable_path(); + } + + // NetBSD does not always return a normalized path. For example if argv[0] is "./a.out" then executable path is "/home/netbsd/./a.out". Normalize with realpath: + char resolved_path[MAXPATHLEN]; + + realpath(buf, resolved_path); + + return String(resolved_path); #elif defined(__FreeBSD__) int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; char buf[MAXPATHLEN]; diff --git a/drivers/windows/file_access_windows.cpp b/drivers/windows/file_access_windows.cpp index 9d21073f19..1b69f5da2c 100644 --- a/drivers/windows/file_access_windows.cpp +++ b/drivers/windows/file_access_windows.cpp @@ -284,6 +284,72 @@ uint8_t FileAccessWindows::get_8() const { return b; } +uint16_t FileAccessWindows::get_16() const { + ERR_FAIL_NULL_V(f, 0); + + if (flags == READ_WRITE || flags == WRITE_READ) { + if (prev_op == WRITE) { + fflush(f); + } + prev_op = READ; + } + + uint16_t b = 0; + if (fread(&b, 1, 2, f) != 2) { + check_errors(); + } + + if (big_endian) { + b = BSWAP16(b); + } + + return b; +} + +uint32_t FileAccessWindows::get_32() const { + ERR_FAIL_NULL_V(f, 0); + + if (flags == READ_WRITE || flags == WRITE_READ) { + if (prev_op == WRITE) { + fflush(f); + } + prev_op = READ; + } + + uint32_t b = 0; + if (fread(&b, 1, 4, f) != 4) { + check_errors(); + } + + if (big_endian) { + b = BSWAP32(b); + } + + return b; +} + +uint64_t FileAccessWindows::get_64() const { + ERR_FAIL_NULL_V(f, 0); + + if (flags == READ_WRITE || flags == WRITE_READ) { + if (prev_op == WRITE) { + fflush(f); + } + prev_op = READ; + } + + uint64_t b = 0; + if (fread(&b, 1, 8, f) != 8) { + check_errors(); + } + + if (big_endian) { + b = BSWAP64(b); + } + + return b; +} + uint64_t FileAccessWindows::get_buffer(uint8_t *p_dst, uint64_t p_length) const { ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); ERR_FAIL_NULL_V(f, -1); @@ -326,6 +392,63 @@ void FileAccessWindows::store_8(uint8_t p_dest) { fwrite(&p_dest, 1, 1, f); } +void FileAccessWindows::store_16(uint16_t p_dest) { + ERR_FAIL_NULL(f); + + if (flags == READ_WRITE || flags == WRITE_READ) { + if (prev_op == READ) { + if (last_error != ERR_FILE_EOF) { + fseek(f, 0, SEEK_CUR); + } + } + prev_op = WRITE; + } + + if (big_endian) { + p_dest = BSWAP16(p_dest); + } + + fwrite(&p_dest, 1, 2, f); +} + +void FileAccessWindows::store_32(uint32_t p_dest) { + ERR_FAIL_NULL(f); + + if (flags == READ_WRITE || flags == WRITE_READ) { + if (prev_op == READ) { + if (last_error != ERR_FILE_EOF) { + fseek(f, 0, SEEK_CUR); + } + } + prev_op = WRITE; + } + + if (big_endian) { + p_dest = BSWAP32(p_dest); + } + + fwrite(&p_dest, 1, 4, f); +} + +void FileAccessWindows::store_64(uint64_t p_dest) { + ERR_FAIL_NULL(f); + + if (flags == READ_WRITE || flags == WRITE_READ) { + if (prev_op == READ) { + if (last_error != ERR_FILE_EOF) { + fseek(f, 0, SEEK_CUR); + } + } + prev_op = WRITE; + } + + if (big_endian) { + p_dest = BSWAP64(p_dest); + } + + fwrite(&p_dest, 1, 8, f); +} + void FileAccessWindows::store_buffer(const uint8_t *p_src, uint64_t p_length) { ERR_FAIL_NULL(f); ERR_FAIL_COND(!p_src && p_length > 0); diff --git a/drivers/windows/file_access_windows.h b/drivers/windows/file_access_windows.h index 73143009fc..dabb174cb2 100644 --- a/drivers/windows/file_access_windows.h +++ b/drivers/windows/file_access_windows.h @@ -69,12 +69,18 @@ public: virtual bool eof_reached() const override; ///< reading passed EOF virtual uint8_t get_8() const override; ///< get a byte + virtual uint16_t get_16() const override; + virtual uint32_t get_32() const override; + virtual uint64_t get_64() const override; virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; virtual Error get_error() const override; ///< get last error virtual void flush() override; virtual void store_8(uint8_t p_dest) override; ///< store a byte + virtual void store_16(uint16_t p_dest) override; + virtual void store_32(uint32_t p_dest) override; + virtual void store_64(uint64_t p_dest) override; virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; ///< store an array of bytes virtual bool file_exists(const String &p_name) override; ///< return true if a file exists diff --git a/editor/action_map_editor.cpp b/editor/action_map_editor.cpp index ab923f99fe..7bb6ec0d5d 100644 --- a/editor/action_map_editor.cpp +++ b/editor/action_map_editor.cpp @@ -532,7 +532,7 @@ ActionMapEditor::ActionMapEditor() { action_list_search = memnew(LineEdit); action_list_search->set_h_size_flags(Control::SIZE_EXPAND_FILL); - action_list_search->set_placeholder(TTR("Filter by name...")); + action_list_search->set_placeholder(TTR("Filter by Name")); action_list_search->set_clear_button_enabled(true); action_list_search->connect("text_changed", callable_mp(this, &ActionMapEditor::_search_term_updated)); top_hbox->add_child(action_list_search); diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp index 08306256f1..ce9bb1c92b 100644 --- a/editor/animation_bezier_editor.cpp +++ b/editor/animation_bezier_editor.cpp @@ -1520,7 +1520,7 @@ void AnimationBezierTrackEdit::_zoom_callback(float p_zoom_factor, Vector2 p_ori // Alternate zoom (doesn't affect timeline). timeline_v_zoom = CLAMP(timeline_v_zoom * p_zoom_factor, 0.000001, 100000); } else { - timeline->get_zoom()->set_value(timeline->get_zoom()->get_value() / p_zoom_factor); + timeline->_zoom_callback(p_zoom_factor, p_origin, p_event); } timeline_v_scroll = timeline_v_scroll + (p_origin.y - get_size().y / 2.0) * (timeline_v_zoom - v_zoom_orig); queue_redraw(); @@ -1688,6 +1688,7 @@ void AnimationBezierTrackEdit::_bind_methods() { AnimationBezierTrackEdit::AnimationBezierTrackEdit() { panner.instantiate(); panner->set_callbacks(callable_mp(this, &AnimationBezierTrackEdit::_pan_callback), callable_mp(this, &AnimationBezierTrackEdit::_zoom_callback)); + panner->set_scroll_zoom_factor(AnimationTimelineEdit::SCROLL_ZOOM_FACTOR); play_position = memnew(Control); play_position->set_mouse_filter(MOUSE_FILTER_PASS); diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index d9d28e5c09..4c897aedb5 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -1247,6 +1247,58 @@ void AnimationMultiTrackKeyEdit::set_use_fps(bool p_enable) { } void AnimationTimelineEdit::_zoom_changed(double) { + double zoom_pivot = 0; // Point on timeline to stay fixed. + double zoom_pivot_delta = 0; // Delta seconds from left-most point on timeline to zoom pivot. + + int timeline_width_pixels = get_size().width - get_buttons_width() - get_name_limit(); + double timeline_width_seconds = timeline_width_pixels / last_zoom_scale; // Length (in seconds) of visible part of timeline before zoom. + double updated_timeline_width_seconds = timeline_width_pixels / get_zoom_scale(); // Length after zoom. + double updated_timeline_half_width = updated_timeline_width_seconds / 2.0; + bool zooming = updated_timeline_width_seconds < timeline_width_seconds; + + double timeline_left = get_value(); + double timeline_right = timeline_left + timeline_width_seconds; + double timeline_center = timeline_left + timeline_width_seconds / 2.0; + + if (zoom_callback_occured) { // Zooming with scroll wheel will focus on the position of the mouse. + double zoom_scroll_origin_norm = (zoom_scroll_origin.x - get_name_limit()) / timeline_width_pixels; + zoom_scroll_origin_norm = MAX(zoom_scroll_origin_norm, 0); + zoom_pivot = timeline_left + timeline_width_seconds * zoom_scroll_origin_norm; + zoom_pivot_delta = updated_timeline_width_seconds * zoom_scroll_origin_norm; + zoom_callback_occured = false; + } else { // Zooming with slider will depend on the current play position. + // If the play position is not in range, or exactly in the center, zoom in on the center. + if (get_play_position() < timeline_left || get_play_position() > timeline_left + timeline_width_seconds || get_play_position() == timeline_center) { + zoom_pivot = timeline_center; + zoom_pivot_delta = updated_timeline_half_width; + } + // Zoom from right if play position is right of center, + // and shrink from right if play position is left of center. + else if ((get_play_position() > timeline_center) == zooming) { + // If play position crosses to other side of center, center it. + bool center_passed = (get_play_position() < timeline_right - updated_timeline_half_width) == zooming; + zoom_pivot = center_passed ? get_play_position() : timeline_right; + double center_offset = CMP_EPSILON * (zooming ? 1 : -1); // Small offset to prevent crossover. + zoom_pivot_delta = center_passed ? updated_timeline_half_width + center_offset : updated_timeline_width_seconds; + } + // Zoom from left if play position is left of center, + // and shrink from left if play position is right of center. + else if ((get_play_position() <= timeline_center) == zooming) { + // If play position crosses to other side of center, center it. + bool center_passed = (get_play_position() > timeline_left + updated_timeline_half_width) == zooming; + zoom_pivot = center_passed ? get_play_position() : timeline_left; + double center_offset = CMP_EPSILON * (zooming ? -1 : 1); // Small offset to prevent crossover. + zoom_pivot_delta = center_passed ? updated_timeline_half_width + center_offset : 0; + } + } + + double hscroll_pos = zoom_pivot - zoom_pivot_delta; + hscroll_pos = CLAMP(hscroll_pos, hscroll->get_min(), hscroll->get_max()); + + hscroll->set_value(hscroll_pos); + hscroll_on_zoom_buffer = hscroll_pos; // In case of page update. + last_zoom_scale = get_zoom_scale(); + queue_redraw(); play_position->queue_redraw(); emit_signal(SNAME("zoom_changed")); @@ -1428,6 +1480,11 @@ void AnimationTimelineEdit::_notification(int p_what) { set_page(zoomw / scale); + if (hscroll->is_visible() && hscroll_on_zoom_buffer >= 0) { + hscroll->set_value(hscroll_on_zoom_buffer); + hscroll_on_zoom_buffer = -1.0; + } + int end_px = (l - get_value()) * scale; int begin_px = -get_value() * scale; Color notimecol = get_theme_color(SNAME("dark_color_2"), EditorStringName(Editor)); @@ -1733,7 +1790,9 @@ void AnimationTimelineEdit::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> void AnimationTimelineEdit::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) { double current_zoom_value = get_zoom()->get_value(); - get_zoom()->set_value(MAX(0.01, current_zoom_value * p_zoom_factor)); + zoom_scroll_origin = p_origin; + zoom_callback_occured = true; + get_zoom()->set_value(MAX(0.01, current_zoom_value - (1.0 - p_zoom_factor))); } void AnimationTimelineEdit::set_use_fps(bool p_use_fps) { @@ -1809,6 +1868,7 @@ AnimationTimelineEdit::AnimationTimelineEdit() { len_hb->hide(); panner.instantiate(); + panner->set_scroll_zoom_factor(SCROLL_ZOOM_FACTOR); panner->set_callbacks(callable_mp(this, &AnimationTimelineEdit::_pan_callback), callable_mp(this, &AnimationTimelineEdit::_zoom_callback)); panner->set_pan_axis(ViewPanner::PAN_AXIS_HORIZONTAL); @@ -5493,8 +5553,7 @@ void AnimationTrackEditor::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p } void AnimationTrackEditor::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) { - double current_zoom_value = timeline->get_zoom()->get_value(); - timeline->get_zoom()->set_value(MAX(0.01, current_zoom_value * p_zoom_factor)); + timeline->_zoom_callback(p_zoom_factor, p_origin, p_event); } void AnimationTrackEditor::_cancel_bezier_edit() { @@ -6530,6 +6589,7 @@ AnimationTrackEditor::AnimationTrackEditor() { timeline->connect("length_changed", callable_mp(this, &AnimationTrackEditor::_update_length)); panner.instantiate(); + panner->set_scroll_zoom_factor(AnimationTimelineEdit::SCROLL_ZOOM_FACTOR); panner->set_callbacks(callable_mp(this, &AnimationTrackEditor::_pan_callback), callable_mp(this, &AnimationTrackEditor::_zoom_callback)); scroll = memnew(ScrollContainer); diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index 0667b8e80e..b5242e2f67 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -131,6 +131,11 @@ protected: class AnimationTimelineEdit : public Range { GDCLASS(AnimationTimelineEdit, Range); + friend class AnimationBezierTrackEdit; + friend class AnimationTrackEditor; + + static constexpr float SCROLL_ZOOM_FACTOR = 1.02f; // Zoom factor per mouse scroll in the animation editor. The closer to 1.0, the finer the control. + Ref<Animation> animation; bool read_only = false; @@ -167,6 +172,11 @@ class AnimationTimelineEdit : public Range { bool dragging_hsize = false; float dragging_hsize_from = 0.0f; float dragging_hsize_at = 0.0f; + double last_zoom_scale = 1.0; + double hscroll_on_zoom_buffer = -1.0; + + Vector2 zoom_scroll_origin; + bool zoom_callback_occured = false; virtual void gui_input(const Ref<InputEvent> &p_event) override; void _track_added(int p_track); diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index 9a10766900..b17affa246 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -1277,7 +1277,7 @@ void CodeTextEditor::move_lines_up() { for (int j = 0; j < caret_groups[i].size(); j++) { int c = caret_groups[i][j]; - Vector<int> caret_parameters = caret_group_parameters[j]; + const Vector<int> &caret_parameters = caret_group_parameters[j]; text_editor->set_caret_line(caret_parameters[4] - 1, c == 0, true, 0, c); text_editor->set_caret_column(caret_parameters[5], c == 0, c); @@ -1374,7 +1374,7 @@ void CodeTextEditor::move_lines_down() { for (int j = 0; j < caret_groups[i].size(); j++) { int c = caret_groups[i][j]; - Vector<int> caret_parameters = caret_group_parameters[j]; + const Vector<int> &caret_parameters = caret_group_parameters[j]; text_editor->set_caret_line(caret_parameters[4] + 1, c == 0, true, 0, c); text_editor->set_caret_column(caret_parameters[5], c == 0, c); diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp index e37035f5eb..c4ca90d1e1 100644 --- a/editor/create_dialog.cpp +++ b/editor/create_dialog.cpp @@ -794,6 +794,7 @@ CreateDialog::CreateDialog() { rec_vb->set_v_size_flags(Control::SIZE_EXPAND_FILL); recent = memnew(ItemList); + recent->set_auto_translate(false); rec_vb->add_margin_child(TTR("Recent:"), recent, true); recent->set_allow_reselect(true); recent->connect("item_selected", callable_mp(this, &CreateDialog::_history_selected)); diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp index 7077bce9f7..d926bd0f12 100644 --- a/editor/debugger/script_editor_debugger.cpp +++ b/editor/debugger/script_editor_debugger.cpp @@ -1687,7 +1687,7 @@ void ScriptEditorDebugger::_item_menu_id_pressed(int p_option) { // Parse back the `file:line @ method()` string. const Vector<String> file_line_number = ci->get_text(1).split("@")[0].strip_edges().split(":"); ERR_FAIL_COND_MSG(file_line_number.size() < 2, "Incorrect C++ source stack trace file:line format (please report)."); - const String file = file_line_number[0]; + const String &file = file_line_number[0]; const int line_number = file_line_number[1].to_int(); // Construct a GitHub repository URL and open it in the user's default web browser. diff --git a/editor/dependency_editor.cpp b/editor/dependency_editor.cpp index 26779f5b52..a891491339 100644 --- a/editor/dependency_editor.cpp +++ b/editor/dependency_editor.cpp @@ -398,6 +398,7 @@ DependencyEditorOwners::DependencyEditorOwners() { file_options->connect("id_pressed", callable_mp(this, &DependencyEditorOwners::_file_option)); owners = memnew(ItemList); + owners->set_auto_translate(false); owners->set_select_mode(ItemList::SELECT_MULTI); owners->connect("item_clicked", callable_mp(this, &DependencyEditorOwners::_list_rmb_clicked)); owners->connect("item_activated", callable_mp(this, &DependencyEditorOwners::_select_file)); diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp index a7e3c03250..9c1fc2a8bf 100644 --- a/editor/doc_tools.cpp +++ b/editor/doc_tools.cpp @@ -382,7 +382,7 @@ void DocTools::generate(BitField<GenerateFlags> p_flags) { continue; } - String cname = name; + const String &cname = name; // Property setters and getters do not get exposed as individual methods. HashSet<StringName> setters_getters; diff --git a/editor/editor_about.cpp b/editor/editor_about.cpp index 672697bab0..1f9911f31e 100644 --- a/editor/editor_about.cpp +++ b/editor/editor_about.cpp @@ -99,6 +99,7 @@ ScrollContainer *EditorAbout::_populate_list(const String &p_name, const List<St vbc->add_child(lbl); ItemList *il = memnew(ItemList); + il->set_auto_translate(false); il->set_h_size_flags(Control::SIZE_EXPAND_FILL); il->set_same_column_width(true); il->set_auto_height(true); diff --git a/editor/editor_build_profile.cpp b/editor/editor_build_profile.cpp index c4a5fbd939..8ab8bc4a52 100644 --- a/editor/editor_build_profile.cpp +++ b/editor/editor_build_profile.cpp @@ -481,7 +481,7 @@ void EditorBuildProfileManager::_detect_classes() { String l = f->get_line(); Vector<String> fields = l.split("::"); if (fields.size() == 4) { - String path = fields[0]; + const String &path = fields[0]; DetectedFile df; df.timestamp = fields[1].to_int(); df.md5 = fields[2]; @@ -597,7 +597,7 @@ void EditorBuildProfileManager::_fill_classes_from(TreeItem *p_parent, const Str TreeItem *class_item = class_list->create_item(p_parent); class_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); class_item->set_icon(0, EditorNode::get_singleton()->get_class_icon(p_class)); - String text = p_class; + const String &text = p_class; bool disabled = edited->is_class_disabled(p_class); if (disabled) { @@ -888,7 +888,7 @@ EditorBuildProfileManager::EditorBuildProfileManager() { export_profile->set_access(EditorFileDialog::ACCESS_FILESYSTEM); force_detect_classes = memnew(LineEdit); - main_vbc->add_margin_child(TTR("Forced classes on detect:"), force_detect_classes); + main_vbc->add_margin_child(TTR("Forced Classes on Detect:"), force_detect_classes); force_detect_classes->connect("text_changed", callable_mp(this, &EditorBuildProfileManager::_force_detect_classes_changed)); set_title(TTR("Edit Build Configuration Profile")); diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp index ad11f6135c..d52dd42493 100644 --- a/editor/editor_file_system.cpp +++ b/editor/editor_file_system.cpp @@ -257,7 +257,7 @@ void EditorFileSystem::_scan_filesystem() { if (l.begins_with("::")) { Vector<String> split = l.split("::"); ERR_CONTINUE(split.size() != 3); - String name = split[1]; + const String &name = split[1]; cpath = name; @@ -290,7 +290,7 @@ void EditorFileSystem::_scan_filesystem() { if (deps.length()) { Vector<String> dp = deps.split("<>"); for (int i = 0; i < dp.size(); i++) { - String path = dp[i]; + const String &path = dp[i]; fc.deps.push_back(path); } } diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp index 2b1ab4ca02..cdb187bd44 100644 --- a/editor/editor_help.cpp +++ b/editor/editor_help.cpp @@ -2867,10 +2867,10 @@ void EditorHelpTooltip::parse_tooltip(const String &p_text) { PackedStringArray slices = p_text.split("|", true, 3); ERR_FAIL_COND_MSG(slices.size() < 4, "Invalid tooltip formatting. The expect string should be formatted as 'type|class|property|args'."); - String type = slices[0]; - String class_name = slices[1]; - String property_name = slices[2]; - String property_args = slices[3]; + const String &type = slices[0]; + const String &class_name = slices[1]; + const String &property_name = slices[2]; + const String &property_args = slices[3]; String title; String description; diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 59816a0eca..5fd387bbdc 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -3021,7 +3021,7 @@ void EditorInspector::update_tree() { Vector<String> components = path.split("/"); for (int i = 0; i < components.size(); i++) { - String component = components[i]; + const String &component = components[i]; acc_path += (i > 0) ? "/" + component : component; if (!vbox_per_path[root_vbox].has(acc_path)) { @@ -4209,6 +4209,7 @@ EditorInspector::EditorInspector() { main_vbox->add_theme_constant_override("separation", 0); add_child(main_vbox); set_horizontal_scroll_mode(SCROLL_MODE_DISABLED); + set_follow_focus(true); changing = 0; search_box = nullptr; diff --git a/editor/editor_interface.cpp b/editor/editor_interface.cpp index e179a0e18a..af1d23a4fe 100644 --- a/editor/editor_interface.cpp +++ b/editor/editor_interface.cpp @@ -127,7 +127,7 @@ Vector<Ref<Texture2D>> EditorInterface::make_mesh_previews(const Vector<Ref<Mesh Vector<Ref<Texture2D>> textures; for (int i = 0; i < p_meshes.size(); i++) { - Ref<Mesh> mesh = p_meshes[i]; + const Ref<Mesh> &mesh = p_meshes[i]; if (!mesh.is_valid()) { textures.push_back(Ref<Texture2D>()); continue; diff --git a/editor/editor_layouts_dialog.cpp b/editor/editor_layouts_dialog.cpp index dce61f66d3..c6f518d4c1 100644 --- a/editor/editor_layouts_dialog.cpp +++ b/editor/editor_layouts_dialog.cpp @@ -114,6 +114,7 @@ EditorLayoutsDialog::EditorLayoutsDialog() { add_child(makevb); layout_names = memnew(ItemList); + layout_names->set_auto_translate(false); layout_names->set_auto_height(true); layout_names->set_custom_minimum_size(Size2(300 * EDSCALE, 50 * EDSCALE)); layout_names->set_visible(true); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index ab68a15b72..3eb40b1931 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -113,9 +113,12 @@ #include "editor/gui/editor_title_bar.h" #include "editor/gui/editor_toaster.h" #include "editor/history_dock.h" +#include "editor/import/3d/editor_import_collada.h" +#include "editor/import/3d/resource_importer_obj.h" +#include "editor/import/3d/resource_importer_scene.h" +#include "editor/import/3d/scene_import_settings.h" #include "editor/import/audio_stream_import_settings.h" #include "editor/import/dynamic_font_import_settings.h" -#include "editor/import/editor_import_collada.h" #include "editor/import/resource_importer_bitmask.h" #include "editor/import/resource_importer_bmfont.h" #include "editor/import/resource_importer_csv_translation.h" @@ -123,12 +126,10 @@ #include "editor/import/resource_importer_image.h" #include "editor/import/resource_importer_imagefont.h" #include "editor/import/resource_importer_layered_texture.h" -#include "editor/import/resource_importer_obj.h" #include "editor/import/resource_importer_shader_file.h" #include "editor/import/resource_importer_texture.h" #include "editor/import/resource_importer_texture_atlas.h" #include "editor/import/resource_importer_wav.h" -#include "editor/import/scene_import_settings.h" #include "editor/import_dock.h" #include "editor/inspector_dock.h" #include "editor/multi_node_edit.h" @@ -177,7 +178,7 @@ void EditorNode::disambiguate_filenames(const Vector<String> p_full_paths, Vecto Vector<RBSet<int>> index_sets; HashMap<String, int> scene_name_to_set_index; for (int i = 0; i < r_filenames.size(); i++) { - String scene_name = r_filenames[i]; + const String &scene_name = r_filenames[i]; if (!scene_name_to_set_index.has(scene_name)) { index_sets.append(RBSet<int>()); scene_name_to_set_index.insert(r_filenames[i], index_sets.size() - 1); @@ -233,7 +234,7 @@ void EditorNode::disambiguate_filenames(const Vector<String> p_full_paths, Vecto if (E->get() == F) { continue; } - String other_scene_name = r_filenames[F]; + const String &other_scene_name = r_filenames[F]; if (other_scene_name == scene_name) { duplicate_found = true; break; @@ -3012,6 +3013,16 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { } break; case SET_RENDERER_NAME_SAVE_AND_RESTART: { ProjectSettings::get_singleton()->set("rendering/renderer/rendering_method", renderer_request); + if (renderer_request == "mobile" || renderer_request == "gl_compatibility") { + // Also change the mobile override if changing to a compatible rendering method. + // This prevents visual discrepancies between desktop and mobile platforms. + ProjectSettings::get_singleton()->set("rendering/renderer/rendering_method.mobile", renderer_request); + } else if (renderer_request == "forward_plus") { + // Use the equivalent mobile rendering method. This prevents the rendering method from staying + // on its old choice if moving from `gl_compatibility` to `forward_plus`. + ProjectSettings::get_singleton()->set("rendering/renderer/rendering_method.mobile", "mobile"); + } + ProjectSettings::get_singleton()->save(); save_all_scenes(); @@ -4198,7 +4209,7 @@ void EditorNode::_quick_opened() { bool open_scene_dialog = quick_open->get_base_type() == "PackedScene"; for (int i = 0; i < files.size(); i++) { - String res_path = files[i]; + const String &res_path = files[i]; if (open_scene_dialog || ClassDB::is_parent_class(ResourceLoader::get_resource_type(res_path), "PackedScene")) { open_request(res_path); } else { @@ -4596,8 +4607,8 @@ String EditorNode::_get_system_info() const { } graphics += rendering_device_name; if (video_adapter_driver_info.size() == 2) { // This vector is always either of length 0 or 2. - String vad_name = video_adapter_driver_info[0]; - String vad_version = video_adapter_driver_info[1]; // Version could be potentially empty on Linux/BSD. + const String &vad_name = video_adapter_driver_info[0]; + const String &vad_version = video_adapter_driver_info[1]; // Version could be potentially empty on Linux/BSD. if (!vad_version.is_empty()) { graphics += vformat(" (%s; %s)", vad_name, vad_version); } else { @@ -5189,7 +5200,7 @@ void EditorNode::_load_docks_from_config(Ref<ConfigFile> p_layout, const String Vector<String> names = String(p_layout->get_value(p_section, "dock_" + itos(i + 1))).split(","); for (int j = names.size() - 1; j >= 0; j--) { - String name = names[j]; + const String &name = names[j]; // FIXME: Find it, in a horribly inefficient way. int atidx = -1; @@ -6010,7 +6021,7 @@ void EditorNode::_add_dropped_files_recursive(const Vector<String> &p_files, Str ERR_FAIL_COND(dir.is_null()); for (int i = 0; i < p_files.size(); i++) { - String from = p_files[i]; + const String &from = p_files[i]; String to = to_path.path_join(from.get_file()); if (dir->dir_exists(from)) { @@ -6575,6 +6586,9 @@ void EditorNode::_renderer_selected(int p_which) { } renderer_request = rendering_method; + video_restart_dialog->set_text( + vformat(TTR("Changing the renderer requires restarting the editor.\n\nChoosing Save & Restart will change the rendering method to:\n- Desktop platforms: %s\n- Mobile platforms: %s\n- Web platform: gl_compatibility"), + renderer_request, renderer_request.replace("forward_plus", "mobile"))); video_restart_dialog->popup_centered(); renderer->select(renderer_current); _update_renderer_color(); @@ -7552,7 +7566,7 @@ EditorNode::EditorNode() { renderer->set_focus_mode(Control::FOCUS_NONE); renderer->add_theme_font_override("font", theme->get_font(SNAME("bold"), EditorStringName(EditorFonts))); renderer->add_theme_font_size_override("font_size", theme->get_font_size(SNAME("bold_size"), EditorStringName(EditorFonts))); - renderer->set_tooltip_text(TTR("Choose a renderer.")); + renderer->set_tooltip_text(TTR("Choose a rendering method.\n\nNotes:\n- On mobile platforms, the Mobile rendering method is used if Forward+ is selected here.\n- On the web platform, the Compatibility rendering method is always used.")); right_menu_hb->add_child(renderer); @@ -7594,7 +7608,6 @@ EditorNode::EditorNode() { _update_renderer_color(); video_restart_dialog = memnew(ConfirmationDialog); - video_restart_dialog->set_text(TTR("Changing the renderer requires restarting the editor.")); video_restart_dialog->set_ok_button_text(TTR("Save & Restart")); video_restart_dialog->connect("confirmed", callable_mp(this, &EditorNode::_menu_option).bind(SET_RENDERER_NAME_SAVE_AND_RESTART)); gui_base->add_child(video_restart_dialog); diff --git a/editor/editor_plugin.cpp b/editor/editor_plugin.cpp index 94a49166b8..e79f662cc9 100644 --- a/editor/editor_plugin.cpp +++ b/editor/editor_plugin.cpp @@ -39,8 +39,8 @@ #include "editor/editor_undo_redo_manager.h" #include "editor/export/editor_export.h" #include "editor/gui/editor_title_bar.h" +#include "editor/import/3d/resource_importer_scene.h" #include "editor/import/editor_import_plugin.h" -#include "editor/import/resource_importer_scene.h" #include "editor/inspector_dock.h" #include "editor/plugins/canvas_item_editor_plugin.h" #include "editor/plugins/editor_debugger_plugin.h" diff --git a/editor/editor_plugin_settings.cpp b/editor/editor_plugin_settings.cpp index ef8730cabf..6c38d9de58 100644 --- a/editor/editor_plugin_settings.cpp +++ b/editor/editor_plugin_settings.cpp @@ -64,7 +64,7 @@ void EditorPluginSettings::update_plugins() { for (int i = 0; i < plugins.size(); i++) { Ref<ConfigFile> cf; cf.instantiate(); - const String path = plugins[i]; + const String &path = plugins[i]; Error err2 = cf->load(path); diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp index 52bdc182b2..256f9c0ea9 100644 --- a/editor/editor_properties_array_dict.cpp +++ b/editor/editor_properties_array_dict.cpp @@ -462,7 +462,7 @@ bool EditorPropertyArray::_is_drop_valid(const Dictionary &p_drag_data) const { Vector<String> files = drag_data["files"]; for (int i = 0; i < files.size(); i++) { - String file = files[i]; + const String &file = files[i]; String ftype = EditorFileSystem::get_singleton()->get_file_type(file); for (int j = 0; j < allowed_type.get_slice_count(","); j++) { @@ -504,7 +504,7 @@ void EditorPropertyArray::drop_data_fw(const Point2 &p_point, const Variant &p_d // Loop the file array and add to existing array. for (int i = 0; i < files.size(); i++) { - String file = files[i]; + const String &file = files[i]; Ref<Resource> res = ResourceLoader::load(file); if (res.is_valid()) { diff --git a/editor/editor_resource_picker.cpp b/editor/editor_resource_picker.cpp index ed20e50685..011cb26621 100644 --- a/editor/editor_resource_picker.cpp +++ b/editor/editor_resource_picker.cpp @@ -675,7 +675,7 @@ bool EditorResourcePicker::_is_drop_valid(const Dictionary &p_drag_data) const { return false; } -bool EditorResourcePicker::_is_type_valid(const String p_type_name, HashSet<StringName> p_allowed_types) const { +bool EditorResourcePicker::_is_type_valid(const String p_type_name, const HashSet<StringName> &p_allowed_types) const { for (const StringName &E : p_allowed_types) { String at = E; if (p_type_name == at || ClassDB::is_parent_class(p_type_name, at) || EditorNode::get_editor_data().script_class_is_parent(p_type_name, at)) { diff --git a/editor/editor_resource_picker.h b/editor/editor_resource_picker.h index 35703bcbeb..fb54455e89 100644 --- a/editor/editor_resource_picker.h +++ b/editor/editor_resource_picker.h @@ -99,7 +99,7 @@ class EditorResourcePicker : public HBoxContainer { String _get_resource_type(const Ref<Resource> &p_resource) const; void _get_allowed_types(bool p_with_convert, HashSet<StringName> *p_vector) const; bool _is_drop_valid(const Dictionary &p_drag_data) const; - bool _is_type_valid(const String p_type_name, HashSet<StringName> p_allowed_types) const; + bool _is_type_valid(const String p_type_name, const HashSet<StringName> &p_allowed_types) const; Variant get_drag_data_fw(const Point2 &p_point, Control *p_from); bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; diff --git a/editor/editor_settings_dialog.cpp b/editor/editor_settings_dialog.cpp index a5e70c5b6c..33a176499d 100644 --- a/editor/editor_settings_dialog.cpp +++ b/editor/editor_settings_dialog.cpp @@ -92,9 +92,6 @@ void EditorSettingsDialog::popup_edit_settings() { inspector->edit(EditorSettings::get_singleton()); inspector->get_inspector()->update_tree(); - search_box->select_all(); - search_box->grab_focus(); - _update_shortcuts(); set_process_shortcut_input(true); @@ -762,7 +759,7 @@ EditorSettingsDialog::EditorSettingsDialog() { tab_shortcuts->add_child(top_hbox); shortcut_search_box = memnew(LineEdit); - shortcut_search_box->set_placeholder(TTR("Filter by name...")); + shortcut_search_box->set_placeholder(TTR("Filter by Name")); shortcut_search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL); top_hbox->add_child(shortcut_search_box); shortcut_search_box->connect("text_changed", callable_mp(this, &EditorSettingsDialog::_filter_shortcuts)); diff --git a/editor/event_listener_line_edit.cpp b/editor/event_listener_line_edit.cpp index e51808c78c..2c46e1c20a 100644 --- a/editor/event_listener_line_edit.cpp +++ b/editor/event_listener_line_edit.cpp @@ -175,12 +175,12 @@ void EventListenerLineEdit::_on_text_changed(const String &p_text) { } void EventListenerLineEdit::_on_focus() { - set_placeholder(TTR("Listening for input...")); + set_placeholder(TTR("Listening for Input")); } void EventListenerLineEdit::_on_unfocus() { ignore_next_event = true; - set_placeholder(TTR("Filter by event...")); + set_placeholder(TTR("Filter by Event")); } Ref<InputEvent> EventListenerLineEdit::get_event() const { @@ -227,5 +227,5 @@ void EventListenerLineEdit::_bind_methods() { EventListenerLineEdit::EventListenerLineEdit() { set_caret_blink_enabled(false); - set_placeholder(TTR("Filter by event...")); + set_placeholder(TTR("Filter by Event")); } diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp index ab2ada9ae4..9a0d84d751 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -456,7 +456,7 @@ void EditorExportPlatform::_edit_files_with_filter(Ref<DirAccess> &da, const Vec da->list_dir_end(); for (int i = 0; i < dirs.size(); ++i) { - String dir = dirs[i]; + const String &dir = dirs[i]; if (dir.begins_with(".")) { continue; } @@ -547,8 +547,10 @@ EditorExportPlatform::ExportNotifier::~ExportNotifier() { for (int i = 0; i < export_plugins.size(); i++) { if (GDVIRTUAL_IS_OVERRIDDEN_PTR(export_plugins[i], _export_end)) { export_plugins.write[i]->_export_end_script(); + } else { + export_plugins.write[i]->_export_end(); } - export_plugins.write[i]->_export_end(); + export_plugins.write[i]->_export_end_clear(); export_plugins.write[i]->set_export_preset(Ref<EditorExportPlugin>()); } } @@ -1096,7 +1098,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & Vector<String> fields = l.split("::"); if (fields.size() == 4) { FileExportCache fec; - String path = fields[0]; + const String &path = fields[0]; fec.source_md5 = fields[1].strip_edges(); fec.source_modified_time = fields[2].strip_edges().to_int(); fec.saved_path = fields[3]; @@ -1364,8 +1366,8 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & if (path_remaps.size()) { if (true) { //new remap mode, use always as it's friendlier with multiple .pck exports for (int i = 0; i < path_remaps.size(); i += 2) { - String from = path_remaps[i]; - String to = path_remaps[i + 1]; + const String &from = path_remaps[i]; + const String &to = path_remaps[i + 1]; String remap_file = "[remap]\n\npath=\"" + to.c_escape() + "\"\n"; CharString utf8 = remap_file.utf8(); Vector<uint8_t> new_file; diff --git a/editor/export/editor_export_plugin.cpp b/editor/export/editor_export_plugin.cpp index 6576960b9a..ec8c8c7b69 100644 --- a/editor/export/editor_export_plugin.cpp +++ b/editor/export/editor_export_plugin.cpp @@ -291,6 +291,8 @@ void EditorExportPlugin::_export_file(const String &p_path, const String &p_type void EditorExportPlugin::_export_begin(const HashSet<String> &p_features, bool p_debug, const String &p_path, int p_flags) { } +void EditorExportPlugin::_export_end() {} + void EditorExportPlugin::skip() { skipped = true; } diff --git a/editor/export/editor_export_plugin.h b/editor/export/editor_export_plugin.h index 7d866ce37e..fe97d2e04f 100644 --- a/editor/export/editor_export_plugin.h +++ b/editor/export/editor_export_plugin.h @@ -71,7 +71,7 @@ class EditorExportPlugin : public RefCounted { skipped = false; } - _FORCE_INLINE_ void _export_end() { + _FORCE_INLINE_ void _export_end_clear() { ios_frameworks.clear(); ios_embedded_frameworks.clear(); ios_bundle_files.clear(); @@ -108,6 +108,7 @@ protected: virtual void _export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features); virtual void _export_begin(const HashSet<String> &p_features, bool p_debug, const String &p_path, int p_flags); + virtual void _export_end(); static void _bind_methods(); diff --git a/editor/export/project_export.cpp b/editor/export/project_export.cpp index f2c5eeb2ed..b22f70b0fa 100644 --- a/editor/export/project_export.cpp +++ b/editor/export/project_export.cpp @@ -1191,6 +1191,7 @@ ProjectExportDialog::ProjectExportDialog() { preset_vb->add_child(mc); mc->set_v_size_flags(Control::SIZE_EXPAND_FILL); presets = memnew(ItemList); + presets->set_auto_translate(false); SET_DRAG_FORWARDING_GCD(presets, ProjectExportDialog); mc->add_child(presets); presets->connect("item_selected", callable_mp(this, &ProjectExportDialog::_edit_preset)); @@ -1283,7 +1284,7 @@ ProjectExportDialog::ProjectExportDialog() { ClassDB::get_inheriters_from_class("Resource", &resource_names); PackedStringArray strippable; - for (StringName resource_name : resource_names) { + for (const StringName &resource_name : resource_names) { if (ClassDB::has_method(resource_name, "create_placeholder", true)) { strippable.push_back(resource_name); } diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 9e11ffa242..12a3478e44 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -47,7 +47,7 @@ #include "editor/editor_string_names.h" #include "editor/gui/editor_dir_dialog.h" #include "editor/gui/editor_scene_tabs.h" -#include "editor/import/scene_import_settings.h" +#include "editor/import/3d/scene_import_settings.h" #include "editor/import_dock.h" #include "editor/plugins/editor_resource_tooltip_plugins.h" #include "editor/scene_create_dialog.h" @@ -388,7 +388,7 @@ void FileSystemDock::_update_tree(const Vector<String> &p_uncollapsed_paths, boo const Color default_folder_color = get_theme_color(SNAME("folder_icon_color"), SNAME("FileDialog")); for (int i = 0; i < favorite_paths.size(); i++) { - String favorite = favorite_paths[i]; + const String &favorite = favorite_paths[i]; if (!favorite.begins_with("res://")) { continue; } @@ -2295,7 +2295,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected // Instantiate all selected scenes. Vector<String> paths; for (int i = 0; i < p_selected.size(); i++) { - String fpath = p_selected[i]; + const String &fpath = p_selected[i]; if (EditorFileSystem::get_singleton()->get_file_type(fpath) == "PackedScene") { paths.push_back(fpath); } @@ -2333,7 +2333,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected case FILE_DEPENDENCIES: { // Checkout the file dependencies. if (!p_selected.is_empty()) { - String fpath = p_selected[0]; + const String &fpath = p_selected[0]; deps_editor->edit(fpath); } } break; @@ -2341,7 +2341,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected case FILE_OWNERS: { // Checkout the file owners. if (!p_selected.is_empty()) { - String fpath = p_selected[0]; + const String &fpath = p_selected[0]; owners_editor->show(fpath); } } break; @@ -2351,7 +2351,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected to_move.clear(); Vector<String> collapsed_paths = _remove_self_included_paths(p_selected); for (int i = collapsed_paths.size() - 1; i >= 0; i--) { - String fpath = collapsed_paths[i]; + const String &fpath = collapsed_paths[i]; if (fpath != "res://") { to_move.push_back(FileOrFolder(fpath, !fpath.ends_with("/"))); } @@ -2399,7 +2399,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected Vector<String> collapsed_paths = _remove_self_included_paths(p_selected); for (int i = 0; i < collapsed_paths.size(); i++) { - String fpath = collapsed_paths[i]; + const String &fpath = collapsed_paths[i]; if (fpath != "res://") { if (fpath.ends_with("/")) { remove_folders.push_back(fpath); @@ -2471,7 +2471,7 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected case FILE_COPY_PATH: { if (!p_selected.is_empty()) { - String fpath = p_selected[0]; + const String &fpath = p_selected[0]; DisplayServer::get_singleton()->clipboard_set(fpath); } } break; @@ -2991,7 +2991,7 @@ void FileSystemDock::_folder_color_index_pressed(int p_index, PopupMenu *p_menu) // Update project settings with new folder colors. for (int i = 0; i < selected.size(); i++) { - String fpath = selected[i]; + const String &fpath = selected[i]; if (chosen_color_name) { assigned_folder_colors[fpath] = chosen_color_name; @@ -3022,7 +3022,7 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, Vector<Str bool all_not_favorites = true; for (int i = 0; i < p_paths.size(); i++) { - String fpath = p_paths[i]; + const String &fpath = p_paths[i]; if (fpath.ends_with("/")) { foldernames.push_back(fpath); all_files = false; @@ -3180,7 +3180,7 @@ void FileSystemDock::_file_and_folders_fill_popup(PopupMenu *p_popup, Vector<Str } if (p_paths.size() == 1) { - const String fpath = p_paths[0]; + const String &fpath = p_paths[0]; #if !defined(ANDROID_ENABLED) && !defined(WEB_ENABLED) p_popup->add_separator(); @@ -3565,7 +3565,7 @@ void FileSystemDock::_update_import_dock() { Vector<String> imports; String import_type; for (int i = 0; i < efiles.size(); i++) { - String fpath = efiles[i]; + const String &fpath = efiles[i]; Ref<ConfigFile> cf; cf.instantiate(); Error err = cf->load(fpath + ".import"); diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp index fad7e29be7..9764574d04 100644 --- a/editor/gui/editor_file_dialog.cpp +++ b/editor/gui/editor_file_dialog.cpp @@ -1909,6 +1909,7 @@ EditorFileDialog::EditorFileDialog() { fav_down->connect("pressed", callable_mp(this, &EditorFileDialog::_favorite_move_down)); favorites = memnew(ItemList); + favorites->set_auto_translate(false); fav_vb->add_child(favorites); favorites->set_v_size_flags(Control::SIZE_EXPAND_FILL); favorites->connect("item_selected", callable_mp(this, &EditorFileDialog::_favorite_selected)); @@ -1918,6 +1919,7 @@ EditorFileDialog::EditorFileDialog() { rec_vb->set_custom_minimum_size(Size2(150, 100) * EDSCALE); rec_vb->set_v_size_flags(Control::SIZE_EXPAND_FILL); recent = memnew(ItemList); + recent->set_auto_translate(false); recent->set_allow_reselect(true); rec_vb->add_margin_child(TTR("Recent:"), recent, true); recent->connect("item_selected", callable_mp(this, &EditorFileDialog::_recent_selected)); @@ -1941,6 +1943,7 @@ EditorFileDialog::EditorFileDialog() { // Item (files and folders) list with context menu. item_list = memnew(ItemList); + item_list->set_auto_translate(false); item_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); item_list->connect("item_clicked", callable_mp(this, &EditorFileDialog::_item_list_item_rmb_clicked)); item_list->connect("empty_clicked", callable_mp(this, &EditorFileDialog::_item_list_empty_clicked)); diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp index 815fe49c7d..33135a6bea 100644 --- a/editor/gui/scene_tree_editor.cpp +++ b/editor/gui/scene_tree_editor.cpp @@ -747,7 +747,7 @@ bool SceneTreeEditor::_item_matches_all_terms(TreeItem *p_item, PackedStringArra } for (int i = 0; i < p_terms.size(); i++) { - String term = p_terms[i]; + const String &term = p_terms[i]; // Recognize special filter. if (term.contains(":") && !term.get_slicec(':', 0).is_empty()) { diff --git a/editor/history_dock.cpp b/editor/history_dock.cpp index 0ec840b8f1..6ec240fdf0 100644 --- a/editor/history_dock.cpp +++ b/editor/history_dock.cpp @@ -248,6 +248,7 @@ HistoryDock::HistoryDock() { global_history_checkbox->connect("toggled", callable_mp(this, &HistoryDock::refresh_history).unbind(1)); action_list = memnew(ItemList); + action_list->set_auto_translate(false); add_child(action_list); action_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); action_list->connect("item_selected", callable_mp(this, &HistoryDock::seek_history)); diff --git a/editor/icons/PhysicsMaterial.svg b/editor/icons/PhysicsMaterial.svg new file mode 100644 index 0000000000..bdb41a1d27 --- /dev/null +++ b/editor/icons/PhysicsMaterial.svg @@ -0,0 +1 @@ +<svg height="16" width="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><g fill="none" stroke="#e0e0e0"><path d="M14 8.5v4L8 15l-6-2.5v-4M8 11v4" stroke-opacity=".6" stroke-linejoin="round" stroke-width="2"/><path d="m8 6 6 2.5L8 11 2 8.5z" stroke-linejoin="round" stroke-width="2"/><path d="M3.5 2l1 1.8M8 1v2m4.5-1-1 1.8" stroke-linecap="round" stroke-width="1.75"/></g></svg> diff --git a/editor/import/collada.cpp b/editor/import/3d/collada.cpp index be63973ea7..ae8041d6fe 100644 --- a/editor/import/collada.cpp +++ b/editor/import/3d/collada.cpp @@ -1718,7 +1718,7 @@ void Collada::_parse_animation(XMLParser &p_parser) { for (int i = 0; i < channel_sources.size(); i++) { String source = _uri_to_id(channel_sources[i]); - String target = channel_targets[i]; + const String &target = channel_targets[i]; ERR_CONTINUE(!samplers.has(source)); HashMap<String, String> &sampler = samplers[source]; diff --git a/editor/import/collada.h b/editor/import/3d/collada.h index 7877b1e86d..7877b1e86d 100644 --- a/editor/import/collada.h +++ b/editor/import/3d/collada.h diff --git a/editor/import/editor_import_collada.cpp b/editor/import/3d/editor_import_collada.cpp index 3f7ed8ab8b..49fec679bf 100644 --- a/editor/import/editor_import_collada.cpp +++ b/editor/import/3d/editor_import_collada.cpp @@ -32,7 +32,7 @@ #include "core/os/os.h" #include "editor/editor_node.h" -#include "editor/import/collada.h" +#include "editor/import/3d/collada.h" #include "scene/3d/camera_3d.h" #include "scene/3d/importer_mesh_instance_3d.h" #include "scene/3d/light_3d.h" @@ -1151,7 +1151,7 @@ Error ColladaImport::_create_resources(Collada::Node *p_node, bool p_use_compres ERR_FAIL_COND_V(skeletons.is_empty(), ERR_INVALID_DATA); - String skname = skeletons[0]; + const String &skname = skeletons[0]; ERR_FAIL_COND_V(!node_map.has(skname), ERR_INVALID_DATA); NodeMap nmsk = node_map[skname]; Skeleton3D *sk = Object::cast_to<Skeleton3D>(nmsk.node); @@ -1205,7 +1205,7 @@ Error ColladaImport::_create_resources(Collada::Node *p_node, bool p_use_compres valid = true; Vector<String> names = morph->sources[target].sarray; for (int i = 0; i < names.size(); i++) { - String meshid2 = names[i]; + const String &meshid2 = names[i]; if (collada.state.mesh_data_map.has(meshid2)) { Ref<ImporterMesh> mesh = Ref<ImporterMesh>(memnew(ImporterMesh)); const Collada::MeshData &meshdata = collada.state.mesh_data_map[meshid2]; diff --git a/editor/import/editor_import_collada.h b/editor/import/3d/editor_import_collada.h index 2950f499cb..e0214c2fd0 100644 --- a/editor/import/editor_import_collada.h +++ b/editor/import/3d/editor_import_collada.h @@ -31,7 +31,7 @@ #ifndef EDITOR_IMPORT_COLLADA_H #define EDITOR_IMPORT_COLLADA_H -#include "editor/import/resource_importer_scene.h" +#include "editor/import/3d/resource_importer_scene.h" class EditorSceneFormatImporterCollada : public EditorSceneFormatImporter { GDCLASS(EditorSceneFormatImporterCollada, EditorSceneFormatImporter); diff --git a/editor/import/post_import_plugin_skeleton_renamer.cpp b/editor/import/3d/post_import_plugin_skeleton_renamer.cpp index adeea51dae..ffe75f189c 100644 --- a/editor/import/post_import_plugin_skeleton_renamer.cpp +++ b/editor/import/3d/post_import_plugin_skeleton_renamer.cpp @@ -30,7 +30,7 @@ #include "post_import_plugin_skeleton_renamer.h" -#include "editor/import/scene_import_settings.h" +#include "editor/import/3d/scene_import_settings.h" #include "scene/3d/bone_attachment_3d.h" #include "scene/3d/importer_mesh_instance_3d.h" #include "scene/3d/skeleton_3d.h" @@ -45,7 +45,7 @@ void PostImportPluginSkeletonRenamer::get_internal_import_options(InternalImport } } -void PostImportPluginSkeletonRenamer::_internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options, HashMap<String, String> p_rename_map) { +void PostImportPluginSkeletonRenamer::_internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options, const HashMap<String, String> &p_rename_map) { // Prepare objects. Object *map = p_options["retarget/bone_map"].get_validated_object(); if (!map || !bool(p_options["retarget/bone_renamer/rename_bones"])) { @@ -121,7 +121,7 @@ void PostImportPluginSkeletonRenamer::_internal_process(InternalImportCategory p // Rename bones in all Nodes by calling method. { Dictionary rename_map_dict; - for (HashMap<String, String>::Iterator E = p_rename_map.begin(); E; ++E) { + for (HashMap<String, String>::ConstIterator E = p_rename_map.begin(); E; ++E) { rename_map_dict[E->key] = E->value; } diff --git a/editor/import/post_import_plugin_skeleton_renamer.h b/editor/import/3d/post_import_plugin_skeleton_renamer.h index 98f778f6c2..a977117374 100644 --- a/editor/import/post_import_plugin_skeleton_renamer.h +++ b/editor/import/3d/post_import_plugin_skeleton_renamer.h @@ -40,7 +40,7 @@ public: virtual void get_internal_import_options(InternalImportCategory p_category, List<ResourceImporter::ImportOption> *r_options) override; virtual void internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options) override; - void _internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options, HashMap<String, String> p_rename_map); + void _internal_process(InternalImportCategory p_category, Node *p_base_scene, Node *p_node, Ref<Resource> p_resource, const Dictionary &p_options, const HashMap<String, String> &p_rename_map); PostImportPluginSkeletonRenamer(); }; diff --git a/editor/import/post_import_plugin_skeleton_rest_fixer.cpp b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp index 3390bf4ed4..53bf24fb7e 100644 --- a/editor/import/post_import_plugin_skeleton_rest_fixer.cpp +++ b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.cpp @@ -30,7 +30,7 @@ #include "post_import_plugin_skeleton_rest_fixer.h" -#include "editor/import/scene_import_settings.h" +#include "editor/import/3d/scene_import_settings.h" #include "scene/3d/bone_attachment_3d.h" #include "scene/3d/importer_mesh_instance_3d.h" #include "scene/3d/skeleton_3d.h" diff --git a/editor/import/post_import_plugin_skeleton_rest_fixer.h b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.h index c765169fd0..c765169fd0 100644 --- a/editor/import/post_import_plugin_skeleton_rest_fixer.h +++ b/editor/import/3d/post_import_plugin_skeleton_rest_fixer.h diff --git a/editor/import/post_import_plugin_skeleton_track_organizer.cpp b/editor/import/3d/post_import_plugin_skeleton_track_organizer.cpp index e5a8e879fc..53bcc59fcb 100644 --- a/editor/import/post_import_plugin_skeleton_track_organizer.cpp +++ b/editor/import/3d/post_import_plugin_skeleton_track_organizer.cpp @@ -30,7 +30,7 @@ #include "post_import_plugin_skeleton_track_organizer.h" -#include "editor/import/scene_import_settings.h" +#include "editor/import/3d/scene_import_settings.h" #include "scene/3d/skeleton_3d.h" #include "scene/animation/animation_player.h" #include "scene/resources/bone_map.h" diff --git a/editor/import/post_import_plugin_skeleton_track_organizer.h b/editor/import/3d/post_import_plugin_skeleton_track_organizer.h index 24a4f70aa9..24a4f70aa9 100644 --- a/editor/import/post_import_plugin_skeleton_track_organizer.h +++ b/editor/import/3d/post_import_plugin_skeleton_track_organizer.h diff --git a/editor/import/resource_importer_obj.cpp b/editor/import/3d/resource_importer_obj.cpp index 082e78fdbe..082e78fdbe 100644 --- a/editor/import/resource_importer_obj.cpp +++ b/editor/import/3d/resource_importer_obj.cpp diff --git a/editor/import/resource_importer_obj.h b/editor/import/3d/resource_importer_obj.h index faf0f336c0..faf0f336c0 100644 --- a/editor/import/resource_importer_obj.h +++ b/editor/import/3d/resource_importer_obj.h diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp index 80dc3f194c..dfb14c6741 100644 --- a/editor/import/resource_importer_scene.cpp +++ b/editor/import/3d/resource_importer_scene.cpp @@ -35,7 +35,7 @@ #include "core/object/script_language.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" -#include "editor/import/scene_import_settings.h" +#include "editor/import/3d/scene_import_settings.h" #include "scene/3d/area_3d.h" #include "scene/3d/collision_shape_3d.h" #include "scene/3d/importer_mesh_instance_3d.h" @@ -2377,9 +2377,8 @@ Node *ResourceImporterScene::pre_import(const String &p_source_file, const HashM ERR_FAIL_COND_V(!importer.is_valid(), nullptr); Error err = OK; - HashMap<StringName, Variant> options_dupe = p_options; - Node *scene = importer->import_scene(p_source_file, EditorSceneFormatImporter::IMPORT_ANIMATION | EditorSceneFormatImporter::IMPORT_GENERATE_TANGENT_ARRAYS | EditorSceneFormatImporter::IMPORT_FORCE_DISABLE_MESH_COMPRESSION, options_dupe, nullptr, &err); + Node *scene = importer->import_scene(p_source_file, EditorSceneFormatImporter::IMPORT_ANIMATION | EditorSceneFormatImporter::IMPORT_GENERATE_TANGENT_ARRAYS | EditorSceneFormatImporter::IMPORT_FORCE_DISABLE_MESH_COMPRESSION, p_options, nullptr, &err); if (!scene || err != OK) { return nullptr; } diff --git a/editor/import/resource_importer_scene.h b/editor/import/3d/resource_importer_scene.h index 6ea4d1af7d..6ea4d1af7d 100644 --- a/editor/import/resource_importer_scene.h +++ b/editor/import/3d/resource_importer_scene.h diff --git a/editor/import/scene_import_settings.cpp b/editor/import/3d/scene_import_settings.cpp index 4ecc6dedbd..4ecc6dedbd 100644 --- a/editor/import/scene_import_settings.cpp +++ b/editor/import/3d/scene_import_settings.cpp diff --git a/editor/import/scene_import_settings.h b/editor/import/3d/scene_import_settings.h index 3e71d5da17..c6c416daba 100644 --- a/editor/import/scene_import_settings.h +++ b/editor/import/3d/scene_import_settings.h @@ -31,7 +31,7 @@ #ifndef SCENE_IMPORT_SETTINGS_H #define SCENE_IMPORT_SETTINGS_H -#include "editor/import/resource_importer_scene.h" +#include "editor/import/3d/resource_importer_scene.h" #include "scene/3d/camera_3d.h" #include "scene/3d/light_3d.h" #include "scene/3d/mesh_instance_3d.h" diff --git a/editor/import/SCsub b/editor/import/SCsub index 359d04e5df..a8c06cc406 100644 --- a/editor/import/SCsub +++ b/editor/import/SCsub @@ -3,3 +3,4 @@ Import("env") env.add_source_files(env.editor_sources, "*.cpp") +env.add_source_files(env.editor_sources, "3d/*.cpp") diff --git a/editor/localization_editor.cpp b/editor/localization_editor.cpp index fb34740318..71906b58e7 100644 --- a/editor/localization_editor.cpp +++ b/editor/localization_editor.cpp @@ -545,7 +545,7 @@ void LocalizationEditor::update_translations() { PackedStringArray selected = remaps[keys[i]]; for (int j = 0; j < selected.size(); j++) { - String s2 = selected[j]; + const String &s2 = selected[j]; int qp = s2.rfind(":"); String path = s2.substr(0, qp); String locale = s2.substr(qp + 1, s2.length()); diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp index 4a17fd89ff..5011c5cf18 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.cpp +++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp @@ -623,6 +623,111 @@ void AnimationNodeBlendTreeEditor::_filter_edited() { updating = false; } +void AnimationNodeBlendTreeEditor::_filter_fill_selection() { + TreeItem *ti = filters->get_root(); + if (!ti) { + return; + } + + updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Fill Selected Filter Children")); + + _filter_fill_selection_recursive(undo_redo, ti, false); + + undo_redo->add_do_method(this, "_update_filters", _filter_edit); + undo_redo->add_undo_method(this, "_update_filters", _filter_edit); + undo_redo->commit_action(); + updating = false; + + _update_filters(_filter_edit); +} + +void AnimationNodeBlendTreeEditor::_filter_invert_selection() { + TreeItem *ti = filters->get_root(); + if (!ti) { + return; + } + + updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Invert Filter Selection")); + + _filter_invert_selection_recursive(undo_redo, ti); + + undo_redo->add_do_method(this, "_update_filters", _filter_edit); + undo_redo->add_undo_method(this, "_update_filters", _filter_edit); + undo_redo->commit_action(); + updating = false; + + _update_filters(_filter_edit); +} + +void AnimationNodeBlendTreeEditor::_filter_clear_selection() { + TreeItem *ti = filters->get_root(); + if (!ti) { + return; + } + + updating = true; + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Clear Filter Selection")); + + _filter_clear_selection_recursive(undo_redo, ti); + + undo_redo->add_do_method(this, "_update_filters", _filter_edit); + undo_redo->add_undo_method(this, "_update_filters", _filter_edit); + undo_redo->commit_action(); + updating = false; + + _update_filters(_filter_edit); +} + +void AnimationNodeBlendTreeEditor::_filter_fill_selection_recursive(EditorUndoRedoManager *p_undo_redo, TreeItem *p_item, bool p_parent_filtered) { + TreeItem *ti = p_item->get_first_child(); + bool parent_filtered = p_parent_filtered; + while (ti) { + NodePath item_path = ti->get_metadata(0); + bool filtered = _filter_edit->is_path_filtered(item_path); + parent_filtered |= filtered; + + p_undo_redo->add_do_method(_filter_edit.ptr(), "set_filter_path", item_path, parent_filtered); + p_undo_redo->add_undo_method(_filter_edit.ptr(), "set_filter_path", item_path, filtered); + + _filter_fill_selection_recursive(p_undo_redo, ti, parent_filtered); + ti = ti->get_next(); + parent_filtered = p_parent_filtered; + } +} + +void AnimationNodeBlendTreeEditor::_filter_invert_selection_recursive(EditorUndoRedoManager *p_undo_redo, TreeItem *p_item) { + TreeItem *ti = p_item->get_first_child(); + while (ti) { + NodePath item_path = ti->get_metadata(0); + bool filtered = _filter_edit->is_path_filtered(item_path); + + p_undo_redo->add_do_method(_filter_edit.ptr(), "set_filter_path", item_path, !filtered); + p_undo_redo->add_undo_method(_filter_edit.ptr(), "set_filter_path", item_path, filtered); + + _filter_invert_selection_recursive(p_undo_redo, ti); + ti = ti->get_next(); + } +} + +void AnimationNodeBlendTreeEditor::_filter_clear_selection_recursive(EditorUndoRedoManager *p_undo_redo, TreeItem *p_item) { + TreeItem *ti = p_item->get_first_child(); + while (ti) { + NodePath item_path = ti->get_metadata(0); + bool filtered = _filter_edit->is_path_filtered(item_path); + + p_undo_redo->add_do_method(_filter_edit.ptr(), "set_filter_path", item_path, false); + p_undo_redo->add_undo_method(_filter_edit.ptr(), "set_filter_path", item_path, filtered); + + _filter_clear_selection_recursive(p_undo_redo, ti); + ti = ti->get_next(); + } +} + bool AnimationNodeBlendTreeEditor::_update_filters(const Ref<AnimationNode> &anode) { if (updating || _filter_edit != anode) { return false; @@ -1121,10 +1226,28 @@ AnimationNodeBlendTreeEditor::AnimationNodeBlendTreeEditor() { VBoxContainer *filter_vbox = memnew(VBoxContainer); filter_dialog->add_child(filter_vbox); + HBoxContainer *filter_hbox = memnew(HBoxContainer); + filter_vbox->add_child(filter_hbox); + filter_enabled = memnew(CheckBox); filter_enabled->set_text(TTR("Enable Filtering")); filter_enabled->connect("pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_filter_toggled)); - filter_vbox->add_child(filter_enabled); + filter_hbox->add_child(filter_enabled); + + filter_fill_selection = memnew(Button); + filter_fill_selection->set_text(TTR("Fill Selected Children")); + filter_fill_selection->connect("pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_filter_fill_selection)); + filter_hbox->add_child(filter_fill_selection); + + filter_invert_selection = memnew(Button); + filter_invert_selection->set_text(TTR("Invert")); + filter_invert_selection->connect("pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_filter_invert_selection)); + filter_hbox->add_child(filter_invert_selection); + + filter_clear_selection = memnew(Button); + filter_clear_selection->set_text(TTR("Clear")); + filter_clear_selection->connect("pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_filter_clear_selection)); + filter_hbox->add_child(filter_clear_selection); filters = memnew(Tree); filter_vbox->add_child(filters); diff --git a/editor/plugins/animation_blend_tree_editor_plugin.h b/editor/plugins/animation_blend_tree_editor_plugin.h index 1f7a33e02a..690b127938 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.h +++ b/editor/plugins/animation_blend_tree_editor_plugin.h @@ -66,6 +66,9 @@ class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin { AcceptDialog *filter_dialog = nullptr; Tree *filters = nullptr; CheckBox *filter_enabled = nullptr; + Button *filter_fill_selection = nullptr; + Button *filter_invert_selection = nullptr; + Button *filter_clear_selection = nullptr; HashMap<StringName, ProgressBar *> animations; Vector<EditorProperty *> visible_properties; @@ -116,6 +119,12 @@ class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin { void _inspect_filters(const String &p_which); void _filter_edited(); void _filter_toggled(); + void _filter_fill_selection(); + void _filter_invert_selection(); + void _filter_clear_selection(); + void _filter_fill_selection_recursive(EditorUndoRedoManager *p_undo_redo, TreeItem *p_item, bool p_parent_filtered); + void _filter_invert_selection_recursive(EditorUndoRedoManager *p_undo_redo, TreeItem *p_item); + void _filter_clear_selection_recursive(EditorUndoRedoManager *p_undo_redo, TreeItem *p_item); Ref<AnimationNode> _filter_edit; void _popup(bool p_has_input_ports, const Vector2 &p_node_position); diff --git a/editor/plugins/animation_library_editor.cpp b/editor/plugins/animation_library_editor.cpp index e3b2154e37..642dfa58df 100644 --- a/editor/plugins/animation_library_editor.cpp +++ b/editor/plugins/animation_library_editor.cpp @@ -137,7 +137,7 @@ void AnimationLibraryEditor::_load_library() { file_dialog->add_filter("*." + K); } - file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); + file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES); file_dialog->set_current_file(""); file_dialog->popup_centered_ratio(); @@ -299,85 +299,9 @@ void AnimationLibraryEditor::_file_popup_selected(int p_id) { } break; } } + void AnimationLibraryEditor::_load_file(String p_path) { switch (file_dialog_action) { - case FILE_DIALOG_ACTION_OPEN_LIBRARY: { - Ref<AnimationLibrary> al = ResourceLoader::load(p_path); - if (al.is_null()) { - error_dialog->set_text(TTR("Invalid AnimationLibrary file.")); - error_dialog->popup_centered(); - return; - } - - List<StringName> libs; - mixer->get_animation_library_list(&libs); - for (const StringName &K : libs) { - Ref<AnimationLibrary> al2 = mixer->get_animation_library(K); - if (al2 == al) { - error_dialog->set_text(TTR("This library is already added to the mixer.")); - error_dialog->popup_centered(); - - return; - } - } - - String name = AnimationLibrary::validate_library_name(p_path.get_file().get_basename()); - - int attempt = 1; - - while (bool(mixer->has_animation_library(name))) { - attempt++; - name = p_path.get_file().get_basename() + " " + itos(attempt); - } - - EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - - undo_redo->create_action(vformat(TTR("Add Animation Library: %s"), name)); - undo_redo->add_do_method(mixer, "add_animation_library", name, al); - undo_redo->add_undo_method(mixer, "remove_animation_library", name); - undo_redo->add_do_method(this, "_update_editor", mixer); - undo_redo->add_undo_method(this, "_update_editor", mixer); - undo_redo->commit_action(); - } break; - case FILE_DIALOG_ACTION_OPEN_ANIMATION: { - Ref<Animation> anim = ResourceLoader::load(p_path); - if (anim.is_null()) { - error_dialog->set_text(TTR("Invalid Animation file.")); - error_dialog->popup_centered(); - return; - } - - Ref<AnimationLibrary> al = mixer->get_animation_library(adding_animation_to_library); - List<StringName> anims; - al->get_animation_list(&anims); - for (const StringName &K : anims) { - Ref<Animation> a2 = al->get_animation(K); - if (a2 == anim) { - error_dialog->set_text(TTR("This animation is already added to the library.")); - error_dialog->popup_centered(); - return; - } - } - - String name = p_path.get_file().get_basename(); - - int attempt = 1; - - while (al->has_animation(name)) { - attempt++; - name = p_path.get_file().get_basename() + " " + itos(attempt); - } - - EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - - undo_redo->create_action(vformat(TTR("Load Animation into Library: %s"), name)); - undo_redo->add_do_method(al.ptr(), "add_animation", name, anim); - undo_redo->add_undo_method(al.ptr(), "remove_animation", name); - undo_redo->add_do_method(this, "_update_editor", mixer); - undo_redo->add_undo_method(this, "_update_editor", mixer); - undo_redo->commit_action(); - } break; - case FILE_DIALOG_ACTION_SAVE_LIBRARY: { Ref<AnimationLibrary> al = mixer->get_animation_library(file_dialog_library); String prev_path = al->get_path(); @@ -415,6 +339,121 @@ void AnimationLibraryEditor::_load_file(String p_path) { undo_redo->commit_action(); } } break; + default: { + } + } +} + +void AnimationLibraryEditor::_load_files(const PackedStringArray &p_paths) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + bool has_created_action = false; + bool show_error_diag = false; + List<String> name_list; + + switch (file_dialog_action) { + case FILE_DIALOG_ACTION_OPEN_LIBRARY: { + for (const String &path : p_paths) { + Ref<AnimationLibrary> al = ResourceLoader::load(path); + if (al.is_null()) { + show_error_diag = true; + error_dialog->set_text(TTR("Some AnimationLibrary files were invalid.")); + continue; + } + + List<StringName> libs; + mixer->get_animation_library_list(&libs); + bool is_already_added = false; + for (const StringName &K : libs) { + if (mixer->get_animation_library(K) == al) { + // Prioritize the "invalid" error message. + if (!show_error_diag) { + show_error_diag = true; + error_dialog->set_text(TTR("Some of the selected libraries were already added to the mixer.")); + } + + is_already_added = true; + break; + } + } + + if (is_already_added) { + continue; + } + + String name = AnimationLibrary::validate_library_name(path.get_file().get_basename()); + int attempt = 1; + while (bool(mixer->has_animation_library(name)) || name_list.find(name)) { + attempt++; + name = path.get_file().get_basename() + " " + itos(attempt); + } + name_list.push_back(name); + + if (!has_created_action) { + has_created_action = true; + undo_redo->create_action(p_paths.size() > 1 ? TTR("Add Animation Libraries") : vformat(TTR("Add Animation Library: %s"), name)); + } + undo_redo->add_do_method(mixer, "add_animation_library", name, al); + undo_redo->add_undo_method(mixer, "remove_animation_library", name); + } + } break; + case FILE_DIALOG_ACTION_OPEN_ANIMATION: { + Ref<AnimationLibrary> al = mixer->get_animation_library(adding_animation_to_library); + for (const String &path : p_paths) { + Ref<Animation> anim = ResourceLoader::load(path); + if (anim.is_null()) { + show_error_diag = true; + error_dialog->set_text(TTR("Some Animation files were invalid.")); + continue; + } + + List<StringName> anims; + al->get_animation_list(&anims); + bool is_already_added = false; + for (const StringName &K : anims) { + if (al->get_animation(K) == anim) { + // Prioritize the "invalid" error message. + if (!show_error_diag) { + show_error_diag = true; + error_dialog->set_text(TTR("Some of the selected animations were already added to the library.")); + } + + is_already_added = true; + break; + } + } + + if (is_already_added) { + continue; + } + + String name = path.get_file().get_basename(); + int attempt = 1; + while (al->has_animation(name) || name_list.find(name)) { + attempt++; + name = path.get_file().get_basename() + " " + itos(attempt); + } + name_list.push_back(name); + + if (!has_created_action) { + has_created_action = true; + undo_redo->create_action(p_paths.size() > 1 ? TTR("Load Animations into Library") : vformat(TTR("Load Animation into Library: %s"), name)); + } + undo_redo->add_do_method(al.ptr(), "add_animation", name, anim); + undo_redo->add_undo_method(al.ptr(), "remove_animation", name); + } + } break; + default: { + } + } + + if (has_created_action) { + undo_redo->add_do_method(this, "_update_editor", mixer); + undo_redo->add_undo_method(this, "_update_editor", mixer); + undo_redo->commit_action(); + } + + if (show_error_diag) { + error_dialog->popup_centered(); } } @@ -506,7 +545,7 @@ void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int } file_dialog->set_title(TTR("Load Animation")); - file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); + file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES); file_dialog->set_current_file(""); file_dialog->popup_centered_ratio(); @@ -673,12 +712,12 @@ void AnimationLibraryEditor::update_tree() { libitem->set_metadata(0, K); libitem->set_icon(0, get_editor_theme_icon("AnimationLibrary")); - libitem->add_button(0, get_editor_theme_icon("Add"), LIB_BUTTON_ADD, animation_library_is_foreign, TTR("Add Animation to Library")); - libitem->add_button(0, get_editor_theme_icon("Load"), LIB_BUTTON_LOAD, animation_library_is_foreign, TTR("Load animation from file and add to library")); - libitem->add_button(0, get_editor_theme_icon("ActionPaste"), LIB_BUTTON_PASTE, animation_library_is_foreign, TTR("Paste Animation to Library from clipboard")); + libitem->add_button(0, get_editor_theme_icon("Add"), LIB_BUTTON_ADD, animation_library_is_foreign, TTR("Add animation to library.")); + libitem->add_button(0, get_editor_theme_icon("Load"), LIB_BUTTON_LOAD, animation_library_is_foreign, TTR("Load animation from file and add to library.")); + libitem->add_button(0, get_editor_theme_icon("ActionPaste"), LIB_BUTTON_PASTE, animation_library_is_foreign, TTR("Paste animation to library from clipboard.")); - libitem->add_button(1, get_editor_theme_icon("Save"), LIB_BUTTON_FILE, false, TTR("Save animation library to resource on disk")); - libitem->add_button(1, get_editor_theme_icon("Remove"), LIB_BUTTON_DELETE, false, TTR("Remove animation library")); + libitem->add_button(1, get_editor_theme_icon("Save"), LIB_BUTTON_FILE, false, TTR("Save animation library to resource on disk.")); + libitem->add_button(1, get_editor_theme_icon("Remove"), LIB_BUTTON_DELETE, false, TTR("Remove animation library.")); libitem->set_custom_bg_color(0, ss_color); @@ -690,7 +729,7 @@ void AnimationLibraryEditor::update_tree() { anitem->set_editable(0, !animation_library_is_foreign); anitem->set_metadata(0, L); anitem->set_icon(0, get_editor_theme_icon("Animation")); - anitem->add_button(0, get_editor_theme_icon("ActionCopy"), ANIM_BUTTON_COPY, animation_library_is_foreign, TTR("Copy animation to clipboard")); + anitem->add_button(0, get_editor_theme_icon("ActionCopy"), ANIM_BUTTON_COPY, animation_library_is_foreign, TTR("Copy animation to clipboard.")); Ref<Animation> anim = al->get_animation(L); String anim_path = anim->get_path(); @@ -717,8 +756,8 @@ void AnimationLibraryEditor::update_tree() { anitem->set_text(1, anim_path.get_file()); } } - anitem->add_button(1, get_editor_theme_icon("Save"), ANIM_BUTTON_FILE, animation_library_is_foreign, TTR("Save animation to resource on disk")); - anitem->add_button(1, get_editor_theme_icon("Remove"), ANIM_BUTTON_DELETE, animation_library_is_foreign, TTR("Remove animation from Library")); + anitem->add_button(1, get_editor_theme_icon("Save"), ANIM_BUTTON_FILE, animation_library_is_foreign, TTR("Save animation to resource on disk.")); + anitem->add_button(1, get_editor_theme_icon("Remove"), ANIM_BUTTON_DELETE, animation_library_is_foreign, TTR("Remove animation from Library.")); } } } @@ -743,6 +782,7 @@ AnimationLibraryEditor::AnimationLibraryEditor() { file_dialog = memnew(EditorFileDialog); add_child(file_dialog); file_dialog->connect("file_selected", callable_mp(this, &AnimationLibraryEditor::_load_file)); + file_dialog->connect("files_selected", callable_mp(this, &AnimationLibraryEditor::_load_files)); add_library_dialog = memnew(ConfirmationDialog); VBoxContainer *dialog_vb = memnew(VBoxContainer); diff --git a/editor/plugins/animation_library_editor.h b/editor/plugins/animation_library_editor.h index b656642a7c..42b4d38e49 100644 --- a/editor/plugins/animation_library_editor.h +++ b/editor/plugins/animation_library_editor.h @@ -98,6 +98,7 @@ class AnimationLibraryEditor : public AcceptDialog { void _add_library_confirm(); void _load_library(); void _load_file(String p_path); + void _load_files(const PackedStringArray &p_paths); void _item_renamed(); void _button_pressed(TreeItem *p_item, int p_column, int p_id, MouseButton p_button); diff --git a/editor/plugins/bone_map_editor_plugin.cpp b/editor/plugins/bone_map_editor_plugin.cpp index db61c95474..153f192838 100644 --- a/editor/plugins/bone_map_editor_plugin.cpp +++ b/editor/plugins/bone_map_editor_plugin.cpp @@ -32,10 +32,10 @@ #include "editor/editor_scale.h" #include "editor/editor_settings.h" -#include "editor/import/post_import_plugin_skeleton_renamer.h" -#include "editor/import/post_import_plugin_skeleton_rest_fixer.h" -#include "editor/import/post_import_plugin_skeleton_track_organizer.h" -#include "editor/import/scene_import_settings.h" +#include "editor/import/3d/post_import_plugin_skeleton_renamer.h" +#include "editor/import/3d/post_import_plugin_skeleton_rest_fixer.h" +#include "editor/import/3d/post_import_plugin_skeleton_track_organizer.h" +#include "editor/import/3d/scene_import_settings.h" #include "scene/gui/aspect_ratio_container.h" #include "scene/gui/separator.h" #include "scene/gui/texture_rect.h" @@ -95,7 +95,7 @@ void BoneMapperButton::_notification(int p_what) { } } -BoneMapperButton::BoneMapperButton(const StringName p_profile_bone_name, bool p_require, bool p_selected) { +BoneMapperButton::BoneMapperButton(const StringName &p_profile_bone_name, bool p_require, bool p_selected) { profile_bone_name = p_profile_bone_name; require = p_require; selected = p_selected; diff --git a/editor/plugins/bone_map_editor_plugin.h b/editor/plugins/bone_map_editor_plugin.h index 9ff32707c7..9479ed3730 100644 --- a/editor/plugins/bone_map_editor_plugin.h +++ b/editor/plugins/bone_map_editor_plugin.h @@ -78,7 +78,7 @@ public: bool is_require() const; - BoneMapperButton(const StringName p_profile_bone_name, bool p_require, bool p_selected); + BoneMapperButton(const StringName &p_profile_bone_name, bool p_require, bool p_selected); ~BoneMapperButton(); }; diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 3676d1165a..8dbf67a37a 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -345,7 +345,7 @@ void CanvasItemEditor::_snap_other_nodes( } } -Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, unsigned int p_forced_modes, const CanvasItem *p_self_canvas_item, List<CanvasItem *> p_other_nodes_exceptions) { +Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, unsigned int p_forced_modes, const CanvasItem *p_self_canvas_item, const List<CanvasItem *> &p_other_nodes_exceptions) { snap_target[0] = SNAP_TARGET_NONE; snap_target[1] = SNAP_TARGET_NONE; @@ -535,7 +535,7 @@ void CanvasItemEditor::_keying_changed() { } } -Rect2 CanvasItemEditor::_get_encompassing_rect_from_list(List<CanvasItem *> p_list) { +Rect2 CanvasItemEditor::_get_encompassing_rect_from_list(const List<CanvasItem *> &p_list) { ERR_FAIL_COND_V(p_list.is_empty(), Rect2()); // Handles the first element @@ -830,7 +830,7 @@ Vector2 CanvasItemEditor::_position_to_anchor(const Control *p_control, Vector2 return output; } -void CanvasItemEditor::_save_canvas_item_state(List<CanvasItem *> p_canvas_items, bool save_bones) { +void CanvasItemEditor::_save_canvas_item_state(const List<CanvasItem *> &p_canvas_items, bool save_bones) { original_transform = Transform2D(); bool transform_stored = false; @@ -853,14 +853,14 @@ void CanvasItemEditor::_save_canvas_item_state(List<CanvasItem *> p_canvas_items } } -void CanvasItemEditor::_restore_canvas_item_state(List<CanvasItem *> p_canvas_items, bool restore_bones) { +void CanvasItemEditor::_restore_canvas_item_state(const List<CanvasItem *> &p_canvas_items, bool restore_bones) { for (CanvasItem *ci : drag_selection) { CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci); ci->_edit_set_state(se->undo_state); } } -void CanvasItemEditor::_commit_canvas_item_state(List<CanvasItem *> p_canvas_items, String action_name, bool commit_bones) { +void CanvasItemEditor::_commit_canvas_item_state(const List<CanvasItem *> &p_canvas_items, String action_name, bool commit_bones) { List<CanvasItem *> modified_canvas_items; for (CanvasItem *ci : p_canvas_items) { Dictionary old_state = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci)->undo_state; @@ -3638,7 +3638,7 @@ void CanvasItemEditor::_draw_invisible_nodes_positions(Node *p_node, const Trans _draw_invisible_nodes_positions(p_node->get_child(i), parent_xform, canvas_xform); } - if (ci && !ci->_edit_use_rect() && (!editor_selection->is_selected(ci) || _is_node_locked(ci))) { + if (show_position_gizmos && ci && !ci->_edit_use_rect() && (!editor_selection->is_selected(ci) || _is_node_locked(ci))) { Transform2D xform = transform * canvas_xform * parent_xform; // Draw the node's position @@ -3775,13 +3775,13 @@ void CanvasItemEditor::_draw_locks_and_groups(Node *p_node, const Transform2D &p real_t offset = 0; Ref<Texture2D> lock = get_editor_theme_icon(SNAME("LockViewport")); - if (p_node->has_meta("_edit_lock_") && show_edit_locks) { + if (show_lock_gizmos && p_node->has_meta("_edit_lock_")) { lock->draw(viewport_ci, (transform * canvas_xform * parent_xform).xform(Point2(0, 0)) + Point2(offset, 0)); offset += lock->get_size().x; } Ref<Texture2D> group = get_editor_theme_icon(SNAME("GroupViewport")); - if (ci->has_meta("_edit_group_") && show_edit_locks) { + if (show_group_gizmos && ci->has_meta("_edit_group_")) { group->draw(viewport_ci, (transform * canvas_xform * parent_xform).xform(Point2(0, 0)) + Point2(offset, 0)); //offset += group->get_size().x; } @@ -4289,16 +4289,28 @@ void CanvasItemEditor::_popup_callback(int p_op) { view_menu->get_popup()->set_item_checked(idx, show_viewport); viewport->queue_redraw(); } break; - case SHOW_EDIT_LOCKS: { - show_edit_locks = !show_edit_locks; - int idx = view_menu->get_popup()->get_item_index(SHOW_EDIT_LOCKS); - view_menu->get_popup()->set_item_checked(idx, show_edit_locks); + case SHOW_POSITION_GIZMOS: { + show_position_gizmos = !show_position_gizmos; + int idx = gizmos_menu->get_item_index(SHOW_POSITION_GIZMOS); + gizmos_menu->set_item_checked(idx, show_position_gizmos); + viewport->queue_redraw(); + } break; + case SHOW_LOCK_GIZMOS: { + show_lock_gizmos = !show_lock_gizmos; + int idx = gizmos_menu->get_item_index(SHOW_LOCK_GIZMOS); + gizmos_menu->set_item_checked(idx, show_lock_gizmos); + viewport->queue_redraw(); + } break; + case SHOW_GROUP_GIZMOS: { + show_group_gizmos = !show_group_gizmos; + int idx = gizmos_menu->get_item_index(SHOW_GROUP_GIZMOS); + gizmos_menu->set_item_checked(idx, show_group_gizmos); viewport->queue_redraw(); } break; case SHOW_TRANSFORMATION_GIZMOS: { show_transformation_gizmos = !show_transformation_gizmos; - int idx = view_menu->get_popup()->get_item_index(SHOW_TRANSFORMATION_GIZMOS); - view_menu->get_popup()->set_item_checked(idx, show_transformation_gizmos); + int idx = gizmos_menu->get_item_index(SHOW_TRANSFORMATION_GIZMOS); + gizmos_menu->set_item_checked(idx, show_transformation_gizmos); viewport->queue_redraw(); } break; case SNAP_USE_NODE_PARENT: { @@ -4759,7 +4771,9 @@ Dictionary CanvasItemEditor::get_state() const { state["show_guides"] = show_guides; state["show_helpers"] = show_helpers; state["show_zoom_control"] = zoom_widget->is_visible(); - state["show_edit_locks"] = show_edit_locks; + state["show_position_gizmos"] = show_position_gizmos; + state["show_lock_gizmos"] = show_lock_gizmos; + state["show_group_gizmos"] = show_group_gizmos; state["show_transformation_gizmos"] = show_transformation_gizmos; state["snap_rotation"] = snap_rotation; state["snap_scale"] = snap_scale; @@ -4896,16 +4910,28 @@ void CanvasItemEditor::set_state(const Dictionary &p_state) { view_menu->get_popup()->set_item_checked(idx, show_helpers); } - if (state.has("show_edit_locks")) { - show_edit_locks = state["show_edit_locks"]; - int idx = view_menu->get_popup()->get_item_index(SHOW_EDIT_LOCKS); - view_menu->get_popup()->set_item_checked(idx, show_edit_locks); + if (state.has("show_position_gizmos")) { + show_position_gizmos = state["show_position_gizmos"]; + int idx = gizmos_menu->get_item_index(SHOW_POSITION_GIZMOS); + gizmos_menu->set_item_checked(idx, show_position_gizmos); + } + + if (state.has("show_lock_gizmos")) { + show_lock_gizmos = state["show_lock_gizmos"]; + int idx = gizmos_menu->get_item_index(SHOW_LOCK_GIZMOS); + gizmos_menu->set_item_checked(idx, show_lock_gizmos); + } + + if (state.has("show_group_gizmos")) { + show_group_gizmos = state["show_group_gizmos"]; + int idx = gizmos_menu->get_item_index(SHOW_GROUP_GIZMOS); + gizmos_menu->set_item_checked(idx, show_group_gizmos); } if (state.has("show_transformation_gizmos")) { show_transformation_gizmos = state["show_transformation_gizmos"]; - int idx = view_menu->get_popup()->get_item_index(SHOW_TRANSFORMATION_GIZMOS); - view_menu->get_popup()->set_item_checked(idx, show_transformation_gizmos); + int idx = gizmos_menu->get_item_index(SHOW_TRANSFORMATION_GIZMOS); + gizmos_menu->set_item_checked(idx, show_transformation_gizmos); } if (state.has("show_zoom_control")) { @@ -5394,8 +5420,18 @@ CanvasItemEditor::CanvasItemEditor() { p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_guides", TTR("Show Guides"), Key::Y), SHOW_GUIDES); p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_origin", TTR("Show Origin")), SHOW_ORIGIN); p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_viewport", TTR("Show Viewport")), SHOW_VIEWPORT); - p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_edit_locks", TTR("Show Group And Lock Icons")), SHOW_EDIT_LOCKS); - p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_transformation_gizmos", TTR("Show Transformation Gizmos")), SHOW_TRANSFORMATION_GIZMOS); + p->add_separator(); + + gizmos_menu = memnew(PopupMenu); + gizmos_menu->set_name("GizmosMenu"); + gizmos_menu->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_popup_callback)); + gizmos_menu->set_hide_on_checkable_item_selection(false); + gizmos_menu->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_position_gizmos", TTR("Position")), SHOW_POSITION_GIZMOS); + gizmos_menu->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_lock_gizmos", TTR("Lock")), SHOW_LOCK_GIZMOS); + gizmos_menu->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_group_gizmos", TTR("Group")), SHOW_GROUP_GIZMOS); + gizmos_menu->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_transformation_gizmos", TTR("Transformation")), SHOW_TRANSFORMATION_GIZMOS); + p->add_child(gizmos_menu); + p->add_submenu_item(TTR("Gizmos"), "GizmosMenu"); p->add_separator(); p->add_shortcut(ED_SHORTCUT("canvas_item_editor/center_selection", TTR("Center Selection"), Key::F), VIEW_CENTER_TO_SELECTION); diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h index 9aac6e44df..7fe63e6282 100644 --- a/editor/plugins/canvas_item_editor_plugin.h +++ b/editor/plugins/canvas_item_editor_plugin.h @@ -126,7 +126,9 @@ private: SHOW_GUIDES, SHOW_ORIGIN, SHOW_VIEWPORT, - SHOW_EDIT_LOCKS, + SHOW_POSITION_GIZMOS, + SHOW_LOCK_GIZMOS, + SHOW_GROUP_GIZMOS, SHOW_TRANSFORMATION_GIZMOS, LOCK_SELECTED, UNLOCK_SELECTED, @@ -209,7 +211,9 @@ private: bool show_origin = true; bool show_viewport = true; bool show_helpers = false; - bool show_edit_locks = true; + bool show_position_gizmos = true; + bool show_lock_gizmos = true; + bool show_group_gizmos = true; bool show_transformation_gizmos = true; real_t zoom = 1.0; @@ -331,6 +335,7 @@ private: MenuButton *view_menu = nullptr; PopupMenu *grid_menu = nullptr; PopupMenu *theme_menu = nullptr; + PopupMenu *gizmos_menu = nullptr; HBoxContainer *animation_hb = nullptr; MenuButton *animation_menu = nullptr; @@ -388,9 +393,9 @@ private: CanvasItem *ref_item = nullptr; - void _save_canvas_item_state(List<CanvasItem *> p_canvas_items, bool save_bones = false); - void _restore_canvas_item_state(List<CanvasItem *> p_canvas_items, bool restore_bones = false); - void _commit_canvas_item_state(List<CanvasItem *> p_canvas_items, String action_name, bool commit_bones = false); + void _save_canvas_item_state(const List<CanvasItem *> &p_canvas_items, bool save_bones = false); + void _restore_canvas_item_state(const List<CanvasItem *> &p_canvas_items, bool restore_bones = false); + void _commit_canvas_item_state(const List<CanvasItem *> &p_canvas_items, String action_name, bool commit_bones = false); Vector2 _anchor_to_position(const Control *p_control, Vector2 anchor); Vector2 _position_to_anchor(const Control *p_control, Vector2 position); @@ -424,7 +429,7 @@ private: void _switch_theme_preview(int p_mode); List<CanvasItem *> _get_edited_canvas_items(bool retrieve_locked = false, bool remove_canvas_item_if_parent_in_selection = true) const; - Rect2 _get_encompassing_rect_from_list(List<CanvasItem *> p_list); + Rect2 _get_encompassing_rect_from_list(const List<CanvasItem *> &p_list); void _expand_encompassing_rect_using_children(Rect2 &r_rect, const Node *p_node, bool &r_first, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D(), bool include_locked_nodes = true); Rect2 _get_encompassing_rect(const Node *p_node); @@ -540,7 +545,7 @@ public: SNAP_DEFAULT = SNAP_GRID | SNAP_GUIDES | SNAP_PIXEL, }; - Point2 snap_point(Point2 p_target, unsigned int p_modes = SNAP_DEFAULT, unsigned int p_forced_modes = 0, const CanvasItem *p_self_canvas_item = nullptr, List<CanvasItem *> p_other_nodes_exceptions = List<CanvasItem *>()); + Point2 snap_point(Point2 p_target, unsigned int p_modes = SNAP_DEFAULT, unsigned int p_forced_modes = 0, const CanvasItem *p_self_canvas_item = nullptr, const List<CanvasItem *> &p_other_nodes_exceptions = List<CanvasItem *>()); real_t snap_angle(real_t p_target, real_t p_start = 0) const; Transform2D get_canvas_transform() const { return transform; } diff --git a/editor/plugins/control_editor_plugin.cpp b/editor/plugins/control_editor_plugin.cpp index a0539c401d..0688ed959e 100644 --- a/editor/plugins/control_editor_plugin.cpp +++ b/editor/plugins/control_editor_plugin.cpp @@ -189,7 +189,7 @@ void EditorPropertyAnchorsPreset::setup(const Vector<String> &p_options) { Vector<String> text_split = p_options[i].split(":"); int64_t current_val = text_split[1].to_int(); - String option_name = text_split[0]; + const String &option_name = text_split[0]; if (option_name.begins_with("Preset")) { String preset_name = option_name.trim_prefix("Preset"); String humanized_name = preset_name.capitalize(); diff --git a/editor/plugins/lightmap_gi_editor_plugin.cpp b/editor/plugins/lightmap_gi_editor_plugin.cpp index 11f60a166c..3b1b1e47cb 100644 --- a/editor/plugins/lightmap_gi_editor_plugin.cpp +++ b/editor/plugins/lightmap_gi_editor_plugin.cpp @@ -107,6 +107,9 @@ void LightmapGIEditorPlugin::_bake_select_file(const String &p_file) { case LightmapGI::BAKE_ERROR_TEXTURE_SIZE_TOO_SMALL: { EditorNode::get_singleton()->show_warning(TTR("Maximum texture size is too small for the lightmap images.")); } break; + case LightmapGI::BAKE_ERROR_LIGHTMAP_TOO_SMALL: { + EditorNode::get_singleton()->show_warning(TTR("Failed creating lightmap images. Make sure all meshes selected to bake have `lightmap_size_hint` value set high enough, and `texel_scale` value of LightmapGI is not too low.")); + } break; default: { } break; } diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index f42fe4e941..146fd54b6e 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -4247,17 +4247,16 @@ bool Node3DEditorViewport::_apply_preview_material(ObjectID p_target, const Poin return false; } - if (spatial_editor->get_preview_material() != mesh_instance->get_surface_override_material(closest_surface)) { - spatial_editor->set_preview_material_surface(closest_surface); - spatial_editor->set_preview_reset_material(mesh_instance->get_surface_override_material(closest_surface)); - mesh_instance->set_surface_override_material(closest_surface, spatial_editor->get_preview_material()); - } + spatial_editor->set_preview_material_surface(closest_surface); + spatial_editor->set_preview_reset_material(mesh_instance->get_surface_override_material(closest_surface)); + mesh_instance->set_surface_override_material(closest_surface, spatial_editor->get_preview_material()); return true; } GeometryInstance3D *geometry_instance = Object::cast_to<GeometryInstance3D>(target_inst); - if (geometry_instance && spatial_editor->get_preview_material() != geometry_instance->get_material_override()) { + if (geometry_instance) { + spatial_editor->set_preview_material_surface(-1); spatial_editor->set_preview_reset_material(geometry_instance->get_material_override()); geometry_instance->set_material_override(spatial_editor->get_preview_material()); return true; @@ -4277,7 +4276,6 @@ void Node3DEditorViewport::_reset_preview_material() const { GeometryInstance3D *geometry_instance = Object::cast_to<GeometryInstance3D>(last_target_inst); if (mesh_instance && spatial_editor->get_preview_material_surface() != -1) { mesh_instance->set_surface_override_material(spatial_editor->get_preview_material_surface(), spatial_editor->get_preview_reset_material()); - spatial_editor->set_preview_material_surface(-1); } else if (geometry_instance) { geometry_instance->set_material_override(spatial_editor->get_preview_reset_material()); } @@ -6489,25 +6487,91 @@ void Node3DEditor::_init_indicators() { origin_enabled = true; grid_enabled = true; - indicator_mat.instantiate(); - indicator_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); - indicator_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); - indicator_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); - indicator_mat->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); - indicator_mat->set_transparency(StandardMaterial3D::Transparency::TRANSPARENCY_ALPHA_DEPTH_PRE_PASS); + Ref<Shader> origin_shader = memnew(Shader); + origin_shader->set_code(R"( +// 3D editor origin line shader. + +shader_type spatial; +render_mode blend_mix,cull_disabled,unshaded, fog_disabled; + +void vertex() { + vec3 point_a = MODEL_MATRIX[3].xyz; + // Encoded in scale. + vec3 point_b = vec3(MODEL_MATRIX[0].x, MODEL_MATRIX[1].y, MODEL_MATRIX[2].z); + + // Points are already in world space, so no need for MODEL_MATRIX anymore. + vec4 clip_a = PROJECTION_MATRIX * (VIEW_MATRIX * vec4(point_a, 1.0)); + vec4 clip_b = PROJECTION_MATRIX * (VIEW_MATRIX * vec4(point_b, 1.0)); + + vec2 screen_a = VIEWPORT_SIZE * (0.5 * clip_a.xy / clip_a.w + 0.5); + vec2 screen_b = VIEWPORT_SIZE * (0.5 * clip_b.xy / clip_b.w + 0.5); + + vec2 x_basis = normalize(screen_b - screen_a); + vec2 y_basis = vec2(-x_basis.y, x_basis.x); + + float width = 3.0; + vec2 screen_point_a = screen_a + width * (VERTEX.x * x_basis + VERTEX.y * y_basis); + vec2 screen_point_b = screen_b + width * (VERTEX.x * x_basis + VERTEX.y * y_basis); + vec2 screen_point_final = mix(screen_point_a, screen_point_b, VERTEX.z); + + vec4 clip_final = mix(clip_a, clip_b, VERTEX.z); + + POSITION = vec4(clip_final.w * ((2.0 * screen_point_final) / VIEWPORT_SIZE - 1.0), clip_final.z, clip_final.w); + UV = VERTEX.yz * clip_final.w; + + if (!OUTPUT_IS_SRGB) { + COLOR.rgb = mix(pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb * (1.0 / 12.92), lessThan(COLOR.rgb, vec3(0.04045))); + } +} + +void fragment() { + // Multiply by 0.5 since UV is actually UV is [-1, 1]. + float line_width = fwidth(UV.x * 0.5); + float line_uv = abs(UV.x * 0.5); + float line = smoothstep(line_width * 1.0, line_width * 0.25, line_uv); + + ALBEDO = COLOR.rgb; + ALPHA *= COLOR.a * line; +} +)"); + + origin_mat.instantiate(); + origin_mat->set_shader(origin_shader); - Vector<Color> origin_colors; Vector<Vector3> origin_points; + origin_points.resize(6); + + origin_points.set(0, Vector3(0.0, -0.5, 0.0)); + origin_points.set(1, Vector3(0.0, -0.5, 1.0)); + origin_points.set(2, Vector3(0.0, 0.5, 1.0)); + + origin_points.set(3, Vector3(0.0, -0.5, 0.0)); + origin_points.set(4, Vector3(0.0, 0.5, 1.0)); + origin_points.set(5, Vector3(0.0, 0.5, 0.0)); + + Array d; + d.resize(RS::ARRAY_MAX); + d[RenderingServer::ARRAY_VERTEX] = origin_points; - const int count_of_elements = 3 * 6; - origin_colors.resize(count_of_elements); - origin_points.resize(count_of_elements); + origin_mesh = RenderingServer::get_singleton()->mesh_create(); - int x = 0; + RenderingServer::get_singleton()->mesh_add_surface_from_arrays(origin_mesh, RenderingServer::PRIMITIVE_TRIANGLES, d); + RenderingServer::get_singleton()->mesh_surface_set_material(origin_mesh, 0, origin_mat->get_rid()); + + origin_multimesh = RenderingServer::get_singleton()->multimesh_create(); + RenderingServer::get_singleton()->multimesh_set_mesh(origin_multimesh, origin_mesh); + RenderingServer::get_singleton()->multimesh_allocate_data(origin_multimesh, 12, RS::MultimeshTransformFormat::MULTIMESH_TRANSFORM_3D, true, false); + RenderingServer::get_singleton()->multimesh_set_visible_instances(origin_multimesh, -1); + + LocalVector<float> distances; + distances.resize(5); + distances[0] = -1000000.0; + distances[1] = -1000.0; + distances[2] = 0.0; + distances[3] = 1000.0; + distances[4] = 1000000.0; for (int i = 0; i < 3; i++) { - Vector3 axis; - axis[i] = 1; Color origin_color; switch (i) { case 0: @@ -6524,27 +6588,26 @@ void Node3DEditor::_init_indicators() { break; } - grid_enable[i] = false; - grid_visible[i] = false; + Vector3 axis; + axis[i] = 1; - origin_colors.set(x, origin_color); - origin_colors.set(x + 1, origin_color); - origin_colors.set(x + 2, origin_color); - origin_colors.set(x + 3, origin_color); - origin_colors.set(x + 4, origin_color); - origin_colors.set(x + 5, origin_color); - // To both allow having a large origin size and avoid jitter - // at small scales, we should segment the line into pieces. - // 3 pieces seems to do the trick, and let's use powers of 2. - origin_points.set(x, axis * 1048576); - origin_points.set(x + 1, axis * 1024); - origin_points.set(x + 2, axis * 1024); - origin_points.set(x + 3, axis * -1024); - origin_points.set(x + 4, axis * -1024); - origin_points.set(x + 5, axis * -1048576); - x += 6; + for (int j = 0; j < 4; j++) { + Transform3D t = Transform3D(); + t = t.scaled(axis * distances[j + 1]); + t = t.translated(axis * distances[j]); + RenderingServer::get_singleton()->multimesh_instance_set_transform(origin_multimesh, i * 4 + j, t); + RenderingServer::get_singleton()->multimesh_instance_set_color(origin_multimesh, i * 4 + j, origin_color); + } } + origin_instance = RenderingServer::get_singleton()->instance_create2(origin_multimesh, get_tree()->get_root()->get_world_3d()->get_scenario()); + RS::get_singleton()->instance_set_layer_mask(origin_instance, 1 << Node3DEditorViewport::GIZMO_GRID_LAYER); + RS::get_singleton()->instance_geometry_set_flag(origin_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(origin_instance, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); + RS::get_singleton()->instance_set_ignore_culling(origin_instance, true); + + RenderingServer::get_singleton()->instance_geometry_set_cast_shadows_setting(origin_instance, RS::SHADOW_CASTING_SETTING_OFF); + Ref<Shader> grid_shader = memnew(Shader); grid_shader->set_code(R"( // 3D editor grid shader. @@ -6593,22 +6656,6 @@ void fragment() { grid_visible[2] = grid_enable[2]; _init_grid(); - - origin = RenderingServer::get_singleton()->mesh_create(); - Array d; - d.resize(RS::ARRAY_MAX); - d[RenderingServer::ARRAY_VERTEX] = origin_points; - d[RenderingServer::ARRAY_COLOR] = origin_colors; - - RenderingServer::get_singleton()->mesh_add_surface_from_arrays(origin, RenderingServer::PRIMITIVE_LINES, d); - RenderingServer::get_singleton()->mesh_surface_set_material(origin, 0, indicator_mat->get_rid()); - - origin_instance = RenderingServer::get_singleton()->instance_create2(origin, get_tree()->get_root()->get_world_3d()->get_scenario()); - RS::get_singleton()->instance_set_layer_mask(origin_instance, 1 << Node3DEditorViewport::GIZMO_GRID_LAYER); - RS::get_singleton()->instance_geometry_set_flag(origin_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); - RS::get_singleton()->instance_geometry_set_flag(origin_instance, RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); - - RenderingServer::get_singleton()->instance_geometry_set_cast_shadows_setting(origin_instance, RS::SHADOW_CASTING_SETTING_OFF); } { @@ -7226,7 +7273,8 @@ void Node3DEditor::_init_grid() { void Node3DEditor::_finish_indicators() { RenderingServer::get_singleton()->free(origin_instance); - RenderingServer::get_singleton()->free(origin); + RenderingServer::get_singleton()->free(origin_multimesh); + RenderingServer::get_singleton()->free(origin_mesh); _finish_grid(); } diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index 1ce09a2bcb..0f6ea71571 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -597,7 +597,8 @@ private: ToolMode tool_mode; - RID origin; + RID origin_mesh; + RID origin_multimesh; RID origin_instance; bool origin_enabled = false; RID grid[3]; @@ -631,7 +632,7 @@ private: RID indicators_instance; RID cursor_mesh; RID cursor_instance; - Ref<StandardMaterial3D> indicator_mat; + Ref<ShaderMaterial> origin_mat; Ref<ShaderMaterial> grid_mat[3]; Ref<StandardMaterial3D> cursor_material; diff --git a/editor/plugins/path_2d_editor_plugin.cpp b/editor/plugins/path_2d_editor_plugin.cpp index 0d6086bb4d..6f44dfc755 100644 --- a/editor/plugins/path_2d_editor_plugin.cpp +++ b/editor/plugins/path_2d_editor_plugin.cpp @@ -37,6 +37,7 @@ #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/editor_undo_redo_manager.h" +#include "scene/gui/dialogs.h" #include "scene/gui/menu_button.h" void Path2DEditor::_notification(int p_what) { @@ -48,6 +49,7 @@ void Path2DEditor::_notification(int p_what) { curve_create->set_icon(get_editor_theme_icon(SNAME("CurveCreate"))); curve_del->set_icon(get_editor_theme_icon(SNAME("CurveDelete"))); curve_close->set_icon(get_editor_theme_icon(SNAME("CurveClose"))); + curve_clear_points->set_icon(get_editor_theme_icon(SNAME("Clear"))); } break; } } @@ -68,7 +70,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { return false; } - if (!node->get_curve().is_valid()) { + if (node->get_curve().is_null()) { return false; } @@ -121,7 +123,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { // Check for point deletion. EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - if ((mb->get_button_index() == MouseButton::RIGHT && mode == MODE_EDIT) || (mb->get_button_index() == MouseButton::LEFT && mode == MODE_DELETE)) { + if ((mb->get_button_index() == MouseButton::RIGHT && (mode == MODE_EDIT || mode == MODE_CREATE)) || (mb->get_button_index() == MouseButton::LEFT && mode == MODE_DELETE)) { if (dist_to_p < grab_threshold) { undo_redo->create_action(TTR("Remove Point from Curve")); undo_redo->add_do_method(curve.ptr(), "remove_point", i); @@ -154,16 +156,14 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { // Check for point creation. if (mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && ((mb->is_command_or_control_pressed() && mode == MODE_EDIT) || mode == MODE_CREATE)) { Ref<Curve2D> curve = node->get_curve(); + curve->add_point(cpoint); EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Add Point to Curve")); undo_redo->add_do_method(curve.ptr(), "add_point", cpoint); - undo_redo->add_undo_method(curve.ptr(), "remove_point", curve->get_point_count()); - undo_redo->add_do_method(canvas_item_editor, "update_viewport"); - undo_redo->add_undo_method(canvas_item_editor, "update_viewport"); - undo_redo->commit_action(); + undo_redo->add_undo_method(curve.ptr(), "remove_point", curve->get_point_count() - 1); - action = ACTION_MOVING_POINT; + action = ACTION_MOVING_NEW_POINT; action_point = curve->get_point_count() - 1; moving_from = curve->get_point_position(action_point); moving_screen_from = gpoint; @@ -191,15 +191,15 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { insertion_point = curve->get_point_count() - 2; } + const Vector2 new_point = xform.affine_inverse().xform(gpoint2); + curve->add_point(new_point, Vector2(0, 0), Vector2(0, 0), insertion_point + 1); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); undo_redo->create_action(TTR("Split Curve")); - undo_redo->add_do_method(curve.ptr(), "add_point", xform.affine_inverse().xform(gpoint2), Vector2(0, 0), Vector2(0, 0), insertion_point + 1); + undo_redo->add_do_method(curve.ptr(), "add_point", new_point, Vector2(0, 0), Vector2(0, 0), insertion_point + 1); undo_redo->add_undo_method(curve.ptr(), "remove_point", insertion_point + 1); - undo_redo->add_do_method(canvas_item_editor, "update_viewport"); - undo_redo->add_undo_method(canvas_item_editor, "update_viewport"); - undo_redo->commit_action(); - action = ACTION_MOVING_POINT; + action = ACTION_MOVING_NEW_POINT; action_point = insertion_point + 1; moving_from = curve->get_point_position(action_point); moving_screen_from = gpoint2; @@ -222,13 +222,16 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { // N/A, handled in above condition. break; - case ACTION_MOVING_POINT: { - undo_redo->create_action(TTR("Move Point in Curve")); + case ACTION_MOVING_POINT: + case ACTION_MOVING_NEW_POINT: { + if (action == ACTION_MOVING_POINT) { + undo_redo->create_action(TTR("Move Point in Curve")); + undo_redo->add_undo_method(curve.ptr(), "set_point_position", action_point, moving_from); + } undo_redo->add_do_method(curve.ptr(), "set_point_position", action_point, cpoint); - undo_redo->add_undo_method(curve.ptr(), "set_point_position", action_point, moving_from); undo_redo->add_do_method(canvas_item_editor, "update_viewport"); undo_redo->add_undo_method(canvas_item_editor, "update_viewport"); - undo_redo->commit_action(); + undo_redo->commit_action(false); } break; @@ -334,7 +337,8 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { // N/A, handled in above condition. break; - case ACTION_MOVING_POINT: { + case ACTION_MOVING_POINT: + case ACTION_MOVING_NEW_POINT: { curve->set_point_position(action_point, cpoint); } break; @@ -364,7 +368,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { } void Path2DEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { - if (!node || !node->is_visible_in_tree() || !node->get_curve().is_valid()) { + if (!node || !node->is_visible_in_tree() || node->get_curve().is_null()) { return; } @@ -437,12 +441,12 @@ void Path2DEditor::edit(Node *p_path2d) { if (p_path2d) { node = Object::cast_to<Path2D>(p_path2d); + if (!node->is_connected("visibility_changed", callable_mp(this, &Path2DEditor::_node_visibility_changed))) { node->connect("visibility_changed", callable_mp(this, &Path2DEditor::_node_visibility_changed)); } - } else { - // node may have been deleted at this point + // The node may have been deleted at this point. if (node && node->is_connected("visibility_changed", callable_mp(this, &Path2DEditor::_node_visibility_changed))) { node->disconnect("visibility_changed", callable_mp(this, &Path2DEditor::_node_visibility_changed)); } @@ -452,6 +456,8 @@ void Path2DEditor::edit(Node *p_path2d) { void Path2DEditor::_bind_methods() { //ClassDB::bind_method(D_METHOD("_menu_option"),&Path2DEditor::_menu_option); + ClassDB::bind_method(D_METHOD("_clear_curve_points"), &Path2DEditor::_clear_curve_points); + ClassDB::bind_method(D_METHOD("_restore_curve_points"), &Path2DEditor::_restore_curve_points); } void Path2DEditor::_mode_selected(int p_mode) { @@ -475,32 +481,46 @@ void Path2DEditor::_mode_selected(int p_mode) { curve_edit->set_pressed(false); curve_edit_curve->set_pressed(false); curve_del->set_pressed(true); - } else if (p_mode == ACTION_CLOSE) { - //? - - if (!node->get_curve().is_valid()) { + } else if (p_mode == MODE_CLOSE) { + if (node->get_curve().is_null()) { return; } if (node->get_curve()->get_point_count() < 3) { return; } - Vector2 begin = node->get_curve()->get_point_position(0); Vector2 end = node->get_curve()->get_point_position(node->get_curve()->get_point_count() - 1); + if (begin.is_equal_approx(end)) { return; } - EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Remove Point from Curve")); + + undo_redo->create_action(TTR("Close the Curve")); undo_redo->add_do_method(node->get_curve().ptr(), "add_point", begin); undo_redo->add_undo_method(node->get_curve().ptr(), "remove_point", node->get_curve()->get_point_count()); undo_redo->add_do_method(canvas_item_editor, "update_viewport"); undo_redo->add_undo_method(canvas_item_editor, "update_viewport"); undo_redo->commit_action(); return; - } + } else if (p_mode == MODE_CLEAR_POINTS) { + if (node->get_curve().is_null()) { + return; + } + if (node->get_curve()->get_point_count() == 0) { + return; + } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + PackedVector2Array points = node->get_curve()->get_points().duplicate(); + undo_redo->create_action(TTR("Clear Curve Points"), UndoRedo::MERGE_DISABLE, node); + undo_redo->add_do_method(this, "_clear_curve_points", node); + undo_redo->add_undo_method(this, "_restore_curve_points", node, points); + undo_redo->add_do_method(canvas_item_editor, "update_viewport"); + undo_redo->add_undo_method(canvas_item_editor, "update_viewport"); + undo_redo->commit_action(); + return; + } mode = Mode(p_mode); } @@ -523,6 +543,52 @@ void Path2DEditor::_handle_option_pressed(int p_option) { } } +void Path2DEditor::_confirm_clear_points() { + if (!node || node->get_curve().is_null()) { + return; + } + if (node->get_curve()->get_point_count() == 0) { + return; + } + clear_points_dialog->reset_size(); + clear_points_dialog->popup_centered(); +} + +void Path2DEditor::_clear_curve_points(Path2D *p_path2d) { + if (!p_path2d || p_path2d->get_curve().is_null()) { + return; + } + Ref<Curve2D> curve = p_path2d->get_curve(); + + if (curve->get_point_count() == 0) { + return; + } + curve->clear_points(); + + if (node == p_path2d) { + _mode_selected(MODE_CREATE); + } +} + +void Path2DEditor::_restore_curve_points(Path2D *p_path2d, const PackedVector2Array &p_points) { + if (!p_path2d || p_path2d->get_curve().is_null()) { + return; + } + Ref<Curve2D> curve = p_path2d->get_curve(); + + if (curve->get_point_count() > 0) { + curve->clear_points(); + } + + for (int i = 0; i < p_points.size(); i += 3) { + curve->add_point(p_points[i + 2], p_points[i], p_points[i + 1]); // The Curve2D::points pattern is [point_in, point_out, point_position]. + } + + if (node == p_path2d) { + _mode_selected(MODE_EDIT); + } +} + Path2DEditor::Path2DEditor() { canvas_item_editor = nullptr; mirror_handle_angle = true; @@ -553,7 +619,7 @@ Path2DEditor::Path2DEditor() { curve_create->set_theme_type_variation("FlatButton"); curve_create->set_toggle_mode(true); curve_create->set_focus_mode(Control::FOCUS_NONE); - curve_create->set_tooltip_text(TTR("Add Point (in empty space)")); + curve_create->set_tooltip_text(TTR("Add Point (in empty space)") + "\n" + TTR("Right Click: Delete Point")); curve_create->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_CREATE)); add_child(curve_create); @@ -569,9 +635,22 @@ Path2DEditor::Path2DEditor() { curve_close->set_theme_type_variation("FlatButton"); curve_close->set_focus_mode(Control::FOCUS_NONE); curve_close->set_tooltip_text(TTR("Close Curve")); - curve_close->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected).bind(ACTION_CLOSE)); + curve_close->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_CLOSE)); add_child(curve_close); + curve_clear_points = memnew(Button); + curve_clear_points->set_theme_type_variation("FlatButton"); + curve_clear_points->set_focus_mode(Control::FOCUS_NONE); + curve_clear_points->set_tooltip_text(TTR("Clear Points")); + curve_clear_points->connect("pressed", callable_mp(this, &Path2DEditor::_confirm_clear_points)); + add_child(curve_clear_points); + + clear_points_dialog = memnew(ConfirmationDialog); + clear_points_dialog->set_title(TTR("Please Confirm...")); + clear_points_dialog->set_text(TTR("Remove all curve points?")); + clear_points_dialog->connect("confirmed", callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_CLEAR_POINTS)); + add_child(clear_points_dialog); + PopupMenu *menu; handle_menu = memnew(MenuButton); diff --git a/editor/plugins/path_2d_editor_plugin.h b/editor/plugins/path_2d_editor_plugin.h index b8816d9b1e..af9f307cc8 100644 --- a/editor/plugins/path_2d_editor_plugin.h +++ b/editor/plugins/path_2d_editor_plugin.h @@ -36,45 +36,51 @@ #include "scene/gui/box_container.h" class CanvasItemEditor; +class ConfirmationDialog; class MenuButton; class Path2DEditor : public HBoxContainer { GDCLASS(Path2DEditor, HBoxContainer); + friend class Path2DEditorPlugin; + CanvasItemEditor *canvas_item_editor = nullptr; Panel *panel = nullptr; Path2D *node = nullptr; - HBoxContainer *base_hb = nullptr; - enum Mode { MODE_CREATE, MODE_EDIT, MODE_EDIT_CURVE, MODE_DELETE, - ACTION_CLOSE + MODE_CLOSE, + MODE_CLEAR_POINTS, }; Mode mode; + Button *curve_clear_points = nullptr; + Button *curve_close = nullptr; Button *curve_create = nullptr; + Button *curve_del = nullptr; Button *curve_edit = nullptr; Button *curve_edit_curve = nullptr; - Button *curve_del = nullptr; - Button *curve_close = nullptr; MenuButton *handle_menu = nullptr; + ConfirmationDialog *clear_points_dialog = nullptr; + bool mirror_handle_angle; bool mirror_handle_length; bool on_edge; enum HandleOption { HANDLE_OPTION_ANGLE, - HANDLE_OPTION_LENGTH + HANDLE_OPTION_LENGTH, }; enum Action { ACTION_NONE, ACTION_MOVING_POINT, + ACTION_MOVING_NEW_POINT, ACTION_MOVING_IN, ACTION_MOVING_OUT, }; @@ -91,7 +97,10 @@ class Path2DEditor : public HBoxContainer { void _handle_option_pressed(int p_option); void _node_visibility_changed(); - friend class Path2DEditorPlugin; + + void _confirm_clear_points(); + void _clear_curve_points(Path2D *p_path2d); + void _restore_curve_points(Path2D *p_path2d, const PackedVector2Array &p_points); protected: void _notification(int p_what); diff --git a/editor/plugins/resource_preloader_editor_plugin.cpp b/editor/plugins/resource_preloader_editor_plugin.cpp index ee7ad739b8..d9f5aee82c 100644 --- a/editor/plugins/resource_preloader_editor_plugin.cpp +++ b/editor/plugins/resource_preloader_editor_plugin.cpp @@ -50,7 +50,7 @@ void ResourcePreloaderEditor::_notification(int p_what) { void ResourcePreloaderEditor::_files_load_request(const Vector<String> &p_paths) { for (int i = 0; i < p_paths.size(); i++) { - String path = p_paths[i]; + const String &path = p_paths[i]; Ref<Resource> resource; resource = ResourceLoader::load(path); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index c1a65b7bb7..edf0b73356 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -2963,7 +2963,7 @@ bool ScriptEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data } for (int i = 0; i < files.size(); i++) { - String file = files[i]; + const String &file = files[i]; if (file.is_empty() || !FileAccess::exists(file)) { continue; } @@ -3043,7 +3043,7 @@ void ScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Co } int num_tabs_before = tab_container->get_tab_count(); for (int i = 0; i < files.size(); i++) { - String file = files[i]; + const String &file = files[i]; if (file.is_empty() || !FileAccess::exists(file)) { continue; } @@ -3860,6 +3860,7 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) { scripts_vbox->add_child(filter_scripts); script_list = memnew(ItemList); + script_list->set_auto_translate(false); scripts_vbox->add_child(script_list); script_list->set_custom_minimum_size(Size2(150, 60) * EDSCALE); //need to give a bit of limit to avoid it from disappearing script_list->set_v_size_flags(SIZE_EXPAND_FILL); @@ -3904,6 +3905,7 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) { overview_vbox->add_child(filter_methods); members_overview = memnew(ItemList); + members_overview->set_auto_translate(false); overview_vbox->add_child(members_overview); members_overview->set_allow_reselect(true); @@ -3912,6 +3914,7 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) { members_overview->set_allow_rmb_select(true); help_overview = memnew(ItemList); + help_overview->set_auto_translate(false); overview_vbox->add_child(help_overview); help_overview->set_allow_reselect(true); help_overview->set_custom_minimum_size(Size2(0, 60) * EDSCALE); //need to give a bit of limit to avoid it from disappearing diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 749221de99..574b18731e 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -1411,7 +1411,7 @@ void ScriptTextEditor::_edit_option(int p_op) { PackedStringArray results; for (int i = 0; i < lines.size(); i++) { - String line = lines[i]; + const String &line = lines[i]; String whitespace = line.substr(0, line.size() - line.strip_edges(true, false).size()); // Extract the whitespace at the beginning. if (expression.parse(line) == OK) { Variant result = expression.execute(Array(), Variant(), false, true); @@ -2449,7 +2449,7 @@ void ScriptTextEditor::register_editor() { ED_SHORTCUT("script_text_editor/indent", TTR("Indent"), Key::NONE); ED_SHORTCUT("script_text_editor/unindent", TTR("Unindent"), KeyModifierMask::SHIFT | Key::TAB); - ED_SHORTCUT_ARRAY("script_text_editor/toggle_comment", TTR("Toggle Comment"), { int32_t(KeyModifierMask::CMD_OR_CTRL | Key::K), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::SLASH) }); + ED_SHORTCUT_ARRAY("script_text_editor/toggle_comment", TTR("Toggle Comment"), { int32_t(KeyModifierMask::CMD_OR_CTRL | Key::K), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::SLASH), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::KP_DIVIDE), int32_t(KeyModifierMask::CMD_OR_CTRL | Key::NUMBERSIGN) }); ED_SHORTCUT("script_text_editor/toggle_fold_line", TTR("Fold/Unfold Line"), KeyModifierMask::ALT | Key::F); ED_SHORTCUT_OVERRIDE("script_text_editor/toggle_fold_line", "macos", KeyModifierMask::CTRL | KeyModifierMask::META | Key::F); ED_SHORTCUT("script_text_editor/fold_all_lines", TTR("Fold All Lines"), Key::NONE); diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index 56b8df1b68..5ee4d4a961 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -533,7 +533,7 @@ bool ShaderEditorPlugin::can_drop_data_fw(const Point2 &p_point, const Variant & } for (int i = 0; i < files.size(); i++) { - String file = files[i]; + const String &file = files[i]; if (ResourceLoader::exists(file, "Shader")) { Ref<Shader> shader = ResourceLoader::load(file); if (shader.is_valid()) { @@ -574,7 +574,7 @@ void ShaderEditorPlugin::drop_data_fw(const Point2 &p_point, const Variant &p_da Vector<String> files = d["files"]; for (int i = 0; i < files.size(); i++) { - String file = files[i]; + const String &file = files[i]; Ref<Resource> res; if (ResourceLoader::exists(file, "Shader") || ResourceLoader::exists(file, "ShaderInclude")) { res = ResourceLoader::load(file); @@ -657,6 +657,7 @@ ShaderEditorPlugin::ShaderEditorPlugin() { } shader_list = memnew(ItemList); + shader_list->set_auto_translate(false); shader_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); vb->add_child(shader_list); shader_list->connect("item_selected", callable_mp(this, &ShaderEditorPlugin::_shader_selected)); diff --git a/editor/plugins/shader_file_editor_plugin.cpp b/editor/plugins/shader_file_editor_plugin.cpp index 11bec4f61c..d68b3bde14 100644 --- a/editor/plugins/shader_file_editor_plugin.cpp +++ b/editor/plugins/shader_file_editor_plugin.cpp @@ -256,6 +256,7 @@ ShaderFileEditor::ShaderFileEditor() { add_child(main_hs); versions = memnew(ItemList); + versions->set_auto_translate(false); versions->connect("item_selected", callable_mp(this, &ShaderFileEditor::_version_selected)); versions->set_custom_minimum_size(Size2i(200 * EDSCALE, 0)); main_hs->add_child(versions); diff --git a/editor/plugins/sprite_2d_editor_plugin.cpp b/editor/plugins/sprite_2d_editor_plugin.cpp index 7c1c6a8f82..d343f80420 100644 --- a/editor/plugins/sprite_2d_editor_plugin.cpp +++ b/editor/plugins/sprite_2d_editor_plugin.cpp @@ -34,7 +34,9 @@ #include "core/math/geometry_2d.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" #include "editor/editor_undo_redo_manager.h" +#include "editor/gui/editor_zoom_widget.h" #include "editor/scene_tree_dock.h" #include "scene/2d/collision_polygon_2d.h" #include "scene/2d/light_occluder_2d.h" @@ -42,6 +44,8 @@ #include "scene/2d/polygon_2d.h" #include "scene/gui/box_container.h" #include "scene/gui/menu_button.h" +#include "scene/gui/panel.h" +#include "scene/gui/view_panner.h" #include "thirdparty/misc/clipper.hpp" void Sprite2DEditor::_node_removed(Node *p_node) { @@ -168,6 +172,8 @@ void Sprite2DEditor::_popup_debug_uv_dialog() { _update_mesh_data(); debug_uv_dialog->popup_centered(); + get_tree()->connect("process_frame", callable_mp(this, &Sprite2DEditor::_center_view), CONNECT_ONE_SHOT); + debug_uv->set_texture_filter(node->get_texture_filter_in_tree()); debug_uv->queue_redraw(); } @@ -460,17 +466,19 @@ void Sprite2DEditor::_add_as_sibling_or_child(Node *p_own_node, Node *p_new_node p_new_node->set_owner(this->get_tree()->get_edited_scene_root()); } +void Sprite2DEditor::_debug_uv_input(const Ref<InputEvent> &p_input) { + if (panner->gui_input(p_input)) { + accept_event(); + } +} + void Sprite2DEditor::_debug_uv_draw() { + debug_uv->draw_set_transform(-draw_offset * draw_zoom, 0, Vector2(draw_zoom, draw_zoom)); + Ref<Texture2D> tex = node->get_texture(); ERR_FAIL_COND(!tex.is_valid()); - Point2 draw_pos_offset = Point2(1.0, 1.0); - Size2 draw_size_offset = Size2(2.0, 2.0); - - debug_uv->set_clip_contents(true); - debug_uv->draw_texture(tex, draw_pos_offset); - debug_uv->set_custom_minimum_size(tex->get_size() + draw_size_offset); - debug_uv->draw_set_transform(draw_pos_offset, 0, Size2(1.0, 1.0)); + debug_uv->draw_texture(tex, Point2()); Color color = Color(1.0, 0.8, 0.7); @@ -487,8 +495,86 @@ void Sprite2DEditor::_debug_uv_draw() { } } +void Sprite2DEditor::_center_view() { + Ref<Texture2D> tex = node->get_texture(); + ERR_FAIL_COND(!tex.is_valid()); + Vector2 zoom_factor = (debug_uv->get_size() - Vector2(1, 1) * 50 * EDSCALE) / tex->get_size(); + zoom_widget->set_zoom(MIN(zoom_factor.x, zoom_factor.y)); + // Recalculate scroll limits. + _update_zoom_and_pan(false); + + Vector2 offset = (tex->get_size() - debug_uv->get_size() / zoom_widget->get_zoom()) / 2; + h_scroll->set_value_no_signal(offset.x); + v_scroll->set_value_no_signal(offset.y); + _update_zoom_and_pan(false); +} + +void Sprite2DEditor::_pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event) { + h_scroll->set_value_no_signal(h_scroll->get_value() - p_scroll_vec.x / draw_zoom); + v_scroll->set_value_no_signal(v_scroll->get_value() - p_scroll_vec.y / draw_zoom); + _update_zoom_and_pan(false); +} + +void Sprite2DEditor::_zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event) { + const real_t prev_zoom = draw_zoom; + zoom_widget->set_zoom(draw_zoom * p_zoom_factor); + draw_offset += p_origin / prev_zoom - p_origin / zoom_widget->get_zoom(); + h_scroll->set_value_no_signal(draw_offset.x); + v_scroll->set_value_no_signal(draw_offset.y); + _update_zoom_and_pan(false); +} + +void Sprite2DEditor::_update_zoom_and_pan(bool p_zoom_at_center) { + real_t previous_zoom = draw_zoom; + draw_zoom = zoom_widget->get_zoom(); + draw_offset = Vector2(h_scroll->get_value(), v_scroll->get_value()); + if (p_zoom_at_center) { + Vector2 center = debug_uv->get_size() / 2; + draw_offset += center / previous_zoom - center / draw_zoom; + } + + Ref<Texture2D> tex = node->get_texture(); + ERR_FAIL_COND(!tex.is_valid()); + + Point2 min_corner; + Point2 max_corner = tex->get_size(); + Size2 page_size = debug_uv->get_size() / draw_zoom; + Vector2 margin = Vector2(50, 50) * EDSCALE / draw_zoom; + min_corner -= page_size - margin; + max_corner += page_size - margin; + + h_scroll->set_block_signals(true); + h_scroll->set_min(min_corner.x); + h_scroll->set_max(max_corner.x); + h_scroll->set_page(page_size.x); + h_scroll->set_value(draw_offset.x); + h_scroll->set_block_signals(false); + + v_scroll->set_block_signals(true); + v_scroll->set_min(min_corner.y); + v_scroll->set_max(max_corner.y); + v_scroll->set_page(page_size.y); + v_scroll->set_value(draw_offset.y); + v_scroll->set_block_signals(false); + + debug_uv->queue_redraw(); +} + void Sprite2DEditor::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_READY: { + v_scroll->set_anchors_and_offsets_preset(Control::PRESET_RIGHT_WIDE); + h_scroll->set_anchors_and_offsets_preset(Control::PRESET_BOTTOM_WIDE); + // Avoid scrollbar overlapping. + Size2 hmin = h_scroll->get_combined_minimum_size(); + Size2 vmin = v_scroll->get_combined_minimum_size(); + h_scroll->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, -vmin.width); + v_scroll->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -hmin.height); + [[fallthrough]]; + } + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EDITOR_GET("editors/panning/simple_panning"))); + } break; case NOTIFICATION_ENTER_TREE: case NOTIFICATION_THEME_CHANGED: { options->set_icon(get_editor_theme_icon(SNAME("Sprite2D"))); @@ -526,12 +612,29 @@ Sprite2DEditor::Sprite2DEditor() { debug_uv_dialog = memnew(ConfirmationDialog); VBoxContainer *vb = memnew(VBoxContainer); debug_uv_dialog->add_child(vb); - ScrollContainer *scroll = memnew(ScrollContainer); - scroll->set_custom_minimum_size(Size2(800, 500) * EDSCALE); - vb->add_margin_child(TTR("Preview:"), scroll, true); - debug_uv = memnew(Control); + debug_uv = memnew(Panel); + debug_uv->connect("gui_input", callable_mp(this, &Sprite2DEditor::_debug_uv_input)); debug_uv->connect("draw", callable_mp(this, &Sprite2DEditor::_debug_uv_draw)); - scroll->add_child(debug_uv); + debug_uv->set_custom_minimum_size(Size2(800, 500) * EDSCALE); + debug_uv->set_clip_contents(true); + vb->add_margin_child(TTR("Preview:"), debug_uv, true); + + panner.instantiate(); + panner->set_callbacks(callable_mp(this, &Sprite2DEditor::_pan_callback), callable_mp(this, &Sprite2DEditor::_zoom_callback)); + + zoom_widget = memnew(EditorZoomWidget); + debug_uv->add_child(zoom_widget); + zoom_widget->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT, Control::PRESET_MODE_MINSIZE, 2 * EDSCALE); + zoom_widget->connect("zoom_changed", callable_mp(this, &Sprite2DEditor::_update_zoom_and_pan).unbind(1).bind(true)); + zoom_widget->set_shortcut_context(nullptr); + + v_scroll = memnew(VScrollBar); + debug_uv->add_child(v_scroll); + v_scroll->connect("value_changed", callable_mp(this, &Sprite2DEditor::_update_zoom_and_pan).unbind(1).bind(false)); + h_scroll = memnew(HScrollBar); + debug_uv->add_child(h_scroll); + h_scroll->connect("value_changed", callable_mp(this, &Sprite2DEditor::_update_zoom_and_pan).unbind(1).bind(false)); + debug_uv_dialog->connect("confirmed", callable_mp(this, &Sprite2DEditor::_create_node)); HBoxContainer *hb = memnew(HBoxContainer); diff --git a/editor/plugins/sprite_2d_editor_plugin.h b/editor/plugins/sprite_2d_editor_plugin.h index 52e4b2b264..1121481341 100644 --- a/editor/plugins/sprite_2d_editor_plugin.h +++ b/editor/plugins/sprite_2d_editor_plugin.h @@ -37,7 +37,10 @@ class AcceptDialog; class ConfirmationDialog; +class EditorZoomWidget; class MenuButton; +class Panel; +class ViewPanner; class Sprite2DEditor : public Control { GDCLASS(Sprite2DEditor, Control); @@ -60,7 +63,7 @@ class Sprite2DEditor : public Control { AcceptDialog *err_dialog = nullptr; ConfirmationDialog *debug_uv_dialog = nullptr; - Control *debug_uv = nullptr; + Panel *debug_uv = nullptr; Vector<Vector2> uv_lines; Vector<Vector<Vector2>> outline_lines; Vector<Vector<Vector2>> computed_outline_lines; @@ -68,6 +71,13 @@ class Sprite2DEditor : public Control { Vector<Vector2> computed_uv; Vector<int> computed_indices; + HScrollBar *h_scroll = nullptr; + VScrollBar *v_scroll = nullptr; + EditorZoomWidget *zoom_widget = nullptr; + Ref<ViewPanner> panner; + Vector2 draw_offset; + real_t draw_zoom = 1.0; + SpinBox *simplification = nullptr; SpinBox *grow_pixels = nullptr; SpinBox *shrink_pixels = nullptr; @@ -78,8 +88,13 @@ class Sprite2DEditor : public Control { //void _create_uv_lines(); friend class Sprite2DEditorPlugin; + void _debug_uv_input(const Ref<InputEvent> &p_input); void _debug_uv_draw(); void _popup_debug_uv_dialog(); + void _center_view(); + void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event); + void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event); + void _update_zoom_and_pan(bool p_zoom_at_center); void _update_mesh_data(); void _create_node(); diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp index a1241ae662..f4fa4b14bb 100644 --- a/editor/plugins/sprite_frames_editor_plugin.cpp +++ b/editor/plugins/sprite_frames_editor_plugin.cpp @@ -1425,7 +1425,7 @@ bool SpriteFramesEditor::can_drop_data_fw(const Point2 &p_point, const Variant & } for (int i = 0; i < files.size(); i++) { - String f = files[i]; + const String &f = files[i]; String ftype = EditorFileSystem::get_singleton()->get_file_type(f); if (!ClassDB::is_parent_class(ftype, "Texture2D")) { @@ -1880,6 +1880,7 @@ SpriteFramesEditor::SpriteFramesEditor() { add_child(file); frame_list = memnew(ItemList); + frame_list->set_auto_translate(false); frame_list->set_v_size_flags(SIZE_EXPAND_FILL); frame_list->set_icon_mode(ItemList::ICON_MODE_TOP); frame_list->set_texture_filter(TEXTURE_FILTER_NEAREST_WITH_MIPMAPS); diff --git a/editor/plugins/texture_editor_plugin.cpp b/editor/plugins/texture_editor_plugin.cpp index ec5785e605..9d640bf0dc 100644 --- a/editor/plugins/texture_editor_plugin.cpp +++ b/editor/plugins/texture_editor_plugin.cpp @@ -38,6 +38,7 @@ #include "scene/resources/atlas_texture.h" #include "scene/resources/compressed_texture.h" #include "scene/resources/image_texture.h" +#include "scene/resources/portable_compressed_texture.h" TextureRect *TexturePreview::get_texture_display() { return texture_display; @@ -158,7 +159,7 @@ TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) { } bool EditorInspectorPluginTexture::can_handle(Object *p_object) { - return Object::cast_to<ImageTexture>(p_object) != nullptr || Object::cast_to<AtlasTexture>(p_object) != nullptr || Object::cast_to<CompressedTexture2D>(p_object) != nullptr || Object::cast_to<AnimatedTexture>(p_object) != nullptr || Object::cast_to<Image>(p_object) != nullptr; + return Object::cast_to<ImageTexture>(p_object) != nullptr || Object::cast_to<AtlasTexture>(p_object) != nullptr || Object::cast_to<CompressedTexture2D>(p_object) != nullptr || Object::cast_to<PortableCompressedTexture2D>(p_object) != nullptr || Object::cast_to<AnimatedTexture>(p_object) != nullptr || Object::cast_to<Image>(p_object) != nullptr; } void EditorInspectorPluginTexture::parse_begin(Object *p_object) { diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index 6c153f6113..cbe0f115d3 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -2247,6 +2247,7 @@ ThemeTypeDialog::ThemeTypeDialog() { add_type_vb->add_child(add_type_options_label); add_type_options = memnew(ItemList); + add_type_options->set_auto_translate(false); add_type_options->set_v_size_flags(Control::SIZE_EXPAND_FILL); add_type_vb->add_child(add_type_options); add_type_options->connect("item_selected", callable_mp(this, &ThemeTypeDialog::_add_type_options_cbk)); @@ -2373,7 +2374,7 @@ void ThemeTypeEditor::_update_type_list_debounced() { update_debounce_timer->start(); } -HashMap<StringName, bool> ThemeTypeEditor::_get_type_items(String p_type_name, void (Theme::*get_list_func)(StringName, List<StringName> *) const, bool include_default) { +HashMap<StringName, bool> ThemeTypeEditor::_get_type_items(String p_type_name, void (Theme::*get_list_func)(const StringName &, List<StringName> *) const, bool include_default) { HashMap<StringName, bool> items; List<StringName> names; diff --git a/editor/plugins/theme_editor_plugin.h b/editor/plugins/theme_editor_plugin.h index cf8c5ceb28..8ad262da55 100644 --- a/editor/plugins/theme_editor_plugin.h +++ b/editor/plugins/theme_editor_plugin.h @@ -373,7 +373,7 @@ class ThemeTypeEditor : public MarginContainer { VBoxContainer *_create_item_list(Theme::DataType p_data_type); void _update_type_list(); void _update_type_list_debounced(); - HashMap<StringName, bool> _get_type_items(String p_type_name, void (Theme::*get_list_func)(StringName, List<StringName> *) const, bool include_default); + HashMap<StringName, bool> _get_type_items(String p_type_name, void (Theme::*get_list_func)(const StringName &, List<StringName> *) const, bool include_default); HBoxContainer *_create_property_control(Theme::DataType p_data_type, String p_item_name, bool p_editable); void _add_focusable(Control *p_control); void _update_type_items(); diff --git a/editor/plugins/tiles/atlas_merging_dialog.cpp b/editor/plugins/tiles/atlas_merging_dialog.cpp index b91f32775f..62b4993947 100644 --- a/editor/plugins/tiles/atlas_merging_dialog.cpp +++ b/editor/plugins/tiles/atlas_merging_dialog.cpp @@ -52,7 +52,7 @@ void AtlasMergingDialog::_generate_merged(Vector<Ref<TileSetAtlasSource>> p_atla // Compute the new texture region size. Vector2i new_texture_region_size; for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) { - Ref<TileSetAtlasSource> atlas_source = p_atlas_sources[source_index]; + const Ref<TileSetAtlasSource> &atlas_source = p_atlas_sources[source_index]; new_texture_region_size = new_texture_region_size.max(atlas_source->get_texture_region_size()); } @@ -60,7 +60,7 @@ void AtlasMergingDialog::_generate_merged(Vector<Ref<TileSetAtlasSource>> p_atla Vector2i atlas_offset; int line_height = 0; for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) { - Ref<TileSetAtlasSource> atlas_source = p_atlas_sources[source_index]; + const Ref<TileSetAtlasSource> &atlas_source = p_atlas_sources[source_index]; Ref<Image> input_image = atlas_source->get_texture()->get_image(); if (input_image->get_format() != Image::FORMAT_RGBA8) { input_image->convert(Image::FORMAT_RGBA8); @@ -111,7 +111,7 @@ void AtlasMergingDialog::_generate_merged(Vector<Ref<TileSetAtlasSource>> p_atla // Copy the tiles to the merged TileSetAtlasSource. for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) { - Ref<TileSetAtlasSource> atlas_source = p_atlas_sources[source_index]; + const Ref<TileSetAtlasSource> &atlas_source = p_atlas_sources[source_index]; for (KeyValue<Vector2i, Vector2i> tile_mapping : merged_mapping[source_index]) { // Create tiles and alternatives, then copy their properties. for (int alternative_index = 0; alternative_index < atlas_source->get_alternative_tiles_count(tile_mapping.key); alternative_index++) { @@ -311,6 +311,7 @@ AtlasMergingDialog::AtlasMergingDialog() { // Atlas sources item list. atlas_merging_atlases_list = memnew(ItemList); + atlas_merging_atlases_list->set_auto_translate(false); atlas_merging_atlases_list->set_fixed_icon_size(Size2(60, 60) * EDSCALE); atlas_merging_atlases_list->set_h_size_flags(Control::SIZE_EXPAND_FILL); atlas_merging_atlases_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp index 221833d450..c8247e0551 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -112,16 +112,16 @@ bool DummyObject::_get(const StringName &p_name, Variant &r_ret) const { return false; } -bool DummyObject::has_dummy_property(StringName p_name) { +bool DummyObject::has_dummy_property(const StringName &p_name) { return properties.has(p_name); } -void DummyObject::add_dummy_property(StringName p_name) { +void DummyObject::add_dummy_property(const StringName &p_name) { ERR_FAIL_COND(properties.has(p_name)); properties[p_name] = Variant(); } -void DummyObject::remove_dummy_property(StringName p_name) { +void DummyObject::remove_dummy_property(const StringName &p_name) { ERR_FAIL_COND(!properties.has(p_name)); properties.erase(p_name); } @@ -719,11 +719,19 @@ void GenericTilePolygonEditor::set_tile_set(Ref<TileSet> p_tile_set) { Vector2 zoomed_tile = editor_zoom_widget->get_zoom() * tile_set->get_tile_size(); while (zoomed_tile.y < default_control_y_size) { editor_zoom_widget->set_zoom_by_increments(6, false); - zoomed_tile = editor_zoom_widget->get_zoom() * tile_set->get_tile_size(); + float current_zoom = editor_zoom_widget->get_zoom(); + zoomed_tile = current_zoom * tile_set->get_tile_size(); + if (Math::is_equal_approx(current_zoom, editor_zoom_widget->get_max_zoom())) { + break; + } } while (zoomed_tile.y > default_control_y_size) { editor_zoom_widget->set_zoom_by_increments(-6, false); - zoomed_tile = editor_zoom_widget->get_zoom() * tile_set->get_tile_size(); + float current_zoom = editor_zoom_widget->get_zoom(); + zoomed_tile = current_zoom * tile_set->get_tile_size(); + if (Math::is_equal_approx(current_zoom, editor_zoom_widget->get_min_zoom())) { + break; + } } editor_zoom_widget->set_zoom_by_increments(-6, false); _zoom_changed(); @@ -948,7 +956,7 @@ GenericTilePolygonEditor::GenericTilePolygonEditor() { _set_snap_option(EditorSettings::get_singleton()->get_project_metadata("editor_metadata", "tile_snap_option", SNAP_NONE)); } -void TileDataDefaultEditor::_property_value_changed(StringName p_property, Variant p_value, StringName p_field) { +void TileDataDefaultEditor::_property_value_changed(const StringName &p_property, Variant p_value, const StringName &p_field) { ERR_FAIL_NULL(dummy_object); dummy_object->set(p_property, p_value); emit_signal(SNAME("needs_redraw")); @@ -981,7 +989,7 @@ Variant TileDataDefaultEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_s return tile_data->get(property); } -void TileDataDefaultEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, HashMap<TileMapCell, Variant, TileMapCell> p_previous_values, Variant p_new_value) { +void TileDataDefaultEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, const HashMap<TileMapCell, Variant, TileMapCell> &p_previous_values, Variant p_new_value) { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); for (const KeyValue<TileMapCell, Variant> &E : p_previous_values) { Vector2i coords = E.key.get_atlas_coords(); @@ -1455,7 +1463,7 @@ Variant TileDataOcclusionShapeEditor::_get_value(TileSetAtlasSource *p_tile_set_ return tile_data->get_occluder(occlusion_layer); } -void TileDataOcclusionShapeEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, HashMap<TileMapCell, Variant, TileMapCell> p_previous_values, Variant p_new_value) { +void TileDataOcclusionShapeEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, const HashMap<TileMapCell, Variant, TileMapCell> &p_previous_values, Variant p_new_value) { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); for (const KeyValue<TileMapCell, Variant> &E : p_previous_values) { Vector2i coords = E.key.get_atlas_coords(); @@ -1481,11 +1489,11 @@ TileDataOcclusionShapeEditor::TileDataOcclusionShapeEditor() { add_child(polygon_editor); } -void TileDataCollisionEditor::_property_value_changed(StringName p_property, Variant p_value, StringName p_field) { +void TileDataCollisionEditor::_property_value_changed(const StringName &p_property, Variant p_value, const StringName &p_field) { dummy_object->set(p_property, p_value); } -void TileDataCollisionEditor::_property_selected(StringName p_path, int p_focusable) { +void TileDataCollisionEditor::_property_selected(const StringName &p_path, int p_focusable) { // Deselect all other properties for (KeyValue<StringName, EditorProperty *> &editor : property_editors) { if (editor.key != p_path) { @@ -1634,10 +1642,10 @@ Variant TileDataCollisionEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas return dict; } -void TileDataCollisionEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, HashMap<TileMapCell, Variant, TileMapCell> p_previous_values, Variant p_new_value) { +void TileDataCollisionEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, const HashMap<TileMapCell, Variant, TileMapCell> &p_previous_values, Variant p_new_value) { Dictionary new_dict = p_new_value; EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - for (KeyValue<TileMapCell, Variant> &E : p_previous_values) { + for (const KeyValue<TileMapCell, Variant> &E : p_previous_values) { Vector2i coords = E.key.get_atlas_coords(); Dictionary old_dict = E.value; @@ -1802,7 +1810,7 @@ void TileDataTerrainsEditor::_update_terrain_selector() { } } -void TileDataTerrainsEditor::_property_value_changed(StringName p_property, Variant p_value, StringName p_field) { +void TileDataTerrainsEditor::_property_value_changed(const StringName &p_property, Variant p_value, const StringName &p_field) { Variant old_value = dummy_object->get(p_property); dummy_object->set(p_property, p_value); if (p_property == "terrain_set") { @@ -2871,7 +2879,7 @@ Variant TileDataNavigationEditor::_get_value(TileSetAtlasSource *p_tile_set_atla return tile_data->get_navigation_polygon(navigation_layer); } -void TileDataNavigationEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, HashMap<TileMapCell, Variant, TileMapCell> p_previous_values, Variant p_new_value) { +void TileDataNavigationEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, const HashMap<TileMapCell, Variant, TileMapCell> &p_previous_values, Variant p_new_value) { EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); for (const KeyValue<TileMapCell, Variant> &E : p_previous_values) { Vector2i coords = E.key.get_atlas_coords(); diff --git a/editor/plugins/tiles/tile_data_editors.h b/editor/plugins/tiles/tile_data_editors.h index 4bba5bb467..27fe4316a0 100644 --- a/editor/plugins/tiles/tile_data_editors.h +++ b/editor/plugins/tiles/tile_data_editors.h @@ -82,9 +82,9 @@ protected: bool _get(const StringName &p_name, Variant &r_ret) const; public: - bool has_dummy_property(StringName p_name); - void add_dummy_property(StringName p_name); - void remove_dummy_property(StringName p_name); + bool has_dummy_property(const StringName &p_name); + void add_dummy_property(const StringName &p_name); + void remove_dummy_property(const StringName &p_name); void clear_dummy_properties(); }; @@ -224,7 +224,7 @@ private: HashMap<TileMapCell, Variant, TileMapCell> drag_modified; Variant drag_painted_value; - void _property_value_changed(StringName p_property, Variant p_value, StringName p_field); + void _property_value_changed(const StringName &p_property, Variant p_value, const StringName &p_field); protected: DummyObject *dummy_object = memnew(DummyObject); @@ -238,7 +238,7 @@ protected: virtual void _set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile); virtual void _set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value); virtual Variant _get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile); - virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, HashMap<TileMapCell, Variant, TileMapCell> p_previous_values, Variant p_new_value); + virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, const HashMap<TileMapCell, Variant, TileMapCell> &p_previous_values, Variant p_new_value); public: virtual Control *get_toolbar() override { return toolbar; }; @@ -291,7 +291,7 @@ private: virtual void _set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override; virtual void _set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) override; virtual Variant _get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override; - virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, HashMap<TileMapCell, Variant, TileMapCell> p_previous_values, Variant p_new_value) override; + virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, const HashMap<TileMapCell, Variant, TileMapCell> &p_previous_values, Variant p_new_value) override; protected: virtual void _tile_set_changed() override; @@ -316,15 +316,15 @@ class TileDataCollisionEditor : public TileDataDefaultEditor { DummyObject *dummy_object = memnew(DummyObject); HashMap<StringName, EditorProperty *> property_editors; - void _property_value_changed(StringName p_property, Variant p_value, StringName p_field); - void _property_selected(StringName p_path, int p_focusable); + void _property_value_changed(const StringName &p_property, Variant p_value, const StringName &p_field); + void _property_selected(const StringName &p_path, int p_focusable); void _polygons_changed(); virtual Variant _get_painted_value() override; virtual void _set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override; virtual void _set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) override; virtual Variant _get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override; - virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, HashMap<TileMapCell, Variant, TileMapCell> p_previous_values, Variant p_new_value) override; + virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, const HashMap<TileMapCell, Variant, TileMapCell> &p_previous_values, Variant p_new_value) override; protected: virtual void _tile_set_changed() override; @@ -368,7 +368,7 @@ private: EditorPropertyEnum *terrain_set_property_editor = nullptr; EditorPropertyEnum *terrain_property_editor = nullptr; - void _property_value_changed(StringName p_property, Variant p_value, StringName p_field); + void _property_value_changed(const StringName &p_property, Variant p_value, const StringName &p_field); void _update_terrain_selector(); @@ -405,7 +405,7 @@ private: virtual void _set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override; virtual void _set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) override; virtual Variant _get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override; - virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, HashMap<TileMapCell, Variant, TileMapCell> p_previous_values, Variant p_new_value) override; + virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, const HashMap<TileMapCell, Variant, TileMapCell> &p_previous_values, Variant p_new_value) override; protected: virtual void _tile_set_changed() override; diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp index 661af16ce8..c0aa5b8efa 100644 --- a/editor/plugins/tiles/tile_map_editor.cpp +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -2421,6 +2421,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { sources_bottom_actions->add_child(source_sort_button); sources_list = memnew(ItemList); + sources_list->set_auto_translate(false); sources_list->set_fixed_icon_size(Size2(60, 60) * EDSCALE); sources_list->set_h_size_flags(Control::SIZE_EXPAND_FILL); sources_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); @@ -2460,6 +2461,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { // Scenes collection source. scene_tiles_list = memnew(ItemList); + scene_tiles_list->set_auto_translate(false); scene_tiles_list->set_h_size_flags(Control::SIZE_EXPAND_FILL); scene_tiles_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); scene_tiles_list->set_select_mode(ItemList::SELECT_MULTI); @@ -2485,6 +2487,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { int thumbnail_size = 64; patterns_item_list = memnew(ItemList); + patterns_item_list->set_auto_translate(false); patterns_item_list->set_max_columns(0); patterns_item_list->set_icon_mode(ItemList::ICON_MODE_TOP); patterns_item_list->set_fixed_column_width(thumbnail_size * 3 / 2); @@ -3560,6 +3563,7 @@ TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() { tilemap_tab_terrains->add_child(terrains_tree); terrains_tile_list = memnew(ItemList); + terrains_tile_list->set_auto_translate(false); terrains_tile_list->set_h_size_flags(Control::SIZE_EXPAND_FILL); terrains_tile_list->set_max_columns(0); terrains_tile_list->set_same_column_width(true); diff --git a/editor/plugins/tiles/tile_proxies_manager_dialog.cpp b/editor/plugins/tiles/tile_proxies_manager_dialog.cpp index 0e244412ab..ba431dfa2f 100644 --- a/editor/plugins/tiles/tile_proxies_manager_dialog.cpp +++ b/editor/plugins/tiles/tile_proxies_manager_dialog.cpp @@ -345,6 +345,7 @@ TileProxiesManagerDialog::TileProxiesManagerDialog() { vbox_container->add_child(source_level_label); source_level_list = memnew(ItemList); + source_level_list->set_auto_translate(false); source_level_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); source_level_list->set_select_mode(ItemList::SELECT_MULTI); source_level_list->set_allow_rmb_select(true); @@ -356,6 +357,7 @@ TileProxiesManagerDialog::TileProxiesManagerDialog() { vbox_container->add_child(coords_level_label); coords_level_list = memnew(ItemList); + coords_level_list->set_auto_translate(false); coords_level_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); coords_level_list->set_select_mode(ItemList::SELECT_MULTI); coords_level_list->set_allow_rmb_select(true); @@ -367,6 +369,7 @@ TileProxiesManagerDialog::TileProxiesManagerDialog() { vbox_container->add_child(alternative_level_label); alternative_level_list = memnew(ItemList); + alternative_level_list->set_auto_translate(false); alternative_level_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); alternative_level_list->set_select_mode(ItemList::SELECT_MULTI); alternative_level_list->set_allow_rmb_select(true); diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index a3fc6aa5f7..895df177ef 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -507,7 +507,7 @@ void TileSetAtlasSourceEditor::AtlasTileProxyObject::_get_property_list(List<Pro } } -void TileSetAtlasSourceEditor::AtlasTileProxyObject::edit(Ref<TileSetAtlasSource> p_tile_set_atlas_source, RBSet<TileSelection> p_tiles) { +void TileSetAtlasSourceEditor::AtlasTileProxyObject::edit(Ref<TileSetAtlasSource> p_tile_set_atlas_source, const RBSet<TileSelection> &p_tiles) { ERR_FAIL_COND(!p_tile_set_atlas_source.is_valid()); ERR_FAIL_COND(p_tiles.is_empty()); for (const TileSelection &E : p_tiles) { diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.h b/editor/plugins/tiles/tile_set_atlas_source_editor.h index 7f6bab804d..e1e6a3113c 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.h +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.h @@ -105,7 +105,7 @@ public: RBSet<TileSelection> get_edited_tiles() const { return tiles; }; // Update the proxyed object. - void edit(Ref<TileSetAtlasSource> p_tile_set_atlas_source, RBSet<TileSelection> p_tiles = RBSet<TileSelection>()); + void edit(Ref<TileSetAtlasSource> p_tile_set_atlas_source, const RBSet<TileSelection> &p_tiles = RBSet<TileSelection>()); AtlasTileProxyObject(TileSetAtlasSourceEditor *p_tiles_set_atlas_source_editor) { tiles_set_atlas_source_editor = p_tiles_set_atlas_source_editor; diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp index 7242920368..bdde662ce1 100644 --- a/editor/plugins/tiles/tile_set_editor.cpp +++ b/editor/plugins/tiles/tile_set_editor.cpp @@ -854,6 +854,7 @@ TileSetEditor::TileSetEditor() { p->set_item_checked(TilesEditorUtils::SOURCE_SORT_ID, true); sources_list = memnew(ItemList); + sources_list->set_auto_translate(false); sources_list->set_fixed_icon_size(Size2(60, 60) * EDSCALE); sources_list->set_h_size_flags(SIZE_EXPAND_FILL); sources_list->set_v_size_flags(SIZE_EXPAND_FILL); @@ -933,6 +934,7 @@ TileSetEditor::TileSetEditor() { //// Patterns //// int thumbnail_size = 64; patterns_item_list = memnew(ItemList); + patterns_item_list->set_auto_translate(false); patterns_item_list->set_max_columns(0); patterns_item_list->set_icon_mode(ItemList::ICON_MODE_TOP); patterns_item_list->set_fixed_column_width(thumbnail_size * 3 / 2); diff --git a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp index 1f4c3651e9..7f6616fe34 100644 --- a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp @@ -549,6 +549,7 @@ TileSetScenesCollectionSourceEditor::TileSetScenesCollectionSourceEditor() { split_container_right_side->add_child(right_vbox_container); scene_tiles_list = memnew(ItemList); + scene_tiles_list->set_auto_translate(false); scene_tiles_list->set_h_size_flags(SIZE_EXPAND_FILL); scene_tiles_list->set_v_size_flags(SIZE_EXPAND_FILL); SET_DRAG_FORWARDING_CDU(scene_tiles_list, TileSetScenesCollectionSourceEditor); diff --git a/editor/pot_generator.cpp b/editor/pot_generator.cpp index 93c446c29a..3804bd8d5b 100644 --- a/editor/pot_generator.cpp +++ b/editor/pot_generator.cpp @@ -69,7 +69,7 @@ void POTGenerator::generate_pot(const String &p_file) { for (int i = 0; i < files.size(); i++) { Vector<String> msgids; Vector<Vector<String>> msgids_context_plural; - String file_path = files[i]; + const String &file_path = files[i]; String file_extension = file_path.get_extension(); if (EditorTranslationParser::get_singleton()->can_parse(file_extension)) { @@ -80,7 +80,7 @@ void POTGenerator::generate_pot(const String &p_file) { } for (int j = 0; j < msgids_context_plural.size(); j++) { - Vector<String> entry = msgids_context_plural[j]; + const Vector<String> &entry = msgids_context_plural[j]; _add_new_msgid(entry[0], entry[1], entry[2], file_path); } for (int j = 0; j < msgids.size(); j++) { diff --git a/editor/project_converter_3_to_4.cpp b/editor/project_converter_3_to_4.cpp index 46d9a02e94..2b0f61c1b0 100644 --- a/editor/project_converter_3_to_4.cpp +++ b/editor/project_converter_3_to_4.cpp @@ -561,7 +561,7 @@ bool ProjectConverter3To4::validate_conversion() { // Check file by file. for (int i = 0; i < collected_files.size(); i++) { - String file_name = collected_files[i]; + const String &file_name = collected_files[i]; Vector<String> lines; uint32_t ignored_lines = 0; uint64_t file_size = 0; @@ -1045,107 +1045,108 @@ bool ProjectConverter3To4::test_conversion(RegExContainer ®_container) { // get_object_of_execution { - { String base = "var roman = kieliszek."; - String expected = "kieliszek."; - String got = get_object_of_execution(base); - if (got != expected) { - ERR_PRINT(vformat("Failed to get proper data from get_object_of_execution. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", base, expected, expected.size(), got, got.size())); + String base = "var roman = kieliszek."; + String expected = "kieliszek."; + String got = get_object_of_execution(base); + if (got != expected) { + ERR_PRINT(vformat("Failed to get proper data from get_object_of_execution. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", base, expected, expected.size(), got, got.size())); + } + valid = valid && (got == expected); + } + { + String base = "r."; + String expected = "r."; + String got = get_object_of_execution(base); + if (got != expected) { + ERR_PRINT(vformat("Failed to get proper data from get_object_of_execution. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", base, expected, expected.size(), got, got.size())); + } + valid = valid && (got == expected); + } + { + String base = "mortadela("; + String expected = ""; + String got = get_object_of_execution(base); + if (got != expected) { + ERR_PRINT(vformat("Failed to get proper data from get_object_of_execution. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", base, expected, expected.size(), got, got.size())); + } + valid = valid && (got == expected); + } + { + String base = "var node = $world/ukraine/lviv."; + String expected = "$world/ukraine/lviv."; + String got = get_object_of_execution(base); + if (got != expected) { + ERR_PRINT(vformat("Failed to get proper data from get_object_of_execution. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", base, expected, expected.size(), got, got.size())); + } + valid = valid && (got == expected); } - valid = valid && (got == expected); -} -{ - String base = "r."; - String expected = "r."; - String got = get_object_of_execution(base); - if (got != expected) { - ERR_PRINT(vformat("Failed to get proper data from get_object_of_execution. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", base, expected, expected.size(), got, got.size())); - } - valid = valid && (got == expected); -} -{ - String base = "mortadela("; - String expected = ""; - String got = get_object_of_execution(base); - if (got != expected) { - ERR_PRINT(vformat("Failed to get proper data from get_object_of_execution. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", base, expected, expected.size(), got, got.size())); - } - valid = valid && (got == expected); -} -{ - String base = "var node = $world/ukraine/lviv."; - String expected = "$world/ukraine/lviv."; - String got = get_object_of_execution(base); - if (got != expected) { - ERR_PRINT(vformat("Failed to get proper data from get_object_of_execution. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", base, expected, expected.size(), got, got.size())); - } - valid = valid && (got == expected); -} -} -// get_starting_space -{ - String base = "\t\t\t var roman = kieliszek."; - String expected = "\t\t\t"; - String got = get_starting_space(base); - if (got != expected) { - ERR_PRINT(vformat("Failed to get proper data from get_object_of_execution. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", base, expected, expected.size(), got, got.size())); - } - valid = valid && (got == expected); -} -// Parse Arguments -{ - String line = "( )"; - Vector<String> got_vector = parse_arguments(line); - String got = ""; - String expected = ""; - for (String &part : got_vector) { - got += part + "|||"; - } - if (got != expected) { - ERR_PRINT(vformat("Failed to get proper data from parse_arguments. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", line, expected, expected.size(), got, got.size())); - } - valid = valid && (got == expected); -} -{ - String line = "(a , b , c)"; - Vector<String> got_vector = parse_arguments(line); - String got = ""; - String expected = "a|||b|||c|||"; - for (String &part : got_vector) { - got += part + "|||"; - } - if (got != expected) { - ERR_PRINT(vformat("Failed to get proper data from parse_arguments. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", line, expected, expected.size(), got, got.size())); - } - valid = valid && (got == expected); -} -{ - String line = "(a , \"b,\" , c)"; - Vector<String> got_vector = parse_arguments(line); - String got = ""; - String expected = "a|||\"b,\"|||c|||"; - for (String &part : got_vector) { - got += part + "|||"; - } - if (got != expected) { - ERR_PRINT(vformat("Failed to get proper data from parse_arguments. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", line, expected, expected.size(), got, got.size())); - } - valid = valid && (got == expected); -} -{ - String line = "(a , \"(,),,,,\" , c)"; - Vector<String> got_vector = parse_arguments(line); - String got = ""; - String expected = "a|||\"(,),,,,\"|||c|||"; - for (String &part : got_vector) { - got += part + "|||"; - } - if (got != expected) { - ERR_PRINT(vformat("Failed to get proper data from parse_arguments. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", line, expected, expected.size(), got, got.size())); - } - valid = valid && (got == expected); -} -return valid; + // get_starting_space + { + String base = "\t\t\t var roman = kieliszek."; + String expected = "\t\t\t"; + String got = get_starting_space(base); + if (got != expected) { + ERR_PRINT(vformat("Failed to get proper data from get_object_of_execution. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", base, expected, expected.size(), got, got.size())); + } + valid = valid && (got == expected); + } + + // Parse Arguments + { + String line = "( )"; + Vector<String> got_vector = parse_arguments(line); + String got = ""; + String expected = ""; + for (String &part : got_vector) { + got += part + "|||"; + } + if (got != expected) { + ERR_PRINT(vformat("Failed to get proper data from parse_arguments. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", line, expected, expected.size(), got, got.size())); + } + valid = valid && (got == expected); + } + { + String line = "(a , b , c)"; + Vector<String> got_vector = parse_arguments(line); + String got = ""; + String expected = "a|||b|||c|||"; + for (String &part : got_vector) { + got += part + "|||"; + } + if (got != expected) { + ERR_PRINT(vformat("Failed to get proper data from parse_arguments. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", line, expected, expected.size(), got, got.size())); + } + valid = valid && (got == expected); + } + { + String line = "(a , \"b,\" , c)"; + Vector<String> got_vector = parse_arguments(line); + String got = ""; + String expected = "a|||\"b,\"|||c|||"; + for (String &part : got_vector) { + got += part + "|||"; + } + if (got != expected) { + ERR_PRINT(vformat("Failed to get proper data from parse_arguments. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", line, expected, expected.size(), got, got.size())); + } + valid = valid && (got == expected); + } + { + String line = "(a , \"(,),,,,\" , c)"; + Vector<String> got_vector = parse_arguments(line); + String got = ""; + String expected = "a|||\"(,),,,,\"|||c|||"; + for (String &part : got_vector) { + got += part + "|||"; + } + if (got != expected) { + ERR_PRINT(vformat("Failed to get proper data from parse_arguments. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", line, expected, expected.size(), got, got.size())); + } + valid = valid && (got == expected); + } + + return valid; } // Validate in all arrays if names don't do cyclic renames "Node" -> "Node2D" | "Node2D" -> "2DNode" @@ -2733,7 +2734,7 @@ void ProjectConverter3To4::rename_joypad_buttons_and_axes(Vector<SourceLine> &so for (int i = 0; i < reg_match.size(); ++i) { Ref<RegExMatch> match = reg_match[i]; PackedStringArray strings = match->get_strings(); - String button_index_entry = strings[0]; + const String &button_index_entry = strings[0]; int button_index_value = strings[1].to_int(); if (button_index_value == 6) { // L2 and R2 are mapped to joypad axes in Godot 4. line = line.replace("InputEventJoypadButton", "InputEventJoypadMotion"); @@ -2742,7 +2743,7 @@ void ProjectConverter3To4::rename_joypad_buttons_and_axes(Vector<SourceLine> &so line = line.replace("InputEventJoypadButton", "InputEventJoypadMotion"); line = line.replace(button_index_entry, ",\"axis\":5,\"axis_value\":1.0"); } else if (button_index_value < 22) { // There are no mappings for indexes greater than 22 in both Godot 3 & 4. - String pressure_and_pressed_properties = strings[2]; + const String &pressure_and_pressed_properties = strings[2]; line = line.replace(button_index_entry, ",\"button_index\":" + String::num_int64(reg_container.joypad_button_mappings[button_index_value]) + "," + pressure_and_pressed_properties); } } @@ -2751,7 +2752,7 @@ void ProjectConverter3To4::rename_joypad_buttons_and_axes(Vector<SourceLine> &so for (int i = 0; i < reg_match.size(); ++i) { Ref<RegExMatch> match = reg_match[i]; PackedStringArray strings = match->get_strings(); - String axis_entry = strings[0]; + const String &axis_entry = strings[0]; int axis_value = strings[1].to_int(); if (axis_value == 6) { line = line.replace(axis_entry, ",\"axis\":4"); @@ -2773,7 +2774,7 @@ Vector<String> ProjectConverter3To4::check_for_rename_joypad_buttons_and_axes(Ve for (int i = 0; i < reg_match.size(); ++i) { Ref<RegExMatch> match = reg_match[i]; PackedStringArray strings = match->get_strings(); - String button_index_entry = strings[0]; + const String &button_index_entry = strings[0]; int button_index_value = strings[1].to_int(); if (button_index_value == 6) { // L2 and R2 are mapped to joypad axes in Godot 4. found_renames.append(line_formatter(current_line, "InputEventJoypadButton", "InputEventJoypadMotion", line)); @@ -2790,7 +2791,7 @@ Vector<String> ProjectConverter3To4::check_for_rename_joypad_buttons_and_axes(Ve for (int i = 0; i < reg_match.size(); ++i) { Ref<RegExMatch> match = reg_match[i]; PackedStringArray strings = match->get_strings(); - String axis_entry = strings[0]; + const String &axis_entry = strings[0]; int axis_value = strings[1].to_int(); if (axis_value == 6) { found_renames.append(line_formatter(current_line, axis_entry, ",\"axis\":4", line)); diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index f74458e89b..a0ff858727 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -103,21 +103,25 @@ void ProjectDialog::_set_message(const String &p_msg, MessageType p_type, InputT } } +static bool is_zip_file(Ref<DirAccess> p_d, const String &p_path) { + return p_path.ends_with(".zip") && p_d->file_exists(p_path); +} + String ProjectDialog::_test_path() { Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + const String base_path = project_path->get_text(); String valid_path, valid_install_path; - if (d->change_dir(project_path->get_text()) == OK) { - valid_path = project_path->get_text(); - } else if (d->change_dir(project_path->get_text().strip_edges()) == OK) { - valid_path = project_path->get_text().strip_edges(); - } else if (project_path->get_text().ends_with(".zip")) { - if (d->file_exists(project_path->get_text())) { - valid_path = project_path->get_text(); - } - } else if (project_path->get_text().strip_edges().ends_with(".zip")) { - if (d->file_exists(project_path->get_text().strip_edges())) { - valid_path = project_path->get_text().strip_edges(); - } + bool is_zip = false; + if (d->change_dir(base_path) == OK) { + valid_path = base_path; + } else if (is_zip_file(d, base_path)) { + valid_path = base_path; + is_zip = true; + } else if (d->change_dir(base_path.strip_edges()) == OK) { + valid_path = base_path.strip_edges(); + } else if (is_zip_file(d, base_path.strip_edges())) { + valid_path = base_path.strip_edges(); + is_zip = true; } if (valid_path.is_empty()) { @@ -126,7 +130,7 @@ String ProjectDialog::_test_path() { return ""; } - if (mode == MODE_IMPORT && valid_path.ends_with(".zip")) { + if (mode == MODE_IMPORT && is_zip) { if (d->change_dir(install_path->get_text()) == OK) { valid_install_path = install_path->get_text(); } else if (d->change_dir(install_path->get_text().strip_edges()) == OK) { @@ -134,15 +138,15 @@ String ProjectDialog::_test_path() { } if (valid_install_path.is_empty()) { - _set_message(TTR("The path specified doesn't exist."), MESSAGE_ERROR, INSTALL_PATH); + _set_message(TTR("The install path specified doesn't exist."), MESSAGE_ERROR, INSTALL_PATH); get_ok_button()->set_disabled(true); return ""; } } if (mode == MODE_IMPORT || mode == MODE_RENAME) { - if (!valid_path.is_empty() && !d->file_exists("project.godot")) { - if (valid_path.ends_with(".zip")) { + if (!d->file_exists("project.godot")) { + if (is_zip) { Ref<FileAccess> io_fa; zlib_filefunc_def io = zipio_create_io(&io_fa); @@ -197,7 +201,7 @@ String ProjectDialog::_test_path() { d->list_dir_end(); if (!is_folder_empty) { - _set_message(TTR("Please choose an empty folder."), MESSAGE_WARNING, INSTALL_PATH); + _set_message(TTR("Please choose an empty install folder."), MESSAGE_WARNING, INSTALL_PATH); get_ok_button()->set_disabled(true); return ""; } @@ -209,8 +213,8 @@ String ProjectDialog::_test_path() { return ""; } - } else if (valid_path.ends_with("zip")) { - _set_message(TTR("This directory already contains a Godot project."), MESSAGE_ERROR, INSTALL_PATH); + } else if (is_zip) { + _set_message(TTR("The install directory already contains a Godot project."), MESSAGE_ERROR, INSTALL_PATH); get_ok_button()->set_disabled(true); return ""; } @@ -252,7 +256,7 @@ String ProjectDialog::_test_path() { return valid_path; } -void ProjectDialog::_path_text_changed(const String &p_path) { +void ProjectDialog::_update_path(const String &p_path) { String sp = _test_path(); if (!sp.is_empty()) { // If the project name is empty or default, infer the project name from the selected folder name @@ -277,6 +281,21 @@ void ProjectDialog::_path_text_changed(const String &p_path) { } } +void ProjectDialog::_path_text_changed(const String &p_path) { + Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); + if (mode == MODE_IMPORT && is_zip_file(d, p_path)) { + install_path->set_text(p_path.get_base_dir()); + install_path_container->show(); + } else if (mode == MODE_IMPORT && is_zip_file(d, p_path.strip_edges())) { + install_path->set_text(p_path.strip_edges().get_base_dir()); + install_path_container->show(); + } else { + install_path_container->hide(); + } + + _update_path(p_path.simplify_path()); +} + void ProjectDialog::_file_selected(const String &p_path) { // If not already shown. show_dialog(); @@ -300,7 +319,7 @@ void ProjectDialog::_file_selected(const String &p_path) { String sp = p.simplify_path(); project_path->set_text(sp); - _path_text_changed(sp); + _update_path(sp); if (p.ends_with(".zip")) { install_path->call_deferred(SNAME("grab_focus")); } else { @@ -314,14 +333,14 @@ void ProjectDialog::_path_selected(const String &p_path) { String sp = p_path.simplify_path(); project_path->set_text(sp); - _path_text_changed(sp); + _update_path(sp); get_ok_button()->call_deferred(SNAME("grab_focus")); } void ProjectDialog::_install_path_selected(const String &p_path) { String sp = p_path.simplify_path(); install_path->set_text(sp); - _path_text_changed(sp); + _update_path(sp); get_ok_button()->call_deferred(SNAME("grab_focus")); } @@ -359,7 +378,7 @@ void ProjectDialog::_create_folder() { d->change_dir(project_name_no_edges); String dir_str = d->get_current_dir(); project_path->set_text(dir_str); - _path_text_changed(dir_str); + _update_path(dir_str); created_folder_path = d->get_current_dir(); create_dir->set_disabled(true); } else { @@ -638,7 +657,7 @@ void ProjectDialog::cancel_pressed() { _remove_created_folder(); project_path->clear(); - _path_text_changed(""); + _update_path(""); project_name->clear(); _text_changed(""); @@ -968,7 +987,7 @@ ProjectDialog::ProjectDialog() { project_name->connect("text_changed", callable_mp(this, &ProjectDialog::_text_changed)); project_path->connect("text_changed", callable_mp(this, &ProjectDialog::_path_text_changed)); - install_path->connect("text_changed", callable_mp(this, &ProjectDialog::_path_text_changed)); + install_path->connect("text_changed", callable_mp(this, &ProjectDialog::_update_path)); fdialog->connect("dir_selected", callable_mp(this, &ProjectDialog::_path_selected)); fdialog->connect("file_selected", callable_mp(this, &ProjectDialog::_file_selected)); fdialog_install->connect("dir_selected", callable_mp(this, &ProjectDialog::_install_path_selected)); @@ -3074,7 +3093,7 @@ ProjectManager::ProjectManager() { language_btn->set_text(current_lang); for (int i = 0; i < editor_languages.size(); i++) { - String lang = editor_languages[i]; + const String &lang = editor_languages[i]; String lang_name = TranslationServer::get_singleton()->get_locale_name(lang); language_btn->add_item(vformat("[%s] %s", lang, lang_name), i); language_btn->set_item_metadata(i, lang); diff --git a/editor/project_manager.h b/editor/project_manager.h index 8a8b2ff99d..7b091050bd 100644 --- a/editor/project_manager.h +++ b/editor/project_manager.h @@ -104,6 +104,7 @@ private: void _set_message(const String &p_msg, MessageType p_type = MESSAGE_SUCCESS, InputType input_type = PROJECT_PATH); String _test_path(); + void _update_path(const String &p_path); void _path_text_changed(const String &p_path); void _path_selected(const String &p_path); void _file_selected(const String &p_path); diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index d587737ed4..b7a5521ffd 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -70,6 +70,8 @@ void ProjectSettingsEditor::popup_project_settings(bool p_clear_filter) { if (p_clear_filter) { search_box->clear(); } + + _focus_current_search_box(); } void ProjectSettingsEditor::queue_save() { @@ -173,7 +175,7 @@ void ProjectSettingsEditor::_feature_selected(int p_index) { void ProjectSettingsEditor::_update_property_box() { const String setting = _get_setting_name(); const Vector<String> t = setting.split(".", true, 1); - const String name = t[0]; + const String &name = t[0]; const String feature = (t.size() == 2) ? t[1] : ""; bool feature_invalid = (t.size() == 2) && (t[1].is_empty()); @@ -255,8 +257,7 @@ void ProjectSettingsEditor::shortcut_input(const Ref<InputEvent> &p_event) { } if (k->is_match(InputEventKey::create_reference(KeyModifierMask::CMD_OR_CTRL | Key::F))) { - search_box->grab_focus(); - search_box->select_all(); + _focus_current_search_box(); handled = true; } @@ -328,6 +329,25 @@ void ProjectSettingsEditor::_add_feature_overrides() { } } +void ProjectSettingsEditor::_tabs_tab_changed(int p_tab) { + _focus_current_search_box(); +} + +void ProjectSettingsEditor::_focus_current_search_box() { + Control *tab = tab_container->get_current_tab_control(); + LineEdit *current_search_box = nullptr; + if (tab == general_editor) { + current_search_box = search_box; + } else if (tab == action_map_editor) { + current_search_box = action_map_editor->get_search_box(); + } + + if (current_search_box) { + current_search_box->grab_focus(); + current_search_box->select_all(); + } +} + void ProjectSettingsEditor::_editor_restart() { ProjectSettings::get_singleton()->save(); EditorNode::get_singleton()->save_all_scenes(); @@ -598,6 +618,7 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) { tab_container = memnew(TabContainer); tab_container->set_use_hidden_tabs_for_min_size(true); tab_container->set_theme_type_variation("TabContainerOdd"); + tab_container->connect("tab_changed", callable_mp(this, &ProjectSettingsEditor::_tabs_tab_changed)); add_child(tab_container); general_editor = memnew(VBoxContainer); diff --git a/editor/project_settings_editor.h b/editor/project_settings_editor.h index 1f18c68acc..7771bdda61 100644 --- a/editor/project_settings_editor.h +++ b/editor/project_settings_editor.h @@ -95,6 +95,9 @@ class ProjectSettingsEditor : public AcceptDialog { void _add_setting(); void _delete_setting(); + void _tabs_tab_changed(int p_tab); + void _focus_current_search_box(); + void _editor_restart_request(); void _editor_restart(); void _editor_restart_close(); diff --git a/editor/register_editor_types.cpp b/editor/register_editor_types.cpp index 075c856c1c..04a06ff732 100644 --- a/editor/register_editor_types.cpp +++ b/editor/register_editor_types.cpp @@ -50,6 +50,8 @@ #include "editor/filesystem_dock.h" #include "editor/gui/editor_file_dialog.h" #include "editor/gui/editor_spin_slider.h" +#include "editor/import/3d/resource_importer_obj.h" +#include "editor/import/3d/resource_importer_scene.h" #include "editor/import/editor_import_plugin.h" #include "editor/import/resource_importer_bitmask.h" #include "editor/import/resource_importer_bmfont.h" @@ -58,8 +60,6 @@ #include "editor/import/resource_importer_image.h" #include "editor/import/resource_importer_imagefont.h" #include "editor/import/resource_importer_layered_texture.h" -#include "editor/import/resource_importer_obj.h" -#include "editor/import/resource_importer_scene.h" #include "editor/import/resource_importer_shader_file.h" #include "editor/import/resource_importer_texture.h" #include "editor/import/resource_importer_texture_atlas.h" diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 4e26fbd6cf..4a5358e435 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -1243,7 +1243,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { if (cant_be_set_unique_names.size()) { String popup_text = TTR("Unique names already used by another node in the scene:"); popup_text += "\n"; - for (StringName name : cant_be_set_unique_names) { + for (const StringName &name : cant_be_set_unique_names) { popup_text += "\n" + String(name); } accept->set_text(popup_text); @@ -1565,7 +1565,9 @@ void SceneTreeDock::_load_request(const String &p_path) { } void SceneTreeDock::_script_open_request(const Ref<Script> &p_script) { - EditorNode::get_singleton()->edit_resource(p_script); + if (ScriptEditor::get_singleton()->edit(p_script, true)) { + EditorNode::get_singleton()->editor_select(EditorNode::EDITOR_SCRIPT); + } } void SceneTreeDock::_push_item(Object *p_object) { @@ -2949,7 +2951,7 @@ void SceneTreeDock::_files_dropped(Vector<String> p_files, NodePath p_to, int p_ _normalize_drop(node, to_pos, p_type); _perform_instantiate_scenes(p_files, node, to_pos); } else { - String res_path = p_files[0]; + const String &res_path = p_files[0]; StringName res_type = EditorFileSystem::get_singleton()->get_file_type(res_path); List<String> valid_properties; @@ -3065,6 +3067,9 @@ void SceneTreeDock::_nodes_dragged(Array p_nodes, NodePath p_to, int p_type) { _normalize_drop(to_node, to_pos, p_type); _do_reparent(to_node, to_pos, nodes, !Input::get_singleton()->is_key_pressed(Key::SHIFT)); + for (Node *E : nodes) { + editor_selection->add_node(E); + } } void SceneTreeDock::_add_children_to_popup(Object *p_obj, int p_depth) { diff --git a/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj b/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj index e5f7ca1144..bd2560c280 100644 --- a/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj +++ b/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj @@ -254,7 +254,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = $min_version; "LD_CLASSIC_1000" = ""; "LD_CLASSIC_1100" = ""; "LD_CLASSIC_1200" = ""; @@ -299,7 +299,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = $min_version; "LD_CLASSIC_1000" = ""; "LD_CLASSIC_1100" = ""; "LD_CLASSIC_1200" = ""; @@ -325,7 +325,7 @@ CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)"; DEVELOPMENT_TEAM = $team_id; INFOPLIST_FILE = "$binary/$binary-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = $min_version; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -360,7 +360,7 @@ CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)"; DEVELOPMENT_TEAM = $team_id; INFOPLIST_FILE = "$binary/$binary-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = $min_version; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/misc/hooks/README.md b/misc/hooks/README.md index 0aaeff8ae3..573f8fe350 100644 --- a/misc/hooks/README.md +++ b/misc/hooks/README.md @@ -29,7 +29,7 @@ so they should work out of the box on Linux/macOS. ##### clang-format - Download LLVM for Windows (version 13 or later) from - <https://releases.llvm.org/download.html> + <https://github.com/llvm/llvm-project/releases> - Make sure LLVM is added to the `PATH` during installation ##### black diff --git a/misc/hooks/pre-commit-clang-format b/misc/hooks/pre-commit-clang-format index fd0213c175..e5cf0c4aa2 100755 --- a/misc/hooks/pre-commit-clang-format +++ b/misc/hooks/pre-commit-clang-format @@ -80,7 +80,7 @@ fi # To get consistent formatting, we recommend contributors to use the same # clang-format version as CI. RECOMMENDED_CLANG_FORMAT_MAJOR_MIN="13" -RECOMMENDED_CLANG_FORMAT_MAJOR_MAX="15" +RECOMMENDED_CLANG_FORMAT_MAJOR_MAX="16" if [ ! -x "$CLANG_FORMAT" ] ; then message="Error: clang-format executable not found. Please install clang-format $RECOMMENDED_CLANG_FORMAT_MAJOR_MAX." diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index a999acd1bd..1f0830aa17 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -1381,51 +1381,43 @@ String GDScript::debug_get_script_name(const Ref<Script> &p_script) { } #endif -GDScript::UpdatableFuncPtr GDScript::func_ptrs_to_update_main_thread; -thread_local GDScript::UpdatableFuncPtr *GDScript::func_ptrs_to_update_thread_local = nullptr; - -GDScript::UpdatableFuncPtrElement GDScript::_add_func_ptr_to_update(GDScriptFunction **p_func_ptr_ptr) { - UpdatableFuncPtrElement result = {}; - - { - MutexLock lock(func_ptrs_to_update_thread_local->mutex); - result.element = func_ptrs_to_update_thread_local->ptrs.push_back(p_func_ptr_ptr); - result.func_ptr = func_ptrs_to_update_thread_local; - - if (likely(func_ptrs_to_update_thread_local->initialized)) { - return result; - } - - func_ptrs_to_update_thread_local->initialized = true; +GDScript::UpdatableFuncPtr::UpdatableFuncPtr(GDScriptFunction *p_function) { + if (p_function == nullptr) { + return; } - MutexLock lock(func_ptrs_to_update_mutex); - func_ptrs_to_update.push_back(func_ptrs_to_update_thread_local); - func_ptrs_to_update_thread_local->rc++; - - return result; -} + ptr = p_function; + script = ptr->get_script(); + ERR_FAIL_NULL(script); -void GDScript::_remove_func_ptr_to_update(const UpdatableFuncPtrElement &p_func_ptr_element) { - ERR_FAIL_NULL(p_func_ptr_element.element); - ERR_FAIL_NULL(p_func_ptr_element.func_ptr); - MutexLock lock(p_func_ptr_element.func_ptr->mutex); - p_func_ptr_element.element->erase(); + MutexLock script_lock(script->func_ptrs_to_update_mutex); + list_element = script->func_ptrs_to_update.push_back(this); } -void GDScript::_fixup_thread_function_bookkeeping() { - // Transfer the ownership of these update items to the main thread, - // because the current one is dying, leaving theirs orphan, dangling. +GDScript::UpdatableFuncPtr::~UpdatableFuncPtr() { + ERR_FAIL_NULL(script); - DEV_ASSERT(!Thread::is_main_thread()); + if (list_element) { + MutexLock script_lock(script->func_ptrs_to_update_mutex); + list_element->erase(); + list_element = nullptr; + } +} - MutexLock lock(func_ptrs_to_update_main_thread.mutex); - MutexLock lock2(func_ptrs_to_update_thread_local->mutex); +void GDScript::_recurse_replace_function_ptrs(const HashMap<GDScriptFunction *, GDScriptFunction *> &p_replacements) const { + MutexLock lock(func_ptrs_to_update_mutex); + for (UpdatableFuncPtr *updatable : func_ptrs_to_update) { + HashMap<GDScriptFunction *, GDScriptFunction *>::ConstIterator replacement = p_replacements.find(updatable->ptr); + if (replacement) { + updatable->ptr = replacement->value; + } else { + // Probably a lambda from another reload, ignore. + updatable->ptr = nullptr; + } + } - while (!func_ptrs_to_update_thread_local->ptrs.is_empty()) { - List<GDScriptFunction **>::Element *E = func_ptrs_to_update_thread_local->ptrs.front(); - E->transfer_to_back(&func_ptrs_to_update_main_thread.ptrs); - func_ptrs_to_update_thread_local->transferred = true; + for (HashMap<StringName, Ref<GDScript>>::ConstIterator subscript = subclasses.begin(); subscript; ++subscript) { + subscript->value->_recurse_replace_function_ptrs(p_replacements); } } @@ -1447,30 +1439,9 @@ void GDScript::clear(ClearData *p_clear_data) { } { - MutexLock outer_lock(func_ptrs_to_update_mutex); + MutexLock lock(func_ptrs_to_update_mutex); for (UpdatableFuncPtr *updatable : func_ptrs_to_update) { - bool destroy = false; - { - MutexLock inner_lock(updatable->mutex); - if (updatable->transferred) { - func_ptrs_to_update_main_thread.mutex.lock(); - } - for (GDScriptFunction **func_ptr_ptr : updatable->ptrs) { - *func_ptr_ptr = nullptr; - } - DEV_ASSERT(updatable->rc != 0); - updatable->rc--; - if (updatable->rc == 0) { - destroy = true; - } - if (updatable->transferred) { - func_ptrs_to_update_main_thread.mutex.unlock(); - } - } - if (destroy) { - DEV_ASSERT(updatable != &func_ptrs_to_update_main_thread); - memdelete(updatable); - } + updatable->ptr = nullptr; } } @@ -1543,6 +1514,13 @@ GDScript::~GDScript() { } destructing = true; + if (is_print_verbose_enabled()) { + MutexLock lock(func_ptrs_to_update_mutex); + if (!func_ptrs_to_update.is_empty()) { + print_line(vformat("GDScript: %d orphaned lambdas becoming invalid at destruction of script '%s'.", func_ptrs_to_update.size(), fully_qualified_name)); + } + } + clear(); { @@ -2091,33 +2069,6 @@ void GDScriptLanguage::remove_named_global_constant(const StringName &p_name) { named_globals.erase(p_name); } -void GDScriptLanguage::thread_enter() { - GDScript::func_ptrs_to_update_thread_local = memnew(GDScript::UpdatableFuncPtr); -} - -void GDScriptLanguage::thread_exit() { - // This thread may have been created before GDScript was up - // (which also means it can't have run any GDScript code at all). - if (!GDScript::func_ptrs_to_update_thread_local) { - return; - } - - GDScript::_fixup_thread_function_bookkeeping(); - - bool destroy = false; - { - MutexLock lock(GDScript::func_ptrs_to_update_thread_local->mutex); - DEV_ASSERT(GDScript::func_ptrs_to_update_thread_local->rc != 0); - GDScript::func_ptrs_to_update_thread_local->rc--; - if (GDScript::func_ptrs_to_update_thread_local->rc == 0) { - destroy = true; - } - } - if (destroy) { - memdelete(GDScript::func_ptrs_to_update_thread_local); - } -} - void GDScriptLanguage::init() { //populate global constants int gcc = CoreConstants::get_global_constant_count(); @@ -2150,8 +2101,6 @@ void GDScriptLanguage::init() { _add_global(E.name, E.ptr); } - GDScript::func_ptrs_to_update_thread_local = &GDScript::func_ptrs_to_update_main_thread; - #ifdef TESTS_ENABLED GDScriptTests::GDScriptTestRunner::handle_cmdline(); #endif @@ -2201,8 +2150,6 @@ void GDScriptLanguage::finish() { } script_list.clear(); function_list.clear(); - - DEV_ASSERT(GDScript::func_ptrs_to_update_main_thread.rc == 1); } void GDScriptLanguage::profiling_start() { diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 31811bba47..7b0e2136ed 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -117,29 +117,28 @@ class GDScript : public Script { HashMap<GDScriptFunction *, LambdaInfo> lambda_info; - // List is used here because a ptr to elements are stored, so the memory locations need to be stable - struct UpdatableFuncPtr { - List<GDScriptFunction **> ptrs; - Mutex mutex; - bool initialized : 1; - bool transferred : 1; - uint32_t rc = 1; - UpdatableFuncPtr() : - initialized(false), transferred(false) {} - }; - struct UpdatableFuncPtrElement { - List<GDScriptFunction **>::Element *element = nullptr; - UpdatableFuncPtr *func_ptr = nullptr; +public: + class UpdatableFuncPtr { + friend class GDScript; + + GDScriptFunction *ptr = nullptr; + GDScript *script = nullptr; + List<UpdatableFuncPtr *>::Element *list_element = nullptr; + + public: + GDScriptFunction *operator->() const { return ptr; } + operator GDScriptFunction *() const { return ptr; } + + UpdatableFuncPtr(GDScriptFunction *p_function); + ~UpdatableFuncPtr(); }; - static UpdatableFuncPtr func_ptrs_to_update_main_thread; - static thread_local UpdatableFuncPtr *func_ptrs_to_update_thread_local; + +private: + // List is used here because a ptr to elements are stored, so the memory locations need to be stable List<UpdatableFuncPtr *> func_ptrs_to_update; Mutex func_ptrs_to_update_mutex; - UpdatableFuncPtrElement _add_func_ptr_to_update(GDScriptFunction **p_func_ptr_ptr); - static void _remove_func_ptr_to_update(const UpdatableFuncPtrElement &p_func_ptr_element); - - static void _fixup_thread_function_bookkeeping(); + void _recurse_replace_function_ptrs(const HashMap<GDScriptFunction *, GDScriptFunction *> &p_replacements) const; #ifdef TOOLS_ENABLED // For static data storage during hot-reloading. @@ -541,7 +540,7 @@ public: virtual void get_string_delimiters(List<String> *p_delimiters) const override; virtual bool is_using_templates() override; virtual Ref<Script> make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const override; - virtual Vector<ScriptTemplate> get_built_in_templates(StringName p_object) override; + virtual Vector<ScriptTemplate> get_built_in_templates(const StringName &p_object) override; virtual bool validate(const String &p_script, const String &p_path = "", List<String> *r_functions = nullptr, List<ScriptLanguage::ScriptError> *r_errors = nullptr, List<ScriptLanguage::Warning> *r_warnings = nullptr, HashSet<int> *r_safe_lines = nullptr) const override; virtual Script *create_script() const override; #ifndef DISABLE_DEPRECATED @@ -562,11 +561,6 @@ public: virtual void add_named_global_constant(const StringName &p_name, const Variant &p_value) override; virtual void remove_named_global_constant(const StringName &p_name) override; - /* MULTITHREAD FUNCTIONS */ - - virtual void thread_enter() override; - virtual void thread_exit() override; - /* DEBUGGER FUNCTIONS */ virtual String debug_get_error() const override; diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index cf537bde16..3fd5b3f519 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -845,7 +845,7 @@ GDScriptParser::DataType GDScriptAnalyzer::resolve_datatype(GDScriptParser::Type return result; } -void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, StringName p_name, const GDScriptParser::Node *p_source) { +void GDScriptAnalyzer::resolve_class_member(GDScriptParser::ClassNode *p_class, const StringName &p_name, const GDScriptParser::Node *p_source) { ERR_FAIL_COND(!p_class->has_member(p_name)); resolve_class_member(p_class, p_class->members_indices[p_name], p_source); } @@ -3636,7 +3636,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod switch (base.builtin_type) { case Variant::NIL: { if (base.is_hard_type()) { - push_error(vformat(R"(Invalid get index "%s" on base Nil)", name), p_identifier); + push_error(vformat(R"(Cannot get property "%s" on a null object.)", name), p_identifier); } return; } diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h index ec155706df..4ed476a3df 100644 --- a/modules/gdscript/gdscript_analyzer.h +++ b/modules/gdscript/gdscript_analyzer.h @@ -62,7 +62,7 @@ class GDScriptAnalyzer { void decide_suite_type(GDScriptParser::Node *p_suite, GDScriptParser::Node *p_statement); void resolve_annotation(GDScriptParser::AnnotationNode *p_annotation); - void resolve_class_member(GDScriptParser::ClassNode *p_class, StringName p_name, const GDScriptParser::Node *p_source = nullptr); + void resolve_class_member(GDScriptParser::ClassNode *p_class, const StringName &p_name, const GDScriptParser::Node *p_source = nullptr); void resolve_class_member(GDScriptParser::ClassNode *p_class, int p_index, const GDScriptParser::Node *p_source = nullptr); void resolve_class_interface(GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_source = nullptr); void resolve_class_interface(GDScriptParser::ClassNode *p_class, bool p_recursive); diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index ee360e581b..f6633f8bf6 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -1375,7 +1375,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code return GDScriptCodeGenerator::Address(); } - main_script->lambda_info.insert(function, { lambda->captures.size(), lambda->use_self }); + codegen.script->lambda_info.insert(function, { lambda->captures.size(), lambda->use_self }); gen->write_lambda(result, function, captures, lambda->use_self); for (int i = 0; i < captures.size(); i++) { @@ -3050,7 +3050,7 @@ GDScriptCompiler::FunctionLambdaInfo GDScriptCompiler::_get_function_replacement FunctionLambdaInfo info; info.function = p_func; info.parent = p_parent_func; - info.script = p_parent_func; + info.script = p_func->get_script(); info.name = p_func->get_name(); info.line = p_func->_initial_line; info.index = p_index; @@ -3061,10 +3061,14 @@ GDScriptCompiler::FunctionLambdaInfo GDScriptCompiler::_get_function_replacement info.default_arg_count = p_func->_default_arg_count; info.sublambdas = _get_function_lambda_replacement_info(p_func, p_depth, p_parent_func); - GDScript::LambdaInfo *extra_info = main_script->lambda_info.getptr(p_func); + ERR_FAIL_NULL_V(info.script, info); + GDScript::LambdaInfo *extra_info = info.script->lambda_info.getptr(p_func); if (extra_info != nullptr) { info.capture_count = extra_info->capture_count; info.use_self = extra_info->use_self; + } else { + info.capture_count = 0; + info.use_self = false; } return info; @@ -3199,22 +3203,7 @@ Error GDScriptCompiler::compile(const GDScriptParser *p_parser, GDScript *p_scri HashMap<GDScriptFunction *, GDScriptFunction *> func_ptr_replacements; _get_function_ptr_replacements(func_ptr_replacements, old_lambda_info, &new_lambda_info); - - { - MutexLock outer_lock(main_script->func_ptrs_to_update_mutex); - for (GDScript::UpdatableFuncPtr *updatable : main_script->func_ptrs_to_update) { - MutexLock inner_lock(updatable->mutex); - for (GDScriptFunction **func_ptr_ptr : updatable->ptrs) { - GDScriptFunction **replacement = func_ptr_replacements.getptr(*func_ptr_ptr); - if (replacement != nullptr) { - *func_ptr_ptr = *replacement; - } else { - // Probably a lambda from another reload, ignore. - *func_ptr_ptr = nullptr; - } - } - } - } + main_script->_recurse_replace_function_ptrs(func_ptr_replacements); if (has_static_data && !root->annotated_static_unload) { GDScriptCache::add_static_script(p_script); diff --git a/modules/gdscript/gdscript_compiler.h b/modules/gdscript/gdscript_compiler.h index fd6b22f527..0adbe1ed8e 100644 --- a/modules/gdscript/gdscript_compiler.h +++ b/modules/gdscript/gdscript_compiler.h @@ -45,19 +45,19 @@ class GDScriptCompiler { GDScript *main_script = nullptr; struct FunctionLambdaInfo { - GDScriptFunction *function; - GDScriptFunction *parent; - Ref<GDScript> script; + GDScriptFunction *function = nullptr; + GDScriptFunction *parent = nullptr; + GDScript *script = nullptr; StringName name; - int line; - int index; - int depth; + int line = 0; + int index = 0; + int depth = 0; //uint64_t code_hash; //int code_size; - int capture_count; - int use_self; - int arg_count; - int default_arg_count; + int capture_count = 0; + bool use_self = false; + int arg_count = 0; + int default_arg_count = 0; //Vector<GDScriptDataType> argument_types; //GDScriptDataType return_type; Vector<FunctionLambdaInfo> sublambdas; diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 9eb6fe8744..9ad2ba1914 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -104,7 +104,7 @@ Ref<Script> GDScriptLanguage::make_template(const String &p_template, const Stri return scr; } -Vector<ScriptLanguage::ScriptTemplate> GDScriptLanguage::get_built_in_templates(StringName p_object) { +Vector<ScriptLanguage::ScriptTemplate> GDScriptLanguage::get_built_in_templates(const StringName &p_object) { Vector<ScriptLanguage::ScriptTemplate> templates; #ifdef TOOLS_ENABLED for (int i = 0; i < TEMPLATES_ARRAY_SIZE; i++) { @@ -537,7 +537,7 @@ struct GDScriptCompletionIdentifier { // appears. For example, if you are completing code in a class that inherits Node2D, a property found on Node2D // will have a "better" (lower) location "score" than a property that is found on CanvasItem. -static int _get_property_location(StringName p_class, StringName p_property) { +static int _get_property_location(const StringName &p_class, const StringName &p_property) { if (!ClassDB::has_property(p_class, p_property)) { return ScriptLanguage::LOCATION_OTHER; } @@ -552,7 +552,7 @@ static int _get_property_location(StringName p_class, StringName p_property) { return depth | ScriptLanguage::LOCATION_PARENT_MASK; } -static int _get_constant_location(StringName p_class, StringName p_constant) { +static int _get_constant_location(const StringName &p_class, const StringName &p_constant) { if (!ClassDB::has_integer_constant(p_class, p_constant)) { return ScriptLanguage::LOCATION_OTHER; } @@ -567,7 +567,7 @@ static int _get_constant_location(StringName p_class, StringName p_constant) { return depth | ScriptLanguage::LOCATION_PARENT_MASK; } -static int _get_signal_location(StringName p_class, StringName p_signal) { +static int _get_signal_location(const StringName &p_class, const StringName &p_signal) { if (!ClassDB::has_signal(p_class, p_signal)) { return ScriptLanguage::LOCATION_OTHER; } @@ -582,7 +582,7 @@ static int _get_signal_location(StringName p_class, StringName p_signal) { return depth | ScriptLanguage::LOCATION_PARENT_MASK; } -static int _get_method_location(StringName p_class, StringName p_method) { +static int _get_method_location(const StringName &p_class, const StringName &p_method) { if (!ClassDB::has_method(p_class, p_method)) { return ScriptLanguage::LOCATION_OTHER; } @@ -597,7 +597,7 @@ static int _get_method_location(StringName p_class, StringName p_method) { return depth | ScriptLanguage::LOCATION_PARENT_MASK; } -static int _get_enum_constant_location(StringName p_class, StringName p_enum_constant) { +static int _get_enum_constant_location(const StringName &p_class, const StringName &p_enum_constant) { if (!ClassDB::get_integer_constant_enum(p_class, p_enum_constant)) { return ScriptLanguage::LOCATION_OTHER; } @@ -620,9 +620,9 @@ static String _trim_parent_class(const String &p_class, const String &p_base_cla } Vector<String> names = p_class.split(".", false, 1); if (names.size() == 2) { - String first = names[0]; - String rest = names[1]; + const String &first = names[0]; if (ClassDB::class_exists(p_base_class) && ClassDB::class_exists(first) && ClassDB::is_parent_class(p_base_class, first)) { + const String &rest = names[1]; return rest; } } @@ -1210,6 +1210,8 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base return; } + int location = ScriptLanguage::LOCATION_OTHER; + if (!p_only_functions) { List<PropertyInfo> members; if (p_base.value.get_type() != Variant::NIL) { @@ -1223,7 +1225,11 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base continue; } if (!String(E.name).contains("/")) { - ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER); + ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, location); + if (base_type.kind == GDScriptParser::DataType::ENUM) { + // Sort enum members in their declaration order. + location += 1; + } if (GDScriptParser::theme_color_names.has(E.name)) { option.theme_color_name = GDScriptParser::theme_color_names[E.name]; } @@ -1239,7 +1245,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base // Enum types are static and cannot change, therefore we skip non-const dictionary methods. continue; } - ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION); + ScriptLanguage::CodeCompletionOption option(E.name, ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, location); if (E.arguments.size()) { option.insert_text += "("; } else { diff --git a/modules/gdscript/gdscript_lambda_callable.cpp b/modules/gdscript/gdscript_lambda_callable.cpp index 547f5607d3..f6fa17c84f 100644 --- a/modules/gdscript/gdscript_lambda_callable.cpp +++ b/modules/gdscript/gdscript_lambda_callable.cpp @@ -139,20 +139,14 @@ void GDScriptLambdaCallable::call(const Variant **p_arguments, int p_argcount, V } } -GDScriptLambdaCallable::GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptFunction *p_function, const Vector<Variant> &p_captures) { +GDScriptLambdaCallable::GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptFunction *p_function, const Vector<Variant> &p_captures) : + function(p_function) { ERR_FAIL_NULL(p_script.ptr()); ERR_FAIL_NULL(p_function); script = p_script; - function = p_function; captures = p_captures; h = (uint32_t)hash_murmur3_one_64((uint64_t)this); - - updatable_func_ptr_element = p_script->_add_func_ptr_to_update(&function); -} - -GDScriptLambdaCallable::~GDScriptLambdaCallable() { - GDScript::_remove_func_ptr_to_update(updatable_func_ptr_element); } bool GDScriptLambdaSelfCallable::compare_equal(const CallableCustom *p_a, const CallableCustom *p_b) { @@ -264,37 +258,23 @@ void GDScriptLambdaSelfCallable::call(const Variant **p_arguments, int p_argcoun } } -GDScriptLambdaSelfCallable::GDScriptLambdaSelfCallable(Ref<RefCounted> p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures) { +GDScriptLambdaSelfCallable::GDScriptLambdaSelfCallable(Ref<RefCounted> p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures) : + function(p_function) { ERR_FAIL_NULL(p_self.ptr()); ERR_FAIL_NULL(p_function); reference = p_self; object = p_self.ptr(); - function = p_function; captures = p_captures; h = (uint32_t)hash_murmur3_one_64((uint64_t)this); - - GDScript *gds = p_function->get_script(); - if (gds != nullptr) { - updatable_func_ptr_element = gds->_add_func_ptr_to_update(&function); - } } -GDScriptLambdaSelfCallable::GDScriptLambdaSelfCallable(Object *p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures) { +GDScriptLambdaSelfCallable::GDScriptLambdaSelfCallable(Object *p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures) : + function(p_function) { ERR_FAIL_NULL(p_self); ERR_FAIL_NULL(p_function); object = p_self; - function = p_function; captures = p_captures; h = (uint32_t)hash_murmur3_one_64((uint64_t)this); - - GDScript *gds = p_function->get_script(); - if (gds != nullptr) { - updatable_func_ptr_element = gds->_add_func_ptr_to_update(&function); - } -} - -GDScriptLambdaSelfCallable::~GDScriptLambdaSelfCallable() { - GDScript::_remove_func_ptr_to_update(updatable_func_ptr_element); } diff --git a/modules/gdscript/gdscript_lambda_callable.h b/modules/gdscript/gdscript_lambda_callable.h index ee7d547544..2c5d01aa16 100644 --- a/modules/gdscript/gdscript_lambda_callable.h +++ b/modules/gdscript/gdscript_lambda_callable.h @@ -42,10 +42,9 @@ class GDScriptFunction; class GDScriptInstance; class GDScriptLambdaCallable : public CallableCustom { - GDScriptFunction *function = nullptr; + GDScript::UpdatableFuncPtr function; Ref<GDScript> script; uint32_t h; - GDScript::UpdatableFuncPtrElement updatable_func_ptr_element; Vector<Variant> captures; @@ -62,17 +61,18 @@ public: StringName get_method() const override; void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; + GDScriptLambdaCallable(GDScriptLambdaCallable &) = delete; + GDScriptLambdaCallable(const GDScriptLambdaCallable &) = delete; GDScriptLambdaCallable(Ref<GDScript> p_script, GDScriptFunction *p_function, const Vector<Variant> &p_captures); - virtual ~GDScriptLambdaCallable(); + virtual ~GDScriptLambdaCallable() = default; }; // Lambda callable that references a particular object, so it can use `self` in the body. class GDScriptLambdaSelfCallable : public CallableCustom { - GDScriptFunction *function = nullptr; + GDScript::UpdatableFuncPtr function; Ref<RefCounted> reference; // For objects that are RefCounted, keep a reference. Object *object = nullptr; // For non RefCounted objects, use a direct pointer. uint32_t h; - GDScript::UpdatableFuncPtrElement updatable_func_ptr_element; Vector<Variant> captures; @@ -88,9 +88,11 @@ public: ObjectID get_object() const override; void call(const Variant **p_arguments, int p_argcount, Variant &r_return_value, Callable::CallError &r_call_error) const override; + GDScriptLambdaSelfCallable(GDScriptLambdaSelfCallable &) = delete; + GDScriptLambdaSelfCallable(const GDScriptLambdaSelfCallable &) = delete; GDScriptLambdaSelfCallable(Ref<RefCounted> p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures); GDScriptLambdaSelfCallable(Object *p_self, GDScriptFunction *p_function, const Vector<Variant> &p_captures); - virtual ~GDScriptLambdaSelfCallable(); + virtual ~GDScriptLambdaSelfCallable() = default; }; #endif // GDSCRIPT_LAMBDA_CALLABLE_H diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index f3a4f2eaa6..a4a12f8bc4 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -629,7 +629,7 @@ GDScriptParser::ClassNode *GDScriptParser::find_class(const String &p_qualified_ // Starts at index 1 because index 0 was handled above. for (int i = 1; result != nullptr && i < class_names.size(); i++) { - String current_name = class_names[i]; + const String ¤t_name = class_names[i]; GDScriptParser::ClassNode *next = nullptr; if (result->has_member(current_name)) { GDScriptParser::ClassNode::Member member = result->get_member(current_name); @@ -1120,7 +1120,12 @@ void GDScriptParser::parse_property_getter(VariableNode *p_variable) { case VariableNode::PROP_INLINE: { FunctionNode *function = alloc_node<FunctionNode>(); - consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" after "get".)"); + if (match(GDScriptTokenizer::Token::PARENTHESIS_OPEN)) { + consume(GDScriptTokenizer::Token::PARENTHESIS_CLOSE, R"*(Expected ")" after "get(".)*"); + consume(GDScriptTokenizer::Token::COLON, R"*(Expected ":" after "get()".)*"); + } else { + consume(GDScriptTokenizer::Token::COLON, R"(Expected ":" or "(" after "get".)"); + } IdentifierNode *identifier = alloc_node<IdentifierNode>(); complete_extents(identifier); @@ -1268,8 +1273,7 @@ GDScriptParser::EnumNode *GDScriptParser::parse_enum(bool p_is_static) { EnumNode *enum_node = alloc_node<EnumNode>(); bool named = false; - if (check(GDScriptTokenizer::Token::IDENTIFIER)) { - advance(); + if (match(GDScriptTokenizer::Token::IDENTIFIER)) { enum_node->identifier = parse_identifier(); named = true; } diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 3abfc7f8e3..7b03ac74d6 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -873,8 +873,12 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GET_VARIANT_PTR(value, 2); bool valid; +#ifdef DEBUG_ENABLED + Variant::VariantSetError err_code; + dst->set(*index, *value, &valid, &err_code); +#else dst->set(*index, *value, &valid); - +#endif #ifdef DEBUG_ENABLED if (!valid) { Object *obj = dst->get_validated_object(); @@ -891,7 +895,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } else { v = "of type '" + _get_var_type(index) + "'"; } - err_text = "Invalid set index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'"; + err_text = "Invalid assignment of property or key " + v + " with value of type '" + _get_var_type(value) + "' on a base object of type '" + _get_var_type(dst) + "'."; + if (err_code == Variant::VariantSetError::SET_INDEXED_ERR) { + err_text = "Invalid assignment of index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'."; + } } OPCODE_BREAK; } @@ -922,7 +929,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } else { v = "of type '" + _get_var_type(index) + "'"; } - err_text = "Invalid set index " + v + " (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'"; + err_text = "Invalid assignment of property or key " + v + " with value of type '" + _get_var_type(value) + "' on a base object of type '" + _get_var_type(dst) + "'."; OPCODE_BREAK; } #endif @@ -972,7 +979,8 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a bool valid; #ifdef DEBUG_ENABLED // Allow better error message in cases where src and dst are the same stack position. - Variant ret = src->get(*index, &valid); + Variant::VariantGetError err_code; + Variant ret = src->get(*index, &valid, &err_code); #else *dst = src->get(*index, &valid); @@ -985,7 +993,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } else { v = "of type '" + _get_var_type(index) + "'"; } - err_text = "Invalid get index " + v + " (on base: '" + _get_var_type(src) + "')."; + err_text = "Invalid access to property or key " + v + " on a base object of type '" + _get_var_type(src) + "'."; + if (err_code == Variant::VariantGetError::GET_INDEXED_ERR) { + err_text = "Invalid access of index " + v + " on a base object of type: '" + _get_var_type(src) + "'."; + } OPCODE_BREAK; } *dst = ret; @@ -1021,7 +1032,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } else { v = "of type '" + _get_var_type(key) + "'"; } - err_text = "Invalid get index " + v + " (on base: '" + _get_var_type(src) + "')."; + err_text = "Invalid access to property or key " + v + " on a base object of type '" + _get_var_type(src) + "'."; OPCODE_BREAK; } *dst = ret; @@ -1086,7 +1097,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (read_only_property) { err_text = vformat(R"(Cannot set value into property "%s" (on base "%s") because it is read-only.)", String(*index), _get_var_type(dst)); } else { - err_text = "Invalid set index '" + String(*index) + "' (on base: '" + _get_var_type(dst) + "') with value of type '" + _get_var_type(value) + "'."; + err_text = "Invalid assignment of property or key '" + String(*index) + "' with value of type '" + _get_var_type(value) + "' on a base object of type '" + _get_var_type(dst) + "'."; } OPCODE_BREAK; } @@ -1131,7 +1142,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #endif #ifdef DEBUG_ENABLED if (!valid) { - err_text = "Invalid get index '" + index->operator String() + "' (on base: '" + _get_var_type(src) + "')."; + err_text = "Invalid access to property or key '" + index->operator String() + "' on a base object of type '" + _get_var_type(src) + "'."; OPCODE_BREAK; } *dst = ret; @@ -2473,7 +2484,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a Variant::construct(ret_type, retvalue, const_cast<const Variant **>(&r), 1, ce); } else { #ifdef DEBUG_ENABLED - err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)", + err_text = vformat(R"(Trying to return value of type "%s" from a function whose return type is "%s".)", Variant::get_type_name(r->get_type()), Variant::get_type_name(ret_type)); #endif // DEBUG_ENABLED @@ -2503,9 +2514,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (r->get_type() != Variant::ARRAY) { #ifdef DEBUG_ENABLED - err_text = vformat(R"(Trying to return a value of type "%s" where expected return type is "Array[%s]".)", - _get_var_type(r), _get_element_type(builtin_type, native_type, *script_type)); -#endif // DEBUG_ENABLED + err_text = vformat(R"(Trying to return value of type "%s" from a function whose return type is "Array[%s]".)", + Variant::get_type_name(r->get_type()), Variant::get_type_name(builtin_type)); +#endif OPCODE_BREAK; } @@ -2536,7 +2547,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a GD_ERR_BREAK(!nc); if (r->get_type() != Variant::OBJECT && r->get_type() != Variant::NIL) { - err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)", + err_text = vformat(R"(Trying to return value of type "%s" from a function whose return type is "%s".)", Variant::get_type_name(r->get_type()), nc->get_name()); OPCODE_BREAK; } @@ -2554,7 +2565,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a #endif // DEBUG_ENABLED if (ret_obj && !ClassDB::is_parent_class(ret_obj->get_class_name(), nc->get_name())) { #ifdef DEBUG_ENABLED - err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)", + err_text = vformat(R"(Trying to return value of type "%s" from a function whose return type is "%s".)", ret_obj->get_class_name(), nc->get_name()); #endif // DEBUG_ENABLED OPCODE_BREAK; @@ -2577,7 +2588,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (r->get_type() != Variant::OBJECT && r->get_type() != Variant::NIL) { #ifdef DEBUG_ENABLED - err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)", + err_text = vformat(R"(Trying to return value of type "%s" from a function whose return type is "%s".)", Variant::get_type_name(r->get_type()), GDScript::debug_get_script_name(Ref<Script>(base_type))); #endif // DEBUG_ENABLED OPCODE_BREAK; @@ -2599,7 +2610,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a ScriptInstance *ret_inst = ret_obj->get_script_instance(); if (!ret_inst) { #ifdef DEBUG_ENABLED - err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)", + err_text = vformat(R"(Trying to return value of type "%s" from a function whose return type is "%s".)", ret_obj->get_class_name(), GDScript::debug_get_script_name(Ref<GDScript>(base_type))); #endif // DEBUG_ENABLED OPCODE_BREAK; @@ -2618,7 +2629,7 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a if (!valid) { #ifdef DEBUG_ENABLED - err_text = vformat(R"(Trying to return value of type "%s" from a function which the return type is "%s".)", + err_text = vformat(R"(Trying to return value of type "%s" from a function whose return type is "%s".)", GDScript::debug_get_script_name(ret_obj->get_script_instance()->get_script()), GDScript::debug_get_script_name(Ref<GDScript>(base_type))); #endif // DEBUG_ENABLED OPCODE_BREAK; diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index 361ca276bb..4d93a6fc18 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -266,7 +266,7 @@ bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) { while (!next.is_empty()) { if (dir->current_is_dir()) { - if (next == "." || next == "..") { + if (next == "." || next == ".." || next == "completion" || next == "lsp") { next = dir->get_next(); continue; } diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.out b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.out index 73a54d7820..a6a3973255 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants.out @@ -3,4 +3,4 @@ GDTEST_RUNTIME_ERROR >> on function: test() >> analyzer/errors/outer_class_constants.gd >> 8 ->> Invalid get index 'OUTER_CONST' (on base: 'GDScript'). +>> Invalid access to property or key 'OUTER_CONST' on a base object of type 'GDScript'. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.out b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.out index 92e7b9316e..70fdc5b62c 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_constants_as_variant.out @@ -3,4 +3,4 @@ GDTEST_RUNTIME_ERROR >> on function: test() >> analyzer/errors/outer_class_constants_as_variant.gd >> 9 ->> Invalid get index 'OUTER_CONST' (on base: 'GDScript'). +>> Invalid access to property or key 'OUTER_CONST' on a base object of type 'GDScript'. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.out b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.out index 892f8e2c3f..6632f056bd 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants.out @@ -3,4 +3,4 @@ GDTEST_RUNTIME_ERROR >> on function: test() >> analyzer/errors/outer_class_instance_constants.gd >> 8 ->> Invalid get index 'OUTER_CONST' (on base: 'RefCounted (Inner)'). +>> Invalid access to property or key 'OUTER_CONST' on a base object of type 'RefCounted (Inner)'. diff --git a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.out b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.out index 8257e74f57..0459b756d1 100644 --- a/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.out +++ b/modules/gdscript/tests/scripts/analyzer/errors/outer_class_instance_constants_as_variant.out @@ -3,4 +3,4 @@ GDTEST_RUNTIME_ERROR >> on function: test() >> analyzer/errors/outer_class_instance_constants_as_variant.gd >> 9 ->> Invalid get index 'OUTER_CONST' (on base: 'RefCounted (Inner)'). +>> Invalid access to property or key 'OUTER_CONST' on a base object of type 'RefCounted (Inner)'. diff --git a/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.cfg b/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.cfg new file mode 100644 index 0000000000..4edee46039 --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.cfg @@ -0,0 +1,4 @@ +[output] +expected=[ + {"display": "autoplay"}, +] diff --git a/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.gd b/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.gd new file mode 100644 index 0000000000..d41bbb970c --- /dev/null +++ b/modules/gdscript/tests/scripts/completion/get_node/get_node_member_annotated.gd @@ -0,0 +1,6 @@ +extends Node + +var test: AnimationPlayer = $AnimationPlayer + +func _ready(): + test.➡ diff --git a/modules/gdscript/tests/scripts/lsp/class.notest.gd b/modules/gdscript/tests/scripts/lsp/class.gd index 53d0b14d72..53d0b14d72 100644 --- a/modules/gdscript/tests/scripts/lsp/class.notest.gd +++ b/modules/gdscript/tests/scripts/lsp/class.gd diff --git a/modules/gdscript/tests/scripts/lsp/enums.notest.gd b/modules/gdscript/tests/scripts/lsp/enums.gd index 38b9ec110a..38b9ec110a 100644 --- a/modules/gdscript/tests/scripts/lsp/enums.notest.gd +++ b/modules/gdscript/tests/scripts/lsp/enums.gd diff --git a/modules/gdscript/tests/scripts/lsp/indentation.notest.gd b/modules/gdscript/tests/scripts/lsp/indentation.gd index c25d73a719..c25d73a719 100644 --- a/modules/gdscript/tests/scripts/lsp/indentation.notest.gd +++ b/modules/gdscript/tests/scripts/lsp/indentation.gd diff --git a/modules/gdscript/tests/scripts/lsp/lambdas.notest.gd b/modules/gdscript/tests/scripts/lsp/lambdas.gd index 6f5d468eea..6f5d468eea 100644 --- a/modules/gdscript/tests/scripts/lsp/lambdas.notest.gd +++ b/modules/gdscript/tests/scripts/lsp/lambdas.gd diff --git a/modules/gdscript/tests/scripts/lsp/local_variables.notest.gd b/modules/gdscript/tests/scripts/lsp/local_variables.gd index b6cc46f7da..b6cc46f7da 100644 --- a/modules/gdscript/tests/scripts/lsp/local_variables.notest.gd +++ b/modules/gdscript/tests/scripts/lsp/local_variables.gd diff --git a/modules/gdscript/tests/scripts/lsp/properties.notest.gd b/modules/gdscript/tests/scripts/lsp/properties.gd index 8dfaee2e5b..8dfaee2e5b 100644 --- a/modules/gdscript/tests/scripts/lsp/properties.notest.gd +++ b/modules/gdscript/tests/scripts/lsp/properties.gd diff --git a/modules/gdscript/tests/scripts/lsp/scopes.notest.gd b/modules/gdscript/tests/scripts/lsp/scopes.gd index 20b8fb9bd7..20b8fb9bd7 100644 --- a/modules/gdscript/tests/scripts/lsp/scopes.notest.gd +++ b/modules/gdscript/tests/scripts/lsp/scopes.gd diff --git a/modules/gdscript/tests/scripts/lsp/shadowing_initializer.notest.gd b/modules/gdscript/tests/scripts/lsp/shadowing_initializer.gd index 338000fa0e..338000fa0e 100644 --- a/modules/gdscript/tests/scripts/lsp/shadowing_initializer.notest.gd +++ b/modules/gdscript/tests/scripts/lsp/shadowing_initializer.gd diff --git a/modules/gdscript/tests/scripts/parser/features/property_setter_getter.gd b/modules/gdscript/tests/scripts/parser/features/property_setter_getter.gd index 9e4b360fb2..82616ee3cf 100644 --- a/modules/gdscript/tests/scripts/parser/features/property_setter_getter.gd +++ b/modules/gdscript/tests/scripts/parser/features/property_setter_getter.gd @@ -6,6 +6,9 @@ var property: set(value): _backing = value - 1000 +var property_2: + get(): # Allow parentheses. + return 123 func test(): print("Not using self:") @@ -35,3 +38,5 @@ func test(): self.property = 5000 print(self.property) print(self._backing) + + print(property_2) diff --git a/modules/gdscript/tests/scripts/parser/features/property_setter_getter.out b/modules/gdscript/tests/scripts/parser/features/property_setter_getter.out index 560e0c3bd7..23f98f44ab 100644 --- a/modules/gdscript/tests/scripts/parser/features/property_setter_getter.out +++ b/modules/gdscript/tests/scripts/parser/features/property_setter_getter.out @@ -17,3 +17,4 @@ Using self: -50 5000 4000 +123 diff --git a/modules/gdscript/tests/scripts/project.godot b/modules/gdscript/tests/scripts/project.godot index 25b49c0abd..c500ef443d 100644 --- a/modules/gdscript/tests/scripts/project.godot +++ b/modules/gdscript/tests/scripts/project.godot @@ -3,7 +3,7 @@ ; It also helps for opening Godot to edit the scripts, but please don't ; let the editor changes be saved. -config_version=4 +config_version=5 [application] diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.out b/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.out index 2a97eaea44..c524a1ae6b 100644 --- a/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.out +++ b/modules/gdscript/tests/scripts/runtime/errors/constant_array_is_deep.out @@ -3,4 +3,4 @@ GDTEST_RUNTIME_ERROR >> on function: test() >> runtime/errors/constant_array_is_deep.gd >> 6 ->> Invalid set index '0' (on base: 'Dictionary') with value of type 'int' +>> Invalid assignment of property or key '0' with value of type 'int' on a base object of type 'Dictionary'. diff --git a/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.out b/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.out index c807db6b0c..cf51b0262d 100644 --- a/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.out +++ b/modules/gdscript/tests/scripts/runtime/errors/constant_dictionary_is_deep.out @@ -3,4 +3,4 @@ GDTEST_RUNTIME_ERROR >> on function: test() >> runtime/errors/constant_dictionary_is_deep.gd >> 6 ->> Invalid set index '0' (on base: 'Array') with value of type 'int' +>> Invalid assignment of index '0' (on base: 'Array') with value of type 'int'. diff --git a/modules/gdscript/tests/test_completion.h b/modules/gdscript/tests/test_completion.h new file mode 100644 index 0000000000..abc34bd4bf --- /dev/null +++ b/modules/gdscript/tests/test_completion.h @@ -0,0 +1,199 @@ +/**************************************************************************/ +/* test_completion.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 TEST_COMPLETION_H +#define TEST_COMPLETION_H + +#ifdef TOOLS_ENABLED + +#include "core/io/config_file.h" +#include "core/io/dir_access.h" +#include "core/io/file_access.h" +#include "core/object/script_language.h" +#include "core/variant/dictionary.h" +#include "core/variant/variant.h" +#include "gdscript_test_runner.h" +#include "modules/modules_enabled.gen.h" // For mono. +#include "scene/resources/packed_scene.h" + +#include "../gdscript.h" +#include "tests/test_macros.h" + +#include "editor/editor_settings.h" +#include "scene/theme/theme_db.h" + +namespace GDScriptTests { + +static bool match_option(const Dictionary p_expected, const ScriptLanguage::CodeCompletionOption p_got) { + if (p_expected.get("display", p_got.display) != p_got.display) { + return false; + } + if (p_expected.get("insert_text", p_got.insert_text) != p_got.insert_text) { + return false; + } + if (p_expected.get("kind", p_got.kind) != Variant(p_got.kind)) { + return false; + } + if (p_expected.get("location", p_got.location) != Variant(p_got.location)) { + return false; + } + return true; +} + +static void to_dict_list(Variant p_variant, List<Dictionary> &p_list) { + ERR_FAIL_COND(!p_variant.is_array()); + + Array arr = p_variant; + for (int i = 0; i < arr.size(); i++) { + if (arr[i].get_type() == Variant::DICTIONARY) { + p_list.push_back(arr[i]); + } + } +} + +static void test_directory(const String &p_dir) { + Error err = OK; + Ref<DirAccess> dir = DirAccess::open(p_dir, &err); + + if (err != OK) { + FAIL("Invalid test directory."); + return; + } + + String path = dir->get_current_dir(); + + dir->list_dir_begin(); + String next = dir->get_next(); + + while (!next.is_empty()) { + if (dir->current_is_dir()) { + if (next == "." || next == "..") { + next = dir->get_next(); + continue; + } + test_directory(path.path_join(next)); + } else if (next.ends_with(".gd") && !next.ends_with(".notest.gd")) { + Ref<FileAccess> acc = FileAccess::open(path.path_join(next), FileAccess::READ, &err); + + if (err != OK) { + next = dir->get_next(); + continue; + } + + String code = acc->get_as_utf8_string(); + // For ease of reading ➡ (0x27A1) acts as sentinel char instead of 0xFFFF in the files. + code = code.replace_first(String::chr(0x27A1), String::chr(0xFFFF)); + // Require pointer sentinel char in scripts. + CHECK(code.find_char(0xFFFF) != -1); + + ConfigFile conf; + if (conf.load(path.path_join(next.get_basename() + ".cfg")) != OK) { + FAIL("No config file found."); + } + +#ifndef MODULE_MONO_ENABLED + if (conf.get_value("input", "cs", false)) { + next = dir->get_next(); + continue; + } +#endif + + EditorSettings::get_singleton()->set_setting("text_editor/completion/use_single_quotes", conf.get_value("input", "use_single_quotes", false)); + + List<Dictionary> include; + to_dict_list(conf.get_value("result", "include", Array()), include); + + List<Dictionary> exclude; + to_dict_list(conf.get_value("result", "exclude", Array()), exclude); + + List<ScriptLanguage::CodeCompletionOption> options; + String call_hint; + bool forced; + + Node *owner = nullptr; + if (dir->file_exists(next.get_basename() + ".tscn")) { + String project_path = "res://completion"; + Ref<PackedScene> scene = ResourceLoader::load(project_path.path_join(next.get_basename() + ".tscn"), "PackedScene"); + if (scene.is_valid()) { + owner = scene->instantiate(); + } + } + + GDScriptLanguage::get_singleton()->complete_code(code, path.path_join(next), owner, &options, forced, call_hint); + String contains_excluded; + for (ScriptLanguage::CodeCompletionOption &option : options) { + for (const Dictionary &E : exclude) { + if (match_option(E, option)) { + contains_excluded = option.display; + break; + } + } + if (!contains_excluded.is_empty()) { + break; + } + + for (const Dictionary &E : include) { + if (match_option(E, option)) { + include.erase(E); + break; + } + } + } + CHECK_MESSAGE(contains_excluded.is_empty(), "Autocompletion suggests illegal option '", contains_excluded, "' for '", path.path_join(next), "'."); + CHECK(include.is_empty()); + + String expected_call_hint = conf.get_value("result", "call_hint", call_hint); + bool expected_forced = conf.get_value("result", "forced", forced); + + CHECK(expected_call_hint == call_hint); + CHECK(expected_forced == forced); + + if (owner) { + memdelete(owner); + } + } + next = dir->get_next(); + } +} + +TEST_SUITE("[Modules][GDScript][Completion]") { + TEST_CASE("[Editor] Check suggestion list") { + // Set all editor settings that code completion relies on. + EditorSettings::get_singleton()->set_setting("text_editor/completion/use_single_quotes", false); + init_language("modules/gdscript/tests/scripts"); + + test_directory("modules/gdscript/tests/scripts/completion"); + } +} +} // namespace GDScriptTests + +#endif + +#endif // TEST_COMPLETION_H diff --git a/modules/gdscript/tests/test_lsp.h b/modules/gdscript/tests/test_lsp.h index e57df00e2d..6192272f80 100644 --- a/modules/gdscript/tests/test_lsp.h +++ b/modules/gdscript/tests/test_lsp.h @@ -76,7 +76,7 @@ namespace GDScriptTests { // LSP GDScript test scripts are located inside project of other GDScript tests: // Cannot reset `ProjectSettings` (singleton) -> Cannot load another workspace and resources in there. // -> Reuse GDScript test project. LSP specific scripts are then placed inside `lsp` folder. -// Access via `res://lsp/my_script.notest.gd`. +// Access via `res://lsp/my_script.gd`. const String root = "modules/gdscript/tests/scripts/"; /* @@ -394,7 +394,7 @@ func f(): Ref<GDScriptWorkspace> workspace = GDScriptLanguageProtocol::get_singleton()->get_workspace(); { - String path = "res://lsp/local_variables.notest.gd"; + String path = "res://lsp/local_variables.gd"; assert_no_errors_in(path); String uri = workspace->get_file_uri(path); Vector<InlineTestData> all_test_data = read_tests(path); @@ -413,7 +413,7 @@ func f(): } SUBCASE("Can get correct ranges for indented variables") { - String path = "res://lsp/indentation.notest.gd"; + String path = "res://lsp/indentation.gd"; assert_no_errors_in(path); String uri = workspace->get_file_uri(path); Vector<InlineTestData> all_test_data = read_tests(path); @@ -421,7 +421,7 @@ func f(): } SUBCASE("Can get correct ranges for scopes") { - String path = "res://lsp/scopes.notest.gd"; + String path = "res://lsp/scopes.gd"; assert_no_errors_in(path); String uri = workspace->get_file_uri(path); Vector<InlineTestData> all_test_data = read_tests(path); @@ -429,7 +429,7 @@ func f(): } SUBCASE("Can get correct ranges for lambda") { - String path = "res://lsp/lambdas.notest.gd"; + String path = "res://lsp/lambdas.gd"; assert_no_errors_in(path); String uri = workspace->get_file_uri(path); Vector<InlineTestData> all_test_data = read_tests(path); @@ -437,7 +437,7 @@ func f(): } SUBCASE("Can get correct ranges for inner class") { - String path = "res://lsp/class.notest.gd"; + String path = "res://lsp/class.gd"; assert_no_errors_in(path); String uri = workspace->get_file_uri(path); Vector<InlineTestData> all_test_data = read_tests(path); @@ -445,7 +445,7 @@ func f(): } SUBCASE("Can get correct ranges for inner class") { - String path = "res://lsp/enums.notest.gd"; + String path = "res://lsp/enums.gd"; assert_no_errors_in(path); String uri = workspace->get_file_uri(path); Vector<InlineTestData> all_test_data = read_tests(path); @@ -453,7 +453,7 @@ func f(): } SUBCASE("Can get correct ranges for shadowing & shadowed variables") { - String path = "res://lsp/shadowing_initializer.notest.gd"; + String path = "res://lsp/shadowing_initializer.gd"; assert_no_errors_in(path); String uri = workspace->get_file_uri(path); Vector<InlineTestData> all_test_data = read_tests(path); @@ -461,7 +461,7 @@ func f(): } SUBCASE("Can get correct ranges for properties and getter/setter") { - String path = "res://lsp/properties.notest.gd"; + String path = "res://lsp/properties.gd"; assert_no_errors_in(path); String uri = workspace->get_file_uri(path); Vector<InlineTestData> all_test_data = read_tests(path); diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp index 6975bc1228..56e4bfbb32 100644 --- a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp +++ b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.cpp @@ -39,7 +39,7 @@ #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/gui/editor_file_dialog.h" -#include "editor/import/scene_import_settings.h" +#include "editor/import/3d/scene_import_settings.h" String SceneExporterGLTFPlugin::get_name() const { return "ConvertGLTF2"; diff --git a/modules/gltf/editor/editor_scene_importer_blend.h b/modules/gltf/editor/editor_scene_importer_blend.h index ec467db457..c1f4280170 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.h +++ b/modules/gltf/editor/editor_scene_importer_blend.h @@ -34,7 +34,7 @@ #ifdef TOOLS_ENABLED #include "editor/editor_file_system.h" -#include "editor/import/resource_importer_scene.h" +#include "editor/import/3d/resource_importer_scene.h" class Animation; class Node; diff --git a/modules/gltf/editor/editor_scene_importer_fbx.h b/modules/gltf/editor/editor_scene_importer_fbx.h index cc60830eac..86ee6568c9 100644 --- a/modules/gltf/editor/editor_scene_importer_fbx.h +++ b/modules/gltf/editor/editor_scene_importer_fbx.h @@ -35,7 +35,7 @@ #include "editor/editor_file_system.h" #include "editor/fbx_importer_manager.h" -#include "editor/import/resource_importer_scene.h" +#include "editor/import/3d/resource_importer_scene.h" class Animation; class Node; diff --git a/modules/gltf/editor/editor_scene_importer_gltf.h b/modules/gltf/editor/editor_scene_importer_gltf.h index 7726c845bf..ec563bf525 100644 --- a/modules/gltf/editor/editor_scene_importer_gltf.h +++ b/modules/gltf/editor/editor_scene_importer_gltf.h @@ -33,7 +33,7 @@ #ifdef TOOLS_ENABLED -#include "editor/import/resource_importer_scene.h" +#include "editor/import/3d/resource_importer_scene.h" class Animation; class Node; diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index e3824562bf..8a60df85cf 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -5049,7 +5049,7 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) { AnimationPlayer *animation_player = p_state->animation_players[player_i]; List<StringName> animations; animation_player->get_animation_list(&animations); - for (StringName animation_name : animations) { + for (const StringName &animation_name : animations) { _convert_animation(p_state, animation_player, animation_name); } } @@ -6155,9 +6155,9 @@ T GLTFDocument::_interpolate_track(const Vector<real_t> &p_times, const Vector<T const float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]); - const T from = p_values[idx * 3 + 1]; + const T &from = p_values[idx * 3 + 1]; const T c1 = from + p_values[idx * 3 + 2]; - const T to = p_values[idx * 3 + 4]; + const T &to = p_values[idx * 3 + 4]; const T c2 = to + p_values[idx * 3 + 3]; return interp.bezier(from, c1, c2, to, c); @@ -6288,6 +6288,9 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_ animation->add_track(Animation::TYPE_POSITION_3D); animation->track_set_path(position_idx, transform_node_path); animation->track_set_imported(position_idx, true); //helps merging later + if (track.position_track.interpolation == GLTFAnimation::INTERP_STEP) { + animation->track_set_interpolation_type(position_idx, Animation::InterpolationType::INTERPOLATION_NEAREST); + } base_idx++; } } @@ -6310,6 +6313,9 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_ animation->add_track(Animation::TYPE_ROTATION_3D); animation->track_set_path(rotation_idx, transform_node_path); animation->track_set_imported(rotation_idx, true); //helps merging later + if (track.rotation_track.interpolation == GLTFAnimation::INTERP_STEP) { + animation->track_set_interpolation_type(rotation_idx, Animation::InterpolationType::INTERPOLATION_NEAREST); + } base_idx++; } } @@ -6332,6 +6338,9 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_ animation->add_track(Animation::TYPE_SCALE_3D); animation->track_set_path(scale_idx, transform_node_path); animation->track_set_imported(scale_idx, true); //helps merging later + if (track.scale_track.interpolation == GLTFAnimation::INTERP_STEP) { + animation->track_set_interpolation_type(scale_idx, Animation::InterpolationType::INTERPOLATION_NEAREST); + } base_idx++; } } @@ -7056,9 +7065,9 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p } else if (String(final_track_path).contains(":")) { //Process skeleton const Vector<String> node_suffix = String(final_track_path).split(":"); - const String node = node_suffix[0]; + const String &node = node_suffix[0]; const NodePath node_path = node; - const String suffix = node_suffix[1]; + const String &suffix = node_suffix[1]; Node *godot_node = animation_base_node->get_node_or_null(node_path); if (!godot_node) { continue; diff --git a/modules/gltf/structures/gltf_skeleton.h b/modules/gltf/structures/gltf_skeleton.h index 72a4a06e5c..b2f2dcb2a2 100644 --- a/modules/gltf/structures/gltf_skeleton.h +++ b/modules/gltf/structures/gltf_skeleton.h @@ -82,7 +82,7 @@ public: //RBMap<int32_t, GLTFNodeIndex> get_godot_bone_node() { // return this->godot_bone_node; //} - //void set_godot_bone_node(RBMap<int32_t, GLTFNodeIndex> p_godot_bone_node) { + //void set_godot_bone_node(const RBMap<int32_t, GLTFNodeIndex> &p_godot_bone_node) { // this->godot_bone_node = p_godot_bone_node; //} Dictionary get_godot_bone_node(); diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp index f7c01ff840..225138dfb3 100644 --- a/modules/gridmap/editor/grid_map_editor_plugin.cpp +++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp @@ -1316,6 +1316,7 @@ GridMapEditor::GridMapEditor() { EDITOR_DEF("editors/grid_map/preview_size", 64); mesh_library_palette = memnew(ItemList); + mesh_library_palette->set_auto_translate(false); add_child(mesh_library_palette); mesh_library_palette->set_v_size_flags(SIZE_EXPAND_FILL); mesh_library_palette->connect("gui_input", callable_mp(this, &GridMapEditor::_mesh_library_palette_input)); diff --git a/modules/jpg/image_loader_jpegd.cpp b/modules/jpg/image_loader_jpegd.cpp index e7fa909706..ada0cd01fa 100644 --- a/modules/jpg/image_loader_jpegd.cpp +++ b/modules/jpg/image_loader_jpegd.cpp @@ -162,6 +162,7 @@ static Error _jpgd_save_to_output_stream(jpge::output_stream *p_output_stream, c ERR_FAIL_COND_V_MSG(error != OK, error, "Couldn't decompress image."); } if (image->get_format() != Image::FORMAT_RGB8) { + image = p_img->duplicate(); image->convert(Image::FORMAT_RGB8); } @@ -173,12 +174,16 @@ static Error _jpgd_save_to_output_stream(jpge::output_stream *p_output_stream, c const uint8_t *src_data = image->get_data().ptr(); for (int i = 0; i < image->get_height(); i++) { - enc.process_scanline(&src_data[i * image->get_width() * 3]); + if (!enc.process_scanline(&src_data[i * image->get_width() * 3])) { + return FAILED; + } } - enc.process_scanline(nullptr); - - return OK; + if (enc.process_scanline(nullptr)) { + return OK; + } else { + return FAILED; + } } static Vector<uint8_t> _jpgd_buffer_save_func(const Ref<Image> &p_img, float p_quality) { diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp index 74dc54641d..5c2c3f96de 100644 --- a/modules/lightmapper_rd/lightmapper_rd.cpp +++ b/modules/lightmapper_rd/lightmapper_rd.cpp @@ -357,7 +357,7 @@ void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i for (int m_i = 0; m_i < mesh_instances.size(); m_i++) { if (p_step_function) { - float p = float(m_i + 1) / mesh_instances.size() * 0.1; + float p = float(m_i + 1) / MAX(1, mesh_instances.size()) * 0.1; p_step_function(0.3 + p, vformat(RTR("Plotting mesh into acceleration structure %d/%d"), m_i + 1, mesh_instances.size()), p_bake_userdata, false); } diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 175e213579..2bbd56776a 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -371,7 +371,7 @@ Ref<Script> CSharpLanguage::make_template(const String &p_template, const String return scr; } -Vector<ScriptLanguage::ScriptTemplate> CSharpLanguage::get_built_in_templates(StringName p_object) { +Vector<ScriptLanguage::ScriptTemplate> CSharpLanguage::get_built_in_templates(const StringName &p_object) { Vector<ScriptLanguage::ScriptTemplate> templates; #ifdef TOOLS_ENABLED for (int i = 0; i < TEMPLATES_ARRAY_SIZE; i++) { diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index 401f5c62e5..41e8d63be1 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -423,7 +423,7 @@ public: void get_string_delimiters(List<String> *p_delimiters) const override; bool is_using_templates() override; virtual Ref<Script> make_template(const String &p_template, const String &p_class_name, const String &p_base_class_name) const override; - virtual Vector<ScriptTemplate> get_built_in_templates(StringName p_object) override; + virtual Vector<ScriptTemplate> get_built_in_templates(const StringName &p_object) override; /* TODO */ bool validate(const String &p_script, const String &p_path, List<String> *r_functions, List<ScriptLanguage::ScriptError> *r_errors = nullptr, List<ScriptLanguage::Warning> *r_warnings = nullptr, HashSet<int> *r_safe_lines = nullptr) const override { return true; diff --git a/modules/multiplayer/scene_replication_interface.cpp b/modules/multiplayer/scene_replication_interface.cpp index c95e4ff9c9..31307e648d 100644 --- a/modules/multiplayer/scene_replication_interface.cpp +++ b/modules/multiplayer/scene_replication_interface.cpp @@ -707,7 +707,7 @@ MultiplayerSynchronizer *SceneReplicationInterface::_find_synchronizer(int p_pee return sync; } -void SceneReplicationInterface::_send_delta(int p_peer, const HashSet<ObjectID> p_synchronizers, uint64_t p_usec, const HashMap<ObjectID, uint64_t> p_last_watch_usecs) { +void SceneReplicationInterface::_send_delta(int p_peer, const HashSet<ObjectID> &p_synchronizers, uint64_t p_usec, const HashMap<ObjectID, uint64_t> &p_last_watch_usecs) { MAKE_ROOM(/* header */ 1 + /* element */ 4 + 8 + 4 + delta_mtu); uint8_t *ptr = packet_cache.ptrw(); ptr[0] = SceneMultiplayer::NETWORK_COMMAND_SYNC | (1 << SceneMultiplayer::CMD_FLAG_0_SHIFT); @@ -799,7 +799,7 @@ Error SceneReplicationInterface::on_delta_receive(int p_from, const uint8_t *p_b return OK; } -void SceneReplicationInterface::_send_sync(int p_peer, const HashSet<ObjectID> p_synchronizers, uint16_t p_sync_net_time, uint64_t p_usec) { +void SceneReplicationInterface::_send_sync(int p_peer, const HashSet<ObjectID> &p_synchronizers, uint16_t p_sync_net_time, uint64_t p_usec) { MAKE_ROOM(/* header */ 3 + /* element */ 4 + 4 + sync_mtu); uint8_t *ptr = packet_cache.ptrw(); ptr[0] = SceneMultiplayer::NETWORK_COMMAND_SYNC; diff --git a/modules/multiplayer/scene_replication_interface.h b/modules/multiplayer/scene_replication_interface.h index 3b3ec6a9ef..31211bb108 100644 --- a/modules/multiplayer/scene_replication_interface.h +++ b/modules/multiplayer/scene_replication_interface.h @@ -101,8 +101,8 @@ private: bool _verify_synchronizer(int p_peer, MultiplayerSynchronizer *p_sync, uint32_t &r_net_id); MultiplayerSynchronizer *_find_synchronizer(int p_peer, uint32_t p_net_ida); - void _send_sync(int p_peer, const HashSet<ObjectID> p_synchronizers, uint16_t p_sync_net_time, uint64_t p_usec); - void _send_delta(int p_peer, const HashSet<ObjectID> p_synchronizers, uint64_t p_usec, const HashMap<ObjectID, uint64_t> p_last_watch_usecs); + void _send_sync(int p_peer, const HashSet<ObjectID> &p_synchronizers, uint16_t p_sync_net_time, uint64_t p_usec); + void _send_delta(int p_peer, const HashSet<ObjectID> &p_synchronizers, uint64_t p_usec, const HashMap<ObjectID, uint64_t> &p_last_watch_usecs); Error _make_spawn_packet(Node *p_node, MultiplayerSpawner *p_spawner, int &r_len); Error _make_despawn_packet(Node *p_node, int &r_len); Error _send_raw(const uint8_t *p_buffer, int p_size, int p_peer, bool p_reliable); diff --git a/modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp b/modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp index 08fcdaf771..51642d8503 100644 --- a/modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp +++ b/modules/openxr/editor/openxr_select_interaction_profile_dialog.cpp @@ -77,7 +77,7 @@ void OpenXRSelectInteractionProfileDialog::open(PackedStringArray p_do_not_inclu // in with the new PackedStringArray interaction_profiles = OpenXRInteractionProfileMetadata::get_singleton()->get_interaction_profile_paths(); for (int i = 0; i < interaction_profiles.size(); i++) { - String path = interaction_profiles[i]; + const String &path = interaction_profiles[i]; if (!p_do_not_include.has(path)) { Button *ip_button = memnew(Button); ip_button->set_flat(true); diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 10d54e8d97..2bdf3073c9 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -1111,7 +1111,7 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p } for (int i = 0; i < feature_names.size(); i++) { - String feature_name = feature_names[i]; + const String &feature_name = feature_names[i]; bool feature_required = feature_required_list[i]; int feature_version = feature_versions[i]; bool has_version_attribute = feature_version != -1; @@ -2229,6 +2229,54 @@ String EditorExportPlatformAndroid::get_apksigner_path(int p_target_sdk, bool p_ return apksigner_path; } +static bool has_valid_keystore_credentials(String &r_error_str, const String &p_keystore, const String &p_username, const String &p_password, const String &p_type) { + String output; + List<String> args; + args.push_back("-list"); + args.push_back("-keystore"); + args.push_back(p_keystore); + args.push_back("-storepass"); + args.push_back(p_password); + args.push_back("-alias"); + args.push_back(p_username); + Error error = OS::get_singleton()->execute("keytool", args, &output, nullptr, true); + String keytool_error = "keytool error:"; + bool valid = output.substr(0, keytool_error.length()) != keytool_error; + + if (error != OK) { + r_error_str = TTR("Error: There was a problem validating the keystore username and password"); + return false; + } + if (!valid) { + r_error_str = TTR(p_type + " Username and/or Password is invalid for the given " + p_type + " Keystore"); + return false; + } + r_error_str = ""; + return true; +} + +bool EditorExportPlatformAndroid::has_valid_username_and_password(const Ref<EditorExportPreset> &p_preset, String &r_error) { + String dk = p_preset->get_or_env("keystore/debug", ENV_ANDROID_KEYSTORE_DEBUG_PATH); + String dk_user = p_preset->get_or_env("keystore/debug_user", ENV_ANDROID_KEYSTORE_DEBUG_USER); + String dk_password = p_preset->get_or_env("keystore/debug_password", ENV_ANDROID_KEYSTORE_DEBUG_PASS); + String rk = p_preset->get_or_env("keystore/release", ENV_ANDROID_KEYSTORE_RELEASE_PATH); + String rk_user = p_preset->get_or_env("keystore/release_user", ENV_ANDROID_KEYSTORE_RELEASE_USER); + String rk_password = p_preset->get_or_env("keystore/release_password", ENV_ANDROID_KEYSTORE_RELEASE_PASS); + + bool valid = true; + if (!dk.is_empty() && !dk_user.is_empty() && !dk_password.is_empty()) { + String err = ""; + valid = has_valid_keystore_credentials(err, dk, dk_user, dk_password, "Debug"); + r_error += err; + } + if (!rk.is_empty() && !rk_user.is_empty() && !rk_password.is_empty()) { + String err = ""; + valid = has_valid_keystore_credentials(err, rk, rk_user, rk_password, "Release"); + r_error += err; + } + return valid; +} + bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug) const { String err; bool valid = false; @@ -2842,6 +2890,11 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Unsupported export format!")); return ERR_UNCONFIGURED; } + String err_string; + if (!has_valid_username_and_password(p_preset, err_string)) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR(err_string)); + return ERR_UNCONFIGURED; + } if (use_gradle_build) { print_verbose("Starting gradle build..."); diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h index a2d0417c5d..5b585581b0 100644 --- a/platform/android/export/export_plugin.h +++ b/platform/android/export/export_plugin.h @@ -228,6 +228,7 @@ public: virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates, bool p_debug = false) const override; virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override; + static bool has_valid_username_and_password(const Ref<EditorExportPreset> &p_preset, String &r_error); virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override; diff --git a/platform/android/file_access_android.cpp b/platform/android/file_access_android.cpp index 1249f2219f..f56eda4694 100644 --- a/platform/android/file_access_android.cpp +++ b/platform/android/file_access_android.cpp @@ -121,6 +121,75 @@ uint8_t FileAccessAndroid::get_8() const { return byte; } +uint16_t FileAccessAndroid::get_16() const { + if (pos >= len) { + eof = true; + return 0; + } + + uint16_t bytes = 0; + int r = AAsset_read(asset, &bytes, 2); + + if (r >= 0) { + pos += r; + if (pos >= len) { + eof = true; + } + } + + if (big_endian) { + bytes = BSWAP16(bytes); + } + + return bytes; +} + +uint32_t FileAccessAndroid::get_32() const { + if (pos >= len) { + eof = true; + return 0; + } + + uint32_t bytes = 0; + int r = AAsset_read(asset, &bytes, 4); + + if (r >= 0) { + pos += r; + if (pos >= len) { + eof = true; + } + } + + if (big_endian) { + bytes = BSWAP32(bytes); + } + + return bytes; +} + +uint64_t FileAccessAndroid::get_64() const { + if (pos >= len) { + eof = true; + return 0; + } + + uint64_t bytes = 0; + int r = AAsset_read(asset, &bytes, 8); + + if (r >= 0) { + pos += r; + if (pos >= len) { + eof = true; + } + } + + if (big_endian) { + bytes = BSWAP64(bytes); + } + + return bytes; +} + uint64_t FileAccessAndroid::get_buffer(uint8_t *p_dst, uint64_t p_length) const { ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); @@ -151,6 +220,18 @@ void FileAccessAndroid::store_8(uint8_t p_dest) { ERR_FAIL(); } +void FileAccessAndroid::store_16(uint16_t p_dest) { + ERR_FAIL(); +} + +void FileAccessAndroid::store_32(uint32_t p_dest) { + ERR_FAIL(); +} + +void FileAccessAndroid::store_64(uint64_t p_dest) { + ERR_FAIL(); +} + bool FileAccessAndroid::file_exists(const String &p_path) { String path = fix_path(p_path).simplify_path(); if (path.begins_with("/")) { diff --git a/platform/android/file_access_android.h b/platform/android/file_access_android.h index 3aa4ca98fc..ec613b6687 100644 --- a/platform/android/file_access_android.h +++ b/platform/android/file_access_android.h @@ -66,12 +66,18 @@ public: virtual bool eof_reached() const override; // reading passed EOF virtual uint8_t get_8() const override; // get a byte + virtual uint16_t get_16() const override; + virtual uint32_t get_32() const override; + virtual uint64_t get_64() const override; virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; virtual Error get_error() const override; // get last error virtual void flush() override; virtual void store_8(uint8_t p_dest) override; // store a byte + virtual void store_16(uint16_t p_dest) override; + virtual void store_32(uint32_t p_dest) override; + virtual void store_64(uint64_t p_dest) override; virtual bool file_exists(const String &p_path) override; // return true if a file exists diff --git a/platform/android/file_access_filesystem_jandroid.cpp b/platform/android/file_access_filesystem_jandroid.cpp index beea73fd61..46d9728632 100644 --- a/platform/android/file_access_filesystem_jandroid.cpp +++ b/platform/android/file_access_filesystem_jandroid.cpp @@ -181,6 +181,36 @@ uint8_t FileAccessFilesystemJAndroid::get_8() const { return byte; } +uint16_t FileAccessFilesystemJAndroid::get_16() const { + ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); + uint16_t bytes = 0; + get_buffer(reinterpret_cast<uint8_t *>(&bytes), 2); + if (big_endian) { + bytes = BSWAP16(bytes); + } + return bytes; +} + +uint32_t FileAccessFilesystemJAndroid::get_32() const { + ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); + uint32_t bytes = 0; + get_buffer(reinterpret_cast<uint8_t *>(&bytes), 4); + if (big_endian) { + bytes = BSWAP32(bytes); + } + return bytes; +} + +uint64_t FileAccessFilesystemJAndroid::get_64() const { + ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use."); + uint64_t bytes = 0; + get_buffer(reinterpret_cast<uint8_t *>(&bytes), 8); + if (big_endian) { + bytes = BSWAP64(bytes); + } + return bytes; +} + String FileAccessFilesystemJAndroid::get_line() const { ERR_FAIL_COND_V_MSG(!is_open(), String(), "File must be opened before use."); @@ -250,6 +280,27 @@ void FileAccessFilesystemJAndroid::store_8(uint8_t p_dest) { store_buffer(&p_dest, 1); } +void FileAccessFilesystemJAndroid::store_16(uint16_t p_dest) { + if (big_endian) { + p_dest = BSWAP16(p_dest); + } + store_buffer(reinterpret_cast<uint8_t *>(&p_dest), 2); +} + +void FileAccessFilesystemJAndroid::store_32(uint32_t p_dest) { + if (big_endian) { + p_dest = BSWAP32(p_dest); + } + store_buffer(reinterpret_cast<uint8_t *>(&p_dest), 4); +} + +void FileAccessFilesystemJAndroid::store_64(uint64_t p_dest) { + if (big_endian) { + p_dest = BSWAP64(p_dest); + } + store_buffer(reinterpret_cast<uint8_t *>(&p_dest), 8); +} + void FileAccessFilesystemJAndroid::store_buffer(const uint8_t *p_src, uint64_t p_length) { if (_file_write) { ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use."); diff --git a/platform/android/file_access_filesystem_jandroid.h b/platform/android/file_access_filesystem_jandroid.h index 0c3f8d7259..f33aa64ebe 100644 --- a/platform/android/file_access_filesystem_jandroid.h +++ b/platform/android/file_access_filesystem_jandroid.h @@ -77,6 +77,9 @@ public: virtual bool eof_reached() const override; ///< reading passed EOF virtual uint8_t get_8() const override; ///< get a byte + virtual uint16_t get_16() const override; + virtual uint32_t get_32() const override; + virtual uint64_t get_64() const override; virtual String get_line() const override; ///< get a line virtual uint64_t get_buffer(uint8_t *p_dst, uint64_t p_length) const override; @@ -84,6 +87,9 @@ public: virtual void flush() override; virtual void store_8(uint8_t p_dest) override; ///< store a byte + virtual void store_16(uint16_t p_dest) override; + virtual void store_32(uint32_t p_dest) override; + virtual void store_64(uint64_t p_dest) override; virtual void store_buffer(const uint8_t *p_src, uint64_t p_length) override; virtual bool file_exists(const String &p_path) override; ///< return true if a file exists diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt index 217e7a2b60..da86e67c7d 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -484,6 +484,14 @@ class Godot(private val context: Context) : SensorEventListener { return containerLayout } + fun onStart(host: GodotHost) { + if (host != primaryHost) { + return + } + + renderView!!.onActivityStarted() + } + fun onResume(host: GodotHost) { if (host != primaryHost) { return @@ -528,6 +536,14 @@ class Godot(private val context: Context) : SensorEventListener { } } + fun onStop(host: GodotHost) { + if (host != primaryHost) { + return + } + + renderView!!.onActivityStopped() + } + fun onDestroy(primaryHost: GodotHost) { if (this.primaryHost != primaryHost) { return diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java index f1c029e7a1..643c9a658e 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotFragment.java @@ -271,6 +271,32 @@ public class GodotFragment extends Fragment implements IDownloaderClient, GodotH } @Override + public void onStop() { + super.onStop(); + if (!godot.isInitialized()) { + if (null != mDownloaderClientStub) { + mDownloaderClientStub.disconnect(getActivity()); + } + return; + } + + godot.onStop(this); + } + + @Override + public void onStart() { + super.onStart(); + if (!godot.isInitialized()) { + if (null != mDownloaderClientStub) { + mDownloaderClientStub.connect(getActivity()); + } + return; + } + + godot.onStart(this); + } + + @Override public void onResume() { super.onResume(); if (!godot.isInitialized()) { diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java index 52350c12a6..81043ce782 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotGLRenderView.java @@ -114,12 +114,30 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView @Override public void onActivityPaused() { - onPause(); + queueEvent(() -> { + GodotLib.focusout(); + // Pause the renderer + godotRenderer.onActivityPaused(); + }); + } + + @Override + public void onActivityStopped() { + pauseGLThread(); } @Override public void onActivityResumed() { - onResume(); + queueEvent(() -> { + // Resume the renderer + godotRenderer.onActivityResumed(); + GodotLib.focusin(); + }); + } + + @Override + public void onActivityStarted() { + resumeGLThread(); } @Override @@ -283,26 +301,4 @@ public class GodotGLRenderView extends GLSurfaceView implements GodotRenderView /* Set the renderer responsible for frame rendering */ setRenderer(godotRenderer); } - - @Override - public void onResume() { - super.onResume(); - - queueEvent(() -> { - // Resume the renderer - godotRenderer.onActivityResumed(); - GodotLib.focusin(); - }); - } - - @Override - public void onPause() { - super.onPause(); - - queueEvent(() -> { - GodotLib.focusout(); - // Pause the renderer - godotRenderer.onActivityPaused(); - }); - } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java index edcd9c4d1f..4b51bd778d 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java @@ -178,12 +178,10 @@ public class GodotIO { } public int[] getDisplaySafeArea() { - DisplayMetrics metrics = activity.getResources().getDisplayMetrics(); - Display display = activity.getWindowManager().getDefaultDisplay(); - Point size = new Point(); - display.getRealSize(size); + Rect rect = new Rect(); + activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect); - int[] result = { 0, 0, size.x, size.y }; + int[] result = { rect.left, rect.top, rect.right, rect.bottom }; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { WindowInsets insets = activity.getWindow().getDecorView().getRootWindowInsets(); DisplayCutout cutout = insets.getDisplayCutout(); diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java index ebf3a6b2fb..5b2f9f57c7 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotRenderView.java @@ -47,8 +47,13 @@ public interface GodotRenderView { void queueOnRenderThread(Runnable event); void onActivityPaused(); + + void onActivityStopped(); + void onActivityResumed(); + void onActivityStarted(); + void onBackPressed(); GodotInputHandler getInputHandler(); diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java index 48708152be..a1ee9bd6b4 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotVulkanRenderView.java @@ -92,12 +92,30 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV @Override public void onActivityPaused() { - onPause(); + queueOnVkThread(() -> { + GodotLib.focusout(); + // Pause the renderer + mRenderer.onVkPause(); + }); + } + + @Override + public void onActivityStopped() { + pauseRenderThread(); + } + + @Override + public void onActivityStarted() { + resumeRenderThread(); } @Override public void onActivityResumed() { - onResume(); + queueOnVkThread(() -> { + // Resume the renderer + mRenderer.onVkResume(); + GodotLib.focusin(); + }); } @Override @@ -211,26 +229,4 @@ public class GodotVulkanRenderView extends VkSurfaceView implements GodotRenderV } return super.onResolvePointerIcon(me, pointerIndex); } - - @Override - public void onResume() { - super.onResume(); - - queueOnVkThread(() -> { - // Resume the renderer - mRenderer.onVkResume(); - GodotLib.focusin(); - }); - } - - @Override - public void onPause() { - super.onPause(); - - queueOnVkThread(() -> { - GodotLib.focusout(); - // Pause the renderer - mRenderer.onVkPause(); - }); - } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java b/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java index 56397bb2c2..ef97aaeab9 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java +++ b/platform/android/java/lib/src/org/godotengine/godot/gl/GLSurfaceView.java @@ -122,8 +122,8 @@ import javax.microedition.khronos.opengles.GL10; * <p> * <h3>Activity Life-cycle</h3> * A GLSurfaceView must be notified when to pause and resume rendering. GLSurfaceView clients - * are required to call {@link #onPause()} when the activity stops and - * {@link #onResume()} when the activity starts. These calls allow GLSurfaceView to + * are required to call {@link #pauseGLThread()} when the activity stops and + * {@link #resumeGLThread()} when the activity starts. These calls allow GLSurfaceView to * pause and resume the rendering thread, and also allow GLSurfaceView to release and recreate * the OpenGL display. * <p> @@ -339,8 +339,8 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback * setRenderer is called: * <ul> * <li>{@link #getRenderMode()} - * <li>{@link #onPause()} - * <li>{@link #onResume()} + * <li>{@link #pauseGLThread()} + * <li>{@link #resumeGLThread()} * <li>{@link #queueEvent(Runnable)} * <li>{@link #requestRender()} * <li>{@link #setRenderMode(int)} @@ -568,6 +568,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback } + // -- GODOT start -- /** * Pause the rendering thread, optionally tearing down the EGL context * depending upon the value of {@link #setPreserveEGLContextOnPause(boolean)}. @@ -578,22 +579,23 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback * * Must not be called before a renderer has been set. */ - public void onPause() { + protected final void pauseGLThread() { mGLThread.onPause(); } /** * Resumes the rendering thread, re-creating the OpenGL context if necessary. It - * is the counterpart to {@link #onPause()}. + * is the counterpart to {@link #pauseGLThread()}. * * This method should typically be called in * {@link android.app.Activity#onStart Activity.onStart}. * * Must not be called before a renderer has been set. */ - public void onResume() { + protected final void resumeGLThread() { mGLThread.onResume(); } + // -- GODOT end -- /** * Queue a runnable to be run on the GL rendering thread. This can be used diff --git a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt index 3828004198..791b425444 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/vulkan/VkSurfaceView.kt @@ -99,7 +99,7 @@ open internal class VkSurfaceView(context: Context) : SurfaceView(context), Surf * * Must not be called before a [VkRenderer] has been set. */ - open fun onResume() { + protected fun resumeRenderThread() { vkThread.onResume() } @@ -108,7 +108,7 @@ open internal class VkSurfaceView(context: Context) : SurfaceView(context), Surf * * Must not be called before a [VkRenderer] has been set. */ - open fun onPause() { + protected fun pauseRenderThread() { vkThread.onPause() } diff --git a/platform/android/java_godot_wrapper.cpp b/platform/android/java_godot_wrapper.cpp index 4e401e633e..3c950bb1b1 100644 --- a/platform/android/java_godot_wrapper.cpp +++ b/platform/android/java_godot_wrapper.cpp @@ -334,7 +334,7 @@ void GodotJavaWrapper::vibrate(int p_duration_ms) { } } -int GodotJavaWrapper::create_new_godot_instance(List<String> args) { +int GodotJavaWrapper::create_new_godot_instance(const List<String> &args) { if (_create_new_godot_instance) { JNIEnv *env = get_jni_env(); ERR_FAIL_NULL_V(env, 0); diff --git a/platform/android/java_godot_wrapper.h b/platform/android/java_godot_wrapper.h index 52043c6027..93998021a9 100644 --- a/platform/android/java_godot_wrapper.h +++ b/platform/android/java_godot_wrapper.h @@ -104,7 +104,7 @@ public: void init_input_devices(); void vibrate(int p_duration_ms); String get_input_fallback_mapping(); - int create_new_godot_instance(List<String> args); + int create_new_godot_instance(const List<String> &args); void begin_benchmark_measure(const String &p_context, const String &p_label); void end_benchmark_measure(const String &p_context, const String &p_label); void dump_benchmark(const String &benchmark_file); diff --git a/platform/ios/doc_classes/EditorExportPlatformIOS.xml b/platform/ios/doc_classes/EditorExportPlatformIOS.xml index 8f16a8c03e..588b20669b 100644 --- a/platform/ios/doc_classes/EditorExportPlatformIOS.xml +++ b/platform/ios/doc_classes/EditorExportPlatformIOS.xml @@ -34,6 +34,9 @@ <member name="application/icon_interpolation" type="int" setter="" getter=""> Interpolation method used to resize application icon. </member> + <member name="application/min_ios_version" type="String" setter="" getter=""> + Minimum version of iOS required for this application to run in the [code]major.minor.patch[/code] or [code]major.minor[/code] format, can only contain numeric characters ([code]0-9[/code]) and periods ([code].[/code]). + </member> <member name="application/provisioning_profile_uuid_debug" type="String" setter="" getter=""> UUID of the provisioning profile. If left empty, Xcode will download or create a provisioning profile automatically. See [url=https://developer.apple.com/help/account/manage-profiles/edit-download-or-delete-profiles]Edit, download, or delete provisioning profiles[/url]. Can be overridden with the environment variable [code]GODOT_IOS_PROVISIONING_PROFILE_UUID_DEBUG[/code]. @@ -60,6 +63,14 @@ <member name="capabilities/access_wifi" type="bool" setter="" getter=""> If [code]true[/code], networking features related to Wi-Fi access are enabled. See [url=https://developer.apple.com/support/required-device-capabilities/]Required Device Capabilities[/url]. </member> + <member name="capabilities/performance_a12" type="bool" setter="" getter=""> + Requires the graphics performance and features of the A12 Bionic and later chips (devices supporting all Vulkan renderer features). + Enabling this option limits supported devices to: iPhone XS, iPhone XR, iPad Mini (5th gen.), iPad Air (3rd gen.), iPad (8th gen) and newer. + </member> + <member name="capabilities/performance_gaming_tier" type="bool" setter="" getter=""> + Requires the graphics performance and features of the A17 Pro and later chips. + Enabling this option limits supported devices to: iPhone 15 Pro and newer. + </member> <member name="capabilities/push_notifications" type="bool" setter="" getter=""> If [code]true[/code], push notifications are enabled. See [url=https://developer.apple.com/support/required-device-capabilities/]Required Device Capabilities[/url]. </member> diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp index d945eb73da..5be4d21411 100644 --- a/platform/ios/export/export_plugin.cpp +++ b/platform/ios/export/export_plugin.cpp @@ -152,6 +152,8 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), "")); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/min_ios_version"), "12.0")); + r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/icon_interpolation", PROPERTY_HINT_ENUM, "Nearest neighbor,Bilinear,Cubic,Trilinear,Lanczos"), 4)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "application/export_project_only"), false)); @@ -187,6 +189,8 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options) r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/access_wifi"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/push_notifications"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/performance_gaming_tier"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "capabilities/performance_a12"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data/accessible_from_files_app"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "user_data/accessible_from_itunes_sharing"), false)); @@ -252,6 +256,8 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ strnew += lines[i].replace("$short_version", p_preset->get_version("application/short_version")) + "\n"; } else if (lines[i].find("$version") != -1) { strnew += lines[i].replace("$version", p_preset->get_version("application/version")) + "\n"; + } else if (lines[i].find("$min_version") != -1) { + strnew += lines[i].replace("$min_version", p_preset->get("application/min_ios_version")) + "\n"; } else if (lines[i].find("$signature") != -1) { strnew += lines[i].replace("$signature", p_preset->get("application/signature")) + "\n"; } else if (lines[i].find("$team_id") != -1) { @@ -324,7 +330,12 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_ if ((bool)p_preset->get("capabilities/access_wifi") && !capabilities_list.has("wifi")) { capabilities_list.push_back("wifi"); } - + if ((bool)p_preset->get("capabilities/performance_gaming_tier") && !capabilities_list.has("iphone-performance-gaming-tier")) { + capabilities_list.push_back("iphone-performance-gaming-tier"); + } + if ((bool)p_preset->get("capabilities/performance_a12") && !capabilities_list.has("iphone-ipad-minimum-performance-a12")) { + capabilities_list.push_back("iphone-ipad-minimum-performance-a12"); + } for (int idx = 0; idx < capabilities_list.size(); idx++) { capabilities += "<string>" + capabilities_list[idx] + "</string>\n"; } @@ -1094,7 +1105,7 @@ Error EditorExportPlatformIOS::_copy_asset(const String &p_out_dir, const String Error EditorExportPlatformIOS::_export_additional_assets(const String &p_out_dir, const Vector<String> &p_assets, bool p_is_framework, bool p_should_embed, Vector<IOSExportAsset> &r_exported_assets) { for (int f_idx = 0; f_idx < p_assets.size(); ++f_idx) { - String asset = p_assets[f_idx]; + const String &asset = p_assets[f_idx]; if (asset.begins_with("res://")) { Error err = _copy_asset(p_out_dir, asset, nullptr, p_is_framework, p_should_embed, r_exported_assets); ERR_FAIL_COND_V(err != OK, err); diff --git a/platform/linuxbsd/freedesktop_portal_desktop.cpp b/platform/linuxbsd/freedesktop_portal_desktop.cpp index 6a5b5b8064..3641f20c70 100644 --- a/platform/linuxbsd/freedesktop_portal_desktop.cpp +++ b/platform/linuxbsd/freedesktop_portal_desktop.cpp @@ -162,7 +162,7 @@ void FreeDesktopPortalDesktop::append_dbus_dict_filters(DBusMessageIter *p_iter, append_dbus_string(&struct_iter, p_filter_names[i]); dbus_message_iter_open_container(&struct_iter, DBUS_TYPE_ARRAY, "(us)", &array_iter); - String flt = p_filter_exts[i]; + const String &flt = p_filter_exts[i]; int filter_slice_count = flt.get_slice_count(","); for (int j = 0; j < filter_slice_count; j++) { dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, nullptr, &array_struct_iter); diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index d22d398a67..aed8574902 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -316,7 +316,7 @@ Vector<String> OS_LinuxBSD::get_video_adapter_driver_info() const { continue; } String device_class = columns[1].trim_suffix(":"); - String vendor_device_id_mapping = columns[2]; + const String &vendor_device_id_mapping = columns[2]; #ifdef MODULE_REGEX_ENABLED if (regex_id_format.search(vendor_device_id_mapping).is_null()) { diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp index 6f9db1427b..6e750d0a16 100644 --- a/platform/macos/export/export_plugin.cpp +++ b/platform/macos/export/export_plugin.cpp @@ -2051,13 +2051,17 @@ bool EditorExportPlatformMacOS::has_valid_export_configuration(const Ref<EditorE String architecture = p_preset->get("binary_format/architecture"); if (architecture == "universal" || architecture == "x86_64") { if (!ResourceImporterTextureSettings::should_import_s3tc_bptc()) { + err += TTR("Cannot export for universal or x86_64 if S3TC BPTC texture format is disabled. Enable it in the Project Settings (Rendering > Textures > VRAM Compression > Import S3TC BPTC).") + "\n"; valid = false; } - } else if (architecture == "arm64") { + } + if (architecture == "universal" || architecture == "arm64") { if (!ResourceImporterTextureSettings::should_import_etc2_astc()) { + err += TTR("Cannot export for universal or arm64 if ETC2 ASTC texture format is disabled. Enable it in the Project Settings (Rendering > Textures > VRAM Compression > Import ETC2 ASTC).") + "\n"; valid = false; } - } else { + } + if (architecture != "universal" && architecture != "x86_64" && architecture != "arm64") { ERR_PRINT("Invalid architecture"); } diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp index a70812cf5b..ee170280e2 100644 --- a/platform/web/export/export_plugin.cpp +++ b/platform/web/export/export_plugin.cpp @@ -112,7 +112,7 @@ Error EditorExportPlatformWeb::_write_or_error(const uint8_t *p_content, int p_s return OK; } -void EditorExportPlatformWeb::_replace_strings(HashMap<String, String> p_replaces, Vector<uint8_t> &r_template) { +void EditorExportPlatformWeb::_replace_strings(const HashMap<String, String> &p_replaces, Vector<uint8_t> &r_template) { String str_template = String::utf8(reinterpret_cast<const char *>(r_template.ptr()), r_template.size()); String out; Vector<String> lines = str_template.split("\n"); diff --git a/platform/web/export/export_plugin.h b/platform/web/export/export_plugin.h index 887000ac45..9bb82d472e 100644 --- a/platform/web/export/export_plugin.h +++ b/platform/web/export/export_plugin.h @@ -90,7 +90,7 @@ class EditorExportPlatformWeb : public EditorExportPlatform { } Error _extract_template(const String &p_template, const String &p_dir, const String &p_name, bool pwa); - void _replace_strings(HashMap<String, String> p_replaces, Vector<uint8_t> &r_template); + void _replace_strings(const HashMap<String, String> &p_replaces, Vector<uint8_t> &r_template); void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes); Error _add_manifest_icon(const String &p_path, const String &p_icon, int p_size, Array &r_arr); Error _build_pwa(const Ref<EditorExportPreset> &p_preset, const String p_path, const Vector<SharedObject> &p_shared_objects); diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp index 2bec804c7c..a6c2cd7313 100644 --- a/platform/windows/display_server_windows.cpp +++ b/platform/windows/display_server_windows.cpp @@ -687,6 +687,8 @@ typedef struct { } EnumRectData; typedef struct { + Vector<DISPLAYCONFIG_PATH_INFO> paths; + Vector<DISPLAYCONFIG_MODE_INFO> modes; int count; int screen; float rate; @@ -738,12 +740,30 @@ static BOOL CALLBACK _MonitorEnumProcRefreshRate(HMONITOR hMonitor, HDC hdcMonit minfo.cbSize = sizeof(minfo); GetMonitorInfoW(hMonitor, &minfo); - DEVMODEW dm; - memset(&dm, 0, sizeof(dm)); - dm.dmSize = sizeof(dm); - EnumDisplaySettingsW(minfo.szDevice, ENUM_CURRENT_SETTINGS, &dm); + bool found = false; + for (const DISPLAYCONFIG_PATH_INFO &path : data->paths) { + DISPLAYCONFIG_SOURCE_DEVICE_NAME source_name; + memset(&source_name, 0, sizeof(source_name)); + source_name.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME; + source_name.header.size = sizeof(source_name); + source_name.header.adapterId = path.sourceInfo.adapterId; + source_name.header.id = path.sourceInfo.id; + if (DisplayConfigGetDeviceInfo(&source_name.header) == ERROR_SUCCESS) { + if (wcscmp(minfo.szDevice, source_name.viewGdiDeviceName) == 0 && path.targetInfo.refreshRate.Numerator != 0 && path.targetInfo.refreshRate.Denominator != 0) { + data->rate = (double)path.targetInfo.refreshRate.Numerator / (double)path.targetInfo.refreshRate.Denominator; + found = true; + break; + } + } + } + if (!found) { + DEVMODEW dm; + memset(&dm, 0, sizeof(dm)); + dm.dmSize = sizeof(dm); + EnumDisplaySettingsW(minfo.szDevice, ENUM_CURRENT_SETTINGS, &dm); - data->rate = dm.dmDisplayFrequency; + data->rate = dm.dmDisplayFrequency; + } } data->count++; @@ -932,7 +952,19 @@ float DisplayServerWindows::screen_get_refresh_rate(int p_screen) const { _THREAD_SAFE_METHOD_ p_screen = _get_screen_index(p_screen); - EnumRefreshRateData data = { 0, p_screen, SCREEN_REFRESH_RATE_FALLBACK }; + EnumRefreshRateData data = { Vector<DISPLAYCONFIG_PATH_INFO>(), Vector<DISPLAYCONFIG_MODE_INFO>(), 0, p_screen, SCREEN_REFRESH_RATE_FALLBACK }; + + uint32_t path_count = 0; + uint32_t mode_count = 0; + if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &path_count, &mode_count) == ERROR_SUCCESS) { + data.paths.resize(path_count); + data.modes.resize(mode_count); + if (QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &path_count, data.paths.ptrw(), &mode_count, data.modes.ptrw(), nullptr) != ERROR_SUCCESS) { + data.paths.clear(); + data.modes.clear(); + } + } + EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcRefreshRate, (LPARAM)&data); return data.rate; } diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp index ee33ff88d4..71da9cc520 100644 --- a/scene/2d/gpu_particles_2d.cpp +++ b/scene/2d/gpu_particles_2d.cpp @@ -712,12 +712,15 @@ void GPUParticles2D::_notification(int p_what) { } break; case NOTIFICATION_INTERNAL_PROCESS: { - RS::get_singleton()->particles_set_emitter_velocity(particles, - Vector3((get_global_position() - previous_position).x, - (get_global_position() - previous_position).y, - 0.0) / - get_process_delta_time()); + const Vector3 velocity = Vector3((get_global_position() - previous_position).x, (get_global_position() - previous_position).y, 0.0) / + get_process_delta_time(); + + if (velocity != previous_velocity) { + RS::get_singleton()->particles_set_emitter_velocity(particles, velocity); + previous_velocity = velocity; + } previous_position = get_global_position(); + if (one_shot) { time += get_process_delta_time(); if (time > emission_time) { diff --git a/scene/2d/gpu_particles_2d.h b/scene/2d/gpu_particles_2d.h index 40831cd30e..58996b0327 100644 --- a/scene/2d/gpu_particles_2d.h +++ b/scene/2d/gpu_particles_2d.h @@ -64,6 +64,7 @@ private: bool fractional_delta = false; bool interpolate = true; float interp_to_end_factor = 0; + Vector3 previous_velocity; Vector2 previous_position; #ifdef TOOLS_ENABLED bool show_visibility_rect = false; diff --git a/scene/2d/path_2d.cpp b/scene/2d/path_2d.cpp index ee0ccc42ff..a0e7e4cf25 100644 --- a/scene/2d/path_2d.cpp +++ b/scene/2d/path_2d.cpp @@ -146,8 +146,8 @@ void Path2D::_notification(int p_what) { for (int i = 0; i < sample_count; i++) { const Vector2 p = r[i].get_origin(); - const Vector2 side = r[i].columns[0]; - const Vector2 forward = r[i].columns[1]; + const Vector2 side = r[i].columns[1]; + const Vector2 forward = r[i].columns[0]; // Fish Bone. w[0] = p + (side - forward) * 5; @@ -232,8 +232,8 @@ void PathFollow2D::_update_transform() { if (rotates) { Transform2D xform = c->sample_baked_with_rotation(progress, cubic); - xform.translate_local(v_offset, h_offset); - set_rotation(xform[1].angle()); + xform.translate_local(h_offset, v_offset); + set_rotation(xform[0].angle()); set_position(xform[2]); } else { Vector2 pos = c->sample_baked(progress, cubic); diff --git a/scene/2d/polygon_2d.cpp b/scene/2d/polygon_2d.cpp index ee186de5f1..4266060466 100644 --- a/scene/2d/polygon_2d.cpp +++ b/scene/2d/polygon_2d.cpp @@ -375,14 +375,14 @@ void Polygon2D::_notification(int p_what) { // Compute transform between mesh and skeleton for runtime AABB compute. const Transform2D mesh_transform = get_global_transform(); const Transform2D skeleton_transform = skeleton_node->get_global_transform(); - const Transform2D mesh_to_sk2d = mesh_transform * skeleton_transform.affine_inverse(); + const Transform2D mesh_to_sk2d = skeleton_transform.affine_inverse() * mesh_transform; // Convert 2d transform to 3d. sd.mesh_to_skeleton_xform.basis.rows[0][0] = mesh_to_sk2d.columns[0][0]; - sd.mesh_to_skeleton_xform.basis.rows[0][1] = mesh_to_sk2d.columns[0][1]; + sd.mesh_to_skeleton_xform.basis.rows[1][0] = mesh_to_sk2d.columns[0][1]; sd.mesh_to_skeleton_xform.origin.x = mesh_to_sk2d.get_origin().x; - sd.mesh_to_skeleton_xform.basis.rows[1][0] = mesh_to_sk2d.columns[1][0]; + sd.mesh_to_skeleton_xform.basis.rows[0][1] = mesh_to_sk2d.columns[1][0]; sd.mesh_to_skeleton_xform.basis.rows[1][1] = mesh_to_sk2d.columns[1][1]; sd.mesh_to_skeleton_xform.origin.y = mesh_to_sk2d.get_origin().y; } diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp index f2be91cbf3..decbbab476 100644 --- a/scene/2d/tile_map.cpp +++ b/scene/2d/tile_map.cpp @@ -32,2858 +32,8 @@ #include "tile_map.compat.inc" #include "core/core_string_names.h" -#include "core/io/marshalls.h" -#include "scene/resources/world_2d.h" -#include "servers/navigation_server_2d.h" - -#ifdef DEBUG_ENABLED -#include "servers/navigation_server_3d.h" -#endif // DEBUG_ENABLED - -#ifdef DEBUG_ENABLED -/////////////////////////////// Debug ////////////////////////////////////////// -constexpr int TILE_MAP_DEBUG_QUADRANT_SIZE = 16; - -Vector2i TileMapLayer::_coords_to_debug_quadrant_coords(const Vector2i &p_coords) const { - return Vector2i( - p_coords.x > 0 ? p_coords.x / TILE_MAP_DEBUG_QUADRANT_SIZE : (p_coords.x - (TILE_MAP_DEBUG_QUADRANT_SIZE - 1)) / TILE_MAP_DEBUG_QUADRANT_SIZE, - p_coords.y > 0 ? p_coords.y / TILE_MAP_DEBUG_QUADRANT_SIZE : (p_coords.y - (TILE_MAP_DEBUG_QUADRANT_SIZE - 1)) / TILE_MAP_DEBUG_QUADRANT_SIZE); -} - -void TileMapLayer::_debug_update() { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - RenderingServer *rs = RenderingServer::get_singleton(); - - // Check if we should cleanup everything. - bool forced_cleanup = in_destructor || !enabled || !tile_map_node->is_inside_tree() || !tile_set.is_valid() || !tile_map_node->is_visible_in_tree(); - - if (forced_cleanup) { - for (KeyValue<Vector2i, Ref<DebugQuadrant>> &kv : debug_quadrant_map) { - // Free the quadrant. - Ref<DebugQuadrant> &debug_quadrant = kv.value; - if (debug_quadrant->canvas_item.is_valid()) { - rs->free(debug_quadrant->canvas_item); - } - } - debug_quadrant_map.clear(); - _debug_was_cleaned_up = true; - return; - } - - // Check if anything is dirty, in such a case, redraw debug. - bool anything_changed = false; - for (int i = 0; i < DIRTY_FLAGS_MAX; i++) { - if (dirty.flags[i]) { - anything_changed = true; - break; - } - } - - // List all debug quadrants to update, creating new ones if needed. - SelfList<DebugQuadrant>::List dirty_debug_quadrant_list; - - if (_debug_was_cleaned_up || anything_changed) { - // Update all cells. - for (KeyValue<Vector2i, CellData> &kv : tile_map) { - CellData &cell_data = kv.value; - _debug_quadrants_update_cell(cell_data, dirty_debug_quadrant_list); - } - } else { - // Update dirty cells. - for (SelfList<CellData> *cell_data_list_element = dirty.cell_list.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { - CellData &cell_data = *cell_data_list_element->self(); - _debug_quadrants_update_cell(cell_data, dirty_debug_quadrant_list); - } - } - - // Update those quadrants. - for (SelfList<DebugQuadrant> *quadrant_list_element = dirty_debug_quadrant_list.first(); quadrant_list_element;) { - SelfList<DebugQuadrant> *next_quadrant_list_element = quadrant_list_element->next(); // "Hack" to clear the list while iterating. - - DebugQuadrant &debug_quadrant = *quadrant_list_element->self(); - - // Check if the quadrant has a tile. - bool has_a_tile = false; - RID &ci = debug_quadrant.canvas_item; - for (SelfList<CellData> *cell_data_list_element = debug_quadrant.cells.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { - CellData &cell_data = *cell_data_list_element->self(); - if (cell_data.cell.source_id != TileSet::INVALID_SOURCE) { - has_a_tile = true; - break; - } - } - - if (has_a_tile) { - // Update the quadrant. - if (ci.is_valid()) { - rs->canvas_item_clear(ci); - } else { - ci = rs->canvas_item_create(); - rs->canvas_item_set_z_index(ci, RS::CANVAS_ITEM_Z_MAX - 1); - rs->canvas_item_set_parent(ci, tile_map_node->get_canvas_item()); - } - - const Vector2 quadrant_pos = tile_map_node->map_to_local(debug_quadrant.quadrant_coords * TILE_MAP_DEBUG_QUADRANT_SIZE); - Transform2D xform(0, quadrant_pos); - rs->canvas_item_set_transform(ci, xform); - - for (SelfList<CellData> *cell_data_list_element = debug_quadrant.cells.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { - CellData &cell_data = *cell_data_list_element->self(); - if (cell_data.cell.source_id != TileSet::INVALID_SOURCE) { - _rendering_draw_cell_debug(ci, quadrant_pos, cell_data); - _physics_draw_cell_debug(ci, quadrant_pos, cell_data); - _navigation_draw_cell_debug(ci, quadrant_pos, cell_data); - _scenes_draw_cell_debug(ci, quadrant_pos, cell_data); - } - } - } else { - // Free the quadrant. - if (ci.is_valid()) { - rs->free(ci); - } - quadrant_list_element->remove_from_list(); - debug_quadrant_map.erase(debug_quadrant.quadrant_coords); - } - - quadrant_list_element = next_quadrant_list_element; - } - - dirty_debug_quadrant_list.clear(); - - _debug_was_cleaned_up = false; -} - -void TileMapLayer::_debug_quadrants_update_cell(CellData &r_cell_data, SelfList<DebugQuadrant>::List &r_dirty_debug_quadrant_list) { - Vector2i quadrant_coords = _coords_to_debug_quadrant_coords(r_cell_data.coords); - - if (!debug_quadrant_map.has(quadrant_coords)) { - // Create a new quadrant and add it to the quadrant map. - Ref<DebugQuadrant> new_quadrant; - new_quadrant.instantiate(); - new_quadrant->quadrant_coords = quadrant_coords; - debug_quadrant_map[quadrant_coords] = new_quadrant; - } - - // Add the cell to its quadrant, if it is not already in there. - Ref<DebugQuadrant> &debug_quadrant = debug_quadrant_map[quadrant_coords]; - if (!r_cell_data.debug_quadrant_list_element.in_list()) { - debug_quadrant->cells.add(&r_cell_data.debug_quadrant_list_element); - } - - // Mark the quadrant as dirty. - if (!debug_quadrant->dirty_quadrant_list_element.in_list()) { - r_dirty_debug_quadrant_list.add(&debug_quadrant->dirty_quadrant_list_element); - } -} -#endif // DEBUG_ENABLED - -/////////////////////////////// Rendering ////////////////////////////////////// -void TileMapLayer::_rendering_update() { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - RenderingServer *rs = RenderingServer::get_singleton(); - - // Check if we should cleanup everything. - bool forced_cleanup = in_destructor || !enabled || !tile_map_node->is_inside_tree() || !tile_set.is_valid() || !tile_map_node->is_visible_in_tree(); - - // ----------- Layer level processing ----------- - if (forced_cleanup) { - // Cleanup. - if (canvas_item.is_valid()) { - rs->free(canvas_item); - canvas_item = RID(); - } - } else { - // Create/Update the layer's CanvasItem. - if (!canvas_item.is_valid()) { - RID ci = rs->canvas_item_create(); - rs->canvas_item_set_parent(ci, tile_map_node->get_canvas_item()); - canvas_item = ci; - } - RID &ci = canvas_item; - rs->canvas_item_set_draw_index(ci, layer_index_in_tile_map_node - (int64_t)0x80000000); - rs->canvas_item_set_sort_children_by_y(ci, y_sort_enabled); - rs->canvas_item_set_use_parent_material(ci, tile_map_node->get_use_parent_material() || tile_map_node->get_material().is_valid()); - rs->canvas_item_set_z_index(ci, z_index); - rs->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(tile_map_node->get_texture_filter_in_tree())); - rs->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(tile_map_node->get_texture_repeat_in_tree())); - rs->canvas_item_set_light_mask(ci, tile_map_node->get_light_mask()); - - // Modulate the layer. - Color layer_modulate = modulate; - int selected_layer = tile_map_node->get_selected_layer(); - if (selected_layer >= 0 && layer_index_in_tile_map_node != selected_layer) { - int z_selected = tile_map_node->get_layer_z_index(selected_layer); - if (z_index < z_selected || (z_index == z_selected && layer_index_in_tile_map_node < selected_layer)) { - layer_modulate = layer_modulate.darkened(0.5); - } else if (z_index > z_selected || (z_index == z_selected && layer_index_in_tile_map_node > selected_layer)) { - layer_modulate = layer_modulate.darkened(0.5); - layer_modulate.a *= 0.3; - } - } - rs->canvas_item_set_modulate(ci, layer_modulate); - } - - // ----------- Quadrants processing ----------- - - // List all rendering quadrants to update, creating new ones if needed. - SelfList<RenderingQuadrant>::List dirty_rendering_quadrant_list; - - // Check if anything changed that might change the quadrant shape. - // If so, recreate everything. - bool quandrant_shape_changed = dirty.flags[DIRTY_FLAGS_TILE_MAP_QUADRANT_SIZE] || - (tile_map_node->is_y_sort_enabled() && y_sort_enabled && (dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ENABLED] || dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ORIGIN] || dirty.flags[DIRTY_FLAGS_TILE_MAP_Y_SORT_ENABLED] || dirty.flags[DIRTY_FLAGS_TILE_MAP_LOCAL_XFORM] || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET])); - - // Free all quadrants. - if (forced_cleanup || quandrant_shape_changed) { - for (const KeyValue<Vector2i, Ref<RenderingQuadrant>> &kv : rendering_quadrant_map) { - for (int i = 0; i < kv.value->canvas_items.size(); i++) { - const RID &ci = kv.value->canvas_items[i]; - if (ci.is_valid()) { - rs->free(ci); - } - } - kv.value->cells.clear(); - } - rendering_quadrant_map.clear(); - _rendering_was_cleaned_up = true; - } - - if (!forced_cleanup) { - // List all quadrants to update, recreating them if needed. - if (dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET] || _rendering_was_cleaned_up) { - // Update all cells. - for (KeyValue<Vector2i, CellData> &kv : tile_map) { - CellData &cell_data = kv.value; - _rendering_quadrants_update_cell(cell_data, dirty_rendering_quadrant_list); - } - } else { - // Update dirty cells. - for (SelfList<CellData> *cell_data_list_element = dirty.cell_list.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { - CellData &cell_data = *cell_data_list_element->self(); - _rendering_quadrants_update_cell(cell_data, dirty_rendering_quadrant_list); - } - } - - // Update all dirty quadrants. - for (SelfList<RenderingQuadrant> *quadrant_list_element = dirty_rendering_quadrant_list.first(); quadrant_list_element;) { - SelfList<RenderingQuadrant> *next_quadrant_list_element = quadrant_list_element->next(); // "Hack" to clear the list while iterating. - - const Ref<RenderingQuadrant> &rendering_quadrant = quadrant_list_element->self(); - - // Check if the quadrant has a tile. - bool has_a_tile = false; - for (SelfList<CellData> *cell_data_list_element = rendering_quadrant->cells.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { - CellData &cell_data = *cell_data_list_element->self(); - if (cell_data.cell.source_id != TileSet::INVALID_SOURCE) { - has_a_tile = true; - break; - } - } - - if (has_a_tile) { - // Process the quadrant. - - // First, clear the quadrant's canvas items. - for (RID &ci : rendering_quadrant->canvas_items) { - rs->free(ci); - } - rendering_quadrant->canvas_items.clear(); - - // Sort the quadrant cells. - if (tile_map_node->is_y_sort_enabled() && is_y_sort_enabled()) { - // For compatibility reasons, we use another comparator for Y-sorted layers. - rendering_quadrant->cells.sort_custom<CellDataYSortedComparator>(); - } else { - rendering_quadrant->cells.sort(); - } - - // Those allow to group cell per material or z-index. - Ref<Material> prev_material; - int prev_z_index = 0; - RID prev_ci; - - for (SelfList<CellData> *cell_data_quadrant_list_element = rendering_quadrant->cells.first(); cell_data_quadrant_list_element; cell_data_quadrant_list_element = cell_data_quadrant_list_element->next()) { - CellData &cell_data = *cell_data_quadrant_list_element->self(); - - TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(*tile_set->get_source(cell_data.cell.source_id)); - - // Get the tile data. - const TileData *tile_data; - if (cell_data.runtime_tile_data_cache) { - tile_data = cell_data.runtime_tile_data_cache; - } else { - tile_data = atlas_source->get_tile_data(cell_data.cell.get_atlas_coords(), cell_data.cell.alternative_tile); - } - - Ref<Material> mat = tile_data->get_material(); - int tile_z_index = tile_data->get_z_index(); - - // Quandrant pos. - - // --- CanvasItems --- - RID ci; - - // Check if the material or the z_index changed. - if (prev_ci == RID() || prev_material != mat || prev_z_index != tile_z_index) { - // If so, create a new CanvasItem. - ci = rs->canvas_item_create(); - if (mat.is_valid()) { - rs->canvas_item_set_material(ci, mat->get_rid()); - } - rs->canvas_item_set_parent(ci, canvas_item); - rs->canvas_item_set_use_parent_material(ci, tile_map_node->get_use_parent_material() || tile_map_node->get_material().is_valid()); - - Transform2D xform(0, rendering_quadrant->canvas_items_position); - rs->canvas_item_set_transform(ci, xform); - - rs->canvas_item_set_light_mask(ci, tile_map_node->get_light_mask()); - rs->canvas_item_set_z_as_relative_to_parent(ci, true); - rs->canvas_item_set_z_index(ci, tile_z_index); - - rs->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(tile_map_node->get_texture_filter_in_tree())); - rs->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(tile_map_node->get_texture_repeat_in_tree())); - - rendering_quadrant->canvas_items.push_back(ci); - - prev_ci = ci; - prev_material = mat; - prev_z_index = tile_z_index; - - } else { - // Keep the same canvas_item to draw on. - ci = prev_ci; - } - - const Vector2 local_tile_pos = tile_map_node->map_to_local(cell_data.coords); - - // Random animation offset. - real_t random_animation_offset = 0.0; - if (atlas_source->get_tile_animation_mode(cell_data.cell.get_atlas_coords()) != TileSetAtlasSource::TILE_ANIMATION_MODE_DEFAULT) { - Array to_hash; - to_hash.push_back(local_tile_pos); - to_hash.push_back(get_instance_id()); // Use instance id as a random hash - random_animation_offset = RandomPCG(to_hash.hash()).randf(); - } - - // Drawing the tile in the canvas item. - tile_map_node->draw_tile(ci, local_tile_pos - rendering_quadrant->canvas_items_position, tile_set, cell_data.cell.source_id, cell_data.cell.get_atlas_coords(), cell_data.cell.alternative_tile, -1, tile_map_node->get_self_modulate(), tile_data, random_animation_offset); - } - } else { - // Free the quadrant. - for (int i = 0; i < rendering_quadrant->canvas_items.size(); i++) { - const RID &ci = rendering_quadrant->canvas_items[i]; - if (ci.is_valid()) { - rs->free(ci); - } - } - rendering_quadrant->cells.clear(); - rendering_quadrant_map.erase(rendering_quadrant->quadrant_coords); - } - - quadrant_list_element = next_quadrant_list_element; - } - - dirty_rendering_quadrant_list.clear(); - - // Reset the drawing indices. - { - int index = -(int64_t)0x80000000; // Always must be drawn below children. - - // Sort the quadrants coords per local coordinates. - RBMap<Vector2, Ref<RenderingQuadrant>, RenderingQuadrant::CoordsWorldComparator> local_to_map; - for (KeyValue<Vector2i, Ref<RenderingQuadrant>> &kv : rendering_quadrant_map) { - Ref<RenderingQuadrant> &rendering_quadrant = kv.value; - local_to_map[tile_map_node->map_to_local(rendering_quadrant->quadrant_coords)] = rendering_quadrant; - } - - // Sort the quadrants. - for (const KeyValue<Vector2, Ref<RenderingQuadrant>> &E : local_to_map) { - for (const RID &ci : E.value->canvas_items) { - RS::get_singleton()->canvas_item_set_draw_index(ci, index++); - } - } - } - - // Updates on TileMap changes. - if (dirty.flags[DIRTY_FLAGS_TILE_MAP_LIGHT_MASK] || - dirty.flags[DIRTY_FLAGS_TILE_MAP_USE_PARENT_MATERIAL] || - dirty.flags[DIRTY_FLAGS_TILE_MAP_MATERIAL] || - dirty.flags[DIRTY_FLAGS_TILE_MAP_TEXTURE_FILTER] || - dirty.flags[DIRTY_FLAGS_TILE_MAP_TEXTURE_REPEAT]) { - for (KeyValue<Vector2i, Ref<RenderingQuadrant>> &kv : rendering_quadrant_map) { - Ref<RenderingQuadrant> &rendering_quadrant = kv.value; - for (const RID &ci : rendering_quadrant->canvas_items) { - rs->canvas_item_set_light_mask(ci, tile_map_node->get_light_mask()); - rs->canvas_item_set_use_parent_material(ci, tile_map_node->get_use_parent_material() || tile_map_node->get_material().is_valid()); - rs->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(tile_map_node->get_texture_filter_in_tree())); - rs->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(tile_map_node->get_texture_repeat_in_tree())); - } - } - } - } - - // ----------- Occluders processing ----------- - if (forced_cleanup) { - // Clean everything. - for (KeyValue<Vector2i, CellData> &kv : tile_map) { - _rendering_occluders_clear_cell(kv.value); - } - } else { - if (_rendering_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET]) { - // Update all cells. - for (KeyValue<Vector2i, CellData> &kv : tile_map) { - _rendering_occluders_update_cell(kv.value); - } - } else { - // Update dirty cells. - for (SelfList<CellData> *cell_data_list_element = dirty.cell_list.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { - CellData &cell_data = *cell_data_list_element->self(); - _rendering_occluders_update_cell(cell_data); - } - } - - // Updates on TileMap changes. - if (dirty.flags[DIRTY_FLAGS_TILE_MAP_IN_CANVAS] || dirty.flags[DIRTY_FLAGS_TILE_MAP_VISIBILITY]) { - for (KeyValue<Vector2i, CellData> &kv : tile_map) { - CellData &cell_data = kv.value; - for (const RID &occluder : cell_data.occluders) { - if (occluder.is_null()) { - continue; - } - Transform2D xform(0, tile_map_node->map_to_local(kv.key)); - rs->canvas_light_occluder_attach_to_canvas(occluder, tile_map_node->get_canvas()); - rs->canvas_light_occluder_set_transform(occluder, tile_map_node->get_global_transform() * xform); - } - } - } - } - - // ----------- - // Mark the rendering state as up to date. - _rendering_was_cleaned_up = forced_cleanup; -} - -void TileMapLayer::_rendering_quadrants_update_cell(CellData &r_cell_data, SelfList<RenderingQuadrant>::List &r_dirty_rendering_quadrant_list) { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - - // Check if the cell is valid and retrieve its y_sort_origin. - bool is_valid = false; - int tile_y_sort_origin = 0; - TileSetSource *source; - if (tile_set->has_source(r_cell_data.cell.source_id)) { - source = *tile_set->get_source(r_cell_data.cell.source_id); - TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); - if (atlas_source && atlas_source->has_tile(r_cell_data.cell.get_atlas_coords()) && atlas_source->has_alternative_tile(r_cell_data.cell.get_atlas_coords(), r_cell_data.cell.alternative_tile)) { - is_valid = true; - const TileData *tile_data; - if (r_cell_data.runtime_tile_data_cache) { - tile_data = r_cell_data.runtime_tile_data_cache; - } else { - tile_data = atlas_source->get_tile_data(r_cell_data.cell.get_atlas_coords(), r_cell_data.cell.alternative_tile); - } - tile_y_sort_origin = tile_data->get_y_sort_origin(); - } - } - - if (is_valid) { - // Get the quadrant coords. - Vector2 canvas_items_position; - Vector2i quadrant_coords; - if (tile_map_node->is_y_sort_enabled() && is_y_sort_enabled()) { - canvas_items_position = Vector2(0, tile_map_node->map_to_local(r_cell_data.coords).y + tile_y_sort_origin + y_sort_origin); - quadrant_coords = canvas_items_position * 100; - } else { - int quad_size = tile_map_node->get_rendering_quadrant_size(); - const Vector2i &coords = r_cell_data.coords; - - // Rounding down, instead of simply rounding towards zero (truncating). - quadrant_coords = Vector2i( - coords.x > 0 ? coords.x / quad_size : (coords.x - (quad_size - 1)) / quad_size, - coords.y > 0 ? coords.y / quad_size : (coords.y - (quad_size - 1)) / quad_size); - canvas_items_position = quad_size * quadrant_coords; - } - - Ref<RenderingQuadrant> rendering_quadrant; - if (rendering_quadrant_map.has(quadrant_coords)) { - // Reuse existing rendering quadrant. - rendering_quadrant = rendering_quadrant_map[quadrant_coords]; - } else { - // Create a new rendering quadrant. - rendering_quadrant.instantiate(); - rendering_quadrant->quadrant_coords = quadrant_coords; - rendering_quadrant->canvas_items_position = canvas_items_position; - rendering_quadrant_map[quadrant_coords] = rendering_quadrant; - } - - // Mark the old quadrant as dirty (if it exists). - if (r_cell_data.rendering_quadrant.is_valid()) { - if (!r_cell_data.rendering_quadrant->dirty_quadrant_list_element.in_list()) { - r_dirty_rendering_quadrant_list.add(&r_cell_data.rendering_quadrant->dirty_quadrant_list_element); - } - } - - // Remove the cell from that quadrant. - if (r_cell_data.rendering_quadrant_list_element.in_list()) { - r_cell_data.rendering_quadrant_list_element.remove_from_list(); - } - - // Add the cell to its new quadrant. - r_cell_data.rendering_quadrant = rendering_quadrant; - r_cell_data.rendering_quadrant->cells.add(&r_cell_data.rendering_quadrant_list_element); - - // Add the new quadrant to the dirty quadrant list. - if (!rendering_quadrant->dirty_quadrant_list_element.in_list()) { - r_dirty_rendering_quadrant_list.add(&rendering_quadrant->dirty_quadrant_list_element); - } - } else { - Ref<RenderingQuadrant> rendering_quadrant = r_cell_data.rendering_quadrant; - - // Remove the cell from its quadrant. - r_cell_data.rendering_quadrant = Ref<RenderingQuadrant>(); - if (r_cell_data.rendering_quadrant_list_element.in_list()) { - rendering_quadrant->cells.remove(&r_cell_data.rendering_quadrant_list_element); - } - - if (rendering_quadrant.is_valid()) { - // Add the quadrant to the dirty quadrant list. - if (!rendering_quadrant->dirty_quadrant_list_element.in_list()) { - r_dirty_rendering_quadrant_list.add(&rendering_quadrant->dirty_quadrant_list_element); - } - } - } -} - -void TileMapLayer::_rendering_occluders_clear_cell(CellData &r_cell_data) { - RenderingServer *rs = RenderingServer::get_singleton(); - - // Free the occluders. - for (const RID &rid : r_cell_data.occluders) { - rs->free(rid); - } - r_cell_data.occluders.clear(); -} - -void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) { - bool node_visible = tile_map_node->is_visible_in_tree(); - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - RenderingServer *rs = RenderingServer::get_singleton(); - - // Free unused occluders then resize the occluders array. - for (uint32_t i = tile_set->get_occlusion_layers_count(); i < r_cell_data.occluders.size(); i++) { - RID occluder_id = r_cell_data.occluders[i]; - if (occluder_id.is_valid()) { - rs->free(occluder_id); - } - } - r_cell_data.occluders.resize(tile_set->get_occlusion_layers_count()); - - TileSetSource *source; - if (tile_set->has_source(r_cell_data.cell.source_id)) { - source = *tile_set->get_source(r_cell_data.cell.source_id); - - if (source->has_tile(r_cell_data.cell.get_atlas_coords()) && source->has_alternative_tile(r_cell_data.cell.get_atlas_coords(), r_cell_data.cell.alternative_tile)) { - TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); - if (atlas_source) { - // Get the tile data. - const TileData *tile_data; - if (r_cell_data.runtime_tile_data_cache) { - tile_data = r_cell_data.runtime_tile_data_cache; - } else { - tile_data = atlas_source->get_tile_data(r_cell_data.cell.get_atlas_coords(), r_cell_data.cell.alternative_tile); - } - - // Transform flags. - bool flip_h = (r_cell_data.cell.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_H); - bool flip_v = (r_cell_data.cell.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_V); - bool transpose = (r_cell_data.cell.alternative_tile & TileSetAtlasSource::TRANSFORM_TRANSPOSE); - - // Create, update or clear occluders. - for (uint32_t occlusion_layer_index = 0; occlusion_layer_index < r_cell_data.occluders.size(); occlusion_layer_index++) { - Ref<OccluderPolygon2D> occluder_polygon = tile_data->get_occluder(occlusion_layer_index); - - RID &occluder = r_cell_data.occluders[occlusion_layer_index]; - - if (occluder_polygon.is_valid()) { - // Create or update occluder. - Transform2D xform; - xform.set_origin(tile_map_node->map_to_local(r_cell_data.coords)); - if (!occluder.is_valid()) { - occluder = rs->canvas_light_occluder_create(); - } - rs->canvas_light_occluder_set_enabled(occluder, node_visible); - rs->canvas_light_occluder_set_transform(occluder, tile_map_node->get_global_transform() * xform); - rs->canvas_light_occluder_set_polygon(occluder, tile_data->get_occluder(occlusion_layer_index, flip_h, flip_v, transpose)->get_rid()); - rs->canvas_light_occluder_attach_to_canvas(occluder, tile_map_node->get_canvas()); - rs->canvas_light_occluder_set_light_mask(occluder, tile_set->get_occlusion_layer_light_mask(occlusion_layer_index)); - } else { - // Clear occluder. - if (occluder.is_valid()) { - rs->free(occluder); - occluder = RID(); - } - } - } - - return; - } - } - } - - // If we did not return earlier, clear the cell. - _rendering_occluders_clear_cell(r_cell_data); -} - -#ifdef DEBUG_ENABLED -void TileMapLayer::_rendering_draw_cell_debug(const RID &p_canvas_item, const Vector2i &p_quadrant_pos, const CellData &r_cell_data) { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - ERR_FAIL_COND(!tile_set.is_valid()); - - if (!Engine::get_singleton()->is_editor_hint()) { - return; - } - - // Draw a placeholder for tiles needing one. - RenderingServer *rs = RenderingServer::get_singleton(); - const TileMapCell &c = r_cell_data.cell; - - TileSetSource *source; - if (tile_set->has_source(c.source_id)) { - source = *tile_set->get_source(c.source_id); - - if (source->has_tile(c.get_atlas_coords()) && source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { - TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); - if (atlas_source) { - Vector2i grid_size = atlas_source->get_atlas_grid_size(); - if (!atlas_source->get_runtime_texture().is_valid() || c.get_atlas_coords().x >= grid_size.x || c.get_atlas_coords().y >= grid_size.y) { - // Generate a random color from the hashed values of the tiles. - Array to_hash; - to_hash.push_back(c.source_id); - to_hash.push_back(c.get_atlas_coords()); - to_hash.push_back(c.alternative_tile); - uint32_t hash = RandomPCG(to_hash.hash()).rand(); - - Color color; - color = color.from_hsv( - (float)((hash >> 24) & 0xFF) / 256.0, - Math::lerp(0.5, 1.0, (float)((hash >> 16) & 0xFF) / 256.0), - Math::lerp(0.5, 1.0, (float)((hash >> 8) & 0xFF) / 256.0), - 0.8); - - // Draw a placeholder tile. - Transform2D cell_to_quadrant; - cell_to_quadrant.set_origin(tile_map_node->map_to_local(r_cell_data.coords) - p_quadrant_pos); - rs->canvas_item_add_set_transform(p_canvas_item, cell_to_quadrant); - rs->canvas_item_add_circle(p_canvas_item, Vector2(), MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 4.0, color); - } - } - } - } -} -#endif // DEBUG_ENABLED - -/////////////////////////////// Physics ////////////////////////////////////// - -void TileMapLayer::_physics_update() { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - - // Check if we should cleanup everything. - bool forced_cleanup = in_destructor || !enabled || !tile_map_node->is_inside_tree() || !tile_set.is_valid(); - if (forced_cleanup) { - // Clean everything. - for (KeyValue<Vector2i, CellData> &kv : tile_map) { - _physics_clear_cell(kv.value); - } - } else { - if (_physics_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET] || dirty.flags[DIRTY_FLAGS_TILE_MAP_COLLISION_ANIMATABLE]) { - // Update all cells. - for (KeyValue<Vector2i, CellData> &kv : tile_map) { - _physics_update_cell(kv.value); - } - } else { - // Update dirty cells. - for (SelfList<CellData> *cell_data_list_element = dirty.cell_list.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { - CellData &cell_data = *cell_data_list_element->self(); - _physics_update_cell(cell_data); - } - } - } - - // ----------- - // Mark the physics state as up to date. - _physics_was_cleaned_up = forced_cleanup; -} - -void TileMapLayer::_physics_notify_tilemap_change(TileMapLayer::DirtyFlags p_what) { - Transform2D gl_transform = tile_map_node->get_global_transform(); - PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); - - bool in_editor = false; -#ifdef TOOLS_ENABLED - in_editor = Engine::get_singleton()->is_editor_hint(); -#endif - - if (p_what == DIRTY_FLAGS_TILE_MAP_XFORM) { - if (tile_map_node->is_inside_tree() && (!tile_map_node->is_collision_animatable() || in_editor)) { - // Move the collisison shapes along with the TileMap. - for (KeyValue<Vector2i, CellData> &kv : tile_map) { - const CellData &cell_data = kv.value; - - for (RID body : cell_data.bodies) { - if (body.is_valid()) { - Transform2D xform(0, tile_map_node->map_to_local(bodies_coords[body])); - xform = gl_transform * xform; - ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); - } - } - } - } - } else if (p_what == DIRTY_FLAGS_TILE_MAP_LOCAL_XFORM) { - // With collisions animatable, move the collisison shapes along with the TileMap only on local xform change (they are synchornized on physics tick instead). - if (tile_map_node->is_inside_tree() && tile_map_node->is_collision_animatable() && !in_editor) { - for (KeyValue<Vector2i, CellData> &kv : tile_map) { - const CellData &cell_data = kv.value; - - for (RID body : cell_data.bodies) { - if (body.is_valid()) { - Transform2D xform(0, tile_map_node->map_to_local(bodies_coords[body])); - xform = gl_transform * xform; - ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); - } - } - } - } - } else if (p_what == DIRTY_FLAGS_TILE_MAP_IN_TREE) { - // Changes in the tree may cause the space to change (e.g. when reparenting to a SubViewport). - if (tile_map_node->is_inside_tree()) { - RID space = tile_map_node->get_world_2d()->get_space(); - - for (KeyValue<Vector2i, CellData> &kv : tile_map) { - const CellData &cell_data = kv.value; - - for (RID body : cell_data.bodies) { - if (body.is_valid()) { - ps->body_set_space(body, space); - } - } - } - } - } -} - -void TileMapLayer::_physics_clear_cell(CellData &r_cell_data) { - PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); - - // Clear bodies. - for (RID body : r_cell_data.bodies) { - if (body.is_valid()) { - bodies_coords.erase(body); - ps->free(body); - } - } - r_cell_data.bodies.clear(); -} - -void TileMapLayer::_physics_update_cell(CellData &r_cell_data) { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - Transform2D gl_transform = tile_map_node->get_global_transform(); - RID space = tile_map_node->get_world_2d()->get_space(); - PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); - - // Recreate bodies and shapes. - TileMapCell &c = r_cell_data.cell; - - TileSetSource *source; - if (tile_set->has_source(c.source_id)) { - source = *tile_set->get_source(c.source_id); - - if (source->has_tile(c.get_atlas_coords()) && source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { - TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); - if (atlas_source) { - const TileData *tile_data; - if (r_cell_data.runtime_tile_data_cache) { - tile_data = r_cell_data.runtime_tile_data_cache; - } else { - tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile); - } - - // Transform flags. - bool flip_h = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_H); - bool flip_v = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_V); - bool transpose = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_TRANSPOSE); - - // Free unused bodies then resize the bodies array. - for (uint32_t i = tile_set->get_physics_layers_count(); i < r_cell_data.bodies.size(); i++) { - RID body = r_cell_data.bodies[i]; - if (body.is_valid()) { - bodies_coords.erase(body); - ps->free(body); - } - } - r_cell_data.bodies.resize(tile_set->get_physics_layers_count()); - - for (uint32_t tile_set_physics_layer = 0; tile_set_physics_layer < (uint32_t)tile_set->get_physics_layers_count(); tile_set_physics_layer++) { - Ref<PhysicsMaterial> physics_material = tile_set->get_physics_layer_physics_material(tile_set_physics_layer); - uint32_t physics_layer = tile_set->get_physics_layer_collision_layer(tile_set_physics_layer); - uint32_t physics_mask = tile_set->get_physics_layer_collision_mask(tile_set_physics_layer); - - RID body = r_cell_data.bodies[tile_set_physics_layer]; - if (tile_data->get_collision_polygons_count(tile_set_physics_layer) == 0) { - // No body needed, free it if it exists. - if (body.is_valid()) { - bodies_coords.erase(body); - ps->free(body); - } - body = RID(); - } else { - // Create or update the body. - if (!body.is_valid()) { - body = ps->body_create(); - } - bodies_coords[body] = r_cell_data.coords; - ps->body_set_mode(body, tile_map_node->is_collision_animatable() ? PhysicsServer2D::BODY_MODE_KINEMATIC : PhysicsServer2D::BODY_MODE_STATIC); - ps->body_set_space(body, space); - - Transform2D xform; - xform.set_origin(tile_map_node->map_to_local(r_cell_data.coords)); - xform = gl_transform * xform; - ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); - - ps->body_attach_object_instance_id(body, tile_map_node->get_instance_id()); - ps->body_set_collision_layer(body, physics_layer); - ps->body_set_collision_mask(body, physics_mask); - ps->body_set_pickable(body, false); - ps->body_set_state(body, PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, tile_data->get_constant_linear_velocity(tile_set_physics_layer)); - ps->body_set_state(body, PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, tile_data->get_constant_angular_velocity(tile_set_physics_layer)); - - if (!physics_material.is_valid()) { - ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, 0); - ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, 1); - } else { - ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, physics_material->computed_bounce()); - ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, physics_material->computed_friction()); - } - - // Clear body's shape if needed. - ps->body_clear_shapes(body); - - // Add the shapes to the body. - int body_shape_index = 0; - for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(tile_set_physics_layer); polygon_index++) { - // Iterate over the polygons. - bool one_way_collision = tile_data->is_collision_polygon_one_way(tile_set_physics_layer, polygon_index); - float one_way_collision_margin = tile_data->get_collision_polygon_one_way_margin(tile_set_physics_layer, polygon_index); - int shapes_count = tile_data->get_collision_polygon_shapes_count(tile_set_physics_layer, polygon_index); - for (int shape_index = 0; shape_index < shapes_count; shape_index++) { - // Add decomposed convex shapes. - Ref<ConvexPolygonShape2D> shape = tile_data->get_collision_polygon_shape(tile_set_physics_layer, polygon_index, shape_index, flip_h, flip_v, transpose); - ps->body_add_shape(body, shape->get_rid()); - ps->body_set_shape_as_one_way_collision(body, body_shape_index, one_way_collision, one_way_collision_margin); - - body_shape_index++; - } - } - } - - // Set the body again. - r_cell_data.bodies[tile_set_physics_layer] = body; - } - - return; - } - } - } - - // If we did not return earlier, clear the cell. - _physics_clear_cell(r_cell_data); -} - -#ifdef DEBUG_ENABLED -void TileMapLayer::_physics_draw_cell_debug(const RID &p_canvas_item, const Vector2i &p_quadrant_pos, const CellData &r_cell_data) { - // Draw the debug collision shapes. - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - ERR_FAIL_COND(!tile_set.is_valid()); - - if (!tile_map_node->get_tree()) { - return; - } - - bool show_collision = false; - switch (tile_map_node->get_collision_visibility_mode()) { - case TileMap::VISIBILITY_MODE_DEFAULT: - show_collision = !Engine::get_singleton()->is_editor_hint() && tile_map_node->get_tree()->is_debugging_collisions_hint(); - break; - case TileMap::VISIBILITY_MODE_FORCE_HIDE: - show_collision = false; - break; - case TileMap::VISIBILITY_MODE_FORCE_SHOW: - show_collision = true; - break; - } - if (!show_collision) { - return; - } - - RenderingServer *rs = RenderingServer::get_singleton(); - PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); - - Color debug_collision_color = tile_map_node->get_tree()->get_debug_collisions_color(); - Vector<Color> color; - color.push_back(debug_collision_color); - - Transform2D quadrant_to_local(0, p_quadrant_pos); - Transform2D global_to_quadrant = (tile_map_node->get_global_transform() * quadrant_to_local).affine_inverse(); - - for (RID body : r_cell_data.bodies) { - if (body.is_valid()) { - Transform2D body_to_quadrant = global_to_quadrant * Transform2D(ps->body_get_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM)); - rs->canvas_item_add_set_transform(p_canvas_item, body_to_quadrant); - for (int shape_index = 0; shape_index < ps->body_get_shape_count(body); shape_index++) { - const RID &shape = ps->body_get_shape(body, shape_index); - const PhysicsServer2D::ShapeType &type = ps->shape_get_type(shape); - if (type == PhysicsServer2D::SHAPE_CONVEX_POLYGON) { - rs->canvas_item_add_polygon(p_canvas_item, ps->shape_get_data(shape), color); - } else { - WARN_PRINT("Wrong shape type for a tile, should be SHAPE_CONVEX_POLYGON."); - } - } - rs->canvas_item_add_set_transform(p_canvas_item, Transform2D()); - } - } -}; -#endif // DEBUG_ENABLED - -/////////////////////////////// Navigation ////////////////////////////////////// - -void TileMapLayer::_navigation_update() { - ERR_FAIL_NULL(NavigationServer2D::get_singleton()); - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - NavigationServer2D *ns = NavigationServer2D::get_singleton(); - - // Check if we should cleanup everything. - bool forced_cleanup = in_destructor || !enabled || !navigation_enabled || !tile_map_node->is_inside_tree() || !tile_set.is_valid(); - - // ----------- Layer level processing ----------- - if (forced_cleanup) { - if (navigation_map.is_valid() && !uses_world_navigation_map) { - ns->free(navigation_map); - navigation_map = RID(); - } - } else { - // Update navigation maps. - if (!navigation_map.is_valid()) { - if (layer_index_in_tile_map_node == 0) { - // Use the default World2D navigation map for the first layer when empty. - navigation_map = tile_map_node->get_world_2d()->get_navigation_map(); - uses_world_navigation_map = true; - } else { - RID new_layer_map = ns->map_create(); - // Set the default NavigationPolygon cell_size on the new map as a mismatch causes an error. - ns->map_set_cell_size(new_layer_map, 1.0); - ns->map_set_active(new_layer_map, true); - navigation_map = new_layer_map; - uses_world_navigation_map = false; - } - } - } - - // ----------- Navigation regions processing ----------- - if (forced_cleanup) { - // Clean everything. - for (KeyValue<Vector2i, CellData> &kv : tile_map) { - _navigation_clear_cell(kv.value); - } - } else { - if (_navigation_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET]) { - // Update all cells. - for (KeyValue<Vector2i, CellData> &kv : tile_map) { - _navigation_update_cell(kv.value); - } - } else { - // Update dirty cells. - for (SelfList<CellData> *cell_data_list_element = dirty.cell_list.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { - CellData &cell_data = *cell_data_list_element->self(); - _navigation_update_cell(cell_data); - } - } - - if (dirty.flags[DIRTY_FLAGS_TILE_MAP_XFORM]) { - Transform2D tilemap_xform = tile_map_node->get_global_transform(); - for (KeyValue<Vector2i, CellData> &kv : tile_map) { - const CellData &cell_data = kv.value; - // Update navigation regions transform. - for (const RID ®ion : cell_data.navigation_regions) { - if (!region.is_valid()) { - continue; - } - Transform2D tile_transform; - tile_transform.set_origin(tile_map_node->map_to_local(kv.key)); - NavigationServer2D::get_singleton()->region_set_transform(region, tilemap_xform * tile_transform); - } - } - } - } - - // ----------- - // Mark the navigation state as up to date. - _navigation_was_cleaned_up = forced_cleanup; -} - -void TileMapLayer::_navigation_clear_cell(CellData &r_cell_data) { - NavigationServer2D *ns = NavigationServer2D::get_singleton(); - // Clear navigation shapes. - for (uint32_t i = 0; i < r_cell_data.navigation_regions.size(); i++) { - const RID ®ion = r_cell_data.navigation_regions[i]; - if (region.is_valid()) { - ns->region_set_map(region, RID()); - ns->free(region); - } - } - r_cell_data.navigation_regions.clear(); -} - -void TileMapLayer::_navigation_update_cell(CellData &r_cell_data) { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - NavigationServer2D *ns = NavigationServer2D::get_singleton(); - Transform2D tilemap_xform = tile_map_node->get_global_transform(); - - // Get the navigation polygons and create regions. - TileMapCell &c = r_cell_data.cell; - - TileSetSource *source; - if (tile_set->has_source(c.source_id)) { - source = *tile_set->get_source(c.source_id); - - if (source->has_tile(c.get_atlas_coords()) && source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { - TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); - if (atlas_source) { - const TileData *tile_data; - if (r_cell_data.runtime_tile_data_cache) { - tile_data = r_cell_data.runtime_tile_data_cache; - } else { - tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile); - } - - // Transform flags. - bool flip_h = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_H); - bool flip_v = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_V); - bool transpose = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_TRANSPOSE); - - // Free unused regions then resize the regions array. - for (uint32_t i = tile_set->get_navigation_layers_count(); i < r_cell_data.navigation_regions.size(); i++) { - RID ®ion = r_cell_data.navigation_regions[i]; - if (region.is_valid()) { - ns->region_set_map(region, RID()); - ns->free(region); - region = RID(); - } - } - r_cell_data.navigation_regions.resize(tile_set->get_navigation_layers_count()); - - // Create, update or clear regions. - for (uint32_t navigation_layer_index = 0; navigation_layer_index < r_cell_data.navigation_regions.size(); navigation_layer_index++) { - Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(navigation_layer_index, flip_h, flip_v, transpose); - - RID ®ion = r_cell_data.navigation_regions[navigation_layer_index]; - - if (navigation_polygon.is_valid() && (navigation_polygon->get_polygon_count() > 0 || navigation_polygon->get_outline_count() > 0)) { - // Create or update regions. - Transform2D tile_transform; - tile_transform.set_origin(tile_map_node->map_to_local(r_cell_data.coords)); - if (!region.is_valid()) { - region = ns->region_create(); - } - ns->region_set_owner_id(region, tile_map_node->get_instance_id()); - ns->region_set_map(region, navigation_map); - ns->region_set_transform(region, tilemap_xform * tile_transform); - ns->region_set_navigation_layers(region, tile_set->get_navigation_layer_layers(navigation_layer_index)); - ns->region_set_navigation_polygon(region, navigation_polygon); - } else { - // Clear region. - if (region.is_valid()) { - ns->region_set_map(region, RID()); - ns->free(region); - region = RID(); - } - } - } - - return; - } - } - } - - // If we did not return earlier, clear the cell. - _navigation_clear_cell(r_cell_data); -} - -#ifdef DEBUG_ENABLED -void TileMapLayer::_navigation_draw_cell_debug(const RID &p_canvas_item, const Vector2i &p_quadrant_pos, const CellData &r_cell_data) { - // Draw the debug collision shapes. - bool show_navigation = false; - switch (tile_map_node->get_navigation_visibility_mode()) { - case TileMap::VISIBILITY_MODE_DEFAULT: - show_navigation = !Engine::get_singleton()->is_editor_hint() && tile_map_node->get_tree()->is_debugging_navigation_hint(); - break; - case TileMap::VISIBILITY_MODE_FORCE_HIDE: - show_navigation = false; - break; - case TileMap::VISIBILITY_MODE_FORCE_SHOW: - show_navigation = true; - break; - } - if (!show_navigation) { - return; - } - - // Check if the navigation is used. - if (r_cell_data.navigation_regions.is_empty()) { - return; - } - - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - - RenderingServer *rs = RenderingServer::get_singleton(); - const NavigationServer2D *ns2d = NavigationServer2D::get_singleton(); - - bool enabled_geometry_face_random_color = ns2d->get_debug_navigation_enable_geometry_face_random_color(); - bool enabled_edge_lines = ns2d->get_debug_navigation_enable_edge_lines(); - - Color debug_face_color = ns2d->get_debug_navigation_geometry_face_color(); - Color debug_edge_color = ns2d->get_debug_navigation_geometry_edge_color(); - - RandomPCG rand; - - const TileMapCell &c = r_cell_data.cell; - - TileSetSource *source; - if (tile_set->has_source(c.source_id)) { - source = *tile_set->get_source(c.source_id); - - if (source->has_tile(c.get_atlas_coords()) && source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { - TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); - if (atlas_source) { - const TileData *tile_data; - if (r_cell_data.runtime_tile_data_cache) { - tile_data = r_cell_data.runtime_tile_data_cache; - } else { - tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile); - } - - Transform2D cell_to_quadrant; - cell_to_quadrant.set_origin(tile_map_node->map_to_local(r_cell_data.coords) - p_quadrant_pos); - rs->canvas_item_add_set_transform(p_canvas_item, cell_to_quadrant); - - for (int layer_index = 0; layer_index < tile_set->get_navigation_layers_count(); layer_index++) { - bool flip_h = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_H); - bool flip_v = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_V); - bool transpose = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_TRANSPOSE); - Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(layer_index, flip_h, flip_v, transpose); - if (navigation_polygon.is_valid()) { - Vector<Vector2> navigation_polygon_vertices = navigation_polygon->get_vertices(); - if (navigation_polygon_vertices.size() < 3) { - continue; - } - - for (int i = 0; i < navigation_polygon->get_polygon_count(); i++) { - // An array of vertices for this polygon. - Vector<int> polygon = navigation_polygon->get_polygon(i); - Vector<Vector2> debug_polygon_vertices; - debug_polygon_vertices.resize(polygon.size()); - for (int j = 0; j < polygon.size(); j++) { - ERR_FAIL_INDEX(polygon[j], navigation_polygon_vertices.size()); - debug_polygon_vertices.write[j] = navigation_polygon_vertices[polygon[j]]; - } - - // Generate the polygon color, slightly randomly modified from the settings one. - Color random_variation_color = debug_face_color; - if (enabled_geometry_face_random_color) { - random_variation_color.set_hsv( - debug_face_color.get_h() + rand.random(-1.0, 1.0) * 0.1, - debug_face_color.get_s(), - debug_face_color.get_v() + rand.random(-1.0, 1.0) * 0.2); - } - random_variation_color.a = debug_face_color.a; - - Vector<Color> debug_face_colors; - debug_face_colors.push_back(random_variation_color); - rs->canvas_item_add_polygon(p_canvas_item, debug_polygon_vertices, debug_face_colors); - - if (enabled_edge_lines) { - Vector<Color> debug_edge_colors; - debug_edge_colors.push_back(debug_edge_color); - debug_polygon_vertices.push_back(debug_polygon_vertices[0]); // Add first again for closing polyline. - rs->canvas_item_add_polyline(p_canvas_item, debug_polygon_vertices, debug_edge_colors); - } - } - } - } - } - } - } -} -#endif // DEBUG_ENABLED - -/////////////////////////////// Scenes ////////////////////////////////////// - -void TileMapLayer::_scenes_update() { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - - // Check if we should cleanup everything. - bool forced_cleanup = in_destructor || !enabled || !tile_map_node->is_inside_tree() || !tile_set.is_valid(); - - if (forced_cleanup) { - // Clean everything. - for (KeyValue<Vector2i, CellData> &kv : tile_map) { - _scenes_clear_cell(kv.value); - } - } else { - if (_scenes_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET]) { - // Update all cells. - for (KeyValue<Vector2i, CellData> &kv : tile_map) { - _scenes_update_cell(kv.value); - } - } else { - // Update dirty cells. - for (SelfList<CellData> *cell_data_list_element = dirty.cell_list.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { - CellData &cell_data = *cell_data_list_element->self(); - _scenes_update_cell(cell_data); - } - } - } - - // ----------- - // Mark the scenes state as up to date. - _scenes_was_cleaned_up = forced_cleanup; -} - -void TileMapLayer::_scenes_clear_cell(CellData &r_cell_data) { - // Cleanup existing scene. - Node *node = tile_map_node->get_node_or_null(r_cell_data.scene); - if (node) { - node->queue_free(); - } - r_cell_data.scene = ""; -} - -void TileMapLayer::_scenes_update_cell(CellData &r_cell_data) { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - - // Clear the scene in any case. - _scenes_clear_cell(r_cell_data); - - // Create the scene. - const TileMapCell &c = r_cell_data.cell; - - TileSetSource *source; - if (tile_set->has_source(c.source_id)) { - source = *tile_set->get_source(c.source_id); - - if (source->has_tile(c.get_atlas_coords()) && source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { - TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source); - if (scenes_collection_source) { - Ref<PackedScene> packed_scene = scenes_collection_source->get_scene_tile_scene(c.alternative_tile); - if (packed_scene.is_valid()) { - Node *scene = packed_scene->instantiate(); - Control *scene_as_control = Object::cast_to<Control>(scene); - Node2D *scene_as_node2d = Object::cast_to<Node2D>(scene); - if (scene_as_control) { - scene_as_control->set_position(tile_map_node->map_to_local(r_cell_data.coords) + scene_as_control->get_position()); - } else if (scene_as_node2d) { - Transform2D xform; - xform.set_origin(tile_map_node->map_to_local(r_cell_data.coords)); - scene_as_node2d->set_transform(xform * scene_as_node2d->get_transform()); - } - tile_map_node->add_child(scene); - r_cell_data.scene = scene->get_name(); - } - } - } - } -} - -#ifdef DEBUG_ENABLED -void TileMapLayer::_scenes_draw_cell_debug(const RID &p_canvas_item, const Vector2i &p_quadrant_pos, const CellData &r_cell_data) { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - ERR_FAIL_COND(!tile_set.is_valid()); - - if (!Engine::get_singleton()->is_editor_hint()) { - return; - } - - // Draw a placeholder for scenes needing one. - RenderingServer *rs = RenderingServer::get_singleton(); - - const TileMapCell &c = r_cell_data.cell; - - TileSetSource *source; - if (tile_set->has_source(c.source_id)) { - source = *tile_set->get_source(c.source_id); - - if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { - return; - } - - TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source); - if (scenes_collection_source) { - if (!scenes_collection_source->get_scene_tile_scene(c.alternative_tile).is_valid() || scenes_collection_source->get_scene_tile_display_placeholder(c.alternative_tile)) { - // Generate a random color from the hashed values of the tiles. - Array to_hash; - to_hash.push_back(c.source_id); - to_hash.push_back(c.alternative_tile); - uint32_t hash = RandomPCG(to_hash.hash()).rand(); - - Color color; - color = color.from_hsv( - (float)((hash >> 24) & 0xFF) / 256.0, - Math::lerp(0.5, 1.0, (float)((hash >> 16) & 0xFF) / 256.0), - Math::lerp(0.5, 1.0, (float)((hash >> 8) & 0xFF) / 256.0), - 0.8); - - // Draw a placeholder tile. - Transform2D cell_to_quadrant; - cell_to_quadrant.set_origin(tile_map_node->map_to_local(r_cell_data.coords) - p_quadrant_pos); - rs->canvas_item_add_set_transform(p_canvas_item, cell_to_quadrant); - rs->canvas_item_add_circle(p_canvas_item, Vector2(), MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 4.0, color); - } - } - } -} -#endif // DEBUG_ENABLED - -///////////////////////////////////////////////////////////////////// - -void TileMapLayer::_build_runtime_update_tile_data() { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - - // Check if we should cleanup everything. - bool forced_cleanup = in_destructor || !enabled || !tile_map_node->is_inside_tree() || !tile_set.is_valid() || !tile_map_node->is_visible_in_tree(); - if (!forced_cleanup) { - if (tile_map_node->GDVIRTUAL_IS_OVERRIDDEN(_use_tile_data_runtime_update) && tile_map_node->GDVIRTUAL_IS_OVERRIDDEN(_tile_data_runtime_update)) { - if (_runtime_update_tile_data_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET]) { - for (KeyValue<Vector2i, CellData> &E : tile_map) { - _build_runtime_update_tile_data_for_cell(E.value); - } - } else if (dirty.flags[DIRTY_FLAGS_TILE_MAP_RUNTIME_UPDATE]) { - for (KeyValue<Vector2i, CellData> &E : tile_map) { - _build_runtime_update_tile_data_for_cell(E.value, true); - } - } else { - for (SelfList<CellData> *cell_data_list_element = dirty.cell_list.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { - CellData &cell_data = *cell_data_list_element->self(); - _build_runtime_update_tile_data_for_cell(cell_data); - } - } - } - } - - // ----------- - // Mark the navigation state as up to date. - _runtime_update_tile_data_was_cleaned_up = forced_cleanup; -} - -void TileMapLayer::_build_runtime_update_tile_data_for_cell(CellData &r_cell_data, bool p_auto_add_to_dirty_list) { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - - TileMapCell &c = r_cell_data.cell; - TileSetSource *source; - if (tile_set->has_source(c.source_id)) { - source = *tile_set->get_source(c.source_id); - - if (source->has_tile(c.get_atlas_coords()) && source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { - TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); - if (atlas_source) { - bool ret = false; - if (tile_map_node->GDVIRTUAL_CALL(_use_tile_data_runtime_update, layer_index_in_tile_map_node, r_cell_data.coords, ret) && ret) { - TileData *tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile); - - // Create the runtime TileData. - TileData *tile_data_runtime_use = tile_data->duplicate(); - tile_data_runtime_use->set_allow_transform(true); - r_cell_data.runtime_tile_data_cache = tile_data_runtime_use; - - tile_map_node->GDVIRTUAL_CALL(_tile_data_runtime_update, layer_index_in_tile_map_node, r_cell_data.coords, tile_data_runtime_use); - - if (p_auto_add_to_dirty_list) { - dirty.cell_list.add(&r_cell_data.dirty_list_element); - } - } - } - } - } -} - -void TileMapLayer::_clear_runtime_update_tile_data() { - for (SelfList<CellData> *cell_data_list_element = dirty.cell_list.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { - CellData &cell_data = *cell_data_list_element->self(); - - // Clear the runtime tile data. - if (cell_data.runtime_tile_data_cache) { - memdelete(cell_data.runtime_tile_data_cache); - cell_data.runtime_tile_data_cache = nullptr; - } - } -} - -TileSet::TerrainsPattern TileMapLayer::_get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, const RBSet<TerrainConstraint> &p_constraints, TileSet::TerrainsPattern p_current_pattern) { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - if (!tile_set.is_valid()) { - return TileSet::TerrainsPattern(); - } - // Returns all tiles compatible with the given constraints. - RBMap<TileSet::TerrainsPattern, int> terrain_pattern_score; - RBSet<TileSet::TerrainsPattern> pattern_set = tile_set->get_terrains_pattern_set(p_terrain_set); - ERR_FAIL_COND_V(pattern_set.is_empty(), TileSet::TerrainsPattern()); - for (TileSet::TerrainsPattern &terrain_pattern : pattern_set) { - int score = 0; - - // Check the center bit constraint. - TerrainConstraint terrain_constraint = TerrainConstraint(tile_map_node, p_position, terrain_pattern.get_terrain()); - const RBSet<TerrainConstraint>::Element *in_set_constraint_element = p_constraints.find(terrain_constraint); - if (in_set_constraint_element) { - if (in_set_constraint_element->get().get_terrain() != terrain_constraint.get_terrain()) { - score += in_set_constraint_element->get().get_priority(); - } - } else if (p_current_pattern.get_terrain() != terrain_pattern.get_terrain()) { - continue; // Ignore a pattern that cannot keep bits without constraints unmodified. - } - - // Check the surrounding bits - bool invalid_pattern = false; - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) { - // Check if the bit is compatible with the constraints. - TerrainConstraint terrain_bit_constraint = TerrainConstraint(tile_map_node, p_position, bit, terrain_pattern.get_terrain_peering_bit(bit)); - in_set_constraint_element = p_constraints.find(terrain_bit_constraint); - if (in_set_constraint_element) { - if (in_set_constraint_element->get().get_terrain() != terrain_bit_constraint.get_terrain()) { - score += in_set_constraint_element->get().get_priority(); - } - } else if (p_current_pattern.get_terrain_peering_bit(bit) != terrain_pattern.get_terrain_peering_bit(bit)) { - invalid_pattern = true; // Ignore a pattern that cannot keep bits without constraints unmodified. - break; - } - } - } - if (invalid_pattern) { - continue; - } - - terrain_pattern_score[terrain_pattern] = score; - } - - // Compute the minimum score. - TileSet::TerrainsPattern min_score_pattern = p_current_pattern; - int min_score = INT32_MAX; - for (KeyValue<TileSet::TerrainsPattern, int> E : terrain_pattern_score) { - if (E.value < min_score) { - min_score_pattern = E.key; - min_score = E.value; - } - } - - return min_score_pattern; -} - -RBSet<TerrainConstraint> TileMapLayer::_get_terrain_constraints_from_added_pattern(const Vector2i &p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - if (!tile_set.is_valid()) { - return RBSet<TerrainConstraint>(); - } - - // Compute the constraints needed from the surrounding tiles. - RBSet<TerrainConstraint> output; - output.insert(TerrainConstraint(tile_map_node, p_position, p_terrains_pattern.get_terrain())); - - for (uint32_t i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - TileSet::CellNeighbor side = TileSet::CellNeighbor(i); - if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, side)) { - TerrainConstraint c = TerrainConstraint(tile_map_node, p_position, side, p_terrains_pattern.get_terrain_peering_bit(side)); - output.insert(c); - } - } - - return output; -} - -RBSet<TerrainConstraint> TileMapLayer::_get_terrain_constraints_from_painted_cells_list(const RBSet<Vector2i> &p_painted, int p_terrain_set, bool p_ignore_empty_terrains) const { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - if (!tile_set.is_valid()) { - return RBSet<TerrainConstraint>(); - } - - ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), RBSet<TerrainConstraint>()); - - // Build a set of dummy constraints to get the constrained points. - RBSet<TerrainConstraint> dummy_constraints; - for (const Vector2i &E : p_painted) { - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { // Iterates over neighbor bits. - TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) { - dummy_constraints.insert(TerrainConstraint(tile_map_node, E, bit, -1)); - } - } - } - - // For each constrained point, we get all overlapping tiles, and select the most adequate terrain for it. - RBSet<TerrainConstraint> constraints; - for (const TerrainConstraint &E_constraint : dummy_constraints) { - HashMap<int, int> terrain_count; - - // Count the number of occurrences per terrain. - HashMap<Vector2i, TileSet::CellNeighbor> overlapping_terrain_bits = E_constraint.get_overlapping_coords_and_peering_bits(); - for (const KeyValue<Vector2i, TileSet::CellNeighbor> &E_overlapping : overlapping_terrain_bits) { - TileData *neighbor_tile_data = nullptr; - TileMapCell neighbor_cell = get_cell(E_overlapping.key); - if (neighbor_cell.source_id != TileSet::INVALID_SOURCE) { - Ref<TileSetSource> source = tile_set->get_source(neighbor_cell.source_id); - Ref<TileSetAtlasSource> atlas_source = source; - if (atlas_source.is_valid()) { - TileData *tile_data = atlas_source->get_tile_data(neighbor_cell.get_atlas_coords(), neighbor_cell.alternative_tile); - if (tile_data && tile_data->get_terrain_set() == p_terrain_set) { - neighbor_tile_data = tile_data; - } - } - } - - int terrain = neighbor_tile_data ? neighbor_tile_data->get_terrain_peering_bit(TileSet::CellNeighbor(E_overlapping.value)) : -1; - if (!p_ignore_empty_terrains || terrain >= 0) { - if (!terrain_count.has(terrain)) { - terrain_count[terrain] = 0; - } - terrain_count[terrain] += 1; - } - } - - // Get the terrain with the max number of occurrences. - int max = 0; - int max_terrain = -1; - for (const KeyValue<int, int> &E_terrain_count : terrain_count) { - if (E_terrain_count.value > max) { - max = E_terrain_count.value; - max_terrain = E_terrain_count.key; - } - } - - // Set the adequate terrain. - if (max > 0) { - TerrainConstraint c = E_constraint; - c.set_terrain(max_terrain); - constraints.insert(c); - } - } - - // Add the centers as constraints. - for (Vector2i E_coords : p_painted) { - TileData *tile_data = nullptr; - TileMapCell cell = get_cell(E_coords); - if (cell.source_id != TileSet::INVALID_SOURCE) { - Ref<TileSetSource> source = tile_set->get_source(cell.source_id); - Ref<TileSetAtlasSource> atlas_source = source; - if (atlas_source.is_valid()) { - tile_data = atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile); - } - } - - int terrain = (tile_data && tile_data->get_terrain_set() == p_terrain_set) ? tile_data->get_terrain() : -1; - if (!p_ignore_empty_terrains || terrain >= 0) { - constraints.insert(TerrainConstraint(tile_map_node, E_coords, terrain)); - } - } - - return constraints; -} - -void TileMapLayer::set_tile_map(TileMap *p_tile_map) { - tile_map_node = p_tile_map; -} - -void TileMapLayer::set_layer_index_in_tile_map_node(int p_index) { - if (p_index == layer_index_in_tile_map_node) { - return; - } - layer_index_in_tile_map_node = p_index; - dirty.flags[DIRTY_FLAGS_LAYER_INDEX_IN_TILE_MAP_NODE] = true; - tile_map_node->queue_internal_update(); -} - -Rect2 TileMapLayer::get_rect(bool &r_changed) const { - // Compute the displayed area of the tilemap. - r_changed = false; -#ifdef DEBUG_ENABLED - - if (rect_cache_dirty) { - Rect2 r_total; - bool first = true; - for (const KeyValue<Vector2i, CellData> &E : tile_map) { - Rect2 r; - r.position = tile_map_node->map_to_local(E.key); - r.size = Size2(); - if (first) { - r_total = r; - first = false; - } else { - r_total = r_total.merge(r); - } - } - - r_changed = rect_cache != r_total; - - rect_cache = r_total; - rect_cache_dirty = false; - } -#endif - return rect_cache; -} - -HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_constraints(const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> &p_constraints) { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - if (!tile_set.is_valid()) { - return HashMap<Vector2i, TileSet::TerrainsPattern>(); - } - - // Copy the constraints set. - RBSet<TerrainConstraint> constraints = p_constraints; - - // Output map. - HashMap<Vector2i, TileSet::TerrainsPattern> output; - - // Add all positions to a set. - for (int i = 0; i < p_to_replace.size(); i++) { - const Vector2i &coords = p_to_replace[i]; - - // Select the best pattern for the given constraints. - TileSet::TerrainsPattern current_pattern = TileSet::TerrainsPattern(*tile_set, p_terrain_set); - TileMapCell cell = get_cell(coords); - if (cell.source_id != TileSet::INVALID_SOURCE) { - TileSetSource *source = *tile_set->get_source(cell.source_id); - TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); - if (atlas_source) { - // Get tile data. - TileData *tile_data = atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile); - if (tile_data && tile_data->get_terrain_set() == p_terrain_set) { - current_pattern = tile_data->get_terrains_pattern(); - } - } - } - TileSet::TerrainsPattern pattern = _get_best_terrain_pattern_for_constraints(p_terrain_set, coords, constraints, current_pattern); - - // Update the constraint set with the new ones. - RBSet<TerrainConstraint> new_constraints = _get_terrain_constraints_from_added_pattern(coords, p_terrain_set, pattern); - for (const TerrainConstraint &E_constraint : new_constraints) { - if (constraints.has(E_constraint)) { - constraints.erase(E_constraint); - } - TerrainConstraint c = E_constraint; - c.set_priority(5); - constraints.insert(c); - } - - output[coords] = pattern; - } - return output; -} - -HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_connect(const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { - HashMap<Vector2i, TileSet::TerrainsPattern> output; - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - ERR_FAIL_COND_V(!tile_set.is_valid(), output); - ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), output); - - // Build list and set of tiles that can be modified (painted and their surroundings). - Vector<Vector2i> can_modify_list; - RBSet<Vector2i> can_modify_set; - RBSet<Vector2i> painted_set; - for (int i = p_coords_array.size() - 1; i >= 0; i--) { - const Vector2i &coords = p_coords_array[i]; - can_modify_list.push_back(coords); - can_modify_set.insert(coords); - painted_set.insert(coords); - } - for (Vector2i coords : p_coords_array) { - // Find the adequate neighbor. - for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { - TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); - if (tile_map_node->is_existing_neighbor(bit)) { - Vector2i neighbor = tile_map_node->get_neighbor_cell(coords, bit); - if (!can_modify_set.has(neighbor)) { - can_modify_list.push_back(neighbor); - can_modify_set.insert(neighbor); - } - } - } - } - - // Build a set, out of the possibly modified tiles, of the one with a center bit that is set (or will be) to the painted terrain. - RBSet<Vector2i> cells_with_terrain_center_bit; - for (Vector2i coords : can_modify_set) { - bool connect = false; - if (painted_set.has(coords)) { - connect = true; - } else { - // Get the center bit of the cell. - TileData *tile_data = nullptr; - TileMapCell cell = get_cell(coords); - if (cell.source_id != TileSet::INVALID_SOURCE) { - Ref<TileSetSource> source = tile_set->get_source(cell.source_id); - Ref<TileSetAtlasSource> atlas_source = source; - if (atlas_source.is_valid()) { - tile_data = atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile); - } - } - - if (tile_data && tile_data->get_terrain_set() == p_terrain_set && tile_data->get_terrain() == p_terrain) { - connect = true; - } - } - if (connect) { - cells_with_terrain_center_bit.insert(coords); - } - } - - RBSet<TerrainConstraint> constraints; - - // Add new constraints from the path drawn. - for (Vector2i coords : p_coords_array) { - // Constraints on the center bit. - TerrainConstraint c = TerrainConstraint(tile_map_node, coords, p_terrain); - c.set_priority(10); - constraints.insert(c); - - // Constraints on the connecting bits. - for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { - TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); - if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) { - c = TerrainConstraint(tile_map_node, coords, bit, p_terrain); - c.set_priority(10); - if ((int(bit) % 2) == 0) { - // Side peering bits: add the constraint if the center is of the same terrain. - Vector2i neighbor = tile_map_node->get_neighbor_cell(coords, bit); - if (cells_with_terrain_center_bit.has(neighbor)) { - constraints.insert(c); - } - } else { - // Corner peering bits: add the constraint if all tiles on the constraint has the same center bit. - HashMap<Vector2i, TileSet::CellNeighbor> overlapping_terrain_bits = c.get_overlapping_coords_and_peering_bits(); - bool valid = true; - for (KeyValue<Vector2i, TileSet::CellNeighbor> kv : overlapping_terrain_bits) { - if (!cells_with_terrain_center_bit.has(kv.key)) { - valid = false; - break; - } - } - if (valid) { - constraints.insert(c); - } - } - } - } - } - - // Fills in the constraint list from existing tiles. - for (TerrainConstraint c : _get_terrain_constraints_from_painted_cells_list(painted_set, p_terrain_set, p_ignore_empty_terrains)) { - constraints.insert(c); - } - - // Fill the terrains. - output = terrain_fill_constraints(can_modify_list, p_terrain_set, constraints); - return output; -} - -HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_path(const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { - HashMap<Vector2i, TileSet::TerrainsPattern> output; - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - ERR_FAIL_COND_V(!tile_set.is_valid(), output); - ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), output); - - // Make sure the path is correct and build the peering bit list while doing it. - Vector<TileSet::CellNeighbor> neighbor_list; - for (int i = 0; i < p_coords_array.size() - 1; i++) { - // Find the adequate neighbor. - TileSet::CellNeighbor found_bit = TileSet::CELL_NEIGHBOR_MAX; - for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { - TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); - if (tile_map_node->is_existing_neighbor(bit)) { - if (tile_map_node->get_neighbor_cell(p_coords_array[i], bit) == p_coords_array[i + 1]) { - found_bit = bit; - break; - } - } - } - ERR_FAIL_COND_V_MSG(found_bit == TileSet::CELL_NEIGHBOR_MAX, output, vformat("Invalid terrain path, %s is not a neighboring tile of %s", p_coords_array[i + 1], p_coords_array[i])); - neighbor_list.push_back(found_bit); - } - - // Build list and set of tiles that can be modified (painted and their surroundings). - Vector<Vector2i> can_modify_list; - RBSet<Vector2i> can_modify_set; - RBSet<Vector2i> painted_set; - for (int i = p_coords_array.size() - 1; i >= 0; i--) { - const Vector2i &coords = p_coords_array[i]; - can_modify_list.push_back(coords); - can_modify_set.insert(coords); - painted_set.insert(coords); - } - for (Vector2i coords : p_coords_array) { - // Find the adequate neighbor. - for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { - TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); - if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) { - Vector2i neighbor = tile_map_node->get_neighbor_cell(coords, bit); - if (!can_modify_set.has(neighbor)) { - can_modify_list.push_back(neighbor); - can_modify_set.insert(neighbor); - } - } - } - } - - RBSet<TerrainConstraint> constraints; - - // Add new constraints from the path drawn. - for (Vector2i coords : p_coords_array) { - // Constraints on the center bit. - TerrainConstraint c = TerrainConstraint(tile_map_node, coords, p_terrain); - c.set_priority(10); - constraints.insert(c); - } - for (int i = 0; i < p_coords_array.size() - 1; i++) { - // Constraints on the peering bits. - TerrainConstraint c = TerrainConstraint(tile_map_node, p_coords_array[i], neighbor_list[i], p_terrain); - c.set_priority(10); - constraints.insert(c); - } - - // Fills in the constraint list from existing tiles. - for (TerrainConstraint c : _get_terrain_constraints_from_painted_cells_list(painted_set, p_terrain_set, p_ignore_empty_terrains)) { - constraints.insert(c); - } - - // Fill the terrains. - output = terrain_fill_constraints(can_modify_list, p_terrain_set, constraints); - return output; -} - -HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_pattern(const Vector<Vector2i> &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains) { - HashMap<Vector2i, TileSet::TerrainsPattern> output; - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - ERR_FAIL_COND_V(!tile_set.is_valid(), output); - ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), output); - - // Build list and set of tiles that can be modified (painted and their surroundings). - Vector<Vector2i> can_modify_list; - RBSet<Vector2i> can_modify_set; - RBSet<Vector2i> painted_set; - for (int i = p_coords_array.size() - 1; i >= 0; i--) { - const Vector2i &coords = p_coords_array[i]; - can_modify_list.push_back(coords); - can_modify_set.insert(coords); - painted_set.insert(coords); - } - for (Vector2i coords : p_coords_array) { - // Find the adequate neighbor. - for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { - TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); - if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) { - Vector2i neighbor = tile_map_node->get_neighbor_cell(coords, bit); - if (!can_modify_set.has(neighbor)) { - can_modify_list.push_back(neighbor); - can_modify_set.insert(neighbor); - } - } - } - } - - // Add constraint by the new ones. - RBSet<TerrainConstraint> constraints; - - // Add new constraints from the path drawn. - for (Vector2i coords : p_coords_array) { - // Constraints on the center bit. - RBSet<TerrainConstraint> added_constraints = _get_terrain_constraints_from_added_pattern(coords, p_terrain_set, p_terrains_pattern); - for (TerrainConstraint c : added_constraints) { - c.set_priority(10); - constraints.insert(c); - } - } - - // Fills in the constraint list from modified tiles border. - for (TerrainConstraint c : _get_terrain_constraints_from_painted_cells_list(painted_set, p_terrain_set, p_ignore_empty_terrains)) { - constraints.insert(c); - } - - // Fill the terrains. - output = terrain_fill_constraints(can_modify_list, p_terrain_set, constraints); - return output; -} - -TileMapCell TileMapLayer::get_cell(const Vector2i &p_coords, bool p_use_proxies) const { - if (!tile_map.has(p_coords)) { - return TileMapCell(); - } else { - TileMapCell c = tile_map.find(p_coords)->value.cell; - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - if (p_use_proxies && tile_set.is_valid()) { - Array proxyed = tile_set->map_tile_proxy(c.source_id, c.get_atlas_coords(), c.alternative_tile); - c.source_id = proxyed[0]; - c.set_atlas_coords(proxyed[1]); - c.alternative_tile = proxyed[2]; - } - return c; - } -} - -void TileMapLayer::set_tile_data(TileMapLayer::DataFormat p_format, const Vector<int> &p_data) { - ERR_FAIL_COND(p_format > TileMapLayer::FORMAT_3); - - // Set data for a given tile from raw data. - - int c = p_data.size(); - const int *r = p_data.ptr(); - - int offset = (p_format >= TileMapLayer::FORMAT_2) ? 3 : 2; - ERR_FAIL_COND_MSG(c % offset != 0, vformat("Corrupted tile data. Got size: %s. Expected modulo: %s", offset)); - - clear(); - -#ifdef DISABLE_DEPRECATED - ERR_FAIL_COND_MSG(p_format != TileMapLayer::FORMAT_3, vformat("Cannot handle deprecated TileMap data format version %d. This Godot version was compiled with no support for deprecated data.", p_format)); -#endif - - 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 < ((p_format >= TileMapLayer::FORMAT_2) ? 12 : 8); 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]); - //TODO: ask someone to check this... - if (FORMAT >= FORMAT_2) { - SWAP(local[8], local[11]); - SWAP(local[9], local[10]); - } -#endif - // Extracts position in TileMap. - int16_t x = decode_uint16(&local[0]); - int16_t y = decode_uint16(&local[2]); - - if (p_format == TileMapLayer::FORMAT_3) { - 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); - } else { -#ifndef DISABLE_DEPRECATED - // Previous decated format. - - uint32_t v = decode_uint32(&local[4]); - // Extract the transform flags that used to be in the tilemap. - bool flip_h = v & (1UL << 29); - bool flip_v = v & (1UL << 30); - bool transpose = v & (1UL << 31); - v &= (1UL << 29) - 1; - - // Extract autotile/atlas coords. - int16_t coord_x = 0; - int16_t coord_y = 0; - if (p_format == TileMapLayer::FORMAT_2) { - coord_x = decode_uint16(&local[8]); - coord_y = decode_uint16(&local[10]); - } - - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - if (tile_set.is_valid()) { - Array a = tile_set->compatibility_tilemap_map(v, Vector2i(coord_x, coord_y), flip_h, flip_v, transpose); - if (a.size() == 3) { - set_cell(Vector2i(x, y), a[0], a[1], a[2]); - } else { - ERR_PRINT(vformat("No valid tile in Tileset for: tile:%s coords:%s flip_h:%s flip_v:%s transpose:%s", v, Vector2i(coord_x, coord_y), flip_h, flip_v, transpose)); - } - } else { - int compatibility_alternative_tile = ((int)flip_h) + ((int)flip_v << 1) + ((int)transpose << 2); - set_cell(Vector2i(x, y), v, Vector2i(coord_x, coord_y), compatibility_alternative_tile); - } -#endif - } - } -} - -Vector<int> TileMapLayer::get_tile_data() const { - // Export tile data to raw format. - Vector<int> tile_data; - tile_data.resize(tile_map.size() * 3); - int *w = tile_data.ptrw(); - - // Save in highest format. - - int idx = 0; - for (const KeyValue<Vector2i, CellData> &E : tile_map) { - 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.cell.source_id, &ptr[4]); - encode_uint16(E.value.cell.coord_x, &ptr[6]); - encode_uint16(E.value.cell.coord_y, &ptr[8]); - encode_uint16(E.value.cell.alternative_tile, &ptr[10]); - idx += 3; - } - - return tile_data; -} - -void TileMapLayer::notify_tile_map_change(DirtyFlags p_what) { - dirty.flags[p_what] = true; - tile_map_node->queue_internal_update(); - _physics_notify_tilemap_change(p_what); -} - -void TileMapLayer::internal_update() { - // Find TileData that need a runtime modification. - // This may add cells to the dirty list is a runtime modification has been notified. - _build_runtime_update_tile_data(); - - // Update all subsystems. - _rendering_update(); - _physics_update(); - _navigation_update(); - _scenes_update(); -#ifdef DEBUG_ENABLED - _debug_update(); -#endif // DEBUG_ENABLED - - _clear_runtime_update_tile_data(); - - // Clear the "what is dirty" flags. - for (int i = 0; i < DIRTY_FLAGS_MAX; i++) { - dirty.flags[i] = false; - } - - // List the cells to delete definitely. - Vector<Vector2i> to_delete; - for (SelfList<CellData> *cell_data_list_element = dirty.cell_list.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { - CellData &cell_data = *cell_data_list_element->self(); - // Select the the cell from tile_map if it is invalid. - if (cell_data.cell.source_id == TileSet::INVALID_SOURCE) { - to_delete.push_back(cell_data.coords); - } - } - - // Remove cells that are empty after the cleanup. - for (const Vector2i &coords : to_delete) { - tile_map.erase(coords); - } - - // Clear the dirty cells list. - dirty.cell_list.clear(); -} - -void TileMapLayer::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) { - // Set the current cell tile (using integer position). - Vector2i pk(p_coords); - HashMap<Vector2i, CellData>::Iterator E = tile_map.find(pk); - - int source_id = p_source_id; - Vector2i atlas_coords = p_atlas_coords; - int alternative_tile = p_alternative_tile; - - if ((source_id == TileSet::INVALID_SOURCE || atlas_coords == TileSetSource::INVALID_ATLAS_COORDS || alternative_tile == TileSetSource::INVALID_TILE_ALTERNATIVE) && - (source_id != TileSet::INVALID_SOURCE || atlas_coords != TileSetSource::INVALID_ATLAS_COORDS || alternative_tile != TileSetSource::INVALID_TILE_ALTERNATIVE)) { - source_id = TileSet::INVALID_SOURCE; - atlas_coords = TileSetSource::INVALID_ATLAS_COORDS; - alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; - } - - if (!E) { - if (source_id == TileSet::INVALID_SOURCE) { - return; // Nothing to do, the tile is already empty. - } - - // Insert a new cell in the tile map. - CellData new_cell_data; - new_cell_data.coords = pk; - E = tile_map.insert(pk, new_cell_data); - } else { - if (E->value.cell.source_id == source_id && E->value.cell.get_atlas_coords() == atlas_coords && E->value.cell.alternative_tile == alternative_tile) { - return; // Nothing changed. - } - } - - TileMapCell &c = E->value.cell; - c.source_id = source_id; - c.set_atlas_coords(atlas_coords); - c.alternative_tile = alternative_tile; - - // Make the given cell dirty. - if (!E->value.dirty_list_element.in_list()) { - dirty.cell_list.add(&(E->value.dirty_list_element)); - } - tile_map_node->queue_internal_update(); - - used_rect_cache_dirty = true; -} - -void TileMapLayer::erase_cell(const Vector2i &p_coords) { - set_cell(p_coords, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); -} - -int TileMapLayer::get_cell_source_id(const Vector2i &p_coords, bool p_use_proxies) const { - // Get a cell source id from position. - HashMap<Vector2i, CellData>::ConstIterator E = tile_map.find(p_coords); - - if (!E) { - return TileSet::INVALID_SOURCE; - } - - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - if (p_use_proxies && tile_set.is_valid()) { - Array proxyed = tile_set->map_tile_proxy(E->value.cell.source_id, E->value.cell.get_atlas_coords(), E->value.cell.alternative_tile); - return proxyed[0]; - } - - return E->value.cell.source_id; -} - -Vector2i TileMapLayer::get_cell_atlas_coords(const Vector2i &p_coords, bool p_use_proxies) const { - // Get a cell source id from position. - HashMap<Vector2i, CellData>::ConstIterator E = tile_map.find(p_coords); - - if (!E) { - return TileSetSource::INVALID_ATLAS_COORDS; - } - - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - if (p_use_proxies && tile_set.is_valid()) { - Array proxyed = tile_set->map_tile_proxy(E->value.cell.source_id, E->value.cell.get_atlas_coords(), E->value.cell.alternative_tile); - return proxyed[1]; - } - - return E->value.cell.get_atlas_coords(); -} - -int TileMapLayer::get_cell_alternative_tile(const Vector2i &p_coords, bool p_use_proxies) const { - // Get a cell source id from position. - HashMap<Vector2i, CellData>::ConstIterator E = tile_map.find(p_coords); - - if (!E) { - return TileSetSource::INVALID_TILE_ALTERNATIVE; - } - - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - if (p_use_proxies && tile_set.is_valid()) { - Array proxyed = tile_set->map_tile_proxy(E->value.cell.source_id, E->value.cell.get_atlas_coords(), E->value.cell.alternative_tile); - return proxyed[2]; - } - - return E->value.cell.alternative_tile; -} - -TileData *TileMapLayer::get_cell_tile_data(const Vector2i &p_coords, bool p_use_proxies) const { - int source_id = get_cell_source_id(p_coords, p_use_proxies); - if (source_id == TileSet::INVALID_SOURCE) { - return nullptr; - } - - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - Ref<TileSetAtlasSource> source = tile_set->get_source(source_id); - if (source.is_valid()) { - return source->get_tile_data(get_cell_atlas_coords(p_coords, p_use_proxies), get_cell_alternative_tile(p_coords, p_use_proxies)); - } - - return nullptr; -} - -void TileMapLayer::clear() { - // Remove all tiles. - for (KeyValue<Vector2i, CellData> &kv : tile_map) { - erase_cell(kv.key); - } - used_rect_cache_dirty = true; -} - -Ref<TileMapPattern> TileMapLayer::get_pattern(TypedArray<Vector2i> p_coords_array) { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - ERR_FAIL_COND_V(!tile_set.is_valid(), nullptr); - - Ref<TileMapPattern> output; - output.instantiate(); - if (p_coords_array.is_empty()) { - return output; - } - - Vector2i min = Vector2i(p_coords_array[0]); - for (int i = 1; i < p_coords_array.size(); i++) { - min = min.min(p_coords_array[i]); - } - - Vector<Vector2i> coords_in_pattern_array; - coords_in_pattern_array.resize(p_coords_array.size()); - Vector2i ensure_positive_offset; - for (int i = 0; i < p_coords_array.size(); i++) { - Vector2i coords = p_coords_array[i]; - Vector2i coords_in_pattern = coords - min; - if (tile_set->get_tile_shape() != TileSet::TILE_SHAPE_SQUARE) { - if (tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_STACKED) { - if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL && bool(min.y % 2) && bool(coords_in_pattern.y % 2)) { - coords_in_pattern.x -= 1; - if (coords_in_pattern.x < 0) { - ensure_positive_offset.x = 1; - } - } else if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(min.x % 2) && bool(coords_in_pattern.x % 2)) { - coords_in_pattern.y -= 1; - if (coords_in_pattern.y < 0) { - ensure_positive_offset.y = 1; - } - } - } else if (tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_STACKED_OFFSET) { - if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL && bool(min.y % 2) && bool(coords_in_pattern.y % 2)) { - coords_in_pattern.x += 1; - } else if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(min.x % 2) && bool(coords_in_pattern.x % 2)) { - coords_in_pattern.y += 1; - } - } - } - coords_in_pattern_array.write[i] = coords_in_pattern; - } - - for (int i = 0; i < coords_in_pattern_array.size(); i++) { - Vector2i coords = p_coords_array[i]; - Vector2i coords_in_pattern = coords_in_pattern_array[i]; - output->set_cell(coords_in_pattern + ensure_positive_offset, get_cell_source_id(coords), get_cell_atlas_coords(coords), get_cell_alternative_tile(coords)); - } - - return output; -} - -void TileMapLayer::set_pattern(const Vector2i &p_position, const Ref<TileMapPattern> p_pattern) { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - ERR_FAIL_COND(tile_set.is_null()); - ERR_FAIL_COND(p_pattern.is_null()); - - TypedArray<Vector2i> used_cells = p_pattern->get_used_cells(); - for (int i = 0; i < used_cells.size(); i++) { - Vector2i coords = tile_map_node->map_pattern(p_position, used_cells[i], p_pattern); - set_cell(coords, p_pattern->get_cell_source_id(used_cells[i]), p_pattern->get_cell_atlas_coords(used_cells[i]), p_pattern->get_cell_alternative_tile(used_cells[i])); - } -} - -void TileMapLayer::set_cells_terrain_connect(TypedArray<Vector2i> p_cells, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - ERR_FAIL_COND(!tile_set.is_valid()); - ERR_FAIL_INDEX(p_terrain_set, tile_set->get_terrain_sets_count()); - - Vector<Vector2i> cells_vector; - HashSet<Vector2i> painted_set; - for (int i = 0; i < p_cells.size(); i++) { - cells_vector.push_back(p_cells[i]); - painted_set.insert(p_cells[i]); - } - HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_output = terrain_fill_connect(cells_vector, p_terrain_set, p_terrain, p_ignore_empty_terrains); - for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &kv : terrain_fill_output) { - if (painted_set.has(kv.key)) { - // Paint a random tile with the correct terrain for the painted path. - TileMapCell c = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, kv.value); - set_cell(kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); - } else { - // Avoids updating the painted path from the output if the new pattern is the same as before. - TileSet::TerrainsPattern in_map_terrain_pattern = TileSet::TerrainsPattern(*tile_set, p_terrain_set); - TileMapCell cell = get_cell(kv.key); - if (cell.source_id != TileSet::INVALID_SOURCE) { - TileSetSource *source = *tile_set->get_source(cell.source_id); - TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); - if (atlas_source) { - // Get tile data. - TileData *tile_data = atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile); - if (tile_data && tile_data->get_terrain_set() == p_terrain_set) { - in_map_terrain_pattern = tile_data->get_terrains_pattern(); - } - } - } - if (in_map_terrain_pattern != kv.value) { - TileMapCell c = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, kv.value); - set_cell(kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); - } - } - } -} - -void TileMapLayer::set_cells_terrain_path(TypedArray<Vector2i> p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { - const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); - ERR_FAIL_COND(!tile_set.is_valid()); - ERR_FAIL_INDEX(p_terrain_set, tile_set->get_terrain_sets_count()); - - Vector<Vector2i> vector_path; - HashSet<Vector2i> painted_set; - for (int i = 0; i < p_path.size(); i++) { - vector_path.push_back(p_path[i]); - painted_set.insert(p_path[i]); - } - - HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_output = terrain_fill_path(vector_path, p_terrain_set, p_terrain, p_ignore_empty_terrains); - for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &kv : terrain_fill_output) { - if (painted_set.has(kv.key)) { - // Paint a random tile with the correct terrain for the painted path. - TileMapCell c = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, kv.value); - set_cell(kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); - } else { - // Avoids updating the painted path from the output if the new pattern is the same as before. - TileSet::TerrainsPattern in_map_terrain_pattern = TileSet::TerrainsPattern(*tile_set, p_terrain_set); - TileMapCell cell = get_cell(kv.key); - if (cell.source_id != TileSet::INVALID_SOURCE) { - TileSetSource *source = *tile_set->get_source(cell.source_id); - TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); - if (atlas_source) { - // Get tile data. - TileData *tile_data = atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile); - if (tile_data && tile_data->get_terrain_set() == p_terrain_set) { - in_map_terrain_pattern = tile_data->get_terrains_pattern(); - } - } - } - if (in_map_terrain_pattern != kv.value) { - TileMapCell c = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, kv.value); - set_cell(kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); - } - } - } -} - -TypedArray<Vector2i> TileMapLayer::get_used_cells() const { - // Returns the cells used in the tilemap. - TypedArray<Vector2i> a; - for (const KeyValue<Vector2i, CellData> &E : tile_map) { - const TileMapCell &c = E.value.cell; - if (c.source_id == TileSet::INVALID_SOURCE) { - continue; - } - a.push_back(E.key); - } - - return a; -} - -TypedArray<Vector2i> TileMapLayer::get_used_cells_by_id(int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) const { - // Returns the cells used in the tilemap. - TypedArray<Vector2i> a; - for (const KeyValue<Vector2i, CellData> &E : tile_map) { - const TileMapCell &c = E.value.cell; - if (c.source_id == TileSet::INVALID_SOURCE) { - continue; - } - if ((p_source_id == TileSet::INVALID_SOURCE || p_source_id == c.source_id) && - (p_atlas_coords == TileSetSource::INVALID_ATLAS_COORDS || p_atlas_coords == c.get_atlas_coords()) && - (p_alternative_tile == TileSetSource::INVALID_TILE_ALTERNATIVE || p_alternative_tile == c.alternative_tile)) { - a.push_back(E.key); - } - } - - return a; -} - -Rect2i TileMapLayer::get_used_rect() const { - // Return the rect of the currently used area. - if (used_rect_cache_dirty) { - used_rect_cache = Rect2i(); - - bool first = true; - for (const KeyValue<Vector2i, CellData> &E : tile_map) { - const TileMapCell &c = E.value.cell; - if (c.source_id == TileSet::INVALID_SOURCE) { - continue; - } - if (first) { - used_rect_cache = Rect2i(E.key.x, E.key.y, 0, 0); - first = false; - } else { - used_rect_cache.expand_to(E.key); - } - } - if (!first) { - // Only if we have at least one cell. - // The cache expands to top-left coordinate, so we add one full tile. - used_rect_cache.size += Vector2i(1, 1); - } - used_rect_cache_dirty = false; - } - - return used_rect_cache; -} - -void TileMapLayer::set_name(String p_name) { - if (name == p_name) { - return; - } - name = p_name; - tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); -} - -String TileMapLayer::get_name() const { - return name; -} - -void TileMapLayer::set_enabled(bool p_enabled) { - if (enabled == p_enabled) { - return; - } - enabled = p_enabled; - dirty.flags[DIRTY_FLAGS_LAYER_ENABLED] = true; - tile_map_node->queue_internal_update(); - tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); - - tile_map_node->update_configuration_warnings(); -} - -bool TileMapLayer::is_enabled() const { - return enabled; -} - -void TileMapLayer::set_modulate(Color p_modulate) { - if (modulate == p_modulate) { - return; - } - modulate = p_modulate; - dirty.flags[DIRTY_FLAGS_LAYER_MODULATE] = true; - tile_map_node->queue_internal_update(); - tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); -} - -Color TileMapLayer::get_modulate() const { - return modulate; -} - -void TileMapLayer::set_y_sort_enabled(bool p_y_sort_enabled) { - if (y_sort_enabled == p_y_sort_enabled) { - return; - } - y_sort_enabled = p_y_sort_enabled; - dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ENABLED] = true; - tile_map_node->queue_internal_update(); - tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); - - tile_map_node->update_configuration_warnings(); -} - -bool TileMapLayer::is_y_sort_enabled() const { - return y_sort_enabled; -} - -void TileMapLayer::set_y_sort_origin(int p_y_sort_origin) { - if (y_sort_origin == p_y_sort_origin) { - return; - } - y_sort_origin = p_y_sort_origin; - dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ORIGIN] = true; - tile_map_node->queue_internal_update(); - tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); -} - -int TileMapLayer::get_y_sort_origin() const { - return y_sort_origin; -} - -void TileMapLayer::set_z_index(int p_z_index) { - if (z_index == p_z_index) { - return; - } - z_index = p_z_index; - dirty.flags[DIRTY_FLAGS_LAYER_Z_INDEX] = true; - tile_map_node->queue_internal_update(); - tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); - - tile_map_node->update_configuration_warnings(); -} - -int TileMapLayer::get_z_index() const { - return z_index; -} - -void TileMapLayer::set_navigation_enabled(bool p_enabled) { - if (navigation_enabled == p_enabled) { - return; - } - navigation_enabled = p_enabled; - dirty.flags[DIRTY_FLAGS_LAYER_NAVIGATION_ENABLED] = true; - tile_map_node->queue_internal_update(); - tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); -} - -bool TileMapLayer::is_navigation_enabled() const { - return navigation_enabled; -} - -void TileMapLayer::set_navigation_map(RID p_map) { - ERR_FAIL_COND_MSG(!tile_map_node->is_inside_tree(), "A TileMap navigation map can only be changed while inside the SceneTree."); - navigation_map = p_map; - uses_world_navigation_map = p_map == tile_map_node->get_world_2d()->get_navigation_map(); -} - -RID TileMapLayer::get_navigation_map() const { - if (navigation_map.is_valid()) { - return navigation_map; - } - return RID(); -} - -void TileMapLayer::fix_invalid_tiles() { - Ref<TileSet> tileset = tile_map_node->get_tileset(); - ERR_FAIL_COND_MSG(tileset.is_null(), "Cannot call fix_invalid_tiles() on a TileMap without a valid TileSet."); - - RBSet<Vector2i> coords; - for (const KeyValue<Vector2i, CellData> &E : tile_map) { - TileSetSource *source = *tileset->get_source(E.value.cell.source_id); - if (!source || !source->has_tile(E.value.cell.get_atlas_coords()) || !source->has_alternative_tile(E.value.cell.get_atlas_coords(), E.value.cell.alternative_tile)) { - coords.insert(E.key); - } - } - for (const Vector2i &E : coords) { - set_cell(E, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); - } -} - -bool TileMapLayer::has_body_rid(RID p_physics_body) const { - return bodies_coords.has(p_physics_body); -} - -Vector2i TileMapLayer::get_coords_for_body_rid(RID p_physics_body) const { - return bodies_coords[p_physics_body]; -} - -TileMapLayer::~TileMapLayer() { - if (!tile_map_node) { - // Temporary layer. - return; - } - - in_destructor = true; - clear(); - internal_update(); -} - -HashMap<Vector2i, TileSet::CellNeighbor> TerrainConstraint::get_overlapping_coords_and_peering_bits() const { - HashMap<Vector2i, TileSet::CellNeighbor> output; - - ERR_FAIL_COND_V(is_center_bit(), output); - - Ref<TileSet> ts = tile_map->get_tileset(); - ERR_FAIL_COND_V(!ts.is_valid(), output); - - TileSet::TileShape shape = ts->get_tile_shape(); - if (shape == TileSet::TILE_SHAPE_SQUARE) { - switch (bit) { - case 1: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE; - break; - case 2: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER; - break; - case 3: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE; - break; - default: - ERR_FAIL_V(output); - } - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) { - switch (bit) { - case 1: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; - break; - case 2: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER; - break; - case 3: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; - break; - default: - ERR_FAIL_V(output); - } - } else { - // Half offset shapes. - TileSet::TileOffsetAxis offset_axis = ts->get_tile_offset_axis(); - if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - switch (bit) { - case 1: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE; - break; - case 2: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_CORNER; - break; - case 3: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; - break; - case 4: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER; - break; - case 5: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; - break; - default: - ERR_FAIL_V(output); - } - } else { - switch (bit) { - case 1: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; - break; - case 2: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; - break; - case 3: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; - break; - case 4: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE; - break; - case 5: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; - break; - default: - ERR_FAIL_V(output); - } - } - } - return output; -} - -TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, int p_terrain) { - tile_map = p_tile_map; - - Ref<TileSet> ts = tile_map->get_tileset(); - ERR_FAIL_COND(!ts.is_valid()); - - bit = 0; - base_cell_coords = p_position; - terrain = p_terrain; -} - -TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain) { - // The way we build the constraint make it easy to detect conflicting constraints. - tile_map = p_tile_map; - - Ref<TileSet> ts = tile_map->get_tileset(); - ERR_FAIL_COND(!ts.is_valid()); - - TileSet::TileShape shape = ts->get_tile_shape(); - if (shape == TileSet::TILE_SHAPE_SQUARE) { - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: - bit = 1; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: - bit = 2; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: - bit = 3; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_LEFT_SIDE: - bit = 1; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER); - break; - case TileSet::CELL_NEIGHBOR_TOP_SIDE: - bit = 3; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); - break; - default: - ERR_FAIL(); - break; - } - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) { - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: - bit = 1; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: - bit = 2; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: - bit = 3; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_LEFT_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: - bit = 1; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_CORNER); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: - bit = 3; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); - break; - default: - ERR_FAIL(); - break; - } - } else { - // Half-offset shapes. - TileSet::TileOffsetAxis offset_axis = ts->get_tile_offset_axis(); - if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: - bit = 1; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: - bit = 2; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: - bit = 3; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: - bit = 4; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: - bit = 5; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_LEFT_SIDE: - bit = 1; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: - bit = 4; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: - bit = 3; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: - bit = 5; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: - bit = 4; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); - break; - default: - ERR_FAIL(); - break; - } - } else { - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: - bit = 1; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: - bit = 2; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: - bit = 3; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: - bit = 4; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: - bit = 1; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: - bit = 5; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_LEFT_CORNER: - bit = 3; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: - bit = 1; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_SIDE: - bit = 4; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: - bit = 3; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: - bit = 5; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); - break; - default: - ERR_FAIL(); - break; - } - } - } - terrain = p_terrain; -} +#include "scene/2d/tile_map_layer.h" +#include "scene/gui/control.h" #define TILEMAP_CALL_FOR_LAYER(layer, function, ...) \ if (layer < 0) { \ @@ -3622,7 +772,7 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) { Vector<String> components = String(p_name).split("/", true, 2); if (p_name == "format") { if (p_value.get_type() == Variant::INT) { - format = (TileMapLayer::DataFormat)(p_value.operator int64_t()); // Set format used for loading. + format = (TileMapDataFormat)(p_value.operator int64_t()); // Set format used for loading. return true; } } @@ -3701,7 +851,7 @@ bool TileMap::_set(const StringName &p_name, const Variant &p_value) { bool TileMap::_get(const StringName &p_name, Variant &r_ret) const { Vector<String> components = String(p_name).split("/", true, 2); if (p_name == "format") { - r_ret = TileMapLayer::FORMAT_MAX - 1; // When saving, always save highest format. + r_ret = TileMapDataFormat::FORMAT_MAX - 1; // When saving, always save highest format. return true; } #ifndef DISABLE_DEPRECATED @@ -4765,7 +1915,7 @@ void TileMap::_bind_methods() { ADD_ARRAY("layers", "layer_"); - ADD_PROPERTY_DEFAULT("format", TileMapLayer::FORMAT_1); + ADD_PROPERTY_DEFAULT("format", TileMapDataFormat::FORMAT_1); ADD_SIGNAL(MethodInfo(CoreStringNames::get_singleton()->changed)); diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h index 1e4c6d0e66..29af0ad2b1 100644 --- a/scene/2d/tile_map.h +++ b/scene/2d/tile_map.h @@ -32,406 +32,17 @@ #define TILE_MAP_H #include "scene/2d/node_2d.h" -#include "scene/gui/control.h" #include "scene/resources/tile_set.h" -class TileSetAtlasSource; +class Control; +class TileMapLayer; +class TerrainConstraint; -class TerrainConstraint { -private: - const TileMap *tile_map = nullptr; - Vector2i base_cell_coords; - int bit = -1; - int terrain = -1; - - int priority = 1; - -public: - bool operator<(const TerrainConstraint &p_other) const { - if (base_cell_coords == p_other.base_cell_coords) { - return bit < p_other.bit; - } - return base_cell_coords < p_other.base_cell_coords; - } - - String to_string() const { - return vformat("Constraint {pos:%s, bit:%d, terrain:%d, priority:%d}", base_cell_coords, bit, terrain, priority); - } - - Vector2i get_base_cell_coords() const { - return base_cell_coords; - } - - bool is_center_bit() const { - return bit == 0; - } - - HashMap<Vector2i, TileSet::CellNeighbor> get_overlapping_coords_and_peering_bits() const; - - void set_terrain(int p_terrain) { - terrain = p_terrain; - } - - int get_terrain() const { - return terrain; - } - - void set_priority(int p_priority) { - priority = p_priority; - } - - int get_priority() const { - return priority; - } - - TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, int p_terrain); // For the center terrain bit - TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain); // For peering bits - TerrainConstraint(){}; -}; - -#ifdef DEBUG_ENABLED -class DebugQuadrant; -#endif // DEBUG_ENABLED -class RenderingQuadrant; - -struct CellData { - Vector2i coords; - TileMapCell cell; - - // Debug. - SelfList<CellData> debug_quadrant_list_element; - - // Rendering. - Ref<RenderingQuadrant> rendering_quadrant; - SelfList<CellData> rendering_quadrant_list_element; - LocalVector<RID> occluders; - - // Physics. - LocalVector<RID> bodies; - - // Navigation. - LocalVector<RID> navigation_regions; - - // Scenes. - String scene; - - // Runtime TileData cache. - TileData *runtime_tile_data_cache = nullptr; - - // List elements. - SelfList<CellData> dirty_list_element; - - bool operator<(const CellData &p_other) const { - return coords < p_other.coords; - } - - // For those, copy everything but SelfList elements. - void operator=(const CellData &p_other) { - coords = p_other.coords; - cell = p_other.cell; - occluders = p_other.occluders; - bodies = p_other.bodies; - navigation_regions = p_other.navigation_regions; - scene = p_other.scene; - runtime_tile_data_cache = p_other.runtime_tile_data_cache; - } - - CellData(const CellData &p_other) : - debug_quadrant_list_element(this), - rendering_quadrant_list_element(this), - dirty_list_element(this) { - coords = p_other.coords; - cell = p_other.cell; - occluders = p_other.occluders; - bodies = p_other.bodies; - navigation_regions = p_other.navigation_regions; - scene = p_other.scene; - runtime_tile_data_cache = p_other.runtime_tile_data_cache; - } - - CellData() : - debug_quadrant_list_element(this), - rendering_quadrant_list_element(this), - dirty_list_element(this) { - } -}; - -// For compatibility reasons, we use another comparator for Y-sorted layers. -struct CellDataYSortedComparator { - _FORCE_INLINE_ bool operator()(const CellData &p_a, const CellData &p_b) const { - return p_a.coords.x == p_b.coords.x ? (p_a.coords.y < p_b.coords.y) : (p_a.coords.x > p_b.coords.x); - } -}; - -#ifdef DEBUG_ENABLED -class DebugQuadrant : public RefCounted { - GDCLASS(DebugQuadrant, RefCounted); - -public: - Vector2i quadrant_coords; - SelfList<CellData>::List cells; - RID canvas_item; - - SelfList<DebugQuadrant> dirty_quadrant_list_element; - - // For those, copy everything but SelfList elements. - DebugQuadrant(const DebugQuadrant &p_other) : - dirty_quadrant_list_element(this) { - quadrant_coords = p_other.quadrant_coords; - cells = p_other.cells; - canvas_item = p_other.canvas_item; - } - - DebugQuadrant() : - dirty_quadrant_list_element(this) { - } - - ~DebugQuadrant() { - cells.clear(); - } -}; -#endif // DEBUG_ENABLED - -class RenderingQuadrant : public RefCounted { - GDCLASS(RenderingQuadrant, RefCounted); - -public: - struct CoordsWorldComparator { - _ALWAYS_INLINE_ bool operator()(const Vector2 &p_a, const Vector2 &p_b) const { - // We sort the cells by their local coords, as it is needed by rendering. - if (p_a.y == p_b.y) { - return p_a.x > p_b.x; - } else { - return p_a.y < p_b.y; - } - } - }; - - Vector2i quadrant_coords; - SelfList<CellData>::List cells; - List<RID> canvas_items; - Vector2 canvas_items_position; - - SelfList<RenderingQuadrant> dirty_quadrant_list_element; - - // For those, copy everything but SelfList elements. - RenderingQuadrant(const RenderingQuadrant &p_other) : - dirty_quadrant_list_element(this) { - quadrant_coords = p_other.quadrant_coords; - cells = p_other.cells; - canvas_items = p_other.canvas_items; - } - - RenderingQuadrant() : - dirty_quadrant_list_element(this) { - } - - ~RenderingQuadrant() { - cells.clear(); - } -}; - -class TileMapLayer : public RefCounted { - GDCLASS(TileMapLayer, RefCounted); - -public: - enum DataFormat { - FORMAT_1 = 0, - FORMAT_2, - FORMAT_3, - FORMAT_MAX, - }; - - enum DirtyFlags { - DIRTY_FLAGS_LAYER_ENABLED = 0, - DIRTY_FLAGS_LAYER_MODULATE, - DIRTY_FLAGS_LAYER_Y_SORT_ENABLED, - DIRTY_FLAGS_LAYER_Y_SORT_ORIGIN, - DIRTY_FLAGS_LAYER_Z_INDEX, - DIRTY_FLAGS_LAYER_NAVIGATION_ENABLED, - DIRTY_FLAGS_LAYER_INDEX_IN_TILE_MAP_NODE, - DIRTY_FLAGS_TILE_MAP_IN_TREE, - DIRTY_FLAGS_TILE_MAP_IN_CANVAS, - DIRTY_FLAGS_TILE_MAP_VISIBILITY, - DIRTY_FLAGS_TILE_MAP_XFORM, - DIRTY_FLAGS_TILE_MAP_LOCAL_XFORM, - DIRTY_FLAGS_TILE_MAP_SELECTED_LAYER, - DIRTY_FLAGS_TILE_MAP_LIGHT_MASK, - DIRTY_FLAGS_TILE_MAP_MATERIAL, - DIRTY_FLAGS_TILE_MAP_USE_PARENT_MATERIAL, - DIRTY_FLAGS_TILE_MAP_TEXTURE_FILTER, - DIRTY_FLAGS_TILE_MAP_TEXTURE_REPEAT, - DIRTY_FLAGS_TILE_MAP_TILE_SET, - DIRTY_FLAGS_TILE_MAP_QUADRANT_SIZE, - DIRTY_FLAGS_TILE_MAP_COLLISION_ANIMATABLE, - DIRTY_FLAGS_TILE_MAP_COLLISION_VISIBILITY_MODE, - DIRTY_FLAGS_TILE_MAP_NAVIGATION_VISIBILITY_MODE, - DIRTY_FLAGS_TILE_MAP_Y_SORT_ENABLED, - DIRTY_FLAGS_TILE_MAP_RUNTIME_UPDATE, - DIRTY_FLAGS_MAX, - }; - -private: - // Exposed properties. - String name; - bool enabled = true; - Color modulate = Color(1, 1, 1, 1); - bool y_sort_enabled = false; - int y_sort_origin = 0; - int z_index = 0; - bool navigation_enabled = true; - RID navigation_map; - bool uses_world_navigation_map = false; - - // Internal. - TileMap *tile_map_node = nullptr; - int layer_index_in_tile_map_node = -1; - RID canvas_item; - HashMap<Vector2i, CellData> tile_map; - - // Dirty flag. Allows knowing what was modified since the last update. - struct { - bool flags[DIRTY_FLAGS_MAX] = { false }; - SelfList<CellData>::List cell_list; - } dirty; - bool in_destructor = false; - - // Rect cache. - mutable Rect2 rect_cache; - mutable bool rect_cache_dirty = true; - mutable Rect2i used_rect_cache; - mutable bool used_rect_cache_dirty = true; - - // Runtime tile data. - bool _runtime_update_tile_data_was_cleaned_up = false; - void _build_runtime_update_tile_data(); - void _build_runtime_update_tile_data_for_cell(CellData &r_cell_data, bool p_auto_add_to_dirty_list = false); - void _clear_runtime_update_tile_data(); - - // Per-system methods. -#ifdef DEBUG_ENABLED - HashMap<Vector2i, Ref<DebugQuadrant>> debug_quadrant_map; - Vector2i _coords_to_debug_quadrant_coords(const Vector2i &p_coords) const; - bool _debug_was_cleaned_up = false; - void _debug_update(); - void _debug_quadrants_update_cell(CellData &r_cell_data, SelfList<DebugQuadrant>::List &r_dirty_debug_quadrant_list); -#endif // DEBUG_ENABLED - - HashMap<Vector2i, Ref<RenderingQuadrant>> rendering_quadrant_map; - bool _rendering_was_cleaned_up = false; - void _rendering_update(); - void _rendering_quadrants_update_cell(CellData &r_cell_data, SelfList<RenderingQuadrant>::List &r_dirty_rendering_quadrant_list); - void _rendering_occluders_clear_cell(CellData &r_cell_data); - void _rendering_occluders_update_cell(CellData &r_cell_data); -#ifdef DEBUG_ENABLED - void _rendering_draw_cell_debug(const RID &p_canvas_item, const Vector2i &p_quadrant_pos, const CellData &r_cell_data); -#endif // DEBUG_ENABLED - - HashMap<RID, Vector2i> bodies_coords; // Mapping for RID to coords. - bool _physics_was_cleaned_up = false; - void _physics_update(); - void _physics_notify_tilemap_change(DirtyFlags p_what); - void _physics_clear_cell(CellData &r_cell_data); - void _physics_update_cell(CellData &r_cell_data); -#ifdef DEBUG_ENABLED - void _physics_draw_cell_debug(const RID &p_canvas_item, const Vector2i &p_quadrant_pos, const CellData &r_cell_data); -#endif // DEBUG_ENABLED - - bool _navigation_was_cleaned_up = false; - void _navigation_update(); - void _navigation_clear_cell(CellData &r_cell_data); - void _navigation_update_cell(CellData &r_cell_data); -#ifdef DEBUG_ENABLED - void _navigation_draw_cell_debug(const RID &p_canvas_item, const Vector2i &p_quadrant_pos, const CellData &r_cell_data); -#endif // DEBUG_ENABLED - - bool _scenes_was_cleaned_up = false; - void _scenes_update(); - void _scenes_clear_cell(CellData &r_cell_data); - void _scenes_update_cell(CellData &r_cell_data); -#ifdef DEBUG_ENABLED - void _scenes_draw_cell_debug(const RID &p_canvas_item, const Vector2i &p_quadrant_pos, const CellData &r_cell_data); -#endif // DEBUG_ENABLED - - // Terrains. - TileSet::TerrainsPattern _get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, const RBSet<TerrainConstraint> &p_constraints, TileSet::TerrainsPattern p_current_pattern); - RBSet<TerrainConstraint> _get_terrain_constraints_from_added_pattern(const Vector2i &p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const; - RBSet<TerrainConstraint> _get_terrain_constraints_from_painted_cells_list(const RBSet<Vector2i> &p_painted, int p_terrain_set, bool p_ignore_empty_terrains) const; - -public: - // TileMap node. - void set_tile_map(TileMap *p_tile_map); - void set_layer_index_in_tile_map_node(int p_index); - - // Rect caching. - Rect2 get_rect(bool &r_changed) const; - - // Terrains. - HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_constraints(const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> &p_constraints); // Not exposed. - HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_connect(const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); // Not exposed. - HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_path(const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); // Not exposed. - HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_pattern(const Vector<Vector2i> &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains = true); // Not exposed. - - // Not exposed to users. - TileMapCell get_cell(const Vector2i &p_coords, bool p_use_proxies = false) const; - - // For TileMap node's use. - void set_tile_data(DataFormat p_format, const Vector<int> &p_data); - Vector<int> get_tile_data() const; - void notify_tile_map_change(DirtyFlags p_what); - void internal_update(); - - // --- Exposed in TileMap --- - - // Cells manipulation. - void set_cell(const Vector2i &p_coords, int p_source_id = TileSet::INVALID_SOURCE, const Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = 0); - void erase_cell(const Vector2i &p_coords); - - int get_cell_source_id(const Vector2i &p_coords, bool p_use_proxies = false) const; - Vector2i get_cell_atlas_coords(const Vector2i &p_coords, bool p_use_proxies = false) const; - int get_cell_alternative_tile(const Vector2i &p_coords, bool p_use_proxies = false) const; - TileData *get_cell_tile_data(const Vector2i &p_coords, bool p_use_proxies = false) const; // Helper method to make accessing the data easier. - void clear(); - - // Patterns. - Ref<TileMapPattern> get_pattern(TypedArray<Vector2i> p_coords_array); - void set_pattern(const Vector2i &p_position, const Ref<TileMapPattern> p_pattern); - - // Terrains. - void set_cells_terrain_connect(TypedArray<Vector2i> p_cells, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); - void set_cells_terrain_path(TypedArray<Vector2i> p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); - - // Cells usage. - TypedArray<Vector2i> get_used_cells() const; - TypedArray<Vector2i> get_used_cells_by_id(int p_source_id = TileSet::INVALID_SOURCE, const Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE) const; - Rect2i get_used_rect() const; - - // Layer properties. - void set_name(String p_name); - String get_name() const; - void set_enabled(bool p_enabled); - bool is_enabled() const; - void set_modulate(Color p_modulate); - Color get_modulate() const; - void set_y_sort_enabled(bool p_y_sort_enabled); - bool is_y_sort_enabled() const; - void set_y_sort_origin(int p_y_sort_origin); - int get_y_sort_origin() const; - void set_z_index(int p_z_index); - int get_z_index() const; - void set_navigation_enabled(bool p_enabled); - bool is_navigation_enabled() const; - void set_navigation_map(RID p_map); - RID get_navigation_map() const; - - // Fixing and clearing methods. - void fix_invalid_tiles(); - - // Find coords for body. - bool has_body_rid(RID p_physics_body) const; - Vector2i get_coords_for_body_rid(RID p_physics_body) const; // For finding tiles from collision. - - ~TileMapLayer(); +enum TileMapDataFormat { + FORMAT_1 = 0, + FORMAT_2, + FORMAT_3, + FORMAT_MAX, }; class TileMap : public Node2D { @@ -448,7 +59,7 @@ private: friend class TileSetPlugin; // A compatibility enum to specify how is the data if formatted. - mutable TileMapLayer::DataFormat format = TileMapLayer::FORMAT_3; + mutable TileMapDataFormat format = TileMapDataFormat::FORMAT_3; static constexpr float FP_ADJUST = 0.00001; diff --git a/scene/2d/tile_map_layer.cpp b/scene/2d/tile_map_layer.cpp new file mode 100644 index 0000000000..c35a53867c --- /dev/null +++ b/scene/2d/tile_map_layer.cpp @@ -0,0 +1,2886 @@ +/**************************************************************************/ +/* tile_map_layer.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_map_layer.h" + +#include "core/core_string_names.h" +#include "core/io/marshalls.h" +#include "scene/gui/control.h" +#include "scene/resources/world_2d.h" +#include "servers/navigation_server_2d.h" + +#ifdef DEBUG_ENABLED +#include "servers/navigation_server_3d.h" +#endif // DEBUG_ENABLED + +#ifdef DEBUG_ENABLED +/////////////////////////////// Debug ////////////////////////////////////////// +constexpr int TILE_MAP_DEBUG_QUADRANT_SIZE = 16; + +Vector2i TileMapLayer::_coords_to_debug_quadrant_coords(const Vector2i &p_coords) const { + return Vector2i( + p_coords.x > 0 ? p_coords.x / TILE_MAP_DEBUG_QUADRANT_SIZE : (p_coords.x - (TILE_MAP_DEBUG_QUADRANT_SIZE - 1)) / TILE_MAP_DEBUG_QUADRANT_SIZE, + p_coords.y > 0 ? p_coords.y / TILE_MAP_DEBUG_QUADRANT_SIZE : (p_coords.y - (TILE_MAP_DEBUG_QUADRANT_SIZE - 1)) / TILE_MAP_DEBUG_QUADRANT_SIZE); +} + +void TileMapLayer::_debug_update() { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + RenderingServer *rs = RenderingServer::get_singleton(); + + // Check if we should cleanup everything. + bool forced_cleanup = in_destructor || !enabled || !tile_map_node->is_inside_tree() || !tile_set.is_valid() || !tile_map_node->is_visible_in_tree(); + + if (forced_cleanup) { + for (KeyValue<Vector2i, Ref<DebugQuadrant>> &kv : debug_quadrant_map) { + // Free the quadrant. + Ref<DebugQuadrant> &debug_quadrant = kv.value; + if (debug_quadrant->canvas_item.is_valid()) { + rs->free(debug_quadrant->canvas_item); + } + } + debug_quadrant_map.clear(); + _debug_was_cleaned_up = true; + return; + } + + // Check if anything is dirty, in such a case, redraw debug. + bool anything_changed = false; + for (int i = 0; i < DIRTY_FLAGS_MAX; i++) { + if (dirty.flags[i]) { + anything_changed = true; + break; + } + } + + // List all debug quadrants to update, creating new ones if needed. + SelfList<DebugQuadrant>::List dirty_debug_quadrant_list; + + if (_debug_was_cleaned_up || anything_changed) { + // Update all cells. + for (KeyValue<Vector2i, CellData> &kv : tile_map) { + CellData &cell_data = kv.value; + _debug_quadrants_update_cell(cell_data, dirty_debug_quadrant_list); + } + } else { + // Update dirty cells. + for (SelfList<CellData> *cell_data_list_element = dirty.cell_list.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { + CellData &cell_data = *cell_data_list_element->self(); + _debug_quadrants_update_cell(cell_data, dirty_debug_quadrant_list); + } + } + + // Update those quadrants. + for (SelfList<DebugQuadrant> *quadrant_list_element = dirty_debug_quadrant_list.first(); quadrant_list_element;) { + SelfList<DebugQuadrant> *next_quadrant_list_element = quadrant_list_element->next(); // "Hack" to clear the list while iterating. + + DebugQuadrant &debug_quadrant = *quadrant_list_element->self(); + + // Check if the quadrant has a tile. + bool has_a_tile = false; + RID &ci = debug_quadrant.canvas_item; + for (SelfList<CellData> *cell_data_list_element = debug_quadrant.cells.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { + CellData &cell_data = *cell_data_list_element->self(); + if (cell_data.cell.source_id != TileSet::INVALID_SOURCE) { + has_a_tile = true; + break; + } + } + + if (has_a_tile) { + // Update the quadrant. + if (ci.is_valid()) { + rs->canvas_item_clear(ci); + } else { + ci = rs->canvas_item_create(); + rs->canvas_item_set_z_index(ci, RS::CANVAS_ITEM_Z_MAX - 1); + rs->canvas_item_set_parent(ci, tile_map_node->get_canvas_item()); + } + + const Vector2 quadrant_pos = tile_map_node->map_to_local(debug_quadrant.quadrant_coords * TILE_MAP_DEBUG_QUADRANT_SIZE); + Transform2D xform(0, quadrant_pos); + rs->canvas_item_set_transform(ci, xform); + + for (SelfList<CellData> *cell_data_list_element = debug_quadrant.cells.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { + CellData &cell_data = *cell_data_list_element->self(); + if (cell_data.cell.source_id != TileSet::INVALID_SOURCE) { + _rendering_draw_cell_debug(ci, quadrant_pos, cell_data); + _physics_draw_cell_debug(ci, quadrant_pos, cell_data); + _navigation_draw_cell_debug(ci, quadrant_pos, cell_data); + _scenes_draw_cell_debug(ci, quadrant_pos, cell_data); + } + } + } else { + // Free the quadrant. + if (ci.is_valid()) { + rs->free(ci); + } + quadrant_list_element->remove_from_list(); + debug_quadrant_map.erase(debug_quadrant.quadrant_coords); + } + + quadrant_list_element = next_quadrant_list_element; + } + + dirty_debug_quadrant_list.clear(); + + _debug_was_cleaned_up = false; +} + +void TileMapLayer::_debug_quadrants_update_cell(CellData &r_cell_data, SelfList<DebugQuadrant>::List &r_dirty_debug_quadrant_list) { + Vector2i quadrant_coords = _coords_to_debug_quadrant_coords(r_cell_data.coords); + + if (!debug_quadrant_map.has(quadrant_coords)) { + // Create a new quadrant and add it to the quadrant map. + Ref<DebugQuadrant> new_quadrant; + new_quadrant.instantiate(); + new_quadrant->quadrant_coords = quadrant_coords; + debug_quadrant_map[quadrant_coords] = new_quadrant; + } + + // Add the cell to its quadrant, if it is not already in there. + Ref<DebugQuadrant> &debug_quadrant = debug_quadrant_map[quadrant_coords]; + if (!r_cell_data.debug_quadrant_list_element.in_list()) { + debug_quadrant->cells.add(&r_cell_data.debug_quadrant_list_element); + } + + // Mark the quadrant as dirty. + if (!debug_quadrant->dirty_quadrant_list_element.in_list()) { + r_dirty_debug_quadrant_list.add(&debug_quadrant->dirty_quadrant_list_element); + } +} +#endif // DEBUG_ENABLED + +/////////////////////////////// Rendering ////////////////////////////////////// +void TileMapLayer::_rendering_update() { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + RenderingServer *rs = RenderingServer::get_singleton(); + + // Check if we should cleanup everything. + bool forced_cleanup = in_destructor || !enabled || !tile_map_node->is_inside_tree() || !tile_set.is_valid() || !tile_map_node->is_visible_in_tree(); + + // ----------- Layer level processing ----------- + if (forced_cleanup) { + // Cleanup. + if (canvas_item.is_valid()) { + rs->free(canvas_item); + canvas_item = RID(); + } + } else { + // Create/Update the layer's CanvasItem. + if (!canvas_item.is_valid()) { + RID ci = rs->canvas_item_create(); + rs->canvas_item_set_parent(ci, tile_map_node->get_canvas_item()); + canvas_item = ci; + } + RID &ci = canvas_item; + rs->canvas_item_set_draw_index(ci, layer_index_in_tile_map_node - (int64_t)0x80000000); + rs->canvas_item_set_sort_children_by_y(ci, y_sort_enabled); + rs->canvas_item_set_use_parent_material(ci, tile_map_node->get_use_parent_material() || tile_map_node->get_material().is_valid()); + rs->canvas_item_set_z_index(ci, z_index); + rs->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(tile_map_node->get_texture_filter_in_tree())); + rs->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(tile_map_node->get_texture_repeat_in_tree())); + rs->canvas_item_set_light_mask(ci, tile_map_node->get_light_mask()); + + // Modulate the layer. + Color layer_modulate = modulate; + int selected_layer = tile_map_node->get_selected_layer(); + if (selected_layer >= 0 && layer_index_in_tile_map_node != selected_layer) { + int z_selected = tile_map_node->get_layer_z_index(selected_layer); + if (z_index < z_selected || (z_index == z_selected && layer_index_in_tile_map_node < selected_layer)) { + layer_modulate = layer_modulate.darkened(0.5); + } else if (z_index > z_selected || (z_index == z_selected && layer_index_in_tile_map_node > selected_layer)) { + layer_modulate = layer_modulate.darkened(0.5); + layer_modulate.a *= 0.3; + } + } + rs->canvas_item_set_modulate(ci, layer_modulate); + } + + // ----------- Quadrants processing ----------- + + // List all rendering quadrants to update, creating new ones if needed. + SelfList<RenderingQuadrant>::List dirty_rendering_quadrant_list; + + // Check if anything changed that might change the quadrant shape. + // If so, recreate everything. + bool quandrant_shape_changed = dirty.flags[DIRTY_FLAGS_TILE_MAP_QUADRANT_SIZE] || + (tile_map_node->is_y_sort_enabled() && y_sort_enabled && (dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ENABLED] || dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ORIGIN] || dirty.flags[DIRTY_FLAGS_TILE_MAP_Y_SORT_ENABLED] || dirty.flags[DIRTY_FLAGS_TILE_MAP_LOCAL_XFORM] || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET])); + + // Free all quadrants. + if (forced_cleanup || quandrant_shape_changed) { + for (const KeyValue<Vector2i, Ref<RenderingQuadrant>> &kv : rendering_quadrant_map) { + for (int i = 0; i < kv.value->canvas_items.size(); i++) { + const RID &ci = kv.value->canvas_items[i]; + if (ci.is_valid()) { + rs->free(ci); + } + } + kv.value->cells.clear(); + } + rendering_quadrant_map.clear(); + _rendering_was_cleaned_up = true; + } + + if (!forced_cleanup) { + // List all quadrants to update, recreating them if needed. + if (dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET] || _rendering_was_cleaned_up) { + // Update all cells. + for (KeyValue<Vector2i, CellData> &kv : tile_map) { + CellData &cell_data = kv.value; + _rendering_quadrants_update_cell(cell_data, dirty_rendering_quadrant_list); + } + } else { + // Update dirty cells. + for (SelfList<CellData> *cell_data_list_element = dirty.cell_list.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { + CellData &cell_data = *cell_data_list_element->self(); + _rendering_quadrants_update_cell(cell_data, dirty_rendering_quadrant_list); + } + } + + // Update all dirty quadrants. + for (SelfList<RenderingQuadrant> *quadrant_list_element = dirty_rendering_quadrant_list.first(); quadrant_list_element;) { + SelfList<RenderingQuadrant> *next_quadrant_list_element = quadrant_list_element->next(); // "Hack" to clear the list while iterating. + + const Ref<RenderingQuadrant> &rendering_quadrant = quadrant_list_element->self(); + + // Check if the quadrant has a tile. + bool has_a_tile = false; + for (SelfList<CellData> *cell_data_list_element = rendering_quadrant->cells.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { + CellData &cell_data = *cell_data_list_element->self(); + if (cell_data.cell.source_id != TileSet::INVALID_SOURCE) { + has_a_tile = true; + break; + } + } + + if (has_a_tile) { + // Process the quadrant. + + // First, clear the quadrant's canvas items. + for (RID &ci : rendering_quadrant->canvas_items) { + rs->free(ci); + } + rendering_quadrant->canvas_items.clear(); + + // Sort the quadrant cells. + if (tile_map_node->is_y_sort_enabled() && is_y_sort_enabled()) { + // For compatibility reasons, we use another comparator for Y-sorted layers. + rendering_quadrant->cells.sort_custom<CellDataYSortedComparator>(); + } else { + rendering_quadrant->cells.sort(); + } + + // Those allow to group cell per material or z-index. + Ref<Material> prev_material; + int prev_z_index = 0; + RID prev_ci; + + for (SelfList<CellData> *cell_data_quadrant_list_element = rendering_quadrant->cells.first(); cell_data_quadrant_list_element; cell_data_quadrant_list_element = cell_data_quadrant_list_element->next()) { + CellData &cell_data = *cell_data_quadrant_list_element->self(); + + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(*tile_set->get_source(cell_data.cell.source_id)); + + // Get the tile data. + const TileData *tile_data; + if (cell_data.runtime_tile_data_cache) { + tile_data = cell_data.runtime_tile_data_cache; + } else { + tile_data = atlas_source->get_tile_data(cell_data.cell.get_atlas_coords(), cell_data.cell.alternative_tile); + } + + Ref<Material> mat = tile_data->get_material(); + int tile_z_index = tile_data->get_z_index(); + + // Quandrant pos. + + // --- CanvasItems --- + RID ci; + + // Check if the material or the z_index changed. + if (prev_ci == RID() || prev_material != mat || prev_z_index != tile_z_index) { + // If so, create a new CanvasItem. + ci = rs->canvas_item_create(); + if (mat.is_valid()) { + rs->canvas_item_set_material(ci, mat->get_rid()); + } + rs->canvas_item_set_parent(ci, canvas_item); + rs->canvas_item_set_use_parent_material(ci, tile_map_node->get_use_parent_material() || tile_map_node->get_material().is_valid()); + + Transform2D xform(0, rendering_quadrant->canvas_items_position); + rs->canvas_item_set_transform(ci, xform); + + rs->canvas_item_set_light_mask(ci, tile_map_node->get_light_mask()); + rs->canvas_item_set_z_as_relative_to_parent(ci, true); + rs->canvas_item_set_z_index(ci, tile_z_index); + + rs->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(tile_map_node->get_texture_filter_in_tree())); + rs->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(tile_map_node->get_texture_repeat_in_tree())); + + rendering_quadrant->canvas_items.push_back(ci); + + prev_ci = ci; + prev_material = mat; + prev_z_index = tile_z_index; + + } else { + // Keep the same canvas_item to draw on. + ci = prev_ci; + } + + const Vector2 local_tile_pos = tile_map_node->map_to_local(cell_data.coords); + + // Random animation offset. + real_t random_animation_offset = 0.0; + if (atlas_source->get_tile_animation_mode(cell_data.cell.get_atlas_coords()) != TileSetAtlasSource::TILE_ANIMATION_MODE_DEFAULT) { + Array to_hash; + to_hash.push_back(local_tile_pos); + to_hash.push_back(get_instance_id()); // Use instance id as a random hash + random_animation_offset = RandomPCG(to_hash.hash()).randf(); + } + + // Drawing the tile in the canvas item. + tile_map_node->draw_tile(ci, local_tile_pos - rendering_quadrant->canvas_items_position, tile_set, cell_data.cell.source_id, cell_data.cell.get_atlas_coords(), cell_data.cell.alternative_tile, -1, tile_map_node->get_self_modulate(), tile_data, random_animation_offset); + } + } else { + // Free the quadrant. + for (int i = 0; i < rendering_quadrant->canvas_items.size(); i++) { + const RID &ci = rendering_quadrant->canvas_items[i]; + if (ci.is_valid()) { + rs->free(ci); + } + } + rendering_quadrant->cells.clear(); + rendering_quadrant_map.erase(rendering_quadrant->quadrant_coords); + } + + quadrant_list_element = next_quadrant_list_element; + } + + dirty_rendering_quadrant_list.clear(); + + // Reset the drawing indices. + { + int index = -(int64_t)0x80000000; // Always must be drawn below children. + + // Sort the quadrants coords per local coordinates. + RBMap<Vector2, Ref<RenderingQuadrant>, RenderingQuadrant::CoordsWorldComparator> local_to_map; + for (KeyValue<Vector2i, Ref<RenderingQuadrant>> &kv : rendering_quadrant_map) { + Ref<RenderingQuadrant> &rendering_quadrant = kv.value; + local_to_map[tile_map_node->map_to_local(rendering_quadrant->quadrant_coords)] = rendering_quadrant; + } + + // Sort the quadrants. + for (const KeyValue<Vector2, Ref<RenderingQuadrant>> &E : local_to_map) { + for (const RID &ci : E.value->canvas_items) { + RS::get_singleton()->canvas_item_set_draw_index(ci, index++); + } + } + } + + // Updates on TileMap changes. + if (dirty.flags[DIRTY_FLAGS_TILE_MAP_LIGHT_MASK] || + dirty.flags[DIRTY_FLAGS_TILE_MAP_USE_PARENT_MATERIAL] || + dirty.flags[DIRTY_FLAGS_TILE_MAP_MATERIAL] || + dirty.flags[DIRTY_FLAGS_TILE_MAP_TEXTURE_FILTER] || + dirty.flags[DIRTY_FLAGS_TILE_MAP_TEXTURE_REPEAT]) { + for (KeyValue<Vector2i, Ref<RenderingQuadrant>> &kv : rendering_quadrant_map) { + Ref<RenderingQuadrant> &rendering_quadrant = kv.value; + for (const RID &ci : rendering_quadrant->canvas_items) { + rs->canvas_item_set_light_mask(ci, tile_map_node->get_light_mask()); + rs->canvas_item_set_use_parent_material(ci, tile_map_node->get_use_parent_material() || tile_map_node->get_material().is_valid()); + rs->canvas_item_set_default_texture_filter(ci, RS::CanvasItemTextureFilter(tile_map_node->get_texture_filter_in_tree())); + rs->canvas_item_set_default_texture_repeat(ci, RS::CanvasItemTextureRepeat(tile_map_node->get_texture_repeat_in_tree())); + } + } + } + } + + // ----------- Occluders processing ----------- + if (forced_cleanup) { + // Clean everything. + for (KeyValue<Vector2i, CellData> &kv : tile_map) { + _rendering_occluders_clear_cell(kv.value); + } + } else { + if (_rendering_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET]) { + // Update all cells. + for (KeyValue<Vector2i, CellData> &kv : tile_map) { + _rendering_occluders_update_cell(kv.value); + } + } else { + // Update dirty cells. + for (SelfList<CellData> *cell_data_list_element = dirty.cell_list.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { + CellData &cell_data = *cell_data_list_element->self(); + _rendering_occluders_update_cell(cell_data); + } + } + + // Updates on TileMap changes. + if (dirty.flags[DIRTY_FLAGS_TILE_MAP_IN_CANVAS] || dirty.flags[DIRTY_FLAGS_TILE_MAP_VISIBILITY]) { + for (KeyValue<Vector2i, CellData> &kv : tile_map) { + CellData &cell_data = kv.value; + for (const RID &occluder : cell_data.occluders) { + if (occluder.is_null()) { + continue; + } + Transform2D xform(0, tile_map_node->map_to_local(kv.key)); + rs->canvas_light_occluder_attach_to_canvas(occluder, tile_map_node->get_canvas()); + rs->canvas_light_occluder_set_transform(occluder, tile_map_node->get_global_transform() * xform); + } + } + } + } + + // ----------- + // Mark the rendering state as up to date. + _rendering_was_cleaned_up = forced_cleanup; +} + +void TileMapLayer::_rendering_quadrants_update_cell(CellData &r_cell_data, SelfList<RenderingQuadrant>::List &r_dirty_rendering_quadrant_list) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + + // Check if the cell is valid and retrieve its y_sort_origin. + bool is_valid = false; + int tile_y_sort_origin = 0; + TileSetSource *source; + if (tile_set->has_source(r_cell_data.cell.source_id)) { + source = *tile_set->get_source(r_cell_data.cell.source_id); + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source && atlas_source->has_tile(r_cell_data.cell.get_atlas_coords()) && atlas_source->has_alternative_tile(r_cell_data.cell.get_atlas_coords(), r_cell_data.cell.alternative_tile)) { + is_valid = true; + const TileData *tile_data; + if (r_cell_data.runtime_tile_data_cache) { + tile_data = r_cell_data.runtime_tile_data_cache; + } else { + tile_data = atlas_source->get_tile_data(r_cell_data.cell.get_atlas_coords(), r_cell_data.cell.alternative_tile); + } + tile_y_sort_origin = tile_data->get_y_sort_origin(); + } + } + + if (is_valid) { + // Get the quadrant coords. + Vector2 canvas_items_position; + Vector2i quadrant_coords; + if (tile_map_node->is_y_sort_enabled() && is_y_sort_enabled()) { + canvas_items_position = Vector2(0, tile_map_node->map_to_local(r_cell_data.coords).y + tile_y_sort_origin + y_sort_origin); + quadrant_coords = canvas_items_position * 100; + } else { + int quad_size = tile_map_node->get_rendering_quadrant_size(); + const Vector2i &coords = r_cell_data.coords; + + // Rounding down, instead of simply rounding towards zero (truncating). + quadrant_coords = Vector2i( + coords.x > 0 ? coords.x / quad_size : (coords.x - (quad_size - 1)) / quad_size, + coords.y > 0 ? coords.y / quad_size : (coords.y - (quad_size - 1)) / quad_size); + canvas_items_position = quad_size * quadrant_coords; + } + + Ref<RenderingQuadrant> rendering_quadrant; + if (rendering_quadrant_map.has(quadrant_coords)) { + // Reuse existing rendering quadrant. + rendering_quadrant = rendering_quadrant_map[quadrant_coords]; + } else { + // Create a new rendering quadrant. + rendering_quadrant.instantiate(); + rendering_quadrant->quadrant_coords = quadrant_coords; + rendering_quadrant->canvas_items_position = canvas_items_position; + rendering_quadrant_map[quadrant_coords] = rendering_quadrant; + } + + // Mark the old quadrant as dirty (if it exists). + if (r_cell_data.rendering_quadrant.is_valid()) { + if (!r_cell_data.rendering_quadrant->dirty_quadrant_list_element.in_list()) { + r_dirty_rendering_quadrant_list.add(&r_cell_data.rendering_quadrant->dirty_quadrant_list_element); + } + } + + // Remove the cell from that quadrant. + if (r_cell_data.rendering_quadrant_list_element.in_list()) { + r_cell_data.rendering_quadrant_list_element.remove_from_list(); + } + + // Add the cell to its new quadrant. + r_cell_data.rendering_quadrant = rendering_quadrant; + r_cell_data.rendering_quadrant->cells.add(&r_cell_data.rendering_quadrant_list_element); + + // Add the new quadrant to the dirty quadrant list. + if (!rendering_quadrant->dirty_quadrant_list_element.in_list()) { + r_dirty_rendering_quadrant_list.add(&rendering_quadrant->dirty_quadrant_list_element); + } + } else { + Ref<RenderingQuadrant> rendering_quadrant = r_cell_data.rendering_quadrant; + + // Remove the cell from its quadrant. + r_cell_data.rendering_quadrant = Ref<RenderingQuadrant>(); + if (r_cell_data.rendering_quadrant_list_element.in_list()) { + rendering_quadrant->cells.remove(&r_cell_data.rendering_quadrant_list_element); + } + + if (rendering_quadrant.is_valid()) { + // Add the quadrant to the dirty quadrant list. + if (!rendering_quadrant->dirty_quadrant_list_element.in_list()) { + r_dirty_rendering_quadrant_list.add(&rendering_quadrant->dirty_quadrant_list_element); + } + } + } +} + +void TileMapLayer::_rendering_occluders_clear_cell(CellData &r_cell_data) { + RenderingServer *rs = RenderingServer::get_singleton(); + + // Free the occluders. + for (const RID &rid : r_cell_data.occluders) { + rs->free(rid); + } + r_cell_data.occluders.clear(); +} + +void TileMapLayer::_rendering_occluders_update_cell(CellData &r_cell_data) { + bool node_visible = tile_map_node->is_visible_in_tree(); + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + RenderingServer *rs = RenderingServer::get_singleton(); + + // Free unused occluders then resize the occluders array. + for (uint32_t i = tile_set->get_occlusion_layers_count(); i < r_cell_data.occluders.size(); i++) { + RID occluder_id = r_cell_data.occluders[i]; + if (occluder_id.is_valid()) { + rs->free(occluder_id); + } + } + r_cell_data.occluders.resize(tile_set->get_occlusion_layers_count()); + + TileSetSource *source; + if (tile_set->has_source(r_cell_data.cell.source_id)) { + source = *tile_set->get_source(r_cell_data.cell.source_id); + + if (source->has_tile(r_cell_data.cell.get_atlas_coords()) && source->has_alternative_tile(r_cell_data.cell.get_atlas_coords(), r_cell_data.cell.alternative_tile)) { + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + // Get the tile data. + const TileData *tile_data; + if (r_cell_data.runtime_tile_data_cache) { + tile_data = r_cell_data.runtime_tile_data_cache; + } else { + tile_data = atlas_source->get_tile_data(r_cell_data.cell.get_atlas_coords(), r_cell_data.cell.alternative_tile); + } + + // Transform flags. + bool flip_h = (r_cell_data.cell.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_H); + bool flip_v = (r_cell_data.cell.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_V); + bool transpose = (r_cell_data.cell.alternative_tile & TileSetAtlasSource::TRANSFORM_TRANSPOSE); + + // Create, update or clear occluders. + for (uint32_t occlusion_layer_index = 0; occlusion_layer_index < r_cell_data.occluders.size(); occlusion_layer_index++) { + Ref<OccluderPolygon2D> occluder_polygon = tile_data->get_occluder(occlusion_layer_index); + + RID &occluder = r_cell_data.occluders[occlusion_layer_index]; + + if (occluder_polygon.is_valid()) { + // Create or update occluder. + Transform2D xform; + xform.set_origin(tile_map_node->map_to_local(r_cell_data.coords)); + if (!occluder.is_valid()) { + occluder = rs->canvas_light_occluder_create(); + } + rs->canvas_light_occluder_set_enabled(occluder, node_visible); + rs->canvas_light_occluder_set_transform(occluder, tile_map_node->get_global_transform() * xform); + rs->canvas_light_occluder_set_polygon(occluder, tile_data->get_occluder(occlusion_layer_index, flip_h, flip_v, transpose)->get_rid()); + rs->canvas_light_occluder_attach_to_canvas(occluder, tile_map_node->get_canvas()); + rs->canvas_light_occluder_set_light_mask(occluder, tile_set->get_occlusion_layer_light_mask(occlusion_layer_index)); + } else { + // Clear occluder. + if (occluder.is_valid()) { + rs->free(occluder); + occluder = RID(); + } + } + } + + return; + } + } + } + + // If we did not return earlier, clear the cell. + _rendering_occluders_clear_cell(r_cell_data); +} + +#ifdef DEBUG_ENABLED +void TileMapLayer::_rendering_draw_cell_debug(const RID &p_canvas_item, const Vector2i &p_quadrant_pos, const CellData &r_cell_data) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + ERR_FAIL_COND(!tile_set.is_valid()); + + if (!Engine::get_singleton()->is_editor_hint()) { + return; + } + + // Draw a placeholder for tiles needing one. + RenderingServer *rs = RenderingServer::get_singleton(); + const TileMapCell &c = r_cell_data.cell; + + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); + + if (source->has_tile(c.get_atlas_coords()) && source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + Vector2i grid_size = atlas_source->get_atlas_grid_size(); + if (!atlas_source->get_runtime_texture().is_valid() || c.get_atlas_coords().x >= grid_size.x || c.get_atlas_coords().y >= grid_size.y) { + // Generate a random color from the hashed values of the tiles. + Array to_hash; + to_hash.push_back(c.source_id); + to_hash.push_back(c.get_atlas_coords()); + to_hash.push_back(c.alternative_tile); + uint32_t hash = RandomPCG(to_hash.hash()).rand(); + + Color color; + color = color.from_hsv( + (float)((hash >> 24) & 0xFF) / 256.0, + Math::lerp(0.5, 1.0, (float)((hash >> 16) & 0xFF) / 256.0), + Math::lerp(0.5, 1.0, (float)((hash >> 8) & 0xFF) / 256.0), + 0.8); + + // Draw a placeholder tile. + Transform2D cell_to_quadrant; + cell_to_quadrant.set_origin(tile_map_node->map_to_local(r_cell_data.coords) - p_quadrant_pos); + rs->canvas_item_add_set_transform(p_canvas_item, cell_to_quadrant); + rs->canvas_item_add_circle(p_canvas_item, Vector2(), MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 4.0, color); + } + } + } + } +} +#endif // DEBUG_ENABLED + +/////////////////////////////// Physics ////////////////////////////////////// + +void TileMapLayer::_physics_update() { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + + // Check if we should cleanup everything. + bool forced_cleanup = in_destructor || !enabled || !tile_map_node->is_inside_tree() || !tile_set.is_valid(); + if (forced_cleanup) { + // Clean everything. + for (KeyValue<Vector2i, CellData> &kv : tile_map) { + _physics_clear_cell(kv.value); + } + } else { + if (_physics_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET] || dirty.flags[DIRTY_FLAGS_TILE_MAP_COLLISION_ANIMATABLE]) { + // Update all cells. + for (KeyValue<Vector2i, CellData> &kv : tile_map) { + _physics_update_cell(kv.value); + } + } else { + // Update dirty cells. + for (SelfList<CellData> *cell_data_list_element = dirty.cell_list.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { + CellData &cell_data = *cell_data_list_element->self(); + _physics_update_cell(cell_data); + } + } + } + + // ----------- + // Mark the physics state as up to date. + _physics_was_cleaned_up = forced_cleanup; +} + +void TileMapLayer::_physics_notify_tilemap_change(TileMapLayer::DirtyFlags p_what) { + Transform2D gl_transform = tile_map_node->get_global_transform(); + PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); + + bool in_editor = false; +#ifdef TOOLS_ENABLED + in_editor = Engine::get_singleton()->is_editor_hint(); +#endif + + if (p_what == DIRTY_FLAGS_TILE_MAP_XFORM) { + if (tile_map_node->is_inside_tree() && (!tile_map_node->is_collision_animatable() || in_editor)) { + // Move the collisison shapes along with the TileMap. + for (KeyValue<Vector2i, CellData> &kv : tile_map) { + const CellData &cell_data = kv.value; + + for (RID body : cell_data.bodies) { + if (body.is_valid()) { + Transform2D xform(0, tile_map_node->map_to_local(bodies_coords[body])); + xform = gl_transform * xform; + ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); + } + } + } + } + } else if (p_what == DIRTY_FLAGS_TILE_MAP_LOCAL_XFORM) { + // With collisions animatable, move the collisison shapes along with the TileMap only on local xform change (they are synchornized on physics tick instead). + if (tile_map_node->is_inside_tree() && tile_map_node->is_collision_animatable() && !in_editor) { + for (KeyValue<Vector2i, CellData> &kv : tile_map) { + const CellData &cell_data = kv.value; + + for (RID body : cell_data.bodies) { + if (body.is_valid()) { + Transform2D xform(0, tile_map_node->map_to_local(bodies_coords[body])); + xform = gl_transform * xform; + ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); + } + } + } + } + } else if (p_what == DIRTY_FLAGS_TILE_MAP_IN_TREE) { + // Changes in the tree may cause the space to change (e.g. when reparenting to a SubViewport). + if (tile_map_node->is_inside_tree()) { + RID space = tile_map_node->get_world_2d()->get_space(); + + for (KeyValue<Vector2i, CellData> &kv : tile_map) { + const CellData &cell_data = kv.value; + + for (RID body : cell_data.bodies) { + if (body.is_valid()) { + ps->body_set_space(body, space); + } + } + } + } + } +} + +void TileMapLayer::_physics_clear_cell(CellData &r_cell_data) { + PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); + + // Clear bodies. + for (RID body : r_cell_data.bodies) { + if (body.is_valid()) { + bodies_coords.erase(body); + ps->free(body); + } + } + r_cell_data.bodies.clear(); +} + +void TileMapLayer::_physics_update_cell(CellData &r_cell_data) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + Transform2D gl_transform = tile_map_node->get_global_transform(); + RID space = tile_map_node->get_world_2d()->get_space(); + PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); + + // Recreate bodies and shapes. + TileMapCell &c = r_cell_data.cell; + + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); + + if (source->has_tile(c.get_atlas_coords()) && source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + const TileData *tile_data; + if (r_cell_data.runtime_tile_data_cache) { + tile_data = r_cell_data.runtime_tile_data_cache; + } else { + tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile); + } + + // Transform flags. + bool flip_h = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_H); + bool flip_v = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_V); + bool transpose = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_TRANSPOSE); + + // Free unused bodies then resize the bodies array. + for (uint32_t i = tile_set->get_physics_layers_count(); i < r_cell_data.bodies.size(); i++) { + RID body = r_cell_data.bodies[i]; + if (body.is_valid()) { + bodies_coords.erase(body); + ps->free(body); + } + } + r_cell_data.bodies.resize(tile_set->get_physics_layers_count()); + + for (uint32_t tile_set_physics_layer = 0; tile_set_physics_layer < (uint32_t)tile_set->get_physics_layers_count(); tile_set_physics_layer++) { + Ref<PhysicsMaterial> physics_material = tile_set->get_physics_layer_physics_material(tile_set_physics_layer); + uint32_t physics_layer = tile_set->get_physics_layer_collision_layer(tile_set_physics_layer); + uint32_t physics_mask = tile_set->get_physics_layer_collision_mask(tile_set_physics_layer); + + RID body = r_cell_data.bodies[tile_set_physics_layer]; + if (tile_data->get_collision_polygons_count(tile_set_physics_layer) == 0) { + // No body needed, free it if it exists. + if (body.is_valid()) { + bodies_coords.erase(body); + ps->free(body); + } + body = RID(); + } else { + // Create or update the body. + if (!body.is_valid()) { + body = ps->body_create(); + } + bodies_coords[body] = r_cell_data.coords; + ps->body_set_mode(body, tile_map_node->is_collision_animatable() ? PhysicsServer2D::BODY_MODE_KINEMATIC : PhysicsServer2D::BODY_MODE_STATIC); + ps->body_set_space(body, space); + + Transform2D xform; + xform.set_origin(tile_map_node->map_to_local(r_cell_data.coords)); + xform = gl_transform * xform; + ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); + + ps->body_attach_object_instance_id(body, tile_map_node->get_instance_id()); + ps->body_set_collision_layer(body, physics_layer); + ps->body_set_collision_mask(body, physics_mask); + ps->body_set_pickable(body, false); + ps->body_set_state(body, PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, tile_data->get_constant_linear_velocity(tile_set_physics_layer)); + ps->body_set_state(body, PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, tile_data->get_constant_angular_velocity(tile_set_physics_layer)); + + if (!physics_material.is_valid()) { + ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, 0); + ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, 1); + } else { + ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, physics_material->computed_bounce()); + ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, physics_material->computed_friction()); + } + + // Clear body's shape if needed. + ps->body_clear_shapes(body); + + // Add the shapes to the body. + int body_shape_index = 0; + for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(tile_set_physics_layer); polygon_index++) { + // Iterate over the polygons. + bool one_way_collision = tile_data->is_collision_polygon_one_way(tile_set_physics_layer, polygon_index); + float one_way_collision_margin = tile_data->get_collision_polygon_one_way_margin(tile_set_physics_layer, polygon_index); + int shapes_count = tile_data->get_collision_polygon_shapes_count(tile_set_physics_layer, polygon_index); + for (int shape_index = 0; shape_index < shapes_count; shape_index++) { + // Add decomposed convex shapes. + Ref<ConvexPolygonShape2D> shape = tile_data->get_collision_polygon_shape(tile_set_physics_layer, polygon_index, shape_index, flip_h, flip_v, transpose); + ps->body_add_shape(body, shape->get_rid()); + ps->body_set_shape_as_one_way_collision(body, body_shape_index, one_way_collision, one_way_collision_margin); + + body_shape_index++; + } + } + } + + // Set the body again. + r_cell_data.bodies[tile_set_physics_layer] = body; + } + + return; + } + } + } + + // If we did not return earlier, clear the cell. + _physics_clear_cell(r_cell_data); +} + +#ifdef DEBUG_ENABLED +void TileMapLayer::_physics_draw_cell_debug(const RID &p_canvas_item, const Vector2i &p_quadrant_pos, const CellData &r_cell_data) { + // Draw the debug collision shapes. + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + ERR_FAIL_COND(!tile_set.is_valid()); + + if (!tile_map_node->get_tree()) { + return; + } + + bool show_collision = false; + switch (tile_map_node->get_collision_visibility_mode()) { + case TileMap::VISIBILITY_MODE_DEFAULT: + show_collision = !Engine::get_singleton()->is_editor_hint() && tile_map_node->get_tree()->is_debugging_collisions_hint(); + break; + case TileMap::VISIBILITY_MODE_FORCE_HIDE: + show_collision = false; + break; + case TileMap::VISIBILITY_MODE_FORCE_SHOW: + show_collision = true; + break; + } + if (!show_collision) { + return; + } + + RenderingServer *rs = RenderingServer::get_singleton(); + PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); + + Color debug_collision_color = tile_map_node->get_tree()->get_debug_collisions_color(); + Vector<Color> color; + color.push_back(debug_collision_color); + + Transform2D quadrant_to_local(0, p_quadrant_pos); + Transform2D global_to_quadrant = (tile_map_node->get_global_transform() * quadrant_to_local).affine_inverse(); + + for (RID body : r_cell_data.bodies) { + if (body.is_valid()) { + Transform2D body_to_quadrant = global_to_quadrant * Transform2D(ps->body_get_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM)); + rs->canvas_item_add_set_transform(p_canvas_item, body_to_quadrant); + for (int shape_index = 0; shape_index < ps->body_get_shape_count(body); shape_index++) { + const RID &shape = ps->body_get_shape(body, shape_index); + const PhysicsServer2D::ShapeType &type = ps->shape_get_type(shape); + if (type == PhysicsServer2D::SHAPE_CONVEX_POLYGON) { + rs->canvas_item_add_polygon(p_canvas_item, ps->shape_get_data(shape), color); + } else { + WARN_PRINT("Wrong shape type for a tile, should be SHAPE_CONVEX_POLYGON."); + } + } + rs->canvas_item_add_set_transform(p_canvas_item, Transform2D()); + } + } +}; +#endif // DEBUG_ENABLED + +/////////////////////////////// Navigation ////////////////////////////////////// + +void TileMapLayer::_navigation_update() { + ERR_FAIL_NULL(NavigationServer2D::get_singleton()); + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + NavigationServer2D *ns = NavigationServer2D::get_singleton(); + + // Check if we should cleanup everything. + bool forced_cleanup = in_destructor || !enabled || !navigation_enabled || !tile_map_node->is_inside_tree() || !tile_set.is_valid(); + + // ----------- Layer level processing ----------- + if (forced_cleanup) { + if (navigation_map.is_valid() && !uses_world_navigation_map) { + ns->free(navigation_map); + navigation_map = RID(); + } + } else { + // Update navigation maps. + if (!navigation_map.is_valid()) { + if (layer_index_in_tile_map_node == 0) { + // Use the default World2D navigation map for the first layer when empty. + navigation_map = tile_map_node->get_world_2d()->get_navigation_map(); + uses_world_navigation_map = true; + } else { + RID new_layer_map = ns->map_create(); + // Set the default NavigationPolygon cell_size on the new map as a mismatch causes an error. + ns->map_set_cell_size(new_layer_map, 1.0); + ns->map_set_active(new_layer_map, true); + navigation_map = new_layer_map; + uses_world_navigation_map = false; + } + } + } + + // ----------- Navigation regions processing ----------- + if (forced_cleanup) { + // Clean everything. + for (KeyValue<Vector2i, CellData> &kv : tile_map) { + _navigation_clear_cell(kv.value); + } + } else { + if (_navigation_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET]) { + // Update all cells. + for (KeyValue<Vector2i, CellData> &kv : tile_map) { + _navigation_update_cell(kv.value); + } + } else { + // Update dirty cells. + for (SelfList<CellData> *cell_data_list_element = dirty.cell_list.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { + CellData &cell_data = *cell_data_list_element->self(); + _navigation_update_cell(cell_data); + } + } + + if (dirty.flags[DIRTY_FLAGS_TILE_MAP_XFORM]) { + Transform2D tilemap_xform = tile_map_node->get_global_transform(); + for (KeyValue<Vector2i, CellData> &kv : tile_map) { + const CellData &cell_data = kv.value; + // Update navigation regions transform. + for (const RID ®ion : cell_data.navigation_regions) { + if (!region.is_valid()) { + continue; + } + Transform2D tile_transform; + tile_transform.set_origin(tile_map_node->map_to_local(kv.key)); + NavigationServer2D::get_singleton()->region_set_transform(region, tilemap_xform * tile_transform); + } + } + } + } + + // ----------- + // Mark the navigation state as up to date. + _navigation_was_cleaned_up = forced_cleanup; +} + +void TileMapLayer::_navigation_clear_cell(CellData &r_cell_data) { + NavigationServer2D *ns = NavigationServer2D::get_singleton(); + // Clear navigation shapes. + for (uint32_t i = 0; i < r_cell_data.navigation_regions.size(); i++) { + const RID ®ion = r_cell_data.navigation_regions[i]; + if (region.is_valid()) { + ns->region_set_map(region, RID()); + ns->free(region); + } + } + r_cell_data.navigation_regions.clear(); +} + +void TileMapLayer::_navigation_update_cell(CellData &r_cell_data) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + NavigationServer2D *ns = NavigationServer2D::get_singleton(); + Transform2D tilemap_xform = tile_map_node->get_global_transform(); + + // Get the navigation polygons and create regions. + TileMapCell &c = r_cell_data.cell; + + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); + + if (source->has_tile(c.get_atlas_coords()) && source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + const TileData *tile_data; + if (r_cell_data.runtime_tile_data_cache) { + tile_data = r_cell_data.runtime_tile_data_cache; + } else { + tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile); + } + + // Transform flags. + bool flip_h = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_H); + bool flip_v = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_V); + bool transpose = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_TRANSPOSE); + + // Free unused regions then resize the regions array. + for (uint32_t i = tile_set->get_navigation_layers_count(); i < r_cell_data.navigation_regions.size(); i++) { + RID ®ion = r_cell_data.navigation_regions[i]; + if (region.is_valid()) { + ns->region_set_map(region, RID()); + ns->free(region); + region = RID(); + } + } + r_cell_data.navigation_regions.resize(tile_set->get_navigation_layers_count()); + + // Create, update or clear regions. + for (uint32_t navigation_layer_index = 0; navigation_layer_index < r_cell_data.navigation_regions.size(); navigation_layer_index++) { + Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(navigation_layer_index, flip_h, flip_v, transpose); + + RID ®ion = r_cell_data.navigation_regions[navigation_layer_index]; + + if (navigation_polygon.is_valid() && (navigation_polygon->get_polygon_count() > 0 || navigation_polygon->get_outline_count() > 0)) { + // Create or update regions. + Transform2D tile_transform; + tile_transform.set_origin(tile_map_node->map_to_local(r_cell_data.coords)); + if (!region.is_valid()) { + region = ns->region_create(); + } + ns->region_set_owner_id(region, tile_map_node->get_instance_id()); + ns->region_set_map(region, navigation_map); + ns->region_set_transform(region, tilemap_xform * tile_transform); + ns->region_set_navigation_layers(region, tile_set->get_navigation_layer_layers(navigation_layer_index)); + ns->region_set_navigation_polygon(region, navigation_polygon); + } else { + // Clear region. + if (region.is_valid()) { + ns->region_set_map(region, RID()); + ns->free(region); + region = RID(); + } + } + } + + return; + } + } + } + + // If we did not return earlier, clear the cell. + _navigation_clear_cell(r_cell_data); +} + +#ifdef DEBUG_ENABLED +void TileMapLayer::_navigation_draw_cell_debug(const RID &p_canvas_item, const Vector2i &p_quadrant_pos, const CellData &r_cell_data) { + // Draw the debug collision shapes. + bool show_navigation = false; + switch (tile_map_node->get_navigation_visibility_mode()) { + case TileMap::VISIBILITY_MODE_DEFAULT: + show_navigation = !Engine::get_singleton()->is_editor_hint() && tile_map_node->get_tree()->is_debugging_navigation_hint(); + break; + case TileMap::VISIBILITY_MODE_FORCE_HIDE: + show_navigation = false; + break; + case TileMap::VISIBILITY_MODE_FORCE_SHOW: + show_navigation = true; + break; + } + if (!show_navigation) { + return; + } + + // Check if the navigation is used. + if (r_cell_data.navigation_regions.is_empty()) { + return; + } + + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + + RenderingServer *rs = RenderingServer::get_singleton(); + const NavigationServer2D *ns2d = NavigationServer2D::get_singleton(); + + bool enabled_geometry_face_random_color = ns2d->get_debug_navigation_enable_geometry_face_random_color(); + bool enabled_edge_lines = ns2d->get_debug_navigation_enable_edge_lines(); + + Color debug_face_color = ns2d->get_debug_navigation_geometry_face_color(); + Color debug_edge_color = ns2d->get_debug_navigation_geometry_edge_color(); + + RandomPCG rand; + + const TileMapCell &c = r_cell_data.cell; + + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); + + if (source->has_tile(c.get_atlas_coords()) && source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + const TileData *tile_data; + if (r_cell_data.runtime_tile_data_cache) { + tile_data = r_cell_data.runtime_tile_data_cache; + } else { + tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile); + } + + Transform2D cell_to_quadrant; + cell_to_quadrant.set_origin(tile_map_node->map_to_local(r_cell_data.coords) - p_quadrant_pos); + rs->canvas_item_add_set_transform(p_canvas_item, cell_to_quadrant); + + for (int layer_index = 0; layer_index < tile_set->get_navigation_layers_count(); layer_index++) { + bool flip_h = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_H); + bool flip_v = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_V); + bool transpose = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_TRANSPOSE); + Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(layer_index, flip_h, flip_v, transpose); + if (navigation_polygon.is_valid()) { + Vector<Vector2> navigation_polygon_vertices = navigation_polygon->get_vertices(); + if (navigation_polygon_vertices.size() < 3) { + continue; + } + + for (int i = 0; i < navigation_polygon->get_polygon_count(); i++) { + // An array of vertices for this polygon. + Vector<int> polygon = navigation_polygon->get_polygon(i); + Vector<Vector2> debug_polygon_vertices; + debug_polygon_vertices.resize(polygon.size()); + for (int j = 0; j < polygon.size(); j++) { + ERR_FAIL_INDEX(polygon[j], navigation_polygon_vertices.size()); + debug_polygon_vertices.write[j] = navigation_polygon_vertices[polygon[j]]; + } + + // Generate the polygon color, slightly randomly modified from the settings one. + Color random_variation_color = debug_face_color; + if (enabled_geometry_face_random_color) { + random_variation_color.set_hsv( + debug_face_color.get_h() + rand.random(-1.0, 1.0) * 0.1, + debug_face_color.get_s(), + debug_face_color.get_v() + rand.random(-1.0, 1.0) * 0.2); + } + random_variation_color.a = debug_face_color.a; + + Vector<Color> debug_face_colors; + debug_face_colors.push_back(random_variation_color); + rs->canvas_item_add_polygon(p_canvas_item, debug_polygon_vertices, debug_face_colors); + + if (enabled_edge_lines) { + Vector<Color> debug_edge_colors; + debug_edge_colors.push_back(debug_edge_color); + debug_polygon_vertices.push_back(debug_polygon_vertices[0]); // Add first again for closing polyline. + rs->canvas_item_add_polyline(p_canvas_item, debug_polygon_vertices, debug_edge_colors); + } + } + } + } + } + } + } +} +#endif // DEBUG_ENABLED + +/////////////////////////////// Scenes ////////////////////////////////////// + +void TileMapLayer::_scenes_update() { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + + // Check if we should cleanup everything. + bool forced_cleanup = in_destructor || !enabled || !tile_map_node->is_inside_tree() || !tile_set.is_valid(); + + if (forced_cleanup) { + // Clean everything. + for (KeyValue<Vector2i, CellData> &kv : tile_map) { + _scenes_clear_cell(kv.value); + } + } else { + if (_scenes_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET]) { + // Update all cells. + for (KeyValue<Vector2i, CellData> &kv : tile_map) { + _scenes_update_cell(kv.value); + } + } else { + // Update dirty cells. + for (SelfList<CellData> *cell_data_list_element = dirty.cell_list.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { + CellData &cell_data = *cell_data_list_element->self(); + _scenes_update_cell(cell_data); + } + } + } + + // ----------- + // Mark the scenes state as up to date. + _scenes_was_cleaned_up = forced_cleanup; +} + +void TileMapLayer::_scenes_clear_cell(CellData &r_cell_data) { + // Cleanup existing scene. + Node *node = tile_map_node->get_node_or_null(r_cell_data.scene); + if (node) { + node->queue_free(); + } + r_cell_data.scene = ""; +} + +void TileMapLayer::_scenes_update_cell(CellData &r_cell_data) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + + // Clear the scene in any case. + _scenes_clear_cell(r_cell_data); + + // Create the scene. + const TileMapCell &c = r_cell_data.cell; + + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); + + if (source->has_tile(c.get_atlas_coords()) && source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source); + if (scenes_collection_source) { + Ref<PackedScene> packed_scene = scenes_collection_source->get_scene_tile_scene(c.alternative_tile); + if (packed_scene.is_valid()) { + Node *scene = packed_scene->instantiate(); + Control *scene_as_control = Object::cast_to<Control>(scene); + Node2D *scene_as_node2d = Object::cast_to<Node2D>(scene); + if (scene_as_control) { + scene_as_control->set_position(tile_map_node->map_to_local(r_cell_data.coords) + scene_as_control->get_position()); + } else if (scene_as_node2d) { + Transform2D xform; + xform.set_origin(tile_map_node->map_to_local(r_cell_data.coords)); + scene_as_node2d->set_transform(xform * scene_as_node2d->get_transform()); + } + tile_map_node->add_child(scene); + r_cell_data.scene = scene->get_name(); + } + } + } + } +} + +#ifdef DEBUG_ENABLED +void TileMapLayer::_scenes_draw_cell_debug(const RID &p_canvas_item, const Vector2i &p_quadrant_pos, const CellData &r_cell_data) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + ERR_FAIL_COND(!tile_set.is_valid()); + + if (!Engine::get_singleton()->is_editor_hint()) { + return; + } + + // Draw a placeholder for scenes needing one. + RenderingServer *rs = RenderingServer::get_singleton(); + + const TileMapCell &c = r_cell_data.cell; + + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); + + if (!source->has_tile(c.get_atlas_coords()) || !source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + return; + } + + TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source); + if (scenes_collection_source) { + if (!scenes_collection_source->get_scene_tile_scene(c.alternative_tile).is_valid() || scenes_collection_source->get_scene_tile_display_placeholder(c.alternative_tile)) { + // Generate a random color from the hashed values of the tiles. + Array to_hash; + to_hash.push_back(c.source_id); + to_hash.push_back(c.alternative_tile); + uint32_t hash = RandomPCG(to_hash.hash()).rand(); + + Color color; + color = color.from_hsv( + (float)((hash >> 24) & 0xFF) / 256.0, + Math::lerp(0.5, 1.0, (float)((hash >> 16) & 0xFF) / 256.0), + Math::lerp(0.5, 1.0, (float)((hash >> 8) & 0xFF) / 256.0), + 0.8); + + // Draw a placeholder tile. + Transform2D cell_to_quadrant; + cell_to_quadrant.set_origin(tile_map_node->map_to_local(r_cell_data.coords) - p_quadrant_pos); + rs->canvas_item_add_set_transform(p_canvas_item, cell_to_quadrant); + rs->canvas_item_add_circle(p_canvas_item, Vector2(), MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 4.0, color); + } + } + } +} +#endif // DEBUG_ENABLED + +///////////////////////////////////////////////////////////////////// + +void TileMapLayer::_build_runtime_update_tile_data() { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + + // Check if we should cleanup everything. + bool forced_cleanup = in_destructor || !enabled || !tile_map_node->is_inside_tree() || !tile_set.is_valid() || !tile_map_node->is_visible_in_tree(); + if (!forced_cleanup) { + if (tile_map_node->GDVIRTUAL_IS_OVERRIDDEN(_use_tile_data_runtime_update) && tile_map_node->GDVIRTUAL_IS_OVERRIDDEN(_tile_data_runtime_update)) { + if (_runtime_update_tile_data_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_MAP_TILE_SET]) { + for (KeyValue<Vector2i, CellData> &E : tile_map) { + _build_runtime_update_tile_data_for_cell(E.value); + } + } else if (dirty.flags[DIRTY_FLAGS_TILE_MAP_RUNTIME_UPDATE]) { + for (KeyValue<Vector2i, CellData> &E : tile_map) { + _build_runtime_update_tile_data_for_cell(E.value, true); + } + } else { + for (SelfList<CellData> *cell_data_list_element = dirty.cell_list.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { + CellData &cell_data = *cell_data_list_element->self(); + _build_runtime_update_tile_data_for_cell(cell_data); + } + } + } + } + + // ----------- + // Mark the navigation state as up to date. + _runtime_update_tile_data_was_cleaned_up = forced_cleanup; +} + +void TileMapLayer::_build_runtime_update_tile_data_for_cell(CellData &r_cell_data, bool p_auto_add_to_dirty_list) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + + TileMapCell &c = r_cell_data.cell; + TileSetSource *source; + if (tile_set->has_source(c.source_id)) { + source = *tile_set->get_source(c.source_id); + + if (source->has_tile(c.get_atlas_coords()) && source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + bool ret = false; + if (tile_map_node->GDVIRTUAL_CALL(_use_tile_data_runtime_update, layer_index_in_tile_map_node, r_cell_data.coords, ret) && ret) { + TileData *tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile); + + // Create the runtime TileData. + TileData *tile_data_runtime_use = tile_data->duplicate(); + tile_data_runtime_use->set_allow_transform(true); + r_cell_data.runtime_tile_data_cache = tile_data_runtime_use; + + tile_map_node->GDVIRTUAL_CALL(_tile_data_runtime_update, layer_index_in_tile_map_node, r_cell_data.coords, tile_data_runtime_use); + + if (p_auto_add_to_dirty_list) { + dirty.cell_list.add(&r_cell_data.dirty_list_element); + } + } + } + } + } +} + +void TileMapLayer::_clear_runtime_update_tile_data() { + for (SelfList<CellData> *cell_data_list_element = dirty.cell_list.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { + CellData &cell_data = *cell_data_list_element->self(); + + // Clear the runtime tile data. + if (cell_data.runtime_tile_data_cache) { + memdelete(cell_data.runtime_tile_data_cache); + cell_data.runtime_tile_data_cache = nullptr; + } + } +} + +TileSet::TerrainsPattern TileMapLayer::_get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, const RBSet<TerrainConstraint> &p_constraints, TileSet::TerrainsPattern p_current_pattern) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + if (!tile_set.is_valid()) { + return TileSet::TerrainsPattern(); + } + // Returns all tiles compatible with the given constraints. + RBMap<TileSet::TerrainsPattern, int> terrain_pattern_score; + RBSet<TileSet::TerrainsPattern> pattern_set = tile_set->get_terrains_pattern_set(p_terrain_set); + ERR_FAIL_COND_V(pattern_set.is_empty(), TileSet::TerrainsPattern()); + for (TileSet::TerrainsPattern &terrain_pattern : pattern_set) { + int score = 0; + + // Check the center bit constraint. + TerrainConstraint terrain_constraint = TerrainConstraint(tile_map_node, p_position, terrain_pattern.get_terrain()); + const RBSet<TerrainConstraint>::Element *in_set_constraint_element = p_constraints.find(terrain_constraint); + if (in_set_constraint_element) { + if (in_set_constraint_element->get().get_terrain() != terrain_constraint.get_terrain()) { + score += in_set_constraint_element->get().get_priority(); + } + } else if (p_current_pattern.get_terrain() != terrain_pattern.get_terrain()) { + continue; // Ignore a pattern that cannot keep bits without constraints unmodified. + } + + // Check the surrounding bits + bool invalid_pattern = false; + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) { + // Check if the bit is compatible with the constraints. + TerrainConstraint terrain_bit_constraint = TerrainConstraint(tile_map_node, p_position, bit, terrain_pattern.get_terrain_peering_bit(bit)); + in_set_constraint_element = p_constraints.find(terrain_bit_constraint); + if (in_set_constraint_element) { + if (in_set_constraint_element->get().get_terrain() != terrain_bit_constraint.get_terrain()) { + score += in_set_constraint_element->get().get_priority(); + } + } else if (p_current_pattern.get_terrain_peering_bit(bit) != terrain_pattern.get_terrain_peering_bit(bit)) { + invalid_pattern = true; // Ignore a pattern that cannot keep bits without constraints unmodified. + break; + } + } + } + if (invalid_pattern) { + continue; + } + + terrain_pattern_score[terrain_pattern] = score; + } + + // Compute the minimum score. + TileSet::TerrainsPattern min_score_pattern = p_current_pattern; + int min_score = INT32_MAX; + for (KeyValue<TileSet::TerrainsPattern, int> E : terrain_pattern_score) { + if (E.value < min_score) { + min_score_pattern = E.key; + min_score = E.value; + } + } + + return min_score_pattern; +} + +RBSet<TerrainConstraint> TileMapLayer::_get_terrain_constraints_from_added_pattern(const Vector2i &p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + if (!tile_set.is_valid()) { + return RBSet<TerrainConstraint>(); + } + + // Compute the constraints needed from the surrounding tiles. + RBSet<TerrainConstraint> output; + output.insert(TerrainConstraint(tile_map_node, p_position, p_terrains_pattern.get_terrain())); + + for (uint32_t i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor side = TileSet::CellNeighbor(i); + if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, side)) { + TerrainConstraint c = TerrainConstraint(tile_map_node, p_position, side, p_terrains_pattern.get_terrain_peering_bit(side)); + output.insert(c); + } + } + + return output; +} + +RBSet<TerrainConstraint> TileMapLayer::_get_terrain_constraints_from_painted_cells_list(const RBSet<Vector2i> &p_painted, int p_terrain_set, bool p_ignore_empty_terrains) const { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + if (!tile_set.is_valid()) { + return RBSet<TerrainConstraint>(); + } + + ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), RBSet<TerrainConstraint>()); + + // Build a set of dummy constraints to get the constrained points. + RBSet<TerrainConstraint> dummy_constraints; + for (const Vector2i &E : p_painted) { + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { // Iterates over neighbor bits. + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) { + dummy_constraints.insert(TerrainConstraint(tile_map_node, E, bit, -1)); + } + } + } + + // For each constrained point, we get all overlapping tiles, and select the most adequate terrain for it. + RBSet<TerrainConstraint> constraints; + for (const TerrainConstraint &E_constraint : dummy_constraints) { + HashMap<int, int> terrain_count; + + // Count the number of occurrences per terrain. + HashMap<Vector2i, TileSet::CellNeighbor> overlapping_terrain_bits = E_constraint.get_overlapping_coords_and_peering_bits(); + for (const KeyValue<Vector2i, TileSet::CellNeighbor> &E_overlapping : overlapping_terrain_bits) { + TileData *neighbor_tile_data = nullptr; + TileMapCell neighbor_cell = get_cell(E_overlapping.key); + if (neighbor_cell.source_id != TileSet::INVALID_SOURCE) { + Ref<TileSetSource> source = tile_set->get_source(neighbor_cell.source_id); + Ref<TileSetAtlasSource> atlas_source = source; + if (atlas_source.is_valid()) { + TileData *tile_data = atlas_source->get_tile_data(neighbor_cell.get_atlas_coords(), neighbor_cell.alternative_tile); + if (tile_data && tile_data->get_terrain_set() == p_terrain_set) { + neighbor_tile_data = tile_data; + } + } + } + + int terrain = neighbor_tile_data ? neighbor_tile_data->get_terrain_peering_bit(TileSet::CellNeighbor(E_overlapping.value)) : -1; + if (!p_ignore_empty_terrains || terrain >= 0) { + if (!terrain_count.has(terrain)) { + terrain_count[terrain] = 0; + } + terrain_count[terrain] += 1; + } + } + + // Get the terrain with the max number of occurrences. + int max = 0; + int max_terrain = -1; + for (const KeyValue<int, int> &E_terrain_count : terrain_count) { + if (E_terrain_count.value > max) { + max = E_terrain_count.value; + max_terrain = E_terrain_count.key; + } + } + + // Set the adequate terrain. + if (max > 0) { + TerrainConstraint c = E_constraint; + c.set_terrain(max_terrain); + constraints.insert(c); + } + } + + // Add the centers as constraints. + for (Vector2i E_coords : p_painted) { + TileData *tile_data = nullptr; + TileMapCell cell = get_cell(E_coords); + if (cell.source_id != TileSet::INVALID_SOURCE) { + Ref<TileSetSource> source = tile_set->get_source(cell.source_id); + Ref<TileSetAtlasSource> atlas_source = source; + if (atlas_source.is_valid()) { + tile_data = atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile); + } + } + + int terrain = (tile_data && tile_data->get_terrain_set() == p_terrain_set) ? tile_data->get_terrain() : -1; + if (!p_ignore_empty_terrains || terrain >= 0) { + constraints.insert(TerrainConstraint(tile_map_node, E_coords, terrain)); + } + } + + return constraints; +} + +void TileMapLayer::set_tile_map(TileMap *p_tile_map) { + tile_map_node = p_tile_map; +} + +void TileMapLayer::set_layer_index_in_tile_map_node(int p_index) { + if (p_index == layer_index_in_tile_map_node) { + return; + } + layer_index_in_tile_map_node = p_index; + dirty.flags[DIRTY_FLAGS_LAYER_INDEX_IN_TILE_MAP_NODE] = true; + tile_map_node->queue_internal_update(); +} + +Rect2 TileMapLayer::get_rect(bool &r_changed) const { + // Compute the displayed area of the tilemap. + r_changed = false; +#ifdef DEBUG_ENABLED + + if (rect_cache_dirty) { + Rect2 r_total; + bool first = true; + for (const KeyValue<Vector2i, CellData> &E : tile_map) { + Rect2 r; + r.position = tile_map_node->map_to_local(E.key); + r.size = Size2(); + if (first) { + r_total = r; + first = false; + } else { + r_total = r_total.merge(r); + } + } + + r_changed = rect_cache != r_total; + + rect_cache = r_total; + rect_cache_dirty = false; + } +#endif + return rect_cache; +} + +HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_constraints(const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> &p_constraints) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + if (!tile_set.is_valid()) { + return HashMap<Vector2i, TileSet::TerrainsPattern>(); + } + + // Copy the constraints set. + RBSet<TerrainConstraint> constraints = p_constraints; + + // Output map. + HashMap<Vector2i, TileSet::TerrainsPattern> output; + + // Add all positions to a set. + for (int i = 0; i < p_to_replace.size(); i++) { + const Vector2i &coords = p_to_replace[i]; + + // Select the best pattern for the given constraints. + TileSet::TerrainsPattern current_pattern = TileSet::TerrainsPattern(*tile_set, p_terrain_set); + TileMapCell cell = get_cell(coords); + if (cell.source_id != TileSet::INVALID_SOURCE) { + TileSetSource *source = *tile_set->get_source(cell.source_id); + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + // Get tile data. + TileData *tile_data = atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile); + if (tile_data && tile_data->get_terrain_set() == p_terrain_set) { + current_pattern = tile_data->get_terrains_pattern(); + } + } + } + TileSet::TerrainsPattern pattern = _get_best_terrain_pattern_for_constraints(p_terrain_set, coords, constraints, current_pattern); + + // Update the constraint set with the new ones. + RBSet<TerrainConstraint> new_constraints = _get_terrain_constraints_from_added_pattern(coords, p_terrain_set, pattern); + for (const TerrainConstraint &E_constraint : new_constraints) { + if (constraints.has(E_constraint)) { + constraints.erase(E_constraint); + } + TerrainConstraint c = E_constraint; + c.set_priority(5); + constraints.insert(c); + } + + output[coords] = pattern; + } + return output; +} + +HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_connect(const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { + HashMap<Vector2i, TileSet::TerrainsPattern> output; + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + ERR_FAIL_COND_V(!tile_set.is_valid(), output); + ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), output); + + // Build list and set of tiles that can be modified (painted and their surroundings). + Vector<Vector2i> can_modify_list; + RBSet<Vector2i> can_modify_set; + RBSet<Vector2i> painted_set; + for (int i = p_coords_array.size() - 1; i >= 0; i--) { + const Vector2i &coords = p_coords_array[i]; + can_modify_list.push_back(coords); + can_modify_set.insert(coords); + painted_set.insert(coords); + } + for (Vector2i coords : p_coords_array) { + // Find the adequate neighbor. + for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); + if (tile_map_node->is_existing_neighbor(bit)) { + Vector2i neighbor = tile_map_node->get_neighbor_cell(coords, bit); + if (!can_modify_set.has(neighbor)) { + can_modify_list.push_back(neighbor); + can_modify_set.insert(neighbor); + } + } + } + } + + // Build a set, out of the possibly modified tiles, of the one with a center bit that is set (or will be) to the painted terrain. + RBSet<Vector2i> cells_with_terrain_center_bit; + for (Vector2i coords : can_modify_set) { + bool connect = false; + if (painted_set.has(coords)) { + connect = true; + } else { + // Get the center bit of the cell. + TileData *tile_data = nullptr; + TileMapCell cell = get_cell(coords); + if (cell.source_id != TileSet::INVALID_SOURCE) { + Ref<TileSetSource> source = tile_set->get_source(cell.source_id); + Ref<TileSetAtlasSource> atlas_source = source; + if (atlas_source.is_valid()) { + tile_data = atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile); + } + } + + if (tile_data && tile_data->get_terrain_set() == p_terrain_set && tile_data->get_terrain() == p_terrain) { + connect = true; + } + } + if (connect) { + cells_with_terrain_center_bit.insert(coords); + } + } + + RBSet<TerrainConstraint> constraints; + + // Add new constraints from the path drawn. + for (Vector2i coords : p_coords_array) { + // Constraints on the center bit. + TerrainConstraint c = TerrainConstraint(tile_map_node, coords, p_terrain); + c.set_priority(10); + constraints.insert(c); + + // Constraints on the connecting bits. + for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); + if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) { + c = TerrainConstraint(tile_map_node, coords, bit, p_terrain); + c.set_priority(10); + if ((int(bit) % 2) == 0) { + // Side peering bits: add the constraint if the center is of the same terrain. + Vector2i neighbor = tile_map_node->get_neighbor_cell(coords, bit); + if (cells_with_terrain_center_bit.has(neighbor)) { + constraints.insert(c); + } + } else { + // Corner peering bits: add the constraint if all tiles on the constraint has the same center bit. + HashMap<Vector2i, TileSet::CellNeighbor> overlapping_terrain_bits = c.get_overlapping_coords_and_peering_bits(); + bool valid = true; + for (KeyValue<Vector2i, TileSet::CellNeighbor> kv : overlapping_terrain_bits) { + if (!cells_with_terrain_center_bit.has(kv.key)) { + valid = false; + break; + } + } + if (valid) { + constraints.insert(c); + } + } + } + } + } + + // Fills in the constraint list from existing tiles. + for (TerrainConstraint c : _get_terrain_constraints_from_painted_cells_list(painted_set, p_terrain_set, p_ignore_empty_terrains)) { + constraints.insert(c); + } + + // Fill the terrains. + output = terrain_fill_constraints(can_modify_list, p_terrain_set, constraints); + return output; +} + +HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_path(const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { + HashMap<Vector2i, TileSet::TerrainsPattern> output; + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + ERR_FAIL_COND_V(!tile_set.is_valid(), output); + ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), output); + + // Make sure the path is correct and build the peering bit list while doing it. + Vector<TileSet::CellNeighbor> neighbor_list; + for (int i = 0; i < p_coords_array.size() - 1; i++) { + // Find the adequate neighbor. + TileSet::CellNeighbor found_bit = TileSet::CELL_NEIGHBOR_MAX; + for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); + if (tile_map_node->is_existing_neighbor(bit)) { + if (tile_map_node->get_neighbor_cell(p_coords_array[i], bit) == p_coords_array[i + 1]) { + found_bit = bit; + break; + } + } + } + ERR_FAIL_COND_V_MSG(found_bit == TileSet::CELL_NEIGHBOR_MAX, output, vformat("Invalid terrain path, %s is not a neighboring tile of %s", p_coords_array[i + 1], p_coords_array[i])); + neighbor_list.push_back(found_bit); + } + + // Build list and set of tiles that can be modified (painted and their surroundings). + Vector<Vector2i> can_modify_list; + RBSet<Vector2i> can_modify_set; + RBSet<Vector2i> painted_set; + for (int i = p_coords_array.size() - 1; i >= 0; i--) { + const Vector2i &coords = p_coords_array[i]; + can_modify_list.push_back(coords); + can_modify_set.insert(coords); + painted_set.insert(coords); + } + for (Vector2i coords : p_coords_array) { + // Find the adequate neighbor. + for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); + if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) { + Vector2i neighbor = tile_map_node->get_neighbor_cell(coords, bit); + if (!can_modify_set.has(neighbor)) { + can_modify_list.push_back(neighbor); + can_modify_set.insert(neighbor); + } + } + } + } + + RBSet<TerrainConstraint> constraints; + + // Add new constraints from the path drawn. + for (Vector2i coords : p_coords_array) { + // Constraints on the center bit. + TerrainConstraint c = TerrainConstraint(tile_map_node, coords, p_terrain); + c.set_priority(10); + constraints.insert(c); + } + for (int i = 0; i < p_coords_array.size() - 1; i++) { + // Constraints on the peering bits. + TerrainConstraint c = TerrainConstraint(tile_map_node, p_coords_array[i], neighbor_list[i], p_terrain); + c.set_priority(10); + constraints.insert(c); + } + + // Fills in the constraint list from existing tiles. + for (TerrainConstraint c : _get_terrain_constraints_from_painted_cells_list(painted_set, p_terrain_set, p_ignore_empty_terrains)) { + constraints.insert(c); + } + + // Fill the terrains. + output = terrain_fill_constraints(can_modify_list, p_terrain_set, constraints); + return output; +} + +HashMap<Vector2i, TileSet::TerrainsPattern> TileMapLayer::terrain_fill_pattern(const Vector<Vector2i> &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains) { + HashMap<Vector2i, TileSet::TerrainsPattern> output; + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + ERR_FAIL_COND_V(!tile_set.is_valid(), output); + ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), output); + + // Build list and set of tiles that can be modified (painted and their surroundings). + Vector<Vector2i> can_modify_list; + RBSet<Vector2i> can_modify_set; + RBSet<Vector2i> painted_set; + for (int i = p_coords_array.size() - 1; i >= 0; i--) { + const Vector2i &coords = p_coords_array[i]; + can_modify_list.push_back(coords); + can_modify_set.insert(coords); + painted_set.insert(coords); + } + for (Vector2i coords : p_coords_array) { + // Find the adequate neighbor. + for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); + if (tile_set->is_valid_terrain_peering_bit(p_terrain_set, bit)) { + Vector2i neighbor = tile_map_node->get_neighbor_cell(coords, bit); + if (!can_modify_set.has(neighbor)) { + can_modify_list.push_back(neighbor); + can_modify_set.insert(neighbor); + } + } + } + } + + // Add constraint by the new ones. + RBSet<TerrainConstraint> constraints; + + // Add new constraints from the path drawn. + for (Vector2i coords : p_coords_array) { + // Constraints on the center bit. + RBSet<TerrainConstraint> added_constraints = _get_terrain_constraints_from_added_pattern(coords, p_terrain_set, p_terrains_pattern); + for (TerrainConstraint c : added_constraints) { + c.set_priority(10); + constraints.insert(c); + } + } + + // Fills in the constraint list from modified tiles border. + for (TerrainConstraint c : _get_terrain_constraints_from_painted_cells_list(painted_set, p_terrain_set, p_ignore_empty_terrains)) { + constraints.insert(c); + } + + // Fill the terrains. + output = terrain_fill_constraints(can_modify_list, p_terrain_set, constraints); + return output; +} + +TileMapCell TileMapLayer::get_cell(const Vector2i &p_coords, bool p_use_proxies) const { + if (!tile_map.has(p_coords)) { + return TileMapCell(); + } else { + TileMapCell c = tile_map.find(p_coords)->value.cell; + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + if (p_use_proxies && tile_set.is_valid()) { + Array proxyed = tile_set->map_tile_proxy(c.source_id, c.get_atlas_coords(), c.alternative_tile); + c.source_id = proxyed[0]; + c.set_atlas_coords(proxyed[1]); + c.alternative_tile = proxyed[2]; + } + return c; + } +} + +void TileMapLayer::set_tile_data(TileMapDataFormat p_format, const Vector<int> &p_data) { + ERR_FAIL_COND(p_format > TileMapDataFormat::FORMAT_3); + + // Set data for a given tile from raw data. + + int c = p_data.size(); + const int *r = p_data.ptr(); + + int offset = (p_format >= TileMapDataFormat::FORMAT_2) ? 3 : 2; + ERR_FAIL_COND_MSG(c % offset != 0, vformat("Corrupted tile data. Got size: %s. Expected modulo: %s", offset)); + + clear(); + +#ifdef DISABLE_DEPRECATED + ERR_FAIL_COND_MSG(p_format != TileMapDataFormat::FORMAT_3, vformat("Cannot handle deprecated TileMap data format version %d. This Godot version was compiled with no support for deprecated data.", p_format)); +#endif + + 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 < ((p_format >= TileMapDataFormat::FORMAT_2) ? 12 : 8); 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]); + //TODO: ask someone to check this... + if (FORMAT >= FORMAT_2) { + SWAP(local[8], local[11]); + SWAP(local[9], local[10]); + } +#endif + // Extracts position in TileMap. + int16_t x = decode_uint16(&local[0]); + int16_t y = decode_uint16(&local[2]); + + if (p_format == TileMapDataFormat::FORMAT_3) { + 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); + } else { +#ifndef DISABLE_DEPRECATED + // Previous decated format. + + uint32_t v = decode_uint32(&local[4]); + // Extract the transform flags that used to be in the tilemap. + bool flip_h = v & (1UL << 29); + bool flip_v = v & (1UL << 30); + bool transpose = v & (1UL << 31); + v &= (1UL << 29) - 1; + + // Extract autotile/atlas coords. + int16_t coord_x = 0; + int16_t coord_y = 0; + if (p_format == TileMapDataFormat::FORMAT_2) { + coord_x = decode_uint16(&local[8]); + coord_y = decode_uint16(&local[10]); + } + + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + if (tile_set.is_valid()) { + Array a = tile_set->compatibility_tilemap_map(v, Vector2i(coord_x, coord_y), flip_h, flip_v, transpose); + if (a.size() == 3) { + set_cell(Vector2i(x, y), a[0], a[1], a[2]); + } else { + ERR_PRINT(vformat("No valid tile in Tileset for: tile:%s coords:%s flip_h:%s flip_v:%s transpose:%s", v, Vector2i(coord_x, coord_y), flip_h, flip_v, transpose)); + } + } else { + int compatibility_alternative_tile = ((int)flip_h) + ((int)flip_v << 1) + ((int)transpose << 2); + set_cell(Vector2i(x, y), v, Vector2i(coord_x, coord_y), compatibility_alternative_tile); + } +#endif + } + } +} + +Vector<int> TileMapLayer::get_tile_data() const { + // Export tile data to raw format. + Vector<int> tile_data; + tile_data.resize(tile_map.size() * 3); + int *w = tile_data.ptrw(); + + // Save in highest format. + + int idx = 0; + for (const KeyValue<Vector2i, CellData> &E : tile_map) { + 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.cell.source_id, &ptr[4]); + encode_uint16(E.value.cell.coord_x, &ptr[6]); + encode_uint16(E.value.cell.coord_y, &ptr[8]); + encode_uint16(E.value.cell.alternative_tile, &ptr[10]); + idx += 3; + } + + return tile_data; +} + +void TileMapLayer::notify_tile_map_change(DirtyFlags p_what) { + dirty.flags[p_what] = true; + tile_map_node->queue_internal_update(); + _physics_notify_tilemap_change(p_what); +} + +void TileMapLayer::internal_update() { + // Find TileData that need a runtime modification. + // This may add cells to the dirty list is a runtime modification has been notified. + _build_runtime_update_tile_data(); + + // Update all subsystems. + _rendering_update(); + _physics_update(); + _navigation_update(); + _scenes_update(); +#ifdef DEBUG_ENABLED + _debug_update(); +#endif // DEBUG_ENABLED + + _clear_runtime_update_tile_data(); + + // Clear the "what is dirty" flags. + for (int i = 0; i < DIRTY_FLAGS_MAX; i++) { + dirty.flags[i] = false; + } + + // List the cells to delete definitely. + Vector<Vector2i> to_delete; + for (SelfList<CellData> *cell_data_list_element = dirty.cell_list.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { + CellData &cell_data = *cell_data_list_element->self(); + // Select the the cell from tile_map if it is invalid. + if (cell_data.cell.source_id == TileSet::INVALID_SOURCE) { + to_delete.push_back(cell_data.coords); + } + } + + // Remove cells that are empty after the cleanup. + for (const Vector2i &coords : to_delete) { + tile_map.erase(coords); + } + + // Clear the dirty cells list. + dirty.cell_list.clear(); +} + +void TileMapLayer::set_cell(const Vector2i &p_coords, int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) { + // Set the current cell tile (using integer position). + Vector2i pk(p_coords); + HashMap<Vector2i, CellData>::Iterator E = tile_map.find(pk); + + int source_id = p_source_id; + Vector2i atlas_coords = p_atlas_coords; + int alternative_tile = p_alternative_tile; + + if ((source_id == TileSet::INVALID_SOURCE || atlas_coords == TileSetSource::INVALID_ATLAS_COORDS || alternative_tile == TileSetSource::INVALID_TILE_ALTERNATIVE) && + (source_id != TileSet::INVALID_SOURCE || atlas_coords != TileSetSource::INVALID_ATLAS_COORDS || alternative_tile != TileSetSource::INVALID_TILE_ALTERNATIVE)) { + source_id = TileSet::INVALID_SOURCE; + atlas_coords = TileSetSource::INVALID_ATLAS_COORDS; + alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; + } + + if (!E) { + if (source_id == TileSet::INVALID_SOURCE) { + return; // Nothing to do, the tile is already empty. + } + + // Insert a new cell in the tile map. + CellData new_cell_data; + new_cell_data.coords = pk; + E = tile_map.insert(pk, new_cell_data); + } else { + if (E->value.cell.source_id == source_id && E->value.cell.get_atlas_coords() == atlas_coords && E->value.cell.alternative_tile == alternative_tile) { + return; // Nothing changed. + } + } + + TileMapCell &c = E->value.cell; + c.source_id = source_id; + c.set_atlas_coords(atlas_coords); + c.alternative_tile = alternative_tile; + + // Make the given cell dirty. + if (!E->value.dirty_list_element.in_list()) { + dirty.cell_list.add(&(E->value.dirty_list_element)); + } + tile_map_node->queue_internal_update(); + + used_rect_cache_dirty = true; +} + +void TileMapLayer::erase_cell(const Vector2i &p_coords) { + set_cell(p_coords, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); +} + +int TileMapLayer::get_cell_source_id(const Vector2i &p_coords, bool p_use_proxies) const { + // Get a cell source id from position. + HashMap<Vector2i, CellData>::ConstIterator E = tile_map.find(p_coords); + + if (!E) { + return TileSet::INVALID_SOURCE; + } + + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + if (p_use_proxies && tile_set.is_valid()) { + Array proxyed = tile_set->map_tile_proxy(E->value.cell.source_id, E->value.cell.get_atlas_coords(), E->value.cell.alternative_tile); + return proxyed[0]; + } + + return E->value.cell.source_id; +} + +Vector2i TileMapLayer::get_cell_atlas_coords(const Vector2i &p_coords, bool p_use_proxies) const { + // Get a cell source id from position. + HashMap<Vector2i, CellData>::ConstIterator E = tile_map.find(p_coords); + + if (!E) { + return TileSetSource::INVALID_ATLAS_COORDS; + } + + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + if (p_use_proxies && tile_set.is_valid()) { + Array proxyed = tile_set->map_tile_proxy(E->value.cell.source_id, E->value.cell.get_atlas_coords(), E->value.cell.alternative_tile); + return proxyed[1]; + } + + return E->value.cell.get_atlas_coords(); +} + +int TileMapLayer::get_cell_alternative_tile(const Vector2i &p_coords, bool p_use_proxies) const { + // Get a cell source id from position. + HashMap<Vector2i, CellData>::ConstIterator E = tile_map.find(p_coords); + + if (!E) { + return TileSetSource::INVALID_TILE_ALTERNATIVE; + } + + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + if (p_use_proxies && tile_set.is_valid()) { + Array proxyed = tile_set->map_tile_proxy(E->value.cell.source_id, E->value.cell.get_atlas_coords(), E->value.cell.alternative_tile); + return proxyed[2]; + } + + return E->value.cell.alternative_tile; +} + +TileData *TileMapLayer::get_cell_tile_data(const Vector2i &p_coords, bool p_use_proxies) const { + int source_id = get_cell_source_id(p_coords, p_use_proxies); + if (source_id == TileSet::INVALID_SOURCE) { + return nullptr; + } + + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + Ref<TileSetAtlasSource> source = tile_set->get_source(source_id); + if (source.is_valid()) { + return source->get_tile_data(get_cell_atlas_coords(p_coords, p_use_proxies), get_cell_alternative_tile(p_coords, p_use_proxies)); + } + + return nullptr; +} + +void TileMapLayer::clear() { + // Remove all tiles. + for (KeyValue<Vector2i, CellData> &kv : tile_map) { + erase_cell(kv.key); + } + used_rect_cache_dirty = true; +} + +Ref<TileMapPattern> TileMapLayer::get_pattern(TypedArray<Vector2i> p_coords_array) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + ERR_FAIL_COND_V(!tile_set.is_valid(), nullptr); + + Ref<TileMapPattern> output; + output.instantiate(); + if (p_coords_array.is_empty()) { + return output; + } + + Vector2i min = Vector2i(p_coords_array[0]); + for (int i = 1; i < p_coords_array.size(); i++) { + min = min.min(p_coords_array[i]); + } + + Vector<Vector2i> coords_in_pattern_array; + coords_in_pattern_array.resize(p_coords_array.size()); + Vector2i ensure_positive_offset; + for (int i = 0; i < p_coords_array.size(); i++) { + Vector2i coords = p_coords_array[i]; + Vector2i coords_in_pattern = coords - min; + if (tile_set->get_tile_shape() != TileSet::TILE_SHAPE_SQUARE) { + if (tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_STACKED) { + if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL && bool(min.y % 2) && bool(coords_in_pattern.y % 2)) { + coords_in_pattern.x -= 1; + if (coords_in_pattern.x < 0) { + ensure_positive_offset.x = 1; + } + } else if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(min.x % 2) && bool(coords_in_pattern.x % 2)) { + coords_in_pattern.y -= 1; + if (coords_in_pattern.y < 0) { + ensure_positive_offset.y = 1; + } + } + } else if (tile_set->get_tile_layout() == TileSet::TILE_LAYOUT_STACKED_OFFSET) { + if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL && bool(min.y % 2) && bool(coords_in_pattern.y % 2)) { + coords_in_pattern.x += 1; + } else if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL && bool(min.x % 2) && bool(coords_in_pattern.x % 2)) { + coords_in_pattern.y += 1; + } + } + } + coords_in_pattern_array.write[i] = coords_in_pattern; + } + + for (int i = 0; i < coords_in_pattern_array.size(); i++) { + Vector2i coords = p_coords_array[i]; + Vector2i coords_in_pattern = coords_in_pattern_array[i]; + output->set_cell(coords_in_pattern + ensure_positive_offset, get_cell_source_id(coords), get_cell_atlas_coords(coords), get_cell_alternative_tile(coords)); + } + + return output; +} + +void TileMapLayer::set_pattern(const Vector2i &p_position, const Ref<TileMapPattern> p_pattern) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + ERR_FAIL_COND(tile_set.is_null()); + ERR_FAIL_COND(p_pattern.is_null()); + + TypedArray<Vector2i> used_cells = p_pattern->get_used_cells(); + for (int i = 0; i < used_cells.size(); i++) { + Vector2i coords = tile_map_node->map_pattern(p_position, used_cells[i], p_pattern); + set_cell(coords, p_pattern->get_cell_source_id(used_cells[i]), p_pattern->get_cell_atlas_coords(used_cells[i]), p_pattern->get_cell_alternative_tile(used_cells[i])); + } +} + +void TileMapLayer::set_cells_terrain_connect(TypedArray<Vector2i> p_cells, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + ERR_FAIL_COND(!tile_set.is_valid()); + ERR_FAIL_INDEX(p_terrain_set, tile_set->get_terrain_sets_count()); + + Vector<Vector2i> cells_vector; + HashSet<Vector2i> painted_set; + for (int i = 0; i < p_cells.size(); i++) { + cells_vector.push_back(p_cells[i]); + painted_set.insert(p_cells[i]); + } + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_output = terrain_fill_connect(cells_vector, p_terrain_set, p_terrain, p_ignore_empty_terrains); + for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &kv : terrain_fill_output) { + if (painted_set.has(kv.key)) { + // Paint a random tile with the correct terrain for the painted path. + TileMapCell c = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, kv.value); + set_cell(kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); + } else { + // Avoids updating the painted path from the output if the new pattern is the same as before. + TileSet::TerrainsPattern in_map_terrain_pattern = TileSet::TerrainsPattern(*tile_set, p_terrain_set); + TileMapCell cell = get_cell(kv.key); + if (cell.source_id != TileSet::INVALID_SOURCE) { + TileSetSource *source = *tile_set->get_source(cell.source_id); + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + // Get tile data. + TileData *tile_data = atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile); + if (tile_data && tile_data->get_terrain_set() == p_terrain_set) { + in_map_terrain_pattern = tile_data->get_terrains_pattern(); + } + } + } + if (in_map_terrain_pattern != kv.value) { + TileMapCell c = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, kv.value); + set_cell(kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); + } + } + } +} + +void TileMapLayer::set_cells_terrain_path(TypedArray<Vector2i> p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains) { + const Ref<TileSet> &tile_set = tile_map_node->get_tileset(); + ERR_FAIL_COND(!tile_set.is_valid()); + ERR_FAIL_INDEX(p_terrain_set, tile_set->get_terrain_sets_count()); + + Vector<Vector2i> vector_path; + HashSet<Vector2i> painted_set; + for (int i = 0; i < p_path.size(); i++) { + vector_path.push_back(p_path[i]); + painted_set.insert(p_path[i]); + } + + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_output = terrain_fill_path(vector_path, p_terrain_set, p_terrain, p_ignore_empty_terrains); + for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &kv : terrain_fill_output) { + if (painted_set.has(kv.key)) { + // Paint a random tile with the correct terrain for the painted path. + TileMapCell c = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, kv.value); + set_cell(kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); + } else { + // Avoids updating the painted path from the output if the new pattern is the same as before. + TileSet::TerrainsPattern in_map_terrain_pattern = TileSet::TerrainsPattern(*tile_set, p_terrain_set); + TileMapCell cell = get_cell(kv.key); + if (cell.source_id != TileSet::INVALID_SOURCE) { + TileSetSource *source = *tile_set->get_source(cell.source_id); + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + // Get tile data. + TileData *tile_data = atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile); + if (tile_data && tile_data->get_terrain_set() == p_terrain_set) { + in_map_terrain_pattern = tile_data->get_terrains_pattern(); + } + } + } + if (in_map_terrain_pattern != kv.value) { + TileMapCell c = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, kv.value); + set_cell(kv.key, c.source_id, c.get_atlas_coords(), c.alternative_tile); + } + } + } +} + +TypedArray<Vector2i> TileMapLayer::get_used_cells() const { + // Returns the cells used in the tilemap. + TypedArray<Vector2i> a; + for (const KeyValue<Vector2i, CellData> &E : tile_map) { + const TileMapCell &c = E.value.cell; + if (c.source_id == TileSet::INVALID_SOURCE) { + continue; + } + a.push_back(E.key); + } + + return a; +} + +TypedArray<Vector2i> TileMapLayer::get_used_cells_by_id(int p_source_id, const Vector2i p_atlas_coords, int p_alternative_tile) const { + // Returns the cells used in the tilemap. + TypedArray<Vector2i> a; + for (const KeyValue<Vector2i, CellData> &E : tile_map) { + const TileMapCell &c = E.value.cell; + if (c.source_id == TileSet::INVALID_SOURCE) { + continue; + } + if ((p_source_id == TileSet::INVALID_SOURCE || p_source_id == c.source_id) && + (p_atlas_coords == TileSetSource::INVALID_ATLAS_COORDS || p_atlas_coords == c.get_atlas_coords()) && + (p_alternative_tile == TileSetSource::INVALID_TILE_ALTERNATIVE || p_alternative_tile == c.alternative_tile)) { + a.push_back(E.key); + } + } + + return a; +} + +Rect2i TileMapLayer::get_used_rect() const { + // Return the rect of the currently used area. + if (used_rect_cache_dirty) { + used_rect_cache = Rect2i(); + + bool first = true; + for (const KeyValue<Vector2i, CellData> &E : tile_map) { + const TileMapCell &c = E.value.cell; + if (c.source_id == TileSet::INVALID_SOURCE) { + continue; + } + if (first) { + used_rect_cache = Rect2i(E.key.x, E.key.y, 0, 0); + first = false; + } else { + used_rect_cache.expand_to(E.key); + } + } + if (!first) { + // Only if we have at least one cell. + // The cache expands to top-left coordinate, so we add one full tile. + used_rect_cache.size += Vector2i(1, 1); + } + used_rect_cache_dirty = false; + } + + return used_rect_cache; +} + +void TileMapLayer::set_name(String p_name) { + if (name == p_name) { + return; + } + name = p_name; + tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); +} + +String TileMapLayer::get_name() const { + return name; +} + +void TileMapLayer::set_enabled(bool p_enabled) { + if (enabled == p_enabled) { + return; + } + enabled = p_enabled; + dirty.flags[DIRTY_FLAGS_LAYER_ENABLED] = true; + tile_map_node->queue_internal_update(); + tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); + + tile_map_node->update_configuration_warnings(); +} + +bool TileMapLayer::is_enabled() const { + return enabled; +} + +void TileMapLayer::set_modulate(Color p_modulate) { + if (modulate == p_modulate) { + return; + } + modulate = p_modulate; + dirty.flags[DIRTY_FLAGS_LAYER_MODULATE] = true; + tile_map_node->queue_internal_update(); + tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); +} + +Color TileMapLayer::get_modulate() const { + return modulate; +} + +void TileMapLayer::set_y_sort_enabled(bool p_y_sort_enabled) { + if (y_sort_enabled == p_y_sort_enabled) { + return; + } + y_sort_enabled = p_y_sort_enabled; + dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ENABLED] = true; + tile_map_node->queue_internal_update(); + tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); + + tile_map_node->update_configuration_warnings(); +} + +bool TileMapLayer::is_y_sort_enabled() const { + return y_sort_enabled; +} + +void TileMapLayer::set_y_sort_origin(int p_y_sort_origin) { + if (y_sort_origin == p_y_sort_origin) { + return; + } + y_sort_origin = p_y_sort_origin; + dirty.flags[DIRTY_FLAGS_LAYER_Y_SORT_ORIGIN] = true; + tile_map_node->queue_internal_update(); + tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); +} + +int TileMapLayer::get_y_sort_origin() const { + return y_sort_origin; +} + +void TileMapLayer::set_z_index(int p_z_index) { + if (z_index == p_z_index) { + return; + } + z_index = p_z_index; + dirty.flags[DIRTY_FLAGS_LAYER_Z_INDEX] = true; + tile_map_node->queue_internal_update(); + tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); + + tile_map_node->update_configuration_warnings(); +} + +int TileMapLayer::get_z_index() const { + return z_index; +} + +void TileMapLayer::set_navigation_enabled(bool p_enabled) { + if (navigation_enabled == p_enabled) { + return; + } + navigation_enabled = p_enabled; + dirty.flags[DIRTY_FLAGS_LAYER_NAVIGATION_ENABLED] = true; + tile_map_node->queue_internal_update(); + tile_map_node->emit_signal(CoreStringNames::get_singleton()->changed); +} + +bool TileMapLayer::is_navigation_enabled() const { + return navigation_enabled; +} + +void TileMapLayer::set_navigation_map(RID p_map) { + ERR_FAIL_COND_MSG(!tile_map_node->is_inside_tree(), "A TileMap navigation map can only be changed while inside the SceneTree."); + navigation_map = p_map; + uses_world_navigation_map = p_map == tile_map_node->get_world_2d()->get_navigation_map(); +} + +RID TileMapLayer::get_navigation_map() const { + if (navigation_map.is_valid()) { + return navigation_map; + } + return RID(); +} + +void TileMapLayer::fix_invalid_tiles() { + Ref<TileSet> tileset = tile_map_node->get_tileset(); + ERR_FAIL_COND_MSG(tileset.is_null(), "Cannot call fix_invalid_tiles() on a TileMap without a valid TileSet."); + + RBSet<Vector2i> coords; + for (const KeyValue<Vector2i, CellData> &E : tile_map) { + TileSetSource *source = *tileset->get_source(E.value.cell.source_id); + if (!source || !source->has_tile(E.value.cell.get_atlas_coords()) || !source->has_alternative_tile(E.value.cell.get_atlas_coords(), E.value.cell.alternative_tile)) { + coords.insert(E.key); + } + } + for (const Vector2i &E : coords) { + set_cell(E, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); + } +} + +bool TileMapLayer::has_body_rid(RID p_physics_body) const { + return bodies_coords.has(p_physics_body); +} + +Vector2i TileMapLayer::get_coords_for_body_rid(RID p_physics_body) const { + return bodies_coords[p_physics_body]; +} + +TileMapLayer::~TileMapLayer() { + if (!tile_map_node) { + // Temporary layer. + return; + } + + in_destructor = true; + clear(); + internal_update(); +} + +HashMap<Vector2i, TileSet::CellNeighbor> TerrainConstraint::get_overlapping_coords_and_peering_bits() const { + HashMap<Vector2i, TileSet::CellNeighbor> output; + + ERR_FAIL_COND_V(is_center_bit(), output); + + Ref<TileSet> ts = tile_map->get_tileset(); + ERR_FAIL_COND_V(!ts.is_valid(), output); + + TileSet::TileShape shape = ts->get_tile_shape(); + if (shape == TileSet::TILE_SHAPE_SQUARE) { + switch (bit) { + case 1: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE; + break; + case 2: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER; + break; + case 3: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE; + break; + default: + ERR_FAIL_V(output); + } + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) { + switch (bit) { + case 1: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; + break; + case 2: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER; + break; + case 3: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; + break; + default: + ERR_FAIL_V(output); + } + } else { + // Half offset shapes. + TileSet::TileOffsetAxis offset_axis = ts->get_tile_offset_axis(); + if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + switch (bit) { + case 1: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE; + break; + case 2: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_CORNER; + break; + case 3: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; + break; + case 4: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER; + break; + case 5: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; + break; + default: + ERR_FAIL_V(output); + } + } else { + switch (bit) { + case 1: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; + break; + case 2: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; + break; + case 3: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; + break; + case 4: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE; + break; + case 5: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; + break; + default: + ERR_FAIL_V(output); + } + } + } + return output; +} + +TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, int p_terrain) { + tile_map = p_tile_map; + + Ref<TileSet> ts = tile_map->get_tileset(); + ERR_FAIL_COND(!ts.is_valid()); + + bit = 0; + base_cell_coords = p_position; + terrain = p_terrain; +} + +TerrainConstraint::TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain) { + // The way we build the constraint make it easy to detect conflicting constraints. + tile_map = p_tile_map; + + Ref<TileSet> ts = tile_map->get_tileset(); + ERR_FAIL_COND(!ts.is_valid()); + + TileSet::TileShape shape = ts->get_tile_shape(); + if (shape == TileSet::TILE_SHAPE_SQUARE) { + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: + bit = 1; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + bit = 2; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: + bit = 3; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_LEFT_SIDE: + bit = 1; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER); + break; + case TileSet::CELL_NEIGHBOR_TOP_SIDE: + bit = 3; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); + break; + default: + ERR_FAIL(); + break; + } + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) { + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + bit = 1; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: + bit = 2; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + bit = 3; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_LEFT_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: + bit = 1; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_CORNER); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: + bit = 3; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); + break; + default: + ERR_FAIL(); + break; + } + } else { + // Half-offset shapes. + TileSet::TileOffsetAxis offset_axis = ts->get_tile_offset_axis(); + if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: + bit = 1; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + bit = 2; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + bit = 3; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: + bit = 4; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + bit = 5; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_LEFT_SIDE: + bit = 1; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + bit = 4; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: + bit = 3; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: + bit = 5; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + bit = 4; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); + break; + default: + ERR_FAIL(); + break; + } + } else { + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: + bit = 1; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + bit = 2; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + bit = 3; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: + bit = 4; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + bit = 1; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + bit = 5; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_LEFT_CORNER: + bit = 3; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + bit = 1; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_SIDE: + bit = 4; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + bit = 3; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: + bit = 5; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); + break; + default: + ERR_FAIL(); + break; + } + } + } + terrain = p_terrain; +}
\ No newline at end of file diff --git a/scene/2d/tile_map_layer.h b/scene/2d/tile_map_layer.h new file mode 100644 index 0000000000..3125fcb488 --- /dev/null +++ b/scene/2d/tile_map_layer.h @@ -0,0 +1,413 @@ +/**************************************************************************/ +/* tile_map_layer.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_MAP_LAYER_H +#define TILE_MAP_LAYER_H + +#include "scene/2d/tile_map.h" +#include "scene/resources/tile_set.h" + +class TileSetAtlasSource; + +class TerrainConstraint { +private: + const TileMap *tile_map = nullptr; + Vector2i base_cell_coords; + int bit = -1; + int terrain = -1; + + int priority = 1; + +public: + bool operator<(const TerrainConstraint &p_other) const { + if (base_cell_coords == p_other.base_cell_coords) { + return bit < p_other.bit; + } + return base_cell_coords < p_other.base_cell_coords; + } + + String to_string() const { + return vformat("Constraint {pos:%s, bit:%d, terrain:%d, priority:%d}", base_cell_coords, bit, terrain, priority); + } + + Vector2i get_base_cell_coords() const { + return base_cell_coords; + } + + bool is_center_bit() const { + return bit == 0; + } + + HashMap<Vector2i, TileSet::CellNeighbor> get_overlapping_coords_and_peering_bits() const; + + void set_terrain(int p_terrain) { + terrain = p_terrain; + } + + int get_terrain() const { + return terrain; + } + + void set_priority(int p_priority) { + priority = p_priority; + } + + int get_priority() const { + return priority; + } + + TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, int p_terrain); // For the center terrain bit + TerrainConstraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain); // For peering bits + TerrainConstraint(){}; +}; + +#ifdef DEBUG_ENABLED +class DebugQuadrant; +#endif // DEBUG_ENABLED +class RenderingQuadrant; + +struct CellData { + Vector2i coords; + TileMapCell cell; + + // Debug. + SelfList<CellData> debug_quadrant_list_element; + + // Rendering. + Ref<RenderingQuadrant> rendering_quadrant; + SelfList<CellData> rendering_quadrant_list_element; + LocalVector<RID> occluders; + + // Physics. + LocalVector<RID> bodies; + + // Navigation. + LocalVector<RID> navigation_regions; + + // Scenes. + String scene; + + // Runtime TileData cache. + TileData *runtime_tile_data_cache = nullptr; + + // List elements. + SelfList<CellData> dirty_list_element; + + bool operator<(const CellData &p_other) const { + return coords < p_other.coords; + } + + // For those, copy everything but SelfList elements. + void operator=(const CellData &p_other) { + coords = p_other.coords; + cell = p_other.cell; + occluders = p_other.occluders; + bodies = p_other.bodies; + navigation_regions = p_other.navigation_regions; + scene = p_other.scene; + runtime_tile_data_cache = p_other.runtime_tile_data_cache; + } + + CellData(const CellData &p_other) : + debug_quadrant_list_element(this), + rendering_quadrant_list_element(this), + dirty_list_element(this) { + coords = p_other.coords; + cell = p_other.cell; + occluders = p_other.occluders; + bodies = p_other.bodies; + navigation_regions = p_other.navigation_regions; + scene = p_other.scene; + runtime_tile_data_cache = p_other.runtime_tile_data_cache; + } + + CellData() : + debug_quadrant_list_element(this), + rendering_quadrant_list_element(this), + dirty_list_element(this) { + } +}; + +// For compatibility reasons, we use another comparator for Y-sorted layers. +struct CellDataYSortedComparator { + _FORCE_INLINE_ bool operator()(const CellData &p_a, const CellData &p_b) const { + return p_a.coords.x == p_b.coords.x ? (p_a.coords.y < p_b.coords.y) : (p_a.coords.x > p_b.coords.x); + } +}; + +#ifdef DEBUG_ENABLED +class DebugQuadrant : public RefCounted { + GDCLASS(DebugQuadrant, RefCounted); + +public: + Vector2i quadrant_coords; + SelfList<CellData>::List cells; + RID canvas_item; + + SelfList<DebugQuadrant> dirty_quadrant_list_element; + + DebugQuadrant() : + dirty_quadrant_list_element(this) { + } + + ~DebugQuadrant() { + cells.clear(); + } +}; +#endif // DEBUG_ENABLED + +class RenderingQuadrant : public RefCounted { + GDCLASS(RenderingQuadrant, RefCounted); + +public: + struct CoordsWorldComparator { + _ALWAYS_INLINE_ bool operator()(const Vector2 &p_a, const Vector2 &p_b) const { + // We sort the cells by their local coords, as it is needed by rendering. + if (p_a.y == p_b.y) { + return p_a.x > p_b.x; + } else { + return p_a.y < p_b.y; + } + } + }; + + Vector2i quadrant_coords; + SelfList<CellData>::List cells; + List<RID> canvas_items; + Vector2 canvas_items_position; + + SelfList<RenderingQuadrant> dirty_quadrant_list_element; + + RenderingQuadrant() : + dirty_quadrant_list_element(this) { + } + + ~RenderingQuadrant() { + cells.clear(); + } +}; + +class TileMapLayer : public RefCounted { + GDCLASS(TileMapLayer, RefCounted); + +public: + enum DirtyFlags { + DIRTY_FLAGS_LAYER_ENABLED = 0, + DIRTY_FLAGS_LAYER_MODULATE, + DIRTY_FLAGS_LAYER_Y_SORT_ENABLED, + DIRTY_FLAGS_LAYER_Y_SORT_ORIGIN, + DIRTY_FLAGS_LAYER_Z_INDEX, + DIRTY_FLAGS_LAYER_NAVIGATION_ENABLED, + DIRTY_FLAGS_LAYER_INDEX_IN_TILE_MAP_NODE, + DIRTY_FLAGS_TILE_MAP_IN_TREE, + DIRTY_FLAGS_TILE_MAP_IN_CANVAS, + DIRTY_FLAGS_TILE_MAP_VISIBILITY, + DIRTY_FLAGS_TILE_MAP_XFORM, + DIRTY_FLAGS_TILE_MAP_LOCAL_XFORM, + DIRTY_FLAGS_TILE_MAP_SELECTED_LAYER, + DIRTY_FLAGS_TILE_MAP_LIGHT_MASK, + DIRTY_FLAGS_TILE_MAP_MATERIAL, + DIRTY_FLAGS_TILE_MAP_USE_PARENT_MATERIAL, + DIRTY_FLAGS_TILE_MAP_TEXTURE_FILTER, + DIRTY_FLAGS_TILE_MAP_TEXTURE_REPEAT, + DIRTY_FLAGS_TILE_MAP_TILE_SET, + DIRTY_FLAGS_TILE_MAP_QUADRANT_SIZE, + DIRTY_FLAGS_TILE_MAP_COLLISION_ANIMATABLE, + DIRTY_FLAGS_TILE_MAP_COLLISION_VISIBILITY_MODE, + DIRTY_FLAGS_TILE_MAP_NAVIGATION_VISIBILITY_MODE, + DIRTY_FLAGS_TILE_MAP_Y_SORT_ENABLED, + DIRTY_FLAGS_TILE_MAP_RUNTIME_UPDATE, + DIRTY_FLAGS_MAX, + }; + +private: + // Exposed properties. + String name; + bool enabled = true; + Color modulate = Color(1, 1, 1, 1); + bool y_sort_enabled = false; + int y_sort_origin = 0; + int z_index = 0; + bool navigation_enabled = true; + RID navigation_map; + bool uses_world_navigation_map = false; + + // Internal. + TileMap *tile_map_node = nullptr; + int layer_index_in_tile_map_node = -1; + RID canvas_item; + HashMap<Vector2i, CellData> tile_map; + + // Dirty flag. Allows knowing what was modified since the last update. + struct { + bool flags[DIRTY_FLAGS_MAX] = { false }; + SelfList<CellData>::List cell_list; + } dirty; + bool in_destructor = false; + + // Rect cache. + mutable Rect2 rect_cache; + mutable bool rect_cache_dirty = true; + mutable Rect2i used_rect_cache; + mutable bool used_rect_cache_dirty = true; + + // Runtime tile data. + bool _runtime_update_tile_data_was_cleaned_up = false; + void _build_runtime_update_tile_data(); + void _build_runtime_update_tile_data_for_cell(CellData &r_cell_data, bool p_auto_add_to_dirty_list = false); + void _clear_runtime_update_tile_data(); + + // Per-system methods. +#ifdef DEBUG_ENABLED + HashMap<Vector2i, Ref<DebugQuadrant>> debug_quadrant_map; + Vector2i _coords_to_debug_quadrant_coords(const Vector2i &p_coords) const; + bool _debug_was_cleaned_up = false; + void _debug_update(); + void _debug_quadrants_update_cell(CellData &r_cell_data, SelfList<DebugQuadrant>::List &r_dirty_debug_quadrant_list); +#endif // DEBUG_ENABLED + + HashMap<Vector2i, Ref<RenderingQuadrant>> rendering_quadrant_map; + bool _rendering_was_cleaned_up = false; + void _rendering_update(); + void _rendering_quadrants_update_cell(CellData &r_cell_data, SelfList<RenderingQuadrant>::List &r_dirty_rendering_quadrant_list); + void _rendering_occluders_clear_cell(CellData &r_cell_data); + void _rendering_occluders_update_cell(CellData &r_cell_data); +#ifdef DEBUG_ENABLED + void _rendering_draw_cell_debug(const RID &p_canvas_item, const Vector2i &p_quadrant_pos, const CellData &r_cell_data); +#endif // DEBUG_ENABLED + + HashMap<RID, Vector2i> bodies_coords; // Mapping for RID to coords. + bool _physics_was_cleaned_up = false; + void _physics_update(); + void _physics_notify_tilemap_change(DirtyFlags p_what); + void _physics_clear_cell(CellData &r_cell_data); + void _physics_update_cell(CellData &r_cell_data); +#ifdef DEBUG_ENABLED + void _physics_draw_cell_debug(const RID &p_canvas_item, const Vector2i &p_quadrant_pos, const CellData &r_cell_data); +#endif // DEBUG_ENABLED + + bool _navigation_was_cleaned_up = false; + void _navigation_update(); + void _navigation_clear_cell(CellData &r_cell_data); + void _navigation_update_cell(CellData &r_cell_data); +#ifdef DEBUG_ENABLED + void _navigation_draw_cell_debug(const RID &p_canvas_item, const Vector2i &p_quadrant_pos, const CellData &r_cell_data); +#endif // DEBUG_ENABLED + + bool _scenes_was_cleaned_up = false; + void _scenes_update(); + void _scenes_clear_cell(CellData &r_cell_data); + void _scenes_update_cell(CellData &r_cell_data); +#ifdef DEBUG_ENABLED + void _scenes_draw_cell_debug(const RID &p_canvas_item, const Vector2i &p_quadrant_pos, const CellData &r_cell_data); +#endif // DEBUG_ENABLED + + // Terrains. + TileSet::TerrainsPattern _get_best_terrain_pattern_for_constraints(int p_terrain_set, const Vector2i &p_position, const RBSet<TerrainConstraint> &p_constraints, TileSet::TerrainsPattern p_current_pattern); + RBSet<TerrainConstraint> _get_terrain_constraints_from_added_pattern(const Vector2i &p_position, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern) const; + RBSet<TerrainConstraint> _get_terrain_constraints_from_painted_cells_list(const RBSet<Vector2i> &p_painted, int p_terrain_set, bool p_ignore_empty_terrains) const; + +public: + // TileMap node. + void set_tile_map(TileMap *p_tile_map); + void set_layer_index_in_tile_map_node(int p_index); + + // Rect caching. + Rect2 get_rect(bool &r_changed) const; + + // Terrains. + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_constraints(const Vector<Vector2i> &p_to_replace, int p_terrain_set, const RBSet<TerrainConstraint> &p_constraints); // Not exposed. + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_connect(const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); // Not exposed. + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_path(const Vector<Vector2i> &p_coords_array, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); // Not exposed. + HashMap<Vector2i, TileSet::TerrainsPattern> terrain_fill_pattern(const Vector<Vector2i> &p_coords_array, int p_terrain_set, TileSet::TerrainsPattern p_terrains_pattern, bool p_ignore_empty_terrains = true); // Not exposed. + + // Not exposed to users. + TileMapCell get_cell(const Vector2i &p_coords, bool p_use_proxies = false) const; + + // For TileMap node's use. + void set_tile_data(TileMapDataFormat p_format, const Vector<int> &p_data); + Vector<int> get_tile_data() const; + void notify_tile_map_change(DirtyFlags p_what); + void internal_update(); + + // --- Exposed in TileMap --- + + // Cells manipulation. + void set_cell(const Vector2i &p_coords, int p_source_id = TileSet::INVALID_SOURCE, const Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = 0); + void erase_cell(const Vector2i &p_coords); + + int get_cell_source_id(const Vector2i &p_coords, bool p_use_proxies = false) const; + Vector2i get_cell_atlas_coords(const Vector2i &p_coords, bool p_use_proxies = false) const; + int get_cell_alternative_tile(const Vector2i &p_coords, bool p_use_proxies = false) const; + TileData *get_cell_tile_data(const Vector2i &p_coords, bool p_use_proxies = false) const; // Helper method to make accessing the data easier. + void clear(); + + // Patterns. + Ref<TileMapPattern> get_pattern(TypedArray<Vector2i> p_coords_array); + void set_pattern(const Vector2i &p_position, const Ref<TileMapPattern> p_pattern); + + // Terrains. + void set_cells_terrain_connect(TypedArray<Vector2i> p_cells, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); + void set_cells_terrain_path(TypedArray<Vector2i> p_path, int p_terrain_set, int p_terrain, bool p_ignore_empty_terrains = true); + + // Cells usage. + TypedArray<Vector2i> get_used_cells() const; + TypedArray<Vector2i> get_used_cells_by_id(int p_source_id = TileSet::INVALID_SOURCE, const Vector2i p_atlas_coords = TileSetSource::INVALID_ATLAS_COORDS, int p_alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE) const; + Rect2i get_used_rect() const; + + // Layer properties. + void set_name(String p_name); + String get_name() const; + void set_enabled(bool p_enabled); + bool is_enabled() const; + void set_modulate(Color p_modulate); + Color get_modulate() const; + void set_y_sort_enabled(bool p_y_sort_enabled); + bool is_y_sort_enabled() const; + void set_y_sort_origin(int p_y_sort_origin); + int get_y_sort_origin() const; + void set_z_index(int p_z_index); + int get_z_index() const; + void set_navigation_enabled(bool p_enabled); + bool is_navigation_enabled() const; + void set_navigation_map(RID p_map); + RID get_navigation_map() const; + + // Fixing and clearing methods. + void fix_invalid_tiles(); + + // Find coords for body. + bool has_body_rid(RID p_physics_body) const; + Vector2i get_coords_for_body_rid(RID p_physics_body) const; // For finding tiles from collision. + + ~TileMapLayer(); +}; + +#endif // TILE_MAP_LAYER_H diff --git a/scene/3d/collision_object_3d.cpp b/scene/3d/collision_object_3d.cpp index 4562ecfb5f..97b1e282ad 100644 --- a/scene/3d/collision_object_3d.cpp +++ b/scene/3d/collision_object_3d.cpp @@ -295,16 +295,12 @@ void CollisionObject3D::_input_event_call(Camera3D *p_camera, const Ref<InputEve } void CollisionObject3D::_mouse_enter() { - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_mouse_enter); - } + GDVIRTUAL_CALL(_mouse_enter); emit_signal(SceneStringNames::get_singleton()->mouse_entered); } void CollisionObject3D::_mouse_exit() { - if (get_script_instance()) { - get_script_instance()->call(SceneStringNames::get_singleton()->_mouse_exit); - } + GDVIRTUAL_CALL(_mouse_exit); emit_signal(SceneStringNames::get_singleton()->mouse_exited); } diff --git a/scene/3d/collision_shape_3d.cpp b/scene/3d/collision_shape_3d.cpp index 0bb0382301..61941a0e53 100644 --- a/scene/3d/collision_shape_3d.cpp +++ b/scene/3d/collision_shape_3d.cpp @@ -35,6 +35,7 @@ #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 "vehicle_body_3d.h" void CollisionShape3D::make_convex_from_siblings() { Node *p = get_parent(); @@ -130,13 +131,24 @@ PackedStringArray CollisionShape3D::get_configuration_warnings() const { } if (shape.is_valid() && Object::cast_to<RigidBody3D>(col_object)) { + String body_type = "RigidBody3D"; + if (Object::cast_to<VehicleBody3D>(col_object)) { + body_type = "VehicleBody3D"; + } + if (Object::cast_to<ConcavePolygonShape3D>(*shape)) { - warnings.push_back(RTR("ConcavePolygonShape3D doesn't support RigidBody3D in another mode than static.")); + warnings.push_back(vformat(RTR("When used for collision, ConcavePolygonShape3D is intended to work with static CollisionObject3D nodes like StaticBody3D.\nIt will likely not behave well for %ss (except when frozen and freeze_mode set to FREEZE_MODE_STATIC)."), body_type)); } else if (Object::cast_to<WorldBoundaryShape3D>(*shape)) { warnings.push_back(RTR("WorldBoundaryShape3D doesn't support RigidBody3D in another mode than static.")); } } + if (shape.is_valid() && Object::cast_to<CharacterBody3D>(col_object)) { + if (Object::cast_to<ConcavePolygonShape3D>(*shape)) { + warnings.push_back(RTR("When used for collision, ConcavePolygonShape3D is intended to work with static CollisionObject3D nodes like StaticBody3D.\nIt will likely not behave well for CharacterBody3Ds.")); + } + } + Vector3 scale = get_transform().get_basis().get_scale(); if (!(Math::is_zero_approx(scale.x - scale.y) && Math::is_zero_approx(scale.y - scale.z))) { warnings.push_back(RTR("A non-uniformly scaled CollisionShape3D node will probably not function as expected.\nPlease make its scale uniform (i.e. the same on all axes), and change the size of its shape resource instead.")); diff --git a/scene/3d/gpu_particles_3d.cpp b/scene/3d/gpu_particles_3d.cpp index fc84b3308e..dfb039d709 100644 --- a/scene/3d/gpu_particles_3d.cpp +++ b/scene/3d/gpu_particles_3d.cpp @@ -460,7 +460,12 @@ void GPUParticles3D::_notification(int p_what) { // Use internal process when emitting and one_shot is on so that when // the shot ends the editor can properly update. case NOTIFICATION_INTERNAL_PROCESS: { - RS::get_singleton()->particles_set_emitter_velocity(particles, (get_global_position() - previous_position) / get_process_delta_time()); + const Vector3 velocity = (get_global_position() - previous_position) / get_process_delta_time(); + + if (velocity != previous_velocity) { + RS::get_singleton()->particles_set_emitter_velocity(particles, velocity); + previous_velocity = velocity; + } previous_position = get_global_position(); if (one_shot) { diff --git a/scene/3d/gpu_particles_3d.h b/scene/3d/gpu_particles_3d.h index ae9349817c..0c9f2c1378 100644 --- a/scene/3d/gpu_particles_3d.h +++ b/scene/3d/gpu_particles_3d.h @@ -95,6 +95,7 @@ private: double emission_time = 0.0; double active_time = 0.0; float interp_to_end_factor = 0; + Vector3 previous_velocity; Vector3 previous_position; void _attach_sub_emitter(); diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp index 76933cd956..92b783392d 100644 --- a/scene/3d/lightmap_gi.cpp +++ b/scene/3d/lightmap_gi.cpp @@ -734,15 +734,15 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa MeshesFound &mf = meshes_found.write[m_i]; - Size2i lightmap_size = mf.mesh->get_lightmap_size_hint(); - - if (lightmap_size == Size2i(0, 0)) { + Size2i mesh_lightmap_size = mf.mesh->get_lightmap_size_hint(); + if (mesh_lightmap_size == Size2i(0, 0)) { // TODO we should compute a size if no lightmap hint is set, as we did in 3.x. // For now set to basic size to avoid crash. - lightmap_size = Size2i(64, 64); + mesh_lightmap_size = Size2i(64, 64); } + Size2i lightmap_size = Size2i(Size2(mesh_lightmap_size) * mf.lightmap_scale * texel_scale); + ERR_FAIL_COND_V(lightmap_size.x == 0 || lightmap_size.y == 0, BAKE_ERROR_LIGHTMAP_TOO_SMALL); - lightmap_size *= mf.lightmap_scale; TypedArray<RID> overrides; overrides.resize(mf.overrides.size()); for (int i = 0; i < mf.overrides.size(); i++) { @@ -1493,6 +1493,15 @@ float LightmapGI::get_bias() const { return bias; } +void LightmapGI::set_texel_scale(float p_multiplier) { + ERR_FAIL_COND(p_multiplier < (0.01 - CMP_EPSILON)); + texel_scale = p_multiplier; +} + +float LightmapGI::get_texel_scale() const { + return texel_scale; +} + void LightmapGI::set_max_texture_size(int p_size) { ERR_FAIL_COND_MSG(p_size < 2048, vformat("The LightmapGI maximum texture size supplied (%d) is too small. The minimum allowed value is 2048.", p_size)); ERR_FAIL_COND_MSG(p_size > 16384, vformat("The LightmapGI maximum texture size supplied (%d) is too large. The maximum allowed value is 16384.", p_size)); @@ -1576,6 +1585,9 @@ void LightmapGI::_bind_methods() { ClassDB::bind_method(D_METHOD("set_environment_custom_energy", "energy"), &LightmapGI::set_environment_custom_energy); ClassDB::bind_method(D_METHOD("get_environment_custom_energy"), &LightmapGI::get_environment_custom_energy); + ClassDB::bind_method(D_METHOD("set_texel_scale", "texel_scale"), &LightmapGI::set_texel_scale); + ClassDB::bind_method(D_METHOD("get_texel_scale"), &LightmapGI::get_texel_scale); + ClassDB::bind_method(D_METHOD("set_max_texture_size", "max_texture_size"), &LightmapGI::set_max_texture_size); ClassDB::bind_method(D_METHOD("get_max_texture_size"), &LightmapGI::get_max_texture_size); @@ -1609,6 +1621,7 @@ void LightmapGI::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_denoiser"), "set_use_denoiser", "is_using_denoiser"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "denoiser_strength", PROPERTY_HINT_RANGE, "0.001,0.2,0.001,or_greater"), "set_denoiser_strength", "get_denoiser_strength"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bias", PROPERTY_HINT_RANGE, "0.00001,0.1,0.00001,or_greater"), "set_bias", "get_bias"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "texel_scale", PROPERTY_HINT_RANGE, "0.01,100.0,0.01"), "set_texel_scale", "get_texel_scale"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_texture_size", PROPERTY_HINT_RANGE, "2048,16384,1"), "set_max_texture_size", "get_max_texture_size"); ADD_GROUP("Environment", "environment_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "environment_mode", PROPERTY_HINT_ENUM, "Disabled,Scene,Custom Sky,Custom Color"), "set_environment_mode", "get_environment_mode"); diff --git a/scene/3d/lightmap_gi.h b/scene/3d/lightmap_gi.h index fec0075693..765e4a731d 100644 --- a/scene/3d/lightmap_gi.h +++ b/scene/3d/lightmap_gi.h @@ -142,6 +142,7 @@ public: BAKE_ERROR_CANT_CREATE_IMAGE, BAKE_ERROR_USER_ABORTED, BAKE_ERROR_TEXTURE_SIZE_TOO_SMALL, + BAKE_ERROR_LIGHTMAP_TOO_SMALL, }; enum EnvironmentMode { @@ -158,6 +159,7 @@ private: int bounces = 3; float bounce_indirect_energy = 1.0; float bias = 0.0005; + float texel_scale = 1.0; int max_texture_size = 16384; bool interior = false; EnvironmentMode environment_mode = ENVIRONMENT_MODE_SCENE; @@ -284,6 +286,9 @@ public: void set_bias(float p_bias); float get_bias() const; + void set_texel_scale(float p_multiplier); + float get_texel_scale() const; + void set_max_texture_size(int p_size); int get_max_texture_size() const; diff --git a/scene/3d/visual_instance_3d.cpp b/scene/3d/visual_instance_3d.cpp index 33f865382d..503c39ae3e 100644 --- a/scene/3d/visual_instance_3d.cpp +++ b/scene/3d/visual_instance_3d.cpp @@ -250,7 +250,7 @@ GeometryInstance3D::VisibilityRangeFadeMode GeometryInstance3D::get_visibility_r return visibility_range_fade_mode; } -const StringName *GeometryInstance3D::_instance_uniform_get_remap(const StringName p_name) const { +const StringName *GeometryInstance3D::_instance_uniform_get_remap(const StringName &p_name) const { StringName *r = instance_shader_parameter_property_remap.getptr(p_name); if (!r) { String s = p_name; diff --git a/scene/3d/visual_instance_3d.h b/scene/3d/visual_instance_3d.h index ef0f7966e2..59ede26ac1 100644 --- a/scene/3d/visual_instance_3d.h +++ b/scene/3d/visual_instance_3d.h @@ -135,7 +135,7 @@ private: GIMode gi_mode = GI_MODE_STATIC; bool ignore_occlusion_culling = false; - const StringName *_instance_uniform_get_remap(const StringName p_name) const; + const StringName *_instance_uniform_get_remap(const StringName &p_name) const; protected: bool _set(const StringName &p_name, const Variant &p_value); diff --git a/scene/3d/xr_nodes.cpp b/scene/3d/xr_nodes.cpp index c12e78eddc..70e32c1a31 100644 --- a/scene/3d/xr_nodes.cpp +++ b/scene/3d/xr_nodes.cpp @@ -58,13 +58,13 @@ void XRCamera3D::_unbind_tracker() { tracker.unref(); } -void XRCamera3D::_changed_tracker(const StringName p_tracker_name, int p_tracker_type) { +void XRCamera3D::_changed_tracker(const StringName &p_tracker_name, int p_tracker_type) { if (p_tracker_name == tracker_name) { _bind_tracker(); } } -void XRCamera3D::_removed_tracker(const StringName p_tracker_name, int p_tracker_type) { +void XRCamera3D::_removed_tracker(const StringName &p_tracker_name, int p_tracker_type) { if (p_tracker_name == tracker_name) { _unbind_tracker(); } @@ -258,7 +258,7 @@ void XRNode3D::_validate_property(PropertyInfo &p_property) const { } } -void XRNode3D::set_tracker(const StringName p_tracker_name) { +void XRNode3D::set_tracker(const StringName &p_tracker_name) { if (tracker.is_valid() && tracker->get_tracker_name() == p_tracker_name) { // didn't change return; @@ -282,7 +282,7 @@ StringName XRNode3D::get_tracker() const { return tracker_name; } -void XRNode3D::set_pose_name(const StringName p_pose_name) { +void XRNode3D::set_pose_name(const StringName &p_pose_name) { pose_name = p_pose_name; // Update pose if we are bound to a tracker with a valid pose @@ -363,7 +363,7 @@ void XRNode3D::_unbind_tracker() { } } -void XRNode3D::_changed_tracker(const StringName p_tracker_name, int p_tracker_type) { +void XRNode3D::_changed_tracker(const StringName &p_tracker_name, int p_tracker_type) { if (tracker_name == p_tracker_name) { // just in case unref our current tracker _unbind_tracker(); @@ -373,7 +373,7 @@ void XRNode3D::_changed_tracker(const StringName p_tracker_name, int p_tracker_t } } -void XRNode3D::_removed_tracker(const StringName p_tracker_name, int p_tracker_type) { +void XRNode3D::_removed_tracker(const StringName &p_tracker_name, int p_tracker_type) { if (tracker_name == p_tracker_name) { // unref our tracker, it's no longer available _unbind_tracker(); diff --git a/scene/3d/xr_nodes.h b/scene/3d/xr_nodes.h index 185a6361d3..ad52cf113d 100644 --- a/scene/3d/xr_nodes.h +++ b/scene/3d/xr_nodes.h @@ -50,8 +50,8 @@ protected: void _bind_tracker(); void _unbind_tracker(); - void _changed_tracker(const StringName p_tracker_name, int p_tracker_type); - void _removed_tracker(const StringName p_tracker_name, int p_tracker_type); + void _changed_tracker(const StringName &p_tracker_name, int p_tracker_type); + void _removed_tracker(const StringName &p_tracker_name, int p_tracker_type); void _pose_changed(const Ref<XRPose> &p_pose); public: @@ -87,8 +87,8 @@ protected: virtual void _bind_tracker(); virtual void _unbind_tracker(); - void _changed_tracker(const StringName p_tracker_name, int p_tracker_type); - void _removed_tracker(const StringName p_tracker_name, int p_tracker_type); + void _changed_tracker(const StringName &p_tracker_name, int p_tracker_type); + void _removed_tracker(const StringName &p_tracker_name, int p_tracker_type); void _pose_changed(const Ref<XRPose> &p_pose); void _pose_lost_tracking(const Ref<XRPose> &p_pose); @@ -96,10 +96,10 @@ protected: public: void _validate_property(PropertyInfo &p_property) const; - void set_tracker(const StringName p_tracker_name); + void set_tracker(const StringName &p_tracker_name); StringName get_tracker() const; - void set_pose_name(const StringName p_pose); + void set_pose_name(const StringName &p_pose); StringName get_pose_name() const; bool get_is_active() const; diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp index da00d1dd7b..637b3917c7 100644 --- a/scene/animation/animation_mixer.cpp +++ b/scene/animation/animation_mixer.cpp @@ -2054,6 +2054,26 @@ void AnimationMixer::_notification(int p_what) { } } +void AnimationMixer::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { + String pf = p_function; + if (p_idx == 0) { + if (pf == "get_animation" || pf == "has_animation") { + List<StringName> al; + get_animation_list(&al); + for (const StringName &name : al) { + r_options->push_back(String(name).quote()); + } + } else if (pf == "get_animation_library" || pf == "has_animation_library" || pf == "remove_animation_library" || pf == "rename_animation_library") { + List<StringName> al; + get_animation_library_list(&al); + for (const StringName &name : al) { + r_options->push_back(String(name).quote()); + } + } + } + Node::get_argument_options(p_function, p_idx, r_options); +} + void AnimationMixer::_bind_methods() { /* ---- Data lists ---- */ ClassDB::bind_method(D_METHOD("add_animation_library", "name", "library"), &AnimationMixer::add_animation_library); @@ -2146,7 +2166,7 @@ AnimationMixer::AnimationMixer() { AnimationMixer::~AnimationMixer() { } -void AnimatedValuesBackup::set_data(const HashMap<NodePath, AnimationMixer::TrackCache *> p_data) { +void AnimatedValuesBackup::set_data(const HashMap<NodePath, AnimationMixer::TrackCache *> &p_data) { clear_data(); for (const KeyValue<NodePath, AnimationMixer::TrackCache *> &E : p_data) { diff --git a/scene/animation/animation_mixer.h b/scene/animation/animation_mixer.h index 0cd204b384..ecc8ab85de 100644 --- a/scene/animation/animation_mixer.h +++ b/scene/animation/animation_mixer.h @@ -340,6 +340,7 @@ protected: void _get_property_list(List<PropertyInfo> *p_list) const; void _notification(int p_what); virtual void _validate_property(PropertyInfo &p_property) const; + virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override; static void _bind_methods(); void _node_removed(Node *p_node); @@ -446,7 +447,7 @@ class AnimatedValuesBackup : public RefCounted { HashMap<NodePath, AnimationMixer::TrackCache *> data; public: - void set_data(const HashMap<NodePath, AnimationMixer::TrackCache *> p_data); + void set_data(const HashMap<NodePath, AnimationMixer::TrackCache *> &p_data); HashMap<NodePath, AnimationMixer::TrackCache *> get_data() const; void clear_data(); diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index d721ee3ec3..5d770c2eb2 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -89,7 +89,7 @@ void FileDialog::set_visible(bool p_visible) { void FileDialog::_native_dialog_cb(bool p_ok, const Vector<String> &p_files, int p_filter) { if (p_ok) { if (p_files.size() > 0) { - String f = p_files[0]; + const String &f = p_files[0]; if (mode == FILE_MODE_OPEN_FILES) { emit_signal(SNAME("files_selected"), p_files); } else { diff --git a/scene/gui/graph_edit_arranger.cpp b/scene/gui/graph_edit_arranger.cpp index 3f2007f7e0..29c3056b3b 100644 --- a/scene/gui/graph_edit_arranger.cpp +++ b/scene/gui/graph_edit_arranger.cpp @@ -287,13 +287,13 @@ Vector<StringName> GraphEditArranger::_split(const Vector<StringName> &r_layer, return Vector<StringName>(); } - StringName p = r_layer[Math::random(0, r_layer.size() - 1)]; + const StringName &p = r_layer[Math::random(0, r_layer.size() - 1)]; Vector<StringName> left; Vector<StringName> right; for (int i = 0; i < r_layer.size(); i++) { if (p != r_layer[i]) { - StringName q = r_layer[i]; + const StringName &q = r_layer[i]; int cross_pq = r_crossings[p][q]; int cross_qp = r_crossings[q][p]; if (cross_pq > cross_qp) { @@ -326,9 +326,9 @@ void GraphEditArranger::_horizontal_alignment(Dictionary &r_root, Dictionary &r_ for (int j = 0; j < lower_layer.size(); j++) { Vector<Pair<int, StringName>> up; - StringName current_node = lower_layer[j]; + const StringName ¤t_node = lower_layer[j]; for (int k = 0; k < upper_layer.size(); k++) { - StringName adjacent_neighbour = upper_layer[k]; + const StringName &adjacent_neighbour = upper_layer[k]; if (r_upper_neighbours[current_node].has(adjacent_neighbour)) { up.push_back(Pair<int, StringName>(k, adjacent_neighbour)); } @@ -360,12 +360,12 @@ void GraphEditArranger::_crossing_minimisation(HashMap<int, Vector<StringName>> HashMap<StringName, Dictionary> c; for (int j = 0; j < lower_layer.size(); j++) { - StringName p = lower_layer[j]; + const StringName &p = lower_layer[j]; Dictionary d; for (int k = 0; k < lower_layer.size(); k++) { unsigned int crossings = 0; - StringName q = lower_layer[k]; + const StringName &q = lower_layer[k]; if (j != k) { for (int h = 1; h < upper_layer.size(); h++) { @@ -421,7 +421,7 @@ void GraphEditArranger::_calculate_inner_shifts(Dictionary &r_inner_shifts, cons } } -float GraphEditArranger::_calculate_threshold(StringName p_v, StringName p_w, const Dictionary &r_node_names, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_inner_shift, real_t p_current_threshold, const HashMap<StringName, Vector2> &r_node_positions) { +float GraphEditArranger::_calculate_threshold(const StringName &p_v, const StringName &p_w, const Dictionary &r_node_names, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_inner_shift, real_t p_current_threshold, const HashMap<StringName, Vector2> &r_node_positions) { #define MAX_ORDER 2147483647 #define ORDER(node, layers) \ for (unsigned int i = 0; i < layers.size(); i++) { \ @@ -503,7 +503,7 @@ float GraphEditArranger::_calculate_threshold(StringName p_v, StringName p_w, co return threshold; } -void GraphEditArranger::_place_block(StringName p_v, float p_delta, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_node_name, const Dictionary &r_inner_shift, Dictionary &r_sink, Dictionary &r_shift, HashMap<StringName, Vector2> &r_node_positions) { +void GraphEditArranger::_place_block(const StringName &p_v, float p_delta, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_node_name, const Dictionary &r_inner_shift, Dictionary &r_sink, Dictionary &r_shift, HashMap<StringName, Vector2> &r_node_positions) { #define PRED(node, layers) \ for (unsigned int i = 0; i < layers.size(); i++) { \ int index = layers[i].find(node); \ diff --git a/scene/gui/graph_edit_arranger.h b/scene/gui/graph_edit_arranger.h index e79944e5dd..925b58d428 100644 --- a/scene/gui/graph_edit_arranger.h +++ b/scene/gui/graph_edit_arranger.h @@ -54,8 +54,8 @@ class GraphEditArranger : public RefCounted { void _horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours, const HashSet<StringName> &r_selected_nodes); void _crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours); void _calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const HashSet<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info); - float _calculate_threshold(StringName p_v, StringName p_w, const Dictionary &r_node_names, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_inner_shift, real_t p_current_threshold, const HashMap<StringName, Vector2> &r_node_positions); - void _place_block(StringName p_v, float p_delta, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_node_name, const Dictionary &r_inner_shift, Dictionary &r_sink, Dictionary &r_shift, HashMap<StringName, Vector2> &r_node_positions); + float _calculate_threshold(const StringName &p_v, const StringName &p_w, const Dictionary &r_node_names, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_inner_shift, real_t p_current_threshold, const HashMap<StringName, Vector2> &r_node_positions); + void _place_block(const StringName &p_v, float p_delta, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_node_name, const Dictionary &r_inner_shift, Dictionary &r_sink, Dictionary &r_shift, HashMap<StringName, Vector2> &r_node_positions); public: void arrange_nodes(); diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index 3b1c1a153f..1b960a9b62 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -482,6 +482,27 @@ Color GraphNode::get_slot_color_left(int p_slot_index) const { return slot_table[p_slot_index].color_left; } +void GraphNode::set_slot_custom_icon_left(int p_slot_index, const Ref<Texture2D> &p_custom_icon) { + ERR_FAIL_COND_MSG(!slot_table.has(p_slot_index), vformat("Cannot set custom_port_icon_left for the slot with index '%d' because it hasn't been enabled.", p_slot_index)); + + if (slot_table[p_slot_index].custom_port_icon_left == p_custom_icon) { + return; + } + + slot_table[p_slot_index].custom_port_icon_left = p_custom_icon; + queue_redraw(); + port_pos_dirty = true; + + emit_signal(SNAME("slot_updated"), p_slot_index); +} + +Ref<Texture2D> GraphNode::get_slot_custom_icon_left(int p_slot_index) const { + if (!slot_table.has(p_slot_index)) { + return Ref<Texture2D>(); + } + return slot_table[p_slot_index].custom_port_icon_left; +} + bool GraphNode::is_slot_enabled_right(int p_slot_index) const { if (!slot_table.has(p_slot_index)) { return false; @@ -545,6 +566,27 @@ Color GraphNode::get_slot_color_right(int p_slot_index) const { return slot_table[p_slot_index].color_right; } +void GraphNode::set_slot_custom_icon_right(int p_slot_index, const Ref<Texture2D> &p_custom_icon) { + ERR_FAIL_COND_MSG(!slot_table.has(p_slot_index), vformat("Cannot set custom_port_icon_right for the slot with index '%d' because it hasn't been enabled.", p_slot_index)); + + if (slot_table[p_slot_index].custom_port_icon_right == p_custom_icon) { + return; + } + + slot_table[p_slot_index].custom_port_icon_right = p_custom_icon; + queue_redraw(); + port_pos_dirty = true; + + emit_signal(SNAME("slot_updated"), p_slot_index); +} + +Ref<Texture2D> GraphNode::get_slot_custom_icon_right(int p_slot_index) const { + if (!slot_table.has(p_slot_index)) { + return Ref<Texture2D>(); + } + return slot_table[p_slot_index].custom_port_icon_right; +} + bool GraphNode::is_slot_draw_stylebox(int p_slot_index) const { if (!slot_table.has(p_slot_index)) { return false; @@ -797,6 +839,9 @@ void GraphNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_slot_color_left", "slot_index", "color"), &GraphNode::set_slot_color_left); ClassDB::bind_method(D_METHOD("get_slot_color_left", "slot_index"), &GraphNode::get_slot_color_left); + ClassDB::bind_method(D_METHOD("set_slot_custom_icon_left", "slot_index", "custom_icon"), &GraphNode::set_slot_custom_icon_left); + ClassDB::bind_method(D_METHOD("get_slot_custom_icon_left", "slot_index"), &GraphNode::get_slot_custom_icon_left); + ClassDB::bind_method(D_METHOD("is_slot_enabled_right", "slot_index"), &GraphNode::is_slot_enabled_right); ClassDB::bind_method(D_METHOD("set_slot_enabled_right", "slot_index", "enable"), &GraphNode::set_slot_enabled_right); @@ -806,6 +851,9 @@ void GraphNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_slot_color_right", "slot_index", "color"), &GraphNode::set_slot_color_right); ClassDB::bind_method(D_METHOD("get_slot_color_right", "slot_index"), &GraphNode::get_slot_color_right); + ClassDB::bind_method(D_METHOD("set_slot_custom_icon_right", "slot_index", "custom_icon"), &GraphNode::set_slot_custom_icon_right); + ClassDB::bind_method(D_METHOD("get_slot_custom_icon_right", "slot_index"), &GraphNode::get_slot_custom_icon_right); + ClassDB::bind_method(D_METHOD("is_slot_draw_stylebox", "slot_index"), &GraphNode::is_slot_draw_stylebox); ClassDB::bind_method(D_METHOD("set_slot_draw_stylebox", "slot_index", "enable"), &GraphNode::set_slot_draw_stylebox); diff --git a/scene/gui/graph_node.h b/scene/gui/graph_node.h index 04ca9e7cb4..a0610b37fb 100644 --- a/scene/gui/graph_node.h +++ b/scene/gui/graph_node.h @@ -129,6 +129,9 @@ public: void set_slot_color_left(int p_slot_index, const Color &p_color); Color get_slot_color_left(int p_slot_index) const; + void set_slot_custom_icon_left(int p_slot_index, const Ref<Texture2D> &p_custom_icon); + Ref<Texture2D> get_slot_custom_icon_left(int p_slot_index) const; + bool is_slot_enabled_right(int p_slot_index) const; void set_slot_enabled_right(int p_slot_index, bool p_enable); @@ -138,6 +141,9 @@ public: void set_slot_color_right(int p_slot_index, const Color &p_color); Color get_slot_color_right(int p_slot_index) const; + void set_slot_custom_icon_right(int p_slot_index, const Ref<Texture2D> &p_custom_icon); + Ref<Texture2D> get_slot_custom_icon_right(int p_slot_index) const; + bool is_slot_draw_stylebox(int p_slot_index) const; void set_slot_draw_stylebox(int p_slot_index, bool p_enable); diff --git a/scene/gui/item_list.cpp b/scene/gui/item_list.cpp index 02d44caa1c..e9d34fae3b 100644 --- a/scene/gui/item_list.cpp +++ b/scene/gui/item_list.cpp @@ -44,7 +44,7 @@ void ItemList::_shape_text(int p_idx) { } else { item.text_buf->set_direction((TextServer::Direction)item.text_direction); } - item.text_buf->add_string(item.text, theme_cache.font, theme_cache.font_size, item.language); + item.text_buf->add_string(item.xl_text, theme_cache.font, theme_cache.font_size, item.language); if (icon_mode == ICON_MODE_TOP && max_text_lines > 0) { item.text_buf->set_break_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_TRIM_EDGE_SPACES); } else { @@ -58,6 +58,7 @@ int ItemList::add_item(const String &p_item, const Ref<Texture2D> &p_texture, bo Item item; item.icon = p_texture; item.text = p_item; + item.xl_text = atr(p_item); item.selectable = p_selectable; items.push_back(item); int item_id = items.size() - 1; @@ -94,6 +95,7 @@ void ItemList::set_item_text(int p_idx, const String &p_text) { } items.write[p_idx].text = p_text; + items.write[p_idx].xl_text = atr(p_text); _shape_text(p_idx); queue_redraw(); shape_changed = true; @@ -1001,7 +1003,6 @@ void ItemList::_notification(int p_what) { } break; case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: - case NOTIFICATION_TRANSLATION_CHANGED: case NOTIFICATION_THEME_CHANGED: { for (int i = 0; i < items.size(); i++) { _shape_text(i); @@ -1009,6 +1010,14 @@ void ItemList::_notification(int p_what) { shape_changed = true; queue_redraw(); } break; + case NOTIFICATION_TRANSLATION_CHANGED: { + for (int i = 0; i < items.size(); i++) { + items.write[i].xl_text = atr(items[i].text); + _shape_text(i); + } + shape_changed = true; + queue_redraw(); + } break; case NOTIFICATION_DRAW: { force_update_list_size(); diff --git a/scene/gui/item_list.h b/scene/gui/item_list.h index 796e0eb687..28f9012058 100644 --- a/scene/gui/item_list.h +++ b/scene/gui/item_list.h @@ -57,6 +57,7 @@ private: Color icon_modulate = Color(1, 1, 1, 1); Ref<Texture2D> tag_icon; String text; + String xl_text; Ref<TextParagraph> text_buf; String language; TextDirection text_direction = TEXT_DIRECTION_AUTO; diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 1b1905e793..56da1332e7 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -813,6 +813,19 @@ Size2 Label::get_minimum_size() const { } } +#ifndef DISABLE_DEPRECATED +bool Label::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == SNAME("valign")) { + set_vertical_alignment((VerticalAlignment)p_value.operator int()); + return true; + } else if (p_name == SNAME("align")) { + set_horizontal_alignment((HorizontalAlignment)p_value.operator int()); + return true; + } + return false; +} +#endif + int Label::get_line_count() const { if (!is_inside_tree()) { return 1; diff --git a/scene/gui/label.h b/scene/gui/label.h index 9306033dd0..4bd0e53605 100644 --- a/scene/gui/label.h +++ b/scene/gui/label.h @@ -91,6 +91,9 @@ private: protected: void _notification(int p_what); static void _bind_methods(); +#ifndef DISABLE_DEPRECATED + bool _set(const StringName &p_name, const Variant &p_value); +#endif public: virtual Size2 get_minimum_size() const override; diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 1efe11a777..7d34358af2 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -1718,6 +1718,12 @@ void LineEdit::set_caret_column(int p_column) { } else if (MAX(primary_caret_offset.x, primary_caret_offset.y) >= ofs_max) { scroll_offset += ofs_max - MAX(primary_caret_offset.x, primary_caret_offset.y); } + + // Scroll to show as much text as possible + if (text_width + scroll_offset + x_ofs < ofs_max) { + scroll_offset = ofs_max - x_ofs - text_width; + } + scroll_offset = MIN(0, scroll_offset); queue_redraw(); diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp index f9740b1217..6386bb20c3 100644 --- a/scene/gui/option_button.cpp +++ b/scene/gui/option_button.cpp @@ -159,7 +159,7 @@ void OptionButton::_notification(int p_what) { bool OptionButton::_set(const StringName &p_name, const Variant &p_value) { Vector<String> components = String(p_name).split("/", true, 2); if (components.size() >= 2 && components[0] == "popup") { - String property = components[2]; + const String &property = components[2]; if (property != "text" && property != "icon" && property != "id" && property != "disabled" && property != "separator") { return false; } @@ -186,7 +186,7 @@ bool OptionButton::_set(const StringName &p_name, const Variant &p_value) { bool OptionButton::_get(const StringName &p_name, Variant &r_ret) const { Vector<String> components = String(p_name).split("/", true, 2); if (components.size() >= 2 && components[0] == "popup") { - String property = components[2]; + const String &property = components[2]; if (property != "text" && property != "icon" && property != "id" && property != "disabled" && property != "separator") { return false; } diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 4fb94e77b1..8af05bd205 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -2546,7 +2546,7 @@ bool PopupMenu::_set(const StringName &p_name, const Variant &p_value) { Vector<String> components = String(p_name).split("/", true, 2); if (components.size() >= 2 && components[0].begins_with("item_") && components[0].trim_prefix("item_").is_valid_int()) { int item_index = components[0].trim_prefix("item_").to_int(); - String property = components[1]; + const String &property = components[1]; if (property == "text") { set_item_text(item_index, p_value); return true; @@ -2625,7 +2625,7 @@ bool PopupMenu::_get(const StringName &p_name, Variant &r_ret) const { Vector<String> components = String(p_name).split("/", true, 2); if (components.size() >= 2 && components[0].begins_with("item_") && components[0].trim_prefix("item_").is_valid_int()) { int item_index = components[0].trim_prefix("item_").to_int(); - String property = components[1]; + const String &property = components[1]; if (property == "text") { r_ret = get_item_text(item_index); return true; diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index fbc374e89e..2d5a32dfdd 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -4532,7 +4532,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { if (subtag_a.size() == 2) { if (subtag_a[0] == "font" || subtag_a[0] == "f") { - String fnt = subtag_a[1]; + const String &fnt = subtag_a[1]; Ref<Font> font = ResourceLoader::load(fnt, "Font"); if (font.is_valid()) { f = font; @@ -4776,7 +4776,7 @@ void RichTextLabel::append_text(const String &p_bbcode) { if (subtag_a.size() == 2) { if (subtag_a[0] == "name" || subtag_a[0] == "n") { - String fnt = subtag_a[1]; + const String &fnt = subtag_a[1]; Ref<Font> font_data = ResourceLoader::load(fnt, "Font"); if (font_data.is_valid()) { font = font_data; diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index e9fbdbb312..718ccccc2c 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -1657,7 +1657,7 @@ bool TabBar::_set(const StringName &p_name, const Variant &p_value) { Vector<String> components = String(p_name).split("/", true, 2); if (components.size() >= 2 && components[0].begins_with("tab_") && components[0].trim_prefix("tab_").is_valid_int()) { int tab_index = components[0].trim_prefix("tab_").to_int(); - String property = components[1]; + const String &property = components[1]; if (property == "title") { set_tab_title(tab_index, p_value); return true; @@ -1676,7 +1676,7 @@ bool TabBar::_get(const StringName &p_name, Variant &r_ret) const { Vector<String> components = String(p_name).split("/", true, 2); if (components.size() >= 2 && components[0].begins_with("tab_") && components[0].trim_prefix("tab_").is_valid_int()) { int tab_index = components[0].trim_prefix("tab_").to_int(); - String property = components[1]; + const String &property = components[1]; if (property == "title") { r_ret = get_tab_title(tab_index); return true; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index cd1450b879..fe35669311 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -989,18 +989,6 @@ void TextEdit::_notification(int p_what) { RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(ofs_x, ofs_y, xmargin_end, row_height), theme_cache.current_line_color); } } - - // Give visual indication of empty selected line. - for (int c = 0; c < carets.size(); c++) { - if (has_selection(c) && line >= get_selection_from_line(c) && line <= get_selection_to_line(c) && char_margin >= xmargin_beg) { - float char_w = theme_cache.font->get_char_size(' ', theme_cache.font_size).width; - if (rtl) { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(size.width - xmargin_beg - ofs_x - char_w, ofs_y, char_w, row_height), theme_cache.selection_color); - } else { - RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(xmargin_beg + ofs_x, ofs_y, char_w, row_height), theme_cache.selection_color); - } - } - } } else { // If it has text, then draw current line marker in the margin, as line number etc will draw over it, draw the rest of line marker later. if (caret_line_wrap_index_map.has(line) && caret_line_wrap_index_map[line].has(line_wrap_index) && highlight_current_line) { @@ -1093,13 +1081,26 @@ void TextEdit::_notification(int p_what) { char_margin = size.width - char_margin - TS->shaped_text_get_size(rid).x; } + // Draw selections. + float char_w = theme_cache.font->get_char_size(' ', theme_cache.font_size).width; for (int c = 0; c < carets.size(); c++) { - if (!clipped && has_selection(c) && line >= get_selection_from_line(c) && line <= get_selection_to_line(c)) { // Selection + if (!clipped && has_selection(c) && line >= get_selection_from_line(c) && line <= get_selection_to_line(c)) { int sel_from = (line > get_selection_from_line(c)) ? TS->shaped_text_get_range(rid).x : get_selection_from_column(c); int sel_to = (line < get_selection_to_line(c)) ? TS->shaped_text_get_range(rid).y : get_selection_to_column(c); Vector<Vector2> sel = TS->shaped_text_get_selection(rid, sel_from, sel_to); + + // Show selection at the end of line. + if (line < get_selection_to_line(c)) { + if (rtl) { + sel.push_back(Vector2(-char_w, 0)); + } else { + float line_end = TS->shaped_text_get_size(rid).width; + sel.push_back(Vector2(line_end, line_end + char_w)); + } + } + for (int j = 0; j < sel.size(); j++) { - Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, sel[j].y - sel[j].x, row_height); + Rect2 rect = Rect2(sel[j].x + char_margin + ofs_x, ofs_y, Math::ceil(sel[j].y - sel[j].x), row_height); if (rect.position.x + rect.size.x <= xmargin_beg || rect.position.x > xmargin_end) { continue; } diff --git a/scene/resources/animation_library.cpp b/scene/resources/animation_library.cpp index 436bf88ec9..fac72a3150 100644 --- a/scene/resources/animation_library.cpp +++ b/scene/resources/animation_library.cpp @@ -143,6 +143,18 @@ Dictionary AnimationLibrary::_get_data() const { return ret; } +void AnimationLibrary::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { + String pf = p_function; + if (p_idx == 0 && (pf == "get_animation" || pf == "has_animation" || pf == "rename_animation" || pf == "remove_animation")) { + List<StringName> names; + get_animation_list(&names); + for (const StringName &E : names) { + r_options->push_back(E.operator String().quote()); + } + } + Resource::get_argument_options(p_function, p_idx, r_options); +} + void AnimationLibrary::_bind_methods() { ClassDB::bind_method(D_METHOD("add_animation", "name", "animation"), &AnimationLibrary::add_animation); ClassDB::bind_method(D_METHOD("remove_animation", "name"), &AnimationLibrary::remove_animation); diff --git a/scene/resources/animation_library.h b/scene/resources/animation_library.h index b2152fd7c5..c1d78068bb 100644 --- a/scene/resources/animation_library.h +++ b/scene/resources/animation_library.h @@ -62,6 +62,8 @@ public: Ref<Animation> get_animation(const StringName &p_name) const; void get_animation_list(List<StringName> *p_animations) const; + virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override; + AnimationLibrary(); }; diff --git a/scene/resources/bone_map.cpp b/scene/resources/bone_map.cpp index 5363b8ec79..97e3af726a 100644 --- a/scene/resources/bone_map.cpp +++ b/scene/resources/bone_map.cpp @@ -77,22 +77,22 @@ void BoneMap::set_profile(const Ref<SkeletonProfile> &p_profile) { notify_property_list_changed(); } -StringName BoneMap::get_skeleton_bone_name(StringName p_profile_bone_name) const { +StringName BoneMap::get_skeleton_bone_name(const StringName &p_profile_bone_name) const { ERR_FAIL_COND_V(!bone_map.has(p_profile_bone_name), StringName()); return bone_map.get(p_profile_bone_name); } -void BoneMap::_set_skeleton_bone_name(StringName p_profile_bone_name, const StringName p_skeleton_bone_name) { +void BoneMap::_set_skeleton_bone_name(const StringName &p_profile_bone_name, const StringName &p_skeleton_bone_name) { ERR_FAIL_COND(!bone_map.has(p_profile_bone_name)); bone_map.insert(p_profile_bone_name, p_skeleton_bone_name); } -void BoneMap::set_skeleton_bone_name(StringName p_profile_bone_name, const StringName p_skeleton_bone_name) { +void BoneMap::set_skeleton_bone_name(const StringName &p_profile_bone_name, const StringName &p_skeleton_bone_name) { _set_skeleton_bone_name(p_profile_bone_name, p_skeleton_bone_name); emit_signal("bone_map_updated"); } -StringName BoneMap::find_profile_bone_name(StringName p_skeleton_bone_name) const { +StringName BoneMap::find_profile_bone_name(const StringName &p_skeleton_bone_name) const { StringName profile_bone_name; HashMap<StringName, StringName>::ConstIterator E = bone_map.begin(); while (E) { @@ -105,7 +105,7 @@ StringName BoneMap::find_profile_bone_name(StringName p_skeleton_bone_name) cons return profile_bone_name; } -int BoneMap::get_skeleton_bone_name_count(const StringName p_skeleton_bone_name) const { +int BoneMap::get_skeleton_bone_name_count(const StringName &p_skeleton_bone_name) const { int count = 0; HashMap<StringName, StringName>::ConstIterator E = bone_map.begin(); while (E) { diff --git a/scene/resources/bone_map.h b/scene/resources/bone_map.h index 983a3b7b5a..fbd2f92dee 100644 --- a/scene/resources/bone_map.h +++ b/scene/resources/bone_map.h @@ -53,13 +53,13 @@ public: Ref<SkeletonProfile> get_profile() const; void set_profile(const Ref<SkeletonProfile> &p_profile); - int get_skeleton_bone_name_count(const StringName p_skeleton_bone_name) const; + int get_skeleton_bone_name_count(const StringName &p_skeleton_bone_name) const; - StringName get_skeleton_bone_name(StringName p_profile_bone_name) const; - void set_skeleton_bone_name(StringName p_profile_bone_name, const StringName p_skeleton_bone_name); - void _set_skeleton_bone_name(StringName p_profile_bone_name, const StringName p_skeleton_bone_name); // Avoid to emit signal for editor. + StringName get_skeleton_bone_name(const StringName &p_profile_bone_name) const; + void set_skeleton_bone_name(const StringName &p_profile_bone_name, const StringName &p_skeleton_bone_name); + void _set_skeleton_bone_name(const StringName &p_profile_bone_name, const StringName &p_skeleton_bone_name); // Avoid to emit signal for editor. - StringName find_profile_bone_name(StringName p_skeleton_bone_name) const; + StringName find_profile_bone_name(const StringName &p_skeleton_bone_name) const; BoneMap(); ~BoneMap(); diff --git a/scene/resources/curve.cpp b/scene/resources/curve.cpp index d2aabf7d5c..2b54acef75 100644 --- a/scene/resources/curve.cpp +++ b/scene/resources/curve.cpp @@ -527,7 +527,7 @@ bool Curve::_set(const StringName &p_name, const Variant &p_value) { Vector<String> components = String(p_name).split("/", true, 2); if (components.size() >= 2 && components[0].begins_with("point_") && components[0].trim_prefix("point_").is_valid_int()) { int point_index = components[0].trim_prefix("point_").to_int(); - String property = components[1]; + const String &property = components[1]; if (property == "position") { Vector2 position = p_value.operator Vector2(); set_point_offset(point_index, position.x); @@ -556,7 +556,7 @@ bool Curve::_get(const StringName &p_name, Variant &r_ret) const { Vector<String> components = String(p_name).split("/", true, 2); if (components.size() >= 2 && components[0].begins_with("point_") && components[0].trim_prefix("point_").is_valid_int()) { int point_index = components[0].trim_prefix("point_").to_int(); - String property = components[1]; + const String &property = components[1]; if (property == "position") { r_ret = get_point_position(point_index); return true; @@ -977,7 +977,7 @@ Transform2D Curve2D::_sample_posture(Interval p_interval) const { const Vector2 forward = forward_begin.slerp(forward_end, frac).normalized(); const Vector2 side = Vector2(-forward.y, forward.x); - return Transform2D(side, forward, Vector2(0.0, 0.0)); + return Transform2D(forward, side, Vector2(0.0, 0.0)); } Vector2 Curve2D::sample_baked(real_t p_offset, bool p_cubic) const { @@ -1046,6 +1046,10 @@ real_t Curve2D::get_bake_interval() const { return bake_interval; } +PackedVector2Array Curve2D::get_points() const { + return _get_data()["points"]; +} + Vector2 Curve2D::get_closest_point(const Vector2 &p_to_point) const { // Brute force method. @@ -1255,7 +1259,7 @@ bool Curve2D::_set(const StringName &p_name, const Variant &p_value) { Vector<String> components = String(p_name).split("/", true, 2); if (components.size() >= 2 && components[0].begins_with("point_") && components[0].trim_prefix("point_").is_valid_int()) { int point_index = components[0].trim_prefix("point_").to_int(); - String property = components[1]; + const String &property = components[1]; if (property == "position") { set_point_position(point_index, p_value); return true; @@ -1274,7 +1278,7 @@ bool Curve2D::_get(const StringName &p_name, Variant &r_ret) const { Vector<String> components = String(p_name).split("/", true, 2); if (components.size() >= 2 && components[0].begins_with("point_") && components[0].trim_prefix("point_").is_valid_int()) { int point_index = components[0].trim_prefix("point_").to_int(); - String property = components[1]; + const String &property = components[1]; if (property == "position") { r_ret = get_point_position(point_index); return true; @@ -2220,7 +2224,7 @@ bool Curve3D::_set(const StringName &p_name, const Variant &p_value) { Vector<String> components = String(p_name).split("/", true, 2); if (components.size() >= 2 && components[0].begins_with("point_") && components[0].trim_prefix("point_").is_valid_int()) { int point_index = components[0].trim_prefix("point_").to_int(); - String property = components[1]; + const String &property = components[1]; if (property == "position") { set_point_position(point_index, p_value); return true; @@ -2242,7 +2246,7 @@ bool Curve3D::_get(const StringName &p_name, Variant &r_ret) const { Vector<String> components = String(p_name).split("/", true, 2); if (components.size() >= 2 && components[0].begins_with("point_") && components[0].trim_prefix("point_").is_valid_int()) { int point_index = components[0].trim_prefix("point_").to_int(); - String property = components[1]; + const String &property = components[1]; if (property == "position") { r_ret = get_point_position(point_index); return true; diff --git a/scene/resources/curve.h b/scene/resources/curve.h index 440e4466f5..e085dfedbd 100644 --- a/scene/resources/curve.h +++ b/scene/resources/curve.h @@ -237,6 +237,7 @@ public: real_t get_baked_length() const; Vector2 sample_baked(real_t p_offset, bool p_cubic = false) const; Transform2D sample_baked_with_rotation(real_t p_offset, bool p_cubic = false) const; + PackedVector2Array get_points() const; PackedVector2Array get_baked_points() const; //useful for going through Vector2 get_closest_point(const Vector2 &p_to_point) const; real_t get_closest_offset(const Vector2 &p_to_point) const; diff --git a/scene/resources/font.cpp b/scene/resources/font.cpp index 13b22ae12c..60c3816020 100644 --- a/scene/resources/font.cpp +++ b/scene/resources/font.cpp @@ -1118,11 +1118,11 @@ bool FontFile::_set(const StringName &p_name, const Variant &p_value) { #endif // DISABLE_DEPRECATED if (tokens.size() == 2 && tokens[0] == "language_support_override") { - String lang_code = tokens[1]; + const String &lang_code = tokens[1]; set_language_support_override(lang_code, p_value); return true; } else if (tokens.size() == 2 && tokens[0] == "script_support_override") { - String script_code = tokens[1]; + const String &script_code = tokens[1]; set_script_support_override(script_code, p_value); return true; } else if (tokens.size() >= 3 && tokens[0] == "cache") { @@ -1209,11 +1209,11 @@ bool FontFile::_set(const StringName &p_name, const Variant &p_value) { bool FontFile::_get(const StringName &p_name, Variant &r_ret) const { Vector<String> tokens = p_name.operator String().split("/"); if (tokens.size() == 2 && tokens[0] == "language_support_override") { - String lang_code = tokens[1]; + const String &lang_code = tokens[1]; r_ret = get_language_support_override(lang_code); return true; } else if (tokens.size() == 2 && tokens[0] == "script_support_override") { - String script_code = tokens[1]; + const String &script_code = tokens[1]; r_ret = get_script_support_override(script_code); return true; } else if (tokens.size() >= 3 && tokens[0] == "cache") { diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp index dba4e4eb34..74b1157e5f 100644 --- a/scene/resources/material.cpp +++ b/scene/resources/material.cpp @@ -1947,7 +1947,7 @@ Ref<Texture2D> BaseMaterial3D::get_texture(TextureParam p_param) const { return textures[p_param]; } -Ref<Texture2D> BaseMaterial3D::get_texture_by_name(StringName p_name) const { +Ref<Texture2D> BaseMaterial3D::get_texture_by_name(const StringName &p_name) const { for (int i = 0; i < (int)BaseMaterial3D::TEXTURE_MAX; i++) { TextureParam param = TextureParam(i); if (p_name == shader_names->texture_names[param]) { diff --git a/scene/resources/material.h b/scene/resources/material.h index 1c698cb104..06522e6470 100644 --- a/scene/resources/material.h +++ b/scene/resources/material.h @@ -682,7 +682,7 @@ public: void set_texture(TextureParam p_param, const Ref<Texture2D> &p_texture); Ref<Texture2D> get_texture(TextureParam p_param) const; // Used only for shader material conversion - Ref<Texture2D> get_texture_by_name(StringName p_name) const; + Ref<Texture2D> get_texture_by_name(const StringName &p_name) const; void set_texture_filter(TextureFilter p_filter); TextureFilter get_texture_filter() const; diff --git a/scene/resources/navigation_mesh.cpp b/scene/resources/navigation_mesh.cpp index 82b5c6257c..dd2e7ef268 100644 --- a/scene/resources/navigation_mesh.cpp +++ b/scene/resources/navigation_mesh.cpp @@ -127,7 +127,7 @@ NavigationMesh::SourceGeometryMode NavigationMesh::get_source_geometry_mode() co return source_geometry_mode; } -void NavigationMesh::set_source_group_name(StringName p_group_name) { +void NavigationMesh::set_source_group_name(const StringName &p_group_name) { source_group_name = p_group_name; } diff --git a/scene/resources/navigation_mesh.h b/scene/resources/navigation_mesh.h index 8b9b810038..cb8880eb94 100644 --- a/scene/resources/navigation_mesh.h +++ b/scene/resources/navigation_mesh.h @@ -122,7 +122,7 @@ public: void set_source_geometry_mode(SourceGeometryMode p_geometry_mode); SourceGeometryMode get_source_geometry_mode() const; - void set_source_group_name(StringName p_group_name); + void set_source_group_name(const StringName &p_group_name); StringName get_source_group_name() const; void set_cell_size(float p_value); diff --git a/scene/resources/navigation_polygon.cpp b/scene/resources/navigation_polygon.cpp index 52840eaa65..2770cb0b87 100644 --- a/scene/resources/navigation_polygon.cpp +++ b/scene/resources/navigation_polygon.cpp @@ -393,7 +393,7 @@ NavigationPolygon::SourceGeometryMode NavigationPolygon::get_source_geometry_mod return source_geometry_mode; } -void NavigationPolygon::set_source_geometry_group_name(StringName p_group_name) { +void NavigationPolygon::set_source_geometry_group_name(const StringName &p_group_name) { source_geometry_group_name = p_group_name; } diff --git a/scene/resources/navigation_polygon.h b/scene/resources/navigation_polygon.h index 4a6a97e2e7..e589ad6dce 100644 --- a/scene/resources/navigation_polygon.h +++ b/scene/resources/navigation_polygon.h @@ -127,7 +127,7 @@ public: void set_source_geometry_mode(SourceGeometryMode p_geometry_mode); SourceGeometryMode get_source_geometry_mode() const; - void set_source_geometry_group_name(StringName p_group_name); + void set_source_geometry_group_name(const StringName &p_group_name); StringName get_source_geometry_group_name() const; void set_agent_radius(real_t p_value); diff --git a/scene/resources/portable_compressed_texture.cpp b/scene/resources/portable_compressed_texture.cpp index a61799c7d6..19a96d6ab2 100644 --- a/scene/resources/portable_compressed_texture.cpp +++ b/scene/resources/portable_compressed_texture.cpp @@ -30,6 +30,7 @@ #include "portable_compressed_texture.h" +#include "core/config/project_settings.h" #include "core/io/marshalls.h" #include "scene/resources/bit_map.h" @@ -41,7 +42,8 @@ void PortableCompressedTexture2D::_set_data(const Vector<uint8_t> &p_data) { const uint8_t *data = p_data.ptr(); uint32_t data_size = p_data.size(); ERR_FAIL_COND(data_size < 20); - compression_mode = CompressionMode(decode_uint32(data + 0)); + compression_mode = CompressionMode(decode_uint16(data)); + DataFormat data_format = DataFormat(decode_uint16(data + 2)); format = Image::Format(decode_uint32(data + 4)); uint32_t mipmap_count = decode_uint32(data + 8); size.width = decode_uint32(data + 12); @@ -56,6 +58,16 @@ void PortableCompressedTexture2D::_set_data(const Vector<uint8_t> &p_data) { switch (compression_mode) { case COMPRESSION_MODE_LOSSLESS: case COMPRESSION_MODE_LOSSY: { + ImageMemLoadFunc loader_func; + if (data_format == DATA_FORMAT_UNDEFINED) { + loader_func = nullptr; + } else if (data_format == DATA_FORMAT_PNG) { + loader_func = Image::_png_mem_unpacker_func; + } else if (data_format == DATA_FORMAT_WEBP) { + loader_func = Image::_webp_mem_loader_func; + } else { + ERR_FAIL(); + } Vector<uint8_t> image_data; ERR_FAIL_COND(data_size < 4); @@ -64,7 +76,9 @@ void PortableCompressedTexture2D::_set_data(const Vector<uint8_t> &p_data) { data += 4; data_size -= 4; ERR_FAIL_COND(mipsize < data_size); - Ref<Image> img = memnew(Image(data, data_size)); + Ref<Image> img = loader_func == nullptr + ? memnew(Image(data, data_size)) + : Ref<Image>(loader_func(data, data_size)); ERR_FAIL_COND(img->is_empty()); if (img->get_format() != format) { // May happen due to webp/png in the tiny mipmaps. img->convert(format); @@ -99,6 +113,7 @@ void PortableCompressedTexture2D::_set_data(const Vector<uint8_t> &p_data) { } image_stored = true; + size_override = size; RenderingServer::get_singleton()->texture_set_size_override(texture, size_override.width, size_override.height); alpha_cache.unref(); @@ -122,7 +137,8 @@ void PortableCompressedTexture2D::create_from_image(const Ref<Image> &p_image, C Vector<uint8_t> buffer; buffer.resize(20); - encode_uint32(p_compression_mode, buffer.ptrw()); + encode_uint16(p_compression_mode, buffer.ptrw()); + encode_uint16(DATA_FORMAT_UNDEFINED, buffer.ptrw() + 2); encode_uint32(p_image->get_format(), buffer.ptrw() + 4); encode_uint32(p_image->get_mipmap_count() + 1, buffer.ptrw() + 8); encode_uint32(p_image->get_width(), buffer.ptrw() + 12); @@ -131,12 +147,22 @@ void PortableCompressedTexture2D::create_from_image(const Ref<Image> &p_image, C switch (p_compression_mode) { case COMPRESSION_MODE_LOSSLESS: case COMPRESSION_MODE_LOSSY: { + bool lossless_force_png = GLOBAL_GET("rendering/textures/lossless_compression/force_png") || + !Image::_webp_mem_loader_func; // WebP module disabled. + bool use_webp = !lossless_force_png && p_image->get_width() <= 16383 && p_image->get_height() <= 16383; // WebP has a size limit. for (int i = 0; i < p_image->get_mipmap_count() + 1; i++) { Vector<uint8_t> data; if (p_compression_mode == COMPRESSION_MODE_LOSSY) { data = Image::webp_lossy_packer(p_image->get_image_from_mipmap(i), p_lossy_quality); + encode_uint16(DATA_FORMAT_WEBP, buffer.ptrw() + 2); } else { - data = Image::webp_lossless_packer(p_image->get_image_from_mipmap(i)); + if (use_webp) { + data = Image::webp_lossless_packer(p_image->get_image_from_mipmap(i)); + encode_uint16(DATA_FORMAT_WEBP, buffer.ptrw() + 2); + } else { + data = Image::png_packer(p_image->get_image_from_mipmap(i)); + encode_uint16(DATA_FORMAT_PNG, buffer.ptrw() + 2); + } } int data_len = data.size(); buffer.resize(buffer.size() + 4); @@ -145,6 +171,7 @@ void PortableCompressedTexture2D::create_from_image(const Ref<Image> &p_image, C } } break; case COMPRESSION_MODE_BASIS_UNIVERSAL: { + encode_uint16(DATA_FORMAT_BASIS_UNIVERSAL, buffer.ptrw() + 2); Image::UsedChannels uc = p_image->detect_used_channels(p_normal_map ? Image::COMPRESS_SOURCE_NORMAL : Image::COMPRESS_SOURCE_GENERIC); Vector<uint8_t> budata = Image::basis_universal_packer(p_image, uc); buffer.append_array(budata); @@ -153,6 +180,7 @@ void PortableCompressedTexture2D::create_from_image(const Ref<Image> &p_image, C case COMPRESSION_MODE_S3TC: case COMPRESSION_MODE_ETC2: case COMPRESSION_MODE_BPTC: { + encode_uint16(DATA_FORMAT_IMAGE, buffer.ptrw() + 2); Ref<Image> copy = p_image->duplicate(); switch (p_compression_mode) { case COMPRESSION_MODE_S3TC: diff --git a/scene/resources/portable_compressed_texture.h b/scene/resources/portable_compressed_texture.h index 86d80e39f7..3103c2daba 100644 --- a/scene/resources/portable_compressed_texture.h +++ b/scene/resources/portable_compressed_texture.h @@ -39,6 +39,14 @@ class PortableCompressedTexture2D : public Texture2D { GDCLASS(PortableCompressedTexture2D, Texture2D); public: + enum DataFormat { + DATA_FORMAT_UNDEFINED, + DATA_FORMAT_IMAGE, + DATA_FORMAT_PNG, + DATA_FORMAT_WEBP, + DATA_FORMAT_BASIS_UNIVERSAL, + }; + enum CompressionMode { COMPRESSION_MODE_LOSSLESS, COMPRESSION_MODE_LOSSY, diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp index 29cd9f648d..a97ff5054d 100644 --- a/scene/resources/resource_format_text.cpp +++ b/scene/resources/resource_format_text.cpp @@ -2289,10 +2289,10 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso } String connstr = "[connection"; - connstr += " signal=\"" + String(state->get_connection_signal(i)) + "\""; - connstr += " from=\"" + String(state->get_connection_source(i).simplified()) + "\""; - connstr += " to=\"" + String(state->get_connection_target(i).simplified()) + "\""; - connstr += " method=\"" + String(state->get_connection_method(i)) + "\""; + connstr += " signal=\"" + String(state->get_connection_signal(i)).c_escape() + "\""; + connstr += " from=\"" + String(state->get_connection_source(i).simplified()).c_escape() + "\""; + connstr += " to=\"" + String(state->get_connection_target(i).simplified()).c_escape() + "\""; + connstr += " method=\"" + String(state->get_connection_method(i)).c_escape() + "\""; int flags = state->get_connection_flags(i); if (flags != Object::CONNECT_PERSIST) { connstr += " flags=" + itos(flags); @@ -2319,7 +2319,7 @@ Error ResourceFormatSaverTextInstance::save(const String &p_path, const Ref<Reso if (i == 0) { f->store_line(""); } - f->store_line("[editable path=\"" + editable_instances[i].operator String() + "\"]"); + f->store_line("[editable path=\"" + editable_instances[i].operator String().c_escape() + "\"]"); } } diff --git a/scene/resources/skeleton_profile.cpp b/scene/resources/skeleton_profile.cpp index da4b1f7311..24ed480289 100644 --- a/scene/resources/skeleton_profile.cpp +++ b/scene/resources/skeleton_profile.cpp @@ -180,7 +180,7 @@ StringName SkeletonProfile::get_root_bone() { return root_bone; } -void SkeletonProfile::set_root_bone(StringName p_bone_name) { +void SkeletonProfile::set_root_bone(const StringName &p_bone_name) { if (is_read_only) { return; } @@ -191,7 +191,7 @@ StringName SkeletonProfile::get_scale_base_bone() { return scale_base_bone; } -void SkeletonProfile::set_scale_base_bone(StringName p_bone_name) { +void SkeletonProfile::set_scale_base_bone(const StringName &p_bone_name) { if (is_read_only) { return; } @@ -217,7 +217,7 @@ StringName SkeletonProfile::get_group_name(int p_group_idx) const { return groups[p_group_idx].group_name; } -void SkeletonProfile::set_group_name(int p_group_idx, const StringName p_group_name) { +void SkeletonProfile::set_group_name(int p_group_idx, const StringName &p_group_name) { if (is_read_only) { return; } @@ -254,7 +254,7 @@ void SkeletonProfile::set_bone_size(int p_size) { notify_property_list_changed(); } -int SkeletonProfile::find_bone(StringName p_bone_name) const { +int SkeletonProfile::find_bone(const StringName &p_bone_name) const { if (p_bone_name == StringName()) { return -1; } @@ -271,7 +271,7 @@ StringName SkeletonProfile::get_bone_name(int p_bone_idx) const { return bones[p_bone_idx].bone_name; } -void SkeletonProfile::set_bone_name(int p_bone_idx, const StringName p_bone_name) { +void SkeletonProfile::set_bone_name(int p_bone_idx, const StringName &p_bone_name) { if (is_read_only) { return; } @@ -285,7 +285,7 @@ StringName SkeletonProfile::get_bone_parent(int p_bone_idx) const { return bones[p_bone_idx].bone_parent; } -void SkeletonProfile::set_bone_parent(int p_bone_idx, const StringName p_bone_parent) { +void SkeletonProfile::set_bone_parent(int p_bone_idx, const StringName &p_bone_parent) { if (is_read_only) { return; } @@ -314,7 +314,7 @@ StringName SkeletonProfile::get_bone_tail(int p_bone_idx) const { return bones[p_bone_idx].bone_tail; } -void SkeletonProfile::set_bone_tail(int p_bone_idx, const StringName p_bone_tail) { +void SkeletonProfile::set_bone_tail(int p_bone_idx, const StringName &p_bone_tail) { if (is_read_only) { return; } @@ -356,7 +356,7 @@ StringName SkeletonProfile::get_group(int p_bone_idx) const { return bones[p_bone_idx].group; } -void SkeletonProfile::set_group(int p_bone_idx, const StringName p_group) { +void SkeletonProfile::set_group(int p_bone_idx, const StringName &p_group) { if (is_read_only) { return; } @@ -379,7 +379,7 @@ void SkeletonProfile::set_require(int p_bone_idx, const bool p_require) { emit_signal("profile_updated"); } -bool SkeletonProfile::has_bone(StringName p_bone_name) { +bool SkeletonProfile::has_bone(const StringName &p_bone_name) { bool is_found = false; for (int i = 0; i < bones.size(); i++) { if (bones[i].bone_name == p_bone_name) { diff --git a/scene/resources/skeleton_profile.h b/scene/resources/skeleton_profile.h index 418a051976..143f495c61 100644 --- a/scene/resources/skeleton_profile.h +++ b/scene/resources/skeleton_profile.h @@ -78,16 +78,16 @@ protected: public: StringName get_root_bone(); - void set_root_bone(StringName p_bone_name); + void set_root_bone(const StringName &p_bone_name); StringName get_scale_base_bone(); - void set_scale_base_bone(StringName p_bone_name); + void set_scale_base_bone(const StringName &p_bone_name); int get_group_size(); void set_group_size(int p_size); StringName get_group_name(int p_group_idx) const; - void set_group_name(int p_group_idx, const StringName p_group_name); + void set_group_name(int p_group_idx, const StringName &p_group_name); Ref<Texture2D> get_texture(int p_group_idx) const; void set_texture(int p_group_idx, const Ref<Texture2D> &p_texture); @@ -95,19 +95,19 @@ public: int get_bone_size(); void set_bone_size(int p_size); - int find_bone(const StringName p_bone_name) const; + int find_bone(const StringName &p_bone_name) const; StringName get_bone_name(int p_bone_idx) const; - void set_bone_name(int p_bone_idx, const StringName p_bone_name); + void set_bone_name(int p_bone_idx, const StringName &p_bone_name); StringName get_bone_parent(int p_bone_idx) const; - void set_bone_parent(int p_bone_idx, const StringName p_bone_parent); + void set_bone_parent(int p_bone_idx, const StringName &p_bone_parent); TailDirection get_tail_direction(int p_bone_idx) const; void set_tail_direction(int p_bone_idx, const TailDirection p_tail_direction); StringName get_bone_tail(int p_bone_idx) const; - void set_bone_tail(int p_bone_idx, const StringName p_bone_tail); + void set_bone_tail(int p_bone_idx, const StringName &p_bone_tail); Transform3D get_reference_pose(int p_bone_idx) const; void set_reference_pose(int p_bone_idx, const Transform3D p_reference_pose); @@ -116,12 +116,12 @@ public: void set_handle_offset(int p_bone_idx, const Vector2 p_handle_offset); StringName get_group(int p_bone_idx) const; - void set_group(int p_bone_idx, const StringName p_group); + void set_group(int p_bone_idx, const StringName &p_group); bool is_require(int p_bone_idx) const; void set_require(int p_bone_idx, const bool p_require); - bool has_bone(StringName p_bone_name); + bool has_bone(const StringName &p_bone_name); SkeletonProfile(); ~SkeletonProfile(); diff --git a/scene/resources/sprite_frames.cpp b/scene/resources/sprite_frames.cpp index 17aaf579dd..fc8bff1f25 100644 --- a/scene/resources/sprite_frames.cpp +++ b/scene/resources/sprite_frames.cpp @@ -224,6 +224,23 @@ void SpriteFrames::_set_animations(const Array &p_animations) { } } +void SpriteFrames::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { + String pf = p_function; + if (p_idx == 0) { + if (pf == "has_animation" || pf == "remove_animation" || pf == "rename_animation" || + pf == "set_animation_speed" || pf == "get_animation_speed" || + pf == "set_animation_loop" || pf == "get_animation_loop" || + pf == "add_frame" || pf == "set_frame" || pf == "remove_frame" || + pf == "get_frame_count" || pf == "get_frame_texture" || pf == "get_frame_duration" || + pf == "clear") { + for (const String &E : get_animation_names()) { + r_options->push_back(E.quote()); + } + } + } + Resource::get_argument_options(p_function, p_idx, r_options); +} + void SpriteFrames::_bind_methods() { ClassDB::bind_method(D_METHOD("add_animation", "anim"), &SpriteFrames::add_animation); ClassDB::bind_method(D_METHOD("has_animation", "anim"), &SpriteFrames::has_animation); diff --git a/scene/resources/sprite_frames.h b/scene/resources/sprite_frames.h index 6de2b4a37b..dc980f8321 100644 --- a/scene/resources/sprite_frames.h +++ b/scene/resources/sprite_frames.h @@ -103,6 +103,8 @@ public: void clear(const StringName &p_anim); void clear_all(); + virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override; + SpriteFrames(); }; diff --git a/scene/resources/theme.cpp b/scene/resources/theme.cpp index d57a0f6b38..e5a4b7c6e6 100644 --- a/scene/resources/theme.cpp +++ b/scene/resources/theme.cpp @@ -322,7 +322,7 @@ void Theme::clear_icon(const StringName &p_name, const StringName &p_theme_type) _emit_theme_changed(true); } -void Theme::get_icon_list(StringName p_theme_type, List<StringName> *p_list) const { +void Theme::get_icon_list(const StringName &p_theme_type, List<StringName> *p_list) const { ERR_FAIL_NULL(p_list); if (!icon_map.has(p_theme_type)) { @@ -432,7 +432,7 @@ void Theme::clear_stylebox(const StringName &p_name, const StringName &p_theme_t _emit_theme_changed(true); } -void Theme::get_stylebox_list(StringName p_theme_type, List<StringName> *p_list) const { +void Theme::get_stylebox_list(const StringName &p_theme_type, List<StringName> *p_list) const { ERR_FAIL_NULL(p_list); if (!style_map.has(p_theme_type)) { @@ -544,7 +544,7 @@ void Theme::clear_font(const StringName &p_name, const StringName &p_theme_type) _emit_theme_changed(true); } -void Theme::get_font_list(StringName p_theme_type, List<StringName> *p_list) const { +void Theme::get_font_list(const StringName &p_theme_type, List<StringName> *p_list) const { ERR_FAIL_NULL(p_list); if (!font_map.has(p_theme_type)) { @@ -643,7 +643,7 @@ void Theme::clear_font_size(const StringName &p_name, const StringName &p_theme_ _emit_theme_changed(true); } -void Theme::get_font_size_list(StringName p_theme_type, List<StringName> *p_list) const { +void Theme::get_font_size_list(const StringName &p_theme_type, List<StringName> *p_list) const { ERR_FAIL_NULL(p_list); if (!font_size_map.has(p_theme_type)) { @@ -729,7 +729,7 @@ void Theme::clear_color(const StringName &p_name, const StringName &p_theme_type _emit_theme_changed(true); } -void Theme::get_color_list(StringName p_theme_type, List<StringName> *p_list) const { +void Theme::get_color_list(const StringName &p_theme_type, List<StringName> *p_list) const { ERR_FAIL_NULL(p_list); if (!color_map.has(p_theme_type)) { @@ -815,7 +815,7 @@ void Theme::clear_constant(const StringName &p_name, const StringName &p_theme_t _emit_theme_changed(true); } -void Theme::get_constant_list(StringName p_theme_type, List<StringName> *p_list) const { +void Theme::get_constant_list(const StringName &p_theme_type, List<StringName> *p_list) const { ERR_FAIL_NULL(p_list); if (!constant_map.has(p_theme_type)) { @@ -1009,7 +1009,7 @@ void Theme::clear_theme_item(DataType p_data_type, const StringName &p_name, con } } -void Theme::get_theme_item_list(DataType p_data_type, StringName p_theme_type, List<StringName> *p_list) const { +void Theme::get_theme_item_list(DataType p_data_type, const StringName &p_theme_type, List<StringName> *p_list) const { switch (p_data_type) { case DATA_TYPE_COLOR: get_color_list(p_theme_type, p_list); diff --git a/scene/resources/theme.h b/scene/resources/theme.h index b26b5b5e84..73f1167c29 100644 --- a/scene/resources/theme.h +++ b/scene/resources/theme.h @@ -136,7 +136,7 @@ public: bool has_icon_nocheck(const StringName &p_name, const StringName &p_theme_type) const; void rename_icon(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type); void clear_icon(const StringName &p_name, const StringName &p_theme_type); - void get_icon_list(StringName p_theme_type, List<StringName> *p_list) const; + void get_icon_list(const StringName &p_theme_type, List<StringName> *p_list) const; void add_icon_type(const StringName &p_theme_type); void remove_icon_type(const StringName &p_theme_type); void get_icon_type_list(List<StringName> *p_list) const; @@ -147,7 +147,7 @@ public: bool has_stylebox_nocheck(const StringName &p_name, const StringName &p_theme_type) const; void rename_stylebox(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type); void clear_stylebox(const StringName &p_name, const StringName &p_theme_type); - void get_stylebox_list(StringName p_theme_type, List<StringName> *p_list) const; + void get_stylebox_list(const StringName &p_theme_type, List<StringName> *p_list) const; void add_stylebox_type(const StringName &p_theme_type); void remove_stylebox_type(const StringName &p_theme_type); void get_stylebox_type_list(List<StringName> *p_list) const; @@ -158,7 +158,7 @@ public: bool has_font_nocheck(const StringName &p_name, const StringName &p_theme_type) const; void rename_font(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type); void clear_font(const StringName &p_name, const StringName &p_theme_type); - void get_font_list(StringName p_theme_type, List<StringName> *p_list) const; + void get_font_list(const StringName &p_theme_type, List<StringName> *p_list) const; void add_font_type(const StringName &p_theme_type); void remove_font_type(const StringName &p_theme_type); void get_font_type_list(List<StringName> *p_list) const; @@ -169,7 +169,7 @@ public: bool has_font_size_nocheck(const StringName &p_name, const StringName &p_theme_type) const; void rename_font_size(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type); void clear_font_size(const StringName &p_name, const StringName &p_theme_type); - void get_font_size_list(StringName p_theme_type, List<StringName> *p_list) const; + void get_font_size_list(const StringName &p_theme_type, List<StringName> *p_list) const; void add_font_size_type(const StringName &p_theme_type); void remove_font_size_type(const StringName &p_theme_type); void get_font_size_type_list(List<StringName> *p_list) const; @@ -180,7 +180,7 @@ public: bool has_color_nocheck(const StringName &p_name, const StringName &p_theme_type) const; void rename_color(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type); void clear_color(const StringName &p_name, const StringName &p_theme_type); - void get_color_list(StringName p_theme_type, List<StringName> *p_list) const; + void get_color_list(const StringName &p_theme_type, List<StringName> *p_list) const; void add_color_type(const StringName &p_theme_type); void remove_color_type(const StringName &p_theme_type); void get_color_type_list(List<StringName> *p_list) const; @@ -191,7 +191,7 @@ public: bool has_constant_nocheck(const StringName &p_name, const StringName &p_theme_type) const; void rename_constant(const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type); void clear_constant(const StringName &p_name, const StringName &p_theme_type); - void get_constant_list(StringName p_theme_type, List<StringName> *p_list) const; + void get_constant_list(const StringName &p_theme_type, List<StringName> *p_list) const; void add_constant_type(const StringName &p_theme_type); void remove_constant_type(const StringName &p_theme_type); void get_constant_type_list(List<StringName> *p_list) const; @@ -202,7 +202,7 @@ public: bool has_theme_item_nocheck(DataType p_data_type, const StringName &p_name, const StringName &p_theme_type) const; void rename_theme_item(DataType p_data_type, const StringName &p_old_name, const StringName &p_name, const StringName &p_theme_type); void clear_theme_item(DataType p_data_type, const StringName &p_name, const StringName &p_theme_type); - void get_theme_item_list(DataType p_data_type, StringName p_theme_type, List<StringName> *p_list) const; + void get_theme_item_list(DataType p_data_type, const StringName &p_theme_type, List<StringName> *p_list) const; void add_theme_item_type(DataType p_data_type, const StringName &p_theme_type); void remove_theme_item_type(DataType p_data_type, const StringName &p_theme_type); void get_theme_item_type_list(DataType p_data_type, List<StringName> *p_list) const; diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 9fb6b364a7..8ff5b54fbe 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -3139,7 +3139,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::preview_ports[] = { { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv2", "UV" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "vec4(1.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" }, - { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, + { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "UV" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "viewport_size", "vec2(1.0)" }, { Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, @@ -3166,7 +3166,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::preview_ports[] = { { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "uv", "UV" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "vec3(1.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "UV" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, // Canvas Item, Light @@ -3176,7 +3176,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::preview_ports[] = { { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_3D, "normal", "vec3(0.0, 0.0, 1.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_4D, "color", "vec4(1.0)" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "1.0" }, - { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, + { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "UV" }, { Shader::MODE_CANVAS_ITEM, VisualShader::TYPE_LIGHT, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, // Particles @@ -3189,7 +3189,7 @@ const VisualShaderNodeInput::Port VisualShaderNodeInput::preview_ports[] = { // Sky - { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "SCREEN_UV" }, + { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_VECTOR_2D, "screen_uv", "UV" }, { Shader::MODE_SKY, VisualShader::TYPE_SKY, VisualShaderNode::PORT_TYPE_SCALAR, "time", "TIME" }, // Fog diff --git a/scene/theme/theme_owner.cpp b/scene/theme/theme_owner.cpp index 2852a64e39..b559c8c58d 100644 --- a/scene/theme/theme_owner.cpp +++ b/scene/theme/theme_owner.cpp @@ -249,7 +249,7 @@ void ThemeOwner::get_theme_type_dependencies(const Node *p_for_node, const Strin ThemeDB::get_singleton()->get_native_type_dependencies(p_theme_type, r_list); } -Variant ThemeOwner::get_theme_item_in_types(Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types) { +Variant ThemeOwner::get_theme_item_in_types(Theme::DataType p_data_type, const StringName &p_name, const List<StringName> &p_theme_types) { ERR_FAIL_COND_V_MSG(p_theme_types.size() == 0, Variant(), "At least one theme type must be specified."); // First, look through each control or window node in the branch, until no valid parent can be found. @@ -285,7 +285,7 @@ Variant ThemeOwner::get_theme_item_in_types(Theme::DataType p_data_type, const S return global_context->get_fallback_theme()->get_theme_item(p_data_type, p_name, StringName()); } -bool ThemeOwner::has_theme_item_in_types(Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types) { +bool ThemeOwner::has_theme_item_in_types(Theme::DataType p_data_type, const StringName &p_name, const List<StringName> &p_theme_types) { ERR_FAIL_COND_V_MSG(p_theme_types.size() == 0, false, "At least one theme type must be specified."); // First, look through each control or window node in the branch, until no valid parent can be found. diff --git a/scene/theme/theme_owner.h b/scene/theme/theme_owner.h index 4923ccb00b..7e19279c2a 100644 --- a/scene/theme/theme_owner.h +++ b/scene/theme/theme_owner.h @@ -71,8 +71,8 @@ public: void get_theme_type_dependencies(const Node *p_for_node, const StringName &p_theme_type, List<StringName> *r_list) const; - Variant get_theme_item_in_types(Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types); - bool has_theme_item_in_types(Theme::DataType p_data_type, const StringName &p_name, List<StringName> p_theme_types); + Variant get_theme_item_in_types(Theme::DataType p_data_type, const StringName &p_name, const List<StringName> &p_theme_types); + bool has_theme_item_in_types(Theme::DataType p_data_type, const StringName &p_name, const List<StringName> &p_theme_types); float get_theme_default_base_scale(); Ref<Font> get_theme_default_font(); diff --git a/scu_builders.py b/scu_builders.py index 71427eb717..56effb393c 100644 --- a/scu_builders.py +++ b/scu_builders.py @@ -277,6 +277,7 @@ def generate_scu_files(max_includes_per_scu): process_folder(["editor/export"]) process_folder(["editor/gui"]) process_folder(["editor/import"]) + process_folder(["editor/import/3d"]) process_folder(["editor/plugins"]) process_folder(["editor/plugins/gizmos"]) process_folder(["editor/plugins/tiles"]) diff --git a/servers/audio_server.cpp b/servers/audio_server.cpp index 026bc6675a..86bdd13c80 100644 --- a/servers/audio_server.cpp +++ b/servers/audio_server.cpp @@ -1120,7 +1120,7 @@ float AudioServer::get_playback_speed_scale() const { return playback_speed_scale; } -void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, StringName p_bus, Vector<AudioFrame> p_volume_db_vector, float p_start_time, float p_pitch_scale) { +void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, const StringName &p_bus, Vector<AudioFrame> p_volume_db_vector, float p_start_time, float p_pitch_scale) { ERR_FAIL_COND(p_playback.is_null()); HashMap<StringName, Vector<AudioFrame>> map; @@ -1129,7 +1129,7 @@ void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, Str start_playback_stream(p_playback, map, p_start_time, p_pitch_scale); } -void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, HashMap<StringName, Vector<AudioFrame>> p_bus_volumes, float p_start_time, float p_pitch_scale, float p_highshelf_gain, float p_attenuation_cutoff_hz) { +void AudioServer::start_playback_stream(Ref<AudioStreamPlayback> p_playback, const HashMap<StringName, Vector<AudioFrame>> &p_bus_volumes, float p_start_time, float p_pitch_scale, float p_highshelf_gain, float p_attenuation_cutoff_hz) { ERR_FAIL_COND(p_playback.is_null()); AudioStreamPlaybackListNode *playback_node = new AudioStreamPlaybackListNode(); @@ -1188,7 +1188,7 @@ void AudioServer::stop_playback_stream(Ref<AudioStreamPlayback> p_playback) { } while (!playback_node->state.compare_exchange_strong(old_state, new_state)); } -void AudioServer::set_playback_bus_exclusive(Ref<AudioStreamPlayback> p_playback, StringName p_bus, Vector<AudioFrame> p_volumes) { +void AudioServer::set_playback_bus_exclusive(Ref<AudioStreamPlayback> p_playback, const StringName &p_bus, Vector<AudioFrame> p_volumes) { ERR_FAIL_COND(p_volumes.size() != MAX_CHANNELS_PER_BUS); HashMap<StringName, Vector<AudioFrame>> map; @@ -1197,7 +1197,7 @@ void AudioServer::set_playback_bus_exclusive(Ref<AudioStreamPlayback> p_playback set_playback_bus_volumes_linear(p_playback, map); } -void AudioServer::set_playback_bus_volumes_linear(Ref<AudioStreamPlayback> p_playback, HashMap<StringName, Vector<AudioFrame>> p_bus_volumes) { +void AudioServer::set_playback_bus_volumes_linear(Ref<AudioStreamPlayback> p_playback, const HashMap<StringName, Vector<AudioFrame>> &p_bus_volumes) { ERR_FAIL_COND(p_bus_volumes.size() > MAX_BUSES_PER_PLAYBACK); AudioStreamPlaybackListNode *playback_node = _find_playback_list_node(p_playback); diff --git a/servers/audio_server.h b/servers/audio_server.h index 9ffa95bc00..4606299c47 100644 --- a/servers/audio_server.h +++ b/servers/audio_server.h @@ -372,13 +372,13 @@ public: float get_playback_speed_scale() const; // Convenience method. - void start_playback_stream(Ref<AudioStreamPlayback> p_playback, StringName p_bus, Vector<AudioFrame> p_volume_db_vector, float p_start_time = 0, float p_pitch_scale = 1); + void start_playback_stream(Ref<AudioStreamPlayback> p_playback, const StringName &p_bus, Vector<AudioFrame> p_volume_db_vector, float p_start_time = 0, float p_pitch_scale = 1); // Expose all parameters. - void start_playback_stream(Ref<AudioStreamPlayback> p_playback, HashMap<StringName, Vector<AudioFrame>> p_bus_volumes, float p_start_time = 0, float p_pitch_scale = 1, float p_highshelf_gain = 0, float p_attenuation_cutoff_hz = 0); + void start_playback_stream(Ref<AudioStreamPlayback> p_playback, const HashMap<StringName, Vector<AudioFrame>> &p_bus_volumes, float p_start_time = 0, float p_pitch_scale = 1, float p_highshelf_gain = 0, float p_attenuation_cutoff_hz = 0); void stop_playback_stream(Ref<AudioStreamPlayback> p_playback); - void set_playback_bus_exclusive(Ref<AudioStreamPlayback> p_playback, StringName p_bus, Vector<AudioFrame> p_volumes); - void set_playback_bus_volumes_linear(Ref<AudioStreamPlayback> p_playback, HashMap<StringName, Vector<AudioFrame>> p_bus_volumes); + void set_playback_bus_exclusive(Ref<AudioStreamPlayback> p_playback, const StringName &p_bus, Vector<AudioFrame> p_volumes); + void set_playback_bus_volumes_linear(Ref<AudioStreamPlayback> p_playback, const HashMap<StringName, Vector<AudioFrame>> &p_bus_volumes); void set_playback_all_bus_volumes_linear(Ref<AudioStreamPlayback> p_playback, Vector<AudioFrame> p_volumes); void set_playback_pitch_scale(Ref<AudioStreamPlayback> p_playback, float p_pitch_scale); void set_playback_paused(Ref<AudioStreamPlayback> p_playback, bool p_paused); diff --git a/servers/rendering/renderer_rd/shader_rd.cpp b/servers/rendering/renderer_rd/shader_rd.cpp index 2873269586..db2ca29474 100644 --- a/servers/rendering/renderer_rd/shader_rd.cpp +++ b/servers/rendering/renderer_rd/shader_rd.cpp @@ -45,7 +45,7 @@ void ShaderRD::_add_stage(const char *p_code, StageType p_stage_type) { String text; for (int i = 0; i < lines.size(); i++) { - String l = lines[i]; + const String &l = lines[i]; bool push_chunk = false; StageTemplate::Chunk chunk; diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp index 876bdf5c71..01ee4f3c01 100644 --- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp +++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp @@ -657,7 +657,8 @@ AABB MeshStorage::mesh_get_aabb(RID p_mesh, RID p_skeleton) { Skeleton *skeleton = skeleton_owner.get_or_null(p_skeleton); - if (!skeleton || skeleton->size == 0 || mesh->skeleton_aabb_version == skeleton->version) { + // A mesh can be shared by multiple skeletons and we need to avoid using the AABB from a different skeleton. + if (!skeleton || skeleton->size == 0 || (mesh->skeleton_aabb_version == skeleton->version && mesh->skeleton_aabb_rid == p_skeleton)) { return mesh->aabb; } @@ -763,6 +764,7 @@ AABB MeshStorage::mesh_get_aabb(RID p_mesh, RID p_skeleton) { mesh->aabb = aabb; mesh->skeleton_aabb_version = skeleton->version; + mesh->skeleton_aabb_rid = p_skeleton; return aabb; } diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h index 0fc1a6f320..a1e2ffcf7e 100644 --- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h +++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h @@ -153,6 +153,7 @@ private: AABB aabb; AABB custom_aabb; uint64_t skeleton_aabb_version = 0; + RID skeleton_aabb_rid; Vector<RID> material_cache; diff --git a/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp b/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp index 6a99eb4108..e77a2e9567 100644 --- a/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp +++ b/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.cpp @@ -330,7 +330,7 @@ RID RenderSceneBuffersRD::create_texture_from_format(const StringName &p_context return named_texture.texture; } -RID RenderSceneBuffersRD::_create_texture_view(const StringName &p_context, const StringName &p_texture_name, const StringName p_view_name, const Ref<RDTextureView> p_view) { +RID RenderSceneBuffersRD::_create_texture_view(const StringName &p_context, const StringName &p_texture_name, const StringName &p_view_name, const Ref<RDTextureView> p_view) { RD::TextureView texture_view; if (p_view.is_valid()) { // only use when supplied, else default. texture_view = p_view->base; @@ -339,7 +339,7 @@ RID RenderSceneBuffersRD::_create_texture_view(const StringName &p_context, cons return create_texture_view(p_context, p_texture_name, p_view_name, texture_view); } -RID RenderSceneBuffersRD::create_texture_view(const StringName &p_context, const StringName &p_texture_name, const StringName p_view_name, RD::TextureView p_view) { +RID RenderSceneBuffersRD::create_texture_view(const StringName &p_context, const StringName &p_texture_name, const StringName &p_view_name, RD::TextureView p_view) { NTKey view_key(p_context, p_view_name); // check if this is a known texture diff --git a/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.h b/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.h index b2946e6bbc..5b8a74de83 100644 --- a/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.h +++ b/servers/rendering/renderer_rd/storage_rd/render_scene_buffers_rd.h @@ -106,7 +106,7 @@ private: } NTKey() {} - NTKey(const StringName p_context, const StringName p_texture_name) { + NTKey(const StringName &p_context, const StringName &p_texture_name) { context = p_context; buffer_name = p_texture_name; } @@ -196,7 +196,7 @@ public: bool has_texture(const StringName &p_context, const StringName &p_texture_name) const; RID create_texture(const StringName &p_context, const StringName &p_texture_name, const RD::DataFormat p_data_format, const uint32_t p_usage_bits, const RD::TextureSamples p_texture_samples = RD::TEXTURE_SAMPLES_1, const Size2i p_size = Size2i(0, 0), const uint32_t p_layers = 0, const uint32_t p_mipmaps = 1, bool p_unique = true); RID create_texture_from_format(const StringName &p_context, const StringName &p_texture_name, const RD::TextureFormat &p_texture_format, RD::TextureView p_view = RD::TextureView(), bool p_unique = true); - RID create_texture_view(const StringName &p_context, const StringName &p_texture_name, const StringName p_view_name, RD::TextureView p_view = RD::TextureView()); + RID create_texture_view(const StringName &p_context, const StringName &p_texture_name, const StringName &p_view_name, RD::TextureView p_view = RD::TextureView()); RID get_texture(const StringName &p_context, const StringName &p_texture_name) const; const RD::TextureFormat get_texture_format(const StringName &p_context, const StringName &p_texture_name) const; RID get_texture_slice(const StringName &p_context, const StringName &p_texture_name, const uint32_t p_layer, const uint32_t p_mipmap, const uint32_t p_layers = 1, const uint32_t p_mipmaps = 1); @@ -310,7 +310,7 @@ public: private: RID _create_texture_from_format(const StringName &p_context, const StringName &p_texture_name, const Ref<RDTextureFormat> &p_texture_format, const Ref<RDTextureView> &p_view = Ref<RDTextureView>(), bool p_unique = true); - RID _create_texture_view(const StringName &p_context, const StringName &p_texture_name, const StringName p_view_name, const Ref<RDTextureView> p_view = Ref<RDTextureView>()); + RID _create_texture_view(const StringName &p_context, const StringName &p_texture_name, const StringName &p_view_name, const Ref<RDTextureView> p_view = Ref<RDTextureView>()); Ref<RDTextureFormat> _get_texture_format(const StringName &p_context, const StringName &p_texture_name) const; RID _get_texture_slice_view(const StringName &p_context, const StringName &p_texture_name, const uint32_t p_layer, const uint32_t p_mipmap, const uint32_t p_layers = 1, const uint32_t p_mipmaps = 1, const Ref<RDTextureView> p_view = Ref<RDTextureView>()); diff --git a/servers/rendering/rendering_device_driver.h b/servers/rendering/rendering_device_driver.h index bb71a29bbc..3fe3c8ac5e 100644 --- a/servers/rendering/rendering_device_driver.h +++ b/servers/rendering/rendering_device_driver.h @@ -535,6 +535,8 @@ public: float depth; uint32_t stencil; }; + + RenderPassClearValue() {} }; struct AttachmentClear { diff --git a/servers/rendering/shader_compiler.cpp b/servers/rendering/shader_compiler.cpp index 880c13096e..6875400d1e 100644 --- a/servers/rendering/shader_compiler.cpp +++ b/servers/rendering/shader_compiler.cpp @@ -543,7 +543,7 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene uniform_names.sort_custom<StringName::AlphCompare>(); //ensure order is deterministic so the same shader is always produced for (int k = 0; k < uniform_names.size(); k++) { - StringName uniform_name = uniform_names[k]; + const StringName &uniform_name = uniform_names[k]; const SL::ShaderNode::Uniform &uniform = pnode->uniforms[uniform_name]; String ucode; @@ -670,7 +670,7 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene varying_names.sort_custom<StringName::AlphCompare>(); //ensure order is deterministic so the same shader is always produced for (int k = 0; k < varying_names.size(); k++) { - StringName varying_name = varying_names[k]; + const StringName &varying_name = varying_names[k]; const SL::ShaderNode::Varying &varying = pnode->varyings[varying_name]; if (varying.stage == SL::ShaderNode::Varying::STAGE_FRAGMENT_TO_LIGHT || varying.stage == SL::ShaderNode::Varying::STAGE_FRAGMENT) { diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp index a2da26eb65..fef1a205d6 100644 --- a/servers/rendering/shader_language.cpp +++ b/servers/rendering/shader_language.cpp @@ -4665,7 +4665,7 @@ bool ShaderLanguage::_validate_assign(Node *p_node, const FunctionInfo &p_functi return false; } -bool ShaderLanguage::_propagate_function_call_sampler_uniform_settings(StringName p_name, int p_argument, TextureFilter p_filter, TextureRepeat p_repeat) { +bool ShaderLanguage::_propagate_function_call_sampler_uniform_settings(const StringName &p_name, int p_argument, TextureFilter p_filter, TextureRepeat p_repeat) { for (int i = 0; i < shader->vfunctions.size(); i++) { if (shader->vfunctions[i].name == p_name) { ERR_FAIL_INDEX_V(p_argument, shader->vfunctions[i].function->arguments.size(), false); @@ -4699,7 +4699,7 @@ bool ShaderLanguage::_propagate_function_call_sampler_uniform_settings(StringNam ERR_FAIL_V(false); //bug? function not found } -bool ShaderLanguage::_propagate_function_call_sampler_builtin_reference(StringName p_name, int p_argument, const StringName &p_builtin) { +bool ShaderLanguage::_propagate_function_call_sampler_builtin_reference(const StringName &p_name, int p_argument, const StringName &p_builtin) { for (int i = 0; i < shader->vfunctions.size(); i++) { if (shader->vfunctions[i].name == p_name) { ERR_FAIL_INDEX_V(p_argument, shader->vfunctions[i].function->arguments.size(), false); diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h index c707f6aad7..737545b8ca 100644 --- a/servers/rendering/shader_language.h +++ b/servers/rendering/shader_language.h @@ -1092,8 +1092,8 @@ private: bool _validate_function_call(BlockNode *p_block, const FunctionInfo &p_function_info, OperatorNode *p_func, DataType *r_ret_type, StringName *r_ret_type_str, bool *r_is_custom_function = nullptr); bool _parse_function_arguments(BlockNode *p_block, const FunctionInfo &p_function_info, OperatorNode *p_func, int *r_complete_arg = nullptr); - bool _propagate_function_call_sampler_uniform_settings(StringName p_name, int p_argument, TextureFilter p_filter, TextureRepeat p_repeat); - bool _propagate_function_call_sampler_builtin_reference(StringName p_name, int p_argument, const StringName &p_builtin); + bool _propagate_function_call_sampler_uniform_settings(const StringName &p_name, int p_argument, TextureFilter p_filter, TextureRepeat p_repeat); + bool _propagate_function_call_sampler_builtin_reference(const StringName &p_name, int p_argument, const StringName &p_builtin); bool _validate_varying_assign(ShaderNode::Varying &p_varying, String *r_message); bool _check_node_constness(const Node *p_node) const; diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index c834a6eb44..00015b74a1 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -103,7 +103,7 @@ PackedInt64Array RenderingServer::_instances_cull_convex_bind(const TypedArray<P } Vector<Plane> planes; for (int i = 0; i < p_convex.size(); ++i) { - Variant v = p_convex[i]; + const Variant &v = p_convex[i]; ERR_FAIL_COND_V(v.get_type() != Variant::PLANE, PackedInt64Array()); planes.push_back(v); } diff --git a/tests/scene/test_curve_2d.h b/tests/scene/test_curve_2d.h index fc141f3d09..099f6fefa9 100644 --- a/tests/scene/test_curve_2d.h +++ b/tests/scene/test_curve_2d.h @@ -155,17 +155,37 @@ TEST_CASE("[Curve2D] Sampling") { SUBCASE("sample_baked_with_rotation") { const real_t pi = 3.14159; - Transform2D t = curve->sample_baked_with_rotation(curve->get_closest_offset(Vector2(0, 0))); - CHECK(t.get_origin() == Vector2(0, 0)); - CHECK(Math::is_equal_approx(t.get_rotation(), pi)); - - t = curve->sample_baked_with_rotation(curve->get_closest_offset(Vector2(0, 25))); + const real_t half_pi = pi * 0.5; + Ref<Curve2D> rot_curve = memnew(Curve2D); + Transform2D t; + + rot_curve->clear_points(); + rot_curve->add_point(Vector2()); + rot_curve->add_point(Vector2(50, 0)); + t = rot_curve->sample_baked_with_rotation(25); + CHECK(t.get_origin() == Vector2(25, 0)); + CHECK(Math::is_equal_approx(t.get_rotation(), 0)); + + rot_curve->clear_points(); + rot_curve->add_point(Vector2()); + rot_curve->add_point(Vector2(0, 50)); + t = rot_curve->sample_baked_with_rotation(25); CHECK(t.get_origin() == Vector2(0, 25)); - CHECK(Math::is_equal_approx(t.get_rotation(), pi)); + CHECK(Math::is_equal_approx(t.get_rotation(), half_pi)); - t = curve->sample_baked_with_rotation(curve->get_closest_offset(Vector2(0, 50))); - CHECK(t.get_origin() == Vector2(0, 50)); + rot_curve->clear_points(); + rot_curve->add_point(Vector2()); + rot_curve->add_point(Vector2(-50, 0)); + t = rot_curve->sample_baked_with_rotation(25); + CHECK(t.get_origin() == Vector2(-25, 0)); CHECK(Math::is_equal_approx(t.get_rotation(), pi)); + + rot_curve->clear_points(); + rot_curve->add_point(Vector2()); + rot_curve->add_point(Vector2(0, -50)); + t = rot_curve->sample_baked_with_rotation(25); + CHECK(t.get_origin() == Vector2(0, -25)); + CHECK(Math::is_equal_approx(t.get_rotation(), -half_pi)); } SUBCASE("get_closest_point") { diff --git a/tests/scene/test_packed_scene.h b/tests/scene/test_packed_scene.h index 3517aba31f..1e784c199d 100644 --- a/tests/scene/test_packed_scene.h +++ b/tests/scene/test_packed_scene.h @@ -150,6 +150,71 @@ TEST_CASE("[PackedScene] Instantiate Packed Scene With Children") { memdelete(instance); } +TEST_CASE("[PackedScene] Set Path") { + // Create a scene to pack. + Node *scene = memnew(Node); + scene->set_name("TestScene"); + + // Pack the scene. + PackedScene packed_scene; + packed_scene.pack(scene); + + // Set a new path for the packed scene. + const String new_path = "NewTestPath"; + packed_scene.set_path(new_path); + + // Check if the path has been set correctly. + Ref<SceneState> state = packed_scene.get_state(); + CHECK(state.is_valid()); + CHECK(state->get_path() == new_path); + + memdelete(scene); +} + +TEST_CASE("[PackedScene] Replace State") { + // Create a scene to pack. + Node *scene = memnew(Node); + scene->set_name("TestScene"); + + // Pack the scene. + PackedScene packed_scene; + packed_scene.pack(scene); + + // Create another scene state to replace with. + Ref<SceneState> new_state = memnew(SceneState); + new_state->set_path("NewPath"); + + // Replace the state. + packed_scene.replace_state(new_state); + + // Check if the state has been replaced. + Ref<SceneState> state = packed_scene.get_state(); + CHECK(state.is_valid()); + CHECK(state == new_state); + + memdelete(scene); +} + +TEST_CASE("[PackedScene] Recreate State") { + // Create a scene to pack. + Node *scene = memnew(Node); + scene->set_name("TestScene"); + + // Pack the scene. + PackedScene packed_scene; + packed_scene.pack(scene); + + // Recreate the state. + packed_scene.recreate_state(); + + // Check if the state has been recreated. + Ref<SceneState> state = packed_scene.get_state(); + CHECK(state.is_valid()); + CHECK(state->get_node_count() == 0); // Since the state was recreated, it should be empty. + + memdelete(scene); +} + } // namespace TestPackedScene #endif // TEST_PACKED_SCENE_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 5187ebd00f..9e020b5d93 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -30,6 +30,8 @@ #include "test_main.h" +#include "editor/editor_paths.h" +#include "editor/editor_settings.h" #include "tests/core/config/test_project_settings.h" #include "tests/core/input/test_input_event.h" #include "tests/core/input/test_input_event_key.h" @@ -221,7 +223,7 @@ struct GodotTestCaseListener : public doctest::IReporter { String name = String(p_in.m_name); String suite_name = String(p_in.m_test_suite); - if (name.find("[SceneTree]") != -1) { + if (name.find("[SceneTree]") != -1 || name.find("[Editor]") != -1) { memnew(MessageQueue); memnew(Input); @@ -264,6 +266,13 @@ struct GodotTestCaseListener : public doctest::IReporter { if (!DisplayServer::get_singleton()->has_feature(DisplayServer::Feature::FEATURE_SUBWINDOWS)) { SceneTree::get_singleton()->get_root()->set_embedding_subwindows(true); } + + if (name.find("[Editor]") != -1) { + Engine::get_singleton()->set_editor_hint(true); + EditorPaths::create(); + EditorSettings::create(); + } + return; } @@ -286,6 +295,12 @@ struct GodotTestCaseListener : public doctest::IReporter { } void test_case_end(const doctest::CurrentTestCaseStats &) override { + if (EditorSettings::get_singleton()) { + EditorSettings::destroy(); + } + + Engine::get_singleton()->set_editor_hint(false); + if (SceneTree::get_singleton()) { SceneTree::get_singleton()->finalize(); } |